/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Copyright (c) 2020 Samsung Electronics Co., Ltd.
 */

#ifndef __SAMSUNG_IOMMU_H
#define __SAMSUNG_IOMMU_H

#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/iommu.h>

#define SYSMMU_EVENT_MAX	2048
enum sysmmu_event_type {
	/* init value */
	SYSMMU_EVENT_NONE,
	/* event for sysmmu drvdata */
	SYSMMU_EVENT_ENABLE,
	SYSMMU_EVENT_DISABLE,
	SYSMMU_EVENT_POWERON,
	SYSMMU_EVENT_POWEROFF,
	SYSMMU_EVENT_TLB_RANGE,
	SYSMMU_EVENT_TLB_ALL,
	SYSMMU_EVENT_IOTLB_SYNC,
	/* event for iommu domain */
	SYSMMU_EVENT_MAP,
	SYSMMU_EVENT_UNMAP,
};

struct sysmmu_log {
	unsigned long long time;
	enum sysmmu_event_type type;
	u32 start;
	u32 end;
};

struct samsung_iommu_log {
	atomic_t index;
	int len;
	struct sysmmu_log *log;
};

struct tlb_config {
	unsigned int index;
	u32 cfg;
	u32 match_cfg;
	u32 match_id;
};

struct tlb_props {
	int id_cnt;
	u32 default_cfg;
	struct tlb_config *cfg;
};

struct sysmmu_drvdata {
	struct list_head list;
	struct iommu_device iommu;
	struct device *dev;
	struct iommu_group *group;
	void __iomem *sfrbase;
	struct clk *clk;
	phys_addr_t pgtable;
	spinlock_t lock; /* protect atomic update to H/W status */
	u32 version;
	int num_tlb;
	int qos;
	int attached_count;
	int rpm_count;
	int secure_irq;
	int max_vm;
	int vmid_mask;
	unsigned int secure_base;
	const unsigned int *reg_set;
	struct tlb_props tlb_props;
	struct samsung_iommu_log log;
	bool no_block_mode;
	bool has_vcr;
	bool async_fault_mode;
};

struct sysmmu_clientdata {
	struct device *dev;
	struct sysmmu_drvdata **sysmmus;
	struct device_link **dev_link;
	int sysmmu_count;
};


enum {
	REG_IDX_DEFAULT = 0,
	REG_IDX_VM,

	MAX_SET_IDX,
};

enum {
	IDX_FLPT_BASE = 0,
	IDX_ALL_INV,
	IDX_VPN_INV,
	IDX_RANGE_INV,
	IDX_RANGE_INV_START,
	IDX_RANGE_INV_END,
	IDX_FAULT_VA,
	IDX_FAULT_TRANS_INFO,
	IDX_TLB_READ,
	IDX_TLB_VPN,
	IDX_TLB_PPN,
	IDX_TLB_ATTR,
	IDX_SBB_READ,
	IDX_SBB_VPN,
	IDX_SBB_LINK,
	IDX_SBB_ATTR,
	IDX_SEC_FLPT_BASE,

	MAX_REG_IDX,
};

#define MMU_REG(data, idx)		((data)->sfrbase + (data)->reg_set[idx])
#define MMU_VM_REG(data, idx, vmid)	(MMU_REG(data, idx) + (vmid) * 0x10)
#define MMU_SEC_REG(data, offset_idx)	((data)->secure_base + (data)->reg_set[offset_idx])
#define MMU_SEC_VM_REG(data, offset_idx, vmid) (MMU_SEC_REG(data, offset_idx) + (vmid) * 0x10)

typedef u32 sysmmu_iova_t;
typedef u32 sysmmu_pte_t;

#define SECT_ORDER 20
#define LPAGE_ORDER 16
#define SPAGE_ORDER 12

#define SECT_SIZE  (1UL << SECT_ORDER)
#define LPAGE_SIZE (1UL << LPAGE_ORDER)
#define SPAGE_SIZE (1UL << SPAGE_ORDER)

#define SECT_MASK (~(SECT_SIZE - 1))
#define LPAGE_MASK (~(LPAGE_SIZE - 1))
#define SPAGE_MASK (~(SPAGE_SIZE - 1))

#define SECT_ENT_MASK	~((SECT_SIZE >> PG_ENT_SHIFT) - 1)
#define LPAGE_ENT_MASK	~((LPAGE_SIZE >> PG_ENT_SHIFT) - 1)
#define SPAGE_ENT_MASK	~((SPAGE_SIZE >> PG_ENT_SHIFT) - 1)

#define SPAGES_PER_LPAGE	(LPAGE_SIZE / SPAGE_SIZE)

#define NUM_LV1ENTRIES	4096
#define NUM_LV2ENTRIES (SECT_SIZE / SPAGE_SIZE)
#define LV1TABLE_SIZE (NUM_LV1ENTRIES * sizeof(sysmmu_pte_t))
#define LV2TABLE_SIZE (NUM_LV2ENTRIES * sizeof(sysmmu_pte_t))

#define lv1ent_offset(iova) ((iova) >> SECT_ORDER)
#define lv2ent_offset(iova) (((iova) & ~SECT_MASK) >> SPAGE_ORDER)

#define FLPD_FLAG_MASK	7
#define SLPD_FLAG_MASK	3

#define SECT_FLAG	2
#define SLPD_FLAG	1

#define LPAGE_FLAG	1
#define SPAGE_FLAG	2

#define PG_ENT_SHIFT	4
#define lv1ent_unmapped(sent)	((*(sent) & 7) == 0)
#define lv1ent_page(sent)	((*(sent) & 7) == 1)

#define lv1ent_section(sent)	((*(sent) & FLPD_FLAG_MASK) == SECT_FLAG)
#define lv2table_base(sent)	((phys_addr_t)(*(sent) & ~0x3F) << PG_ENT_SHIFT)
#define lv2ent_unmapped(pent)	((*(pent) & SLPD_FLAG_MASK) == 0)
#define lv2ent_small(pent)	((*(pent) & SLPD_FLAG_MASK) == SPAGE_FLAG)
#define lv2ent_large(pent)	((*(pent) & SLPD_FLAG_MASK) == LPAGE_FLAG)

#define PGBASE_TO_PHYS(pgent)	((phys_addr_t)(pgent) << PG_ENT_SHIFT)
#define ENT_TO_PHYS(ent) (phys_addr_t)(*(ent))
#define section_phys(sent) PGBASE_TO_PHYS(ENT_TO_PHYS(sent) & SECT_ENT_MASK)
#define section_offs(iova) ((iova) & (SECT_SIZE - 1))
#define lpage_phys(pent) PGBASE_TO_PHYS(ENT_TO_PHYS(pent) & LPAGE_ENT_MASK)
#define lpage_offs(iova) ((iova) & (LPAGE_SIZE - 1))
#define spage_phys(pent) PGBASE_TO_PHYS(ENT_TO_PHYS(pent) & SPAGE_ENT_MASK)
#define spage_offs(iova) ((iova) & (SPAGE_SIZE - 1))

static inline sysmmu_pte_t *page_entry(sysmmu_pte_t *sent, sysmmu_iova_t iova)
{
	return (sysmmu_pte_t *)(phys_to_virt(lv2table_base(sent))) +
				lv2ent_offset(iova);
}

static inline sysmmu_pte_t *section_entry(sysmmu_pte_t *pgtable,
					  sysmmu_iova_t iova)
{
	return pgtable + lv1ent_offset(iova);
}

#define REG_MMU_CTRL			0x000
#define REG_MMU_CFG			0x004
#define REG_MMU_STATUS			0x008
#define REG_MMU_FLPT_BASE		0x00C
#define REG_MMU_VERSION			0x034
#define REG_MMU_CAPA0_V7		0x870
#define REG_MMU_CAPA1_V7		0x874

#define SYSMMU_VM_OFFSET		0x1000
#define REG_VMID_OFFSET(offset, vmid)	((offset) + (vmid) * SYSMMU_VM_OFFSET)
#define REG_MMU_CTRL_VM(vmid)		REG_VMID_OFFSET(0x8000, vmid)
#define REG_MMU_CFG_VM(vmid)		REG_VMID_OFFSET(0x8004, vmid)

#define MMU_CAPA_NUM_TLB_WAY(reg)	((reg) & 0xFF)
#define MMU_CAPA_NUM_SBB_ENTRY(reg)	(((reg) >> 12) & 0xF)
#define MMU_CAPA_NUM_PAGE_TABLE(reg)	(((reg) >> 16) & 0xF)
#define MMU_CAPA1_EXIST(reg)		(((reg) >> 11) & 0x1)
#define MMU_CAPA1_TYPE(reg)		(((reg) >> 28) & 0xF)
#define MMU_CAPA1_NO_BLOCK_MODE(reg)	(((reg) >> 15) & 0x1)
#define MMU_CAPA1_VCR_ENABLED(reg)	(((reg) >> 14) & 0x1)
#define MMU_CAPA1_NUM_TLB(reg)		(((reg) >> 4) & 0xFF)
#define MMU_CAPA1_NUM_PORT(reg)		((reg) & 0xF)

#define MMU_MAJ_VER(val)	((val) >> 11)
#define MMU_MIN_VER(val)	(((val) >> 4) & 0x7F)
#define MMU_REV_VER(val)	((val) & 0xF)
#define MMU_RAW_VER(reg)	(((reg) >> 17) & 0x7FFF)
#define MMU_VERSION(x, y, z)	((z) | ((y) << 4) | ((x) << 11))

#define CTRL_VID_ENABLE			0x1
#define CTRL_MMU_ENABLE			BIT(0)
#define CTRL_MMU_BLOCK			BIT(1)
#define CTRL_INT_ENABLE			BIT(2)
#define CTRL_FAULT_STALL_MODE		BIT(3)
#define CTRL_RESP_SLAVE_ERROR		0x20

#define CFG_MASK_GLOBAL			0x00000F80 /* Bit 11, 10-7 */
#define CFG_MASK_VM			0xB00F1004 /* Bit 31, 29, 28, 19-16, 12, 2 */
#define CFG_QOS_OVRRIDE			BIT(11)
#define CFG_QOS(n)			(((n) & 0xF) << 7)

irqreturn_t samsung_sysmmu_irq_thread(int irq, void *dev_id);
irqreturn_t samsung_sysmmu_irq(int irq, void *dev_id);

#endif /* __SAMSUNG_IOMMU_H */