// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DMA driver for AMD Queue-based DMA Subsystem
*
* Copyright (C) 2023-2024, Advanced Micro Devices, Inc.
*/
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/dmaengine.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/dma-map-ops.h>
#include <linux/platform_device.h>
#include <linux/platform_data/amd_qdma.h>
#include <linux/regmap.h>
#include "qdma.h"
#define CHAN_STR(q) (((q)->dir == DMA_MEM_TO_DEV) ? "H2C" : "C2H")
#define QDMA_REG_OFF(d, r) ((d)->roffs[r].off)
/* MMIO regmap config for all QDMA registers */
static const struct regmap_config qdma_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
static inline struct qdma_queue *to_qdma_queue(struct dma_chan *chan)
{
return container_of(chan, struct qdma_queue, vchan.chan);
}
static inline struct qdma_mm_vdesc *to_qdma_vdesc(struct virt_dma_desc *vdesc)
{
return container_of(vdesc, struct qdma_mm_vdesc, vdesc);
}
static inline u32 qdma_get_intr_ring_idx(struct qdma_device *qdev)
{
u32 idx;
idx = qdev->qintr_rings[qdev->qintr_ring_idx++].ridx;
qdev->qintr_ring_idx %= qdev->qintr_ring_num;
return idx;
}
static u64 qdma_get_field(const struct qdma_device *qdev, const u32 *data,
enum qdma_reg_fields field)
{
const struct qdma_reg_field *f = &qdev->rfields[field];
u16 low_pos, hi_pos, low_bit, hi_bit;
u64 value = 0, mask;
low_pos = f->lsb / BITS_PER_TYPE(*data);
hi_pos = f->msb / BITS_PER_TYPE(*data);
if (low_pos == hi_pos) {
low_bit = f->lsb % BITS_PER_TYPE(*data);
hi_bit = f->msb % BITS_PER_TYPE(*data);
mask = GENMASK(hi_bit, low_bit);
value = (data[low_pos] & mask) >> low_bit;
} else if (hi_pos == low_pos + 1) {
low_bit = f->lsb % BITS_PER_TYPE(*data);
hi_bit = low_bit + (f->msb - f->lsb);
value = ((u64)data[hi_pos] << BITS_PER_TYPE(*data)) |
data[low_pos];
mask = GENMASK_ULL(hi_bit, low_bit);
value = (value & mask) >> low_bit;
} else {
hi_bit = f->msb % BITS_PER_TYPE(*data);
mask = GENMASK(hi_bit, 0);
value = data[hi_pos] & mask;
low_bit = f->msb - f->lsb - hi_bit;
value <<= low_bit;
low_bit -= 32;
value |= (u64)data[hi_pos - 1] << low_bit;
mask = GENMASK(31, 32 - low_bit);
value |= (data[hi_pos - 2] & mask) >> low_bit;
}
return value;
}
static void qdma_set_field(const struct qdma_device *qdev, u32 *data,
enum qdma_reg_fields field, u64 value)
{
const struct qdma_reg_field *f = &qdev->rfields[field];
u16 low_pos, hi_pos, low_bit;
low_pos = f->lsb / BITS_PER_TYPE(*data);
hi_pos = f->msb / BITS_PER_TYPE(*data);
low_bit = f->lsb % BITS_PER_TYPE(*data);
data[low_pos++] |= value << low_bit;
if (low_pos <= hi_pos)
data[low_pos++] |= (u32)(value >> (32 - low_bit));
if (low_pos <= hi_pos)
data[low_pos] |= (u32)(value >> (64 - low_bit));
}
static inline int qdma_reg_write(const struct qdma_device *qdev,
const u32 *data, enum qdma_regs reg)
{
const struct qdma_reg *r = &qdev->roffs[reg];
int ret;
if (r->count > 1)
ret = regmap_bulk_write(qdev->regmap, r->