xref: /openbsd-src/sys/dev/pci/drm/apple/iomfb_template.c (revision 485c4124b97af28ad7ac1fe4712f7fceb063686d)
15dd0baa8Skettenis // SPDX-License-Identifier: GPL-2.0-only OR MIT
25dd0baa8Skettenis /*
35dd0baa8Skettenis  * Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io>
45dd0baa8Skettenis  * Copyright The Asahi Linux Contributors
55dd0baa8Skettenis  */
65dd0baa8Skettenis 
75dd0baa8Skettenis #include <linux/align.h>
85dd0baa8Skettenis #include <linux/bitmap.h>
95dd0baa8Skettenis #include <linux/clk.h>
105dd0baa8Skettenis #include <linux/completion.h>
115dd0baa8Skettenis #include <linux/delay.h>
125dd0baa8Skettenis #include <linux/dma-mapping.h>
135dd0baa8Skettenis #include <linux/iommu.h>
145dd0baa8Skettenis #include <linux/kref.h>
155dd0baa8Skettenis #include <linux/module.h>
165dd0baa8Skettenis #include <linux/of_device.h>
175dd0baa8Skettenis #include <linux/pm_runtime.h>
185dd0baa8Skettenis #include <linux/slab.h>
195dd0baa8Skettenis 
205dd0baa8Skettenis #include <drm/drm_fb_dma_helper.h>
215dd0baa8Skettenis #include <drm/drm_fourcc.h>
225dd0baa8Skettenis #include <drm/drm_framebuffer.h>
235dd0baa8Skettenis #include <drm/drm_gem_dma_helper.h>
245dd0baa8Skettenis #include <drm/drm_probe_helper.h>
255dd0baa8Skettenis #include <drm/drm_vblank.h>
265dd0baa8Skettenis 
275dd0baa8Skettenis #include "dcp.h"
285dd0baa8Skettenis #include "dcp-internal.h"
295dd0baa8Skettenis #include "iomfb.h"
305dd0baa8Skettenis #include "iomfb_internal.h"
315dd0baa8Skettenis #include "parser.h"
325dd0baa8Skettenis #include "trace.h"
335dd0baa8Skettenis #include "version_utils.h"
345dd0baa8Skettenis 
355dd0baa8Skettenis /* Register defines used in bandwidth setup structure */
365dd0baa8Skettenis #define REG_DOORBELL_BIT(idx) (2 + (idx))
375dd0baa8Skettenis 
385dd0baa8Skettenis struct dcp_wait_cookie {
395dd0baa8Skettenis 	struct kref refcount;
405dd0baa8Skettenis 	struct completion done;
415dd0baa8Skettenis };
425dd0baa8Skettenis 
435dd0baa8Skettenis static void release_wait_cookie(struct kref *ref)
445dd0baa8Skettenis {
455dd0baa8Skettenis 	struct dcp_wait_cookie *cookie;
465dd0baa8Skettenis 	cookie = container_of(ref, struct dcp_wait_cookie, refcount);
475dd0baa8Skettenis 
485dd0baa8Skettenis         kfree(cookie);
495dd0baa8Skettenis }
505dd0baa8Skettenis 
515dd0baa8Skettenis DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32);
525dd0baa8Skettenis DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32);
535dd0baa8Skettenis DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32);
545dd0baa8Skettenis 
555dd0baa8Skettenis IOMFB_THUNK_INOUT(set_matrix);
565dd0baa8Skettenis IOMFB_THUNK_INOUT(get_color_remap_mode);
575dd0baa8Skettenis IOMFB_THUNK_INOUT(last_client_close);
585dd0baa8Skettenis IOMFB_THUNK_INOUT(abort_swaps_dcp);
595dd0baa8Skettenis 
605dd0baa8Skettenis DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit,
615dd0baa8Skettenis 		struct DCP_FW_NAME(dcp_swap_submit_req),
625dd0baa8Skettenis 		struct DCP_FW_NAME(dcp_swap_submit_resp));
635dd0baa8Skettenis 
645dd0baa8Skettenis DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req,
655dd0baa8Skettenis 		struct dcp_swap_start_resp);
665dd0baa8Skettenis 
675dd0baa8Skettenis DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state,
685dd0baa8Skettenis 		struct dcp_set_power_state_req,
695dd0baa8Skettenis 		struct dcp_set_power_state_resp);
705dd0baa8Skettenis 
715dd0baa8Skettenis DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode,
725dd0baa8Skettenis 		struct dcp_set_digital_out_mode_req, u32);
735dd0baa8Skettenis 
745dd0baa8Skettenis DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32);
755dd0baa8Skettenis 
765dd0baa8Skettenis DCP_THUNK_OUT(dcp_set_display_refresh_properties,
775dd0baa8Skettenis 	      dcpep_set_display_refresh_properties, u32);
785dd0baa8Skettenis 
795dd0baa8Skettenis #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
805dd0baa8Skettenis DCP_THUNK_INOUT(dcp_late_init_signal, dcpep_late_init_signal, u32, u32);
815dd0baa8Skettenis #else
825dd0baa8Skettenis DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32);
835dd0baa8Skettenis #endif
845dd0baa8Skettenis DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32);
855dd0baa8Skettenis DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32);
865dd0baa8Skettenis DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32);
875dd0baa8Skettenis DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits);
885dd0baa8Skettenis DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb);
895dd0baa8Skettenis DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open);
905dd0baa8Skettenis 
915dd0baa8Skettenis DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp,
925dd0baa8Skettenis 		struct dcp_set_parameter_dcp, u32);
935dd0baa8Skettenis 
945dd0baa8Skettenis DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings,
955dd0baa8Skettenis 		dcpep_enable_disable_video_power_savings, u32, int);
965dd0baa8Skettenis 
975dd0baa8Skettenis DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32);
985dd0baa8Skettenis 
995dd0baa8Skettenis /* DCP callback handlers */
1005dd0baa8Skettenis static void dcpep_cb_nop(struct apple_dcp *dcp)
1015dd0baa8Skettenis {
1025dd0baa8Skettenis 	/* No operation */
1035dd0baa8Skettenis }
1045dd0baa8Skettenis 
1055dd0baa8Skettenis static u8 dcpep_cb_true(struct apple_dcp *dcp)
1065dd0baa8Skettenis {
1075dd0baa8Skettenis 	return true;
1085dd0baa8Skettenis }
1095dd0baa8Skettenis 
1105dd0baa8Skettenis static u8 dcpep_cb_false(struct apple_dcp *dcp)
1115dd0baa8Skettenis {
1125dd0baa8Skettenis 	return false;
1135dd0baa8Skettenis }
1145dd0baa8Skettenis 
1155dd0baa8Skettenis static u32 dcpep_cb_zero(struct apple_dcp *dcp)
1165dd0baa8Skettenis {
1175dd0baa8Skettenis 	return 0;
1185dd0baa8Skettenis }
1195dd0baa8Skettenis 
1205dd0baa8Skettenis static void dcpep_cb_swap_complete(struct apple_dcp *dcp,
1215dd0baa8Skettenis 				   struct DCP_FW_NAME(dc_swap_complete_resp) *resp)
1225dd0baa8Skettenis {
1235dd0baa8Skettenis 	trace_iomfb_swap_complete(dcp, resp->swap_id);
1245dd0baa8Skettenis 	dcp->last_swap_id = resp->swap_id;
1255dd0baa8Skettenis 
1265dd0baa8Skettenis 	dcp_drm_crtc_vblank(dcp->crtc);
1275dd0baa8Skettenis }
1285dd0baa8Skettenis 
1295dd0baa8Skettenis /* special */
1305dd0baa8Skettenis static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, void *cookie)
1315dd0baa8Skettenis {
1325dd0baa8Skettenis 	// ack D100 cb_match_pmu_service
1335dd0baa8Skettenis 	dcp_ack(dcp, DCP_CONTEXT_CB);
1345dd0baa8Skettenis }
1355dd0baa8Skettenis 
1365dd0baa8Skettenis static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in)
1375dd0baa8Skettenis {
1385dd0baa8Skettenis 	trace_iomfb_callback(dcp, tag, __func__);
1395dd0baa8Skettenis 	iomfb_a358_vi_set_temperature_hint(dcp, false,
1405dd0baa8Skettenis 					   complete_vi_set_temperature_hint,
1415dd0baa8Skettenis 					   NULL);
1425dd0baa8Skettenis 
1435dd0baa8Skettenis 	// return false for deferred ACK
1445dd0baa8Skettenis 	return false;
1455dd0baa8Skettenis }
1465dd0baa8Skettenis 
1475dd0baa8Skettenis static void complete_pmu_service_matched(struct apple_dcp *dcp, void *out, void *cookie)
1485dd0baa8Skettenis {
1495dd0baa8Skettenis 	struct dcp_channel *ch = &dcp->ch_cb;
1505dd0baa8Skettenis 	u8 *succ = ch->output[ch->depth - 1];
1515dd0baa8Skettenis 
1525dd0baa8Skettenis 	*succ = true;
1535dd0baa8Skettenis 
1545dd0baa8Skettenis 	// ack D206 cb_match_pmu_service_2
1555dd0baa8Skettenis 	dcp_ack(dcp, DCP_CONTEXT_CB);
1565dd0baa8Skettenis }
1575dd0baa8Skettenis 
1585dd0baa8Skettenis static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void *out, void *in)
1595dd0baa8Skettenis {
1605dd0baa8Skettenis 	trace_iomfb_callback(dcp, tag, __func__);
1615dd0baa8Skettenis 
1625dd0baa8Skettenis 	iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched,
1635dd0baa8Skettenis 				       out);
1645dd0baa8Skettenis 
1655dd0baa8Skettenis 	// return false for deferred ACK
1665dd0baa8Skettenis 	return false;
1675dd0baa8Skettenis }
1685dd0baa8Skettenis 
1695dd0baa8Skettenis static void complete_backlight_service_matched(struct apple_dcp *dcp, void *out, void *cookie)
1705dd0baa8Skettenis {
1715dd0baa8Skettenis 	struct dcp_channel *ch = &dcp->ch_cb;
1725dd0baa8Skettenis 	u8 *succ = ch->output[ch->depth - 1];
1735dd0baa8Skettenis 
1745dd0baa8Skettenis 	*succ = true;
1755dd0baa8Skettenis 
1765dd0baa8Skettenis 	// ack D206 cb_match_backlight_service
1775dd0baa8Skettenis 	dcp_ack(dcp, DCP_CONTEXT_CB);
1785dd0baa8Skettenis }
1795dd0baa8Skettenis 
1805dd0baa8Skettenis static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, void *out, void *in)
1815dd0baa8Skettenis {
1825dd0baa8Skettenis 	trace_iomfb_callback(dcp, tag, __func__);
1835dd0baa8Skettenis 
1845dd0baa8Skettenis 	if (!dcp_has_panel(dcp)) {
1855dd0baa8Skettenis 		u8 *succ = out;
1865dd0baa8Skettenis 		*succ = true;
1875dd0baa8Skettenis 		return true;
1885dd0baa8Skettenis 	}
1895dd0baa8Skettenis 
1905dd0baa8Skettenis 	iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out);
1915dd0baa8Skettenis 
1925dd0baa8Skettenis 	// return false for deferred ACK
1935dd0baa8Skettenis 	return false;
1945dd0baa8Skettenis }
1955dd0baa8Skettenis 
1965dd0baa8Skettenis static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *prop)
1975dd0baa8Skettenis {
1985dd0baa8Skettenis 	switch (prop->id) {
1995dd0baa8Skettenis 	case IOMFB_PROPERTY_NITS:
2005dd0baa8Skettenis 	{
2015dd0baa8Skettenis 		if (dcp_has_panel(dcp)) {
2025dd0baa8Skettenis 			dcp->brightness.nits = prop->value / dcp->brightness.scale;
2035dd0baa8Skettenis 			/* notify backlight device of the initial brightness */
2045dd0baa8Skettenis 			if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0)
2055dd0baa8Skettenis 				schedule_work(&dcp->bl_register_wq);
2065dd0baa8Skettenis 			trace_iomfb_brightness(dcp, prop->value);
2075dd0baa8Skettenis 		}
2085dd0baa8Skettenis 		break;
2095dd0baa8Skettenis 	}
2105dd0baa8Skettenis 	default:
2115dd0baa8Skettenis 		dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value);
2125dd0baa8Skettenis 	}
2135dd0baa8Skettenis }
2145dd0baa8Skettenis 
2155dd0baa8Skettenis static struct dcp_get_uint_prop_resp
2165dd0baa8Skettenis dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req)
2175dd0baa8Skettenis {
2185dd0baa8Skettenis 	struct dcp_get_uint_prop_resp resp = (struct dcp_get_uint_prop_resp){
2195dd0baa8Skettenis 	    .value = 0
2205dd0baa8Skettenis 	};
2215dd0baa8Skettenis 
2225dd0baa8Skettenis 	if (dcp->panel.has_mini_led &&
2235dd0baa8Skettenis 	    memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */
2245dd0baa8Skettenis 	    if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) {
2255dd0baa8Skettenis 		/*
2265dd0baa8Skettenis 		 * TODO: value from j314c, find out if it is temperature in
2275dd0baa8Skettenis 		 *       centigrade C and which temperature sensor reports it
2285dd0baa8Skettenis 		 */
2295dd0baa8Skettenis 		resp.value = 3029;
2305dd0baa8Skettenis 		resp.ret = true;
2315dd0baa8Skettenis 	    }
2325dd0baa8Skettenis 	}
2335dd0baa8Skettenis 
2345dd0baa8Skettenis 	return resp;
2355dd0baa8Skettenis }
2365dd0baa8Skettenis 
2375dd0baa8Skettenis static u8 iomfbep_cb_sr_set_property_int(struct apple_dcp *dcp,
2385dd0baa8Skettenis 					 struct iomfb_sr_set_property_int_req *req)
2395dd0baa8Skettenis {
2405dd0baa8Skettenis 	if (memcmp(req->obj, "FMOI", sizeof(req->obj)) == 0) { /* "IOMF */
2415dd0baa8Skettenis 		if (strncmp(req->key, "Brightness_Scale", sizeof(req->key)) == 0) {
2425dd0baa8Skettenis 			if (!req->value_null)
2435dd0baa8Skettenis 				dcp->brightness.scale = req->value;
2445dd0baa8Skettenis 		}
2455dd0baa8Skettenis 	}
2465dd0baa8Skettenis 
2475dd0baa8Skettenis 	return 1;
2485dd0baa8Skettenis }
2495dd0baa8Skettenis 
2505dd0baa8Skettenis static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req)
2515dd0baa8Skettenis {
2525dd0baa8Skettenis     // TODO: trace this, see if there properties which needs to used later
2535dd0baa8Skettenis }
2545dd0baa8Skettenis 
2555dd0baa8Skettenis /*
2565dd0baa8Skettenis  * Callback to map a buffer allocated with allocate_buf for PIODMA usage.
2575dd0baa8Skettenis  * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated
2585dd0baa8Skettenis  * stream of the display DART, rather than the expected DCP DART.
2595dd0baa8Skettenis  */
2605dd0baa8Skettenis static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp,
2615dd0baa8Skettenis 						   struct dcp_map_buf_req *req)
2625dd0baa8Skettenis {
2635dd0baa8Skettenis 	struct dcp_mem_descriptor *memdesc;
2645dd0baa8Skettenis 	struct sg_table *map;
2655dd0baa8Skettenis 	ssize_t ret;
2665dd0baa8Skettenis 
2675dd0baa8Skettenis 	if (req->buffer >= ARRAY_SIZE(dcp->memdesc))
2685dd0baa8Skettenis 		goto reject;
2695dd0baa8Skettenis 
2705dd0baa8Skettenis 	memdesc = &dcp->memdesc[req->buffer];
2715dd0baa8Skettenis 	map = &memdesc->map;
2725dd0baa8Skettenis 
2735dd0baa8Skettenis 	if (!map->sgl)
2745dd0baa8Skettenis 		goto reject;
2755dd0baa8Skettenis 
2765dd0baa8Skettenis 	/* use the piodma iommu domain to map against the right IOMMU */
2775dd0baa8Skettenis 	ret = iommu_map_sgtable(dcp->iommu_dom, memdesc->dva, map,
2785dd0baa8Skettenis 				IOMMU_READ | IOMMU_WRITE);
2795dd0baa8Skettenis 
2805dd0baa8Skettenis 	/* HACK: expect size to be 16K aligned since the iommu API only maps
2815dd0baa8Skettenis 	 *       full pages
2825dd0baa8Skettenis 	 */
2835dd0baa8Skettenis 	if (ret < 0 || ret != ALIGN(memdesc->size, SZ_16K)) {
2845dd0baa8Skettenis 		dev_err(dcp->dev, "iommu_map_sgtable() returned %zd instead of expected buffer size of %zu\n", ret, memdesc->size);
2855dd0baa8Skettenis 		goto reject;
2865dd0baa8Skettenis 	}
2875dd0baa8Skettenis 
2885dd0baa8Skettenis 	return (struct dcp_map_buf_resp){ .dva = memdesc->dva };
2895dd0baa8Skettenis 
2905dd0baa8Skettenis reject:
2915dd0baa8Skettenis 	dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n",
2925dd0baa8Skettenis 		req->buffer);
2935dd0baa8Skettenis 	return (struct dcp_map_buf_resp){ .ret = EINVAL };
2945dd0baa8Skettenis }
2955dd0baa8Skettenis 
2965dd0baa8Skettenis static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp,
2975dd0baa8Skettenis 				  struct dcp_unmap_buf_resp *resp)
2985dd0baa8Skettenis {
2995dd0baa8Skettenis 	struct dcp_mem_descriptor *memdesc;
3005dd0baa8Skettenis 
3015dd0baa8Skettenis 	if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) {
30244a5d259Skettenis 		dev_warn(dcp->dev, "unmap request for out of range buffer %llu\n",
3035dd0baa8Skettenis 			 resp->buffer);
3045dd0baa8Skettenis 		return;
3055dd0baa8Skettenis 	}
3065dd0baa8Skettenis 
3075dd0baa8Skettenis 	memdesc = &dcp->memdesc[resp->buffer];
3085dd0baa8Skettenis 
3095dd0baa8Skettenis 	if (!memdesc->buf) {
3105dd0baa8Skettenis 		dev_warn(dcp->dev,
31144a5d259Skettenis 			 "unmap for non-mapped buffer %llu iova:0x%08llx\n",
3125dd0baa8Skettenis 			 resp->buffer, resp->dva);
3135dd0baa8Skettenis 		return;
3145dd0baa8Skettenis 	}
3155dd0baa8Skettenis 
3165dd0baa8Skettenis 	if (memdesc->dva != resp->dva) {
3175dd0baa8Skettenis 		dev_warn(dcp->dev, "unmap buffer %llu address mismatch "
31844a5d259Skettenis 			 "memdesc.dva:%llx dva:%llx\n", resp->buffer,
3195dd0baa8Skettenis 			 memdesc->dva, resp->dva);
3205dd0baa8Skettenis 		return;
3215dd0baa8Skettenis 	}
3225dd0baa8Skettenis 
3235dd0baa8Skettenis 	/* use the piodma iommu domain to unmap from the right IOMMU */
3245dd0baa8Skettenis 	iommu_unmap(dcp->iommu_dom, memdesc->dva, memdesc->size);
3255dd0baa8Skettenis }
3265dd0baa8Skettenis 
3275dd0baa8Skettenis /*
3285dd0baa8Skettenis  * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be
329172aa44cSkettenis  * physically contiguous, however we should save the sgtable in case the
3305dd0baa8Skettenis  * buffer needs to be later mapped for PIODMA.
3315dd0baa8Skettenis  */
3325dd0baa8Skettenis static struct dcp_allocate_buffer_resp
3335dd0baa8Skettenis dcpep_cb_allocate_buffer(struct apple_dcp *dcp,
3345dd0baa8Skettenis 			 struct dcp_allocate_buffer_req *req)
3355dd0baa8Skettenis {
3365dd0baa8Skettenis 	struct dcp_allocate_buffer_resp resp = { 0 };
3375dd0baa8Skettenis 	struct dcp_mem_descriptor *memdesc;
3385dd0baa8Skettenis 	size_t size;
3395dd0baa8Skettenis 	u32 id;
3405dd0baa8Skettenis 
3415dd0baa8Skettenis 	resp.dva_size = ALIGN(req->size, 4096);
3425dd0baa8Skettenis 	resp.mem_desc_id =
3435dd0baa8Skettenis 		find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
3445dd0baa8Skettenis 
3455dd0baa8Skettenis 	if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) {
34644a5d259Skettenis 		dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring\n");
3475dd0baa8Skettenis 		resp.dva_size = 0;
3485dd0baa8Skettenis 		resp.mem_desc_id = 0;
3495dd0baa8Skettenis 		return resp;
3505dd0baa8Skettenis 	}
3515dd0baa8Skettenis 	id = resp.mem_desc_id;
3525dd0baa8Skettenis 	set_bit(id, dcp->memdesc_map);
3535dd0baa8Skettenis 
3545dd0baa8Skettenis 	memdesc = &dcp->memdesc[id];
3555dd0baa8Skettenis 
3565dd0baa8Skettenis 	memdesc->size = resp.dva_size;
3575dd0baa8Skettenis 	/* HACK: align size to 16K since the iommu API only maps full pages */
3585dd0baa8Skettenis 	size = ALIGN(resp.dva_size, SZ_16K);
3595dd0baa8Skettenis 	memdesc->buf = dma_alloc_coherent(dcp->dev, size,
3605dd0baa8Skettenis 					  &memdesc->dva, GFP_KERNEL);
3615dd0baa8Skettenis 
3625dd0baa8Skettenis 	dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva,
3635dd0baa8Skettenis 			size);
3645dd0baa8Skettenis 	resp.dva = memdesc->dva;
3655dd0baa8Skettenis 
3665dd0baa8Skettenis 	return resp;
3675dd0baa8Skettenis }
3685dd0baa8Skettenis 
3695dd0baa8Skettenis static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id)
3705dd0baa8Skettenis {
3715dd0baa8Skettenis 	struct dcp_mem_descriptor *memdesc;
3725dd0baa8Skettenis 	u32 id = *mem_desc_id;
3735dd0baa8Skettenis 
3745dd0baa8Skettenis 	if (id >= DCP_MAX_MAPPINGS) {
3755dd0baa8Skettenis 		dev_warn(dcp->dev,
3765dd0baa8Skettenis 			 "unmap request for out of range mem_desc_id %u", id);
3775dd0baa8Skettenis 		return 0;
3785dd0baa8Skettenis 	}
3795dd0baa8Skettenis 
3805dd0baa8Skettenis 	if (!test_and_clear_bit(id, dcp->memdesc_map)) {
38144a5d259Skettenis 		dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u\n",
3825dd0baa8Skettenis 			 id);
3835dd0baa8Skettenis 		return 0;
3845dd0baa8Skettenis 	}
3855dd0baa8Skettenis 
3865dd0baa8Skettenis 	memdesc = &dcp->memdesc[id];
3875dd0baa8Skettenis 	if (memdesc->buf) {
3885dd0baa8Skettenis 		dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf,
3895dd0baa8Skettenis 				  memdesc->dva);
3905dd0baa8Skettenis 
3915dd0baa8Skettenis 		memdesc->buf = NULL;
3925dd0baa8Skettenis 		memset(&memdesc->map, 0, sizeof(memdesc->map));
3935dd0baa8Skettenis 	} else {
3945dd0baa8Skettenis 		memdesc->reg = 0;
3955dd0baa8Skettenis 	}
3965dd0baa8Skettenis 
3975dd0baa8Skettenis 	memdesc->size = 0;
3985dd0baa8Skettenis 
3995dd0baa8Skettenis 	return 1;
4005dd0baa8Skettenis }
4015dd0baa8Skettenis 
4025dd0baa8Skettenis /* Validate that the specified region is a display register */
4035dd0baa8Skettenis static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end)
4045dd0baa8Skettenis {
4055dd0baa8Skettenis 	int i;
4065dd0baa8Skettenis 
4075dd0baa8Skettenis 	for (i = 0; i < dcp->nr_disp_registers; ++i) {
4085dd0baa8Skettenis 		struct resource *r = dcp->disp_registers[i];
4095dd0baa8Skettenis 
4105dd0baa8Skettenis 		if ((start >= r->start) && (end <= r->end))
4115dd0baa8Skettenis 			return true;
4125dd0baa8Skettenis 	}
4135dd0baa8Skettenis 
4145dd0baa8Skettenis 	return false;
4155dd0baa8Skettenis }
4165dd0baa8Skettenis 
4175dd0baa8Skettenis /*
4185dd0baa8Skettenis  * Map contiguous physical memory into the DCP's address space. The firmware
4195dd0baa8Skettenis  * uses this to map the display registers we advertise in
4205dd0baa8Skettenis  * sr_map_device_memory_with_index, so we bounds check against that to guard
4215dd0baa8Skettenis  * safe against malicious coprocessors.
4225dd0baa8Skettenis  */
4235dd0baa8Skettenis static struct dcp_map_physical_resp
4245dd0baa8Skettenis dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
4255dd0baa8Skettenis {
4265dd0baa8Skettenis 	int size = ALIGN(req->size, 4096);
4275dd0baa8Skettenis 	dma_addr_t dva;
4285dd0baa8Skettenis 	u32 id;
4295dd0baa8Skettenis 
4305dd0baa8Skettenis 	if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) {
43144a5d259Skettenis 		dev_err(dcp->dev, "refusing to map phys address %llx size %llx\n",
4325dd0baa8Skettenis 			req->paddr, req->size);
4335dd0baa8Skettenis 		return (struct dcp_map_physical_resp){};
4345dd0baa8Skettenis 	}
4355dd0baa8Skettenis 
4365dd0baa8Skettenis 	id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
4375dd0baa8Skettenis 	set_bit(id, dcp->memdesc_map);
4385dd0baa8Skettenis 	dcp->memdesc[id].size = size;
4395dd0baa8Skettenis 	dcp->memdesc[id].reg = req->paddr;
4405dd0baa8Skettenis 
4415dd0baa8Skettenis 	dva = dma_map_resource(dcp->dev, req->paddr, size, DMA_BIDIRECTIONAL, 0);
4425dd0baa8Skettenis 	WARN_ON(dva == DMA_MAPPING_ERROR);
4435dd0baa8Skettenis 
4445dd0baa8Skettenis 	return (struct dcp_map_physical_resp){
4455dd0baa8Skettenis 		.dva_size = size,
4465dd0baa8Skettenis 		.mem_desc_id = id,
4475dd0baa8Skettenis 		.dva = dva,
4485dd0baa8Skettenis 	};
4495dd0baa8Skettenis }
4505dd0baa8Skettenis 
4515dd0baa8Skettenis static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp)
4525dd0baa8Skettenis {
4535dd0baa8Skettenis 	return clk_get_rate(dcp->clk);
4545dd0baa8Skettenis }
4555dd0baa8Skettenis 
4565dd0baa8Skettenis static struct DCP_FW_NAME(dcp_map_reg_resp) dcpep_cb_map_reg(struct apple_dcp *dcp,
4575dd0baa8Skettenis 						struct DCP_FW_NAME(dcp_map_reg_req) *req)
4585dd0baa8Skettenis {
4595dd0baa8Skettenis 	if (req->index >= dcp->nr_disp_registers) {
46044a5d259Skettenis 		dev_warn(dcp->dev, "attempted to read invalid reg index %u\n",
4615dd0baa8Skettenis 			 req->index);
4625dd0baa8Skettenis 
4635dd0baa8Skettenis 		return (struct DCP_FW_NAME(dcp_map_reg_resp)){ .ret = 1 };
4645dd0baa8Skettenis 	} else {
4655dd0baa8Skettenis 		struct resource *rsrc = dcp->disp_registers[req->index];
4665dd0baa8Skettenis #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
4675dd0baa8Skettenis 		dma_addr_t dva = dma_map_resource(dcp->dev, rsrc->start, resource_size(rsrc),
4685dd0baa8Skettenis 						  DMA_BIDIRECTIONAL, 0);
4695dd0baa8Skettenis 		WARN_ON(dva == DMA_MAPPING_ERROR);
4705dd0baa8Skettenis #endif
4715dd0baa8Skettenis 
4725dd0baa8Skettenis 		return (struct DCP_FW_NAME(dcp_map_reg_resp)){
4735dd0baa8Skettenis 			.addr = rsrc->start,
4745dd0baa8Skettenis 			.length = resource_size(rsrc),
4755dd0baa8Skettenis #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
4765dd0baa8Skettenis 			.dva = dva,
4775dd0baa8Skettenis #endif
4785dd0baa8Skettenis 		};
4795dd0baa8Skettenis 	}
4805dd0baa8Skettenis }
4815dd0baa8Skettenis 
4825dd0baa8Skettenis static struct dcp_read_edt_data_resp
4835dd0baa8Skettenis dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req)
4845dd0baa8Skettenis {
4855dd0baa8Skettenis 	return (struct dcp_read_edt_data_resp){
4865dd0baa8Skettenis 		.value[0] = req->value[0],
4875dd0baa8Skettenis 		.ret = 0,
4885dd0baa8Skettenis 	};
4895dd0baa8Skettenis }
4905dd0baa8Skettenis 
4915dd0baa8Skettenis static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp,
4925dd0baa8Skettenis 							 u8 *enabled)
4935dd0baa8Skettenis {
4945dd0baa8Skettenis 	/*
4955dd0baa8Skettenis 	 * update backlight brightness on next swap, on non mini-LED displays
4965dd0baa8Skettenis 	 * DCP seems to set an invalid iDAC value after coming out of DPMS.
4975dd0baa8Skettenis 	 * syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range"
4985dd0baa8Skettenis 	 */
4995dd0baa8Skettenis 	dcp->brightness.update = true;
50048fd60f3Skettenis 	schedule_work(&dcp->bl_update_wq);
5015dd0baa8Skettenis }
5025dd0baa8Skettenis 
5035dd0baa8Skettenis /* Chunked data transfer for property dictionaries */
5045dd0baa8Skettenis static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length)
5055dd0baa8Skettenis {
5065dd0baa8Skettenis 	if (dcp->chunks.data != NULL) {
5075dd0baa8Skettenis 		dev_warn(dcp->dev, "ignoring spurious transfer start\n");
5085dd0baa8Skettenis 		return false;
5095dd0baa8Skettenis 	}
5105dd0baa8Skettenis 
5115dd0baa8Skettenis 	dcp->chunks.length = *length;
5125dd0baa8Skettenis 	dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL);
5135dd0baa8Skettenis 
5145dd0baa8Skettenis 	if (!dcp->chunks.data) {
5155dd0baa8Skettenis 		dev_warn(dcp->dev, "failed to allocate chunks\n");
5165dd0baa8Skettenis 		return false;
5175dd0baa8Skettenis 	}
5185dd0baa8Skettenis 
5195dd0baa8Skettenis 	return true;
5205dd0baa8Skettenis }
5215dd0baa8Skettenis 
5225dd0baa8Skettenis static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp,
5235dd0baa8Skettenis 			      struct dcp_set_dcpav_prop_chunk_req *req)
5245dd0baa8Skettenis {
5255dd0baa8Skettenis 	if (!dcp->chunks.data) {
5265dd0baa8Skettenis 		dev_warn(dcp->dev, "ignoring spurious chunk\n");
5275dd0baa8Skettenis 		return false;
5285dd0baa8Skettenis 	}
5295dd0baa8Skettenis 
5305dd0baa8Skettenis 	if (req->offset + req->length > dcp->chunks.length) {
5315dd0baa8Skettenis 		dev_warn(dcp->dev, "ignoring overflowing chunk\n");
5325dd0baa8Skettenis 		return false;
5335dd0baa8Skettenis 	}
5345dd0baa8Skettenis 
5355dd0baa8Skettenis 	memcpy(dcp->chunks.data + req->offset, req->data, req->length);
5365dd0baa8Skettenis 	return true;
5375dd0baa8Skettenis }
5385dd0baa8Skettenis 
5395dd0baa8Skettenis static bool dcpep_process_chunks(struct apple_dcp *dcp,
5405dd0baa8Skettenis 				 struct dcp_set_dcpav_prop_end_req *req)
5415dd0baa8Skettenis {
5425dd0baa8Skettenis 	struct dcp_parse_ctx ctx;
5435dd0baa8Skettenis 	int ret;
5445dd0baa8Skettenis 
5455dd0baa8Skettenis 	if (!dcp->chunks.data) {
5465dd0baa8Skettenis 		dev_warn(dcp->dev, "ignoring spurious end\n");
5475dd0baa8Skettenis 		return false;
5485dd0baa8Skettenis 	}
5495dd0baa8Skettenis 
5505dd0baa8Skettenis 	/* used just as opaque pointer for tracing */
5515dd0baa8Skettenis 	ctx.dcp = dcp;
5525dd0baa8Skettenis 
5535dd0baa8Skettenis 	ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx);
5545dd0baa8Skettenis 
5555dd0baa8Skettenis 	if (ret) {
5565dd0baa8Skettenis 		dev_warn(dcp->dev, "bad header on dcpav props\n");
5575dd0baa8Skettenis 		return false;
5585dd0baa8Skettenis 	}
5595dd0baa8Skettenis 
5605dd0baa8Skettenis 	if (!strcmp(req->key, "TimingElements")) {
5615dd0baa8Skettenis 		dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes,
5625dd0baa8Skettenis 					     dcp->width_mm, dcp->height_mm,
5635dd0baa8Skettenis 					     dcp->notch_height);
5645dd0baa8Skettenis 
5655dd0baa8Skettenis 		if (IS_ERR(dcp->modes)) {
5665dd0baa8Skettenis 			dev_warn(dcp->dev, "failed to parse modes\n");
5675dd0baa8Skettenis 			dcp->modes = NULL;
5685dd0baa8Skettenis 			dcp->nr_modes = 0;
5695dd0baa8Skettenis 			return false;
5705dd0baa8Skettenis 		}
5715dd0baa8Skettenis 		if (dcp->nr_modes == 0)
5725dd0baa8Skettenis 			dev_warn(dcp->dev, "TimingElements without valid modes!\n");
5735dd0baa8Skettenis 	} else if (!strcmp(req->key, "DisplayAttributes")) {
5745dd0baa8Skettenis 		ret = parse_display_attributes(&ctx, &dcp->width_mm,
5755dd0baa8Skettenis 					&dcp->height_mm);
5765dd0baa8Skettenis 
5775dd0baa8Skettenis 		if (ret) {
5785dd0baa8Skettenis 			dev_warn(dcp->dev, "failed to parse display attribs\n");
5795dd0baa8Skettenis 			return false;
5805dd0baa8Skettenis 		}
5815dd0baa8Skettenis 
5825dd0baa8Skettenis 		dcp_set_dimensions(dcp);
5835dd0baa8Skettenis 	}
5845dd0baa8Skettenis 
5855dd0baa8Skettenis 	return true;
5865dd0baa8Skettenis }
5875dd0baa8Skettenis 
5885dd0baa8Skettenis static u8 dcpep_cb_prop_end(struct apple_dcp *dcp,
5895dd0baa8Skettenis 			    struct dcp_set_dcpav_prop_end_req *req)
5905dd0baa8Skettenis {
5915dd0baa8Skettenis 	u8 resp = dcpep_process_chunks(dcp, req);
5925dd0baa8Skettenis 
5935dd0baa8Skettenis 	/* Reset for the next transfer */
5945dd0baa8Skettenis 	devm_kfree(dcp->dev, dcp->chunks.data);
5955dd0baa8Skettenis 	dcp->chunks.data = NULL;
5965dd0baa8Skettenis 
5975dd0baa8Skettenis 	return resp;
5985dd0baa8Skettenis }
5995dd0baa8Skettenis 
6005dd0baa8Skettenis /* Boot sequence */
6015dd0baa8Skettenis static void boot_done(struct apple_dcp *dcp, void *out, void *cookie)
6025dd0baa8Skettenis {
6035dd0baa8Skettenis 	struct dcp_channel *ch = &dcp->ch_cb;
6045dd0baa8Skettenis 	u8 *succ = ch->output[ch->depth - 1];
60544a5d259Skettenis 	dev_dbg(dcp->dev, "boot done\n");
6065dd0baa8Skettenis 
6075dd0baa8Skettenis 	*succ = true;
6085dd0baa8Skettenis 	dcp_ack(dcp, DCP_CONTEXT_CB);
6095dd0baa8Skettenis }
6105dd0baa8Skettenis 
6115dd0baa8Skettenis static void boot_5(struct apple_dcp *dcp, void *out, void *cookie)
6125dd0baa8Skettenis {
6135dd0baa8Skettenis 	dcp_set_display_refresh_properties(dcp, false, boot_done, NULL);
6145dd0baa8Skettenis }
6155dd0baa8Skettenis 
6165dd0baa8Skettenis static void boot_4(struct apple_dcp *dcp, void *out, void *cookie)
6175dd0baa8Skettenis {
6185dd0baa8Skettenis #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
6195dd0baa8Skettenis 	u32 v_true = 1;
6205dd0baa8Skettenis 	dcp_late_init_signal(dcp, false, &v_true, boot_5, NULL);
6215dd0baa8Skettenis #else
6225dd0baa8Skettenis 	dcp_late_init_signal(dcp, false, boot_5, NULL);
6235dd0baa8Skettenis #endif
6245dd0baa8Skettenis }
6255dd0baa8Skettenis 
6265dd0baa8Skettenis static void boot_3(struct apple_dcp *dcp, void *out, void *cookie)
6275dd0baa8Skettenis {
6285dd0baa8Skettenis 	u32 v_true = true;
6295dd0baa8Skettenis 
6305dd0baa8Skettenis 	dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL);
6315dd0baa8Skettenis }
6325dd0baa8Skettenis 
6335dd0baa8Skettenis static void boot_2(struct apple_dcp *dcp, void *out, void *cookie)
6345dd0baa8Skettenis {
6355dd0baa8Skettenis 	dcp_setup_video_limits(dcp, false, boot_3, NULL);
6365dd0baa8Skettenis }
6375dd0baa8Skettenis 
6385dd0baa8Skettenis static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie)
6395dd0baa8Skettenis {
6405dd0baa8Skettenis 	dcp_create_default_fb(dcp, false, boot_2, NULL);
6415dd0baa8Skettenis }
6425dd0baa8Skettenis 
6435dd0baa8Skettenis /* Use special function signature to defer the ACK */
6445dd0baa8Skettenis static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in)
6455dd0baa8Skettenis {
6465dd0baa8Skettenis 	trace_iomfb_callback(dcp, tag, __func__);
6475dd0baa8Skettenis 	dcp_set_create_dfb(dcp, false, boot_1_5, NULL);
6485dd0baa8Skettenis 	return false;
6495dd0baa8Skettenis }
6505dd0baa8Skettenis 
6515dd0baa8Skettenis static struct dcp_allocate_bandwidth_resp dcpep_cb_allocate_bandwidth(struct apple_dcp *dcp,
6525dd0baa8Skettenis 						struct dcp_allocate_bandwidth_req *req)
6535dd0baa8Skettenis {
6545dd0baa8Skettenis 	return (struct dcp_allocate_bandwidth_resp){
6555dd0baa8Skettenis 		.unk1 = req->unk1,
6565dd0baa8Skettenis 		.unk2 = req->unk2,
6575dd0baa8Skettenis 		.ret = 1,
6585dd0baa8Skettenis 	};
6595dd0baa8Skettenis }
6605dd0baa8Skettenis 
6615dd0baa8Skettenis static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp)
6625dd0baa8Skettenis {
6635dd0baa8Skettenis 	struct dcp_rt_bandwidth rt_bw = (struct dcp_rt_bandwidth){
6645dd0baa8Skettenis 			.reg_scratch = 0,
6655dd0baa8Skettenis 			.reg_doorbell = 0,
6665dd0baa8Skettenis 			.doorbell_bit = 0,
6675dd0baa8Skettenis 	};
6685dd0baa8Skettenis 
6695dd0baa8Skettenis 	if (dcp->disp_bw_scratch_index) {
6705dd0baa8Skettenis 		u32 offset = dcp->disp_bw_scratch_offset;
6715dd0baa8Skettenis 		u32 index = dcp->disp_bw_scratch_index;
6725dd0baa8Skettenis 		rt_bw.reg_scratch = dcp->disp_registers[index]->start + offset;
6735dd0baa8Skettenis 	}
6745dd0baa8Skettenis 
6755dd0baa8Skettenis 	if (dcp->disp_bw_doorbell_index) {
6765dd0baa8Skettenis 		u32 index = dcp->disp_bw_doorbell_index;
6775dd0baa8Skettenis 		rt_bw.reg_doorbell = dcp->disp_registers[index]->start;
6785dd0baa8Skettenis 		rt_bw.doorbell_bit = REG_DOORBELL_BIT(dcp->index);
6795dd0baa8Skettenis 		/*
6805dd0baa8Skettenis 		 * This is most certainly not padding. t8103-dcp crashes without
6815dd0baa8Skettenis 		 * setting this immediately during modeset on 12.3 and 13.5
6825dd0baa8Skettenis 		 * firmware.
6835dd0baa8Skettenis 		 */
6845dd0baa8Skettenis 		rt_bw.padding[3] = 0x4;
6855dd0baa8Skettenis 	}
6865dd0baa8Skettenis 
6875dd0baa8Skettenis 	return rt_bw;
6885dd0baa8Skettenis }
6895dd0baa8Skettenis 
6905dd0baa8Skettenis static struct dcp_set_frame_sync_props_resp
6915dd0baa8Skettenis dcpep_cb_set_frame_sync_props(struct apple_dcp *dcp,
6925dd0baa8Skettenis 			      struct dcp_set_frame_sync_props_req *req)
6935dd0baa8Skettenis {
6945dd0baa8Skettenis 	return (struct dcp_set_frame_sync_props_resp){};
6955dd0baa8Skettenis }
6965dd0baa8Skettenis 
6975dd0baa8Skettenis /* Callback to get the current time as milliseconds since the UNIX epoch */
6985dd0baa8Skettenis static u64 dcpep_cb_get_time(struct apple_dcp *dcp)
6995dd0baa8Skettenis {
7005dd0baa8Skettenis 	return ktime_to_ms(ktime_get_real());
7015dd0baa8Skettenis }
7025dd0baa8Skettenis 
7035dd0baa8Skettenis struct dcp_swap_cookie {
7045dd0baa8Skettenis 	struct kref refcount;
7055dd0baa8Skettenis 	struct completion done;
7065dd0baa8Skettenis 	u32 swap_id;
7075dd0baa8Skettenis };
7085dd0baa8Skettenis 
7095dd0baa8Skettenis static void release_swap_cookie(struct kref *ref)
7105dd0baa8Skettenis {
7115dd0baa8Skettenis 	struct dcp_swap_cookie *cookie;
7125dd0baa8Skettenis 	cookie = container_of(ref, struct dcp_swap_cookie, refcount);
7135dd0baa8Skettenis 
7145dd0baa8Skettenis         kfree(cookie);
7155dd0baa8Skettenis }
7165dd0baa8Skettenis 
7175dd0baa8Skettenis static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie)
7185dd0baa8Skettenis {
7195dd0baa8Skettenis 	struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data;
7205dd0baa8Skettenis 
7215dd0baa8Skettenis 	if (cookie) {
7225dd0baa8Skettenis 		struct dcp_swap_cookie *info = cookie;
7235dd0baa8Skettenis 		complete(&info->done);
7245dd0baa8Skettenis 		kref_put(&info->refcount, release_swap_cookie);
7255dd0baa8Skettenis 	}
7265dd0baa8Skettenis 
7275dd0baa8Skettenis 	if (resp->ret) {
7285dd0baa8Skettenis 		dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret);
7295dd0baa8Skettenis 		dcp_drm_crtc_vblank(dcp->crtc);
7305dd0baa8Skettenis 		return;
7315dd0baa8Skettenis 	}
7325dd0baa8Skettenis 
7335dd0baa8Skettenis 	while (!list_empty(&dcp->swapped_out_fbs)) {
7345dd0baa8Skettenis 		struct dcp_fb_reference *entry;
7355dd0baa8Skettenis 		entry = list_first_entry(&dcp->swapped_out_fbs,
7365dd0baa8Skettenis 					 struct dcp_fb_reference, head);
7375dd0baa8Skettenis 		if (entry->swap_id == dcp->last_swap_id)
7385dd0baa8Skettenis 			break;
7395dd0baa8Skettenis 		if (entry->fb)
7405dd0baa8Skettenis 			drm_framebuffer_put(entry->fb);
7415dd0baa8Skettenis 		list_del(&entry->head);
7425dd0baa8Skettenis 		kfree(entry);
7435dd0baa8Skettenis 	}
7445dd0baa8Skettenis }
7455dd0baa8Skettenis 
7465dd0baa8Skettenis static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data,
7475dd0baa8Skettenis 				   void *cookie)
7485dd0baa8Skettenis {
7495dd0baa8Skettenis 	struct dcp_swap_start_resp *resp = data;
7505dd0baa8Skettenis 	DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id;
7515dd0baa8Skettenis 
7525dd0baa8Skettenis 	if (cookie) {
7535dd0baa8Skettenis 		struct dcp_swap_cookie *info = cookie;
7545dd0baa8Skettenis 		info->swap_id = resp->swap_id;
7555dd0baa8Skettenis 	}
7565dd0baa8Skettenis 
7575dd0baa8Skettenis 	dcp_swap_submit(dcp, false, &DCP_FW_UNION(dcp->swap), dcp_swap_cleared, cookie);
7585dd0baa8Skettenis }
7595dd0baa8Skettenis 
7605dd0baa8Skettenis static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie)
7615dd0baa8Skettenis {
7625dd0baa8Skettenis 	struct dcp_wait_cookie *wait = cookie;
7635dd0baa8Skettenis 
7645dd0baa8Skettenis 	if (wait) {
7655dd0baa8Skettenis 		complete(&wait->done);
7665dd0baa8Skettenis 		kref_put(&wait->refcount, release_wait_cookie);
7675dd0baa8Skettenis 	}
7685dd0baa8Skettenis }
7695dd0baa8Skettenis 
7705dd0baa8Skettenis static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cookie)
7715dd0baa8Skettenis {
7725dd0baa8Skettenis 	struct dcp_set_power_state_req req = {
7735dd0baa8Skettenis 		.unklong = 1,
7745dd0baa8Skettenis 	};
7755dd0baa8Skettenis 
7765dd0baa8Skettenis 	dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie);
7775dd0baa8Skettenis }
7785dd0baa8Skettenis 
7795dd0baa8Skettenis static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie)
7805dd0baa8Skettenis {
7815dd0baa8Skettenis 	struct dcp_set_parameter_dcp param = {
7825dd0baa8Skettenis 		.param = 14,
7835dd0baa8Skettenis 		.value = { 0 },
7845dd0baa8Skettenis #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
7855dd0baa8Skettenis 		.count = 3,
7865dd0baa8Skettenis #else
7875dd0baa8Skettenis 		.count = 1,
7885dd0baa8Skettenis #endif
7895dd0baa8Skettenis 	};
7905dd0baa8Skettenis 
7915dd0baa8Skettenis 	dcp_set_parameter_dcp(dcp, false, &param, dcp_on_set_power_state, cookie);
7925dd0baa8Skettenis }
7935dd0baa8Skettenis 
7945dd0baa8Skettenis void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp)
7955dd0baa8Skettenis {
7965dd0baa8Skettenis 	struct dcp_wait_cookie *cookie;
7975dd0baa8Skettenis 	int ret;
7985dd0baa8Skettenis 	u32 handle;
799*485c4124Sjsg 	dev_info(dcp->dev, "dcp_poweron() starting\n");
8005dd0baa8Skettenis 
8015dd0baa8Skettenis 	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
8025dd0baa8Skettenis 	if (!cookie)
8035dd0baa8Skettenis 		return;
8045dd0baa8Skettenis 
8055dd0baa8Skettenis 	init_completion(&cookie->done);
8065dd0baa8Skettenis 	kref_init(&cookie->refcount);
8075dd0baa8Skettenis 	/* increase refcount to ensure the receiver has a reference */
8085dd0baa8Skettenis 	kref_get(&cookie->refcount);
8095dd0baa8Skettenis 
8105dd0baa8Skettenis 	if (dcp->main_display) {
8115dd0baa8Skettenis 		handle = 0;
8125dd0baa8Skettenis 		dcp_set_display_device(dcp, false, &handle, dcp_on_set_power_state,
8135dd0baa8Skettenis 				       cookie);
8145dd0baa8Skettenis 	} else {
8155dd0baa8Skettenis 		handle = 2;
8165dd0baa8Skettenis 		dcp_set_display_device(dcp, false, &handle,
8175dd0baa8Skettenis 				       dcp_on_set_parameter, cookie);
8185dd0baa8Skettenis 	}
8195dd0baa8Skettenis 	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500));
8205dd0baa8Skettenis 
8215dd0baa8Skettenis 	if (ret == 0)
82244a5d259Skettenis 		dev_warn(dcp->dev, "wait for power timed out\n");
8235dd0baa8Skettenis 
8245dd0baa8Skettenis 	kref_put(&cookie->refcount, release_wait_cookie);;
8255dd0baa8Skettenis 
8265dd0baa8Skettenis 	/* Force a brightness update after poweron, to restore the brightness */
8275dd0baa8Skettenis 	dcp->brightness.update = true;
8285dd0baa8Skettenis }
8295dd0baa8Skettenis 
8305dd0baa8Skettenis static void complete_set_powerstate(struct apple_dcp *dcp, void *out,
8315dd0baa8Skettenis 				    void *cookie)
8325dd0baa8Skettenis {
8335dd0baa8Skettenis 	struct dcp_wait_cookie *wait = cookie;
8345dd0baa8Skettenis 
8355dd0baa8Skettenis 	if (wait) {
8365dd0baa8Skettenis 		complete(&wait->done);
8375dd0baa8Skettenis 		kref_put(&wait->refcount, release_wait_cookie);
8385dd0baa8Skettenis 	}
8395dd0baa8Skettenis }
8405dd0baa8Skettenis 
8415dd0baa8Skettenis static void last_client_closed_poff(struct apple_dcp *dcp, void *out, void *cookie)
8425dd0baa8Skettenis {
8435dd0baa8Skettenis 	struct dcp_set_power_state_req power_req = {
8445dd0baa8Skettenis 		.unklong = 0,
8455dd0baa8Skettenis 	};
8465dd0baa8Skettenis 	dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate,
8475dd0baa8Skettenis 			    cookie);
8485dd0baa8Skettenis }
8495dd0baa8Skettenis 
8505dd0baa8Skettenis static void aborted_swaps_dcp_poff(struct apple_dcp *dcp, void *out, void *cookie)
8515dd0baa8Skettenis {
8525dd0baa8Skettenis 	struct iomfb_last_client_close_req last_client_req = {};
8535dd0baa8Skettenis 	iomfb_last_client_close(dcp, false, &last_client_req,
8545dd0baa8Skettenis 				last_client_closed_poff, cookie);
8555dd0baa8Skettenis }
8565dd0baa8Skettenis 
8575dd0baa8Skettenis void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp)
8585dd0baa8Skettenis {
8595dd0baa8Skettenis 	int ret, swap_id;
8605dd0baa8Skettenis 	struct iomfb_abort_swaps_dcp_req abort_req = {
8615dd0baa8Skettenis 		.client = {
8625dd0baa8Skettenis 			.flag2 = 1,
8635dd0baa8Skettenis 		},
8645dd0baa8Skettenis 	};
8655dd0baa8Skettenis 	struct dcp_swap_cookie *cookie;
8665dd0baa8Skettenis 	struct dcp_wait_cookie *poff_cookie;
8675dd0baa8Skettenis 	struct dcp_swap_start_req swap_req = { 0 };
8685dd0baa8Skettenis 	struct DCP_FW_NAME(dcp_swap_submit_req) *swap = &DCP_FW_UNION(dcp->swap);
8695dd0baa8Skettenis 
8705dd0baa8Skettenis 	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
8715dd0baa8Skettenis 	if (!cookie)
8725dd0baa8Skettenis 		return;
8735dd0baa8Skettenis 	init_completion(&cookie->done);
8745dd0baa8Skettenis 	kref_init(&cookie->refcount);
8755dd0baa8Skettenis 	/* increase refcount to ensure the receiver has a reference */
8765dd0baa8Skettenis 	kref_get(&cookie->refcount);
8775dd0baa8Skettenis 
8785dd0baa8Skettenis 	// clear surfaces
8795dd0baa8Skettenis 	memset(swap, 0, sizeof(*swap));
8805dd0baa8Skettenis 
8815dd0baa8Skettenis 	swap->swap.swap_enabled =
8825dd0baa8Skettenis 		swap->swap.swap_completed = IOMFB_SET_BACKGROUND | 0x7;
8835dd0baa8Skettenis 	swap->swap.bg_color = 0xFF000000;
8845dd0baa8Skettenis 
8855dd0baa8Skettenis 	/*
8865dd0baa8Skettenis 	 * Turn off the backlight. This matters because the DCP's idea of
8875dd0baa8Skettenis 	 * backlight brightness gets desynced after a power change, and it
8885dd0baa8Skettenis 	 * needs to be told it's going to turn off so it will consider the
8895dd0baa8Skettenis 	 * subsequent update on poweron an actual change and restore the
8905dd0baa8Skettenis 	 * brightness.
8915dd0baa8Skettenis 	 */
8925dd0baa8Skettenis 	if (dcp_has_panel(dcp)) {
8935dd0baa8Skettenis 		swap->swap.bl_unk = 1;
8945dd0baa8Skettenis 		swap->swap.bl_value = 0;
8955dd0baa8Skettenis 		swap->swap.bl_power = 0;
8965dd0baa8Skettenis 	}
8975dd0baa8Skettenis 
8985dd0baa8Skettenis 	for (int l = 0; l < SWAP_SURFACES; l++)
8995dd0baa8Skettenis 		swap->surf_null[l] = true;
9005dd0baa8Skettenis #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
9015dd0baa8Skettenis 	for (int l = 0; l < 5; l++)
9025dd0baa8Skettenis 		swap->surf2_null[l] = true;
9035dd0baa8Skettenis 	swap->unkU32Ptr_null = true;
9045dd0baa8Skettenis 	swap->unkU32out_null = true;
9055dd0baa8Skettenis #endif
9065dd0baa8Skettenis 
9075dd0baa8Skettenis 	dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie);
9085dd0baa8Skettenis 
9095dd0baa8Skettenis 	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50));
9105dd0baa8Skettenis 	swap_id = cookie->swap_id;
9115dd0baa8Skettenis 	kref_put(&cookie->refcount, release_swap_cookie);
9125dd0baa8Skettenis 	if (ret <= 0) {
9135dd0baa8Skettenis 		dcp->crashed = true;
9145dd0baa8Skettenis 		return;
9155dd0baa8Skettenis 	}
9165dd0baa8Skettenis 
91744a5d259Skettenis 	dev_dbg(dcp->dev, "%s: clear swap submitted: %u\n", __func__, swap_id);
9185dd0baa8Skettenis 
9195dd0baa8Skettenis 	poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL);
9205dd0baa8Skettenis 	if (!poff_cookie)
9215dd0baa8Skettenis 		return;
9225dd0baa8Skettenis 	init_completion(&poff_cookie->done);
9235dd0baa8Skettenis 	kref_init(&poff_cookie->refcount);
9245dd0baa8Skettenis 	/* increase refcount to ensure the receiver has a reference */
9255dd0baa8Skettenis 	kref_get(&poff_cookie->refcount);
9265dd0baa8Skettenis 
9275dd0baa8Skettenis 	iomfb_abort_swaps_dcp(dcp, false, &abort_req,
9285dd0baa8Skettenis 				aborted_swaps_dcp_poff, poff_cookie);
9295dd0baa8Skettenis 	ret = wait_for_completion_timeout(&poff_cookie->done,
9305dd0baa8Skettenis 					  msecs_to_jiffies(1000));
9315dd0baa8Skettenis 
9325dd0baa8Skettenis 	if (ret == 0)
93344a5d259Skettenis 		dev_warn(dcp->dev, "setPowerState(0) timeout %u ms\n", 1000);
9345dd0baa8Skettenis 	else if (ret > 0)
9355dd0baa8Skettenis 		dev_dbg(dcp->dev,
9365dd0baa8Skettenis 			"setPowerState(0) finished with %d ms to spare",
9375dd0baa8Skettenis 			jiffies_to_msecs(ret));
9385dd0baa8Skettenis 
9395dd0baa8Skettenis 	kref_put(&poff_cookie->refcount, release_wait_cookie);
9405dd0baa8Skettenis 
941*485c4124Sjsg 	dev_info(dcp->dev, "dcp_poweroff() done\n");
9425dd0baa8Skettenis }
9435dd0baa8Skettenis 
9445dd0baa8Skettenis static void last_client_closed_sleep(struct apple_dcp *dcp, void *out, void *cookie)
9455dd0baa8Skettenis {
9465dd0baa8Skettenis 	struct dcp_set_power_state_req power_req = {
9475dd0baa8Skettenis 		.unklong = 0,
9485dd0baa8Skettenis 	};
9495dd0baa8Skettenis 	dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, cookie);
9505dd0baa8Skettenis }
9515dd0baa8Skettenis 
9525dd0baa8Skettenis static void aborted_swaps_dcp_sleep(struct apple_dcp *dcp, void *out, void *cookie)
9535dd0baa8Skettenis {
9545dd0baa8Skettenis 	struct iomfb_last_client_close_req req = { 0 };
9555dd0baa8Skettenis 	iomfb_last_client_close(dcp, false, &req, last_client_closed_sleep, cookie);
9565dd0baa8Skettenis }
9575dd0baa8Skettenis 
9585dd0baa8Skettenis void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp)
9595dd0baa8Skettenis {
9605dd0baa8Skettenis 	int ret;
9615dd0baa8Skettenis 	struct iomfb_abort_swaps_dcp_req req = {
9625dd0baa8Skettenis 		.client = {
9635dd0baa8Skettenis 			.flag2 = 1,
9645dd0baa8Skettenis 		},
9655dd0baa8Skettenis 	};
9665dd0baa8Skettenis 
9675dd0baa8Skettenis 	struct dcp_wait_cookie *cookie;
9685dd0baa8Skettenis 
9695dd0baa8Skettenis 	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
9705dd0baa8Skettenis 	if (!cookie)
9715dd0baa8Skettenis 		return;
9725dd0baa8Skettenis 	init_completion(&cookie->done);
9735dd0baa8Skettenis 	kref_init(&cookie->refcount);
9745dd0baa8Skettenis 	/* increase refcount to ensure the receiver has a reference */
9755dd0baa8Skettenis 	kref_get(&cookie->refcount);
9765dd0baa8Skettenis 
9775dd0baa8Skettenis 	iomfb_abort_swaps_dcp(dcp, false, &req, aborted_swaps_dcp_sleep,
9785dd0baa8Skettenis 				cookie);
9795dd0baa8Skettenis 	ret = wait_for_completion_timeout(&cookie->done,
9805dd0baa8Skettenis 					  msecs_to_jiffies(1000));
9815dd0baa8Skettenis 
9825dd0baa8Skettenis 	if (ret == 0)
98344a5d259Skettenis 		dev_warn(dcp->dev, "setDCPPower(0) timeout %u ms\n", 1000);
9845dd0baa8Skettenis 
9855dd0baa8Skettenis 	kref_put(&cookie->refcount, release_wait_cookie);
986*485c4124Sjsg 	dev_info(dcp->dev, "dcp_sleep() done\n");
9875dd0baa8Skettenis }
9885dd0baa8Skettenis 
9895dd0baa8Skettenis static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected)
9905dd0baa8Skettenis {
9915dd0baa8Skettenis 	struct apple_connector *connector = dcp->connector;
9925dd0baa8Skettenis 
9935dd0baa8Skettenis 	/* DCP issues hotplug_gated callbacks after SetPowerState() calls on
9945dd0baa8Skettenis 	 * devices with display (macbooks, imacs). This must not result in
9955dd0baa8Skettenis 	 * connector state changes on DRM side. Some applications won't enable
9965dd0baa8Skettenis 	 * a CRTC with a connector in disconnected state. Weston after DPMS off
9975dd0baa8Skettenis 	 * is one example. dcp_is_main_display() returns true on devices with
9985dd0baa8Skettenis 	 * integrated display. Ignore the hotplug_gated() callbacks there.
9995dd0baa8Skettenis 	 */
10005dd0baa8Skettenis 	if (dcp->main_display)
10015dd0baa8Skettenis 		return;
10025dd0baa8Skettenis 
10035dd0baa8Skettenis 	if (dcp->during_modeset) {
10045dd0baa8Skettenis 		dev_info(dcp->dev,
10055dd0baa8Skettenis 			 "cb_hotplug() ignored during modeset connected:%llu\n",
10065dd0baa8Skettenis 			 *connected);
10075dd0baa8Skettenis 		return;
10085dd0baa8Skettenis 	}
10095dd0baa8Skettenis 
10105dd0baa8Skettenis 	dev_info(dcp->dev, "cb_hotplug() connected:%llu, valid_mode:%d\n",
10115dd0baa8Skettenis 		 *connected, dcp->valid_mode);
10125dd0baa8Skettenis 
10135dd0baa8Skettenis 	/* Hotplug invalidates mode. DRM doesn't always handle this. */
10145dd0baa8Skettenis 	if (!(*connected)) {
10155dd0baa8Skettenis 		dcp->valid_mode = false;
10165dd0baa8Skettenis 		/* after unplug swap will not complete until the next
10175dd0baa8Skettenis 		 * set_digital_out_mode */
10185dd0baa8Skettenis 		schedule_work(&dcp->vblank_wq);
10195dd0baa8Skettenis 	}
10205dd0baa8Skettenis 
10215dd0baa8Skettenis 	if (connector && connector->connected != !!(*connected)) {
10225dd0baa8Skettenis 		connector->connected = !!(*connected);
10235dd0baa8Skettenis 		dcp->valid_mode = false;
10245dd0baa8Skettenis 		schedule_work(&connector->hotplug_wq);
10255dd0baa8Skettenis 	}
10265dd0baa8Skettenis }
10275dd0baa8Skettenis 
10285dd0baa8Skettenis static void
10295dd0baa8Skettenis dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
10305dd0baa8Skettenis 				    struct dcp_swap_complete_intent_gated *info)
10315dd0baa8Skettenis {
10325dd0baa8Skettenis 	trace_iomfb_swap_complete_intent_gated(dcp, info->swap_id,
10335dd0baa8Skettenis 		info->width, info->height);
10345dd0baa8Skettenis }
10355dd0baa8Skettenis 
10365dd0baa8Skettenis static void
10375dd0baa8Skettenis dcpep_cb_abort_swap_ap_gated(struct apple_dcp *dcp, u32 *swap_id)
10385dd0baa8Skettenis {
10395dd0baa8Skettenis 	trace_iomfb_abort_swap_ap_gated(dcp, *swap_id);
10405dd0baa8Skettenis }
10415dd0baa8Skettenis 
10425dd0baa8Skettenis static struct dcpep_get_tiling_state_resp
10435dd0baa8Skettenis dcpep_cb_get_tiling_state(struct apple_dcp *dcp,
10445dd0baa8Skettenis 			  struct dcpep_get_tiling_state_req *req)
10455dd0baa8Skettenis {
10465dd0baa8Skettenis 	return (struct dcpep_get_tiling_state_resp){
10475dd0baa8Skettenis 		.value = 0,
10485dd0baa8Skettenis 		.ret = 1,
10495dd0baa8Skettenis 	};
10505dd0baa8Skettenis }
10515dd0baa8Skettenis 
10525dd0baa8Skettenis static u8 dcpep_cb_create_backlight_service(struct apple_dcp *dcp)
10535dd0baa8Skettenis {
10545dd0baa8Skettenis 	return dcp_has_panel(dcp);
10555dd0baa8Skettenis }
10565dd0baa8Skettenis 
10575dd0baa8Skettenis TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop);
10585dd0baa8Skettenis TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8);
10595dd0baa8Skettenis TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8);
10605dd0baa8Skettenis TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32);
10615dd0baa8Skettenis TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete,
10625dd0baa8Skettenis 	      struct DCP_FW_NAME(dc_swap_complete_resp));
10635dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop,
10645dd0baa8Skettenis 		 struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp);
10655dd0baa8Skettenis TRAMPOLINE_IN(trampoline_set_fx_prop, iomfbep_cb_set_fx_prop,
10665dd0baa8Skettenis 	      struct iomfb_set_fx_prop_req)
10675dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma,
10685dd0baa8Skettenis 		 struct dcp_map_buf_req, struct dcp_map_buf_resp);
10695dd0baa8Skettenis TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma,
10705dd0baa8Skettenis 	      struct dcp_unmap_buf_resp);
10715dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_sr_set_property_int, iomfbep_cb_sr_set_property_int,
10725dd0baa8Skettenis 		 struct iomfb_sr_set_property_int_req, u8);
10735dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer,
10745dd0baa8Skettenis 		 struct dcp_allocate_buffer_req,
10755dd0baa8Skettenis 		 struct dcp_allocate_buffer_resp);
10765dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical,
10775dd0baa8Skettenis 		 struct dcp_map_physical_req, struct dcp_map_physical_resp);
10785dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, u32,
10795dd0baa8Skettenis 		 u8);
10805dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg,
10815dd0baa8Skettenis 		 struct DCP_FW_NAME(dcp_map_reg_req),
10825dd0baa8Skettenis 		 struct DCP_FW_NAME(dcp_map_reg_resp));
10835dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data,
10845dd0baa8Skettenis 		 struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp);
10855dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8);
10865dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk,
10875dd0baa8Skettenis 		 struct dcp_set_dcpav_prop_chunk_req, u8);
10885dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end,
10895dd0baa8Skettenis 		 struct dcp_set_dcpav_prop_end_req, u8);
10905dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_allocate_bandwidth, dcpep_cb_allocate_bandwidth,
10915dd0baa8Skettenis 	       struct dcp_allocate_bandwidth_req, struct dcp_allocate_bandwidth_resp);
10925dd0baa8Skettenis TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth,
10935dd0baa8Skettenis 	       struct dcp_rt_bandwidth);
10945dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_set_frame_sync_props, dcpep_cb_set_frame_sync_props,
10955dd0baa8Skettenis 	       struct dcp_set_frame_sync_props_req,
10965dd0baa8Skettenis 	       struct dcp_set_frame_sync_props_resp);
10975dd0baa8Skettenis TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64);
10985dd0baa8Skettenis TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64);
10995dd0baa8Skettenis TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64);
11005dd0baa8Skettenis TRAMPOLINE_IN(trampoline_swap_complete_intent_gated,
11015dd0baa8Skettenis 	      dcpep_cb_swap_complete_intent_gated,
11025dd0baa8Skettenis 	      struct dcp_swap_complete_intent_gated);
11035dd0baa8Skettenis TRAMPOLINE_IN(trampoline_abort_swap_ap_gated, dcpep_cb_abort_swap_ap_gated, u32);
11045dd0baa8Skettenis TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated,
11055dd0baa8Skettenis 	      iomfbep_cb_enable_backlight_message_ap_gated, u8);
11065dd0baa8Skettenis TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish,
11075dd0baa8Skettenis 	      struct iomfb_property);
11085dd0baa8Skettenis TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state,
11095dd0baa8Skettenis 		 struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp);
11105dd0baa8Skettenis TRAMPOLINE_OUT(trampoline_create_backlight_service, dcpep_cb_create_backlight_service, u8);
11115dd0baa8Skettenis 
11125dd0baa8Skettenis /*
11135dd0baa8Skettenis  * Callback for swap requests. If a swap failed, we'll never get a swap
11145dd0baa8Skettenis  * complete event so we need to fake a vblank event early to avoid a hang.
11155dd0baa8Skettenis  */
11165dd0baa8Skettenis 
11175dd0baa8Skettenis static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie)
11185dd0baa8Skettenis {
11195dd0baa8Skettenis 	struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data;
11205dd0baa8Skettenis 
11215dd0baa8Skettenis 	if (resp->ret) {
11225dd0baa8Skettenis 		dev_err(dcp->dev, "swap failed! status %u\n", resp->ret);
11235dd0baa8Skettenis 		dcp_drm_crtc_vblank(dcp->crtc);
11245dd0baa8Skettenis 		return;
11255dd0baa8Skettenis 	}
11265dd0baa8Skettenis 
11275dd0baa8Skettenis 	while (!list_empty(&dcp->swapped_out_fbs)) {
11285dd0baa8Skettenis 		struct dcp_fb_reference *entry;
11295dd0baa8Skettenis 		entry = list_first_entry(&dcp->swapped_out_fbs,
11305dd0baa8Skettenis 					 struct dcp_fb_reference, head);
11315dd0baa8Skettenis 		if (entry->swap_id == dcp->last_swap_id)
11325dd0baa8Skettenis 			break;
11335dd0baa8Skettenis 		if (entry->fb)
11345dd0baa8Skettenis 			drm_framebuffer_put(entry->fb);
11355dd0baa8Skettenis 		list_del(&entry->head);
11365dd0baa8Skettenis 		kfree(entry);
11375dd0baa8Skettenis 	}
11385dd0baa8Skettenis }
11395dd0baa8Skettenis 
11405dd0baa8Skettenis static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie)
11415dd0baa8Skettenis {
11425dd0baa8Skettenis 	struct dcp_swap_start_resp *resp = data;
11435dd0baa8Skettenis 
11445dd0baa8Skettenis 	DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id;
11455dd0baa8Skettenis 
11465dd0baa8Skettenis 	trace_iomfb_swap_submit(dcp, resp->swap_id);
11475dd0baa8Skettenis 	dcp_swap_submit(dcp, false, &DCP_FW_UNION(dcp->swap), dcp_swapped, NULL);
11485dd0baa8Skettenis }
11495dd0baa8Skettenis 
11505dd0baa8Skettenis /* Helpers to modeset and swap, used to flush */
11515dd0baa8Skettenis static void do_swap(struct apple_dcp *dcp, void *data, void *cookie)
11525dd0baa8Skettenis {
11535dd0baa8Skettenis 	struct dcp_swap_start_req start_req = { 0 };
11545dd0baa8Skettenis 
11555dd0baa8Skettenis 	if (dcp->connector && dcp->connector->connected)
11565dd0baa8Skettenis 		dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL);
11575dd0baa8Skettenis 	else
11585dd0baa8Skettenis 		dcp_drm_crtc_vblank(dcp->crtc);
11595dd0baa8Skettenis }
11605dd0baa8Skettenis 
11615dd0baa8Skettenis static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data,
11625dd0baa8Skettenis 					  void *cookie)
11635dd0baa8Skettenis {
11645dd0baa8Skettenis 	struct dcp_wait_cookie *wait = cookie;
11655dd0baa8Skettenis 
11665dd0baa8Skettenis 	if (wait) {
11675dd0baa8Skettenis 		complete(&wait->done);
11685dd0baa8Skettenis 		kref_put(&wait->refcount, release_wait_cookie);
11695dd0baa8Skettenis 	}
11705dd0baa8Skettenis }
11715dd0baa8Skettenis 
11725dd0baa8Skettenis int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp,
11735dd0baa8Skettenis 			       struct drm_crtc_state *crtc_state)
11745dd0baa8Skettenis {
11755dd0baa8Skettenis 	struct dcp_display_mode *mode;
11765dd0baa8Skettenis 	struct dcp_wait_cookie *cookie;
11775dd0baa8Skettenis 	struct dcp_color_mode *cmode = NULL;
11785dd0baa8Skettenis 	int ret;
11795dd0baa8Skettenis 
11805dd0baa8Skettenis 	mode = lookup_mode(dcp, &crtc_state->mode);
11815dd0baa8Skettenis 	if (!mode) {
11825dd0baa8Skettenis 		dev_err(dcp->dev, "no match for " DRM_MODE_FMT "\n",
11835dd0baa8Skettenis 			DRM_MODE_ARG(&crtc_state->mode));
11845dd0baa8Skettenis 		return -EIO;
11855dd0baa8Skettenis 	}
11865dd0baa8Skettenis 
11875dd0baa8Skettenis 	dev_info(dcp->dev,
11885dd0baa8Skettenis 		 "set_digital_out_mode(color:%d timing:%d) " DRM_MODE_FMT "\n",
11895dd0baa8Skettenis 		 mode->color_mode_id, mode->timing_mode_id,
11905dd0baa8Skettenis 		 DRM_MODE_ARG(&crtc_state->mode));
11915dd0baa8Skettenis 	if (mode->color_mode_id == mode->sdr_rgb.id)
11925dd0baa8Skettenis 		cmode = &mode->sdr_rgb;
11935dd0baa8Skettenis 	else if (mode->color_mode_id == mode->sdr_444.id)
11945dd0baa8Skettenis 		cmode = &mode->sdr_444;
11955dd0baa8Skettenis 	else if (mode->color_mode_id == mode->sdr.id)
11965dd0baa8Skettenis 		cmode = &mode->sdr;
11975dd0baa8Skettenis 	else if (mode->color_mode_id == mode->best.id)
11985dd0baa8Skettenis 		cmode = &mode->best;
11995dd0baa8Skettenis 	if (cmode)
12005dd0baa8Skettenis 		dev_info(dcp->dev,
12015dd0baa8Skettenis 			"set_digital_out_mode() color mode depth:%hhu format:%u "
12025dd0baa8Skettenis 			"colorimetry:%u eotf:%u range:%u\n", cmode->depth,
12035dd0baa8Skettenis 			cmode->format, cmode->colorimetry, cmode->eotf,
12045dd0baa8Skettenis 			cmode->range);
12055dd0baa8Skettenis 
12065dd0baa8Skettenis 	dcp->mode = (struct dcp_set_digital_out_mode_req){
12075dd0baa8Skettenis 		.color_mode_id = mode->color_mode_id,
12085dd0baa8Skettenis 		.timing_mode_id = mode->timing_mode_id
12095dd0baa8Skettenis 	};
12105dd0baa8Skettenis 
12115dd0baa8Skettenis 	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
12125dd0baa8Skettenis 	if (!cookie) {
12135dd0baa8Skettenis 		return -ENOMEM;
12145dd0baa8Skettenis 	}
12155dd0baa8Skettenis 
12165dd0baa8Skettenis 	init_completion(&cookie->done);
12175dd0baa8Skettenis 	kref_init(&cookie->refcount);
12185dd0baa8Skettenis 	/* increase refcount to ensure the receiver has a reference */
12195dd0baa8Skettenis 	kref_get(&cookie->refcount);
12205dd0baa8Skettenis 
12215dd0baa8Skettenis 	dcp->during_modeset = true;
12225dd0baa8Skettenis 
12235dd0baa8Skettenis 	dcp_set_digital_out_mode(dcp, false, &dcp->mode,
12245dd0baa8Skettenis 				 complete_set_digital_out_mode, cookie);
12255dd0baa8Skettenis 
12265dd0baa8Skettenis 	/*
12275dd0baa8Skettenis 	 * The DCP firmware has an internal timeout of ~8 seconds for
12285dd0baa8Skettenis 	 * modesets. Add an extra 500ms to safe side that the modeset
12295dd0baa8Skettenis 	 * call has returned.
12305dd0baa8Skettenis 	 */
12315dd0baa8Skettenis 	ret = wait_for_completion_timeout(&cookie->done,
12325dd0baa8Skettenis 					  msecs_to_jiffies(8500));
12335dd0baa8Skettenis 
12345dd0baa8Skettenis 	kref_put(&cookie->refcount, release_wait_cookie);
12355dd0baa8Skettenis 	dcp->during_modeset = false;
12365dd0baa8Skettenis 	dev_info(dcp->dev, "set_digital_out_mode finished:%d\n", ret);
12375dd0baa8Skettenis 
12385dd0baa8Skettenis 	if (ret == 0) {
12395dd0baa8Skettenis 		dev_info(dcp->dev, "set_digital_out_mode timed out\n");
12405dd0baa8Skettenis 		return -EIO;
12415dd0baa8Skettenis 	} else if (ret < 0) {
12425dd0baa8Skettenis 		dev_info(dcp->dev,
12435dd0baa8Skettenis 			 "waiting on set_digital_out_mode failed:%d\n", ret);
12445dd0baa8Skettenis 		return -EIO;
12455dd0baa8Skettenis 
12465dd0baa8Skettenis 	} else if (ret > 0) {
12475dd0baa8Skettenis 		dev_dbg(dcp->dev,
12485dd0baa8Skettenis 			"set_digital_out_mode finished with %d to spare\n",
12495dd0baa8Skettenis 			jiffies_to_msecs(ret));
12505dd0baa8Skettenis 	}
12515dd0baa8Skettenis 	dcp->valid_mode = true;
12525dd0baa8Skettenis 
12535dd0baa8Skettenis 	return 0;
12545dd0baa8Skettenis }
12555dd0baa8Skettenis 
12565dd0baa8Skettenis void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state)
12575dd0baa8Skettenis {
12585dd0baa8Skettenis 	struct drm_plane *plane;
12595dd0baa8Skettenis 	struct drm_plane_state *new_state, *old_state;
12605dd0baa8Skettenis 	struct drm_crtc_state *crtc_state;
12615dd0baa8Skettenis 	struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_UNION(dcp->swap);
12625dd0baa8Skettenis 	int plane_idx, l;
12635dd0baa8Skettenis 	int has_surface = 0;
12645dd0baa8Skettenis 
12655dd0baa8Skettenis 	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
12665dd0baa8Skettenis 
12675dd0baa8Skettenis 	/* Reset to defaults */
12685dd0baa8Skettenis 	memset(req, 0, sizeof(*req));
12695dd0baa8Skettenis 	for (l = 0; l < SWAP_SURFACES; l++)
12705dd0baa8Skettenis 		req->surf_null[l] = true;
12715dd0baa8Skettenis #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
12725dd0baa8Skettenis 	for (l = 0; l < 5; l++)
12735dd0baa8Skettenis 		req->surf2_null[l] = true;
12745dd0baa8Skettenis 	req->unkU32Ptr_null = true;
12755dd0baa8Skettenis 	req->unkU32out_null = true;
12765dd0baa8Skettenis #endif
12775dd0baa8Skettenis 
12785dd0baa8Skettenis 	/*
12795dd0baa8Skettenis 	 * Clear all surfaces on startup. The boot framebuffer in surface 0
12805dd0baa8Skettenis 	 * sticks around.
12815dd0baa8Skettenis 	 */
12825dd0baa8Skettenis 	if (!dcp->surfaces_cleared) {
12835dd0baa8Skettenis 		req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0x7;
12845dd0baa8Skettenis 		req->swap.bg_color = 0xFF000000;
12855dd0baa8Skettenis 		dcp->surfaces_cleared = true;
12865dd0baa8Skettenis 	}
12875dd0baa8Skettenis 
12885dd0baa8Skettenis 	// Surface 0 has limitations at least on t600x.
12895dd0baa8Skettenis 	l = 1;
12905dd0baa8Skettenis 	for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) {
12915dd0baa8Skettenis 		struct drm_framebuffer *fb = new_state->fb;
12925dd0baa8Skettenis 		struct drm_gem_dma_object *obj;
12935dd0baa8Skettenis 		struct drm_rect src_rect;
12945dd0baa8Skettenis 		bool is_premultiplied = false;
12955dd0baa8Skettenis 
12965dd0baa8Skettenis 		/* skip planes not for this crtc */
12975dd0baa8Skettenis 		if (old_state->crtc != crtc && new_state->crtc != crtc)
12985dd0baa8Skettenis 			continue;
12995dd0baa8Skettenis 
13005dd0baa8Skettenis 		WARN_ON(l >= SWAP_SURFACES);
13015dd0baa8Skettenis 
13025dd0baa8Skettenis 		req->swap.swap_enabled |= BIT(l);
13035dd0baa8Skettenis 
13045dd0baa8Skettenis 		if (old_state->fb && fb != old_state->fb) {
13055dd0baa8Skettenis 			/*
13065dd0baa8Skettenis 			 * Race condition between a framebuffer unbind getting
13075dd0baa8Skettenis 			 * swapped out and GEM unreferencing a framebuffer. If
13085dd0baa8Skettenis 			 * we lose the race, the display gets IOVA faults and
13095dd0baa8Skettenis 			 * the DCP crashes. We need to extend the lifetime of
13105dd0baa8Skettenis 			 * the drm_framebuffer (and hence the GEM object) until
13115dd0baa8Skettenis 			 * after we get a swap complete for the swap unbinding
13125dd0baa8Skettenis 			 * it.
13135dd0baa8Skettenis 			 */
13145dd0baa8Skettenis 			struct dcp_fb_reference *entry =
13155dd0baa8Skettenis 				kzalloc(sizeof(*entry), GFP_KERNEL);
13165dd0baa8Skettenis 			if (entry) {
13175dd0baa8Skettenis 				entry->fb = old_state->fb;
13185dd0baa8Skettenis 				entry->swap_id = dcp->last_swap_id;
13195dd0baa8Skettenis 				list_add_tail(&entry->head,
13205dd0baa8Skettenis 					      &dcp->swapped_out_fbs);
13215dd0baa8Skettenis 			}
13225dd0baa8Skettenis 			drm_framebuffer_get(old_state->fb);
13235dd0baa8Skettenis 		}
13245dd0baa8Skettenis 
13255dd0baa8Skettenis 		if (!new_state->fb) {
13265dd0baa8Skettenis 			l += 1;
13275dd0baa8Skettenis 			continue;
13285dd0baa8Skettenis 		}
13295dd0baa8Skettenis 		req->surf_null[l] = false;
13305dd0baa8Skettenis 		has_surface = 1;
13315dd0baa8Skettenis 
13325dd0baa8Skettenis 		/*
13335dd0baa8Skettenis 		 * DCP doesn't support XBGR8 / XRGB8 natively. Blending as
13345dd0baa8Skettenis 		 * pre-multiplied alpha with a black background can be used as
13355dd0baa8Skettenis 		 * workaround for the bottommost plane.
13365dd0baa8Skettenis 		 */
13375dd0baa8Skettenis 		if (fb->format->format == DRM_FORMAT_XRGB8888 ||
13385dd0baa8Skettenis 		    fb->format->format == DRM_FORMAT_XBGR8888)
13395dd0baa8Skettenis 		    is_premultiplied = true;
13405dd0baa8Skettenis 
13415dd0baa8Skettenis 		drm_rect_fp_to_int(&src_rect, &new_state->src);
13425dd0baa8Skettenis 
13435dd0baa8Skettenis 		req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect);
13445dd0baa8Skettenis 		req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst);
13455dd0baa8Skettenis 
13465dd0baa8Skettenis 		if (dcp->notch_height > 0)
13475dd0baa8Skettenis 			req->swap.dst_rect[l].y += dcp->notch_height;
13485dd0baa8Skettenis 
13495dd0baa8Skettenis 		/* the obvious helper call drm_fb_dma_get_gem_addr() adjusts
13505dd0baa8Skettenis 		 * the address for source x/y offsets. Since IOMFB has a direct
13515dd0baa8Skettenis 		 * support source position prefer that.
13525dd0baa8Skettenis 		 */
13535dd0baa8Skettenis 		obj = drm_fb_dma_get_gem_obj(fb, 0);
13545dd0baa8Skettenis 		if (obj)
13555dd0baa8Skettenis 			req->surf_iova[l] = obj->dma_addr + fb->offsets[0];
13565dd0baa8Skettenis 
13575dd0baa8Skettenis 		req->surf[l] = (struct DCP_FW_NAME(dcp_surface)){
13585dd0baa8Skettenis 			.is_premultiplied = is_premultiplied,
13595dd0baa8Skettenis 			.format = drm_format_to_dcp(fb->format->format),
13605dd0baa8Skettenis 			.xfer_func = DCP_XFER_FUNC_SDR,
13615dd0baa8Skettenis 			.colorspace = DCP_COLORSPACE_NATIVE,
13625dd0baa8Skettenis 			.stride = fb->pitches[0],
13635dd0baa8Skettenis 			.width = fb->width,
13645dd0baa8Skettenis 			.height = fb->height,
13655dd0baa8Skettenis 			.buf_size = fb->height * fb->pitches[0],
13665dd0baa8Skettenis 			.surface_id = req->swap.surf_ids[l],
13675dd0baa8Skettenis 
13685dd0baa8Skettenis 			/* Only used for compressed or multiplanar surfaces */
13695dd0baa8Skettenis 			.pix_size = 1,
13705dd0baa8Skettenis 			.pel_w = 1,
13715dd0baa8Skettenis 			.pel_h = 1,
13725dd0baa8Skettenis 			.has_comp = 1,
13735dd0baa8Skettenis 			.has_planes = 1,
13745dd0baa8Skettenis 		};
13755dd0baa8Skettenis 
13765dd0baa8Skettenis 		l += 1;
13775dd0baa8Skettenis 	}
13785dd0baa8Skettenis 
13795dd0baa8Skettenis 	if (!has_surface && !crtc_state->color_mgmt_changed) {
13805dd0baa8Skettenis 		if (crtc_state->enable && crtc_state->active &&
13815dd0baa8Skettenis 		    !crtc_state->planes_changed) {
13825dd0baa8Skettenis 			schedule_work(&dcp->vblank_wq);
13835dd0baa8Skettenis 			return;
13845dd0baa8Skettenis 		}
13855dd0baa8Skettenis 
13865dd0baa8Skettenis 		/* Set black background */
13875dd0baa8Skettenis 		req->swap.swap_enabled |= IOMFB_SET_BACKGROUND;
13885dd0baa8Skettenis 		req->swap.bg_color = 0xFF000000;
13895dd0baa8Skettenis 		req->clear = 1;
13905dd0baa8Skettenis 	}
13915dd0baa8Skettenis 
13925dd0baa8Skettenis 	/* These fields should be set together */
13935dd0baa8Skettenis 	req->swap.swap_completed = req->swap.swap_enabled;
13945dd0baa8Skettenis 
13955dd0baa8Skettenis 	/* update brightness if changed */
13965dd0baa8Skettenis 	if (dcp_has_panel(dcp) && dcp->brightness.update) {
13975dd0baa8Skettenis 		req->swap.bl_unk = 1;
13985dd0baa8Skettenis 		req->swap.bl_value = dcp->brightness.dac;
13995dd0baa8Skettenis 		req->swap.bl_power = 0x40;
14005dd0baa8Skettenis 		dcp->brightness.update = false;
14015dd0baa8Skettenis 	}
14025dd0baa8Skettenis 
14035dd0baa8Skettenis 	if (crtc_state->color_mgmt_changed && crtc_state->ctm) {
14045dd0baa8Skettenis 		struct iomfb_set_matrix_req mat;
14055dd0baa8Skettenis 		struct drm_color_ctm *ctm = (struct drm_color_ctm *)crtc_state->ctm->data;
14065dd0baa8Skettenis 
14075dd0baa8Skettenis 		mat.unk_u32 = 9;
14085dd0baa8Skettenis 		mat.r[0] = ctm->matrix[0];
14095dd0baa8Skettenis 		mat.r[1] = ctm->matrix[1];
14105dd0baa8Skettenis 		mat.r[2] = ctm->matrix[2];
14115dd0baa8Skettenis 		mat.g[0] = ctm->matrix[3];
14125dd0baa8Skettenis 		mat.g[1] = ctm->matrix[4];
14135dd0baa8Skettenis 		mat.g[2] = ctm->matrix[5];
14145dd0baa8Skettenis 		mat.b[0] = ctm->matrix[6];
14155dd0baa8Skettenis 		mat.b[1] = ctm->matrix[7];
14165dd0baa8Skettenis 		mat.b[2] = ctm->matrix[8];
14175dd0baa8Skettenis 
14185dd0baa8Skettenis 		iomfb_set_matrix(dcp, false, &mat, do_swap, NULL);
14195dd0baa8Skettenis 	} else
14205dd0baa8Skettenis 		do_swap(dcp, NULL, NULL);
14215dd0baa8Skettenis }
14225dd0baa8Skettenis 
14235dd0baa8Skettenis static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie)
14245dd0baa8Skettenis {
14255dd0baa8Skettenis 	struct apple_connector *connector;
14265dd0baa8Skettenis 	int result = *(int *)out;
14275dd0baa8Skettenis 	dev_info(dcp->dev, "DCP is_main_display: %d\n", result);
14285dd0baa8Skettenis 
14295dd0baa8Skettenis 	dcp->main_display = result != 0;
14305dd0baa8Skettenis 
14315dd0baa8Skettenis 	connector = dcp->connector;
14325dd0baa8Skettenis 	if (connector) {
14335dd0baa8Skettenis 		connector->connected = dcp->nr_modes > 0;
14345dd0baa8Skettenis 		schedule_work(&connector->hotplug_wq);
14355dd0baa8Skettenis 	}
14365dd0baa8Skettenis 
14375dd0baa8Skettenis 	dcp->active = true;
14385dd0baa8Skettenis 	complete(&dcp->start_done);
14395dd0baa8Skettenis }
14405dd0baa8Skettenis 
14415dd0baa8Skettenis static void init_3(struct apple_dcp *dcp, void *out, void *cookie)
14425dd0baa8Skettenis {
14435dd0baa8Skettenis 	dcp_is_main_display(dcp, false, res_is_main_display, NULL);
14445dd0baa8Skettenis }
14455dd0baa8Skettenis 
14465dd0baa8Skettenis static void init_2(struct apple_dcp *dcp, void *out, void *cookie)
14475dd0baa8Skettenis {
14485dd0baa8Skettenis 	dcp_first_client_open(dcp, false, init_3, NULL);
14495dd0baa8Skettenis }
14505dd0baa8Skettenis 
14515dd0baa8Skettenis static void init_1(struct apple_dcp *dcp, void *out, void *cookie)
14525dd0baa8Skettenis {
14535dd0baa8Skettenis 	u32 val = 0;
14545dd0baa8Skettenis 	dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL);
14555dd0baa8Skettenis }
14565dd0baa8Skettenis 
14575dd0baa8Skettenis static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie)
14585dd0baa8Skettenis {
14595dd0baa8Skettenis 	struct iomfb_get_color_remap_mode_req color_remap =
14605dd0baa8Skettenis 		(struct iomfb_get_color_remap_mode_req){
14615dd0baa8Skettenis 			.mode = 6,
14625dd0baa8Skettenis 		};
14635dd0baa8Skettenis 
14645dd0baa8Skettenis 	dev_info(dcp->dev, "DCP booted\n");
14655dd0baa8Skettenis 
14665dd0baa8Skettenis 	iomfb_get_color_remap_mode(dcp, false, &color_remap, init_1, cookie);
14675dd0baa8Skettenis }
14685dd0baa8Skettenis 
14695dd0baa8Skettenis void DCP_FW_NAME(iomfb_shutdown)(struct apple_dcp *dcp)
14705dd0baa8Skettenis {
14715dd0baa8Skettenis 	struct dcp_set_power_state_req req = {
14725dd0baa8Skettenis 		/* defaults are ok */
14735dd0baa8Skettenis 	};
14745dd0baa8Skettenis 
14755dd0baa8Skettenis 	dcp_set_power_state(dcp, false, &req, NULL, NULL);
14765dd0baa8Skettenis }
1477