Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e4d2d25 authored by Maheshwar Ajja's avatar Maheshwar Ajja Committed by Gerrit - the friendly Code Review server
Browse files

msm: vidc: Add thermal mitigation feature



Thermal agent from userspace reports thermal level
value through sysfs node. Based on thermal levels,
video driver takes below action to avoid any further
overheating.
1. Normal thermal level(T0) is ignored.
2. Low(T1) or high(T2) thermal level is ignored for
   usecase running in nominal/svs clock whereas all
   video sessions are terminated for turbo usecase.
3. During critical(T3) thermal level, all video
   sessions are terminated.

Change-Id: I516761fc51e824b35957a85009de7f08b15d887c
Signed-off-by: default avatarVikash Garodia <vgarodia@codeaurora.org>
Signed-off-by: default avatarMaheshwar Ajja <majja@codeaurora.org>
parent 913bc12d
Loading
Loading
Loading
Loading
+47 −3
Original line number Diff line number Diff line
@@ -388,6 +388,49 @@ static ssize_t show_pwr_collapse_delay(struct device *dev,
static DEVICE_ATTR(pwr_collapse_delay, 0644, show_pwr_collapse_delay,
		store_pwr_collapse_delay);

static ssize_t show_thermal_level(struct device *dev,
		struct device_attribute *attr,
		char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%d\n", vidc_driver->thermal_level);
}

static ssize_t store_thermal_level(struct device *dev,
		struct device_attribute *attr,
		const char *buf, size_t count)
{
	int rc = 0, val = 0;

	rc = kstrtoint(buf, 0, &val);
	if (rc || val < 0) {
		dprintk(VIDC_WARN,
			"Invalid thermal level value: %s\n", buf);
		return -EINVAL;
	}
	dprintk(VIDC_DBG, "Thermal level old %d new %d\n",
			vidc_driver->thermal_level, val);

	if (val == vidc_driver->thermal_level)
		return count;
	vidc_driver->thermal_level = val;

	msm_comm_handle_thermal_event();
	return count;
}

static DEVICE_ATTR(thermal_level, S_IRUGO | S_IWUSR, show_thermal_level,
		store_thermal_level);

static struct attribute *msm_vidc_core_attrs[] = {
		&dev_attr_pwr_collapse_delay.attr,
		&dev_attr_thermal_level.attr,
		NULL
};

static struct attribute_group msm_vidc_core_attr_group = {
		.attrs = msm_vidc_core_attrs,
};

static int msm_vidc_probe(struct platform_device *pdev)
{
	int rc = 0;
@@ -407,10 +450,10 @@ static int msm_vidc_probe(struct platform_device *pdev)
		dprintk(VIDC_ERR, "Failed to init core\n");
		goto err_core_init;
	}
	rc = device_create_file(&pdev->dev, &dev_attr_pwr_collapse_delay);
	rc = sysfs_create_group(&pdev->dev.kobj, &msm_vidc_core_attr_group);
	if (rc) {
		dprintk(VIDC_ERR,
				"Failed to create pwr_collapse_delay sysfs node");
				"Failed to create attributes\n");
		goto err_core_init;
	}
	if (core->hfi_type == VIDC_HFI_Q6) {
@@ -513,7 +556,7 @@ err_dec_attr_link_name:
err_dec_register:
	v4l2_device_unregister(&core->v4l2_dev);
err_v4l2_register:
	device_remove_file(&pdev->dev, &dev_attr_pwr_collapse_delay);
	sysfs_remove_group(&pdev->dev.kobj, &msm_vidc_core_attr_group);
err_core_init:
	kfree(core);
err_no_mem:
@@ -546,6 +589,7 @@ static int msm_vidc_remove(struct platform_device *pdev)
	v4l2_device_unregister(&core->v4l2_dev);

	msm_vidc_free_platform_resources(&core->resources);
	sysfs_remove_group(&pdev->dev.kobj, &msm_vidc_core_attr_group);
	kfree(core);
	return rc;
}
+215 −51
Original line number Diff line number Diff line
@@ -1080,6 +1080,43 @@ static void handle_session_error(enum command_response cmd, void *data)
	}
}

static void msm_comm_clean_notify_client(struct msm_vidc_core *core)
{
	int rc = 0;
	struct msm_vidc_inst *inst = NULL;
	struct hfi_device *hdev = NULL;
	if (!core || !core->device) {
		dprintk(VIDC_ERR, "%s: Invalid params\n", __func__);
		return;
	}

	dprintk(VIDC_WARN, "%s: Core %p\n", __func__, core);
	mutex_lock(&core->lock);
	core->state = VIDC_CORE_INVALID;

	list_for_each_entry(inst, &core->instances, list) {
		mutex_lock(&inst->lock);
		inst->state = MSM_VIDC_CORE_INVALID;
		hdev = inst->core->device;
		if (hdev && inst->session) {
			dprintk(VIDC_DBG,
				"cleaning up inst: 0x%p\n", inst);
			rc = call_hfi_op(hdev, session_clean,
					(void *) inst->session);
			if (rc)
				dprintk(VIDC_ERR,
					"Sess clean failed :%p\n", inst);
		}
		inst->session = NULL;
		mutex_unlock(&inst->lock);
		dprintk(VIDC_WARN,
			"%s Send sys error for inst %p\n", __func__, inst);
		msm_vidc_queue_v4l2_event(inst,
				V4L2_EVENT_MSM_VIDC_SYS_ERROR);
	}
	mutex_unlock(&core->lock);
}

struct sys_err_handler_data {
	struct msm_vidc_core *core;
	struct delayed_work work;
@@ -1133,9 +1170,6 @@ static void handle_sys_error(enum command_response cmd, void *data)
	struct msm_vidc_cb_cmd_done *response = data;
	struct msm_vidc_core *core = NULL;
	struct sys_err_handler_data *handler = NULL;
	struct hfi_device *hdev = NULL;
	struct msm_vidc_inst *inst = NULL;
	int rc = 0;

	subsystem_crashed("venus");
	if (!response) {
@@ -1152,35 +1186,7 @@ static void handle_sys_error(enum command_response cmd, void *data)
	}

	dprintk(VIDC_WARN, "SYS_ERROR %d received for core %p\n", cmd, core);
	mutex_lock(&core->lock);
	core->state = VIDC_CORE_INVALID;

	/*
	* 1. Delete each instance session from hfi list
	* 2. Notify all clients about hardware error.
	*/
	list_for_each_entry(inst, &core->instances, list) {
		mutex_lock(&inst->lock);
		inst->state = MSM_VIDC_CORE_INVALID;
		if (inst->core)
			hdev = inst->core->device;
		if (hdev && inst->session) {
			dprintk(VIDC_DBG,
			"cleaning up inst: 0x%p\n", inst);
			rc = call_hfi_op(hdev, session_clean,
				(void *) inst->session);
			if (rc)
				dprintk(VIDC_ERR,
					"Sess clean failed :%p\n",
					inst);
		}
		inst->session = NULL;
		mutex_unlock(&inst->lock);
		msm_vidc_queue_v4l2_event(inst,
				V4L2_EVENT_MSM_VIDC_SYS_ERROR);
	}
	mutex_unlock(&core->lock);

	msm_comm_clean_notify_client(core);

	handler = kzalloc(sizeof(*handler), GFP_KERNEL);
	if (!handler) {
@@ -2143,6 +2149,167 @@ void msm_comm_scale_clocks_and_bus(struct msm_vidc_inst *inst)
	}
}

static inline enum msm_vidc_thermal_level msm_comm_vidc_thermal_level(int level)
{
	switch (level) {
	case 0:
		return VIDC_THERMAL_NORMAL;
	case 1:
		return VIDC_THERMAL_LOW;
	case 2:
		return VIDC_THERMAL_HIGH;
	default:
		return VIDC_THERMAL_CRITICAL;
	}
}

static unsigned long msm_comm_get_clock_rate(struct msm_vidc_core *core)
{
	struct hfi_device *hdev;
	unsigned long freq = 0;

	if (!core || !core->device) {
		dprintk(VIDC_ERR, "%s Invalid params\n", __func__);
		return -EINVAL;
	}
	hdev = core->device;

	freq = call_hfi_op(hdev, get_core_clock_rate, hdev->hfi_device_data);
	dprintk(VIDC_DBG, "clock freq %ld\n", freq);

	return freq;
}

static bool is_core_turbo(struct msm_vidc_core *core, unsigned long freq)
{
	int i = 0;
	struct msm_vidc_platform_resources *res = &core->resources;
	struct load_freq_table *table = res->load_freq_tbl;
	u32 max_freq = 0;

	for (i = 0; i < res->load_freq_tbl_size; i++) {
		if (max_freq < table[i].freq)
			max_freq = table[i].freq;
	}
	return freq >= max_freq;
}

static bool is_thermal_permissible(struct msm_vidc_core *core)
{
	enum msm_vidc_thermal_level tl;
	unsigned long freq = 0;
	bool is_turbo = false;

	tl = msm_comm_vidc_thermal_level(vidc_driver->thermal_level);
	freq = msm_comm_get_clock_rate(core);

	is_turbo = is_core_turbo(core, freq);
	dprintk(VIDC_DBG,
		"Core freq %ld Thermal level %d Turbo mode %d\n",
		freq, tl, is_turbo);

	if ((!is_turbo && tl >= VIDC_THERMAL_CRITICAL) ||
				(is_turbo && tl >= VIDC_THERMAL_LOW)) {
		dprintk(VIDC_ERR,
			"Video session not allowed. Turbo mode %d Thermal level %d\n",
			is_turbo, tl);
		return false;
	}
	return true;
}

static int msm_comm_session_abort(struct msm_vidc_inst *inst)
{
	int rc = 0, abort_completion = 0;
	struct hfi_device *hdev;

	if (!inst || !inst->core || !inst->core->device) {
		dprintk(VIDC_ERR, "%s invalid params\n", __func__);
		return -EINVAL;
	}
	hdev = inst->core->device;

	rc = call_hfi_op(hdev, session_abort, (void *)inst->session);
	if (rc) {
		dprintk(VIDC_ERR,
			"%s session_abort failed rc: %d\n", __func__, rc);
		return rc;
	}
	abort_completion = SESSION_MSG_INDEX(SESSION_ABORT_DONE);
	init_completion(&inst->completions[abort_completion]);
	rc = wait_for_completion_timeout(
			&inst->completions[abort_completion],
			msecs_to_jiffies(msm_vidc_hw_rsp_timeout));
	if (!rc) {
		dprintk(VIDC_ERR,
				"%s: Wait interrupted or timed out [%p]: %d\n",
				__func__, inst, abort_completion);
		rc = -EBUSY;
	} else {
		rc = 0;
	}

	return rc;
}

static void handle_thermal_event(struct msm_vidc_core *core)
{
	int rc = 0;
	struct msm_vidc_inst *inst;

	if (!core || !core->device) {
		dprintk(VIDC_ERR, "%s Invalid params\n", __func__);
		return;
	}
	mutex_lock(&core->lock);
	list_for_each_entry(inst, &core->instances, list) {
		if (!inst->session)
			continue;

		mutex_unlock(&core->lock);
		if (inst->state >= MSM_VIDC_OPEN_DONE &&
			inst->state < MSM_VIDC_CLOSE_DONE) {
			dprintk(VIDC_WARN, "%s: abort inst %p\n",
				__func__, inst);
			rc = msm_comm_session_abort(inst);
			if (rc) {
				dprintk(VIDC_ERR,
					"%s session_abort failed rc: %d\n",
					__func__, rc);
				goto err_sess_abort;
			}
			change_inst_state(inst, MSM_VIDC_CORE_INVALID);
			dprintk(VIDC_WARN,
				"%s Send sys error for inst %p\n",
				__func__, inst);
			msm_vidc_queue_v4l2_event(inst,
					V4L2_EVENT_MSM_VIDC_SYS_ERROR);
		} else {
			msm_comm_generate_session_error(inst);
		}
		mutex_lock(&core->lock);
	}
	mutex_unlock(&core->lock);
	return;

err_sess_abort:
	msm_comm_clean_notify_client(core);
	return;
}

void msm_comm_handle_thermal_event()
{
	struct msm_vidc_core *core;

	list_for_each_entry(core, &vidc_driver->cores, list) {
		if (!is_thermal_permissible(core)) {
			dprintk(VIDC_WARN,
				"Thermal level critical, stop all active sessions!\n");
			handle_thermal_event(core);
		}
	}
}

static int msm_comm_init_core_done(struct msm_vidc_inst *inst)
{
	struct msm_vidc_core *core = inst->core;
@@ -3031,6 +3198,7 @@ int msm_comm_try_state(struct msm_vidc_inst *inst, int state)
		if (rc || state <= get_flipped_state(inst->state, state))
			break;
	case MSM_VIDC_CORE_UNINIT:
	case MSM_VIDC_CORE_INVALID:
		dprintk(VIDC_DBG, "Sending core uninit\n");
		rc = msm_vidc_deinit_core(inst);
		if (rc || state == get_flipped_state(inst->state, state))
@@ -4377,6 +4545,7 @@ int msm_vidc_check_session_supported(struct msm_vidc_inst *inst)
	struct msm_vidc_core_capability *capability;
	int rc = 0;
	struct hfi_device *hdev;
	struct msm_vidc_core *core;

	if (!inst || !inst->core || !inst->core->device) {
		dprintk(VIDC_WARN, "%s: Invalid parameter\n", __func__);
@@ -4384,6 +4553,7 @@ int msm_vidc_check_session_supported(struct msm_vidc_inst *inst)
	}
	capability = &inst->capability;
	hdev = inst->core->device;
	core = inst->core;
	rc = msm_vidc_load_supported(inst);
	if (rc) {
		change_inst_state(inst, MSM_VIDC_CORE_INVALID);
@@ -4391,6 +4561,13 @@ int msm_vidc_check_session_supported(struct msm_vidc_inst *inst)
			"%s: Hardware is overloaded\n", __func__);
		return rc;
	}

	if (!is_thermal_permissible(core)) {
		dprintk(VIDC_WARN,
			"Thermal level critical, stop all active sessions!\n");
		return -ENOTSUPP;
	}

	if (!rc && inst->capability.capability_set) {
		if (inst->prop.width[CAPTURE_PORT] < capability->width.min ||
			inst->prop.height[CAPTURE_PORT] <
@@ -4481,27 +4658,14 @@ int msm_comm_kill_session(struct msm_vidc_inst *inst)
	 */
	if (inst->state >= MSM_VIDC_OPEN_DONE &&
			inst->state < MSM_VIDC_CLOSE_DONE) {
		struct hfi_device *hdev = inst->core->device;
		int abort_completion = SESSION_MSG_INDEX(SESSION_ABORT_DONE);

		rc = call_hfi_op(hdev, session_abort, (void *) inst->session);
		if (rc) {
			dprintk(VIDC_ERR, "session_abort failed rc: %d\n", rc);
		rc = msm_comm_session_abort(inst);
		if (rc == -EBUSY) {
			msm_comm_generate_sys_error(inst);
			return 0;
		} else if (rc)
			return rc;
		}

		init_completion(&inst->completions[abort_completion]);
		rc = wait_for_completion_timeout(
				&inst->completions[abort_completion],
				msecs_to_jiffies(msm_vidc_hw_rsp_timeout));
		if (!rc) {
			dprintk(VIDC_ERR,
					"%s: Wait interrupted or timed out [%p]: %d\n",
					__func__, inst, abort_completion);
			msm_comm_generate_sys_error(inst);
		} else {
		change_inst_state(inst, MSM_VIDC_CLOSE_DONE);
		}
	} else {
		dprintk(VIDC_WARN,
				"Inactive session %p, triggering an internal session error\n",
+2 −0
Original line number Diff line number Diff line
@@ -169,6 +169,7 @@ struct msm_vidc_drv {
	struct list_head cores;
	int num_cores;
	struct dentry *debugfs_root;
	int thermal_level;
};

struct msm_video_device {
@@ -389,6 +390,7 @@ int qbuf_dynamic_buf(struct msm_vidc_inst *inst,
int unmap_and_deregister_buf(struct msm_vidc_inst *inst,
			struct buffer_info *binfo);

void msm_comm_handle_thermal_event(void);
void *msm_smem_new_client(enum smem_type mtype,
				void *platform_resources);
struct msm_smem *msm_smem_alloc(void *clt, size_t size, u32 align, u32 flags,
+32 −0
Original line number Diff line number Diff line
@@ -1352,6 +1352,20 @@ static inline int venus_hfi_reset_core(struct venus_hfi_device *device)
	return rc;
}

static struct clock_info *venus_hfi_get_clock(struct venus_hfi_device *device,
		char *name)
{
	struct clock_info *vc;

	venus_hfi_for_each_clock(device, vc) {
		if (!strcmp(vc->name, name))
			return vc;
	}
	dprintk(VIDC_WARN, "%s Clock %s not found\n", __func__, name);

	return NULL;
}

static unsigned long venus_hfi_get_clock_rate(struct clock_info *clock,
	int num_mbs_per_sec, int codecs_enabled)
{
@@ -1376,6 +1390,23 @@ static unsigned long venus_hfi_get_clock_rate(struct clock_info *clock,
	return freq;
}

static unsigned long venus_hfi_get_core_clock_rate(void *dev)
{
	struct venus_hfi_device *device = (struct venus_hfi_device *) dev;
	struct clock_info *vc;

	if (!device) {
		dprintk(VIDC_ERR, "%s Invalid args: %p\n", __func__, device);
		return -EINVAL;
	}

	vc = venus_hfi_get_clock(device, "core_clk");
	if (vc)
		return clk_get_rate(vc->clk);
	else
		return 0;
}

static int venus_hfi_suspend(void *dev)
{
	int rc = 0;
@@ -4193,6 +4224,7 @@ static void venus_init_hfi_callbacks(struct hfi_device *hdev)
	hdev->get_core_capabilities = venus_hfi_get_core_capabilities;
	hdev->power_enable = venus_hfi_power_enable;
	hdev->suspend = venus_hfi_suspend;
	hdev->get_core_clock_rate = venus_hfi_get_core_clock_rate;
}

int venus_hfi_initialize(struct hfi_device *hdev, u32 device_id,
+8 −0
Original line number Diff line number Diff line
@@ -1259,6 +1259,13 @@ enum fw_info {
	FW_INFO_MAX,
};

enum msm_vidc_thermal_level {
	VIDC_THERMAL_NORMAL = 0,
	VIDC_THERMAL_LOW,
	VIDC_THERMAL_HIGH,
	VIDC_THERMAL_CRITICAL
};

enum vidc_bus_vote_data_session {
	VIDC_BUS_VOTE_DATA_SESSION_INVALID = 0,
	/* No declarations exist. Values generated by VIDC_VOTE_DATA_SESSION_VAL
@@ -1340,6 +1347,7 @@ struct hfi_device {
	int (*get_core_capabilities)(void);
	int (*power_enable)(void *dev);
	int (*suspend)(void *dev);
	unsigned long (*get_core_clock_rate)(void *dev);
};

typedef void (*hfi_cmd_response_callback) (enum command_response cmd,