src/sys/dev/pci/drm/apple/iomfb_template.c

1477 lines
40 KiB
C

// SPDX-License-Identifier: GPL-2.0-only OR MIT
/*
* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io>
* Copyright The Asahi Linux Contributors
*/
#include <linux/align.h>
#include <linux/bitmap.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/iommu.h>
#include <linux/kref.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include "dcp.h"
#include "dcp-internal.h"
#include "iomfb.h"
#include "iomfb_internal.h"
#include "parser.h"
#include "trace.h"
#include "version_utils.h"
/* Register defines used in bandwidth setup structure */
#define REG_DOORBELL_BIT(idx) (2 + (idx))
struct dcp_wait_cookie {
struct kref refcount;
struct completion done;
};
static void release_wait_cookie(struct kref *ref)
{
struct dcp_wait_cookie *cookie;
cookie = container_of(ref, struct dcp_wait_cookie, refcount);
kfree(cookie);
}
DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32);
DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32);
DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32);
IOMFB_THUNK_INOUT(set_matrix);
IOMFB_THUNK_INOUT(get_color_remap_mode);
IOMFB_THUNK_INOUT(last_client_close);
IOMFB_THUNK_INOUT(abort_swaps_dcp);
DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit,
struct DCP_FW_NAME(dcp_swap_submit_req),
struct DCP_FW_NAME(dcp_swap_submit_resp));
DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req,
struct dcp_swap_start_resp);
DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state,
struct dcp_set_power_state_req,
struct dcp_set_power_state_resp);
DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode,
struct dcp_set_digital_out_mode_req, u32);
DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32);
DCP_THUNK_OUT(dcp_set_display_refresh_properties,
dcpep_set_display_refresh_properties, u32);
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
DCP_THUNK_INOUT(dcp_late_init_signal, dcpep_late_init_signal, u32, u32);
#else
DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32);
#endif
DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32);
DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32);
DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32);
DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits);
DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb);
DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open);
DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp,
struct dcp_set_parameter_dcp, u32);
DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings,
dcpep_enable_disable_video_power_savings, u32, int);
DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32);
/* DCP callback handlers */
static void dcpep_cb_nop(struct apple_dcp *dcp)
{
/* No operation */
}
static u8 dcpep_cb_true(struct apple_dcp *dcp)
{
return true;
}
static u8 dcpep_cb_false(struct apple_dcp *dcp)
{
return false;
}
static u32 dcpep_cb_zero(struct apple_dcp *dcp)
{
return 0;
}
static void dcpep_cb_swap_complete(struct apple_dcp *dcp,
struct DCP_FW_NAME(dc_swap_complete_resp) *resp)
{
trace_iomfb_swap_complete(dcp, resp->swap_id);
dcp->last_swap_id = resp->swap_id;
dcp_drm_crtc_vblank(dcp->crtc);
}
/* special */
static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, void *cookie)
{
// ack D100 cb_match_pmu_service
dcp_ack(dcp, DCP_CONTEXT_CB);
}
static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in)
{
trace_iomfb_callback(dcp, tag, __func__);
iomfb_a358_vi_set_temperature_hint(dcp, false,
complete_vi_set_temperature_hint,
NULL);
// return false for deferred ACK
return false;
}
static void complete_pmu_service_matched(struct apple_dcp *dcp, void *out, void *cookie)
{
struct dcp_channel *ch = &dcp->ch_cb;
u8 *succ = ch->output[ch->depth - 1];
*succ = true;
// ack D206 cb_match_pmu_service_2
dcp_ack(dcp, DCP_CONTEXT_CB);
}
static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void *out, void *in)
{
trace_iomfb_callback(dcp, tag, __func__);
iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched,
out);
// return false for deferred ACK
return false;
}
static void complete_backlight_service_matched(struct apple_dcp *dcp, void *out, void *cookie)
{
struct dcp_channel *ch = &dcp->ch_cb;
u8 *succ = ch->output[ch->depth - 1];
*succ = true;
// ack D206 cb_match_backlight_service
dcp_ack(dcp, DCP_CONTEXT_CB);
}
static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, void *out, void *in)
{
trace_iomfb_callback(dcp, tag, __func__);
if (!dcp_has_panel(dcp)) {
u8 *succ = out;
*succ = true;
return true;
}
iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out);
// return false for deferred ACK
return false;
}
static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *prop)
{
switch (prop->id) {
case IOMFB_PROPERTY_NITS:
{
if (dcp_has_panel(dcp)) {
dcp->brightness.nits = prop->value / dcp->brightness.scale;
/* notify backlight device of the initial brightness */
if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0)
schedule_work(&dcp->bl_register_wq);
trace_iomfb_brightness(dcp, prop->value);
}
break;
}
default:
dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value);
}
}
static struct dcp_get_uint_prop_resp
dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req)
{
struct dcp_get_uint_prop_resp resp = (struct dcp_get_uint_prop_resp){
.value = 0
};
if (dcp->panel.has_mini_led &&
memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */
if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) {
/*
* TODO: value from j314c, find out if it is temperature in
* centigrade C and which temperature sensor reports it
*/
resp.value = 3029;
resp.ret = true;
}
}
return resp;
}
static u8 iomfbep_cb_sr_set_property_int(struct apple_dcp *dcp,
struct iomfb_sr_set_property_int_req *req)
{
if (memcmp(req->obj, "FMOI", sizeof(req->obj)) == 0) { /* "IOMF */
if (strncmp(req->key, "Brightness_Scale", sizeof(req->key)) == 0) {
if (!req->value_null)
dcp->brightness.scale = req->value;
}
}
return 1;
}
static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req)
{
// TODO: trace this, see if there properties which needs to used later
}
/*
* Callback to map a buffer allocated with allocate_buf for PIODMA usage.
* PIODMA is separate from the main DCP and uses own IOVA space on a dedicated
* stream of the display DART, rather than the expected DCP DART.
*/
static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp,
struct dcp_map_buf_req *req)
{
struct dcp_mem_descriptor *memdesc;
struct sg_table *map;
ssize_t ret;
if (req->buffer >= ARRAY_SIZE(dcp->memdesc))
goto reject;
memdesc = &dcp->memdesc[req->buffer];
map = &memdesc->map;
if (!map->sgl)
goto reject;
/* use the piodma iommu domain to map against the right IOMMU */
ret = iommu_map_sgtable(dcp->iommu_dom, memdesc->dva, map,
IOMMU_READ | IOMMU_WRITE);
/* HACK: expect size to be 16K aligned since the iommu API only maps
* full pages
*/
if (ret < 0 || ret != ALIGN(memdesc->size, SZ_16K)) {
dev_err(dcp->dev, "iommu_map_sgtable() returned %zd instead of expected buffer size of %zu\n", ret, memdesc->size);
goto reject;
}
return (struct dcp_map_buf_resp){ .dva = memdesc->dva };
reject:
dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n",
req->buffer);
return (struct dcp_map_buf_resp){ .ret = EINVAL };
}
static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp,
struct dcp_unmap_buf_resp *resp)
{
struct dcp_mem_descriptor *memdesc;
if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) {
dev_warn(dcp->dev, "unmap request for out of range buffer %llu\n",
resp->buffer);
return;
}
memdesc = &dcp->memdesc[resp->buffer];
if (!memdesc->buf) {
dev_warn(dcp->dev,
"unmap for non-mapped buffer %llu iova:0x%08llx\n",
resp->buffer, resp->dva);
return;
}
if (memdesc->dva != resp->dva) {
dev_warn(dcp->dev, "unmap buffer %llu address mismatch "
"memdesc.dva:%llx dva:%llx\n", resp->buffer,
memdesc->dva, resp->dva);
return;
}
/* use the piodma iommu domain to unmap from the right IOMMU */
iommu_unmap(dcp->iommu_dom, memdesc->dva, memdesc->size);
}
/*
* Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be
* physically contiguous, however we should save the sgtable in case the
* buffer needs to be later mapped for PIODMA.
*/
static struct dcp_allocate_buffer_resp
dcpep_cb_allocate_buffer(struct apple_dcp *dcp,
struct dcp_allocate_buffer_req *req)
{
struct dcp_allocate_buffer_resp resp = { 0 };
struct dcp_mem_descriptor *memdesc;
size_t size;
u32 id;
resp.dva_size = ALIGN(req->size, 4096);
resp.mem_desc_id =
find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) {
dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring\n");
resp.dva_size = 0;
resp.mem_desc_id = 0;
return resp;
}
id = resp.mem_desc_id;
set_bit(id, dcp->memdesc_map);
memdesc = &dcp->memdesc[id];
memdesc->size = resp.dva_size;
/* HACK: align size to 16K since the iommu API only maps full pages */
size = ALIGN(resp.dva_size, SZ_16K);
memdesc->buf = dma_alloc_coherent(dcp->dev, size,
&memdesc->dva, GFP_KERNEL);
dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva,
size);
resp.dva = memdesc->dva;
return resp;
}
static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id)
{
struct dcp_mem_descriptor *memdesc;
u32 id = *mem_desc_id;
if (id >= DCP_MAX_MAPPINGS) {
dev_warn(dcp->dev,
"unmap request for out of range mem_desc_id %u", id);
return 0;
}
if (!test_and_clear_bit(id, dcp->memdesc_map)) {
dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u\n",
id);
return 0;
}
memdesc = &dcp->memdesc[id];
if (memdesc->buf) {
dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf,
memdesc->dva);
memdesc->buf = NULL;
memset(&memdesc->map, 0, sizeof(memdesc->map));
} else {
memdesc->reg = 0;
}
memdesc->size = 0;
return 1;
}
/* Validate that the specified region is a display register */
static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end)
{
int i;
for (i = 0; i < dcp->nr_disp_registers; ++i) {
struct resource *r = dcp->disp_registers[i];
if ((start >= r->start) && (end <= r->end))
return true;
}
return false;
}
/*
* Map contiguous physical memory into the DCP's address space. The firmware
* uses this to map the display registers we advertise in
* sr_map_device_memory_with_index, so we bounds check against that to guard
* safe against malicious coprocessors.
*/
static struct dcp_map_physical_resp
dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
{
int size = ALIGN(req->size, 4096);
dma_addr_t dva;
u32 id;
if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) {
dev_err(dcp->dev, "refusing to map phys address %llx size %llx\n",
req->paddr, req->size);
return (struct dcp_map_physical_resp){};
}
id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
set_bit(id, dcp->memdesc_map);
dcp->memdesc[id].size = size;
dcp->memdesc[id].reg = req->paddr;
dva = dma_map_resource(dcp->dev, req->paddr, size, DMA_BIDIRECTIONAL, 0);
WARN_ON(dva == DMA_MAPPING_ERROR);
return (struct dcp_map_physical_resp){
.dva_size = size,
.mem_desc_id = id,
.dva = dva,
};
}
static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp)
{
return clk_get_rate(dcp->clk);
}
static struct DCP_FW_NAME(dcp_map_reg_resp) dcpep_cb_map_reg(struct apple_dcp *dcp,
struct DCP_FW_NAME(dcp_map_reg_req) *req)
{
if (req->index >= dcp->nr_disp_registers) {
dev_warn(dcp->dev, "attempted to read invalid reg index %u\n",
req->index);
return (struct DCP_FW_NAME(dcp_map_reg_resp)){ .ret = 1 };
} else {
struct resource *rsrc = dcp->disp_registers[req->index];
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
dma_addr_t dva = dma_map_resource(dcp->dev, rsrc->start, resource_size(rsrc),
DMA_BIDIRECTIONAL, 0);
WARN_ON(dva == DMA_MAPPING_ERROR);
#endif
return (struct DCP_FW_NAME(dcp_map_reg_resp)){
.addr = rsrc->start,
.length = resource_size(rsrc),
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
.dva = dva,
#endif
};
}
}
static struct dcp_read_edt_data_resp
dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req)
{
return (struct dcp_read_edt_data_resp){
.value[0] = req->value[0],
.ret = 0,
};
}
static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp,
u8 *enabled)
{
/*
* update backlight brightness on next swap, on non mini-LED displays
* DCP seems to set an invalid iDAC value after coming out of DPMS.
* syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range"
*/
dcp->brightness.update = true;
schedule_work(&dcp->bl_update_wq);
}
/* Chunked data transfer for property dictionaries */
static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length)
{
if (dcp->chunks.data != NULL) {
dev_warn(dcp->dev, "ignoring spurious transfer start\n");
return false;
}
dcp->chunks.length = *length;
dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL);
if (!dcp->chunks.data) {
dev_warn(dcp->dev, "failed to allocate chunks\n");
return false;
}
return true;
}
static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp,
struct dcp_set_dcpav_prop_chunk_req *req)
{
if (!dcp->chunks.data) {
dev_warn(dcp->dev, "ignoring spurious chunk\n");
return false;
}
if (req->offset + req->length > dcp->chunks.length) {
dev_warn(dcp->dev, "ignoring overflowing chunk\n");
return false;
}
memcpy(dcp->chunks.data + req->offset, req->data, req->length);
return true;
}
static bool dcpep_process_chunks(struct apple_dcp *dcp,
struct dcp_set_dcpav_prop_end_req *req)
{
struct dcp_parse_ctx ctx;
int ret;
if (!dcp->chunks.data) {
dev_warn(dcp->dev, "ignoring spurious end\n");
return false;
}
/* used just as opaque pointer for tracing */
ctx.dcp = dcp;
ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx);
if (ret) {
dev_warn(dcp->dev, "bad header on dcpav props\n");
return false;
}
if (!strcmp(req->key, "TimingElements")) {
dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes,
dcp->width_mm, dcp->height_mm,
dcp->notch_height);
if (IS_ERR(dcp->modes)) {
dev_warn(dcp->dev, "failed to parse modes\n");
dcp->modes = NULL;
dcp->nr_modes = 0;
return false;
}
if (dcp->nr_modes == 0)
dev_warn(dcp->dev, "TimingElements without valid modes!\n");
} else if (!strcmp(req->key, "DisplayAttributes")) {
ret = parse_display_attributes(&ctx, &dcp->width_mm,
&dcp->height_mm);
if (ret) {
dev_warn(dcp->dev, "failed to parse display attribs\n");
return false;
}
dcp_set_dimensions(dcp);
}
return true;
}
static u8 dcpep_cb_prop_end(struct apple_dcp *dcp,
struct dcp_set_dcpav_prop_end_req *req)
{
u8 resp = dcpep_process_chunks(dcp, req);
/* Reset for the next transfer */
devm_kfree(dcp->dev, dcp->chunks.data);
dcp->chunks.data = NULL;
return resp;
}
/* Boot sequence */
static void boot_done(struct apple_dcp *dcp, void *out, void *cookie)
{
struct dcp_channel *ch = &dcp->ch_cb;
u8 *succ = ch->output[ch->depth - 1];
dev_dbg(dcp->dev, "boot done\n");
*succ = true;
dcp_ack(dcp, DCP_CONTEXT_CB);
}
static void boot_5(struct apple_dcp *dcp, void *out, void *cookie)
{
dcp_set_display_refresh_properties(dcp, false, boot_done, NULL);
}
static void boot_4(struct apple_dcp *dcp, void *out, void *cookie)
{
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
u32 v_true = 1;
dcp_late_init_signal(dcp, false, &v_true, boot_5, NULL);
#else
dcp_late_init_signal(dcp, false, boot_5, NULL);
#endif
}
static void boot_3(struct apple_dcp *dcp, void *out, void *cookie)
{
u32 v_true = true;
dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL);
}
static void boot_2(struct apple_dcp *dcp, void *out, void *cookie)
{
dcp_setup_video_limits(dcp, false, boot_3, NULL);
}
static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie)
{
dcp_create_default_fb(dcp, false, boot_2, NULL);
}
/* Use special function signature to defer the ACK */
static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in)
{
trace_iomfb_callback(dcp, tag, __func__);
dcp_set_create_dfb(dcp, false, boot_1_5, NULL);
return false;
}
static struct dcp_allocate_bandwidth_resp dcpep_cb_allocate_bandwidth(struct apple_dcp *dcp,
struct dcp_allocate_bandwidth_req *req)
{
return (struct dcp_allocate_bandwidth_resp){
.unk1 = req->unk1,
.unk2 = req->unk2,
.ret = 1,
};
}
static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp)
{
struct dcp_rt_bandwidth rt_bw = (struct dcp_rt_bandwidth){
.reg_scratch = 0,
.reg_doorbell = 0,
.doorbell_bit = 0,
};
if (dcp->disp_bw_scratch_index) {
u32 offset = dcp->disp_bw_scratch_offset;
u32 index = dcp->disp_bw_scratch_index;
rt_bw.reg_scratch = dcp->disp_registers[index]->start + offset;
}
if (dcp->disp_bw_doorbell_index) {
u32 index = dcp->disp_bw_doorbell_index;
rt_bw.reg_doorbell = dcp->disp_registers[index]->start;
rt_bw.doorbell_bit = REG_DOORBELL_BIT(dcp->index);
/*
* This is most certainly not padding. t8103-dcp crashes without
* setting this immediately during modeset on 12.3 and 13.5
* firmware.
*/
rt_bw.padding[3] = 0x4;
}
return rt_bw;
}
static struct dcp_set_frame_sync_props_resp
dcpep_cb_set_frame_sync_props(struct apple_dcp *dcp,
struct dcp_set_frame_sync_props_req *req)
{
return (struct dcp_set_frame_sync_props_resp){};
}
/* Callback to get the current time as milliseconds since the UNIX epoch */
static u64 dcpep_cb_get_time(struct apple_dcp *dcp)
{
return ktime_to_ms(ktime_get_real());
}
struct dcp_swap_cookie {
struct kref refcount;
struct completion done;
u32 swap_id;
};
static void release_swap_cookie(struct kref *ref)
{
struct dcp_swap_cookie *cookie;
cookie = container_of(ref, struct dcp_swap_cookie, refcount);
kfree(cookie);
}
static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie)
{
struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data;
if (cookie) {
struct dcp_swap_cookie *info = cookie;
complete(&info->done);
kref_put(&info->refcount, release_swap_cookie);
}
if (resp->ret) {
dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret);
dcp_drm_crtc_vblank(dcp->crtc);
return;
}
while (!list_empty(&dcp->swapped_out_fbs)) {
struct dcp_fb_reference *entry;
entry = list_first_entry(&dcp->swapped_out_fbs,
struct dcp_fb_reference, head);
if (entry->swap_id == dcp->last_swap_id)
break;
if (entry->fb)
drm_framebuffer_put(entry->fb);
list_del(&entry->head);
kfree(entry);
}
}
static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data,
void *cookie)
{
struct dcp_swap_start_resp *resp = data;
DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id;
if (cookie) {
struct dcp_swap_cookie *info = cookie;
info->swap_id = resp->swap_id;
}
dcp_swap_submit(dcp, false, &DCP_FW_UNION(dcp->swap), dcp_swap_cleared, cookie);
}
static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie)
{
struct dcp_wait_cookie *wait = cookie;
if (wait) {
complete(&wait->done);
kref_put(&wait->refcount, release_wait_cookie);
}
}
static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cookie)
{
struct dcp_set_power_state_req req = {
.unklong = 1,
};
dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie);
}
static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie)
{
struct dcp_set_parameter_dcp param = {
.param = 14,
.value = { 0 },
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
.count = 3,
#else
.count = 1,
#endif
};
dcp_set_parameter_dcp(dcp, false, &param, dcp_on_set_power_state, cookie);
}
void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp)
{
struct dcp_wait_cookie *cookie;
int ret;
u32 handle;
dev_err(dcp->dev, "dcp_poweron() starting\n");
cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
if (!cookie)
return;
init_completion(&cookie->done);
kref_init(&cookie->refcount);
/* increase refcount to ensure the receiver has a reference */
kref_get(&cookie->refcount);
if (dcp->main_display) {
handle = 0;
dcp_set_display_device(dcp, false, &handle, dcp_on_set_power_state,
cookie);
} else {
handle = 2;
dcp_set_display_device(dcp, false, &handle,
dcp_on_set_parameter, cookie);
}
ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500));
if (ret == 0)
dev_warn(dcp->dev, "wait for power timed out\n");
kref_put(&cookie->refcount, release_wait_cookie);;
/* Force a brightness update after poweron, to restore the brightness */
dcp->brightness.update = true;
}
static void complete_set_powerstate(struct apple_dcp *dcp, void *out,
void *cookie)
{
struct dcp_wait_cookie *wait = cookie;
if (wait) {
complete(&wait->done);
kref_put(&wait->refcount, release_wait_cookie);
}
}
static void last_client_closed_poff(struct apple_dcp *dcp, void *out, void *cookie)
{
struct dcp_set_power_state_req power_req = {
.unklong = 0,
};
dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate,
cookie);
}
static void aborted_swaps_dcp_poff(struct apple_dcp *dcp, void *out, void *cookie)
{
struct iomfb_last_client_close_req last_client_req = {};
iomfb_last_client_close(dcp, false, &last_client_req,
last_client_closed_poff, cookie);
}
void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp)
{
int ret, swap_id;
struct iomfb_abort_swaps_dcp_req abort_req = {
.client = {
.flag2 = 1,
},
};
struct dcp_swap_cookie *cookie;
struct dcp_wait_cookie *poff_cookie;
struct dcp_swap_start_req swap_req = { 0 };
struct DCP_FW_NAME(dcp_swap_submit_req) *swap = &DCP_FW_UNION(dcp->swap);
cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
if (!cookie)
return;
init_completion(&cookie->done);
kref_init(&cookie->refcount);
/* increase refcount to ensure the receiver has a reference */
kref_get(&cookie->refcount);
// clear surfaces
memset(swap, 0, sizeof(*swap));
swap->swap.swap_enabled =
swap->swap.swap_completed = IOMFB_SET_BACKGROUND | 0x7;
swap->swap.bg_color = 0xFF000000;
/*
* Turn off the backlight. This matters because the DCP's idea of
* backlight brightness gets desynced after a power change, and it
* needs to be told it's going to turn off so it will consider the
* subsequent update on poweron an actual change and restore the
* brightness.
*/
if (dcp_has_panel(dcp)) {
swap->swap.bl_unk = 1;
swap->swap.bl_value = 0;
swap->swap.bl_power = 0;
}
for (int l = 0; l < SWAP_SURFACES; l++)
swap->surf_null[l] = true;
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
for (int l = 0; l < 5; l++)
swap->surf2_null[l] = true;
swap->unkU32Ptr_null = true;
swap->unkU32out_null = true;
#endif
dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie);
ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50));
swap_id = cookie->swap_id;
kref_put(&cookie->refcount, release_swap_cookie);
if (ret <= 0) {
dcp->crashed = true;
return;
}
dev_dbg(dcp->dev, "%s: clear swap submitted: %u\n", __func__, swap_id);
poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL);
if (!poff_cookie)
return;
init_completion(&poff_cookie->done);
kref_init(&poff_cookie->refcount);
/* increase refcount to ensure the receiver has a reference */
kref_get(&poff_cookie->refcount);
iomfb_abort_swaps_dcp(dcp, false, &abort_req,
aborted_swaps_dcp_poff, poff_cookie);
ret = wait_for_completion_timeout(&poff_cookie->done,
msecs_to_jiffies(1000));
if (ret == 0)
dev_warn(dcp->dev, "setPowerState(0) timeout %u ms\n", 1000);
else if (ret > 0)
dev_dbg(dcp->dev,
"setPowerState(0) finished with %d ms to spare",
jiffies_to_msecs(ret));
kref_put(&poff_cookie->refcount, release_wait_cookie);
dev_err(dcp->dev, "dcp_poweroff() done\n");
}
static void last_client_closed_sleep(struct apple_dcp *dcp, void *out, void *cookie)
{
struct dcp_set_power_state_req power_req = {
.unklong = 0,
};
dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, cookie);
}
static void aborted_swaps_dcp_sleep(struct apple_dcp *dcp, void *out, void *cookie)
{
struct iomfb_last_client_close_req req = { 0 };
iomfb_last_client_close(dcp, false, &req, last_client_closed_sleep, cookie);
}
void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp)
{
int ret;
struct iomfb_abort_swaps_dcp_req req = {
.client = {
.flag2 = 1,
},
};
struct dcp_wait_cookie *cookie;
cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
if (!cookie)
return;
init_completion(&cookie->done);
kref_init(&cookie->refcount);
/* increase refcount to ensure the receiver has a reference */
kref_get(&cookie->refcount);
iomfb_abort_swaps_dcp(dcp, false, &req, aborted_swaps_dcp_sleep,
cookie);
ret = wait_for_completion_timeout(&cookie->done,
msecs_to_jiffies(1000));
if (ret == 0)
dev_warn(dcp->dev, "setDCPPower(0) timeout %u ms\n", 1000);
kref_put(&cookie->refcount, release_wait_cookie);
dev_err(dcp->dev, "dcp_sleep() done\n");
}
static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected)
{
struct apple_connector *connector = dcp->connector;
/* DCP issues hotplug_gated callbacks after SetPowerState() calls on
* devices with display (macbooks, imacs). This must not result in
* connector state changes on DRM side. Some applications won't enable
* a CRTC with a connector in disconnected state. Weston after DPMS off
* is one example. dcp_is_main_display() returns true on devices with
* integrated display. Ignore the hotplug_gated() callbacks there.
*/
if (dcp->main_display)
return;
if (dcp->during_modeset) {
dev_info(dcp->dev,
"cb_hotplug() ignored during modeset connected:%llu\n",
*connected);
return;
}
dev_info(dcp->dev, "cb_hotplug() connected:%llu, valid_mode:%d\n",
*connected, dcp->valid_mode);
/* Hotplug invalidates mode. DRM doesn't always handle this. */
if (!(*connected)) {
dcp->valid_mode = false;
/* after unplug swap will not complete until the next
* set_digital_out_mode */
schedule_work(&dcp->vblank_wq);
}
if (connector && connector->connected != !!(*connected)) {
connector->connected = !!(*connected);
dcp->valid_mode = false;
schedule_work(&connector->hotplug_wq);
}
}
static void
dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
struct dcp_swap_complete_intent_gated *info)
{
trace_iomfb_swap_complete_intent_gated(dcp, info->swap_id,
info->width, info->height);
}
static void
dcpep_cb_abort_swap_ap_gated(struct apple_dcp *dcp, u32 *swap_id)
{
trace_iomfb_abort_swap_ap_gated(dcp, *swap_id);
}
static struct dcpep_get_tiling_state_resp
dcpep_cb_get_tiling_state(struct apple_dcp *dcp,
struct dcpep_get_tiling_state_req *req)
{
return (struct dcpep_get_tiling_state_resp){
.value = 0,
.ret = 1,
};
}
static u8 dcpep_cb_create_backlight_service(struct apple_dcp *dcp)
{
return dcp_has_panel(dcp);
}
TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop);
TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8);
TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8);
TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32);
TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete,
struct DCP_FW_NAME(dc_swap_complete_resp));
TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop,
struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp);
TRAMPOLINE_IN(trampoline_set_fx_prop, iomfbep_cb_set_fx_prop,
struct iomfb_set_fx_prop_req)
TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma,
struct dcp_map_buf_req, struct dcp_map_buf_resp);
TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma,
struct dcp_unmap_buf_resp);
TRAMPOLINE_INOUT(trampoline_sr_set_property_int, iomfbep_cb_sr_set_property_int,
struct iomfb_sr_set_property_int_req, u8);
TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer,
struct dcp_allocate_buffer_req,
struct dcp_allocate_buffer_resp);
TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical,
struct dcp_map_physical_req, struct dcp_map_physical_resp);
TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, u32,
u8);
TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg,
struct DCP_FW_NAME(dcp_map_reg_req),
struct DCP_FW_NAME(dcp_map_reg_resp));
TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data,
struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp);
TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8);
TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk,
struct dcp_set_dcpav_prop_chunk_req, u8);
TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end,
struct dcp_set_dcpav_prop_end_req, u8);
TRAMPOLINE_INOUT(trampoline_allocate_bandwidth, dcpep_cb_allocate_bandwidth,
struct dcp_allocate_bandwidth_req, struct dcp_allocate_bandwidth_resp);
TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth,
struct dcp_rt_bandwidth);
TRAMPOLINE_INOUT(trampoline_set_frame_sync_props, dcpep_cb_set_frame_sync_props,
struct dcp_set_frame_sync_props_req,
struct dcp_set_frame_sync_props_resp);
TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64);
TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64);
TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64);
TRAMPOLINE_IN(trampoline_swap_complete_intent_gated,
dcpep_cb_swap_complete_intent_gated,
struct dcp_swap_complete_intent_gated);
TRAMPOLINE_IN(trampoline_abort_swap_ap_gated, dcpep_cb_abort_swap_ap_gated, u32);
TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated,
iomfbep_cb_enable_backlight_message_ap_gated, u8);
TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish,
struct iomfb_property);
TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state,
struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp);
TRAMPOLINE_OUT(trampoline_create_backlight_service, dcpep_cb_create_backlight_service, u8);
/*
* Callback for swap requests. If a swap failed, we'll never get a swap
* complete event so we need to fake a vblank event early to avoid a hang.
*/
static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie)
{
struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data;
if (resp->ret) {
dev_err(dcp->dev, "swap failed! status %u\n", resp->ret);
dcp_drm_crtc_vblank(dcp->crtc);
return;
}
while (!list_empty(&dcp->swapped_out_fbs)) {
struct dcp_fb_reference *entry;
entry = list_first_entry(&dcp->swapped_out_fbs,
struct dcp_fb_reference, head);
if (entry->swap_id == dcp->last_swap_id)
break;
if (entry->fb)
drm_framebuffer_put(entry->fb);
list_del(&entry->head);
kfree(entry);
}
}
static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie)
{
struct dcp_swap_start_resp *resp = data;
DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id;
trace_iomfb_swap_submit(dcp, resp->swap_id);
dcp_swap_submit(dcp, false, &DCP_FW_UNION(dcp->swap), dcp_swapped, NULL);
}
/* Helpers to modeset and swap, used to flush */
static void do_swap(struct apple_dcp *dcp, void *data, void *cookie)
{
struct dcp_swap_start_req start_req = { 0 };
if (dcp->connector && dcp->connector->connected)
dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL);
else
dcp_drm_crtc_vblank(dcp->crtc);
}
static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data,
void *cookie)
{
struct dcp_wait_cookie *wait = cookie;
if (wait) {
complete(&wait->done);
kref_put(&wait->refcount, release_wait_cookie);
}
}
int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp,
struct drm_crtc_state *crtc_state)
{
struct dcp_display_mode *mode;
struct dcp_wait_cookie *cookie;
struct dcp_color_mode *cmode = NULL;
int ret;
mode = lookup_mode(dcp, &crtc_state->mode);
if (!mode) {
dev_err(dcp->dev, "no match for " DRM_MODE_FMT "\n",
DRM_MODE_ARG(&crtc_state->mode));
return -EIO;
}
dev_info(dcp->dev,
"set_digital_out_mode(color:%d timing:%d) " DRM_MODE_FMT "\n",
mode->color_mode_id, mode->timing_mode_id,
DRM_MODE_ARG(&crtc_state->mode));
if (mode->color_mode_id == mode->sdr_rgb.id)
cmode = &mode->sdr_rgb;
else if (mode->color_mode_id == mode->sdr_444.id)
cmode = &mode->sdr_444;
else if (mode->color_mode_id == mode->sdr.id)
cmode = &mode->sdr;
else if (mode->color_mode_id == mode->best.id)
cmode = &mode->best;
if (cmode)
dev_info(dcp->dev,
"set_digital_out_mode() color mode depth:%hhu format:%u "
"colorimetry:%u eotf:%u range:%u\n", cmode->depth,
cmode->format, cmode->colorimetry, cmode->eotf,
cmode->range);
dcp->mode = (struct dcp_set_digital_out_mode_req){
.color_mode_id = mode->color_mode_id,
.timing_mode_id = mode->timing_mode_id
};
cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
if (!cookie) {
return -ENOMEM;
}
init_completion(&cookie->done);
kref_init(&cookie->refcount);
/* increase refcount to ensure the receiver has a reference */
kref_get(&cookie->refcount);
dcp->during_modeset = true;
dcp_set_digital_out_mode(dcp, false, &dcp->mode,
complete_set_digital_out_mode, cookie);
/*
* The DCP firmware has an internal timeout of ~8 seconds for
* modesets. Add an extra 500ms to safe side that the modeset
* call has returned.
*/
ret = wait_for_completion_timeout(&cookie->done,
msecs_to_jiffies(8500));
kref_put(&cookie->refcount, release_wait_cookie);
dcp->during_modeset = false;
dev_info(dcp->dev, "set_digital_out_mode finished:%d\n", ret);
if (ret == 0) {
dev_info(dcp->dev, "set_digital_out_mode timed out\n");
return -EIO;
} else if (ret < 0) {
dev_info(dcp->dev,
"waiting on set_digital_out_mode failed:%d\n", ret);
return -EIO;
} else if (ret > 0) {
dev_dbg(dcp->dev,
"set_digital_out_mode finished with %d to spare\n",
jiffies_to_msecs(ret));
}
dcp->valid_mode = true;
return 0;
}
void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state)
{
struct drm_plane *plane;
struct drm_plane_state *new_state, *old_state;
struct drm_crtc_state *crtc_state;
struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_UNION(dcp->swap);
int plane_idx, l;
int has_surface = 0;
crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
/* Reset to defaults */
memset(req, 0, sizeof(*req));
for (l = 0; l < SWAP_SURFACES; l++)
req->surf_null[l] = true;
#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
for (l = 0; l < 5; l++)
req->surf2_null[l] = true;
req->unkU32Ptr_null = true;
req->unkU32out_null = true;
#endif
/*
* Clear all surfaces on startup. The boot framebuffer in surface 0
* sticks around.
*/
if (!dcp->surfaces_cleared) {
req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0x7;
req->swap.bg_color = 0xFF000000;
dcp->surfaces_cleared = true;
}
// Surface 0 has limitations at least on t600x.
l = 1;
for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) {
struct drm_framebuffer *fb = new_state->fb;
struct drm_gem_dma_object *obj;
struct drm_rect src_rect;
bool is_premultiplied = false;
/* skip planes not for this crtc */
if (old_state->crtc != crtc && new_state->crtc != crtc)
continue;
WARN_ON(l >= SWAP_SURFACES);
req->swap.swap_enabled |= BIT(l);
if (old_state->fb && fb != old_state->fb) {
/*
* Race condition between a framebuffer unbind getting
* swapped out and GEM unreferencing a framebuffer. If
* we lose the race, the display gets IOVA faults and
* the DCP crashes. We need to extend the lifetime of
* the drm_framebuffer (and hence the GEM object) until
* after we get a swap complete for the swap unbinding
* it.
*/
struct dcp_fb_reference *entry =
kzalloc(sizeof(*entry), GFP_KERNEL);
if (entry) {
entry->fb = old_state->fb;
entry->swap_id = dcp->last_swap_id;
list_add_tail(&entry->head,
&dcp->swapped_out_fbs);
}
drm_framebuffer_get(old_state->fb);
}
if (!new_state->fb) {
l += 1;
continue;
}
req->surf_null[l] = false;
has_surface = 1;
/*
* DCP doesn't support XBGR8 / XRGB8 natively. Blending as
* pre-multiplied alpha with a black background can be used as
* workaround for the bottommost plane.
*/
if (fb->format->format == DRM_FORMAT_XRGB8888 ||
fb->format->format == DRM_FORMAT_XBGR8888)
is_premultiplied = true;
drm_rect_fp_to_int(&src_rect, &new_state->src);
req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect);
req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst);
if (dcp->notch_height > 0)
req->swap.dst_rect[l].y += dcp->notch_height;
/* the obvious helper call drm_fb_dma_get_gem_addr() adjusts
* the address for source x/y offsets. Since IOMFB has a direct
* support source position prefer that.
*/
obj = drm_fb_dma_get_gem_obj(fb, 0);
if (obj)
req->surf_iova[l] = obj->dma_addr + fb->offsets[0];
req->surf[l] = (struct DCP_FW_NAME(dcp_surface)){
.is_premultiplied = is_premultiplied,
.format = drm_format_to_dcp(fb->format->format),
.xfer_func = DCP_XFER_FUNC_SDR,
.colorspace = DCP_COLORSPACE_NATIVE,
.stride = fb->pitches[0],
.width = fb->width,
.height = fb->height,
.buf_size = fb->height * fb->pitches[0],
.surface_id = req->swap.surf_ids[l],
/* Only used for compressed or multiplanar surfaces */
.pix_size = 1,
.pel_w = 1,
.pel_h = 1,
.has_comp = 1,
.has_planes = 1,
};
l += 1;
}
if (!has_surface && !crtc_state->color_mgmt_changed) {
if (crtc_state->enable && crtc_state->active &&
!crtc_state->planes_changed) {
schedule_work(&dcp->vblank_wq);
return;
}
/* Set black background */
req->swap.swap_enabled |= IOMFB_SET_BACKGROUND;
req->swap.bg_color = 0xFF000000;
req->clear = 1;
}
/* These fields should be set together */
req->swap.swap_completed = req->swap.swap_enabled;
/* update brightness if changed */
if (dcp_has_panel(dcp) && dcp->brightness.update) {
req->swap.bl_unk = 1;
req->swap.bl_value = dcp->brightness.dac;
req->swap.bl_power = 0x40;
dcp->brightness.update = false;
}
if (crtc_state->color_mgmt_changed && crtc_state->ctm) {
struct iomfb_set_matrix_req mat;
struct drm_color_ctm *ctm = (struct drm_color_ctm *)crtc_state->ctm->data;
mat.unk_u32 = 9;
mat.r[0] = ctm->matrix[0];
mat.r[1] = ctm->matrix[1];
mat.r[2] = ctm->matrix[2];
mat.g[0] = ctm->matrix[3];
mat.g[1] = ctm->matrix[4];
mat.g[2] = ctm->matrix[5];
mat.b[0] = ctm->matrix[6];
mat.b[1] = ctm->matrix[7];
mat.b[2] = ctm->matrix[8];
iomfb_set_matrix(dcp, false, &mat, do_swap, NULL);
} else
do_swap(dcp, NULL, NULL);
}
static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie)
{
struct apple_connector *connector;
int result = *(int *)out;
dev_info(dcp->dev, "DCP is_main_display: %d\n", result);
dcp->main_display = result != 0;
connector = dcp->connector;
if (connector) {
connector->connected = dcp->nr_modes > 0;
schedule_work(&connector->hotplug_wq);
}
dcp->active = true;
complete(&dcp->start_done);
}
static void init_3(struct apple_dcp *dcp, void *out, void *cookie)
{
dcp_is_main_display(dcp, false, res_is_main_display, NULL);
}
static void init_2(struct apple_dcp *dcp, void *out, void *cookie)
{
dcp_first_client_open(dcp, false, init_3, NULL);
}
static void init_1(struct apple_dcp *dcp, void *out, void *cookie)
{
u32 val = 0;
dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL);
}
static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie)
{
struct iomfb_get_color_remap_mode_req color_remap =
(struct iomfb_get_color_remap_mode_req){
.mode = 6,
};
dev_info(dcp->dev, "DCP booted\n");
iomfb_get_color_remap_mode(dcp, false, &color_remap, init_1, cookie);
}
void DCP_FW_NAME(iomfb_shutdown)(struct apple_dcp *dcp)
{
struct dcp_set_power_state_req req = {
/* defaults are ok */
};
dcp_set_power_state(dcp, false, &req, NULL, NULL);
}