/**
 * Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd
 * author: chenhengming chm@rock-chips.com
 *	   Alpha Lin, alpha.lin@rock-chips.com
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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 the
 * GNU General Public License for more details.
 *
 */

#ifndef __ROCKCHIP_MPP_DEV_COMMON_H
#define __ROCKCHIP_MPP_DEV_COMMON_H

#include <linux/cdev.h>
#include <linux/dma-buf.h>
#include <linux/rockchip_ion.h>
#include <linux/rockchip-iovmm.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/wakelock.h>

#include <video/rk_vpu_service.h>

extern int mpp_dev_debug;

#define MPP_IOC_CUSTOM_BASE			0x1000

/*
 * debug flag usage:
 * +------+-------------------+
 * | 8bit |      24bit        |
 * +------+-------------------+
 *  0~23 bit is for different information type
 * 24~31 bit is for information print format
 */

#define DEBUG_POWER				0x00000001
#define DEBUG_CLOCK				0x00000002
#define DEBUG_IRQ_STATUS			0x00000004
#define DEBUG_IOMMU				0x00000008
#define DEBUG_IOCTL				0x00000010
#define DEBUG_FUNCTION				0x00000020
#define DEBUG_REGISTER				0x00000040
#define DEBUG_EXTRA_INFO			0x00000080
#define DEBUG_TIMING				0x00000100
#define DEBUG_TASK_INFO				0x00000200
#define DEBUG_DUMP_ERR_REG			0x00000400

#define DEBUG_SET_REG				0x00001000
#define DEBUG_GET_REG				0x00002000
#define DEBUG_PPS_FILL				0x00004000
#define DEBUG_IRQ_CHECK				0x00008000
#define DEBUG_CACHE_32B				0x00010000

#define DEBUG_RESET				0x00020000

#define PRINT_FUNCTION				0x80000000
#define PRINT_LINE				0x40000000

#define DEBUG
#ifdef DEBUG
#define mpp_debug_func(type, fmt, args...)			\
	do {							\
		if (unlikely(mpp_dev_debug & type)) {		\
			pr_info("%s:%d: " fmt,			\
				 __func__, __LINE__, ##args);	\
		}						\
	} while (0)
#define mpp_debug(type, fmt, args...)				\
	do {							\
		if (unlikely(mpp_dev_debug & type)) {		\
			pr_info(fmt, ##args);			\
		}						\
	} while (0)
#else
#define mpp_debug_func(level, fmt, args...)
#define mpp_debug(level, fmt, args...)
#endif

#define mpp_debug_enter() mpp_debug_func(DEBUG_FUNCTION, "enter\n")
#define mpp_debug_leave() mpp_debug_func(DEBUG_FUNCTION, "leave\n")

#define mpp_err(fmt, args...)				\
		pr_err("%s:%d: " fmt, __func__, __LINE__, ##args)

struct mpp_trans_info {
	const int count;
	const char * const table;
};

enum RKVENC_MODE {
	RKVENC_MODE_NONE,
	RKVENC_MODE_ONEFRAME,
	RKVENC_MODE_LINKTABLE_FIX,
	RKVENC_MODE_LINKTABLE_UPDATE,
	RKVENC_MODE_NUM
};

struct rockchip_mpp_dev;
struct mpp_service;
struct mpp_ctx;

struct mpp_mem_region {
	struct list_head srv_lnk;
	struct list_head reg_lnk;
	struct list_head session_lnk;
	/* virtual address for iommu */
	unsigned long iova;
	unsigned long len;
	u32 reg_idx;
	int hdl;
};

/**
 * struct for process register set
 *
 * @author ChenHengming (2011-5-4)
 */
struct mpp_ctx {
	/* context belong to */
	struct rockchip_mpp_dev *mpp;
	struct mpp_session *session;

	/* link to service session */
	struct list_head session_link;
	/* link to service list */
	struct list_head status_link;

	struct list_head mem_region_list;

	/* record context running start time */
	struct timeval start;
};

enum vpu_ctx_state {
	MMU_ACTIVATED	= BIT(0),
};

struct extra_info_elem {
	u32 index;
	u32 offset;
};

#define EXTRA_INFO_MAGIC	0x4C4A46

struct extra_info_for_iommu {
	u32 magic;
	u32 cnt;
	struct extra_info_elem elem[20];
};

struct rockchip_mpp_dev_variant {
	u32 data_len;
	u32 reg_len;
	struct mpp_trans_info *trans_info;
	char *mmu_dev_dts_name;

	int (*hw_probe)(struct rockchip_mpp_dev *mpp);
	void (*hw_remove)(struct rockchip_mpp_dev *mpp);
	void (*power_on)(struct rockchip_mpp_dev *mpp);
	void (*power_off)(struct rockchip_mpp_dev *mpp);
	int (*reset)(struct rockchip_mpp_dev *mpp);
};

struct rockchip_mpp_dev {
	struct mpp_dev_ops *ops;

	struct cdev cdev;
	dev_t dev_t;
	struct device *child_dev;

	int irq;
	struct mpp_service *srv;

	void __iomem *reg_base;
	struct list_head lnk_service;

	struct device *dev;

	unsigned long state;
	struct vpu_iommu_info *iommu_info;

	const struct rockchip_mpp_dev_variant *variant;

	struct device *mmu_dev;
	u32 iommu_enable;

	struct wake_lock wake_lock;
	struct delayed_work power_off_work;
	/* record previous power-on time */
	ktime_t last;
	atomic_t power_on_cnt;
	atomic_t power_off_cnt;
	atomic_t total_running;
	atomic_t enabled;
	atomic_t reset_request;
};

/**
 * struct mpp_dev_ops - context specific operations for mpp_device
 *
 * @init	Prepare for registers file for specific hardware.
 * @prepare	Check HW status for determining run next task or not.
 * @run		Start a single {en,de}coding run. Set registers to hardware.
 * @done	Read back processing results and additional data from hardware.
 * @result	Read status to userspace.
 * @deinit	Release the resource allocate during init.
 * @ioctl	ioctl for special HW besides the common ioctl.
 * @irq		interrupt service for specific hardware.
 * @open	a specific instance open operation for hardware.
 * @release	a specific instance release operation for hardware.
 */
struct mpp_dev_ops {
	/* size: in bytes, data sent from userspace, length in bytes */
	struct mpp_ctx *(*init)(struct rockchip_mpp_dev *mpp,
				struct mpp_session *session,
				void __user *src, u32 size);
	int (*prepare)(struct rockchip_mpp_dev *mpp);
	int (*run)(struct rockchip_mpp_dev *mpp);
	int (*done)(struct rockchip_mpp_dev *mpp);
	int (*irq)(struct rockchip_mpp_dev *mpp);
	int (*result)(struct rockchip_mpp_dev *mpp, struct mpp_ctx *ctx,
		      u32 __user *dst);
	void (*deinit)(struct rockchip_mpp_dev *mpp);
	long (*ioctl)(struct mpp_session *isession,
		      unsigned int cmd, unsigned long arg);
	struct mpp_session *(*open)(struct rockchip_mpp_dev *mpp);
	void (*release)(struct mpp_session *session);
	void (*free)(struct mpp_session *session);
};

void mpp_dump_reg(void __iomem *regs, int count);
void mpp_dump_reg_mem(u32 *regs, int count);
int mpp_reg_address_translate(struct rockchip_mpp_dev *data,
			      u32 *reg,
			      struct mpp_ctx *ctx,
			      int idx);
void mpp_translate_extra_info(struct mpp_ctx *ctx,
			      struct extra_info_for_iommu *ext_inf,
			      u32 *reg);

int mpp_dev_common_ctx_init(struct rockchip_mpp_dev *mpp, struct mpp_ctx *cfg);
void mpp_dev_common_ctx_deinit(struct rockchip_mpp_dev *mpp,
			       struct mpp_ctx *ctx);
void mpp_dev_power_on(struct rockchip_mpp_dev *mpp);
void mpp_dev_power_off(struct rockchip_mpp_dev *mpp);
bool mpp_dev_is_power_on(struct rockchip_mpp_dev *mpp);

static inline void mpp_write_relaxed(struct rockchip_mpp_dev *mpp,
				     u32 val, u32 reg)
{
	mpp_debug(DEBUG_SET_REG, "MARK: set reg[%03d]: %08x\n", reg / 4, val);
	writel_relaxed(val, mpp->reg_base + reg);
}

static inline void mpp_write(struct rockchip_mpp_dev *mpp,
			     u32 val, u32 reg)
{
	mpp_debug(DEBUG_SET_REG, "MARK: set reg[%03d]: %08x\n", reg / 4, val);
	writel(val, mpp->reg_base + reg);
}

static inline u32 mpp_read(struct rockchip_mpp_dev *mpp, u32 reg)
{
	u32 val = readl(mpp->reg_base + reg);

	mpp_debug(DEBUG_GET_REG, "MARK: get reg[%03d] 0x%x: %08x\n", reg / 4,
		  reg, val);
	return val;
}

static inline void mpp_time_record(struct mpp_ctx *ctx)
{
	if (unlikely(mpp_dev_debug & DEBUG_TIMING) && ctx)
		do_gettimeofday(&ctx->start);
}

static inline void mpp_time_diff(struct mpp_ctx *ctx)
{
	struct timeval end;

	do_gettimeofday(&end);
	mpp_debug(DEBUG_TIMING, "consume: %ld us\n",
		  (end.tv_sec  - ctx->start.tv_sec)  * 1000000 +
		  (end.tv_usec - ctx->start.tv_usec));
}

extern const struct rockchip_mpp_dev_variant rkvenc_variant;
extern const struct rockchip_mpp_dev_variant vepu_variant;
extern const struct rockchip_mpp_dev_variant h265e_variant;

#endif