/* The order of words in an LLI. */ #define PL080_LLI_SRC 0 #define PL080_LLI_DST 1 #define PL080_LLI_LLI 2 #define PL080_LLI_CCTL 3 #define PL080S_LLI_CCTL2 4
/* Total words in an LLI. */ #define PL080_LLI_WORDS 4 #define PL080S_LLI_WORDS 8
/* Whether a certain channel is busy or not */ staticint pl08x_phy_channel_busy(struct pl08x_phy_chan *ch)
{ unsignedint val;
/* If we have a special busy register, take a shortcut */ if (ch->reg_busy) {
val = readl(ch->reg_busy); return !!(val & BIT(ch->id));
}
val = readl(ch->reg_config); return val & PL080_CONFIG_ACTIVE;
}
/* *TheFTMAC020hasadifferentlayoutintheCCTLwordoftheLLI *andtheCCTLregisterwhichissplitinCSRandSIZEregisters. *ConverttheLLIitemCCTLintothepropervaluestowriteinto *theCSRandSIZEregisters.
*/ if (phychan->ftdmac020) {
u32 llictl = lli[PL080_LLI_CCTL];
u32 val = 0;
/* Write the transfer size (12 bits) to the size register */
writel_relaxed(llictl & FTDMAC020_LLI_TRANSFER_SIZE_MASK,
phychan->base + FTDMAC020_CH_SIZE); /* *Thenwritethecontrolbits28..16tothecontrolregister *byshuffleingthebitsaroundtowheretheyareinthe *mainregister.Themappingisasfollows: *Bit28:TC_MSK-maskonallexceptlastLLI *Bit27..25:SRC_WIDTH *Bit24..22:DST_WIDTH *Bit21..20:SRCAD_CTRL *Bit19..17:DSTAD_CTRL *Bit17:SRC_SEL *Bit16:DST_SEL
*/ if (llictl & FTDMAC020_LLI_TC_MSK)
val |= FTDMAC020_CH_CSR_TC_MSK;
val |= ((llictl & FTDMAC020_LLI_SRC_WIDTH_MSK) >>
(FTDMAC020_LLI_SRC_WIDTH_SHIFT -
FTDMAC020_CH_CSR_SRC_WIDTH_SHIFT));
val |= ((llictl & FTDMAC020_LLI_DST_WIDTH_MSK) >>
(FTDMAC020_LLI_DST_WIDTH_SHIFT -
FTDMAC020_CH_CSR_DST_WIDTH_SHIFT));
val |= ((llictl & FTDMAC020_LLI_SRCAD_CTL_MSK) >>
(FTDMAC020_LLI_SRCAD_CTL_SHIFT -
FTDMAC020_CH_CSR_SRCAD_CTL_SHIFT));
val |= ((llictl & FTDMAC020_LLI_DSTAD_CTL_MSK) >>
(FTDMAC020_LLI_DSTAD_CTL_SHIFT -
FTDMAC020_CH_CSR_DSTAD_CTL_SHIFT)); if (llictl & FTDMAC020_LLI_SRC_SEL)
val |= FTDMAC020_CH_CSR_SRC_SEL; if (llictl & FTDMAC020_LLI_DST_SEL)
val |= FTDMAC020_CH_CSR_DST_SEL;
/* *SetupthebitsthatexistintheCSRbutarenot *parttheLLI,i.e.onlygetswrittentothecontrol *registerrighthere. * *FIXME:donotjusthandlememcpy,alsohandleslaveDMA.
*/ switch (pl08x->pd->memcpy_burst_size) { default: case PL08X_BURST_SZ_1:
val |= PL080_BSIZE_1 <<
FTDMAC020_CH_CSR_SRC_SIZE_SHIFT; break; case PL08X_BURST_SZ_4:
val |= PL080_BSIZE_4 <<
FTDMAC020_CH_CSR_SRC_SIZE_SHIFT; break; case PL08X_BURST_SZ_8:
val |= PL080_BSIZE_8 <<
FTDMAC020_CH_CSR_SRC_SIZE_SHIFT; break; case PL08X_BURST_SZ_16:
val |= PL080_BSIZE_16 <<
FTDMAC020_CH_CSR_SRC_SIZE_SHIFT; break; case PL08X_BURST_SZ_32:
val |= PL080_BSIZE_32 <<
FTDMAC020_CH_CSR_SRC_SIZE_SHIFT; break; case PL08X_BURST_SZ_64:
val |= PL080_BSIZE_64 <<
FTDMAC020_CH_CSR_SRC_SIZE_SHIFT; break; case PL08X_BURST_SZ_128:
val |= PL080_BSIZE_128 <<
FTDMAC020_CH_CSR_SRC_SIZE_SHIFT; break; case PL08X_BURST_SZ_256:
val |= PL080_BSIZE_256 <<
FTDMAC020_CH_CSR_SRC_SIZE_SHIFT; break;
}
/* Protection flags */ if (pl08x->pd->memcpy_prot_buff)
val |= FTDMAC020_CH_CSR_PROT2; if (pl08x->pd->memcpy_prot_cache)
val |= FTDMAC020_CH_CSR_PROT3; /* We are the kernel, so we are in privileged mode */
val |= FTDMAC020_CH_CSR_PROT1;
writel_relaxed(val, phychan->reg_control);
} else { /* Bits are just identical */
writel_relaxed(lli[PL080_LLI_CCTL], phychan->reg_control);
}
/* Second control word on the PL080s */ if (pl08x->vd->pl080s)
writel_relaxed(lli[PL080S_LLI_CCTL2],
phychan->base + PL080S_CH_CONTROL2);
/* Enable the DMA channel */ /* Do not access config register until channel shows as disabled */ while (readl(pl08x->base + PL080_EN_CHAN) & BIT(phychan->id))
cpu_relax();
/* Do not access config register until channel shows as inactive */ if (phychan->ftdmac020) {
val = readl(phychan->reg_config); while (val & FTDMAC020_CH_CFG_BUSY)
val = readl(phychan->reg_config);
val = readl(phychan->reg_control); while (val & FTDMAC020_CH_CSR_EN)
val = readl(phychan->reg_control);
writel(val | FTDMAC020_CH_CSR_EN,
phychan->reg_control);
} else {
val = readl(phychan->reg_config); while ((val & PL080_CONFIG_ACTIVE) ||
(val & PL080_CONFIG_ENABLE))
val = readl(phychan->reg_config);
if (ch->ftdmac020) { /* Use the enable bit on the FTDMAC020 */
val = readl(ch->reg_control);
val &= ~FTDMAC020_CH_CSR_EN;
writel(val, ch->reg_control); return;
}
/* Set the HALT bit and wait for the FIFO to drain */
val = readl(ch->reg_config);
val |= PL080_CONFIG_HALT;
writel(val, ch->reg_config);
/* Wait for channel inactive */ for (timeout = 1000; timeout; timeout--) { if (!pl08x_phy_channel_busy(ch)) break;
udelay(1);
} if (pl08x_phy_channel_busy(ch))
pr_err("pl08x: channel%u timeout waiting for pause\n", ch->id);
}
/* Use the enable bit on the FTDMAC020 */ if (ch->ftdmac020) {
val = readl(ch->reg_control);
val |= FTDMAC020_CH_CSR_EN;
writel(val, ch->reg_control); return;
}
/* Clear the HALT bit */
val = readl(ch->reg_config);
val &= ~PL080_CONFIG_HALT;
writel(val, ch->reg_config);
}
/* The layout for the FTDMAC020 is different */ if (ch->ftdmac020) { /* Disable all interrupts */
val = readl(ch->reg_config);
val |= (FTDMAC020_CH_CFG_INT_ABT_MASK |
FTDMAC020_CH_CFG_INT_ERR_MASK |
FTDMAC020_CH_CFG_INT_TC_MASK);
writel(val, ch->reg_config);
/* Abort and disable channel */
val = readl(ch->reg_control);
val &= ~FTDMAC020_CH_CSR_EN;
val |= FTDMAC020_CH_CSR_ABT;
writel(val, ch->reg_control);
if (ch->ftdmac020) {
bytes = readl(ch->base + FTDMAC020_CH_SIZE);
val = readl(ch->reg_control);
val &= FTDMAC020_CH_CSR_SRC_WIDTH_MSK;
val >>= FTDMAC020_CH_CSR_SRC_WIDTH_SHIFT;
} elseif (ch->pl080s) {
val = readl(ch->base + PL080S_CH_CONTROL2);
bytes = val & PL080S_CONTROL_TRANSFER_SIZE_MASK;
val = readl(ch->reg_control);
val &= PL080_CONTROL_SWIDTH_MASK;
val >>= PL080_CONTROL_SWIDTH_SHIFT;
} else { /* Plain PL08x */
val = readl(ch->reg_control);
bytes = val & PL080_CONTROL_TRANSFER_SIZE_MASK;
val &= PL080_CONTROL_SWIDTH_MASK;
val >>= PL080_CONTROL_SWIDTH_SHIFT;
}
switch (val) { case PL080_WIDTH_8BIT: break; case PL080_WIDTH_16BIT:
bytes *= 2; break; case PL080_WIDTH_32BIT:
bytes *= 4; break;
} return bytes;
}
if (ch->ftdmac020) {
val = llis_va[PL080_LLI_CCTL];
bytes = val & FTDMAC020_LLI_TRANSFER_SIZE_MASK;
val = llis_va[PL080_LLI_CCTL];
val &= FTDMAC020_LLI_SRC_WIDTH_MSK;
val >>= FTDMAC020_LLI_SRC_WIDTH_SHIFT;
} elseif (ch->pl080s) {
val = llis_va[PL080S_LLI_CCTL2];
bytes = val & PL080S_CONTROL_TRANSFER_SIZE_MASK;
val = llis_va[PL080_LLI_CCTL];
val &= PL080_CONTROL_SWIDTH_MASK;
val >>= PL080_CONTROL_SWIDTH_SHIFT;
} else { /* Plain PL08x */
val = llis_va[PL080_LLI_CCTL];
bytes = val & PL080_CONTROL_TRANSFER_SIZE_MASK;
val &= PL080_CONTROL_SWIDTH_MASK;
val >>= PL080_CONTROL_SWIDTH_SHIFT;
}
switch (val) { case PL080_WIDTH_8BIT: break; case PL080_WIDTH_16BIT:
bytes *= 2; break; case PL080_WIDTH_32BIT:
bytes *= 4; break;
} return bytes;
}
/* The channel should be paused when calling this */ static u32 pl08x_getbytes_chan(struct pl08x_dma_chan *plchan)
{ struct pl08x_driver_data *pl08x = plchan->host; const u32 *llis_va, *llis_va_limit; struct pl08x_phy_chan *ch;
dma_addr_t llis_bus; struct pl08x_txd *txd;
u32 llis_max_words;
size_t bytes;
u32 clli;
if (i == pl08x->vd->channels) { /* No physical channel available, cope with it */ return NULL;
}
return ch;
}
/* Mark the physical channel as free. Note, this write is atomic. */ staticinlinevoid pl08x_put_phy_channel(struct pl08x_driver_data *pl08x, struct pl08x_phy_chan *ch)
{
ch->serving = NULL;
}
ch = pl08x_get_phy_channel(pl08x, plchan); if (!ch) {
dev_dbg(&pl08x->adev->dev, "no physical channel available for xfer on %s\n", plchan->name);
plchan->state = PL08X_CHAN_WAITING;
plchan->waiting_at = jiffies; return;
}
dev_dbg(&pl08x->adev->dev, "allocated physical channel %d for xfer on %s\n",
ch->id, plchan->name);
if (!next && pl08x->has_slave) {
list_for_each_entry(p, &pl08x->slave.channels, vc.chan.device_node) if (p->state == PL08X_CHAN_WAITING &&
p->waiting_at <= waiting_at) {
next = p;
waiting_at = p->waiting_at;
}
}
/* Ensure that the physical channel is stopped */
pl08x_terminate_phy_chan(pl08x, plchan->phychan);
if (next) { bool success;
/* *Eww.Weknowthisisn'tgoingtodeadlock *butlockdepprobablydoesn't.
*/
spin_lock(&next->vc.lock); /* Re-check the state now that we have the lock */
success = next->state == PL08X_CHAN_WAITING; if (success)
pl08x_phy_reassign_start(plchan->phychan, next);
spin_unlock(&next->vc.lock);
/* If the state changed, try to find another channel */ if (!success) goto retry;
} else { /* No more jobs, so free up the physical channel */
pl08x_put_phy_channel(pl08x, plchan->phychan);
}
if (pl08x->vd->ftdmac020) { /* FIXME: only memcpy so far so both increase */
bd->srcbus.addr += len;
bd->dstbus.addr += len;
} else { if (cctl & PL080_CONTROL_SRC_INCR)
bd->srcbus.addr += len; if (cctl & PL080_CONTROL_DST_INCR)
bd->dstbus.addr += len;
}
/* FTDMAC020 only does memory-to-memory */ if (pl08x->vd->ftdmac020)
fc = PL080_FLOW_MEM2MEM; else
fc = (txd->ccfg & PL080_CONFIG_FLOW_CONTROL_MASK) >>
PL080_CONFIG_FLOW_CONTROL_SHIFT; if (!((fc >= PL080_FLOW_SRC2DST_DST) &&
(fc <= PL080_FLOW_SRC2DST_SRC))) {
dev_err(&pl08x->adev->dev, "%s sg len can't be zero",
__func__); return0;
}
if (!IS_BUS_ALIGNED(&bd.srcbus) ||
!IS_BUS_ALIGNED(&bd.dstbus)) {
dev_err(&pl08x->adev->dev, "%s src & dst address must be aligned to src" " & dst width if peripheral is flow controller",
__func__); return0;
}
if (bd.remainder) { /* *Masternowaligned *-ifslaveisnotthenwemustsetitswidthdown
*/ if (!IS_BUS_ALIGNED(sbus)) {
dev_dbg(&pl08x->adev->dev, "%s set down bus width to one byte\n",
__func__);
sbus->buswidth = 1;
}
/* *Bytestransferred=tsize*srcwidth,not *MIN(buswidths)
*/
max_bytes_per_lli = bd.srcbus.buswidth *
pl08x->vd->max_transfer_size;
dev_vdbg(&pl08x->adev->dev, "%s max bytes per lli = %zu\n",
__func__, max_bytes_per_lli);
if (total_bytes != dsg->len) {
dev_err(&pl08x->adev->dev, "%s size of encoded lli:s don't match total txd, transferred 0x%08zx from size 0x%08zx\n",
__func__, total_bytes, dsg->len); return0;
}
if (num_llis >= MAX_NUM_TSFR_LLIS) {
dev_err(&pl08x->adev->dev, "%s need to increase MAX_NUM_TSFR_LLIS from 0x%08x\n",
__func__, MAX_NUM_TSFR_LLIS); return0;
}
}
if (txd->cyclic) { /* Link back to the first LLI. */
last_lli[PL080_LLI_LLI] = txd->llis_bus | bd.lli_bus;
} else { /* The final LLI terminates the LLI. */
last_lli[PL080_LLI_LLI] = 0; /* The final LLI element shall also fire an interrupt. */ if (pl08x->vd->ftdmac020)
last_lli[PL080_LLI_CCTL] &= ~FTDMAC020_LLI_TC_MSK; else
last_lli[PL080_LLI_CCTL] |= PL080_CONTROL_TC_IRQ_EN;
}
/* The FTDMAC020 use different bits to indicate src/dst bus */ if (ftdmac020) {
dst_ahb2 = FTDMAC020_LLI_DST_SEL;
src_ahb2 = FTDMAC020_LLI_SRC_SEL;
} else {
dst_ahb2 = PL080_CONTROL_DST_AHB2;
src_ahb2 = PL080_CONTROL_SRC_AHB2;
}
ret = pl08x_request_mux(plchan);
if (ret < 0) {
pl08x_free_txd(pl08x, txd);
dev_dbg(&pl08x->adev->dev,
"unable to mux for transfer on %s due to platform restrictions\n",
plchan->name);
return NULL;
}
dev_dbg(&pl08x->adev->dev, "allocated DMA request signal %d for xfer on %s\n",
plchan->signal, plchan->name);
/* Assign the flow control signal to this channel */
if (direction == DMA_MEM_TO_DEV)
txd->ccfg |= plchan->signal << PL080_CONFIG_DST_SEL_SHIFT;
else
txd->ccfg |= plchan->signal << PL080_CONFIG_SRC_SEL_SHIFT;
return txd;
}
static int pl08x_tx_add_sg(struct pl08x_txd *txd,
enum dma_transfer_direction direction,
dma_addr_t slave_addr,
dma_addr_t buf_addr,
unsigned int len)
{
struct pl08x_sg *dsg;
dsg = kzalloc(sizeof(struct pl08x_sg), GFP_NOWAIT);
if (!dsg)
return -ENOMEM;
if (config->device_fc && pl08x->vd->pl080s) {
dev_err(&pl08x->adev->dev,
"%s: PL080S does not support peripheral flow control\n",
__func__);
return -EINVAL;
}
plchan->cfg = *config;
return 0;
}
static int pl08x_terminate_all(struct dma_chan *chan)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
struct pl08x_driver_data *pl08x = plchan->host;
unsigned long flags;
if (plchan->phychan) {
/*
* Mark physical channel as free and free any slave
* signal
*/
pl08x_phy_free(plchan);
}
/* Dequeue jobs and free LLIs */
if (plchan->at) {
vchan_terminate_vdesc(&plchan->at->vd);
plchan->at = NULL;
}
/* Dequeue jobs not yet fired as well */
pl08x_free_txd_list(pl08x, plchan);
static int pl08x_pause(struct dma_chan *chan)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
unsigned long flags;
/*
* Anything succeeds on channels with no physical allocation and
* no queued transfers.
*/
spin_lock_irqsave(&plchan->vc.lock, flags);
if (!plchan->phychan && !plchan->at) {
spin_unlock_irqrestore(&plchan->vc.lock, flags);
return 0;
}
static int pl08x_resume(struct dma_chan *chan)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
unsigned long flags;
/*
* Anything succeeds on channels with no physical allocation and
* no queued transfers.
*/
spin_lock_irqsave(&plchan->vc.lock, flags);
if (!plchan->phychan && !plchan->at) {
spin_unlock_irqrestore(&plchan->vc.lock, flags);
return 0;
}
/*
* Just check that the device is there and active
* TODO: turn this bit on/off depending on the number of physical channels
* actually used, if it is zero... well shut it off. That will save some
* power. Cut the clock at the same time.
*/
static void pl08x_ensure_on(struct pl08x_driver_data *pl08x)
{
/* The Nomadik variant does not have the config register */
if (pl08x->vd->nomadik)
return;
/* The FTDMAC020 variant does this in another register */
if (pl08x->vd->ftdmac020) {
writel(PL080_CONFIG_ENABLE, pl08x->base + FTDMAC020_CSR);
return;
}
writel(PL080_CONFIG_ENABLE, pl08x->base + PL080_CONFIG);
}
for (i = 0; i < pl08x->vd->channels; i++) {
if ((BIT(i) & err) || (BIT(i) & tc)) {
/* Locate physical channel */
struct pl08x_phy_chan *phychan = &pl08x->phy_chans[i];
struct pl08x_dma_chan *plchan = phychan->serving;
struct pl08x_txd *tx;
if (!plchan) {
dev_err(&pl08x->adev->dev,
"%s Error TC interrupt on unused channel: 0x%08x\n",
__func__, i);
continue;
}
spin_lock(&plchan->vc.lock);
tx = plchan->at;
if (tx && tx->cyclic) {
vchan_cyclic_callback(&tx->vd);
} else if (tx) {
plchan->at = NULL;
/*
* This descriptor is done, release its mux
* reservation.
*/
pl08x_release_mux(plchan);
tx->done = true;
vchan_cookie_complete(&tx->vd);
/*
* And start the next descriptor (if any),
* otherwise free this channel.
*/
if (vchan_next_desc(&plchan->vc))
pl08x_start_next_txd(plchan);
else
pl08x_phy_free(plchan);
}
spin_unlock(&plchan->vc.lock);
/*
* Initialise the DMAC memcpy/slave channels.
* Make a local wrapper to hold required data
*/
static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x,
struct dma_device *dmadev, unsigned int channels, bool slave)
{
struct pl08x_dma_chan *chan;
int i;
INIT_LIST_HEAD(&dmadev->channels);
/*
* Register as many memcpy as we have physical channels,
* we won't always be able to use all but the code will have
* to cope with that situation.
*/
for (i = 0; i < channels; i++) {
chan = kzalloc(sizeof(*chan), GFP_KERNEL);
if (!chan)
return -ENOMEM;
if (dma_spec->args_count != 2) {
dev_err(&pl08x->adev->dev,
"DMA channel translation requires two cells\n");
return NULL;
}
dma_chan = pl08x_find_chan_id(pl08x, dma_spec->args[0]);
if (!dma_chan) {
dev_err(&pl08x->adev->dev,
"DMA slave channel not found\n");
return NULL;
}
plchan = to_pl08x_chan(dma_chan);
dev_dbg(&pl08x->adev->dev,
"translated channel for signal %d\n",
dma_spec->args[0]);
/* Augment channel data for applicable AHB buses */
plchan->cd->periph_buses = dma_spec->args[1];
return dma_get_slave_channel(dma_chan);
}
static int pl08x_of_probe(struct amba_device *adev,
struct pl08x_driver_data *pl08x,
struct device_node *np)
{
struct pl08x_platform_data *pd;
struct pl08x_channel_data *chanp = NULL;
u32 val;
int ret;
int i;
pd = devm_kzalloc(&adev->dev, sizeof(*pd), GFP_KERNEL);
if (!pd)
return -ENOMEM;
/* Eligible bus masters for fetching LLIs */
if (of_property_read_bool(np, "lli-bus-interface-ahb1"))
pd->lli_buses |= PL08X_AHB1;
if (of_property_read_bool(np, "lli-bus-interface-ahb2"))
pd->lli_buses |= PL08X_AHB2;
if (!pd->lli_buses) {
dev_info(&adev->dev, "no bus masters for LLIs stated, assume all\n");
pd->lli_buses |= PL08X_AHB1 | PL08X_AHB2;
}
/* Eligible bus masters for memory access */
if (of_property_read_bool(np, "mem-bus-interface-ahb1"))
pd->mem_buses |= PL08X_AHB1;
if (of_property_read_bool(np, "mem-bus-interface-ahb2"))
pd->mem_buses |= PL08X_AHB2;
if (!pd->mem_buses) {
dev_info(&adev->dev, "no bus masters for memory stated, assume all\n");
pd->mem_buses |= PL08X_AHB1 | PL08X_AHB2;
}
/* Parse the memcpy channel properties */
ret = of_property_read_u32(np, "memcpy-burst-size", &val);
if (ret) {
dev_info(&adev->dev, "no memcpy burst size specified, using 1 byte\n");
val = 1;
}
switch (val) {
default:
dev_err(&adev->dev, "illegal burst size for memcpy, set to 1\n");
fallthrough;
case 1:
pd->memcpy_burst_size = PL08X_BURST_SZ_1;
break;
case 4:
pd->memcpy_burst_size = PL08X_BURST_SZ_4;
break;
case 8:
pd->memcpy_burst_size = PL08X_BURST_SZ_8;
break;
case 16:
pd->memcpy_burst_size = PL08X_BURST_SZ_16;
break;
case 32:
pd->memcpy_burst_size = PL08X_BURST_SZ_32;
break;
case 64:
pd->memcpy_burst_size = PL08X_BURST_SZ_64;
break;
case 128:
pd->memcpy_burst_size = PL08X_BURST_SZ_128;
break;
case 256:
pd->memcpy_burst_size = PL08X_BURST_SZ_256;
break;
}
ret = of_property_read_u32(np, "memcpy-bus-width", &val);
if (ret) {
dev_info(&adev->dev, "no memcpy bus width specified, using 8 bits\n");
val = 8;
}
switch (val) {
default:
dev_err(&adev->dev, "illegal bus width for memcpy, set to 8 bits\n");
fallthrough;
case 8:
pd->memcpy_bus_width = PL08X_BUS_WIDTH_8_BITS;
break;
case 16:
pd->memcpy_bus_width = PL08X_BUS_WIDTH_16_BITS;
break;
case 32:
pd->memcpy_bus_width = PL08X_BUS_WIDTH_32_BITS;
break;
}
/*
* Allocate channel data for all possible slave channels (one
* for each possible signal), channels will then be allocated
* for a device and have it's AHB interfaces set up at
* translation time.
*/
if (pl08x->vd->signals) {
chanp = devm_kcalloc(&adev->dev,
pl08x->vd->signals,
sizeof(struct pl08x_channel_data),
GFP_KERNEL);
if (!chanp)
return -ENOMEM;
pd->slave_channels = chanp;
for (i = 0; i < pl08x->vd->signals; i++) {
/*
* chanp->periph_buses will be assigned at translation
*/
chanp->bus_id = kasprintf(GFP_KERNEL, "slave%d", i);
chanp++;
}
pd->num_slave_channels = pl08x->vd->signals;
}
/* A DMA memory pool for LLIs, align on 1-byte boundary */
pl08x->pool = dma_pool_create(DRIVER_NAME, &pl08x->adev->dev,
tsfr_size, PL08X_ALIGN, 0);
if (!pl08x->pool) {
ret = -ENOMEM;
goto out_no_lli_pool;
}
/* Turn on the PL08x */
pl08x_ensure_on(pl08x);
/* Clear any pending interrupts */
if (vd->ftdmac020)
/* This variant has error IRQs in bits 16-19 */
writel(0x0000FFFF, pl08x->base + PL080_ERR_CLEAR);
else
writel(0x000000FF, pl08x->base + PL080_ERR_CLEAR);
writel(0x000000FF, pl08x->base + PL080_TC_CLEAR);
/* Attach the interrupt handler */
ret = request_irq(adev->irq[0], pl08x_irq, 0, DRIVER_NAME, pl08x);
if (ret) {
dev_err(&adev->dev, "%s failed to request interrupt %d\n",
__func__, adev->irq[0]);
goto out_no_irq;
}
/* Initialize physical channels */
pl08x->phy_chans = kcalloc(vd->channels, sizeof(*pl08x->phy_chans),
GFP_KERNEL);
if (!pl08x->phy_chans) {
ret = -ENOMEM;
goto out_no_phychans;
}
for (i = 0; i < vd->channels; i++) {
struct pl08x_phy_chan *ch = &pl08x->phy_chans[i];
/*
* Nomadik variants can have channels that are locked
* down for the secure world only. Lock up these channels
* by perpetually serving a dummy virtual channel.
*/
if (vd->nomadik) {
u32 val;
val = readl(ch->reg_config);
if (val & (PL080N_CONFIG_ITPROT | PL080N_CONFIG_SECPROT)) {
dev_info(&adev->dev, "physical channel %d reserved for secure access only\n", i);
ch->locked = true;
}
}
dev_dbg(&adev->dev, "physical channel %d is %s\n",
i, pl08x_phy_channel_busy(ch) ? "BUSY" : "FREE");
}
/* Register as many memcpy channels as there are physical channels */
ret = pl08x_dma_init_virtual_channels(pl08x, &pl08x->memcpy,
pl08x->vd->channels, false);
if (ret <= 0) {
dev_warn(&pl08x->adev->dev,
"%s failed to enumerate memcpy channels - %d\n",
__func__, ret);
goto out_no_memcpy;
}
/* Register slave channels */
if (pl08x->has_slave) {
ret = pl08x_dma_init_virtual_channels(pl08x, &pl08x->slave,
pl08x->pd->num_slave_channels, true);
if (ret < 0) {
dev_warn(&pl08x->adev->dev,
"%s failed to enumerate slave channels - %d\n",
__func__, ret);
goto out_no_slave;
}
}
ret = dma_async_device_register(&pl08x->memcpy);
if (ret) {
dev_warn(&pl08x->adev->dev,
"%s failed to register memcpy as an async device - %d\n",
__func__, ret);
goto out_no_memcpy_reg;
}
if (pl08x->has_slave) {
ret = dma_async_device_register(&pl08x->slave);
if (ret) {
dev_warn(&pl08x-> Documentation/bindings//usb-device.yaml
" register slave anasync device - %d\n",
__func__,ret)java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 18
oto interrupt-parent = <&gpio0>;
}
}
static int __init pl08x_init(void)
{
int retval;
retval = amba_driver_register(&pl08x_amba_driver);
if (retval)
printk(KERN_WARNING DRIVER_NAME
"failed to register as an AMBA device (%d)\n",
retval);
return retval;
}
subsys_initcall(pl08x_init);
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.71Angebot
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.