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

Commit ec416f81 authored by qctecmdr Service's avatar qctecmdr Service Committed by Gerrit - the friendly Code Review server
Browse files

Merge "Merge remote-tracking branch 'quic/dev/msm-4.14-display' into msm-4.14"

parents b87c6286 81f78b4e
Loading
Loading
Loading
Loading
+82 −3
Original line number Diff line number Diff line
@@ -294,6 +294,12 @@ static void drm_dp_encode_sideband_req(struct drm_dp_sideband_msg_req_body *req,
		memcpy(&buf[idx], req->u.i2c_write.bytes, req->u.i2c_write.num_bytes);
		idx += req->u.i2c_write.num_bytes;
		break;

	case DP_POWER_DOWN_PHY:
	case DP_POWER_UP_PHY:
		buf[idx] = (req->u.port_num.port_number & 0xf) << 4;
		idx++;
		break;
	}
	raw->cur_len = idx;
}
@@ -538,6 +544,21 @@ static bool drm_dp_sideband_parse_query_payload_ack(struct drm_dp_sideband_msg_r
	return false;
}

static bool drm_dp_sideband_parse_power_updown_phy_ack(struct drm_dp_sideband_msg_rx *raw,
						       struct drm_dp_sideband_msg_reply_body *repmsg)
{
	int idx = 1;

	repmsg->u.port_number.port_number = (raw->msg[idx] >> 4) & 0xf;
	idx++;
	if (idx > raw->curlen) {
		DRM_DEBUG_KMS("power up/down phy parse length fail %d %d\n",
			      idx, raw->curlen);
		return false;
	}
	return true;
}

static bool drm_dp_sideband_parse_reply(struct drm_dp_sideband_msg_rx *raw,
					struct drm_dp_sideband_msg_reply_body *msg)
{
@@ -567,6 +588,9 @@ static bool drm_dp_sideband_parse_reply(struct drm_dp_sideband_msg_rx *raw,
		return drm_dp_sideband_parse_enum_path_resources_ack(raw, msg);
	case DP_ALLOCATE_PAYLOAD:
		return drm_dp_sideband_parse_allocate_payload_ack(raw, msg);
	case DP_POWER_DOWN_PHY:
	case DP_POWER_UP_PHY:
		return drm_dp_sideband_parse_power_updown_phy_ack(raw, msg);
	default:
		DRM_ERROR("Got unknown reply 0x%02x\n", msg->req_type);
		return false;
@@ -693,6 +717,22 @@ static int build_allocate_payload(struct drm_dp_sideband_msg_tx *msg, int port_n
	return 0;
}

static int build_power_updown_phy(struct drm_dp_sideband_msg_tx *msg,
				  int port_num, bool power_up)
{
	struct drm_dp_sideband_msg_req_body req;

	if (power_up)
		req.req_type = DP_POWER_UP_PHY;
	else
		req.req_type = DP_POWER_DOWN_PHY;

	req.u.port_num.port_number = port_num;
	drm_dp_encode_sideband_req(&req, msg);
	msg->path_msg = true;
	return 0;
}

static int drm_dp_mst_assign_payload_id(struct drm_dp_mst_topology_mgr *mgr,
					struct drm_dp_vcpi *vcpi)
{
@@ -1042,10 +1082,12 @@ static bool drm_dp_port_setup_pdt(struct drm_dp_mst_port *port)
		lct = drm_dp_calculate_rad(port, rad);

		port->mstb = drm_dp_add_mst_branch_device(lct, rad);
		if (port->mstb) {
			port->mstb->mgr = port->mgr;
			port->mstb->port_parent = port;

			send_link = true;
		}
		break;
	}
	return send_link;
@@ -1724,6 +1766,40 @@ static int drm_dp_payload_send_msg(struct drm_dp_mst_topology_mgr *mgr,
	return ret;
}

int drm_dp_send_power_updown_phy(struct drm_dp_mst_topology_mgr *mgr,
				 struct drm_dp_mst_port *port, bool power_up)
{
	struct drm_dp_sideband_msg_tx *txmsg;
	int len, ret;

	port = drm_dp_get_validated_port_ref(mgr, port);
	if (!port)
		return -EINVAL;

	txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
	if (!txmsg) {
		drm_dp_put_port(port);
		return -ENOMEM;
	}

	txmsg->dst = port->parent;
	len = build_power_updown_phy(txmsg, port->port_num, power_up);
	drm_dp_queue_down_tx(mgr, txmsg);

	ret = drm_dp_mst_wait_tx_reply(port->parent, txmsg);
	if (ret > 0) {
		if (txmsg->reply.reply_type == 1)
			ret = -EINVAL;
		else
			ret = 0;
	}
	kfree(txmsg);
	drm_dp_put_port(port);

	return ret;
}
EXPORT_SYMBOL(drm_dp_send_power_updown_phy);

static int drm_dp_create_payload_step1(struct drm_dp_mst_topology_mgr *mgr,
				       int id,
				       struct drm_dp_payload *payload)
@@ -2013,6 +2089,9 @@ static bool drm_dp_get_vc_payload_bw(int dp_link_bw,
	case DP_LINK_BW_5_4:
		*out = 10 * dp_link_count;
		break;
	case DP_LINK_BW_8_1:
		*out = 15 * dp_link_count;
		break;
	}
	return true;
}
+34 −4
Original line number Diff line number Diff line
@@ -27,9 +27,9 @@
#define MAX_PRE_EMP_LEVELS 4

static u8 const vm_pre_emphasis[MAX_VOLTAGE_LEVELS][MAX_PRE_EMP_LEVELS] = {
	{0x00, 0x0B, 0x14, 0xFF},       /* pe0, 0 db */
	{0x00, 0x0B, 0x12, 0xFF},       /* pe1, 3.5 db */
	{0x00, 0x0B, 0xFF, 0xFF},       /* pe2, 6.0 db */
	{0x00, 0x0E, 0x16, 0xFF},       /* pe0, 0 db */
	{0x00, 0x0E, 0x16, 0xFF},       /* pe1, 3.5 db */
	{0x00, 0x0E, 0xFF, 0xFF},       /* pe2, 6.0 db */
	{0xFF, 0xFF, 0xFF, 0xFF}        /* pe3, 9.5 db */
};

@@ -37,7 +37,7 @@ static u8 const vm_pre_emphasis[MAX_VOLTAGE_LEVELS][MAX_PRE_EMP_LEVELS] = {
static u8 const vm_voltage_swing[MAX_VOLTAGE_LEVELS][MAX_PRE_EMP_LEVELS] = {
	{0x07, 0x0F, 0x16, 0xFF}, /* sw0, 0.4v  */
	{0x11, 0x1E, 0x1F, 0xFF}, /* sw1, 0.6 v */
	{0x19, 0x1F, 0xFF, 0xFF}, /* sw1, 0.8 v */
	{0x1A, 0x1F, 0xFF, 0xFF}, /* sw1, 0.8 v */
	{0xFF, 0xFF, 0xFF, 0xFF}  /* sw1, 1.2 v, optional */
};

@@ -102,6 +102,34 @@ static void dp_catalog_aux_setup_v420(struct dp_catalog_aux *aux,
			0x1F);
}

static void dp_catalog_aux_clear_hw_interrupts_v420(struct dp_catalog_aux *aux)
{
	struct dp_catalog_private_v420 *catalog;
	struct dp_io_data *io_data;
	u32 data = 0;

	if (!aux) {
		pr_err("invalid input\n");
		return;
	}

	catalog = dp_catalog_get_priv_v420(aux);
	io_data = catalog->io->dp_phy;

	data = dp_read(catalog->exe_mode, io_data,
		DP_PHY_AUX_INTERRUPT_STATUS_V420);

	dp_write(catalog->exe_mode, io_data,
		DP_PHY_AUX_INTERRUPT_CLEAR_V420, 0x1f);
	wmb(); /* make sure 0x1f is written before next write */
	dp_write(catalog->exe_mode, io_data,
		DP_PHY_AUX_INTERRUPT_CLEAR_V420, 0x9f);
	wmb(); /* make sure 0x9f is written before next write */
	dp_write(catalog->exe_mode, io_data,
		DP_PHY_AUX_INTERRUPT_CLEAR_V420, 0);
	wmb(); /* make sure register is cleared */
}

static void dp_catalog_panel_config_msa_v420(struct dp_catalog_panel *panel,
					u32 rate, u32 stream_rate_khz,
					bool fixed_nvid)
@@ -312,6 +340,8 @@ int dp_catalog_get_v420(struct device *dev, struct dp_catalog *catalog,
	catalog->priv.set_exe_mode = dp_catalog_set_exe_mode_v420;

	catalog->aux.setup         = dp_catalog_aux_setup_v420;
	catalog->aux.clear_hw_interrupts =
				dp_catalog_aux_clear_hw_interrupts_v420;
	catalog->panel.config_msa  = dp_catalog_panel_config_msa_v420;
	catalog->ctrl.phy_lane_cfg = dp_catalog_ctrl_phy_lane_cfg_v420;
	catalog->ctrl.update_vx_px = dp_catalog_ctrl_update_vx_px_v420;
+51 −16
Original line number Diff line number Diff line
@@ -451,8 +451,10 @@ static ssize_t dp_debug_write_mst_con_id(struct file *file,
	struct dp_mst_connector *mst_connector;
	char buf[SZ_32];
	size_t len = 0;
	int con_id = 0;
	int con_id = 0, status;
	bool in_list = false;
	const int dp_en = BIT(3), hpd_high = BIT(7), hpd_irq = BIT(8);
	int vdo = dp_en | hpd_high | hpd_irq;

	if (!debug)
		return -ENODEV;
@@ -467,8 +469,10 @@ static ssize_t dp_debug_write_mst_con_id(struct file *file,

	buf[len] = '\0';

	if (kstrtoint(buf, 10, &con_id) != 0)
		goto clear;
	if (sscanf(buf, "%d %d", &con_id, &status) != 2) {
		len = 0;
		goto end;
	}

	if (!con_id)
		goto clear;
@@ -480,6 +484,7 @@ static ssize_t dp_debug_write_mst_con_id(struct file *file,
		if (mst_connector->con_id == con_id) {
			in_list = true;
			debug->mst_con_id = con_id;
			mst_connector->state = status;
			break;
		}
	}
@@ -487,6 +492,10 @@ static ssize_t dp_debug_write_mst_con_id(struct file *file,

	if (!in_list)
		pr_err("invalid connector id %u\n", con_id);
	else if (status != connector_status_unknown) {
		debug->dp_debug.mst_hpd_sim = true;
		debug->hpd->simulate_attention(debug->hpd, vdo);
	}

	goto end;
clear:
@@ -530,6 +539,21 @@ static ssize_t dp_debug_bw_code_write(struct file *file,
	return len;
}

static ssize_t dp_debug_mst_mode_read(struct file *file,
	char __user *user_buff, size_t count, loff_t *ppos)
{
	struct dp_debug_private *debug = file->private_data;
	char buf[64];
	ssize_t len;

	len = scnprintf(buf, sizeof(buf),
			"mst_mode = %d, mst_state = %d\n",
			debug->parser->has_mst,
			debug->panel->mst_state);

	return simple_read_from_buffer(user_buff, count, ppos, buf, len);
}

static ssize_t dp_debug_mst_mode_write(struct file *file,
		const char __user *user_buff, size_t count, loff_t *ppos)
{
@@ -631,28 +655,35 @@ static ssize_t dp_debug_mst_sideband_mode_write(struct file *file,
	struct dp_debug_private *debug = file->private_data;
	char buf[SZ_8];
	size_t len = 0;
	u32 mst_sideband_mode = 0;
	int mst_sideband_mode = 0;
	u32 mst_port_cnt = 0;

	if (!debug)
		return -ENODEV;

	if (*ppos)
		return 0;

	/* Leave room for termination char */
	len = min_t(size_t, count, SZ_8 - 1);
	if (copy_from_user(buf, user_buff, len))
		return 0;
		return -EFAULT;

	buf[len] = '\0';

	if (kstrtoint(buf, 10, &mst_sideband_mode) != 0)
		return 0;
	if (sscanf(buf, "%d %u", &mst_sideband_mode, &mst_port_cnt) != 2) {
		pr_err("invalid input\n");
		return -EINVAL;
	}

	debug->parser->has_mst_sideband = mst_sideband_mode ? true : false;
	pr_debug("mst_enable: %d\n", mst_sideband_mode);
	if (mst_port_cnt > DP_MST_SIM_MAX_PORTS) {
		pr_err("port cnt:%d exceeding max:%d\n", mst_port_cnt,
				DP_MST_SIM_MAX_PORTS);
		return -EINVAL;
	}

	return len;
	debug->parser->has_mst_sideband = mst_sideband_mode ? true : false;
	debug->dp_debug.mst_port_cnt = mst_port_cnt;
	pr_debug("mst_sideband_mode: %d port_cnt:%d\n",
			mst_sideband_mode, mst_port_cnt);
	return count;
}

static ssize_t dp_debug_widebus_mode_write(struct file *file,
@@ -1049,9 +1080,10 @@ static ssize_t dp_debug_read_mst_conn_info(struct file *file,
			continue;
		}

		ret = snprintf(buf + len, max_size,
				"conn name:%s, conn id:%d\n", connector->name,
				connector->base.id);
		ret = scnprintf(buf + len, max_size,
				"conn name:%s, conn id:%d state:%d\n",
				connector->name, connector->base.id,
				connector->status);
		if (dp_debug_check_buffer_overflow(ret, &max_size, &len))
			break;
	}
@@ -1659,6 +1691,7 @@ static const struct file_operations dump_fops = {
static const struct file_operations mst_mode_fops = {
	.open = simple_open,
	.write = dp_debug_mst_mode_write,
	.read = dp_debug_mst_mode_read,
};

static const struct file_operations mst_sideband_mode_fops = {
@@ -1988,6 +2021,8 @@ struct dp_debug *dp_debug_get(struct dp_debug_in *in)
	dp_debug->dp_mst_connector_list.conn = NULL;
	dp_debug->dp_mst_connector_list.debug_en = false;

	dp_debug->max_pclk_khz = debug->parser->max_pclk_khz;

	return dp_debug;
error:
	return ERR_PTR(rc);
+2 −0
Original line number Diff line number Diff line
@@ -45,6 +45,8 @@ struct dp_debug {
	bool force_encryption;
	char hdcp_status[SZ_128];
	struct dp_mst_connector dp_mst_connector_list;
	bool mst_hpd_sim;
	u32 mst_port_cnt;

	u8 *(*get_edid)(struct dp_debug *dp_debug);
	void (*abort)(struct dp_debug *dp_debug);
+90 −22
Original line number Diff line number Diff line
@@ -572,10 +572,17 @@ static int dp_display_send_hpd_notification(struct dp_display_private *dp)
	return 0;
}

static void dp_display_update_mst_state(struct dp_display_private *dp,
					bool state)
{
	dp->mst.mst_active = state;
	dp->panel->mst_state = state;
}

static void dp_display_process_mst_hpd_high(struct dp_display_private *dp)
{
	bool is_mst_receiver;
	struct dp_mst_hdp_info info;
	struct dp_mst_hpd_info info;

	if (dp->parser->has_mst && dp->mst.drm_registered) {
		DP_MST_DEBUG("mst_hpd_high work\n");
@@ -583,9 +590,14 @@ static void dp_display_process_mst_hpd_high(struct dp_display_private *dp)
		is_mst_receiver = dp->panel->read_mst_cap(dp->panel);

		if (is_mst_receiver && !dp->mst.mst_active) {
			dp->mst.mst_active = true;

			/* clear sink mst state */
			drm_dp_dpcd_writeb(dp->aux->drm_aux, DP_MSTM_CTRL, 0);

			dp_display_update_mst_state(dp, true);

			info.mst_protocol = dp->parser->has_mst_sideband;
			info.mst_port_cnt = dp->debug->mst_port_cnt;
			info.edid = dp->debug->get_edid(dp->debug);

			if (dp->mst.cbs.hpd)
@@ -604,9 +616,6 @@ static void dp_display_host_init(struct dp_display_private *dp)
	if (dp->core_initialized)
		return;

	if (!dp->debug->sim_mode && !dp->parser->no_aux_switch)
		dp->aux->aux_switch(dp->aux, true, dp->hpd->orientation);

	if (dp->hpd->orientation == ORIENTATION_CC2)
		flip = true;

@@ -632,9 +641,6 @@ static void dp_display_host_deinit(struct dp_display_private *dp)
		return;
	}

	if (!dp->debug->sim_mode && !dp->parser->no_aux_switch)
		dp->aux->aux_switch(dp->aux, false, ORIENTATION_NONE);

	dp->aux->deinit(dp->aux);
	dp->ctrl->deinit(dp->ctrl);
	dp->power->deinit(dp->power);
@@ -652,7 +658,8 @@ static int dp_display_process_hpd_high(struct dp_display_private *dp)

	dp->is_connected = true;

	dp->dp_display.max_pclk_khz = dp->parser->max_pclk_khz;
	dp->dp_display.max_pclk_khz = min(dp->parser->max_pclk_khz,
					dp->debug->max_pclk_khz);

	dp_display_host_init(dp);

@@ -693,7 +700,7 @@ static int dp_display_process_hpd_high(struct dp_display_private *dp)

static void dp_display_process_mst_hpd_low(struct dp_display_private *dp)
{
	struct dp_mst_hdp_info info = {0};
	struct dp_mst_hpd_info info = {0};

	if (dp->mst.mst_active) {
		DP_MST_DEBUG("mst_hpd_low work\n");
@@ -702,8 +709,7 @@ static void dp_display_process_mst_hpd_low(struct dp_display_private *dp)
			info.mst_protocol = dp->parser->has_mst_sideband;
			dp->mst.cbs.hpd(&dp->dp_display, false, &info);
		}

		dp->mst.mst_active = false;
		dp_display_update_mst_state(dp, false);
	}

	DP_MST_DEBUG("mst_hpd_low. mst_active:%d\n", dp->mst.mst_active);
@@ -765,6 +771,12 @@ static int dp_display_usbpd_configure_cb(struct device *dev)
		goto end;
	}

	if (!dp->debug->sim_mode && !dp->parser->no_aux_switch) {
		rc = dp->aux->aux_switch(dp->aux, true, dp->hpd->orientation);
		if (rc)
			goto end;
	}

	dp_display_host_init(dp);

	/* check for hpd high and framework ready */
@@ -820,7 +832,7 @@ static void dp_display_clean(struct dp_display_private *dp)

		dp_display_stream_pre_disable(dp, dp_panel);
		dp_display_stream_disable(dp, dp_panel);
		dp_panel->deinit(dp_panel);
		dp_panel->deinit(dp_panel, 0);
	}

	dp->power_on = false;
@@ -900,6 +912,9 @@ static int dp_display_usbpd_disconnect_cb(struct device *dev)

	dp_display_disconnect_sync(dp);
	dp->dp_display.post_open = NULL;

	if (!dp->debug->sim_mode && !dp->parser->no_aux_switch)
		dp->aux->aux_switch(dp->aux, false, ORIENTATION_NONE);
end:
	return rc;
}
@@ -926,8 +941,13 @@ static int dp_display_stream_enable(struct dp_display_private *dp,

static void dp_display_mst_attention(struct dp_display_private *dp)
{
	if (dp->mst.mst_active && dp->mst.cbs.hpd_irq)
		dp->mst.cbs.hpd_irq(&dp->dp_display);
	struct dp_mst_hpd_info hpd_irq = {0};

	if (dp->mst.mst_active && dp->mst.cbs.hpd_irq) {
		hpd_irq.mst_hpd_sim = dp->debug->mst_hpd_sim;
		dp->mst.cbs.hpd_irq(&dp->dp_display, &hpd_irq);
		dp->debug->mst_hpd_sim = false;
	}

	DP_MST_DEBUG("mst_attention_work. mst_active:%d\n", dp->mst.mst_active);
}
@@ -937,6 +957,9 @@ static void dp_display_attention_work(struct work_struct *work)
	struct dp_display_private *dp = container_of(work,
			struct dp_display_private, attention_work);

	if (dp->debug->mst_hpd_sim)
		goto mst_attention;

	if (dp->link->process_request(dp->link))
		goto cp_irq;

@@ -1002,7 +1025,8 @@ static int dp_display_usbpd_attention_cb(struct device *dev)

	if (!dp->hpd->hpd_high)
		dp_display_disconnect_sync(dp);
	else if (dp->hpd->hpd_irq && dp->core_initialized)
	else if ((dp->hpd->hpd_irq && dp->core_initialized) ||
			dp->debug->mst_hpd_sim)
		queue_work(dp->wq, &dp->attention_work);
	else if (!dp->power_on)
		queue_delayed_work(dp->wq, &dp->connect_work, 0);
@@ -1572,11 +1596,7 @@ static int dp_display_disable(struct dp_display *dp_display, void *panel)
	}

	dp_display_stream_disable(dp, dp_panel);

	/* log this as it results from user action of cable dis-connection */
	pr_info("[OK]\n");
end:
	dp_panel->deinit(dp_panel);
	mutex_unlock(&dp->session_lock);
	return 0;
}
@@ -1629,6 +1649,8 @@ static struct dp_debug *dp_get_debug(struct dp_display *dp_display)
static int dp_display_unprepare(struct dp_display *dp_display, void *panel)
{
	struct dp_display_private *dp;
	struct dp_panel *dp_panel = panel;
	u32 flags = 0;

	if (!dp_display || !panel) {
		pr_err("invalid input\n");
@@ -1639,10 +1661,19 @@ static int dp_display_unprepare(struct dp_display *dp_display, void *panel)

	mutex_lock(&dp->session_lock);

	/*
	 * Check if the power off sequence was triggered
	 * by a source initialated action like framework
	 * reboot or suspend-resume but not from normal
	 * hot plug.
	 */
	if (dp_display_is_ready(dp))
		flags |= DP_PANEL_SRC_INITIATED_POWER_DOWN;

	if (dp->active_stream_cnt)
		goto end;

	if (dp_display_is_ready(dp)) {
	if (flags & DP_PANEL_SRC_INITIATED_POWER_DOWN) {
		dp->link->psm_config(dp->link, &dp->panel->link_info, true);
		dp->debug->psm_enabled = true;

@@ -1663,8 +1694,10 @@ static int dp_display_unprepare(struct dp_display *dp_display, void *panel)

	complete_all(&dp->notification_comp);

	pr_debug("[OK]\n");
	/* log this as it results from user action of cable dis-connection */
	pr_info("[OK]\n");
end:
	dp_panel->deinit(dp_panel, flags);
	mutex_unlock(&dp->session_lock);

	return 0;
@@ -2034,6 +2067,7 @@ static int dp_display_mst_connector_install(struct dp_display *dp_display,
	mst_connector->debug_en = false;
	mst_connector->conn = connector;
	mst_connector->con_id = connector->base.id;
	mst_connector->state = connector_status_unknown;
	INIT_LIST_HEAD(&mst_connector->list);

	list_add(&mst_connector->list,
@@ -2099,6 +2133,38 @@ static int dp_display_mst_connector_uninstall(struct dp_display *dp_display,
	return rc;
}

static int dp_display_mst_get_connector_info(struct dp_display *dp_display,
			struct drm_connector *connector,
			struct dp_mst_connector *mst_conn)
{
	struct dp_display_private *dp;
	struct dp_mst_connector *conn, *temp_conn;

	if (!connector || !mst_conn) {
		pr_err("invalid input\n");
		return -EINVAL;
	}

	dp = container_of(dp_display, struct dp_display_private, dp_display);

	mutex_lock(&dp->session_lock);
	if (!dp->mst.drm_registered) {
		pr_debug("drm mst not registered\n");
		mutex_unlock(&dp->session_lock);
		return -EPERM;
	}

	mutex_lock(&dp->debug->dp_mst_connector_list.lock);
	list_for_each_entry_safe(conn, temp_conn,
			&dp->debug->dp_mst_connector_list.list, list) {
		if (conn->con_id == connector->base.id)
			memcpy(mst_conn, conn, sizeof(*mst_conn));
	}
	mutex_unlock(&dp->debug->dp_mst_connector_list.lock);
	mutex_unlock(&dp->session_lock);
	return 0;
}

static int dp_display_mst_connector_update_edid(struct dp_display *dp_display,
			struct drm_connector *connector,
			struct edid *edid)
@@ -2221,6 +2287,8 @@ static int dp_display_probe(struct platform_device *pdev)
	g_dp_display->get_mst_caps = dp_display_get_mst_caps;
	g_dp_display->set_stream_info = dp_display_set_stream_info;
	g_dp_display->convert_to_dp_mode = dp_display_convert_to_dp_mode;
	g_dp_display->mst_get_connector_info =
					dp_display_mst_get_connector_info;

	rc = component_add(&pdev->dev, &dp_display_comp_ops);
	if (rc) {
Loading