/* SPDX-License-Identifier: LGPL-2.1-only OR BSD-3-Clause */
/******************************************************************************
 *
 * This file is provided under a dual license.  When you use or
 * distribute this software, you may choose to be licensed under
 * version 2.1 of the GNU Lesser General Public License
 * ("LGPLv2.1 License") or BSD License.
 *
 * LGPLv2.1 License
 *
 * Copyright(C) 2019 MediaTek Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html for more
 * details.
 *
 * BSD LICENSE
 *
 * Copyright(C) 2019 MediaTek Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *****************************************************************************/

/*-----------------------------------------------------------------------------
 *
 * $Author$
 * $Date$
 * $RCSfile: x_m4u.h,v $
 * $Revision$
 *
 *---------------------------------------------------------------------------*/

#ifndef X_M4U_H
#define X_M4U_H
#include <linux/dma-mapping.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)
#include <linux/dma-attrs.h>
#else
#define DEFINE_DMA_ATTRS(dma_attr)	unsigned long dma_attr = 0
#define dma_set_attr(flag, dma_attr)    *(dma_attr) |= (flag)
#endif
#include <linux/mtk_iommu.h>

#if defined(CC_SUPPORT_SMI)
#include "../smi/smi_larb.h"
#endif

enum
{
    M4U_ATTR_PHYS_CONTIGUOUS=1,/*physical address is contiguous*/
    M4U_ATTR_NO_CPU_ACCESS=1<<1,/*cpu will not access this area*/
    M4U_ATTR_NO_CPU_SYNC=1<<2,/*cpu does not touch the buffer,skip cpu cache operation*/
};
enum
{
    M4UMEM_FOR_HW_READ=1,
    M4UMEM_FOR_HW_WRITE=1<<1,   
};

#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)
static inline void m4u_attr2dma_attr(UINT32 m4uattr,struct dma_attrs *dma_attr)
#else
static inline void m4u_attr2dma_attr(UINT32 m4uattr, unsigned long *dma_attr)
#endif
{
    if(m4uattr &M4U_ATTR_PHYS_CONTIGUOUS)dma_set_attr(DMA_ATTR_FORCE_CONTIGUOUS,dma_attr);
    if(m4uattr &M4U_ATTR_NO_CPU_ACCESS)dma_set_attr(DMA_ATTR_NO_KERNEL_MAPPING,dma_attr);
    if(m4uattr &M4U_ATTR_NO_CPU_SYNC)dma_set_attr(DMA_ATTR_SKIP_CPU_SYNC,dma_attr);    
}
static inline enum dma_data_direction m4u_mem2dma_dir(UINT32 m4umem_type)
{
    enum dma_data_direction dir=DMA_NONE;
    if(m4umem_type==(M4UMEM_FOR_HW_READ|M4UMEM_FOR_HW_WRITE))dir=DMA_BIDIRECTIONAL;
    else if(m4umem_type &M4UMEM_FOR_HW_READ)dir=DMA_TO_DEVICE;
    else if(m4umem_type &M4UMEM_FOR_HW_WRITE)dir=DMA_FROM_DEVICE;
    return dir;
}
/**
 * x_mem_m4u_alloc - allocate physical memory,map it into m4u address space and kernel address space
 * @larb_id:the larbid of the hw attached to m4u
 * @size:allocate size,will be aligned to page size
 * @iova: allocated virtual address for hw,as output argument
 * @flags: the flag affect allocate process(see  M4U_ATTR*)
 * @return:the cpu address if no M4U_ATTR_NO_CPU_ACCESS is specified in flags,otherwise it is the pointer to physical page array
 */
EXTERN VOID* x_mem_m4u_alloc(UINT32 larb_id,SIZE_T size,dma_addr_t *iova,UINT32 flags);
/**
 * x_mem_m4u_free - unmap the iova in m4u domain,unmap the cpu address in kernel address space,then free the physical memory
 * @larb_id:the larbid of the hw attached to m4u
 * @size:allocated size,will be aligned to page size,shoule be the same as x_mem_m4u_alloc
 * @iova: allocated virtual address for hw,it is obtained from x_mem_m4u_alloc
 * @cpu_addr: the cpu address,it is returned by x_mem_m4u_alloc
  * @flags: the flag affect allocate process(see  M4U_ATTR*),shoule be the same as x_mem_m4u_alloc
 * @return:void
 */
EXTERN VOID x_mem_m4u_free(UINT32 larb_id,SIZE_T size,dma_addr_t iova,VOID *cpu_addr,UINT32 flags);
/**
 * x_mem_m4u_sync_for_cpu - do sync operation for cpu to access the m4u memory range
 * @larb_id:the larbid of the hw attached to m4u
 * @iova: allocated virtual address for hw,as output argument
 * @size:allocate size,will be aligned to page size
 * @memtype: the usage of this memory range(M4UMEM_FOR_HW_*),hw will read or write this memory range
 * @return:void
 *
 Especially,When the m4u memory range is written by the hw,and before cpu want to access the memory,
 it need do sync operation at first to keep consistent between hw and cpu.otherwise,cpu will read incomplete data.
 So,please set the correct memory type in argument memtype
 */
EXTERN VOID x_mem_m4u_sync_for_cpu(UINT32 larb_id,dma_addr_t iova,SIZE_T size,UINT32 memtype);
/**
 * x_mem_m4u_sync_for_hw - do sync operation for hw to access the m4u memory range
 * @larb_id:the larbid of the hw attached to m4u
 * @iova: allocated virtual address for hw,as output argument
 * @size:allocate size,will be aligned to page size
 * @memtype: the usage of this memory range(M4UMEM_FOR_HW_*),hw will read or write this memory range
 * @return:void
 *
 Especially,When the m4u memory range is written by the cpu,and before hw want to access the memory,
 it need do sync operation at first to keep consistent between hw and cpu.otherwise,hw will read incomplete data.
 So,please set the correct memory type in argument memtype
 */
EXTERN VOID x_mem_m4u_sync_for_hw(UINT32 larb_id,dma_addr_t iova,SIZE_T size,UINT32 memtype);
/**
 * x_mem_m4u_alloc_from_usr - allocate iova frm user space virtual address and mapping it to kernel space if no M4U_ATTR_NO_CPU_ACCESS is specified in flags
 * @larb_id:the larbid of the hw attached to m4u
 * @usr_vaddr:the user space virtual address used to mapping iova
 * @size:allocate size,will be aligned to page size,it is also the valid user virtual address range
 * @flags: the flag affect allocate process(see  M4U_ATTR*)
 * @memtype: the usage of this memory range(M4UMEM_FOR_HW_*),hw will read or write this memory range
 * @return:struct m4u_dma_sg_buf *(See include/linux/mtk_iommu.h)
 */
EXTERN struct m4u_dma_sg_buf* x_mem_m4u_alloc_from_usr(UINT32 larb_id,UPTR usr_vaddr,SIZE_T size,UINT32 flags,UINT32 memtype);
/**
 * x_mem_m4u_free_from_usr - unmap the iova in m4u domain,unmap the cpu address in kernel address space,then release the user space page frame
 * @buf:the buffer returned by x_mem_m4u_alloc_from_usr
 * @return:void
 */
EXTERN VOID  x_mem_m4u_free_from_usr(struct m4u_dma_sg_buf* buf);
/**
 * x_mem_m4u_alloc_from_larb - allocate new iova from existing iova in old larb for new larb.It means they share the same physical pages
 * @old_larb_id:the old larb holds the existing iova
 * @new_larb_id: the new larb want to allocate new iova from exsting iova
 * @size:allocate size,it should be the same as old iova size
 * @memtype: the usage of this memory range(M4UMEM_FOR_HW_*),hw will read or write this memory range
 * @return: 0 allocate fail
 *                 >0 new iova
 *
*   This API is called in below scenario:
    When hw module wants to read/write the physical address mapped by iova allocated by other hw module.
    And the two hw modules do not share the same iommu table(like m4u mm1 and mm2)
 */
EXTERN dma_addr_t x_mem_m4u_alloc_from_larb(UINT32 old_larb_id,UINT32 new_larb_id,dma_addr_t iova,SIZE_T size,UINT32 memtype);
/**
 * x_mem_m4u_free_iova - unmap the iova in m4u domain
 * @larb_id:the larbid of the hw attached to m4u
 * @size:allocated size,will be aligned to page size,shoule be the same as x_mem_m4u_alloc
 * @iova: allocated virtual address for hw,it is obtained from x_mem_m4u_alloc or x_mem_m4u_alloc_from_larb
 * @flags: the flag affect allocate process(see  M4U_ATTR*),shoule be the same as x_mem_m4u_alloc
 * @memtype: the usage of this memory range(M4UMEM_FOR_HW_*),hw will read or write this memory range
 * @return:void
 *   This API is called in below scenario:
        Free iova in iommu domain but does not want to free the corresponding physical memory mainly because the physical memory is shared by 
        other hw module.The iova is from  x_mem_m4u_alloc_from_larb probably.Especially,when the iova is only used by hw,and cpu does not touch it,it will be 
        better to use M4U_ATTR_NO_CPU_SYNC in flags to avoid cpu cache operation(cpu maybe flush cache to memory depending on the memtype)
 */
EXTERN VOID x_mem_m4u_free_iova(UINT32 larb_id,SIZE_T size,dma_addr_t iova,UINT32 flags,UINT32 memtype);

#endif  // X_M4U_H
