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, ¶m, 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