/*
 * Exynos regulator support.
 *
 * Copyright (c) 2016 Samsung Electronics Co., Ltd.
 *              http://www.samsung.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/slab.h>

#define EXYNOS_FLEXPMU_DBG_PREFIX	"EXYNOS-FLEXPMU-DBG: "

#define DATA_LINE	(16)
#define DATA_IDX	(8)

#define FLEXPMU_DBG_FUNC_READ(__name)		\
	exynos_flexpmu_dbg_ ## __name ## _read

#define BUF_MAX_LINE	10
#define BUF_LINE_SIZE	255
#define BUF_SIZE	(BUF_MAX_LINE * BUF_LINE_SIZE)

#define DEC_PRINT	1
#define HEX_PRINT	2

/* enum for debugfs files */
enum flexpmu_debugfs_id {
	FID_CPU_STATUS,
	FID_SEQ_STATUS,
	FID_CUR_SEQ,
	FID_SW_FLAG,
	FID_SEQ_COUNT,
	FID_MIF_ALWAYS_ON,
	FID_LPM_COUNT,
	FID_APM_REQ_INFO,
#if defined(CONFIG_SOC_S5E9925)
	FID_MID_DVS_EN,
#endif
	FID_MAX
};

char *flexpmu_debugfs_name[FID_MAX] = {
	"cpu_status",
	"seq_status",
	"cur_sequence",
	"sw_flag",
	"seq_count",
	"mif_always_on",
	"lpm_count",
	"apm_req_info",
#if defined(CONFIG_SOC_S5E9925)
	"mid_dvs_en",
#endif
};

/* enum for data lines */
enum data_id {
	DID_CPU_STATUS,		/* 0 */
	DID_SEQ_STATUS,
	DID_CUR_SEQ0,
	DID_CUR_SEQ1,
	DID_PWR_MODE0,		/* 4 */
	DID_PWR_MODE1,
	DID_PWR_MODE2,
	DID_PWR_MODE3,
	DID_PWR_MODE4,
	DID_PWR_MODE5,
	DID_SW_FLAG,
	DID_IRQ_STATUS,		/* 11 */
	DID_IRQ_DATA,
	DID_IPC_AP_STATUS,
	DID_IPC_AP_RXDATA,
	DID_IPC_AP_TXDATA,
	DID_SOC_COUNT,
	DID_MIF_COUNT,
	DID_IPC_VTS0,
	DID_SLEEP_WAKEUP,
	DID_LOCAL_PWR,
	DID_MIF_ALWAYS_ON,		/* 21 */
	DID_AP_COUNT_SLEEP,
	DID_MIF_COUNT_SLEEP,
	DID_AP_COUNT_SICD,
	DID_MIF_COUNT_SICD,
	DID_DSUPD_COUNT,
	DID_CUR_PMD,
	DID_CPU_INFORM01,
	DID_CPU_INFORM23,
	DID_CPU_INFORM45,
	DID_CPU_INFORM67,
	DID_INT_REG01,
	DID_INT_REG02,
	DID_INT_REG03,
	DID_INT_REG04,
	DID_INT_REG05,
	DID_INT_REG06,
	DID_INT_REG07,
	DID_INT_REG08,
	DID_INT_REG09,
	DID_INT_REG10,
	DID_INT_REG11,
#if defined(CONFIG_SOC_S5E9925)
	DID_MIFAP0,
	DID_MIFAP1,
	DID_MIFAUD0,
	DID_MIFAUD1,
	DID_MIFVTS0,
	DID_MIFVTS1,
	DID_MIFCP0,
	DID_MIFCP1,
	DID_MID_DVS_EN,
#elif defined(CONFIG_SOC_S5E8825)
	DID_CMGP_REQ,
	DID_LOCAL01,
	DID_LOCAL02,
	DID_LOCAL03,
	DID_RCO_DIV,
	DID_MIFAUD0,
	DID_MIFAUD1,
	DID_MIFAUD2,
	DID_MIFVTS0,
	DID_MIFVTS1,
	DID_MIFVTS2,
	DID_MIFCHUB0,
	DID_MIFCHUB1,
	DID_MIFCHUB2,
	DID_MIFCP0,
	DID_MIFCP1,
	DID_MIFCP2,
	DID_MIFGNSS0,
	DID_MIFGNSS1,
	DID_MIFGNSS2,
	DID_MIFWLBT0,
	DID_MIFWLBT1,
	DID_MIFWLBT2,
	DID_MIFAP0,
	DID_MIFAP1,
	DID_MIFAP2,
	DID_TC_CP0,
	DID_TC_CP1,
	DID_TC_CP2,
	DID_TC_GNSS0,
	DID_TC_GNSS1,
	DID_TC_GNSS2,
	DID_TC_WLBT0,
	DID_TC_WLBT1,
	DID_TC_WLBT2,
	DID_PWRCP0,
	DID_PWRCP1,
	DID_PWRCP2,
	DID_PWRGNSS0,
	DID_PWRGNSS1,
	DID_PWRGNSS2,
	DID_PWRWLBT0,
	DID_PWRWLBT1,
	DID_PWRWLBT2,
	DID_pENTIME,
	DID_SICD_PHY,
#endif
	DID_MAX

};

struct flexpmu_dbg_print_arg {
	char prefix[BUF_LINE_SIZE];
	int print_type;
};

struct dbgfs_info {
	int fid;
	struct dentry *den;
	struct file_operations fops;
};

struct dbgfs_info *flexpmu_dbg_info;
void __iomem *flexpmu_dbg_base;
static struct dentry *flexpmu_dbg_root;

struct flexpmu_apm_req_info {
	unsigned int active_req_tick;
	unsigned int last_rel_tick;
	unsigned int total_count;
	unsigned int total_time_tick;
	unsigned long long int active_since_us;
	unsigned long long int last_rel_us;
	unsigned long long int total_time_us;
	bool active_flag;
};

void __iomem *rtc_base;


#if defined(CONFIG_SOC_S5E9925)
#define MIF_MASTER_MAX		4
char *flexpmu_master_name[MIF_MASTER_MAX] = {
	"MIF_AP",
	"MIF_AUD",
	"MIF_VTS",
	"MIF_CP",
};

#elif defined(CONFIG_SOC_S5E8825)
#define MIF_MASTER_MAX		7
char *flexpmu_master_name[MIF_MASTER_MAX] = {
	"MIF_AUD",
	"MIF_VTS",
	"MIF_CHUB",
	"MIF_CP",
	"MIF_GNSS",
	"MIF_WLBT",
	"MIF_AP",
};
#endif

struct flexpmu_apm_req_info apm_req[MIF_MASTER_MAX];

u32 acpm_get_mifdn_count(void)
{
	return __raw_readl(flexpmu_dbg_base + (DATA_LINE * DID_MIF_COUNT_SLEEP) + DATA_IDX + 4);
}
EXPORT_SYMBOL_GPL(acpm_get_mifdn_count);

u32 acpm_get_apsocdn_count(void)
{
	return __raw_readl(flexpmu_dbg_base + (DATA_LINE * DID_AP_COUNT_SLEEP) + DATA_IDX + 4);
}
EXPORT_SYMBOL_GPL(acpm_get_apsocdn_count);

u32 acpm_get_early_wakeup_count(void)
{
	return __raw_readl(flexpmu_dbg_base + (DATA_LINE * DID_AP_COUNT_SLEEP) + DATA_IDX);
}
EXPORT_SYMBOL_GPL(acpm_get_early_wakeup_count);

/*notify to flexpmu, it is SICD w MIF(is_dsu_cpd=0) or SICD wo MIF(is_dsu_cpd=1).*/
u32 acpm_noti_dsu_cpd(bool is_dsu_cpd)
{
	__raw_writel(is_dsu_cpd, flexpmu_dbg_base + (DATA_LINE * DID_MIF_ALWAYS_ON) + DATA_IDX);
	return 0;
}
EXPORT_SYMBOL_GPL(acpm_noti_dsu_cpd);

u32 acpm_get_dsu_cpd(void)
{
	return __raw_readl(flexpmu_dbg_base + (DATA_LINE * DID_MIF_ALWAYS_ON) + DATA_IDX);
}
EXPORT_SYMBOL_GPL(acpm_get_dsu_cpd);

#define MIF_REQ_MASK		(0x00FF0000)
#define MIF_REQ_SHIFT		(16)
u32 acpm_get_mif_request(void)
{
	u32 reg = __raw_readl(flexpmu_dbg_base + (DATA_LINE * DID_SW_FLAG) + DATA_IDX + 4);

	return ((reg & MIF_REQ_MASK) >> MIF_REQ_SHIFT);

}
EXPORT_SYMBOL_GPL(acpm_get_mif_request);

static ssize_t print_dataline_2(int did, struct flexpmu_dbg_print_arg *print_arg,
		ssize_t len, char *buf, int *data_count)
{
	int data[2];
	ssize_t line_len;
	int i;

	for (i = 0; i < 2; i++) {
		if (print_arg[*data_count].print_type == DEC_PRINT) {
			data[i] = __raw_readl(flexpmu_dbg_base +
					(DATA_LINE * did) + DATA_IDX + i * 4);

			line_len = snprintf(buf + len, BUF_SIZE - len, "%s : %d\n",
					print_arg[*data_count].prefix, data[i]);
			if (line_len > 0)
				len += line_len;
		} else if (print_arg[*data_count].print_type == HEX_PRINT) {
			data[i] = __raw_readl(flexpmu_dbg_base +
					(DATA_LINE * did) + DATA_IDX + i * 4);

			line_len = snprintf(buf + len, BUF_SIZE - len, "%s : 0x%x\n",
					print_arg[*data_count].prefix, data[i]);
			if (line_len > 0)
				len += line_len;
		}
		*data_count += 1;
	}

	return len;
}

static ssize_t print_dataline_8(int did, struct flexpmu_dbg_print_arg *print_arg,
		ssize_t len, char *buf, int *data_count)
{
	int data[8];
	ssize_t line_len;
	int i;

	for (i = 0; i < 8; i++) {
		if (print_arg[*data_count].print_type == DEC_PRINT) {
			data[i] = __raw_readb(flexpmu_dbg_base +
					(DATA_LINE * did) + DATA_IDX + i);

			line_len = snprintf(buf + len, BUF_SIZE - len, "%s : %d\n",
					print_arg[*data_count].prefix, data[i]);
			if (line_len > 0)
				len += line_len;
		} else if (print_arg[*data_count].print_type == HEX_PRINT) {
			data[i] = __raw_readb(flexpmu_dbg_base +
					(DATA_LINE * did) + DATA_IDX + i);

			line_len = snprintf(buf + len, BUF_SIZE - len, "%s : 0x%x\n",
					print_arg[*data_count].prefix, data[i]);
			if (line_len > 0)
				len += line_len;
		}
		*data_count += 1;
	}

	return len;
}

static struct flexpmu_dbg_print_arg *exynos_flexpmu_dbg_alloc_print_arg(int nargs, ...)
{
	struct flexpmu_dbg_print_arg *print_arg;
	va_list args;
	int i;

	if (nargs > BUF_MAX_LINE || nargs <= 0)
		return NULL;

	print_arg = kzalloc(sizeof(struct flexpmu_dbg_print_arg) * BUF_MAX_LINE, GFP_KERNEL);

	va_start(args, nargs);

	for (i = 0; i < nargs; i++) {
		char *prefix = va_arg(args, char *);
		int print_type = va_arg(args, int);

		if (prefix != NULL) {
			strncpy(print_arg[i].prefix, prefix, strlen(prefix));
			print_arg[i].print_type = print_type;
		}
	}

	va_end(args);

	return print_arg;
}

static ssize_t exynos_flexpmu_dbg_cpu_status_read(int fid, char *buf)
{
	ssize_t ret = 0;
	int data_count = 0;

	struct flexpmu_dbg_print_arg *print_arg = exynos_flexpmu_dbg_alloc_print_arg(8, 
		"CL0_CPU0", DEC_PRINT,
		"CL0_CPU1", DEC_PRINT,
		"CL0_CPU2", DEC_PRINT,
		"CL0_CPU3", DEC_PRINT,
		"CL1_CPU0", DEC_PRINT,
		"CL1_CPU1", DEC_PRINT,
		"CL1_CPU2", DEC_PRINT,
		"CL1_CPU3", DEC_PRINT
		);

	ret = print_dataline_8(DID_CPU_STATUS, print_arg, ret, buf, &data_count);

	kfree(print_arg);

	return ret;
}

static ssize_t exynos_flexpmu_dbg_seq_status_read(int fid, char *buf)
{
	ssize_t ret = 0;
	int data_count = 0;

	struct flexpmu_dbg_print_arg *print_arg = exynos_flexpmu_dbg_alloc_print_arg(7,
		"SOC seq", DEC_PRINT,
		"MIF seq", DEC_PRINT,
		NULL, 0,
		NULL, 0,
		"nonCPU CL0", DEC_PRINT,
		"nonCPU CL1", DEC_PRINT,
		NULL, 0
	);

	ret = print_dataline_8(DID_SEQ_STATUS, print_arg, ret, buf, &data_count);

	kfree(print_arg);

	return ret;
}

static ssize_t exynos_flexpmu_dbg_cur_sequence_read(int fid, char *buf)
{
	ssize_t ret = 0;
	int data_count = 0;

	struct flexpmu_dbg_print_arg *print_arg = exynos_flexpmu_dbg_alloc_print_arg(4,
		"UP Sequence", DEC_PRINT,
		"DOWN Sequence", DEC_PRINT,
		"Access Type", DEC_PRINT,
		"Seq Index", DEC_PRINT
	);

	ret = print_dataline_2(DID_CUR_SEQ0, print_arg, ret, buf, &data_count);
	ret = print_dataline_2(DID_CUR_SEQ1, print_arg, ret, buf, &data_count);

	kfree(print_arg);

	return ret;
}

static ssize_t exynos_flexpmu_dbg_sw_flag_read(int fid, char *buf)
{
	ssize_t ret = 0;
	int data_count = 0;

	struct flexpmu_dbg_print_arg *print_arg = exynos_flexpmu_dbg_alloc_print_arg(8,
		"Hotplug out flag", HEX_PRINT,
		"Reset-Release flag", HEX_PRINT,
		NULL, 0,
		NULL, 0,
		"CHUB ref_count", DEC_PRINT,
		NULL, 0,
		"MIF req_Master", HEX_PRINT,
		"MIF req_count", DEC_PRINT
	);

	ret = print_dataline_8(DID_SW_FLAG, print_arg, ret, buf, &data_count);

	kfree(print_arg);

	return ret;
}

static ssize_t exynos_flexpmu_dbg_seq_count_read(int fid, char *buf)
{
	ssize_t ret = 0;
	int data_count = 0;

	struct flexpmu_dbg_print_arg *print_arg = exynos_flexpmu_dbg_alloc_print_arg(4,
		"Early Wakeup", DEC_PRINT,
		"SOC sequence", DEC_PRINT,
		NULL, 0,
		"MIF sequence", DEC_PRINT
	);

	ret = print_dataline_2(DID_SOC_COUNT, print_arg, ret, buf, &data_count);
	ret = print_dataline_2(DID_MIF_COUNT, print_arg, ret, buf, &data_count);

	kfree(print_arg);

	return ret;
}

static ssize_t exynos_flexpmu_dbg_mif_always_on_read(int fid, char *buf)
{
	ssize_t ret = 0;
	int data_count = 0;

	struct flexpmu_dbg_print_arg *print_arg = exynos_flexpmu_dbg_alloc_print_arg(2,
		NULL, 0,
		"MIF always on", DEC_PRINT
	);

	ret = print_dataline_2(DID_MIF_ALWAYS_ON, print_arg, ret, buf, &data_count);

	kfree(print_arg);

	return ret;
}
#if defined(CONFIG_SOC_S5E9925)
static ssize_t exynos_flexpmu_dbg_mid_dvs_en_read(int fid, char *buf)
{
	ssize_t ret = 0;
	int data_count = 0;

	struct flexpmu_dbg_print_arg *print_arg = exynos_flexpmu_dbg_alloc_print_arg(2,
		"MID DVS states", HEX_PRINT,
		"MID DVS enable", DEC_PRINT
	);

	ret = print_dataline_2(DID_MID_DVS_EN, print_arg, ret, buf, &data_count);

	kfree(print_arg);

	return ret;
}
#endif 
static ssize_t exynos_flexpmu_dbg_lpm_count_read(int fid, char *buf)
{
	ssize_t ret = 0;
	int data_count = 0;

	struct flexpmu_dbg_print_arg *print_arg = exynos_flexpmu_dbg_alloc_print_arg(10,
		"[SLEEP] Early wakeup", DEC_PRINT,
		"[SLEEP] SOC seq down", DEC_PRINT,
		NULL, 0,
		"[SLEEP] MIF seq down", DEC_PRINT,
		"[SICD] Early wakeup", DEC_PRINT,
		"[SICD] SOC seq down", DEC_PRINT,
		NULL, 0,
		"[SICD] MIF seq down", DEC_PRINT,
		NULL, 0,
		"[DSUPD] SOC seq down", DEC_PRINT
	);

	ret = print_dataline_2(DID_AP_COUNT_SLEEP, print_arg, ret, buf, &data_count);
	ret = print_dataline_2(DID_MIF_COUNT_SLEEP, print_arg, ret, buf, &data_count);
	ret = print_dataline_2(DID_AP_COUNT_SICD, print_arg, ret, buf, &data_count);
	ret = print_dataline_2(DID_MIF_COUNT_SICD, print_arg, ret, buf, &data_count);
	ret = print_dataline_2(DID_DSUPD_COUNT, print_arg, ret, buf, &data_count);
	kfree(print_arg);

	return ret;
}

#define RTC_TICK_TO_US		976	/* 1024 Hz : 1tick = 976.5625us */
#define CURTICCNT_0		0x90

static ssize_t exynos_flexpmu_dbg_apm_req_info_read(int fid, char *buf)
{
	size_t ret = 0;
	unsigned long long int curr_tick = 0;
	int i = 0;

	if (!rtc_base) {
		ret = snprintf(buf + ret, BUF_SIZE - ret,
				"%s\n", "This node is not supported.\n");
		return ret;
	}

	curr_tick = __raw_readl(rtc_base + CURTICCNT_0);
	ret += snprintf(buf + ret, BUF_SIZE - ret,
			"%s: %lld\n", "curr_time", curr_tick * RTC_TICK_TO_US);
	ret += snprintf(buf + ret, BUF_SIZE - ret,
			"%8s   %32s %32s %32s %32s\n", "Master", "active_since(us ago)",
			"last_rel_time(us ago)", "total_req_time(us)", "req_count");

	for (i = 0; i < MIF_MASTER_MAX; i++) {
#if defined(CONFIG_SOC_S5E9925)
		apm_req[i].active_req_tick = __raw_readl(flexpmu_dbg_base
				+ (DATA_LINE * (DID_MIFAP0 + i * 2)) + DATA_IDX);
		apm_req[i].last_rel_tick = __raw_readl(flexpmu_dbg_base
				+ (DATA_LINE * (DID_MIFAP0 + i * 2)) + DATA_IDX  + 4);
		apm_req[i].total_count = __raw_readl(flexpmu_dbg_base
				+ (DATA_LINE * (DID_MIFAP1 + i * 2)) + DATA_IDX);
		apm_req[i].total_time_tick = __raw_readl(flexpmu_dbg_base
				+ (DATA_LINE * (DID_MIFAP1 + i * 2)) + DATA_IDX  + 4);
#elif defined(CONFIG_SOC_S5E8825)
		apm_req[i].active_req_tick = __raw_readl(flexpmu_dbg_base
				+ (DATA_LINE * (DID_MIFAUD0 + i * 3)) + DATA_IDX);
		apm_req[i].last_rel_tick = __raw_readl(flexpmu_dbg_base
				+ (DATA_LINE * (DID_MIFAUD0 + i * 3)) + DATA_IDX  + 4);
		apm_req[i].total_count = __raw_readl(flexpmu_dbg_base
				+ (DATA_LINE * (DID_MIFAUD1 + i * 3)) + DATA_IDX);
		apm_req[i].total_time_tick = __raw_readl(flexpmu_dbg_base
				+ (DATA_LINE * (DID_MIFAUD1 + i * 3)) + DATA_IDX  + 4);
#endif
		if (apm_req[i].last_rel_tick > 0) {
			apm_req[i].last_rel_us =
				(curr_tick - apm_req[i].last_rel_tick) * RTC_TICK_TO_US;
		}

		apm_req[i].total_time_us =
			apm_req[i].total_time_tick * RTC_TICK_TO_US;

		if (apm_req[i].active_req_tick == 0) {
			apm_req[i].active_flag = false;
			apm_req[i].active_since_us = 0;
		} else {
			apm_req[i].active_flag = true;
			apm_req[i].active_since_us =
				(curr_tick - apm_req[i].active_req_tick) * RTC_TICK_TO_US;
			apm_req[i].total_time_us += apm_req[i].active_since_us;
		}

		if (BUF_SIZE - (BUF_MAX_LINE * 3) > ret) {
			ret += snprintf(buf + ret, BUF_SIZE - ret,
					"%8s : %32lld %32lld %32lld %32d\n",
					flexpmu_master_name[i],
					apm_req[i].active_since_us,
					apm_req[i].last_rel_us,
					apm_req[i].total_time_us,
					apm_req[i].total_count);
		}
	}

		return ret;
}

static ssize_t (*flexpmu_debugfs_read_fptr[FID_MAX])(int, char *) = {
	FLEXPMU_DBG_FUNC_READ(cpu_status),
	FLEXPMU_DBG_FUNC_READ(seq_status),
	FLEXPMU_DBG_FUNC_READ(cur_sequence),
	FLEXPMU_DBG_FUNC_READ(sw_flag),
	FLEXPMU_DBG_FUNC_READ(seq_count),
	FLEXPMU_DBG_FUNC_READ(mif_always_on),
	FLEXPMU_DBG_FUNC_READ(lpm_count),
	FLEXPMU_DBG_FUNC_READ(apm_req_info),
#if defined(CONFIG_SOC_S5E9925)
	FLEXPMU_DBG_FUNC_READ(mid_dvs_en),
#endif
};

static ssize_t exynos_flexpmu_dbg_read(struct file *file, char __user *user_buf,
		size_t count, loff_t *ppos)
{
	struct dbgfs_info *d2f = file->private_data;
	ssize_t ret;
	char *buf = NULL;

	buf = kzalloc(sizeof(char) * BUF_SIZE, GFP_KERNEL);

	ret = flexpmu_debugfs_read_fptr[d2f->fid](d2f->fid, buf);
	if (ret > sizeof(char) * BUF_SIZE)
		return ret;

	ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);

	kfree(buf);

	return ret;
}

static ssize_t exynos_flexpmu_dbg_write(struct file *file, const char __user *user_buf,
		size_t count, loff_t *ppos)
{
	struct dbgfs_info *f2d = file->private_data;
	ssize_t ret;
	char buf[32];

	ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
	if (ret < 0)
		return ret;

	switch (f2d->fid) {
	case FID_MIF_ALWAYS_ON:
		if (buf[0] == '0') {
			__raw_writel(0, flexpmu_dbg_base +
					(DATA_LINE * DID_MIF_ALWAYS_ON) + 0xC);
		}
		if (buf[0] == '1') {
			__raw_writel(1, flexpmu_dbg_base +
					(DATA_LINE * DID_MIF_ALWAYS_ON) + 0xC);
		}
		break;
#if defined(CONFIG_SOC_S5E9925)
	case FID_MID_DVS_EN:
		if (buf[0] == '0') {
			__raw_writel(0, flexpmu_dbg_base +
					(DATA_LINE * DID_MID_DVS_EN) + 0xC);
		}
		if (buf[0] == '1') {
			__raw_writel(1, flexpmu_dbg_base +
					(DATA_LINE * DID_MID_DVS_EN) + 0xC);
		}
		break;
#endif
	default:
		break;
	}

	return ret;
}

void exynos_flexpmu_dbg_set_sleep_req(void)
{
	__raw_writel(1, flexpmu_dbg_base + (DATA_LINE * DID_SLEEP_WAKEUP) + 0x8);
}
EXPORT_SYMBOL_GPL(exynos_flexpmu_dbg_set_sleep_req);

void exynos_flexpmu_dbg_clr_wakeup_req(void)
{
	__raw_writel(0, flexpmu_dbg_base + (DATA_LINE * DID_SLEEP_WAKEUP) + 0xC);
}
EXPORT_SYMBOL_GPL(exynos_flexpmu_dbg_clr_wakeup_req);

static int exynos_flexpmu_dbg_probe(struct platform_device *pdev)
{
	int ret = 0;
	const __be32 *prop;
	unsigned int len = 0;
	unsigned int data_base = 0;
	unsigned int data_size = 0;
	unsigned int disable_mifdown = 0;
	int i;

	flexpmu_dbg_info = kzalloc(sizeof(struct dbgfs_info) * FID_MAX, GFP_KERNEL);
	if (!flexpmu_dbg_info) {
		pr_err("%s %s: can not allocate mem for flexpmu_dbg_info\n",
				EXYNOS_FLEXPMU_DBG_PREFIX, __func__);
		ret = -ENOMEM;
		goto err_flexpmu_info;
	}

	prop = of_get_property(pdev->dev.of_node, "data-base", &len);
	if (!prop) {
		pr_err("%s %s: can not read data-base in DT\n",
				EXYNOS_FLEXPMU_DBG_PREFIX, __func__);
		ret = -EINVAL;
		goto err_dbgfs_probe;
	}
	data_base = be32_to_cpup(prop);

	prop = of_get_property(pdev->dev.of_node, "data-size", &len);
	if (!prop) {
		pr_err("%s %s: can not read data-base in DT\n",
				EXYNOS_FLEXPMU_DBG_PREFIX, __func__);
		ret = -EINVAL;
		goto err_dbgfs_probe;
	}
	data_size = be32_to_cpup(prop);

	if (data_base && data_size)
		flexpmu_dbg_base = ioremap(data_base, data_size);

	flexpmu_dbg_root = debugfs_create_dir("flexpmu-dbg", NULL);
	if (!flexpmu_dbg_root) {
		pr_err("%s %s: could not create debugfs root dir\n",
				EXYNOS_FLEXPMU_DBG_PREFIX, __func__);
		ret = -ENOMEM;
		goto err_dbgfs_probe;
	}

	for (i = 0; i < FID_MAX; i++) {
		flexpmu_dbg_info[i].fid = i;
		flexpmu_dbg_info[i].fops.open = simple_open;
		flexpmu_dbg_info[i].fops.read = exynos_flexpmu_dbg_read;
		flexpmu_dbg_info[i].fops.write = exynos_flexpmu_dbg_write;
		flexpmu_dbg_info[i].fops.llseek = default_llseek;
		flexpmu_dbg_info[i].den = debugfs_create_file(flexpmu_debugfs_name[i],
				0644, flexpmu_dbg_root, &flexpmu_dbg_info[i],
				&flexpmu_dbg_info[i].fops);
	}

	rtc_base = of_iomap(pdev->dev.of_node, 0);
	if (!rtc_base) {
		dev_info(&pdev->dev,
				"apm_req_info node is not available!\n");
	}

	of_property_read_u32(pdev->dev.of_node, "disable_mifdown", &disable_mifdown);
	if (disable_mifdown)
		__raw_writel(0, flexpmu_dbg_base +
				(DATA_LINE * DID_MIF_ALWAYS_ON) + 0xC);

	platform_set_drvdata(pdev, flexpmu_dbg_info);

	return 0;

err_dbgfs_probe:
	kfree(flexpmu_dbg_info);
err_flexpmu_info:
	return ret;
}

static int exynos_flexpmu_dbg_remove(struct platform_device *pdev)
{
	struct dbgfs_info *flexpmu_dbg_info = platform_get_drvdata(pdev);

	debugfs_remove_recursive(flexpmu_dbg_root);
	kfree(flexpmu_dbg_info);
	platform_set_drvdata(pdev, NULL);

	return 0;
}

static const struct of_device_id exynos_flexpmu_dbg_match[] = {
	{
		.compatible = "samsung,exynos-flexpmu-dbg",
	},
	{},
};

static struct platform_driver exynos_flexpmu_dbg_drv = {
	.probe		= exynos_flexpmu_dbg_probe,
	.remove		= exynos_flexpmu_dbg_remove,
	.driver		= {
		.name	= "exynos_flexpmu_dbg",
		.owner	= THIS_MODULE,
		.of_match_table = exynos_flexpmu_dbg_match,
	},
};

static int exynos_flexpmu_dbg_init(void)
{
	return platform_driver_register(&exynos_flexpmu_dbg_drv);
}
late_initcall(exynos_flexpmu_dbg_init);

MODULE_LICENSE("GPL");