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

Commit 6739b3d7 authored by Dave Airlie's avatar Dave Airlie
Browse files

Merge branch 'drm-fixes-mst' of git://people.freedesktop.org/~airlied/linux into drm-fixes

displayport multistream fixes from AMD.

* 'drm-fixes-mst' of git://people.freedesktop.org/~airlied/linux:
  drm/dp/mst: deallocate payload on port destruction
  drm/dp/mst: Reverse order of MST enable and clearing VC payload table.
  drm/dp/mst: move GUID storage from mgr, port to only mst branch
  drm/dp/mst: change MST detection scheme
  drm/dp/mst: Calculate MST PBN with 31.32 fixed point
  drm: Add drm_fixp_from_fraction and drm_fixp2int_ceil
  drm/mst: Add range check for max_payloads during init
  drm/mst: Don't ignore the MST PBN self-test result
  drm: fix missing reference counting decrease
parents c745884b 91a25e46
Loading
Loading
Loading
Loading
+182 −97
Original line number Diff line number Diff line
@@ -803,12 +803,33 @@ static struct drm_dp_mst_branch *drm_dp_add_mst_branch_device(u8 lct, u8 *rad)
	return mstb;
}

static void drm_dp_free_mst_port(struct kref *kref);

static void drm_dp_free_mst_branch_device(struct kref *kref)
{
	struct drm_dp_mst_branch *mstb = container_of(kref, struct drm_dp_mst_branch, kref);
	if (mstb->port_parent) {
		if (list_empty(&mstb->port_parent->next))
			kref_put(&mstb->port_parent->kref, drm_dp_free_mst_port);
	}
	kfree(mstb);
}

static void drm_dp_destroy_mst_branch_device(struct kref *kref)
{
	struct drm_dp_mst_branch *mstb = container_of(kref, struct drm_dp_mst_branch, kref);
	struct drm_dp_mst_port *port, *tmp;
	bool wake_tx = false;

	/*
	 * init kref again to be used by ports to remove mst branch when it is
	 * not needed anymore
	 */
	kref_init(kref);

	if (mstb->port_parent && list_empty(&mstb->port_parent->next))
		kref_get(&mstb->port_parent->kref);

	/*
	 * destroy all ports - don't need lock
	 * as there are no more references to the mst branch
@@ -835,7 +856,8 @@ static void drm_dp_destroy_mst_branch_device(struct kref *kref)

	if (wake_tx)
		wake_up(&mstb->mgr->tx_waitq);
	kfree(mstb);

	kref_put(kref, drm_dp_free_mst_branch_device);
}

static void drm_dp_put_mst_branch_device(struct drm_dp_mst_branch *mstb)
@@ -883,6 +905,7 @@ static void drm_dp_destroy_port(struct kref *kref)
			 * from an EDID retrieval */

			mutex_lock(&mgr->destroy_connector_lock);
			kref_get(&port->parent->kref);
			list_add(&port->next, &mgr->destroy_connector_list);
			mutex_unlock(&mgr->destroy_connector_lock);
			schedule_work(&mgr->destroy_connector_work);
@@ -1018,18 +1041,27 @@ static bool drm_dp_port_setup_pdt(struct drm_dp_mst_port *port)
	return send_link;
}

static void drm_dp_check_port_guid(struct drm_dp_mst_branch *mstb,
				   struct drm_dp_mst_port *port)
static void drm_dp_check_mstb_guid(struct drm_dp_mst_branch *mstb, u8 *guid)
{
	int ret;
	if (port->dpcd_rev >= 0x12) {
		port->guid_valid = drm_dp_validate_guid(mstb->mgr, port->guid);
		if (!port->guid_valid) {
			ret = drm_dp_send_dpcd_write(mstb->mgr,
						     port,

	memcpy(mstb->guid, guid, 16);

	if (!drm_dp_validate_guid(mstb->mgr, mstb->guid)) {
		if (mstb->port_parent) {
			ret = drm_dp_send_dpcd_write(
					mstb->mgr,
					mstb->port_parent,
					DP_GUID,
						     16, port->guid);
			port->guid_valid = true;
					16,
					mstb->guid);
		} else {

			ret = drm_dp_dpcd_write(
					mstb->mgr->aux,
					DP_GUID,
					mstb->guid,
					16);
		}
	}
}
@@ -1086,7 +1118,6 @@ static void drm_dp_add_port(struct drm_dp_mst_branch *mstb,
	port->dpcd_rev = port_msg->dpcd_revision;
	port->num_sdp_streams = port_msg->num_sdp_streams;
	port->num_sdp_stream_sinks = port_msg->num_sdp_stream_sinks;
	memcpy(port->guid, port_msg->peer_guid, 16);

	/* manage mstb port lists with mgr lock - take a reference
	   for this list */
@@ -1099,11 +1130,9 @@ static void drm_dp_add_port(struct drm_dp_mst_branch *mstb,

	if (old_ddps != port->ddps) {
		if (port->ddps) {
			drm_dp_check_port_guid(mstb, port);
			if (!port->input)
				drm_dp_send_enum_path_resources(mstb->mgr, mstb, port);
		} else {
			port->guid_valid = false;
			port->available_pbn = 0;
			}
	}
@@ -1130,13 +1159,11 @@ static void drm_dp_add_port(struct drm_dp_mst_branch *mstb,
			drm_dp_put_port(port);
			goto out;
		}
		if (port->port_num >= DP_MST_LOGICAL_PORT_0) {
			port->cached_edid = drm_get_edid(port->connector, &port->aux.ddc);

		drm_mode_connector_set_tile_property(port->connector);
		}

		(*mstb->mgr->cbs->register_connector)(port->connector);
	}

out:
	/* put reference to this port */
	drm_dp_put_port(port);
@@ -1161,11 +1188,9 @@ static void drm_dp_update_port(struct drm_dp_mst_branch *mstb,
	port->ddps = conn_stat->displayport_device_plug_status;

	if (old_ddps != port->ddps) {
		if (port->ddps) {
			drm_dp_check_port_guid(mstb, port);
		dowork = true;
		if (port->ddps) {
		} else {
			port->guid_valid = false;
			port->available_pbn = 0;
		}
	}
@@ -1222,13 +1247,14 @@ static struct drm_dp_mst_branch *get_mst_branch_device_by_guid_helper(
	struct drm_dp_mst_branch *found_mstb;
	struct drm_dp_mst_port *port;

	if (memcmp(mstb->guid, guid, 16) == 0)
		return mstb;


	list_for_each_entry(port, &mstb->ports, next) {
		if (!port->mstb)
			continue;

		if (port->guid_valid && memcmp(port->guid, guid, 16) == 0)
			return port->mstb;

		found_mstb = get_mst_branch_device_by_guid_helper(port->mstb, guid);

		if (found_mstb)
@@ -1247,9 +1273,6 @@ static struct drm_dp_mst_branch *drm_dp_get_mst_branch_device_by_guid(
	/* find the port by iterating down */
	mutex_lock(&mgr->lock);

	if (mgr->guid_valid && memcmp(mgr->guid, guid, 16) == 0)
		mstb = mgr->mst_primary;
	else
	mstb = get_mst_branch_device_by_guid_helper(mgr->mst_primary, guid);

	if (mstb)
@@ -1271,8 +1294,13 @@ static void drm_dp_check_and_send_link_address(struct drm_dp_mst_topology_mgr *m
		if (port->input)
			continue;

		if (!port->ddps)
		if (!port->ddps) {
			if (port->cached_edid) {
				kfree(port->cached_edid);
				port->cached_edid = NULL;
			}
			continue;
		}

		if (!port->available_pbn)
			drm_dp_send_enum_path_resources(mgr, mstb, port);
@@ -1283,6 +1311,12 @@ static void drm_dp_check_and_send_link_address(struct drm_dp_mst_topology_mgr *m
				drm_dp_check_and_send_link_address(mgr, mstb_child);
				drm_dp_put_mst_branch_device(mstb_child);
			}
		} else if (port->pdt == DP_PEER_DEVICE_SST_SINK ||
			port->pdt == DP_PEER_DEVICE_DP_LEGACY_CONV) {
			if (!port->cached_edid) {
				port->cached_edid =
					drm_get_edid(port->connector, &port->aux.ddc);
			}
		}
	}
}
@@ -1302,6 +1336,8 @@ static void drm_dp_mst_link_probe_work(struct work_struct *work)
		drm_dp_check_and_send_link_address(mgr, mstb);
		drm_dp_put_mst_branch_device(mstb);
	}

	(*mgr->cbs->hotplug)(mgr);
}

static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr,
@@ -1555,10 +1591,12 @@ static void drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr,
				       txmsg->reply.u.link_addr.ports[i].num_sdp_streams,
				       txmsg->reply.u.link_addr.ports[i].num_sdp_stream_sinks);
			}

			drm_dp_check_mstb_guid(mstb, txmsg->reply.u.link_addr.guid);

			for (i = 0; i < txmsg->reply.u.link_addr.nports; i++) {
				drm_dp_add_port(mstb, mgr->dev, &txmsg->reply.u.link_addr.ports[i]);
			}
			(*mgr->cbs->hotplug)(mgr);
		}
	} else {
		mstb->link_address_sent = false;
@@ -1602,6 +1640,37 @@ static int drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr,
	return 0;
}

static struct drm_dp_mst_port *drm_dp_get_last_connected_port_to_mstb(struct drm_dp_mst_branch *mstb)
{
	if (!mstb->port_parent)
		return NULL;

	if (mstb->port_parent->mstb != mstb)
		return mstb->port_parent;

	return drm_dp_get_last_connected_port_to_mstb(mstb->port_parent->parent);
}

static struct drm_dp_mst_branch *drm_dp_get_last_connected_port_and_mstb(struct drm_dp_mst_topology_mgr *mgr,
									 struct drm_dp_mst_branch *mstb,
									 int *port_num)
{
	struct drm_dp_mst_branch *rmstb = NULL;
	struct drm_dp_mst_port *found_port;
	mutex_lock(&mgr->lock);
	if (mgr->mst_primary) {
		found_port = drm_dp_get_last_connected_port_to_mstb(mstb);

		if (found_port) {
			rmstb = found_port->parent;
			kref_get(&rmstb->kref);
			*port_num = found_port->port_num;
		}
	}
	mutex_unlock(&mgr->lock);
	return rmstb;
}

static int drm_dp_payload_send_msg(struct drm_dp_mst_topology_mgr *mgr,
				   struct drm_dp_mst_port *port,
				   int id,
@@ -1609,13 +1678,18 @@ static int drm_dp_payload_send_msg(struct drm_dp_mst_topology_mgr *mgr,
{
	struct drm_dp_sideband_msg_tx *txmsg;
	struct drm_dp_mst_branch *mstb;
	int len, ret;
	int len, ret, port_num;
	u8 sinks[DRM_DP_MAX_SDP_STREAMS];
	int i;

	port_num = port->port_num;
	mstb = drm_dp_get_validated_mstb_ref(mgr, port->parent);
	if (!mstb) {
		mstb = drm_dp_get_last_connected_port_and_mstb(mgr, port->parent, &port_num);

		if (!mstb)
			return -EINVAL;
	}

	txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
	if (!txmsg) {
@@ -1627,7 +1701,7 @@ static int drm_dp_payload_send_msg(struct drm_dp_mst_topology_mgr *mgr,
		sinks[i] = i;

	txmsg->dst = mstb;
	len = build_allocate_payload(txmsg, port->port_num,
	len = build_allocate_payload(txmsg, port_num,
				     id,
				     pbn, port->num_sdp_streams, sinks);

@@ -1983,31 +2057,17 @@ int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool ms
		mgr->mst_primary = mstb;
		kref_get(&mgr->mst_primary->kref);

		{
			struct drm_dp_payload reset_pay;
			reset_pay.start_slot = 0;
			reset_pay.num_slots = 0x3f;
			drm_dp_dpcd_write_payload(mgr, 0, &reset_pay);
		}

		ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL,
							 DP_MST_EN | DP_UP_REQ_EN | DP_UPSTREAM_IS_SRC);
		if (ret < 0) {
			goto out_unlock;
		}


		/* sort out guid */
		ret = drm_dp_dpcd_read(mgr->aux, DP_GUID, mgr->guid, 16);
		if (ret != 16) {
			DRM_DEBUG_KMS("failed to read DP GUID %d\n", ret);
			goto out_unlock;
		}

		mgr->guid_valid = drm_dp_validate_guid(mgr, mgr->guid);
		if (!mgr->guid_valid) {
			ret = drm_dp_dpcd_write(mgr->aux, DP_GUID, mgr->guid, 16);
			mgr->guid_valid = true;
		{
			struct drm_dp_payload reset_pay;
			reset_pay.start_slot = 0;
			reset_pay.num_slots = 0x3f;
			drm_dp_dpcd_write_payload(mgr, 0, &reset_pay);
		}

		queue_work(system_long_wq, &mgr->work);
@@ -2231,9 +2291,8 @@ static int drm_dp_mst_handle_up_req(struct drm_dp_mst_topology_mgr *mgr)
			}

			drm_dp_update_port(mstb, &msg.u.conn_stat);
			DRM_DEBUG_KMS("Got CSN: pn: %d ldps:%d ddps: %d mcs: %d ip: %d pdt: %d\n", msg.u.conn_stat.port_number, msg.u.conn_stat.legacy_device_plug_status, msg.u.conn_stat.displayport_device_plug_status, msg.u.conn_stat.message_capability_status, msg.u.conn_stat.input_port, msg.u.conn_stat.peer_device_type);
			(*mgr->cbs->hotplug)(mgr);

			DRM_DEBUG_KMS("Got CSN: pn: %d ldps:%d ddps: %d mcs: %d ip: %d pdt: %d\n", msg.u.conn_stat.port_number, msg.u.conn_stat.legacy_device_plug_status, msg.u.conn_stat.displayport_device_plug_status, msg.u.conn_stat.message_capability_status, msg.u.conn_stat.input_port, msg.u.conn_stat.peer_device_type);
		} else if (msg.req_type == DP_RESOURCE_STATUS_NOTIFY) {
			drm_dp_send_up_ack_reply(mgr, mgr->mst_primary, msg.req_type, seqno, false);
			if (!mstb)
@@ -2320,10 +2379,6 @@ enum drm_connector_status drm_dp_mst_detect_port(struct drm_connector *connector

	case DP_PEER_DEVICE_SST_SINK:
		status = connector_status_connected;
		/* for logical ports - cache the EDID */
		if (port->port_num >= 8 && !port->cached_edid) {
			port->cached_edid = drm_get_edid(connector, &port->aux.ddc);
		}
		break;
	case DP_PEER_DEVICE_DP_LEGACY_CONV:
		if (port->ldps)
@@ -2378,10 +2433,7 @@ struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_

	if (port->cached_edid)
		edid = drm_edid_duplicate(port->cached_edid);
	else {
		edid = drm_get_edid(connector, &port->aux.ddc);
		drm_mode_connector_set_tile_property(connector);
	}

	port->has_audio = drm_detect_monitor_audio(edid);
	drm_dp_put_port(port);
	return edid;
@@ -2446,6 +2498,7 @@ bool drm_dp_mst_allocate_vcpi(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp
		DRM_DEBUG_KMS("payload: vcpi %d already allocated for pbn %d - requested pbn %d\n", port->vcpi.vcpi, port->vcpi.pbn, pbn);
		if (pbn == port->vcpi.pbn) {
			*slots = port->vcpi.num_slots;
			drm_dp_put_port(port);
			return true;
		}
	}
@@ -2605,32 +2658,31 @@ EXPORT_SYMBOL(drm_dp_check_act_status);
 */
int drm_dp_calc_pbn_mode(int clock, int bpp)
{
	fixed20_12 pix_bw;
	fixed20_12 fbpp;
	fixed20_12 result;
	fixed20_12 margin, tmp;
	u32 res;

	pix_bw.full = dfixed_const(clock);
	fbpp.full = dfixed_const(bpp);
	tmp.full = dfixed_const(8);
	fbpp.full = dfixed_div(fbpp, tmp);

	result.full = dfixed_mul(pix_bw, fbpp);
	margin.full = dfixed_const(54);
	tmp.full = dfixed_const(64);
	margin.full = dfixed_div(margin, tmp);
	result.full = dfixed_div(result, margin);

	margin.full = dfixed_const(1006);
	tmp.full = dfixed_const(1000);
	margin.full = dfixed_div(margin, tmp);
	result.full = dfixed_mul(result, margin);

	result.full = dfixed_div(result, tmp);
	result.full = dfixed_ceil(result);
	res = dfixed_trunc(result);
	return res;
	u64 kbps;
	s64 peak_kbps;
	u32 numerator;
	u32 denominator;

	kbps = clock * bpp;

	/*
	 * margin 5300ppm + 300ppm ~ 0.6% as per spec, factor is 1.006
	 * The unit of 54/64Mbytes/sec is an arbitrary unit chosen based on
	 * common multiplier to render an integer PBN for all link rate/lane
	 * counts combinations
	 * calculate
	 * peak_kbps *= (1006/1000)
	 * peak_kbps *= (64/54)
	 * peak_kbps *= 8    convert to bytes
	 */

	numerator = 64 * 1006;
	denominator = 54 * 8 * 1000 * 1000;

	kbps *= numerator;
	peak_kbps = drm_fixp_from_fraction(kbps, denominator);

	return drm_fixp2int_ceil(peak_kbps);
}
EXPORT_SYMBOL(drm_dp_calc_pbn_mode);

@@ -2638,11 +2690,23 @@ static int test_calc_pbn_mode(void)
{
	int ret;
	ret = drm_dp_calc_pbn_mode(154000, 30);
	if (ret != 689)
	if (ret != 689) {
		DRM_ERROR("PBN calculation test failed - clock %d, bpp %d, expected PBN %d, actual PBN %d.\n",
				154000, 30, 689, ret);
		return -EINVAL;
	}
	ret = drm_dp_calc_pbn_mode(234000, 30);
	if (ret != 1047)
	if (ret != 1047) {
		DRM_ERROR("PBN calculation test failed - clock %d, bpp %d, expected PBN %d, actual PBN %d.\n",
				234000, 30, 1047, ret);
		return -EINVAL;
	}
	ret = drm_dp_calc_pbn_mode(297000, 24);
	if (ret != 1063) {
		DRM_ERROR("PBN calculation test failed - clock %d, bpp %d, expected PBN %d, actual PBN %d.\n",
				297000, 24, 1063, ret);
		return -EINVAL;
	}
	return 0;
}

@@ -2783,6 +2847,13 @@ static void drm_dp_tx_work(struct work_struct *work)
	mutex_unlock(&mgr->qlock);
}

static void drm_dp_free_mst_port(struct kref *kref)
{
	struct drm_dp_mst_port *port = container_of(kref, struct drm_dp_mst_port, kref);
	kref_put(&port->parent->kref, drm_dp_free_mst_branch_device);
	kfree(port);
}

static void drm_dp_destroy_connector_work(struct work_struct *work)
{
	struct drm_dp_mst_topology_mgr *mgr = container_of(work, struct drm_dp_mst_topology_mgr, destroy_connector_work);
@@ -2803,13 +2874,22 @@ static void drm_dp_destroy_connector_work(struct work_struct *work)
		list_del(&port->next);
		mutex_unlock(&mgr->destroy_connector_lock);

		kref_init(&port->kref);
		INIT_LIST_HEAD(&port->next);

		mgr->cbs->destroy_connector(mgr, port->connector);

		drm_dp_port_teardown_pdt(port, port->pdt);

		if (!port->input && port->vcpi.vcpi > 0)
		if (!port->input && port->vcpi.vcpi > 0) {
			if (mgr->mst_state) {
				drm_dp_mst_reset_vcpi_slots(mgr, port);
				drm_dp_update_payload_part1(mgr);
				drm_dp_mst_put_payload_id(mgr, port->vcpi.vcpi);
		kfree(port);
			}
		}

		kref_put(&port->kref, drm_dp_free_mst_port);
		send_hotplug = true;
	}
	if (send_hotplug)
@@ -2847,6 +2927,9 @@ int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr,
	mgr->max_dpcd_transaction_bytes = max_dpcd_transaction_bytes;
	mgr->max_payloads = max_payloads;
	mgr->conn_base_id = conn_base_id;
	if (max_payloads + 1 > sizeof(mgr->payload_mask) * 8 ||
	    max_payloads + 1 > sizeof(mgr->vcpi_mask) * 8)
		return -EINVAL;
	mgr->payloads = kcalloc(max_payloads, sizeof(struct drm_dp_payload), GFP_KERNEL);
	if (!mgr->payloads)
		return -ENOMEM;
@@ -2854,7 +2937,9 @@ int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr,
	if (!mgr->proposed_vcpis)
		return -ENOMEM;
	set_bit(0, &mgr->payload_mask);
	test_calc_pbn_mode();
	if (test_calc_pbn_mode() < 0)
		DRM_ERROR("MST PBN self-test failed\n");

	return 0;
}
EXPORT_SYMBOL(drm_dp_mst_topology_mgr_init);
+10 −15
Original line number Diff line number Diff line
@@ -44,8 +44,6 @@ struct drm_dp_vcpi {
/**
 * struct drm_dp_mst_port - MST port
 * @kref: reference count for this port.
 * @guid_valid: for DP 1.2 devices if we have validated the GUID.
 * @guid: guid for DP 1.2 device on this port.
 * @port_num: port number
 * @input: if this port is an input port.
 * @mcs: message capability status - DP 1.2 spec.
@@ -70,10 +68,6 @@ struct drm_dp_vcpi {
struct drm_dp_mst_port {
	struct kref kref;

	/* if dpcd 1.2 device is on this port - its GUID info */
	bool guid_valid;
	u8 guid[16];

	u8 port_num;
	bool input;
	bool mcs;
@@ -110,10 +104,12 @@ struct drm_dp_mst_port {
 * @tx_slots: transmission slots for this device.
 * @last_seqno: last sequence number used to talk to this.
 * @link_address_sent: if a link address message has been sent to this device yet.
 * @guid: guid for DP 1.2 branch device. port under this branch can be
 * identified by port #.
 *
 * This structure represents an MST branch device, there is one
 * primary branch device at the root, along with any others connected
 * to downstream ports
 * primary branch device at the root, along with any other branches connected
 * to downstream port of parent branches.
 */
struct drm_dp_mst_branch {
	struct kref kref;
@@ -132,6 +128,9 @@ struct drm_dp_mst_branch {
	struct drm_dp_sideband_msg_tx *tx_slots[2];
	int last_seqno;
	bool link_address_sent;

	/* global unique identifier to identify branch devices */
	u8 guid[16];
};


@@ -406,11 +405,9 @@ struct drm_dp_payload {
 * @conn_base_id: DRM connector ID this mgr is connected to.
 * @down_rep_recv: msg receiver state for down replies.
 * @up_req_recv: msg receiver state for up requests.
 * @lock: protects mst state, primary, guid, dpcd.
 * @lock: protects mst state, primary, dpcd.
 * @mst_state: if this manager is enabled for an MST capable port.
 * @mst_primary: pointer to the primary branch device.
 * @guid_valid: GUID valid for the primary branch device.
 * @guid: GUID for primary port.
 * @dpcd: cache of DPCD for primary port.
 * @pbn_div: PBN to slots divisor.
 *
@@ -432,13 +429,11 @@ struct drm_dp_mst_topology_mgr {
	struct drm_dp_sideband_msg_rx up_req_recv;

	/* pointer to info about the initial MST device */
	struct mutex lock; /* protects mst_state + primary + guid + dpcd */
	struct mutex lock; /* protects mst_state + primary + dpcd */

	bool mst_state;
	struct drm_dp_mst_branch *mst_primary;
	/* primary MST device GUID */
	bool guid_valid;
	u8 guid[16];

	u8 dpcd[DP_RECEIVER_CAP_SIZE];
	u8 sink_count;
	int pbn_div;
+51 −2
Original line number Diff line number Diff line
@@ -73,18 +73,28 @@ static inline u32 dfixed_div(fixed20_12 A, fixed20_12 B)
#define DRM_FIXED_ONE		(1ULL << DRM_FIXED_POINT)
#define DRM_FIXED_DECIMAL_MASK	(DRM_FIXED_ONE - 1)
#define DRM_FIXED_DIGITS_MASK	(~DRM_FIXED_DECIMAL_MASK)
#define DRM_FIXED_EPSILON	1LL
#define DRM_FIXED_ALMOST_ONE	(DRM_FIXED_ONE - DRM_FIXED_EPSILON)

static inline s64 drm_int2fixp(int a)
{
	return ((s64)a) << DRM_FIXED_POINT;
}

static inline int drm_fixp2int(int64_t a)
static inline int drm_fixp2int(s64 a)
{
	return ((s64)a) >> DRM_FIXED_POINT;
}

static inline unsigned drm_fixp_msbset(int64_t a)
static inline int drm_fixp2int_ceil(s64 a)
{
	if (a > 0)
		return drm_fixp2int(a + DRM_FIXED_ALMOST_ONE);
	else
		return drm_fixp2int(a - DRM_FIXED_ALMOST_ONE);
}

static inline unsigned drm_fixp_msbset(s64 a)
{
	unsigned shift, sign = (a >> 63) & 1;

@@ -136,6 +146,45 @@ static inline s64 drm_fixp_div(s64 a, s64 b)
	return result;
}

static inline s64 drm_fixp_from_fraction(s64 a, s64 b)
{
	s64 res;
	bool a_neg = a < 0;
	bool b_neg = b < 0;
	u64 a_abs = a_neg ? -a : a;
	u64 b_abs = b_neg ? -b : b;
	u64 rem;

	/* determine integer part */
	u64 res_abs  = div64_u64_rem(a_abs, b_abs, &rem);

	/* determine fractional part */
	{
		u32 i = DRM_FIXED_POINT;

		do {
			rem <<= 1;
			res_abs <<= 1;
			if (rem >= b_abs) {
				res_abs |= 1;
				rem -= b_abs;
			}
		} while (--i != 0);
	}

	/* round up LSB */
	{
		u64 summand = (rem << 1) >= b_abs;

		res_abs += summand;
	}

	res = (s64) res_abs;
	if (a_neg ^ b_neg)
		res = -res;
	return res;
}

static inline s64 drm_fixp_exp(s64 x)
{
	s64 tolerance = div64_s64(DRM_FIXED_ONE, 1000000);