/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright (C) 2020 Marvell International Ltd.
 *
 * Interface to the hardware Free Pool Allocator on Octeon chips.
 * These are the legacy models, i.e. prior to CN78XX/CN76XX.
 */

#ifndef __CVMX_FPA1_HW_H__
#define __CVMX_FPA1_HW_H__

#include "cvmx-scratch.h"
#include "cvmx-fpa-defs.h"
#include "cvmx-fpa3.h"

/* Legacy pool range is 0..7 and 8 on CN68XX */
typedef int cvmx_fpa1_pool_t;

#define CVMX_FPA1_NUM_POOLS    8
#define CVMX_FPA1_INVALID_POOL ((cvmx_fpa1_pool_t)-1)
#define CVMX_FPA1_NAME_SIZE    16

/**
 * Structure describing the data format used for stores to the FPA.
 */
typedef union {
	u64 u64;
	struct {
		u64 scraddr : 8;
		u64 len : 8;
		u64 did : 8;
		u64 addr : 40;
	} s;
} cvmx_fpa1_iobdma_data_t;

/*
 * Allocate or reserve the specified fpa pool.
 *
 * @param pool	  FPA pool to allocate/reserve. If -1 it
 *                finds an empty pool to allocate.
 * @return        Alloctaed pool number or CVMX_FPA1_POOL_INVALID
 *                if fails to allocate the pool
 */
cvmx_fpa1_pool_t cvmx_fpa1_reserve_pool(cvmx_fpa1_pool_t pool);

/**
 * Free the specified fpa pool.
 * @param pool	   Pool to free
 * @return         0 for success -1 failure
 */
int cvmx_fpa1_release_pool(cvmx_fpa1_pool_t pool);

static inline void cvmx_fpa1_free(void *ptr, cvmx_fpa1_pool_t pool, u64 num_cache_lines)
{
	cvmx_addr_t newptr;

	newptr.u64 = cvmx_ptr_to_phys(ptr);
	newptr.sfilldidspace.didspace = CVMX_ADDR_DIDSPACE(CVMX_FULL_DID(CVMX_OCT_DID_FPA, pool));
	/* Make sure that any previous writes to memory go out before we free
	 * this buffer.  This also serves as a barrier to prevent GCC from
	 * reordering operations to after the free.
	 */
	CVMX_SYNCWS;
	/* value written is number of cache lines not written back */
	cvmx_write_io(newptr.u64, num_cache_lines);
}

static inline void cvmx_fpa1_free_nosync(void *ptr, cvmx_fpa1_pool_t pool,
					 unsigned int num_cache_lines)
{
	cvmx_addr_t newptr;

	newptr.u64 = cvmx_ptr_to_phys(ptr);
	newptr.sfilldidspace.didspace = CVMX_ADDR_DIDSPACE(CVMX_FULL_DID(CVMX_OCT_DID_FPA, pool));
	/* Prevent GCC from reordering around free */
	asm volatile("" : : : "memory");
	/* value written is number of cache lines not written back */
	cvmx_write_io(newptr.u64, num_cache_lines);
}

/**
 * Enable the FPA for use. Must be performed after any CSR
 * configuration but before any other FPA functions.
 */
static inline void cvmx_fpa1_enable(void)
{
	cvmx_fpa_ctl_status_t status;

	status.u64 = csr_rd(CVMX_FPA_CTL_STATUS);
	if (status.s.enb) {
		/*
		 * CN68XXP1 should not reset the FPA (doing so may break
		 * the SSO, so we may end up enabling it more than once.
		 * Just return and don't spew messages.
		 */
		return;
	}

	status.u64 = 0;
	status.s.enb = 1;
	csr_wr(CVMX_FPA_CTL_STATUS, status.u64);
}

/**
 * Reset FPA to disable. Make sure buffers from all FPA pools are freed
 * before disabling FPA.
 */
static inline void cvmx_fpa1_disable(void)
{
	cvmx_fpa_ctl_status_t status;

	if (OCTEON_IS_MODEL(OCTEON_CN68XX_PASS1))
		return;

	status.u64 = csr_rd(CVMX_FPA_CTL_STATUS);
	status.s.reset = 1;
	csr_wr(CVMX_FPA_CTL_STATUS, status.u64);
}

static inline void *cvmx_fpa1_alloc(cvmx_fpa1_pool_t pool)
{
	u64 address;

	for (;;) {
		address = csr_rd(CVMX_ADDR_DID(CVMX_FULL_DID(CVMX_OCT_DID_FPA, pool)));
		if (cvmx_likely(address)) {
			return cvmx_phys_to_ptr(address);
		} else {
			if (csr_rd(CVMX_FPA_QUEX_AVAILABLE(pool)) > 0)
				udelay(50);
			else
				return NULL;
		}
	}
}

/**
 * Asynchronously get a new block from the FPA
 * @INTERNAL
 *
 * The result of cvmx_fpa_async_alloc() may be retrieved using
 * cvmx_fpa_async_alloc_finish().
 *
 * @param scr_addr Local scratch address to put response in.  This is a byte
 *		   address but must be 8 byte aligned.
 * @param pool      Pool to get the block from
 */
static inline void cvmx_fpa1_async_alloc(u64 scr_addr, cvmx_fpa1_pool_t pool)
{
	cvmx_fpa1_iobdma_data_t data;

	/* Hardware only uses 64 bit aligned locations, so convert from byte
	 * address to 64-bit index
	 */
	data.u64 = 0ull;
	data.s.scraddr = scr_addr >> 3;
	data.s.len = 1;
	data.s.did = CVMX_FULL_DID(CVMX_OCT_DID_FPA, pool);
	data.s.addr = 0;

	cvmx_scratch_write64(scr_addr, 0ull);
	CVMX_SYNCW;
	cvmx_send_single(data.u64);
}

/**
 * Retrieve the result of cvmx_fpa_async_alloc
 * @INTERNAL
 *
 * @param scr_addr The Local scratch address.  Must be the same value
 * passed to cvmx_fpa_async_alloc().
 *
 * @param pool Pool the block came from.  Must be the same value
 * passed to cvmx_fpa_async_alloc.
 *
 * @return Pointer to the block or NULL on failure
 */
static inline void *cvmx_fpa1_async_alloc_finish(u64 scr_addr, cvmx_fpa1_pool_t pool)
{
	u64 address;

	CVMX_SYNCIOBDMA;

	address = cvmx_scratch_read64(scr_addr);
	if (cvmx_likely(address))
		return cvmx_phys_to_ptr(address);
	else
		return cvmx_fpa1_alloc(pool);
}

static inline u64 cvmx_fpa1_get_available(cvmx_fpa1_pool_t pool)
{
	return csr_rd(CVMX_FPA_QUEX_AVAILABLE(pool));
}

#endif /* __CVMX_FPA1_HW_H__ */
