in amba-pl08x.c [2701:2990]
static int pl08x_probe(struct amba_device *adev, const struct amba_id *id)
{
struct pl08x_driver_data *pl08x;
struct vendor_data *vd = id->data;
struct device_node *np = adev->dev.of_node;
u32 tsfr_size;
int ret = 0;
int i;
ret = amba_request_regions(adev, NULL);
if (ret)
return ret;
/* Ensure that we can do DMA */
ret = dma_set_mask_and_coherent(&adev->dev, DMA_BIT_MASK(32));
if (ret)
goto out_no_pl08x;
/* Create the driver state holder */
pl08x = kzalloc(sizeof(*pl08x), GFP_KERNEL);
if (!pl08x) {
ret = -ENOMEM;
goto out_no_pl08x;
}
/* Assign useful pointers to the driver state */
pl08x->adev = adev;
pl08x->vd = vd;
pl08x->base = ioremap(adev->res.start, resource_size(&adev->res));
if (!pl08x->base) {
ret = -ENOMEM;
goto out_no_ioremap;
}
if (vd->ftdmac020) {
u32 val;
val = readl(pl08x->base + FTDMAC020_REVISION);
dev_info(&pl08x->adev->dev, "FTDMAC020 %d.%d rel %d\n",
(val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff);
val = readl(pl08x->base + FTDMAC020_FEATURE);
dev_info(&pl08x->adev->dev, "FTDMAC020 %d channels, "
"%s built-in bridge, %s, %s linked lists\n",
(val >> 12) & 0x0f,
(val & BIT(10)) ? "no" : "has",
(val & BIT(9)) ? "AHB0 and AHB1" : "AHB0",
(val & BIT(8)) ? "supports" : "does not support");
/* Vendor data from feature register */
if (!(val & BIT(8)))
dev_warn(&pl08x->adev->dev,
"linked lists not supported, required\n");
vd->channels = (val >> 12) & 0x0f;
vd->dualmaster = !!(val & BIT(9));
}
/* Initialize memcpy engine */
dma_cap_set(DMA_MEMCPY, pl08x->memcpy.cap_mask);
pl08x->memcpy.dev = &adev->dev;
pl08x->memcpy.device_free_chan_resources = pl08x_free_chan_resources;
pl08x->memcpy.device_prep_dma_memcpy = pl08x_prep_dma_memcpy;
pl08x->memcpy.device_prep_dma_interrupt = pl08x_prep_dma_interrupt;
pl08x->memcpy.device_tx_status = pl08x_dma_tx_status;
pl08x->memcpy.device_issue_pending = pl08x_issue_pending;
pl08x->memcpy.device_config = pl08x_config;
pl08x->memcpy.device_pause = pl08x_pause;
pl08x->memcpy.device_resume = pl08x_resume;
pl08x->memcpy.device_terminate_all = pl08x_terminate_all;
pl08x->memcpy.device_synchronize = pl08x_synchronize;
pl08x->memcpy.src_addr_widths = PL80X_DMA_BUSWIDTHS;
pl08x->memcpy.dst_addr_widths = PL80X_DMA_BUSWIDTHS;
pl08x->memcpy.directions = BIT(DMA_MEM_TO_MEM);
pl08x->memcpy.residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;
if (vd->ftdmac020)
pl08x->memcpy.copy_align = DMAENGINE_ALIGN_4_BYTES;
/*
* Initialize slave engine, if the block has no signals, that means
* we have no slave support.
*/
if (vd->signals) {
pl08x->has_slave = true;
dma_cap_set(DMA_SLAVE, pl08x->slave.cap_mask);
dma_cap_set(DMA_CYCLIC, pl08x->slave.cap_mask);
pl08x->slave.dev = &adev->dev;
pl08x->slave.device_free_chan_resources =
pl08x_free_chan_resources;
pl08x->slave.device_prep_dma_interrupt =
pl08x_prep_dma_interrupt;
pl08x->slave.device_tx_status = pl08x_dma_tx_status;
pl08x->slave.device_issue_pending = pl08x_issue_pending;
pl08x->slave.device_prep_slave_sg = pl08x_prep_slave_sg;
pl08x->slave.device_prep_dma_cyclic = pl08x_prep_dma_cyclic;
pl08x->slave.device_config = pl08x_config;
pl08x->slave.device_pause = pl08x_pause;
pl08x->slave.device_resume = pl08x_resume;
pl08x->slave.device_terminate_all = pl08x_terminate_all;
pl08x->slave.device_synchronize = pl08x_synchronize;
pl08x->slave.src_addr_widths = PL80X_DMA_BUSWIDTHS;
pl08x->slave.dst_addr_widths = PL80X_DMA_BUSWIDTHS;
pl08x->slave.directions =
BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
pl08x->slave.residue_granularity =
DMA_RESIDUE_GRANULARITY_SEGMENT;
}
/* Get the platform data */
pl08x->pd = dev_get_platdata(&adev->dev);
if (!pl08x->pd) {
if (np) {
ret = pl08x_of_probe(adev, pl08x, np);
if (ret)
goto out_no_platdata;
} else {
dev_err(&adev->dev, "no platform data supplied\n");
ret = -EINVAL;
goto out_no_platdata;
}
} else {
pl08x->slave.filter.map = pl08x->pd->slave_map;
pl08x->slave.filter.mapcnt = pl08x->pd->slave_map_len;
pl08x->slave.filter.fn = pl08x_filter_fn;
}
/* By default, AHB1 only. If dualmaster, from platform */
pl08x->lli_buses = PL08X_AHB1;
pl08x->mem_buses = PL08X_AHB1;
if (pl08x->vd->dualmaster) {
pl08x->lli_buses = pl08x->pd->lli_buses;
pl08x->mem_buses = pl08x->pd->mem_buses;
}
if (vd->pl080s)
pl08x->lli_words = PL080S_LLI_WORDS;
else
pl08x->lli_words = PL080_LLI_WORDS;
tsfr_size = MAX_NUM_TSFR_LLIS * pl08x->lli_words * sizeof(u32);
/* 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 = kzalloc((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];
ch->id = i;
ch->base = pl08x->base + PL080_Cx_BASE(i);
if (vd->ftdmac020) {
/* FTDMA020 has a special channel busy register */
ch->reg_busy = ch->base + FTDMAC020_CH_BUSY;
ch->reg_config = ch->base + FTDMAC020_CH_CFG;
ch->reg_control = ch->base + FTDMAC020_CH_CSR;
ch->reg_src = ch->base + FTDMAC020_CH_SRC_ADDR;
ch->reg_dst = ch->base + FTDMAC020_CH_DST_ADDR;
ch->reg_lli = ch->base + FTDMAC020_CH_LLP;
ch->ftdmac020 = true;
} else {
ch->reg_config = ch->base + vd->config_offset;
ch->reg_control = ch->base + PL080_CH_CONTROL;
ch->reg_src = ch->base + PL080_CH_SRC_ADDR;
ch->reg_dst = ch->base + PL080_CH_DST_ADDR;
ch->reg_lli = ch->base + PL080_CH_LLI;
}
if (vd->pl080s)
ch->pl080s = true;
spin_lock_init(&ch->lock);
/*
* 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->adev->dev,
"%s failed to register slave as an async device - %d\n",
__func__, ret);
goto out_no_slave_reg;
}
}
amba_set_drvdata(adev, pl08x);
init_pl08x_debugfs(pl08x);
dev_info(&pl08x->adev->dev, "DMA: PL%03x%s rev%u at 0x%08llx irq %d\n",
amba_part(adev), pl08x->vd->pl080s ? "s" : "", amba_rev(adev),
(unsigned long long)adev->res.start, adev->irq[0]);
return 0;
out_no_slave_reg:
dma_async_device_unregister(&pl08x->memcpy);
out_no_memcpy_reg:
if (pl08x->has_slave)
pl08x_free_virtual_channels(&pl08x->slave);
out_no_slave:
pl08x_free_virtual_channels(&pl08x->memcpy);
out_no_memcpy:
kfree(pl08x->phy_chans);
out_no_phychans:
free_irq(adev->irq[0], pl08x);
out_no_irq:
dma_pool_destroy(pl08x->pool);
out_no_lli_pool:
out_no_platdata:
iounmap(pl08x->base);
out_no_ioremap:
kfree(pl08x);
out_no_pl08x:
amba_release_regions(adev);
return ret;
}