diff mbox series

[U-Boot,RFC,v3,03/15] dma: add bcm6348-iudma support

Message ID 20180221161040.22246-4-noltari@gmail.com
State Superseded, archived
Headers show
Series bmips: add bcm6348-enet support | expand

Commit Message

Álvaro Fernández Rojas Feb. 21, 2018, 4:10 p.m. UTC
BCM6348 IUDMA controller is present on multiple BMIPS (BCM63xx) SoCs.

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
---
 v3: no changes
 v2: Fix dma rx burst config and select DMA_CHANNELS.

 drivers/dma/Kconfig         |   9 +
 drivers/dma/Makefile        |   1 +
 drivers/dma/bcm6348-iudma.c | 505 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 515 insertions(+)
 create mode 100644 drivers/dma/bcm6348-iudma.c

Comments

Grygorii Strashko Feb. 22, 2018, 7:50 p.m. UTC | #1
Hi 

I'd appreciated if you can clarify few points below.

On 02/21/2018 10:10 AM, Álvaro Fernández Rojas wrote:
> BCM6348 IUDMA controller is present on multiple BMIPS (BCM63xx) SoCs.
> 
> Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
> ---
>   v3: no changes
>   v2: Fix dma rx burst config and select DMA_CHANNELS.
> 
>   drivers/dma/Kconfig         |   9 +
>   drivers/dma/Makefile        |   1 +
>   drivers/dma/bcm6348-iudma.c | 505 ++++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 515 insertions(+)
>   create mode 100644 drivers/dma/bcm6348-iudma.c
>

[...]
 
>
> +static int bcm6348_iudma_enable(struct dma *dma)
> +{
> +	struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev);
> +	struct bcm6348_chan_priv *ch_priv = priv->ch_priv[dma->id];
> +	struct bcm6348_dma_desc *dma_desc;
> +	uint8_t i;
> +
> +	/* init dma rings */
> +	dma_desc = ch_priv->dma_ring;
> +	for (i = 0; i < ch_priv->dma_ring_size; i++) {
> +		if (bcm6348_iudma_chan_is_rx(dma->id)) {
> +			dma_desc->status = DMAD_ST_OWN_MASK;
> +			dma_desc->length = PKTSIZE_ALIGN;
> +			dma_desc->address = virt_to_phys(net_rx_packets[i]);

You are filling RX queue/ring with buffers defined in Net core.
Does it mean that this DMA driver will not be usable for other purposes, as
Net can be compiled out?

Wouldn't it be reasonable to have some sort of .receive_prepare() callback in
DMA dma_ops, so DMA user can control which buffers to push in RX DMA channel?
And it also can be used in eth_ops.free_pkt() callback (see below).

> +		} else {
> +			dma_desc->status = 0;
> +			dma_desc->length = 0;
> +			dma_desc->address = 0;
> +		}
> +
> +		if (i == ch_priv->dma_ring_size - 1)
> +			dma_desc->status |= DMAD_ST_WRAP_MASK;
> +
> +		if (bcm6348_iudma_chan_is_rx(dma->id))
> +			writel_be(1,
> +				  priv->base + DMA_FLOWC_ALLOC_REG(dma->id));
> +
> +		dma_desc++;
> +	}
> +
> +	/* init to first descriptor */
> +	ch_priv->desc_id = 0;
> +
> +	/* force cache writeback */
> +	flush_dcache_range((ulong)ch_priv->dma_ring,
> +		ALIGN_END_ADDR(struct bcm6348_dma_desc, ch_priv->dma_ring,
> +			       ch_priv->dma_ring_size));
> +
> +	/* clear sram */
> +	writel_be(0, priv->sram + DMAS_STATE_DATA_REG(dma->id));
> +	writel_be(0, priv->sram + DMAS_DESC_LEN_STATUS_REG(dma->id));
> +	writel_be(0, priv->sram + DMAS_DESC_BASE_BUFPTR_REG(dma->id));
> +
> +	/* set dma ring start */
> +	writel_be(virt_to_phys(ch_priv->dma_ring),
> +		  priv->sram + DMAS_RSTART_REG(dma->id));
> +
> +	/* set flow control */
> +	if (bcm6348_iudma_chan_is_rx(dma->id)) {
> +		u32 val;
> +
> +		setbits_be32(priv->base + DMA_CFG_REG,
> +			     DMA_CFG_FLOWC_ENABLE(dma->id));
> +
> +		val = ch_priv->dma_ring_size / 3;
> +		writel_be(val, priv->base + DMA_FLOWC_THR_LO_REG(dma->id));
> +
> +		val = (ch_priv->dma_ring_size * 2) / 3;
> +		writel_be(val, priv->base + DMA_FLOWC_THR_HI_REG(dma->id));
> +	}
> +
> +	/* set dma max burst */
> +	writel_be(ch_priv->dma_ring_size,
> +		  priv->chan + DMAC_BURST_REG(dma->id));
> +
> +	/* clear interrupts */
> +	writel_be(DMAC_IR_DONE_MASK, priv->chan + DMAC_IR_ST_REG(dma->id));
> +	writel_be(0, priv->chan + DMAC_IR_EN_REG(dma->id));
> +
> +	setbits_be32(priv->chan + DMAC_CFG_REG(dma->id), DMAC_CFG_ENABLE_MASK);
> +
> +	return 0;
> +}

[...]

> +
> +static int bcm6348_iudma_receive(struct dma *dma, void **dst)
> +{
> +	struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev);
> +	struct bcm6348_chan_priv *ch_priv = priv->ch_priv[dma->id];
> +	struct bcm6348_dma_desc *dma_desc;
> +	void __iomem *dma_buff;
> +	uint16_t status;
> +	int ret;
> +
> +	/* get dma ring descriptor address */
> +	dma_desc = ch_priv->dma_ring;
> +	dma_desc += ch_priv->desc_id;
> +
> +	/* invalidate cache data */
> +	invalidate_dcache_range((ulong)dma_desc,
> +		ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1));
> +
> +	/* check dma own */
> +	if (dma_desc->status & DMAD_ST_OWN_MASK)
> +		return 0;
> +
> +	/* check dma end */
> +	if (!(dma_desc->status & DMAD_ST_EOP_MASK))
> +		return -EINVAL;
> +
> +	/* get dma buff descriptor address */
> +	dma_buff = phys_to_virt(dma_desc->address);
> +
> +	/* invalidate cache data */
> +	invalidate_dcache_range((ulong)dma_buff,
> +				(ulong)(dma_buff + PKTSIZE_ALIGN));
> +
> +	/* get dma data */
> +	*dst = dma_buff;
> +	ret = dma_desc->length;
> +
> +	/* reinit dma descriptor */
> +	status = dma_desc->status & DMAD_ST_WRAP_MASK;
> +	status |= DMAD_ST_OWN_MASK;
> +
> +	dma_desc->length = PKTSIZE_ALIGN;
> +	dma_desc->status = status;
> +
> +	/* flush cache */
> +	flush_dcache_range((ulong)dma_desc,
> +		ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1));

Could you clarify pls, if you do return dma_desc to RX ring here or not?

if yes, wouldn't it cause potential problem on Net RX path 
		ret = eth_get_ops(current)->recv(current, flags, &packet);
^^ (1) here buffer will be received from DMA ( and pushed back to RX ring ?? )

		flags = 0;
		if (ret > 0)
			net_process_received_packet(packet, ret);
^^ (2) here it will be passed in Net stack

		if (ret >= 0 && eth_get_ops(current)->free_pkt)
			eth_get_ops(current)->free_pkt(current, packet, ret);
^^ at this point it should be safe to return buffer in DMA RX ring.

		if (ret <= 0)
			break;

Can DMA overwrite packet after point (1) while packet is still processed (2)?

> +
> +	/* set flow control buffer alloc */
> +	writel_be(1, priv->base + DMA_FLOWC_ALLOC_REG(dma->id));
> +
> +	/* enable dma */
> +	setbits_be32(priv->chan + DMAC_CFG_REG(dma->id), DMAC_CFG_ENABLE_MASK);
> +
> +	/* set interrupt */
> +	writel_be(DMAC_IR_DONE_MASK, priv->chan + DMAC_IR_EN_REG(dma->id));
> +
> +	/* increment dma descriptor */
> +	ch_priv->desc_id = (ch_priv->desc_id + 1) % ch_priv->dma_ring_size;
> +
> +	return ret - 4;
> +}
> +
Álvaro Fernández Rojas Feb. 22, 2018, 8:48 p.m. UTC | #2
Hi Grygori,

El 22/02/2018 a las 20:50, Grygorii Strashko escribió:
> Hi
>
> I'd appreciated if you can clarify few points below.
>
> On 02/21/2018 10:10 AM, Álvaro Fernández Rojas wrote:
>> BCM6348 IUDMA controller is present on multiple BMIPS (BCM63xx) SoCs.
>>
>> Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
>> ---
>>    v3: no changes
>>    v2: Fix dma rx burst config and select DMA_CHANNELS.
>>
>>    drivers/dma/Kconfig         |   9 +
>>    drivers/dma/Makefile        |   1 +
>>    drivers/dma/bcm6348-iudma.c | 505 ++++++++++++++++++++++++++++++++++++++++++++
>>    3 files changed, 515 insertions(+)
>>    create mode 100644 drivers/dma/bcm6348-iudma.c
>>
> [...]
>   
>> +static int bcm6348_iudma_enable(struct dma *dma)
>> +{
>> +	struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev);
>> +	struct bcm6348_chan_priv *ch_priv = priv->ch_priv[dma->id];
>> +	struct bcm6348_dma_desc *dma_desc;
>> +	uint8_t i;
>> +
>> +	/* init dma rings */
>> +	dma_desc = ch_priv->dma_ring;
>> +	for (i = 0; i < ch_priv->dma_ring_size; i++) {
>> +		if (bcm6348_iudma_chan_is_rx(dma->id)) {
>> +			dma_desc->status = DMAD_ST_OWN_MASK;
>> +			dma_desc->length = PKTSIZE_ALIGN;
>> +			dma_desc->address = virt_to_phys(net_rx_packets[i]);
> You are filling RX queue/ring with buffers defined in Net core.
> Does it mean that this DMA driver will not be usable for other purposes, as
> Net can be compiled out?
As far as I know, and depending on the specific SoC, BCM63xx IUDMA is 
used for Ethernet, USB (device only) and xDSL.
So yes, in u-boot it will be used for ethernet only.
BTW, my first attempt didn't use net_rx_packets, but I saw that in 
pic32_eth implementation and dropped the dma specific buffers. I will 
add them again ;).
>
> Wouldn't it be reasonable to have some sort of .receive_prepare() callback in
> DMA dma_ops, so DMA user can control which buffers to push in RX DMA channel?
> And it also can be used in eth_ops.free_pkt() callback (see below).
Yes, probably, but maybe we can achieve that without adding another call.
>
>> +		} else {
>> +			dma_desc->status = 0;
>> +			dma_desc->length = 0;
>> +			dma_desc->address = 0;
>> +		}
>> +
>> +		if (i == ch_priv->dma_ring_size - 1)
>> +			dma_desc->status |= DMAD_ST_WRAP_MASK;
>> +
>> +		if (bcm6348_iudma_chan_is_rx(dma->id))
>> +			writel_be(1,
>> +				  priv->base + DMA_FLOWC_ALLOC_REG(dma->id));
>> +
>> +		dma_desc++;
>> +	}
>> +
>> +	/* init to first descriptor */
>> +	ch_priv->desc_id = 0;
>> +
>> +	/* force cache writeback */
>> +	flush_dcache_range((ulong)ch_priv->dma_ring,
>> +		ALIGN_END_ADDR(struct bcm6348_dma_desc, ch_priv->dma_ring,
>> +			       ch_priv->dma_ring_size));
>> +
>> +	/* clear sram */
>> +	writel_be(0, priv->sram + DMAS_STATE_DATA_REG(dma->id));
>> +	writel_be(0, priv->sram + DMAS_DESC_LEN_STATUS_REG(dma->id));
>> +	writel_be(0, priv->sram + DMAS_DESC_BASE_BUFPTR_REG(dma->id));
>> +
>> +	/* set dma ring start */
>> +	writel_be(virt_to_phys(ch_priv->dma_ring),
>> +		  priv->sram + DMAS_RSTART_REG(dma->id));
>> +
>> +	/* set flow control */
>> +	if (bcm6348_iudma_chan_is_rx(dma->id)) {
>> +		u32 val;
>> +
>> +		setbits_be32(priv->base + DMA_CFG_REG,
>> +			     DMA_CFG_FLOWC_ENABLE(dma->id));
>> +
>> +		val = ch_priv->dma_ring_size / 3;
>> +		writel_be(val, priv->base + DMA_FLOWC_THR_LO_REG(dma->id));
>> +
>> +		val = (ch_priv->dma_ring_size * 2) / 3;
>> +		writel_be(val, priv->base + DMA_FLOWC_THR_HI_REG(dma->id));
>> +	}
>> +
>> +	/* set dma max burst */
>> +	writel_be(ch_priv->dma_ring_size,
>> +		  priv->chan + DMAC_BURST_REG(dma->id));
>> +
>> +	/* clear interrupts */
>> +	writel_be(DMAC_IR_DONE_MASK, priv->chan + DMAC_IR_ST_REG(dma->id));
>> +	writel_be(0, priv->chan + DMAC_IR_EN_REG(dma->id));
>> +
>> +	setbits_be32(priv->chan + DMAC_CFG_REG(dma->id), DMAC_CFG_ENABLE_MASK);
>> +
>> +	return 0;
>> +}
> [...]
>
>> +
>> +static int bcm6348_iudma_receive(struct dma *dma, void **dst)
>> +{
>> +	struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev);
>> +	struct bcm6348_chan_priv *ch_priv = priv->ch_priv[dma->id];
>> +	struct bcm6348_dma_desc *dma_desc;
>> +	void __iomem *dma_buff;
>> +	uint16_t status;
>> +	int ret;
>> +
>> +	/* get dma ring descriptor address */
>> +	dma_desc = ch_priv->dma_ring;
>> +	dma_desc += ch_priv->desc_id;
>> +
>> +	/* invalidate cache data */
>> +	invalidate_dcache_range((ulong)dma_desc,
>> +		ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1));
>> +
>> +	/* check dma own */
>> +	if (dma_desc->status & DMAD_ST_OWN_MASK)
>> +		return 0;
>> +
>> +	/* check dma end */
>> +	if (!(dma_desc->status & DMAD_ST_EOP_MASK))
>> +		return -EINVAL;
>> +
>> +	/* get dma buff descriptor address */
>> +	dma_buff = phys_to_virt(dma_desc->address);
>> +
>> +	/* invalidate cache data */
>> +	invalidate_dcache_range((ulong)dma_buff,
>> +				(ulong)(dma_buff + PKTSIZE_ALIGN));
>> +
>> +	/* get dma data */
>> +	*dst = dma_buff;
>> +	ret = dma_desc->length;
>> +
>> +	/* reinit dma descriptor */
>> +	status = dma_desc->status & DMAD_ST_WRAP_MASK;
>> +	status |= DMAD_ST_OWN_MASK;
>> +
>> +	dma_desc->length = PKTSIZE_ALIGN;
>> +	dma_desc->status = status;
>> +
>> +	/* flush cache */
>> +	flush_dcache_range((ulong)dma_desc,
>> +		ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1));
> Could you clarify pls, if you do return dma_desc to RX ring here or not?
Yes.
>
> if yes, wouldn't it cause potential problem on Net RX path
> 		ret = eth_get_ops(current)->recv(current, flags, &packet);
> ^^ (1) here buffer will be received from DMA ( and pushed back to RX ring ?? )
>
> 		flags = 0;
> 		if (ret > 0)
> 			net_process_received_packet(packet, ret);
> ^^ (2) here it will be passed in Net stack
>
> 		if (ret >= 0 && eth_get_ops(current)->free_pkt)
> 			eth_get_ops(current)->free_pkt(current, packet, ret);
> ^^ at this point it should be safe to return buffer in DMA RX ring.
>
> 		if (ret <= 0)
> 			break;
>
> Can DMA overwrite packet after point (1) while packet is still processed (2)?
I don't think so, because as far as I know u-boot is not processing more 
than one packet at once, is it?
But yeah, I see your point and if it does process more than one packet 
at once this is not the proper way to do that.
I will use free_pkt in next version and lock the packet until it's 
processed.
>
>> +
>> +	/* set flow control buffer alloc */
>> +	writel_be(1, priv->base + DMA_FLOWC_ALLOC_REG(dma->id));
>> +
>> +	/* enable dma */
>> +	setbits_be32(priv->chan + DMAC_CFG_REG(dma->id), DMAC_CFG_ENABLE_MASK);
>> +
>> +	/* set interrupt */
>> +	writel_be(DMAC_IR_DONE_MASK, priv->chan + DMAC_IR_EN_REG(dma->id));
>> +
>> +	/* increment dma descriptor */
>> +	ch_priv->desc_id = (ch_priv->desc_id + 1) % ch_priv->dma_ring_size;
>> +
>> +	return ret - 4;
>> +}
>> +
>
>
Regards,
Álvaro.
Grygorii Strashko Feb. 23, 2018, 4:57 p.m. UTC | #3
Hi

thanks for your comments.

On 02/22/2018 02:48 PM, Álvaro Fernández Rojas wrote:
> El 22/02/2018 a las 20:50, Grygorii Strashko escribió:
>> On 02/21/2018 10:10 AM, Álvaro Fernández Rojas wrote:
>>> BCM6348 IUDMA controller is present on multiple BMIPS (BCM63xx) SoCs.
>>>
>>> Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
>>> ---
>>>    v3: no changes
>>>    v2: Fix dma rx burst config and select DMA_CHANNELS.
>>>
>>>    drivers/dma/Kconfig         |   9 +
>>>    drivers/dma/Makefile        |   1 +
>>>    drivers/dma/bcm6348-iudma.c | 505 
>>> ++++++++++++++++++++++++++++++++++++++++++++
>>>    3 files changed, 515 insertions(+)
>>>    create mode 100644 drivers/dma/bcm6348-iudma.c
>>>
>> [...]
>>> +static int bcm6348_iudma_enable(struct dma *dma)
>>> +{
>>> +    struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev);
>>> +    struct bcm6348_chan_priv *ch_priv = priv->ch_priv[dma->id];
>>> +    struct bcm6348_dma_desc *dma_desc;
>>> +    uint8_t i;
>>> +
>>> +    /* init dma rings */
>>> +    dma_desc = ch_priv->dma_ring;
>>> +    for (i = 0; i < ch_priv->dma_ring_size; i++) {
>>> +        if (bcm6348_iudma_chan_is_rx(dma->id)) {
>>> +            dma_desc->status = DMAD_ST_OWN_MASK;
>>> +            dma_desc->length = PKTSIZE_ALIGN;
>>> +            dma_desc->address = virt_to_phys(net_rx_packets[i]);
>> You are filling RX queue/ring with buffers defined in Net core.
>> Does it mean that this DMA driver will not be usable for other 
>> purposes, as
>> Net can be compiled out?
> As far as I know, and depending on the specific SoC, BCM63xx IUDMA is 
> used for Ethernet, USB (device only) and xDSL.
> So yes, in u-boot it will be used for ethernet only.
> BTW, my first attempt didn't use net_rx_packets, but I saw that in 
> pic32_eth implementation and dropped the dma specific buffers. I will 
> add them again ;).

it is really net specific :)

>>
>> Wouldn't it be reasonable to have some sort of .receive_prepare() 
>> callback in
>> DMA dma_ops, so DMA user can control which buffers to push in RX DMA 
>> channel?
>> And it also can be used in eth_ops.free_pkt() callback (see below).
> Yes, probably, but maybe we can achieve that without adding another call.
>>

I'm looking at this patch set from our HW point of view. In my case,
DMA channel can be used with different IPs (not only networking), so
it would be really great if DMA user can pass RX buffers in DMA driver -
network driver can use net_rx_packets, other drivers might use own buffers.
So hard-codding RX buffers in DMA driver looks like not a good choice.

>>> +
>>> +    /* flush cache */
>>> +    flush_dcache_range((ulong)dma_desc,
>>> +        ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1));
>> Could you clarify pls, if you do return dma_desc to RX ring here or not?
> Yes.
>>
>> if yes, wouldn't it cause potential problem on Net RX path
>>         ret = eth_get_ops(current)->recv(current, flags, &packet);
>> ^^ (1) here buffer will be received from DMA ( and pushed back to RX 
>> ring ?? )
>>
>>         flags = 0;
>>         if (ret > 0)
>>             net_process_received_packet(packet, ret);
>> ^^ (2) here it will be passed in Net stack
>>
>>         if (ret >= 0 && eth_get_ops(current)->free_pkt)
>>             eth_get_ops(current)->free_pkt(current, packet, ret);
>> ^^ at this point it should be safe to return buffer in DMA RX ring.
>>
>>         if (ret <= 0)
>>             break;
>>
>> Can DMA overwrite packet after point (1) while packet is still 
>> processed (2)?
> I don't think so, because as far as I know u-boot is not processing more 
> than one packet at once, is it?

u-boot can't process more than one packet, but dma does. if buffer returned
to DMA and there are some traffic on the line - DMA can potentially refill 
all buffers in its RX ring while u-boot still processing one packet.


> But yeah, I see your point and if it does process more than one packet 
> at once this is not the proper way to do that.
> I will use free_pkt in next version and lock the packet until it's 
> processed.
Álvaro Fernández Rojas March 3, 2018, 9:06 a.m. UTC | #4
Hi Grygorii,

El 23/02/2018 a las 17:57, Grygorii Strashko escribió:
> Hi
>
> thanks for your comments.
>
> On 02/22/2018 02:48 PM, Álvaro Fernández Rojas wrote:
>> El 22/02/2018 a las 20:50, Grygorii Strashko escribió:
>>> On 02/21/2018 10:10 AM, Álvaro Fernández Rojas wrote:
>>>> BCM6348 IUDMA controller is present on multiple BMIPS (BCM63xx) SoCs.
>>>>
>>>> Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
>>>> ---
>>>>     v3: no changes
>>>>     v2: Fix dma rx burst config and select DMA_CHANNELS.
>>>>
>>>>     drivers/dma/Kconfig         |   9 +
>>>>     drivers/dma/Makefile        |   1 +
>>>>     drivers/dma/bcm6348-iudma.c | 505
>>>> ++++++++++++++++++++++++++++++++++++++++++++
>>>>     3 files changed, 515 insertions(+)
>>>>     create mode 100644 drivers/dma/bcm6348-iudma.c
>>>>
>>> [...]
>>>> +static int bcm6348_iudma_enable(struct dma *dma)
>>>> +{
>>>> +    struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev);
>>>> +    struct bcm6348_chan_priv *ch_priv = priv->ch_priv[dma->id];
>>>> +    struct bcm6348_dma_desc *dma_desc;
>>>> +    uint8_t i;
>>>> +
>>>> +    /* init dma rings */
>>>> +    dma_desc = ch_priv->dma_ring;
>>>> +    for (i = 0; i < ch_priv->dma_ring_size; i++) {
>>>> +        if (bcm6348_iudma_chan_is_rx(dma->id)) {
>>>> +            dma_desc->status = DMAD_ST_OWN_MASK;
>>>> +            dma_desc->length = PKTSIZE_ALIGN;
>>>> +            dma_desc->address = virt_to_phys(net_rx_packets[i]);
>>> You are filling RX queue/ring with buffers defined in Net core.
>>> Does it mean that this DMA driver will not be usable for other
>>> purposes, as
>>> Net can be compiled out?
>> As far as I know, and depending on the specific SoC, BCM63xx IUDMA is
>> used for Ethernet, USB (device only) and xDSL.
>> So yes, in u-boot it will be used for ethernet only.
>> BTW, my first attempt didn't use net_rx_packets, but I saw that in
>> pic32_eth implementation and dropped the dma specific buffers. I will
>> add them again ;).
> it is really net specific :)
>
>>> Wouldn't it be reasonable to have some sort of .receive_prepare()
>>> callback in
>>> DMA dma_ops, so DMA user can control which buffers to push in RX DMA
>>> channel?
>>> And it also can be used in eth_ops.free_pkt() callback (see below).
>> Yes, probably, but maybe we can achieve that without adding another call.
> I'm looking at this patch set from our HW point of view. In my case,
> DMA channel can be used with different IPs (not only networking), so
> it would be really great if DMA user can pass RX buffers in DMA driver -
> network driver can use net_rx_packets, other drivers might use own buffers.
> So hard-codding RX buffers in DMA driver looks like not a good choice.
>
>>>> +
>>>> +    /* flush cache */
>>>> +    flush_dcache_range((ulong)dma_desc,
>>>> +        ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1));
>>> Could you clarify pls, if you do return dma_desc to RX ring here or not?
>> Yes.
>>> if yes, wouldn't it cause potential problem on Net RX path
>>>          ret = eth_get_ops(current)->recv(current, flags, &packet);
>>> ^^ (1) here buffer will be received from DMA ( and pushed back to RX
>>> ring ?? )
>>>
>>>          flags = 0;
>>>          if (ret > 0)
>>>              net_process_received_packet(packet, ret);
>>> ^^ (2) here it will be passed in Net stack
>>>
>>>          if (ret >= 0 && eth_get_ops(current)->free_pkt)
>>>              eth_get_ops(current)->free_pkt(current, packet, ret);
>>> ^^ at this point it should be safe to return buffer in DMA RX ring.
>>>
>>>          if (ret <= 0)
>>>              break;
>>>
>>> Can DMA overwrite packet after point (1) while packet is still
>>> processed (2)?
>> I don't think so, because as far as I know u-boot is not processing more
>> than one packet at once, is it?
> u-boot can't process more than one packet, but dma does. if buffer returned
> to DMA and there are some traffic on the line - DMA can potentially refill
> all buffers in its RX ring while u-boot still processing one packet.
I just sent a new version of the patches which doesn't use 
net_rx_packets in bcm6348-iudma.
However, after several tests it turns out that dma received buffers must 
be processed as soon as possible in order to avoid flow control issues.
Considering that, releasing the buffer by using free_pkt after the 
packet is processed isn't a valid option, since it makes ethernet unusable.
That's why I decided to allocate a dynamic buffer on bcm6348-iudma, 
saving the received packets on net_rx_packets and immediately returning 
the dma descriptor to the RX ring.

However, you can still add dma_receive_prepare to dma ops if you need it 
for your HW.
>
>
>> But yeah, I see your point and if it does process more than one packet
>> at once this is not the proper way to do that.
>> I will use free_pkt in next version and lock the packet until it's
>> processed.
>
Regards,
Álvaro.
diff mbox series

Patch

diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 21b2c0dcaa..9afa158b51 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -19,6 +19,15 @@  config DMA_CHANNELS
 	  Enable channels support for DMA. Some DMA controllers have multiple
 	  channels which can either transfer data to/from different devices.
 
+config BCM6348_IUDMA
+	bool "BCM6348 IUDMA driver"
+	depends on ARCH_BMIPS
+	select DMA_CHANNELS
+	help
+	  Enable the BCM6348 IUDMA driver.
+	  This driver support data transfer from devices to
+	  memory and from memory to devices.
+
 config TI_EDMA3
 	bool "TI EDMA3 driver"
 	help
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 39b78b2a3d..b2b4147349 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -9,6 +9,7 @@  obj-$(CONFIG_DMA) += dma-uclass.o
 
 obj-$(CONFIG_FSLDMAFEC) += MCD_tasksInit.o MCD_dmaApi.o MCD_tasks.o
 obj-$(CONFIG_APBH_DMA) += apbh_dma.o
+obj-$(CONFIG_BCM6348_IUDMA) += bcm6348-iudma.o
 obj-$(CONFIG_FSL_DMA) += fsl_dma.o
 obj-$(CONFIG_TI_KSNAV) += keystone_nav.o keystone_nav_cfg.o
 obj-$(CONFIG_TI_EDMA3) += ti-edma3.o
diff --git a/drivers/dma/bcm6348-iudma.c b/drivers/dma/bcm6348-iudma.c
new file mode 100644
index 0000000000..8016a58be2
--- /dev/null
+++ b/drivers/dma/bcm6348-iudma.c
@@ -0,0 +1,505 @@ 
+/*
+ * Copyright (C) 2018 Álvaro Fernández Rojas <noltari@gmail.com>
+ *
+ * Derived from linux/drivers/dma/bcm63xx-iudma.c:
+ *	Copyright (C) 2015 Simon Arlott <simon@fire.lp0.eu>
+ *
+ * Derived from linux/drivers/net/ethernet/broadcom/bcm63xx_enet.c:
+ *	Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
+ *
+ * Derived from bcm963xx_4.12L.06B_consumer/shared/opensource/include/bcm963xx/63268_map_part.h:
+ *	Copyright (C) 2000-2010 Broadcom Corporation
+ *
+ * Derived from bcm963xx_4.12L.06B_consumer/bcmdrivers/opensource/net/enet/impl4/bcmenet.c:
+ *	Copyright (C) 2010 Broadcom Corporation
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <dma-uclass.h>
+#include <memalign.h>
+#include <net.h>
+#include <reset.h>
+#include <asm/io.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define ALIGN_END_ADDR(type, ptr, size)	\
+	((unsigned long)(ptr) + roundup((size) * sizeof(type), \
+	 ARCH_DMA_MINALIGN))
+
+/* DMA Channels */
+#define DMA_CHAN_FLOWC(x)		((x) >> 1)
+#define DMA_CHAN_FLOWC_MAX		8
+#define DMA_CHAN_MAX			16
+#define DMA_CHAN_SIZE			0x10
+#define DMA_CHAN_TOUT			500
+
+/* DMA Global Configuration register */
+#define DMA_CFG_REG			0x00
+#define DMA_CFG_ENABLE_SHIFT		0
+#define DMA_CFG_ENABLE_MASK		(1 << DMA_CFG_ENABLE_SHIFT)
+#define DMA_CFG_FLOWC_ENABLE(x)		BIT(DMA_CHAN_FLOWC(x) + 1)
+#define DMA_CFG_NCHANS_SHIFT		24
+#define DMA_CFG_NCHANS_MASK		(0xf << DMA_CFG_NCHANS_SHIFT)
+
+/* DMA Global Flow Control Threshold registers */
+#define DMA_FLOWC_THR_LO_REG(x)		(0x04 + DMA_CHAN_FLOWC(x) * 0x0c)
+#define DMA_FLOWC_THR_LO_SHIFT		0
+#define DMA_FLOWC_THR_LO_MASK		(5 << DMA_FLOWC_THR_LO_SHIFT)
+
+#define DMA_FLOWC_THR_HI_REG(x)		(0x08 + DMA_CHAN_FLOWC(x) * 0x0c)
+#define DMA_FLOWC_THR_HI_SHIFT		0
+#define DMA_FLOWC_THR_HI_MASK		(10 << DMA_FLOWC_THR_HI_SHIFT)
+
+/* DMA Global Flow Control Buffer Allocation registers */
+#define DMA_FLOWC_ALLOC_REG(x)		(0x0c + DMA_CHAN_FLOWC(x) * 0x0c)
+#define DMA_FLOWC_ALLOC_FORCE_SHIFT	31
+#define DMA_FLOWC_ALLOC_FORCE_MASK	(1 << DMA_FLOWC_ALLOC_FORCE_SHIFT)
+
+/* DMA Global Reset register */
+#define DMA_RST_REG			0x34
+#define DMA_RST_CHAN_SHIFT		0
+#define DMA_RST_CHAN_MASK(x)		(1 << x)
+
+/* DMA Channel Configuration register */
+#define DMAC_CFG_REG(x)			(DMA_CHAN_SIZE * (x) + 0x00)
+#define DMAC_CFG_ENABLE_SHIFT		0
+#define DMAC_CFG_ENABLE_MASK		(1 << DMAC_CFG_ENABLE_SHIFT)
+#define DMAC_CFG_PKT_HALT_SHIFT		1
+#define DMAC_CFG_PKT_HALT_MASK		(1 << DMAC_CFG_PKT_HALT_SHIFT)
+#define DMAC_CFG_BRST_HALT_SHIFT	2
+#define DMAC_CFG_BRST_HALT_MASK		(1 << DMAC_CFG_BRST_HALT_SHIFT)
+
+/* DMA Channel Interrupts registers */
+#define DMAC_IR_ST_REG(x)		(DMA_CHAN_SIZE * (x) + 0x04)
+#define DMAC_IR_EN_REG(x)		(DMA_CHAN_SIZE * (x) + 0x08)
+
+#define DMAC_IR_DONE_SHIFT		2
+#define DMAC_IR_DONE_MASK		(1 << DMAC_IR_DONE_SHIFT)
+
+/* DMA Channel Max Burst Length register */
+#define DMAC_BURST_REG(x)		(DMA_CHAN_SIZE * (x) + 0x0c)
+#define DMAC_BURST_MAX_SHIFT		0
+#define DMAC_BURST_MAX_MASK		(16 << DMAC_BURST_MAX_SHIFT)
+
+/* DMA SRAM Descriptor Ring Start register */
+#define DMAS_RSTART_REG(x)		(DMA_CHAN_SIZE * (x) + 0x00)
+
+/* DMA SRAM State/Bytes done/ring offset register */
+#define DMAS_STATE_DATA_REG(x)		(DMA_CHAN_SIZE * (x) + 0x04)
+
+/* DMA SRAM Buffer Descriptor status and length register */
+#define DMAS_DESC_LEN_STATUS_REG(x)	(DMA_CHAN_SIZE * (x) + 0x08)
+
+/* DMA SRAM Buffer Descriptor status and length register */
+#define DMAS_DESC_BASE_BUFPTR_REG(x)	(DMA_CHAN_SIZE * (x) + 0x0c)
+
+struct bcm6348_dma_desc {
+	uint16_t length;
+
+	uint16_t status;
+#define DMAD_ST_CRC_SHIFT	8
+#define DMAD_ST_CRC_MASK	(1 << DMAD_ST_CRC_SHIFT)
+#define DMAD_ST_WRAP_SHIFT	12
+#define DMAD_ST_WRAP_MASK	(1 << DMAD_ST_WRAP_SHIFT)
+#define DMAD_ST_SOP_SHIFT	13
+#define DMAD_ST_SOP_MASK	(1 << DMAD_ST_SOP_SHIFT)
+#define DMAD_ST_EOP_SHIFT	14
+#define DMAD_ST_EOP_MASK	(1 << DMAD_ST_EOP_SHIFT)
+#define DMAD_ST_OWN_SHIFT	15
+#define DMAD_ST_OWN_MASK	(1 << DMAD_ST_OWN_SHIFT)
+
+	uint32_t address;
+} __attribute__((aligned(1)));
+
+struct bcm6348_chan_priv {
+	void __iomem *dma_ring;
+	uint8_t dma_ring_size;
+	uint8_t desc_id;
+};
+
+struct bcm6348_iudma_priv {
+	void __iomem *base;
+	void __iomem *chan;
+	void __iomem *sram;
+	struct bcm6348_chan_priv **ch_priv;
+	uint8_t n_channels;
+};
+
+static bool bcm6348_iudma_chan_is_rx(uint8_t ch)
+{
+	return !(ch & 1);
+}
+
+static void bcm6348_iudma_chan_stop(struct bcm6348_iudma_priv *priv,
+				    uint8_t ch)
+{
+	unsigned int timeout = DMA_CHAN_TOUT;
+
+	/* disable dma channel interrupts */
+	writel_be(0, priv->chan + DMAC_IR_EN_REG(ch));
+
+	do {
+		uint32_t cfg, halt;
+
+		if (timeout > DMA_CHAN_TOUT / 2)
+			halt = DMAC_CFG_PKT_HALT_MASK;
+		else
+			halt = DMAC_CFG_BRST_HALT_MASK;
+
+		/* try to stop dma channel */
+		writel_be(halt, priv->chan + DMAC_CFG_REG(ch));
+		mb();
+
+		/* check if channel was stopped */
+		cfg = readl_be(priv->chan + DMAC_CFG_REG(ch));
+		if (!(cfg & DMAC_CFG_ENABLE_MASK))
+			break;
+
+		udelay(1);
+	} while (--timeout);
+
+	if (!timeout)
+		pr_err("unable to stop channel %u\n", ch);
+
+	/* reset dma channel */
+	setbits_be32(priv->base + DMA_RST_REG, DMA_RST_CHAN_MASK(ch));
+	clrbits_be32(priv->base + DMA_RST_REG, DMA_RST_CHAN_MASK(ch));
+}
+
+static int bcm6348_iudma_disable(struct dma *dma)
+{
+	struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev);
+
+	bcm6348_iudma_chan_stop(priv, dma->id);
+
+	if (bcm6348_iudma_chan_is_rx(dma->id))
+		writel_be(DMA_FLOWC_ALLOC_FORCE_MASK,
+			  DMA_FLOWC_ALLOC_REG(dma->id));
+
+	return 0;
+}
+
+static int bcm6348_iudma_enable(struct dma *dma)
+{
+	struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev);
+	struct bcm6348_chan_priv *ch_priv = priv->ch_priv[dma->id];
+	struct bcm6348_dma_desc *dma_desc;
+	uint8_t i;
+
+	/* init dma rings */
+	dma_desc = ch_priv->dma_ring;
+	for (i = 0; i < ch_priv->dma_ring_size; i++) {
+		if (bcm6348_iudma_chan_is_rx(dma->id)) {
+			dma_desc->status = DMAD_ST_OWN_MASK;
+			dma_desc->length = PKTSIZE_ALIGN;
+			dma_desc->address = virt_to_phys(net_rx_packets[i]);
+		} else {
+			dma_desc->status = 0;
+			dma_desc->length = 0;
+			dma_desc->address = 0;
+		}
+
+		if (i == ch_priv->dma_ring_size - 1)
+			dma_desc->status |= DMAD_ST_WRAP_MASK;
+
+		if (bcm6348_iudma_chan_is_rx(dma->id))
+			writel_be(1,
+				  priv->base + DMA_FLOWC_ALLOC_REG(dma->id));
+
+		dma_desc++;
+	}
+
+	/* init to first descriptor */
+	ch_priv->desc_id = 0;
+
+	/* force cache writeback */
+	flush_dcache_range((ulong)ch_priv->dma_ring,
+		ALIGN_END_ADDR(struct bcm6348_dma_desc, ch_priv->dma_ring,
+			       ch_priv->dma_ring_size));
+
+	/* clear sram */
+	writel_be(0, priv->sram + DMAS_STATE_DATA_REG(dma->id));
+	writel_be(0, priv->sram + DMAS_DESC_LEN_STATUS_REG(dma->id));
+	writel_be(0, priv->sram + DMAS_DESC_BASE_BUFPTR_REG(dma->id));
+
+	/* set dma ring start */
+	writel_be(virt_to_phys(ch_priv->dma_ring),
+		  priv->sram + DMAS_RSTART_REG(dma->id));
+
+	/* set flow control */
+	if (bcm6348_iudma_chan_is_rx(dma->id)) {
+		u32 val;
+
+		setbits_be32(priv->base + DMA_CFG_REG,
+			     DMA_CFG_FLOWC_ENABLE(dma->id));
+
+		val = ch_priv->dma_ring_size / 3;
+		writel_be(val, priv->base + DMA_FLOWC_THR_LO_REG(dma->id));
+
+		val = (ch_priv->dma_ring_size * 2) / 3;
+		writel_be(val, priv->base + DMA_FLOWC_THR_HI_REG(dma->id));
+	}
+
+	/* set dma max burst */
+	writel_be(ch_priv->dma_ring_size,
+		  priv->chan + DMAC_BURST_REG(dma->id));
+
+	/* clear interrupts */
+	writel_be(DMAC_IR_DONE_MASK, priv->chan + DMAC_IR_ST_REG(dma->id));
+	writel_be(0, priv->chan + DMAC_IR_EN_REG(dma->id));
+
+	setbits_be32(priv->chan + DMAC_CFG_REG(dma->id), DMAC_CFG_ENABLE_MASK);
+
+	return 0;
+}
+
+static int bcm6348_iudma_request(struct dma *dma)
+{
+	struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev);
+	struct bcm6348_chan_priv *ch_priv;
+
+	/* check if channel is valid */
+	if (dma->id >= priv->n_channels)
+		return -ENODEV;
+
+	/* alloc channel private data */
+	priv->ch_priv[dma->id] = calloc(1, sizeof(struct bcm6348_chan_priv));
+	if (!priv->ch_priv[dma->id])
+		return -ENOMEM;
+	ch_priv = priv->ch_priv[dma->id];
+
+	/* alloc dma ring */
+	if (bcm6348_iudma_chan_is_rx(dma->id))
+		ch_priv->dma_ring_size = PKTBUFSRX;
+	else
+		ch_priv->dma_ring_size = 1;
+	ch_priv->dma_ring =
+		malloc_cache_aligned(sizeof(struct bcm6348_dma_desc) *
+				     ch_priv->dma_ring_size);
+	if (!ch_priv->dma_ring)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int bcm6348_iudma_receive(struct dma *dma, void **dst)
+{
+	struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev);
+	struct bcm6348_chan_priv *ch_priv = priv->ch_priv[dma->id];
+	struct bcm6348_dma_desc *dma_desc;
+	void __iomem *dma_buff;
+	uint16_t status;
+	int ret;
+
+	/* get dma ring descriptor address */
+	dma_desc = ch_priv->dma_ring;
+	dma_desc += ch_priv->desc_id;
+
+	/* invalidate cache data */
+	invalidate_dcache_range((ulong)dma_desc,
+		ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1));
+
+	/* check dma own */
+	if (dma_desc->status & DMAD_ST_OWN_MASK)
+		return 0;
+
+	/* check dma end */
+	if (!(dma_desc->status & DMAD_ST_EOP_MASK))
+		return -EINVAL;
+
+	/* get dma buff descriptor address */
+	dma_buff = phys_to_virt(dma_desc->address);
+
+	/* invalidate cache data */
+	invalidate_dcache_range((ulong)dma_buff,
+				(ulong)(dma_buff + PKTSIZE_ALIGN));
+
+	/* get dma data */
+	*dst = dma_buff;
+	ret = dma_desc->length;
+
+	/* reinit dma descriptor */
+	status = dma_desc->status & DMAD_ST_WRAP_MASK;
+	status |= DMAD_ST_OWN_MASK;
+
+	dma_desc->length = PKTSIZE_ALIGN;
+	dma_desc->status = status;
+
+	/* flush cache */
+	flush_dcache_range((ulong)dma_desc,
+		ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1));
+
+	/* set flow control buffer alloc */
+	writel_be(1, priv->base + DMA_FLOWC_ALLOC_REG(dma->id));
+
+	/* enable dma */
+	setbits_be32(priv->chan + DMAC_CFG_REG(dma->id), DMAC_CFG_ENABLE_MASK);
+
+	/* set interrupt */
+	writel_be(DMAC_IR_DONE_MASK, priv->chan + DMAC_IR_EN_REG(dma->id));
+
+	/* increment dma descriptor */
+	ch_priv->desc_id = (ch_priv->desc_id + 1) % ch_priv->dma_ring_size;
+
+	return ret - 4;
+}
+
+static int bcm6348_iudma_send(struct dma *dma, void *src, size_t len)
+{
+	struct bcm6348_iudma_priv *priv = dev_get_priv(dma->dev);
+	struct bcm6348_chan_priv *ch_priv = priv->ch_priv[dma->id];
+	struct bcm6348_dma_desc *dma_desc;
+	uint16_t val;
+
+	/* get dma ring descriptor address */
+	dma_desc = ch_priv->dma_ring;
+	dma_desc += ch_priv->desc_id;
+
+	dma_desc->address = virt_to_phys(src);
+
+	/* config dma descriptor */
+	val = (DMAD_ST_OWN_MASK |
+	       DMAD_ST_EOP_MASK |
+	       DMAD_ST_CRC_MASK |
+	       DMAD_ST_SOP_MASK);
+	if (ch_priv->desc_id == ch_priv->dma_ring_size - 1)
+		val |= DMAD_ST_WRAP_MASK;
+
+	dma_desc->length = len;
+	dma_desc->status = val;
+
+	/* flush cache */
+	flush_dcache_range((ulong)src, (ulong)src + PKTSIZE_ALIGN);
+
+	/* flush cache */
+	flush_dcache_range((ulong)dma_desc,
+		ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1));
+
+	/* enable dma */
+	setbits_be32(priv->chan + DMAC_CFG_REG(dma->id), DMAC_CFG_ENABLE_MASK);
+
+	/* set interrupt */
+	writel_be(DMAC_IR_DONE_MASK, priv->chan + DMAC_IR_EN_REG(dma->id));
+
+	/* poll dma status */
+	do {
+		/* invalidate cache */
+		invalidate_dcache_range((ulong)dma_desc,
+			ALIGN_END_ADDR(struct bcm6348_dma_desc, dma_desc, 1));
+
+		if (!(dma_desc->status & DMAD_ST_OWN_MASK))
+			break;
+	} while(1);
+
+	/* increment dma descriptor */
+	ch_priv->desc_id = (ch_priv->desc_id + 1) % ch_priv->dma_ring_size;
+
+	return 0;
+}
+
+static const struct dma_ops bcm6348_iudma_ops = {
+	.disable = bcm6348_iudma_disable,
+	.enable = bcm6348_iudma_enable,
+	.request = bcm6348_iudma_request,
+	.receive = bcm6348_iudma_receive,
+	.send = bcm6348_iudma_send,
+};
+
+static const struct udevice_id bcm6348_iudma_ids[] = {
+	{ .compatible = "brcm,bcm6348-iudma", },
+	{ /* sentinel */ }
+};
+
+static int bcm6348_iudma_probe(struct udevice *dev)
+{
+	struct dma_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+	struct bcm6348_iudma_priv *priv = dev_get_priv(dev);
+	fdt_addr_t addr;
+	uint8_t ch;
+	int i;
+
+	uc_priv->supported = DMA_SUPPORTS_DEV_TO_MEM |
+			     DMA_SUPPORTS_MEM_TO_DEV;
+
+	/* try to enable clocks */
+	for (i = 0; ; i++) {
+		struct clk clk;
+		int ret;
+
+		ret = clk_get_by_index(dev, i, &clk);
+		if (ret < 0)
+			break;
+		if (clk_enable(&clk))
+			pr_err("failed to enable clock %d\n", i);
+		clk_free(&clk);
+	}
+
+	/* try to perform resets */
+	for (i = 0; ; i++) {
+		struct reset_ctl reset;
+		int ret;
+
+		ret = reset_get_by_index(dev, i, &reset);
+		if (ret < 0)
+			break;
+		if (reset_deassert(&reset))
+			pr_err("failed to deassert reset %d\n", i);
+		reset_free(&reset);
+	}
+
+	/* dma global base address */
+	addr = devfdt_get_addr_name(dev, "dma");
+	if (addr == FDT_ADDR_T_NONE)
+		return -EINVAL;
+	priv->base = ioremap(addr, 0);
+
+	/* dma channels base address */
+	addr = devfdt_get_addr_name(dev, "dma-channels");
+	if (addr == FDT_ADDR_T_NONE)
+		return -EINVAL;
+	priv->chan = ioremap(addr, 0);
+
+	/* dma sram base address */
+	addr = devfdt_get_addr_name(dev, "dma-sram");
+	if (addr == FDT_ADDR_T_NONE)
+		return -EINVAL;
+	priv->sram = ioremap(addr, 0);
+
+	/* disable dma controller */
+	clrbits_be32(priv->base + DMA_CFG_REG, DMA_CFG_ENABLE_MASK);
+
+	/* get number of channels */
+	priv->n_channels = fdtdec_get_uint(gd->fdt_blob, dev_of_offset(dev),
+					   "dma-channels", 8);
+	if (priv->n_channels > DMA_CHAN_MAX)
+		return -EINVAL;
+
+	/* alloc channel private data pointers */
+	priv->ch_priv = calloc(priv->n_channels,
+			       sizeof(struct bcm6348_chan_priv*));
+	if (!priv->ch_priv)
+		return -ENOMEM;
+
+	/* stop dma channels */
+	for (ch = 0; ch < priv->n_channels; ch++)
+		bcm6348_iudma_chan_stop(priv, ch);
+
+	/* enable dma controller */
+	setbits_be32(priv->base + DMA_CFG_REG, DMA_CFG_ENABLE_MASK);
+
+	return 0;
+}
+
+U_BOOT_DRIVER(bcm6348_iudma) = {
+	.name = "bcm6348_iudma",
+	.id = UCLASS_DMA,
+	.of_match = bcm6348_iudma_ids,
+	.ops = &bcm6348_iudma_ops,
+	.priv_auto_alloc_size = sizeof(struct bcm6348_iudma_priv),
+	.probe = bcm6348_iudma_probe,
+};