// SPDX-License-Identifier: GPL-2.0
/*
* hal.c - DIM2 HAL implementation
* (MediaLB, Device Interface Macro IP, OS62420)
*
* Copyright (C) 2015-2016, Microchip Technology Germany II GmbH & Co. KG
*/
/* Author: Andrey Shvetsov <andrey.shvetsov@k2l.de> */
#include "hal.h"
#include "errors.h"
#include "reg.h"
#include <linux/io.h>
#include <linux/kernel.h>
/*
* Size factor for isochronous DBR buffer.
* Minimal value is 3.
*/
#define ISOC_DBR_FACTOR 3u
/*
* Number of 32-bit units for DBR map.
*
* 1: block size is 512, max allocation is 16K
* 2: block size is 256, max allocation is 8K
* 4: block size is 128, max allocation is 4K
* 8: block size is 64, max allocation is 2K
*
* Min allocated space is block size.
* Max possible allocated space is 32 blocks.
*/
#define DBR_MAP_SIZE 2
/* -------------------------------------------------------------------------- */
/* not configurable area */
#define CDT 0x00
#define ADT 0x40
#define MLB_CAT 0x80
#define AHB_CAT 0x88
#define DBR_SIZE (16 * 1024) /* specified by IP */
#define DBR_BLOCK_SIZE (DBR_SIZE / 32 / DBR_MAP_SIZE)
/* -------------------------------------------------------------------------- */
/* generic helper functions and macros */
static inline u32 bit_mask(u8 position)
{
return (u32)1 << position;
}
static inline bool dim_on_error(u8 error_id, const char *error_message)
{
dimcb_on_error(error_id, error_message);
return false;
}
/* -------------------------------------------------------------------------- */
/* types and local variables */
struct async_tx_dbr {
u8 ch_addr;
u16 rpc;
u16 wpc;
u16 rest_size;
u16 sz_queue[CDT0_RPC_MASK + 1];
};
struct lld_global_vars_t {
bool dim_is_initialized;
bool mcm_is_initialized;
struct dim2_regs __iomem *dim2; /* DIM2 core base address */
struct async_tx_dbr atx_dbr;
u32 fcnt;
u32 dbr_map[DBR_MAP_SIZE];
};
static struct lld_global_vars_t g = { false };
/* -------------------------------------------------------------------------- */
static int dbr_get_mask_size(u16 size)
{
int i;
for (i = 0; i < 6; i++)
if (size <= (DBR_BLOCK_SIZE << i))
return 1 << i;
return 0;
}
/**
* alloc_dbr() - Allocates DBR memory.
* @size: Allocating memory size.
* Returns: Offset in DBR memory by success or DBR_SIZE if out of memory.
*/
static int alloc_dbr(u16 size)
{
int mask_size;
int i, block_idx = 0;
if (size <= 0)
return DBR_SIZE; /* out of memory */
mask_size = dbr_get_mask_size(size);
if (mask_size == 0)
return DBR_SIZE; /* out of memory */
for (i = 0; i < DBR_MAP_SIZE; i++) {
u32 const blocks = DIV_ROUND_UP(size, DBR_BLOCK_SIZE);
u32 mask = ~((~(u32)0) << blocks);
do {
if ((g.dbr_map[i] & mask) == 0) {
g.dbr_map[i] |= mask;
return block_idx * DBR_BLOCK_SIZE;
}
block_idx += mask_size;
/* do shift left with 2 steps in case mask_size == 32 */
mask <<= mask_size - 1;
} while ((mask <<= 1) != 0);
}
return DBR_SIZE; /* out of memory */
}
static void free_dbr(int offs, int size)
{
int block_idx = offs / DBR_BLOCK_SIZE;
u32 const blocks = DIV_ROUND_UP(size, DBR_BLOCK_SIZE);
u32 mask = ~((~(u32)0) << blocks);
mask <<= block_idx % 32;
g.dbr_map[block_idx / 32] &= ~mask;
}
/* -------------------------------------------------------------------------- */
static void dim2_transfer_madr(u32 val)
{
writel(val, &g.dim2->MADR);
/* wait for transfer completion */
while ((readl(&g.dim2->MCTL) & 1) != 1)
continue;
writel(0, &g.dim2->MCTL); /* clear transfer complete */
}
static void dim2_clear_dbr(u16 addr, u16 size)
{
enum { MADR_TB_BIT = 30, MADR_WNR_BIT = 31 };
u16 const end_addr = addr + size;
u32 const cmd = bit_mask(MADR_WNR_BIT) | bit_mask(MADR_TB_BIT);
writel(0, &g.dim2->MCTL); /* clear transfer complete */
writel(0, &g.dim2->MDAT0);
for (; addr < end_addr; addr++)
dim2_transfer_madr(cmd | addr);
}
static u32 dim2_read_ctr(u32 ctr_addr, u16 mdat_idx)
{
dim2_transfer_madr(ctr_addr);
return readl((&g.dim2->MDAT0