static void mmdvfs_dump_info(void)
{
	MMDVFSMSG("CMD %d %d %d\n", g_mmdvfs_cmd.sensor_size,
	g_mmdvfs_cmd.sensor_fps, g_mmdvfs_cmd.camera_mode);
	MMDVFSMSG("INFO VR %d %d\n", g_mmdvfs_info->video_record_size[0],
	g_mmdvfs_info->video_record_size[1]);
}
int mmdvfs_set_step(MTK_SMI_BWC_SCEN scenario, mmdvfs_voltage_enum step)
{
	int i, scen_index;
	mmdvfs_voltage_enum final_step = mmdvfs_get_default_step();

#if !MMDVFS_ENABLE
	return 0;
#endif

#if defined(SMI_D1)
	/* D1 FHD always HPM. do not have to trigger vcore dvfs. */
	if (mmdvfs_get_lcd_resolution() == MMDVFS_LCD_SIZE_FHD)
		return 0;

#endif

	MMDVFSMSG("MMDVFS set voltage scen %d step %d\n", scenario, step);

	if ((scenario >= MMDVFS_SCEN_COUNT) || (scenario < SMI_BWC_SCEN_NORMAL)) {
		MMDVFSERR("invalid scenario\n");
		return -1;
	}

	/* dump information */
	mmdvfs_dump_info();

	/* go through all scenarios to decide the final step */
	scen_index = (int)scenario;

	spin_lock(&g_mmdvfs_mgr->scen_lock);

	g_mmdvfs_scenario_voltage[scen_index] = step;

	/* one high = final high */
	for (i = 0; i < MMDVFS_SCEN_COUNT; i++) {
		if (g_mmdvfs_scenario_voltage[i] == MMDVFS_VOLTAGE_HIGH) {
			final_step = MMDVFS_VOLTAGE_HIGH;
			break;
		}
	}

	g_mmdvfs_current_step = final_step;

	spin_unlock(&g_mmdvfs_mgr->scen_lock);

	MMDVFSMSG("MMDVFS set voltage scen %d step %d final %d\n", scenario,
	step, final_step);

#if MMDVFS_ENABLE
	/* call vcore dvfs API */
	if (final_step == MMDVFS_VOLTAGE_HIGH)
		vcorefs_request_dvfs_opp(KIR_MM, OPPI_PERF);
	else
		vcorefs_request_dvfs_opp(KIR_MM, OPPI_UNREQ);

#endif

	return 0;
}
void mmdvfs_handle_cmd(MTK_MMDVFS_CMD *cmd)
{
#if !MMDVFS_ENABLE
	return;
#endif

	MMDVFSMSG("MMDVFS cmd %u %d\n", cmd->type, cmd->scen);

	switch (cmd->type) {
	case MTK_MMDVFS_CMD_TYPE_SET:
		/* save cmd */
		mmdvfs_update_cmd(cmd);
		cmd->ret = mmdvfs_set_step(cmd->scen,
		mmdvfs_query(cmd->scen, cmd));
		break;

	case MTK_MMDVFS_CMD_TYPE_QUERY: { /* query with some parameters */
		if (mmdvfs_get_lcd_resolution() == MMDVFS_LCD_SIZE_FHD) {
			/* QUERY ALWAYS HIGH for FHD */
			cmd->ret = (unsigned int)MMDVFS_STEP_HIGH2HIGH;

		} else { /* FHD */
			mmdvfs_voltage_enum query_voltage = mmdvfs_query(cmd->scen, cmd);

			mmdvfs_voltage_enum current_voltage =	mmdvfs_get_current_step();

			if (current_voltage < query_voltage) {
				cmd->ret = (unsigned int)MMDVFS_STEP_LOW2HIGH;
			} else if (current_voltage > query_voltage) {
				cmd->ret = (unsigned int)MMDVFS_STEP_HIGH2LOW;
			} else {
				cmd->ret
				= (unsigned int)(query_voltage
				== MMDVFS_VOLTAGE_HIGH
							 ? MMDVFS_STEP_HIGH2HIGH
							 : MMDVFS_STEP_LOW2LOW);
			}
		}

		MMDVFSMSG("query %d\n", cmd->ret);
		/* cmd->ret = (unsigned int)query_voltage; */
		break;
	}

	default:
		MMDVFSMSG("invalid mmdvfs cmd\n");
		BUG();
		break;
	}
}
void mmdvfs_notify_scenario_concurrency(unsigned int u4Concurrency)
{
	/* raise EMI monitor BW threshold in VP, VR, VR SLOW motion cases to make sure vcore stay MMDVFS level as long as possible */
	if (u4Concurrency & ((1 << SMI_BWC_SCEN_VP) | (1 << SMI_BWC_SCEN_VR) | (1 << SMI_BWC_SCEN_VR_SLOW))) {
	#if MMDVFS_ENABLE
		MMDVFSMSG("fliper high\n");
		fliper_set_bw(BW_THRESHOLD_HIGH);
	#endif		
	} else {
	#if MMDVFS_ENABLE	
		MMDVFSMSG("fliper normal\n");	
		fliper_restore_bw();
	#endif		
	}
}
void mmdvfs_notify_scenario_enter(MTK_SMI_BWC_SCEN scen)
{
#if !MMDVFS_ENABLE
	return;
#endif

	MMDVFSMSG("enter %d\n", scen);

	/* ISP ON = high */
	switch (scen) {
#if defined(SMI_D2)         /* d2 sensor > 6M */
	case SMI_BWC_SCEN_VR:
	mmdvfs_set_step(scen, mmdvfs_query(scen, NULL));
	break;
	case SMI_BWC_SCEN_VR_SLOW:
#elif defined(SMI_D1)       /* default VR high */
	case SMI_BWC_SCEN_VR:
	case SMI_BWC_SCEN_VR_SLOW:
#else               /* D3 */
	case SMI_BWC_SCEN_WFD:
	case SMI_BWC_SCEN_VSS:
#endif
		/* Fall through */
	case SMI_BWC_SCEN_ICFP:
		/* Fall through */
	case SMI_BWC_SCEN_FORCE_MMDVFS:
		mmdvfs_set_step(scen, MMDVFS_VOLTAGE_HIGH);
		break;

	default:
		break;
	}
}
void mmdvfs_notify_scenario_enter(MTK_SMI_BWC_SCEN scen)
{
#if !MMDVFS_ENABLE
	return;
#endif

	MMDVFSMSG("enter %d\n", scen);

	if (mmdvfs_get_lcd_resolution() == MMDVFS_LCD_SIZE_WQHD) {
	#if MMDVFS_ENABLE_WQHD
		if (scen == SMI_BWC_SCEN_VP) {
			mmdvfs_start_gpu_monitor(&g_mmdvfs_mgr->gpu_monitor);
		}
	#endif /* MMDVFS_ENABLE_WQHD */
	} else {	/* FHD */
		switch (scen) {
			case SMI_BWC_SCEN_ICFP:
				mmdvfs_set_step(scen, MMDVFS_VOLTAGE_HIGH);
				break;

			case SMI_BWC_SCEN_VR:
			case SMI_BWC_SCEN_VR_SLOW:		
				mmdvfs_set_step(scen, mmdvfs_query(scen, NULL));
				/* workaround for ICFP...its mmdvfs_set() will come after leaving ICFP */
				mmdvfs_set_step(SMI_BWC_SCEN_ICFP, mmdvfs_get_default_step());
				break;
				
			default:
				break;
		}
	}	
}
static void mmdvfs_start_cam_monitor(void)
{
	mmdvfs_stop_cam_monitor();
	MMDVFSMSG("CAM start %d\n", jiffies_to_msecs(jiffies));
	mmdvfs_set_step(MMDVFS_CAM_MON_SCEN, MMDVFS_VOLTAGE_HIGH);
	/* 4 seconds for PIP switch preview aspect delays... */
	schedule_delayed_work(&g_mmdvfs_cam_work, MMDVFS_CAM_MON_DELAY);
}
static void mmdvfs_update_cmd(MTK_MMDVFS_CMD *cmd)
{
	if (cmd == NULL)
		return;

	if (cmd->sensor_size)
		g_mmdvfs_cmd.sensor_size = cmd->sensor_size;

	if (cmd->sensor_fps)
		g_mmdvfs_cmd.sensor_fps = cmd->sensor_fps;

	MMDVFSMSG("update cm %d %d\n", cmd->camera_mode, cmd->sensor_size);
	g_mmdvfs_cmd.camera_mode = cmd->camera_mode;
}
void mmdvfs_notify_scenario_exit(MTK_SMI_BWC_SCEN scen)
{
#if !MMDVFS_ENABLE
	return;
#endif

	MMDVFSMSG("leave %d\n", scen);

	if (mmdvfs_get_lcd_resolution() == MMDVFS_LCD_SIZE_WQHD) {
	#if MMDVFS_ENABLE_WQHD	
		if (scen == SMI_BWC_SCEN_VP) {
			mmdvfs_stop_gpu_monitor(&g_mmdvfs_mgr->gpu_monitor);
		}
	#endif /* MMDVFS_ENABLE_WQHD */
	}

	/* reset scenario voltage to default when it exits */
	mmdvfs_set_step(scen, mmdvfs_get_default_step());
}
static void mmdvfs_update_cmd(MTK_MMDVFS_CMD *cmd)
{
	if (cmd == NULL) {
		return;
	}

	if (cmd->sensor_size) {	
		g_mmdvfs_cmd.sensor_size = cmd->sensor_size;
	}

	if (cmd->sensor_fps) {
		g_mmdvfs_cmd.sensor_fps = cmd->sensor_fps;
	}

	
	MMDVFSMSG("update cm %d\n", cmd->camera_mode);

	// if (cmd->camera_mode != MMDVFS_CAMERA_MODE_FLAG_DEFAULT) {
		g_mmdvfs_cmd.camera_mode = cmd->camera_mode;
	// }
}
static void mmdvfs_timer_callback(unsigned long data)
{
	mmdvfs_gpu_monitor_struct *gpu_monitor = (mmdvfs_gpu_monitor_struct *)data;
	
	unsigned int gpu_loading = 0;

	if (mtk_get_gpu_loading(&gpu_loading)) {
		// MMDVFSMSG("gpuload %d %ld\n", gpu_loading, jiffies_to_msecs(jiffies));
	}

	/* store gpu loading into the array */
	gpu_monitor->gpu_loadings[gpu_monitor->gpu_loading_index++] = gpu_loading;

	/* fire another timer until the end */
	if (gpu_monitor->gpu_loading_index < MMDVFS_GPU_LOADING_NUM - 1)
	{
		mod_timer(&gpu_monitor->timer, jiffies + msecs_to_jiffies(MMDVFS_GPU_LOADING_SAMPLE_DURATION_IN_MS));
	} else {
		/* the final timer */
		int i;
		int avg_loading;
		unsigned int sum = 0;

		for (i = MMDVFS_GPU_LOADING_START_INDEX; i < MMDVFS_GPU_LOADING_NUM; i++)
		{
			sum += gpu_monitor->gpu_loadings[i];
		}

		avg_loading = sum / MMDVFS_GPU_LOADING_NUM;

		MMDVFSMSG("gpuload %d AVG %d\n", jiffies_to_msecs(jiffies), avg_loading);

		/* drops to low step if the gpu loading is low */
		if (avg_loading <= MMDVFS_GPU_LOADING_THRESHOLD) {
			queue_work(gpu_monitor->work_queue, &gpu_monitor->work);
		}
	}
	
}
void mmdvfs_notify_scenario_exit(MTK_SMI_BWC_SCEN scen)
{
#if !MMDVFS_ENABLE
	return;
#endif

	MMDVFSMSG("leave %d\n", scen);

#if !defined(SMI_D3)        /* d3 does not need this workaround because the MMCLK is always the highest */
	/*
	 * keep HPM for 4 seconds after exiting camera scenarios to get rid of
	 * cam framework will let us go to normal scenario for a short time
	 * (ex: STOP PREVIEW --> NORMAL --> START PREVIEW)
	 * where the LPM mode (low MMCLK) may cause ISP failures
	 */
	if ((scen == SMI_BWC_SCEN_VR) || (scen == SMI_BWC_SCEN_VR_SLOW)
	|| (scen == SMI_BWC_SCEN_ICFP)) {
		mmdvfs_start_cam_monitor();
	}
#endif              /* !defined(SMI_D3) */

	/* reset scenario voltage to default when it exits */
	mmdvfs_set_step(scen, mmdvfs_get_default_step());
}
static void mmdvfs_cam_work_handler(struct work_struct *work)
{
	MMDVFSMSG("CAM handler %d\n", jiffies_to_msecs(jiffies));
	mmdvfs_set_step(MMDVFS_CAM_MON_SCEN, mmdvfs_get_default_step());
}
int mmdvfs_set_step(MTK_SMI_BWC_SCEN scenario, mmdvfs_voltage_enum step)
{
	int i, scen_index;
	mmdvfs_voltage_enum final_step = mmdvfs_get_default_step();

#if !MMDVFS_ENABLE
	return 0;
#endif

#if !MMDVFS_ENABLE_WQHD
	/* do nothing if disable MMDVFS in WQHD */
	if (mmdvfs_get_lcd_resolution() == MMDVFS_LCD_SIZE_WQHD) {
		return 0;
	}
#endif /* MMDVFS_ENABLE_WQHD */

	MMDVFSMSG("MMDVFS set voltage scen %d step %d\n", scenario, step);

	if (scenario >= SMI_BWC_SCEN_CNT || (scenario < SMI_BWC_SCEN_NORMAL))
	{
		MMDVFSERR("invalid scenario\n");
		return -1;
	}

	/* dump information */
	mmdvfs_dump_info();

	/* go through all scenarios to decide the final step */
	scen_index = (int)scenario;

	spin_lock(&g_mmdvfs_mgr->scen_lock);
	
	g_mmdvfs_scenario_voltage[scen_index] = step;

	/* one high = final high */
	for (i = 0; i < SMI_BWC_SCEN_CNT; i++) {
		if (g_mmdvfs_scenario_voltage[i] == MMDVFS_VOLTAGE_HIGH) {
			final_step = MMDVFS_VOLTAGE_HIGH;
			break;
		}
	}

	g_mmdvfs_current_step = final_step;
	
	spin_unlock(&g_mmdvfs_mgr->scen_lock);

	MMDVFSMSG("MMDVFS set voltage scen %d step %d final %d\n", scenario, step, final_step);

#if	MMDVFS_ENABLE
	/* call vcore dvfs API */
    if (mmdvfs_get_lcd_resolution() == MMDVFS_LCD_SIZE_WQHD) {	/* WQHD */
    	// TODO: use final_step and retry vcore dvfs
		MMDVFSMSG("HIGH to %d\n", step);
		if (step == MMDVFS_VOLTAGE_LOW) {
			vcorefs_request_dvfs_opp(KR_MM_SCEN, OPPI_LOW_PWR);
		} else {
			vcorefs_request_dvfs_opp(KR_MM_SCEN, OPPI_UNREQ);	
		}
	} else { /* FHD */
		MMDVFSMSG("FHD %d\n", final_step);	
		if (final_step == MMDVFS_VOLTAGE_HIGH) {
			vcorefs_request_dvfs_opp(KR_MM_SCEN, OPPI_PERF);
		} else {
			vcorefs_request_dvfs_opp(KR_MM_SCEN, OPPI_UNREQ);
		}
	}
#endif

	return 0;
}
static void mmdvfs_gpu_monitor_work(struct work_struct *work)
{
	MMDVFSMSG("WQ %d\n", jiffies_to_msecs(jiffies));
}