162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2006-2007 PA Semi, Inc
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Common functions for DMA access on PA Semi PWRficient
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/kernel.h>
962306a36Sopenharmony_ci#include <linux/export.h>
1062306a36Sopenharmony_ci#include <linux/pci.h>
1162306a36Sopenharmony_ci#include <linux/slab.h>
1262306a36Sopenharmony_ci#include <linux/of.h>
1362306a36Sopenharmony_ci#include <linux/of_address.h>
1462306a36Sopenharmony_ci#include <linux/of_irq.h>
1562306a36Sopenharmony_ci#include <linux/sched.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <asm/pasemi_dma.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define MAX_TXCH 64
2062306a36Sopenharmony_ci#define MAX_RXCH 64
2162306a36Sopenharmony_ci#define MAX_FLAGS 64
2262306a36Sopenharmony_ci#define MAX_FUN 8
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistatic struct pasdma_status *dma_status;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic void __iomem *iob_regs;
2762306a36Sopenharmony_cistatic void __iomem *mac_regs[6];
2862306a36Sopenharmony_cistatic void __iomem *dma_regs;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic int base_hw_irq;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic int num_txch, num_rxch;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic struct pci_dev *dma_pdev;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/* Bitmaps to handle allocation of channels */
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistatic DECLARE_BITMAP(txch_free, MAX_TXCH);
3962306a36Sopenharmony_cistatic DECLARE_BITMAP(rxch_free, MAX_RXCH);
4062306a36Sopenharmony_cistatic DECLARE_BITMAP(flags_free, MAX_FLAGS);
4162306a36Sopenharmony_cistatic DECLARE_BITMAP(fun_free, MAX_FUN);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/* pasemi_read_iob_reg - read IOB register
4462306a36Sopenharmony_ci * @reg: Register to read (offset into PCI CFG space)
4562306a36Sopenharmony_ci */
4662306a36Sopenharmony_ciunsigned int pasemi_read_iob_reg(unsigned int reg)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	return in_le32(iob_regs+reg);
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_read_iob_reg);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci/* pasemi_write_iob_reg - write IOB register
5362306a36Sopenharmony_ci * @reg: Register to write to (offset into PCI CFG space)
5462306a36Sopenharmony_ci * @val: Value to write
5562306a36Sopenharmony_ci */
5662306a36Sopenharmony_civoid pasemi_write_iob_reg(unsigned int reg, unsigned int val)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	out_le32(iob_regs+reg, val);
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_write_iob_reg);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci/* pasemi_read_mac_reg - read MAC register
6362306a36Sopenharmony_ci * @intf: MAC interface
6462306a36Sopenharmony_ci * @reg: Register to read (offset into PCI CFG space)
6562306a36Sopenharmony_ci */
6662306a36Sopenharmony_ciunsigned int pasemi_read_mac_reg(int intf, unsigned int reg)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	return in_le32(mac_regs[intf]+reg);
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_read_mac_reg);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci/* pasemi_write_mac_reg - write MAC register
7362306a36Sopenharmony_ci * @intf: MAC interface
7462306a36Sopenharmony_ci * @reg: Register to write to (offset into PCI CFG space)
7562306a36Sopenharmony_ci * @val: Value to write
7662306a36Sopenharmony_ci */
7762306a36Sopenharmony_civoid pasemi_write_mac_reg(int intf, unsigned int reg, unsigned int val)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	out_le32(mac_regs[intf]+reg, val);
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_write_mac_reg);
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci/* pasemi_read_dma_reg - read DMA register
8462306a36Sopenharmony_ci * @reg: Register to read (offset into PCI CFG space)
8562306a36Sopenharmony_ci */
8662306a36Sopenharmony_ciunsigned int pasemi_read_dma_reg(unsigned int reg)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	return in_le32(dma_regs+reg);
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_read_dma_reg);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci/* pasemi_write_dma_reg - write DMA register
9362306a36Sopenharmony_ci * @reg: Register to write to (offset into PCI CFG space)
9462306a36Sopenharmony_ci * @val: Value to write
9562306a36Sopenharmony_ci */
9662306a36Sopenharmony_civoid pasemi_write_dma_reg(unsigned int reg, unsigned int val)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	out_le32(dma_regs+reg, val);
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_write_dma_reg);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_cistatic int pasemi_alloc_tx_chan(enum pasemi_dmachan_type type)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	int bit;
10562306a36Sopenharmony_ci	int start, limit;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	switch (type & (TXCHAN_EVT0|TXCHAN_EVT1)) {
10862306a36Sopenharmony_ci	case TXCHAN_EVT0:
10962306a36Sopenharmony_ci		start = 0;
11062306a36Sopenharmony_ci		limit = 10;
11162306a36Sopenharmony_ci		break;
11262306a36Sopenharmony_ci	case TXCHAN_EVT1:
11362306a36Sopenharmony_ci		start = 10;
11462306a36Sopenharmony_ci		limit = MAX_TXCH;
11562306a36Sopenharmony_ci		break;
11662306a36Sopenharmony_ci	default:
11762306a36Sopenharmony_ci		start = 0;
11862306a36Sopenharmony_ci		limit = MAX_TXCH;
11962306a36Sopenharmony_ci		break;
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ciretry:
12262306a36Sopenharmony_ci	bit = find_next_bit(txch_free, MAX_TXCH, start);
12362306a36Sopenharmony_ci	if (bit >= limit)
12462306a36Sopenharmony_ci		return -ENOSPC;
12562306a36Sopenharmony_ci	if (!test_and_clear_bit(bit, txch_free))
12662306a36Sopenharmony_ci		goto retry;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	return bit;
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic void pasemi_free_tx_chan(int chan)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	BUG_ON(test_bit(chan, txch_free));
13462306a36Sopenharmony_ci	set_bit(chan, txch_free);
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic int pasemi_alloc_rx_chan(void)
13862306a36Sopenharmony_ci{
13962306a36Sopenharmony_ci	int bit;
14062306a36Sopenharmony_ciretry:
14162306a36Sopenharmony_ci	bit = find_first_bit(rxch_free, MAX_RXCH);
14262306a36Sopenharmony_ci	if (bit >= MAX_TXCH)
14362306a36Sopenharmony_ci		return -ENOSPC;
14462306a36Sopenharmony_ci	if (!test_and_clear_bit(bit, rxch_free))
14562306a36Sopenharmony_ci		goto retry;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	return bit;
14862306a36Sopenharmony_ci}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic void pasemi_free_rx_chan(int chan)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	BUG_ON(test_bit(chan, rxch_free));
15362306a36Sopenharmony_ci	set_bit(chan, rxch_free);
15462306a36Sopenharmony_ci}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci/* pasemi_dma_alloc_chan - Allocate a DMA channel
15762306a36Sopenharmony_ci * @type: Type of channel to allocate
15862306a36Sopenharmony_ci * @total_size: Total size of structure to allocate (to allow for more
15962306a36Sopenharmony_ci *		room behind the structure to be used by the client)
16062306a36Sopenharmony_ci * @offset: Offset in bytes from start of the total structure to the beginning
16162306a36Sopenharmony_ci *	    of struct pasemi_dmachan. Needed when struct pasemi_dmachan is
16262306a36Sopenharmony_ci *	    not the first member of the client structure.
16362306a36Sopenharmony_ci *
16462306a36Sopenharmony_ci * pasemi_dma_alloc_chan allocates a DMA channel for use by a client. The
16562306a36Sopenharmony_ci * type argument specifies whether it's a RX or TX channel, and in the case
16662306a36Sopenharmony_ci * of TX channels which group it needs to belong to (if any).
16762306a36Sopenharmony_ci *
16862306a36Sopenharmony_ci * Returns a pointer to the total structure allocated on success, NULL
16962306a36Sopenharmony_ci * on failure.
17062306a36Sopenharmony_ci */
17162306a36Sopenharmony_civoid *pasemi_dma_alloc_chan(enum pasemi_dmachan_type type,
17262306a36Sopenharmony_ci			    int total_size, int offset)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	void *buf;
17562306a36Sopenharmony_ci	struct pasemi_dmachan *chan;
17662306a36Sopenharmony_ci	int chno;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	BUG_ON(total_size < sizeof(struct pasemi_dmachan));
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	buf = kzalloc(total_size, GFP_KERNEL);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	if (!buf)
18362306a36Sopenharmony_ci		return NULL;
18462306a36Sopenharmony_ci	chan = buf + offset;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	chan->priv = buf;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	switch (type & (TXCHAN|RXCHAN)) {
18962306a36Sopenharmony_ci	case RXCHAN:
19062306a36Sopenharmony_ci		chno = pasemi_alloc_rx_chan();
19162306a36Sopenharmony_ci		chan->chno = chno;
19262306a36Sopenharmony_ci		chan->irq = irq_create_mapping(NULL,
19362306a36Sopenharmony_ci					       base_hw_irq + num_txch + chno);
19462306a36Sopenharmony_ci		chan->status = &dma_status->rx_sta[chno];
19562306a36Sopenharmony_ci		break;
19662306a36Sopenharmony_ci	case TXCHAN:
19762306a36Sopenharmony_ci		chno = pasemi_alloc_tx_chan(type);
19862306a36Sopenharmony_ci		chan->chno = chno;
19962306a36Sopenharmony_ci		chan->irq = irq_create_mapping(NULL, base_hw_irq + chno);
20062306a36Sopenharmony_ci		chan->status = &dma_status->tx_sta[chno];
20162306a36Sopenharmony_ci		break;
20262306a36Sopenharmony_ci	}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	chan->chan_type = type;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	return chan;
20762306a36Sopenharmony_ci}
20862306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_alloc_chan);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci/* pasemi_dma_free_chan - Free a previously allocated channel
21162306a36Sopenharmony_ci * @chan: Channel to free
21262306a36Sopenharmony_ci *
21362306a36Sopenharmony_ci * Frees a previously allocated channel. It will also deallocate any
21462306a36Sopenharmony_ci * descriptor ring associated with the channel, if allocated.
21562306a36Sopenharmony_ci */
21662306a36Sopenharmony_civoid pasemi_dma_free_chan(struct pasemi_dmachan *chan)
21762306a36Sopenharmony_ci{
21862306a36Sopenharmony_ci	if (chan->ring_virt)
21962306a36Sopenharmony_ci		pasemi_dma_free_ring(chan);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	switch (chan->chan_type & (RXCHAN|TXCHAN)) {
22262306a36Sopenharmony_ci	case RXCHAN:
22362306a36Sopenharmony_ci		pasemi_free_rx_chan(chan->chno);
22462306a36Sopenharmony_ci		break;
22562306a36Sopenharmony_ci	case TXCHAN:
22662306a36Sopenharmony_ci		pasemi_free_tx_chan(chan->chno);
22762306a36Sopenharmony_ci		break;
22862306a36Sopenharmony_ci	}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	kfree(chan->priv);
23162306a36Sopenharmony_ci}
23262306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_free_chan);
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci/* pasemi_dma_alloc_ring - Allocate descriptor ring for a channel
23562306a36Sopenharmony_ci * @chan: Channel for which to allocate
23662306a36Sopenharmony_ci * @ring_size: Ring size in 64-bit (8-byte) words
23762306a36Sopenharmony_ci *
23862306a36Sopenharmony_ci * Allocate a descriptor ring for a channel. Returns 0 on success, errno
23962306a36Sopenharmony_ci * on failure. The passed in struct pasemi_dmachan is updated with the
24062306a36Sopenharmony_ci * virtual and DMA addresses of the ring.
24162306a36Sopenharmony_ci */
24262306a36Sopenharmony_ciint pasemi_dma_alloc_ring(struct pasemi_dmachan *chan, int ring_size)
24362306a36Sopenharmony_ci{
24462306a36Sopenharmony_ci	BUG_ON(chan->ring_virt);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	chan->ring_size = ring_size;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	chan->ring_virt = dma_alloc_coherent(&dma_pdev->dev,
24962306a36Sopenharmony_ci					     ring_size * sizeof(u64),
25062306a36Sopenharmony_ci					     &chan->ring_dma, GFP_KERNEL);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	if (!chan->ring_virt)
25362306a36Sopenharmony_ci		return -ENOMEM;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	return 0;
25662306a36Sopenharmony_ci}
25762306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_alloc_ring);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci/* pasemi_dma_free_ring - Free an allocated descriptor ring for a channel
26062306a36Sopenharmony_ci * @chan: Channel for which to free the descriptor ring
26162306a36Sopenharmony_ci *
26262306a36Sopenharmony_ci * Frees a previously allocated descriptor ring for a channel.
26362306a36Sopenharmony_ci */
26462306a36Sopenharmony_civoid pasemi_dma_free_ring(struct pasemi_dmachan *chan)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	BUG_ON(!chan->ring_virt);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	dma_free_coherent(&dma_pdev->dev, chan->ring_size * sizeof(u64),
26962306a36Sopenharmony_ci			  chan->ring_virt, chan->ring_dma);
27062306a36Sopenharmony_ci	chan->ring_virt = NULL;
27162306a36Sopenharmony_ci	chan->ring_size = 0;
27262306a36Sopenharmony_ci	chan->ring_dma = 0;
27362306a36Sopenharmony_ci}
27462306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_free_ring);
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci/* pasemi_dma_start_chan - Start a DMA channel
27762306a36Sopenharmony_ci * @chan: Channel to start
27862306a36Sopenharmony_ci * @cmdsta: Additional CCMDSTA/TCMDSTA bits to write
27962306a36Sopenharmony_ci *
28062306a36Sopenharmony_ci * Enables (starts) a DMA channel with optional additional arguments.
28162306a36Sopenharmony_ci */
28262306a36Sopenharmony_civoid pasemi_dma_start_chan(const struct pasemi_dmachan *chan, const u32 cmdsta)
28362306a36Sopenharmony_ci{
28462306a36Sopenharmony_ci	if (chan->chan_type == RXCHAN)
28562306a36Sopenharmony_ci		pasemi_write_dma_reg(PAS_DMA_RXCHAN_CCMDSTA(chan->chno),
28662306a36Sopenharmony_ci				     cmdsta | PAS_DMA_RXCHAN_CCMDSTA_EN);
28762306a36Sopenharmony_ci	else
28862306a36Sopenharmony_ci		pasemi_write_dma_reg(PAS_DMA_TXCHAN_TCMDSTA(chan->chno),
28962306a36Sopenharmony_ci				     cmdsta | PAS_DMA_TXCHAN_TCMDSTA_EN);
29062306a36Sopenharmony_ci}
29162306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_start_chan);
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci/* pasemi_dma_stop_chan - Stop a DMA channel
29462306a36Sopenharmony_ci * @chan: Channel to stop
29562306a36Sopenharmony_ci *
29662306a36Sopenharmony_ci * Stops (disables) a DMA channel. This is done by setting the ST bit in the
29762306a36Sopenharmony_ci * CMDSTA register and waiting on the ACT (active) bit to clear, then
29862306a36Sopenharmony_ci * finally disabling the whole channel.
29962306a36Sopenharmony_ci *
30062306a36Sopenharmony_ci * This function will only try for a short while for the channel to stop, if
30162306a36Sopenharmony_ci * it doesn't it will return failure.
30262306a36Sopenharmony_ci *
30362306a36Sopenharmony_ci * Returns 1 on success, 0 on failure.
30462306a36Sopenharmony_ci */
30562306a36Sopenharmony_ci#define MAX_RETRIES 5000
30662306a36Sopenharmony_ciint pasemi_dma_stop_chan(const struct pasemi_dmachan *chan)
30762306a36Sopenharmony_ci{
30862306a36Sopenharmony_ci	int reg, retries;
30962306a36Sopenharmony_ci	u32 sta;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	if (chan->chan_type == RXCHAN) {
31262306a36Sopenharmony_ci		reg = PAS_DMA_RXCHAN_CCMDSTA(chan->chno);
31362306a36Sopenharmony_ci		pasemi_write_dma_reg(reg, PAS_DMA_RXCHAN_CCMDSTA_ST);
31462306a36Sopenharmony_ci		for (retries = 0; retries < MAX_RETRIES; retries++) {
31562306a36Sopenharmony_ci			sta = pasemi_read_dma_reg(reg);
31662306a36Sopenharmony_ci			if (!(sta & PAS_DMA_RXCHAN_CCMDSTA_ACT)) {
31762306a36Sopenharmony_ci				pasemi_write_dma_reg(reg, 0);
31862306a36Sopenharmony_ci				return 1;
31962306a36Sopenharmony_ci			}
32062306a36Sopenharmony_ci			cond_resched();
32162306a36Sopenharmony_ci		}
32262306a36Sopenharmony_ci	} else {
32362306a36Sopenharmony_ci		reg = PAS_DMA_TXCHAN_TCMDSTA(chan->chno);
32462306a36Sopenharmony_ci		pasemi_write_dma_reg(reg, PAS_DMA_TXCHAN_TCMDSTA_ST);
32562306a36Sopenharmony_ci		for (retries = 0; retries < MAX_RETRIES; retries++) {
32662306a36Sopenharmony_ci			sta = pasemi_read_dma_reg(reg);
32762306a36Sopenharmony_ci			if (!(sta & PAS_DMA_TXCHAN_TCMDSTA_ACT)) {
32862306a36Sopenharmony_ci				pasemi_write_dma_reg(reg, 0);
32962306a36Sopenharmony_ci				return 1;
33062306a36Sopenharmony_ci			}
33162306a36Sopenharmony_ci			cond_resched();
33262306a36Sopenharmony_ci		}
33362306a36Sopenharmony_ci	}
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	return 0;
33662306a36Sopenharmony_ci}
33762306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_stop_chan);
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci/* pasemi_dma_alloc_buf - Allocate a buffer to use for DMA
34062306a36Sopenharmony_ci * @chan: Channel to allocate for
34162306a36Sopenharmony_ci * @size: Size of buffer in bytes
34262306a36Sopenharmony_ci * @handle: DMA handle
34362306a36Sopenharmony_ci *
34462306a36Sopenharmony_ci * Allocate a buffer to be used by the DMA engine for read/write,
34562306a36Sopenharmony_ci * similar to dma_alloc_coherent().
34662306a36Sopenharmony_ci *
34762306a36Sopenharmony_ci * Returns the virtual address of the buffer, or NULL in case of failure.
34862306a36Sopenharmony_ci */
34962306a36Sopenharmony_civoid *pasemi_dma_alloc_buf(struct pasemi_dmachan *chan, int size,
35062306a36Sopenharmony_ci			   dma_addr_t *handle)
35162306a36Sopenharmony_ci{
35262306a36Sopenharmony_ci	return dma_alloc_coherent(&dma_pdev->dev, size, handle, GFP_KERNEL);
35362306a36Sopenharmony_ci}
35462306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_alloc_buf);
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci/* pasemi_dma_free_buf - Free a buffer used for DMA
35762306a36Sopenharmony_ci * @chan: Channel the buffer was allocated for
35862306a36Sopenharmony_ci * @size: Size of buffer in bytes
35962306a36Sopenharmony_ci * @handle: DMA handle
36062306a36Sopenharmony_ci *
36162306a36Sopenharmony_ci * Frees a previously allocated buffer.
36262306a36Sopenharmony_ci */
36362306a36Sopenharmony_civoid pasemi_dma_free_buf(struct pasemi_dmachan *chan, int size,
36462306a36Sopenharmony_ci			 dma_addr_t *handle)
36562306a36Sopenharmony_ci{
36662306a36Sopenharmony_ci	dma_free_coherent(&dma_pdev->dev, size, handle, GFP_KERNEL);
36762306a36Sopenharmony_ci}
36862306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_free_buf);
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci/* pasemi_dma_alloc_flag - Allocate a flag (event) for channel synchronization
37162306a36Sopenharmony_ci *
37262306a36Sopenharmony_ci * Allocates a flag for use with channel synchronization (event descriptors).
37362306a36Sopenharmony_ci * Returns allocated flag (0-63), < 0 on error.
37462306a36Sopenharmony_ci */
37562306a36Sopenharmony_ciint pasemi_dma_alloc_flag(void)
37662306a36Sopenharmony_ci{
37762306a36Sopenharmony_ci	int bit;
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ciretry:
38062306a36Sopenharmony_ci	bit = find_first_bit(flags_free, MAX_FLAGS);
38162306a36Sopenharmony_ci	if (bit >= MAX_FLAGS)
38262306a36Sopenharmony_ci		return -ENOSPC;
38362306a36Sopenharmony_ci	if (!test_and_clear_bit(bit, flags_free))
38462306a36Sopenharmony_ci		goto retry;
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	return bit;
38762306a36Sopenharmony_ci}
38862306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_alloc_flag);
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci/* pasemi_dma_free_flag - Deallocates a flag (event)
39262306a36Sopenharmony_ci * @flag: Flag number to deallocate
39362306a36Sopenharmony_ci *
39462306a36Sopenharmony_ci * Frees up a flag so it can be reused for other purposes.
39562306a36Sopenharmony_ci */
39662306a36Sopenharmony_civoid pasemi_dma_free_flag(int flag)
39762306a36Sopenharmony_ci{
39862306a36Sopenharmony_ci	BUG_ON(test_bit(flag, flags_free));
39962306a36Sopenharmony_ci	BUG_ON(flag >= MAX_FLAGS);
40062306a36Sopenharmony_ci	set_bit(flag, flags_free);
40162306a36Sopenharmony_ci}
40262306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_free_flag);
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci/* pasemi_dma_set_flag - Sets a flag (event) to 1
40662306a36Sopenharmony_ci * @flag: Flag number to set active
40762306a36Sopenharmony_ci *
40862306a36Sopenharmony_ci * Sets the flag provided to 1.
40962306a36Sopenharmony_ci */
41062306a36Sopenharmony_civoid pasemi_dma_set_flag(int flag)
41162306a36Sopenharmony_ci{
41262306a36Sopenharmony_ci	BUG_ON(flag >= MAX_FLAGS);
41362306a36Sopenharmony_ci	if (flag < 32)
41462306a36Sopenharmony_ci		pasemi_write_dma_reg(PAS_DMA_TXF_SFLG0, 1 << flag);
41562306a36Sopenharmony_ci	else
41662306a36Sopenharmony_ci		pasemi_write_dma_reg(PAS_DMA_TXF_SFLG1, 1 << flag);
41762306a36Sopenharmony_ci}
41862306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_set_flag);
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci/* pasemi_dma_clear_flag - Sets a flag (event) to 0
42162306a36Sopenharmony_ci * @flag: Flag number to set inactive
42262306a36Sopenharmony_ci *
42362306a36Sopenharmony_ci * Sets the flag provided to 0.
42462306a36Sopenharmony_ci */
42562306a36Sopenharmony_civoid pasemi_dma_clear_flag(int flag)
42662306a36Sopenharmony_ci{
42762306a36Sopenharmony_ci	BUG_ON(flag >= MAX_FLAGS);
42862306a36Sopenharmony_ci	if (flag < 32)
42962306a36Sopenharmony_ci		pasemi_write_dma_reg(PAS_DMA_TXF_CFLG0, 1 << flag);
43062306a36Sopenharmony_ci	else
43162306a36Sopenharmony_ci		pasemi_write_dma_reg(PAS_DMA_TXF_CFLG1, 1 << flag);
43262306a36Sopenharmony_ci}
43362306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_clear_flag);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci/* pasemi_dma_alloc_fun - Allocate a function engine
43662306a36Sopenharmony_ci *
43762306a36Sopenharmony_ci * Allocates a function engine to use for crypto/checksum offload
43862306a36Sopenharmony_ci * Returns allocated engine (0-8), < 0 on error.
43962306a36Sopenharmony_ci */
44062306a36Sopenharmony_ciint pasemi_dma_alloc_fun(void)
44162306a36Sopenharmony_ci{
44262306a36Sopenharmony_ci	int bit;
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ciretry:
44562306a36Sopenharmony_ci	bit = find_first_bit(fun_free, MAX_FLAGS);
44662306a36Sopenharmony_ci	if (bit >= MAX_FLAGS)
44762306a36Sopenharmony_ci		return -ENOSPC;
44862306a36Sopenharmony_ci	if (!test_and_clear_bit(bit, fun_free))
44962306a36Sopenharmony_ci		goto retry;
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	return bit;
45262306a36Sopenharmony_ci}
45362306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_alloc_fun);
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci/* pasemi_dma_free_fun - Deallocates a function engine
45762306a36Sopenharmony_ci * @flag: Engine number to deallocate
45862306a36Sopenharmony_ci *
45962306a36Sopenharmony_ci * Frees up a function engine so it can be used for other purposes.
46062306a36Sopenharmony_ci */
46162306a36Sopenharmony_civoid pasemi_dma_free_fun(int fun)
46262306a36Sopenharmony_ci{
46362306a36Sopenharmony_ci	BUG_ON(test_bit(fun, fun_free));
46462306a36Sopenharmony_ci	BUG_ON(fun >= MAX_FLAGS);
46562306a36Sopenharmony_ci	set_bit(fun, fun_free);
46662306a36Sopenharmony_ci}
46762306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_free_fun);
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_cistatic void *map_onedev(struct pci_dev *p, int index)
47162306a36Sopenharmony_ci{
47262306a36Sopenharmony_ci	struct device_node *dn;
47362306a36Sopenharmony_ci	void __iomem *ret;
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	dn = pci_device_to_OF_node(p);
47662306a36Sopenharmony_ci	if (!dn)
47762306a36Sopenharmony_ci		goto fallback;
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci	ret = of_iomap(dn, index);
48062306a36Sopenharmony_ci	if (!ret)
48162306a36Sopenharmony_ci		goto fallback;
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci	return ret;
48462306a36Sopenharmony_cifallback:
48562306a36Sopenharmony_ci	/* This is hardcoded and ugly, but we have some firmware versions
48662306a36Sopenharmony_ci	 * that don't provide the register space in the device tree. Luckily
48762306a36Sopenharmony_ci	 * they are at well-known locations so we can just do the math here.
48862306a36Sopenharmony_ci	 */
48962306a36Sopenharmony_ci	return ioremap(0xe0000000 + (p->devfn << 12), 0x2000);
49062306a36Sopenharmony_ci}
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci/* pasemi_dma_init - Initialize the PA Semi DMA library
49362306a36Sopenharmony_ci *
49462306a36Sopenharmony_ci * This function initializes the DMA library. It must be called before
49562306a36Sopenharmony_ci * any other function in the library.
49662306a36Sopenharmony_ci *
49762306a36Sopenharmony_ci * Returns 0 on success, errno on failure.
49862306a36Sopenharmony_ci */
49962306a36Sopenharmony_ciint pasemi_dma_init(void)
50062306a36Sopenharmony_ci{
50162306a36Sopenharmony_ci	static DEFINE_SPINLOCK(init_lock);
50262306a36Sopenharmony_ci	struct pci_dev *iob_pdev;
50362306a36Sopenharmony_ci	struct pci_dev *pdev;
50462306a36Sopenharmony_ci	struct resource res;
50562306a36Sopenharmony_ci	struct device_node *dn;
50662306a36Sopenharmony_ci	int i, intf, err = 0;
50762306a36Sopenharmony_ci	unsigned long timeout;
50862306a36Sopenharmony_ci	u32 tmp;
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	if (!machine_is(pasemi))
51162306a36Sopenharmony_ci		return -ENODEV;
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	spin_lock(&init_lock);
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	/* Make sure we haven't already initialized */
51662306a36Sopenharmony_ci	if (dma_pdev)
51762306a36Sopenharmony_ci		goto out;
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci	iob_pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa001, NULL);
52062306a36Sopenharmony_ci	if (!iob_pdev) {
52162306a36Sopenharmony_ci		BUG();
52262306a36Sopenharmony_ci		pr_warn("Can't find I/O Bridge\n");
52362306a36Sopenharmony_ci		err = -ENODEV;
52462306a36Sopenharmony_ci		goto out;
52562306a36Sopenharmony_ci	}
52662306a36Sopenharmony_ci	iob_regs = map_onedev(iob_pdev, 0);
52762306a36Sopenharmony_ci
52862306a36Sopenharmony_ci	dma_pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa007, NULL);
52962306a36Sopenharmony_ci	if (!dma_pdev) {
53062306a36Sopenharmony_ci		BUG();
53162306a36Sopenharmony_ci		pr_warn("Can't find DMA controller\n");
53262306a36Sopenharmony_ci		err = -ENODEV;
53362306a36Sopenharmony_ci		goto out;
53462306a36Sopenharmony_ci	}
53562306a36Sopenharmony_ci	dma_regs = map_onedev(dma_pdev, 0);
53662306a36Sopenharmony_ci	base_hw_irq = virq_to_hw(dma_pdev->irq);
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	pci_read_config_dword(dma_pdev, PAS_DMA_CAP_TXCH, &tmp);
53962306a36Sopenharmony_ci	num_txch = (tmp & PAS_DMA_CAP_TXCH_TCHN_M) >> PAS_DMA_CAP_TXCH_TCHN_S;
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	pci_read_config_dword(dma_pdev, PAS_DMA_CAP_RXCH, &tmp);
54262306a36Sopenharmony_ci	num_rxch = (tmp & PAS_DMA_CAP_RXCH_RCHN_M) >> PAS_DMA_CAP_RXCH_RCHN_S;
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci	intf = 0;
54562306a36Sopenharmony_ci	for (pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa006, NULL);
54662306a36Sopenharmony_ci	     pdev;
54762306a36Sopenharmony_ci	     pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa006, pdev))
54862306a36Sopenharmony_ci		mac_regs[intf++] = map_onedev(pdev, 0);
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	pci_dev_put(pdev);
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	for (pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa005, NULL);
55362306a36Sopenharmony_ci	     pdev;
55462306a36Sopenharmony_ci	     pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa005, pdev))
55562306a36Sopenharmony_ci		mac_regs[intf++] = map_onedev(pdev, 0);
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_ci	pci_dev_put(pdev);
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci	dn = pci_device_to_OF_node(iob_pdev);
56062306a36Sopenharmony_ci	if (dn)
56162306a36Sopenharmony_ci		err = of_address_to_resource(dn, 1, &res);
56262306a36Sopenharmony_ci	if (!dn || err) {
56362306a36Sopenharmony_ci		/* Fallback for old firmware */
56462306a36Sopenharmony_ci		res.start = 0xfd800000;
56562306a36Sopenharmony_ci		res.end = res.start + 0x1000;
56662306a36Sopenharmony_ci	}
56762306a36Sopenharmony_ci	dma_status = ioremap_cache(res.start, resource_size(&res));
56862306a36Sopenharmony_ci	pci_dev_put(iob_pdev);
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	for (i = 0; i < MAX_TXCH; i++)
57162306a36Sopenharmony_ci		__set_bit(i, txch_free);
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	for (i = 0; i < MAX_RXCH; i++)
57462306a36Sopenharmony_ci		__set_bit(i, rxch_free);
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci	timeout = jiffies + HZ;
57762306a36Sopenharmony_ci	pasemi_write_dma_reg(PAS_DMA_COM_RXCMD, 0);
57862306a36Sopenharmony_ci	while (pasemi_read_dma_reg(PAS_DMA_COM_RXSTA) & 1) {
57962306a36Sopenharmony_ci		if (time_after(jiffies, timeout)) {
58062306a36Sopenharmony_ci			pr_warn("Warning: Could not disable RX section\n");
58162306a36Sopenharmony_ci			break;
58262306a36Sopenharmony_ci		}
58362306a36Sopenharmony_ci	}
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci	timeout = jiffies + HZ;
58662306a36Sopenharmony_ci	pasemi_write_dma_reg(PAS_DMA_COM_TXCMD, 0);
58762306a36Sopenharmony_ci	while (pasemi_read_dma_reg(PAS_DMA_COM_TXSTA) & 1) {
58862306a36Sopenharmony_ci		if (time_after(jiffies, timeout)) {
58962306a36Sopenharmony_ci			pr_warn("Warning: Could not disable TX section\n");
59062306a36Sopenharmony_ci			break;
59162306a36Sopenharmony_ci		}
59262306a36Sopenharmony_ci	}
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci	/* setup resource allocations for the different DMA sections */
59562306a36Sopenharmony_ci	tmp = pasemi_read_dma_reg(PAS_DMA_COM_CFG);
59662306a36Sopenharmony_ci	pasemi_write_dma_reg(PAS_DMA_COM_CFG, tmp | 0x18000000);
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	/* enable tx section */
59962306a36Sopenharmony_ci	pasemi_write_dma_reg(PAS_DMA_COM_TXCMD, PAS_DMA_COM_TXCMD_EN);
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci	/* enable rx section */
60262306a36Sopenharmony_ci	pasemi_write_dma_reg(PAS_DMA_COM_RXCMD, PAS_DMA_COM_RXCMD_EN);
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci	for (i = 0; i < MAX_FLAGS; i++)
60562306a36Sopenharmony_ci		__set_bit(i, flags_free);
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ci	for (i = 0; i < MAX_FUN; i++)
60862306a36Sopenharmony_ci		__set_bit(i, fun_free);
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	/* clear all status flags */
61162306a36Sopenharmony_ci	pasemi_write_dma_reg(PAS_DMA_TXF_CFLG0, 0xffffffff);
61262306a36Sopenharmony_ci	pasemi_write_dma_reg(PAS_DMA_TXF_CFLG1, 0xffffffff);
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	pr_info("PA Semi PWRficient DMA library initialized "
61562306a36Sopenharmony_ci		"(%d tx, %d rx channels)\n", num_txch, num_rxch);
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ciout:
61862306a36Sopenharmony_ci	spin_unlock(&init_lock);
61962306a36Sopenharmony_ci	return err;
62062306a36Sopenharmony_ci}
62162306a36Sopenharmony_ciEXPORT_SYMBOL(pasemi_dma_init);
622