// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/*
* Microsemi SoCs FDMA driver
*
* Copyright (c) 2021 Microchip
*
* Page recycling code is mostly taken from gianfar driver.
*/
#include <linux/align.h>
#include <linux/bitops.h>
#include <linux/dmapool.h>
#include <linux/dsa/ocelot.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include "ocelot_fdma.h"
#include "ocelot_qs.h"
DEFINE_STATIC_KEY_FALSE(ocelot_fdma_enabled);
static void ocelot_fdma_writel(struct ocelot *ocelot, u32 reg, u32 data)
{
regmap_write(ocelot->targets[FDMA], reg, data);
}
static u32 ocelot_fdma_readl(struct ocelot *ocelot, u32 reg)
{
u32 retval;
regmap_read(ocelot->targets[FDMA], reg, &retval);
return retval;
}
static dma_addr_t ocelot_fdma_idx_dma(dma_addr_t base, u16 idx)
{
return base + idx * sizeof(struct ocelot_fdma_dcb);
}
static u16 ocelot_fdma_dma_idx(dma_addr_t base, dma_addr_t dma)
{
return (dma - base) / sizeof(struct ocelot_fdma_dcb);
}
static u16 ocelot_fdma_idx_next(u16 idx, u16 ring_sz)
{
return unlikely(idx == ring_sz - 1) ? 0 : idx + 1;
}
static u16 ocelot_fdma_idx_prev(u16 idx, u16 ring_sz)
{
return unlikely(idx == 0) ? ring_sz - 1 : idx - 1;
}
static int ocelot_fdma_rx_ring_free(struct ocelot_fdma *fdma)
{
struct ocelot_fdma_rx_ring *rx_ring = &fdma->rx_ring;
if (rx_ring->next_to_use >= rx_ring->next_to_clean)
return OCELOT_FDMA_RX_RING_SIZE -
(rx_ring->next_to_use - rx_ring->next_to_clean) - 1;
else
return rx_ring->next_to_clean - rx_ring->next_to_use - 1;
}
static int ocelot_fdma_tx_ring_free(struct ocelot_fdma *fdma)
{
struct ocelot_fdma_tx_ring *tx_ring = &fdma->tx_ring;
if (tx_ring->next_to_use >= tx_ring->next_to_clean)
return OCELOT_FDMA_TX_RING_SIZE -
(tx_ring->next_to_use - tx_ring->next_to_clean) - 1;
else
return tx_ring->next_to_clean - tx_ring->next_to_use - 1;
}
static bool ocelot_fdma_tx_ring_empty(struct ocelot_fdma *fdma)
{
struct ocelot_fdma_tx_ring *tx_ring = &fdma->tx_ring;
return tx_ring->next_to_clean == tx_ring->next_to_use;
}
static void ocelot_fdma_activate_chan(struct ocelot *ocelot, dma_addr_t dma,
int chan)
{
ocelot_fdma_writel(ocelot, MSCC_FDMA_DCB_LLP(chan), dma);
/* Barrier to force memory writes to DCB to be completed before starting
* the channel.
*/
wmb();
ocelot_fdma_writel(ocelot, MSCC_FDMA_CH_ACTIVATE, BIT(chan));
}
static u32 ocelot_fdma_read_ch_safe(struct ocelot *ocelot)
{
return ocelot_fdma_readl(ocelot, MSCC_FDMA_CH_SAFE);
}
static int ocelot_fdma_wait_chan_safe(struct ocelot *ocelot, int chan)
{
u32 safe;
return readx_poll_timeout_atomic(ocelot_fdma_read_ch_safe, ocelot, safe,
safe & BIT(chan), 0,
OCELOT_FDMA_CH_SAFE_TIMEOUT_US);
}
static void ocelot_fdma_dcb_set_data(struct ocelot_fdma_dcb *dcb,
dma_addr_t dma_addr,
size_t size)
{
u32 offset = dma_addr & 0x3;
dcb->llp = 0;
dcb->datap = ALIGN_DOWN(dma_addr, 4);
dcb->datal = ALIGN_DOWN(size, 4);
dcb->stat = MSCC_FDMA_DCB_STAT_BLOCKO(offset);
}
static bool ocelot_fdma_rx_alloc_page(struct ocelot *ocelot,
struct ocelot_fdma_rx_buf *rxb)
{
dma_addr_t mapping;
struct page *page;
page = dev_alloc_page();
if (unlikely(!page))
return false;
mapping = dma_map_page(ocelot->dev, page, 0, PAGE_SIZE,
DMA_FROM_DEVICE);
if (unlikely(dma_mapping_error(ocelot->dev, mapping))) {
__free_page(page);
return false;
}
rxb->page = page;
rxb->page_offset = 0;
rxb->dma_addr = mapping;
return true;
}
static int ocelot_fdma_alloc_rx_buffs(struct ocelot *ocelot, u16 alloc_cnt)
{
struct ocelot_fdma *fdma = ocelot->fdma;
struct ocelot_fdma_rx_ring *rx_ring;
struct ocelot_fdma_rx_buf *rxb;
struct ocelot_fdma_dcb *dcb;
dma_addr_t dma_addr;
int ret = 0;
u16 idx;
rx_ring = &fdma->rx_ring;