#include <linux/align.h>
#include <linux/apple-mailbox.h>
#include <linux/bitmap.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/component.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/gpio/consumer.h>
#include <linux/iommu.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/soc/apple/rtkit.h>
#include <linux/string.h>
#include <linux/workqueue.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_module.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include "afk.h"
#include "dcp.h"
#include "dcp-internal.h"
#include "iomfb.h"
#include "parser.h"
#include "trace.h"
#define APPLE_DCP_COPROC_CPU_CONTROL 0x44
#define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4)
#define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000)
static bool show_notch;
module_param(show_notch, bool, 0644);
MODULE_PARM_DESC(show_notch, "Use the full display height and shows the notch");
void dcp_drm_crtc_vblank(struct apple_crtc *crtc)
{
unsigned long flags;
spin_lock_irqsave(&crtc->base.dev->event_lock, flags);
if (crtc->event) {
drm_crtc_send_vblank_event(&crtc->base, crtc->event);
crtc->event = NULL;
}
spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags);
}
void dcp_set_dimensions(struct apple_dcp *dcp)
{
int i;
int width_mm = dcp->width_mm;
int height_mm = dcp->height_mm;
if (width_mm == 0 || height_mm == 0) {
width_mm = dcp->panel.width_mm;
height_mm = dcp->panel.height_mm;
}
if (dcp->connector) {
struct drm_connector *connector = &dcp->connector->base;
mutex_lock(&connector->dev->mode_config.mutex);
connector->display_info.width_mm = width_mm;
connector->display_info.height_mm = height_mm;
mutex_unlock(&connector->dev->mode_config.mutex);
}
for (i = 0; i < dcp->nr_modes; ++i) {
dcp->modes[i].mode.width_mm = width_mm;
dcp->modes[i].mode.height_mm = height_mm;
}
}
bool dcp_has_panel(struct apple_dcp *dcp)
{
return dcp->panel.width_mm > 0;
}
static void dcp_delayed_vblank(struct work_struct *work)
{
struct apple_dcp *dcp;
dcp = container_of(work, struct apple_dcp, vblank_wq);
mdelay(5);
dcp_drm_crtc_vblank(dcp->crtc);
}
static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message)
{
struct apple_dcp *dcp = cookie;
trace_dcp_recv_msg(dcp, endpoint, message);
switch (endpoint) {
case IOMFB_ENDPOINT:
return iomfb_recv_msg(dcp, message);
case SYSTEM_ENDPOINT:
afk_receive_message(dcp->systemep, message);
return;
case DISP0_ENDPOINT:
afk_receive_message(dcp->ibootep, message);
return;
case DPTX_ENDPOINT:
afk_receive_message(dcp->dptxep, message);
return;
default:
WARN(endpoint, "unknown DCP endpoint %hhu\n", endpoint);
}
}
static void dcp_rtk_crashed(void *cookie)
{
struct apple_dcp *dcp = cookie;
dcp->crashed = true;
dev_err(dcp->dev, "DCP has crashed\n");
if (dcp->connector) {
dcp->connector->connected = 0;
schedule_work(&dcp->connector->hotplug_wq);
}
complete(&dcp->start_done);
}
static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
{
struct apple_dcp *dcp = cookie;
if (bfr->iova) {
struct iommu_domain *domain =
iommu_get_domain_for_dev(dcp->dev);
phys_addr_t phy_addr;
if (!domain)
return -ENOMEM;
phy_addr = iommu_iova_to_phys(domain, bfr->iova);
if (!phy_addr)
return -ENOMEM;
bfr->buffer = memremap(phy_addr, bfr->size, MEMREMAP_WB);
if (!bfr->buffer)
return -ENOMEM;
bfr->is_mapped = true;
dev_info(dcp->dev,
"shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx\n",
(uintptr_t)bfr->iova, (uintptr_t)phy_addr,
(uintptr_t)bfr->buffer);
} else {
bfr->buffer = dma_alloc_coherent(dcp->dev, bfr->size,
&bfr->iova, GFP_KERNEL);
if (!bfr->buffer)
return -ENOMEM;
dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx\n",
(uintptr_t)bfr->iova, (uintptr_t)bfr->buffer);
}
return 0;
}
static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr)
{
struct apple_dcp *dcp = cookie;
if (bfr->is_mapped)
memunmap(bfr->buffer);
else
dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova);
}
static struct apple_rtkit_ops rtkit_ops = {
.crashed = dcp_rtk_crashed,
.recv_message = dcp_recv_msg,
.shmem_setup = dcp_rtk_shmem_setup,
.shmem_destroy = dcp_rtk_shmem_destroy,
};
void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message)
{
trace_dcp_send_msg(dcp, endpoint, message);
apple_rtkit_send_message(dcp->rtk, endpoint, message, NULL,
true);
}
int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
{
struct platform_device *pdev = to_apple_crtc(crtc)->dcp;
struct apple_dcp *dcp = platform_get_drvdata(pdev);
struct drm_plane_state *new_state;
struct drm_plane *plane;
struct drm_crtc_state *crtc_state;
int plane_idx, plane_count = 0;
bool needs_modeset;
if (dcp->crashed)
return -EINVAL;
crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
needs_modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode;
if (!needs_modeset && !dcp->connector->connected) {
dev_err(dcp->dev, "crtc_atomic_check: disconnected but no modeset\n");
return -EINVAL;
}
for_each_new_plane_in_state(state, plane, new_state, plane_idx) {
if (new_state->crtc != crtc)
continue;
plane_count += 1;
}
if (plane_count > DCP_MAX_PLANES) {
dev_err(dcp->dev, "crtc_atomic_check: Blend supports only 2 layers!\n");
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_GPL(dcp_crtc_atomic_check);
int dcp_get_connector_type(struct platform_device *pdev)
{
struct apple_dcp *dcp = platform_get_drvdata(pdev);
return (dcp->connector_type);
}
EXPORT_SYMBOL_GPL(dcp_get_connector_type);
#define DPTX_CONNECT_TIMEOUT msecs_to_jiffies(1000)
static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port)
{
int ret = 0;
if (!dcp->phy) {
dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n");
return -ENODEV;
}
dev_info(dcp->dev, "%s(port=%d)\n", __func__, port);
mutex_lock(&dcp->hpd_mutex);
if (!dcp->dptxport[port].enabled) {
dev_warn(dcp->dev, "dcp_dptx_connect: dptx service for port %d not enabled\n", port);
ret = -ENODEV;
goto out_unlock;
}
if (dcp->dptxport[port].connected)
goto out_unlock;
reinit_completion(&dcp->dptxport[port].linkcfg_completion);
dcp->dptxport[port].atcphy = dcp->phy;
dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die);
dptxport_request_display(dcp->dptxport[port].service);
dcp->dptxport[port].connected = true;
mutex_unlock(&dcp->hpd_mutex);
ret = wait_for_completion_timeout(&dcp->dptxport[port].linkcfg_completion,
DPTX_CONNECT_TIMEOUT);
if (ret < 0)
dev_warn(dcp->dev, "dcp_dptx_connect: port %d link complete failed:%d\n",
port, ret);
else
dev_dbg(dcp->dev, "dcp_dptx_connect: waited %d ms for link\n",
jiffies_to_msecs(DPTX_CONNECT_TIMEOUT - ret));
usleep_range(5, 10);
return 0;
out_unlock:
mutex_unlock(&dcp->hpd_mutex);
return ret;
}
static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port)
{
dev_info(dcp->dev, "%s(port=%d)\n", __func__, port);
mutex_lock(&dcp->hpd_mutex);
if (dcp->dptxport[port].enabled && dcp->dptxport[port].connected) {
dptxport_release_display(dcp->dptxport[port].service);
dcp->dptxport[port].connected = false;
}
mutex_unlock(&dcp->hpd_mutex);
return 0;
}
static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data)
{
struct apple_dcp *dcp = data;
bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
dev_info(dcp->dev, "DP2HDMI HPD irq, connected:%d\n", connected);
if (connected)
dcp_dptx_connect(dcp, 0);
return IRQ_HANDLED;
}
void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc,
struct apple_connector *connector)
{
struct apple_dcp *dcp = platform_get_drvdata(pdev);
dcp->crtc = crtc;
dcp->connector = connector;
}
EXPORT_SYMBOL_GPL(dcp_link);
int dcp_start(struct platform_device *pdev)
{
struct apple_dcp *dcp = platform_get_drvdata(pdev);
int ret;
init_completion(&dcp->start_done);
ret = systemep_init(dcp);
if (ret)
dev_warn(dcp->dev, "Failed to start system endpoint: %d\n", ret);
if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) {
ret = ibootep_init(dcp);
if (ret)
dev_warn(dcp->dev, "Failed to start IBOOT endpoint: %d\n",
ret);
ret = dptxep_init(dcp);
if (ret)
dev_warn(dcp->dev, "Failed to start DPTX endpoint: %d\n",
ret);
else if (dcp->dptxport[0].enabled) {
bool connected;
dptxport_set_hpd(dcp->dptxport[0].service, false);
dptxport_release_display(dcp->dptxport[0].service);
usleep_range(10 * USEC_PER_MSEC, 25 * USEC_PER_MSEC);
connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected);
if (connected)
dcp_dptx_connect(dcp, 0);
}
} else if (dcp->phy)
dev_warn(dcp->dev, "OS firmware incompatible with dptxport EP\n");
ret = iomfb_start_rtkit(dcp);
if (ret)
dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d\n", ret);
return ret;
}
EXPORT_SYMBOL(dcp_start);
static int dcp_enable_dp2hdmi_hpd(struct apple_dcp *dcp)
{
if (dcp->hdmi_hpd_irq)
enable_irq(dcp->hdmi_hpd_irq);
return 0;
}
int dcp_wait_ready(struct platform_device *pdev, u64 timeout)
{
struct apple_dcp *dcp = platform_get_drvdata(pdev);
int ret;
if (dcp->crashed)
return -ENODEV;
if (dcp->active)
return dcp_enable_dp2hdmi_hpd(dcp);;
if (timeout <= 0)
return -ETIMEDOUT;
ret = wait_for_completion_timeout(&dcp->start_done, timeout);
if (ret < 0)
return ret;
if (dcp->crashed)
return -ENODEV;
if (dcp->active)
dcp_enable_dp2hdmi_hpd(dcp);
return dcp->active ? 0 : -ETIMEDOUT;
}
EXPORT_SYMBOL(dcp_wait_ready);
static void __maybe_unused dcp_sleep(struct apple_dcp *dcp)
{
switch (dcp->fw_compat) {
case DCP_FIRMWARE_V_12_3:
iomfb_sleep_v12_3(dcp);
break;
case DCP_FIRMWARE_V_13_5:
iomfb_sleep_v13_3(dcp);
break;
default:
WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
break;
}
}
void dcp_poweron(struct platform_device *pdev)
{
struct apple_dcp *dcp = platform_get_drvdata(pdev);
if (dcp->hdmi_hpd) {
bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected);
if (connected)
dcp_dptx_connect(dcp, 0);
}
switch (dcp->fw_compat) {
case DCP_FIRMWARE_V_12_3:
iomfb_poweron_v12_3(dcp);
break;
case DCP_FIRMWARE_V_13_5:
iomfb_poweron_v13_3(dcp);
break;
default:
WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
break;
}
}
EXPORT_SYMBOL(dcp_poweron);
void dcp_poweroff(struct platform_device *pdev)
{
struct apple_dcp *dcp = platform_get_drvdata(pdev);
switch (dcp->fw_compat) {
case DCP_FIRMWARE_V_12_3:
iomfb_poweroff_v12_3(dcp);
break;
case DCP_FIRMWARE_V_13_5:
iomfb_poweroff_v13_3(dcp);
break;
default:
WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
break;
}
if (dcp->hdmi_hpd) {
bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
if (!connected)
dcp_dptx_disconnect(dcp, 0);
}
}
EXPORT_SYMBOL(dcp_poweroff);
static void dcp_work_register_backlight(struct work_struct *work)
{
int ret;
struct apple_dcp *dcp;
dcp = container_of(work, struct apple_dcp, bl_register_wq);
mutex_lock(&dcp->bl_register_mutex);
if (dcp->brightness.bl_dev)
goto out_unlock;
ret = dcp_backlight_register(dcp);
if (ret) {
dev_err(dcp->dev, "Unable to register backlight device\n");
dcp->brightness.maximum = 0;
}
out_unlock:
mutex_unlock(&dcp->bl_register_mutex);
}
static void dcp_work_update_backlight(struct work_struct *work)
{
struct apple_dcp *dcp;
dcp = container_of(work, struct apple_dcp, bl_update_wq);
dcp_backlight_update(dcp);
}
static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp)
{
int ret;
struct device_node *node = of_get_child_by_name(dcp->dev->of_node, "piodma");
if (!node)
return dev_err_probe(dcp->dev, -ENODEV,
"Failed to get piodma child DT node\n");
dcp->piodma = of_platform_device_create(node, NULL, dcp->dev);
if (!dcp->piodma) {
of_node_put(node);
return dev_err_probe(dcp->dev, -ENODEV, "Failed to gcreate piodma pdev for %pOF\n", node);
}
ret = dma_set_mask_and_coherent(&dcp->piodma->dev, DMA_BIT_MASK(42));
if (ret)
goto err_destroy_pdev;
ret = of_dma_configure(&dcp->piodma->dev, node, true);
if (ret) {
ret = dev_err_probe(dcp->dev, ret,
"Failed to configure IOMMU child DMA\n");
goto err_destroy_pdev;
}
of_node_put(node);
dcp->iommu_dom = iommu_domain_alloc(&platform_bus_type);
if (!dcp->iommu_dom) {
ret = -ENOMEM;
goto err_destroy_pdev;
}
ret = iommu_attach_device(dcp->iommu_dom, &dcp->piodma->dev);
if (ret) {
ret = dev_err_probe(dcp->dev, ret,
"Failed to attach IOMMU child domain\n");
goto err_free_domain;
}
return 0;
err_free_domain:
iommu_domain_free(dcp->iommu_dom);
err_destroy_pdev:
of_node_put(node);
of_platform_device_destroy(&dcp->piodma->dev, NULL);
return ret;
}
static int dcp_get_bw_scratch_reg(struct apple_dcp *dcp, u32 expected)
{
struct of_phandle_args ph_args;
u32 addr_idx, disp_idx, offset;
int ret;
ret = of_parse_phandle_with_args(dcp->dev->of_node, "apple,bw-scratch",
"#apple,bw-scratch-cells", 0, &ph_args);
if (ret < 0) {
dev_err(dcp->dev, "Failed to read 'apple,bw-scratch': %d\n", ret);
return ret;
}
if (ph_args.args_count != 3) {
dev_err(dcp->dev, "Unexpected 'apple,bw-scratch' arg count %d\n",
ph_args.args_count);
ret = -EINVAL;
goto err_of_node_put;
}
addr_idx = ph_args.args[0];
disp_idx = ph_args.args[1];
offset = ph_args.args[2];
if (disp_idx != expected || disp_idx >= MAX_DISP_REGISTERS) {
dev_err(dcp->dev, "Unexpected disp_reg value in 'apple,bw-scratch': %d\n",
disp_idx);
ret = -EINVAL;
goto err_of_node_put;
}
ret = of_address_to_resource(ph_args.np, addr_idx, &dcp->disp_bw_scratch_res);
if (ret < 0) {
dev_err(dcp->dev, "Failed to get 'apple,bw-scratch' resource %d from %pOF\n",
addr_idx, ph_args.np);
goto err_of_node_put;
}
if (offset > resource_size(&dcp->disp_bw_scratch_res) - 4) {
ret = -EINVAL;
goto err_of_node_put;
}
dcp->disp_registers[disp_idx] = &dcp->disp_bw_scratch_res;
dcp->disp_bw_scratch_index = disp_idx;
dcp->disp_bw_scratch_offset = offset;
ret = 0;
err_of_node_put:
of_node_put(ph_args.np);
return ret;
}
static int dcp_get_bw_doorbell_reg(struct apple_dcp *dcp, u32 expected)
{
struct of_phandle_args ph_args;
u32 addr_idx, disp_idx;
int ret;
ret = of_parse_phandle_with_args(dcp->dev->of_node, "apple,bw-doorbell",
"#apple,bw-doorbell-cells", 0, &ph_args);
if (ret < 0) {
dev_err(dcp->dev, "Failed to read 'apple,bw-doorbell': %d\n", ret);
return ret;
}
if (ph_args.args_count != 2) {
dev_err(dcp->dev, "Unexpected 'apple,bw-doorbell' arg count %d\n",
ph_args.args_count);
ret = -EINVAL;
goto err_of_node_put;
}
addr_idx = ph_args.args[0];
disp_idx = ph_args.args[1];
if (disp_idx != expected || disp_idx >= MAX_DISP_REGISTERS) {
dev_err(dcp->dev, "Unexpected disp_reg value in 'apple,bw-doorbell': %d\n",
disp_idx);
ret = -EINVAL;
goto err_of_node_put;
}
ret = of_address_to_resource(ph_args.np, addr_idx, &dcp->disp_bw_doorbell_res);
if (ret < 0) {
dev_err(dcp->dev, "Failed to get 'apple,bw-doorbell' resource %d from %pOF\n",
addr_idx, ph_args.np);
goto err_of_node_put;
}
dcp->disp_bw_doorbell_index = disp_idx;
dcp->disp_registers[disp_idx] = &dcp->disp_bw_doorbell_res;
ret = 0;
err_of_node_put:
of_node_put(ph_args.np);
return ret;
}
static int dcp_get_disp_regs(struct apple_dcp *dcp)
{
struct platform_device *pdev = to_platform_device(dcp->dev);
int count = pdev->num_resources - 1;
int i, ret;
if (count <= 0 || count > MAX_DISP_REGISTERS)
return -EINVAL;
for (i = 0; i < count; ++i) {
dcp->disp_registers[i] =
platform_get_resource(pdev, IORESOURCE_MEM, 1 + i);
}
ret = dcp_get_bw_scratch_reg(dcp, count);
if (ret < 0)
return ret;
count += 1;
if (of_property_present(dcp->dev->of_node, "apple,bw-doorbell")) {
ret = dcp_get_bw_doorbell_reg(dcp, count);
if (ret < 0)
return ret;
count += 1;
}
dcp->nr_disp_registers = count;
return 0;
}
#define DCP_FW_VERSION_MIN_LEN 3
#define DCP_FW_VERSION_MAX_LEN 5
#define DCP_FW_VERSION_STR_LEN (DCP_FW_VERSION_MAX_LEN * 4)
static int dcp_read_fw_version(struct device *dev, const char *name,
char *version_str)
{
u32 ver[DCP_FW_VERSION_MAX_LEN];
int len_str;
int len;
len = of_property_read_variable_u32_array(dev->of_node, name, ver,
DCP_FW_VERSION_MIN_LEN,
DCP_FW_VERSION_MAX_LEN);
switch (len) {
case 3:
len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN,
"%d.%d.%d", ver[0], ver[1], ver[2]);
break;
case 4:
len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN,
"%d.%d.%d.%d", ver[0], ver[1], ver[2],
ver[3]);
break;
case 5:
len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN,
"%d.%d.%d.%d.%d", ver[0], ver[1], ver[2],
ver[3], ver[4]);
break;
default:
len_str = strscpy(version_str, "UNKNOWN",
DCP_FW_VERSION_STR_LEN);
if (len >= 0)
len = -EOVERFLOW;
break;
}
if (len_str >= DCP_FW_VERSION_STR_LEN)
dev_warn(dev, "'%s' truncated: '%s'\n", name, version_str);
return len;
}
static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev)
{
char compat_str[DCP_FW_VERSION_STR_LEN];
char fw_str[DCP_FW_VERSION_STR_LEN];
int ret;
dcp_read_fw_version(dev, "apple,firmware-version", fw_str);
ret = dcp_read_fw_version(dev, "apple,firmware-compat", compat_str);
if (ret < 0) {
dev_err(dev, "Could not read 'apple,firmware-compat': %d\n", ret);
return DCP_FIRMWARE_UNKNOWN;
}
if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0)
return DCP_FIRMWARE_V_12_3;
else if ((strncmp(compat_str, "13.3.0", sizeof(compat_str)) == 0) &&
(strncmp(fw_str, "13.5.0", sizeof(compat_str)) == 0))
return DCP_FIRMWARE_V_13_5;
else if (strncmp(compat_str, "13.5.0", sizeof(compat_str)) == 0)
return DCP_FIRMWARE_V_13_5;
dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n",
compat_str, fw_str);
return DCP_FIRMWARE_UNKNOWN;
}
static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
{
struct device_node *panel_np;
struct apple_dcp *dcp = dev_get_drvdata(dev);
u32 cpu_ctrl;
int ret;
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(42));
if (ret)
return ret;
dcp->coproc_reg = devm_platform_ioremap_resource_byname(to_platform_device(dev), "coproc");
if (IS_ERR(dcp->coproc_reg))
return PTR_ERR(dcp->coproc_reg);
of_property_read_u32(dev->of_node, "apple,dcp-index",
&dcp->index);
of_property_read_u32(dev->of_node, "apple,dptx-phy",
&dcp->dptx_phy);
of_property_read_u32(dev->of_node, "apple,dptx-die",
&dcp->dptx_die);
if (dcp->index || dcp->dptx_phy || dcp->dptx_die)
dev_info(dev, "DCP index:%u dptx target phy: %u dptx die: %u\n",
dcp->index, dcp->dptx_phy, dcp->dptx_die);
rw_init(&dcp->hpd_mutex, "aplhpd");
if (!show_notch)
ret = of_property_read_u32(dev->of_node, "apple,notch-height",
&dcp->notch_height);
if (dcp->notch_height > MAX_NOTCH_HEIGHT)
dcp->notch_height = MAX_NOTCH_HEIGHT;
if (dcp->notch_height > 0)
dev_info(dev, "Detected display with notch of %u pixel\n", dcp->notch_height);
dcp->brightness.scale = 65536;
panel_np = of_get_compatible_child(dev->of_node, "apple,panel-mini-led");
if (panel_np)
dcp->panel.has_mini_led = true;
else
panel_np = of_get_compatible_child(dev->of_node, "apple,panel");
if (panel_np) {
const char height_prop[2][16] = { "adj-height-mm", "height-mm" };
if (of_device_is_available(panel_np)) {
ret = of_property_read_u32(panel_np, "apple,max-brightness",
&dcp->brightness.maximum);
if (ret)
dev_err(dev, "Missing property 'apple,max-brightness'\n");
}
of_property_read_u32(panel_np, "width-mm", &dcp->panel.width_mm);
of_property_read_u32(panel_np, height_prop[!dcp->notch_height],
&dcp->panel.height_mm);
of_node_put(panel_np);
dcp->connector_type = DRM_MODE_CONNECTOR_eDP;
INIT_WORK(&dcp->bl_register_wq, dcp_work_register_backlight);
rw_init(&dcp->bl_register_mutex, "dcpbl");
INIT_WORK(&dcp->bl_update_wq, dcp_work_update_backlight);
} else if (of_property_match_string(dev->of_node, "apple,connector-type", "HDMI-A") >= 0)
dcp->connector_type = DRM_MODE_CONNECTOR_HDMIA;
else if (of_property_match_string(dev->of_node, "apple,connector-type", "DP") >= 0)
dcp->connector_type = DRM_MODE_CONNECTOR_DisplayPort;
else if (of_property_match_string(dev->of_node, "apple,connector-type", "USB-C") >= 0)
dcp->connector_type = DRM_MODE_CONNECTOR_USB;
else
dcp->connector_type = DRM_MODE_CONNECTOR_Unknown;
ret = dcp_create_piodma_iommu_dev(dcp);
if (ret)
return dev_err_probe(dev, ret,
"Failed to created PIODMA iommu child device");
ret = dcp_get_disp_regs(dcp);
if (ret) {
dev_err(dev, "failed to find display registers\n");
return ret;
}
dcp->clk = devm_clk_get(dev, NULL);
if (IS_ERR(dcp->clk))
return dev_err_probe(dev, PTR_ERR(dcp->clk),
"Unable to find clock\n");
bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS);
set_bit(0, dcp->memdesc_map);
INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank);
dcp->swapped_out_fbs =
(struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs);
cpu_ctrl =
readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL);
writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN,
dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL);
dcp->rtk = devm_apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops);
if (IS_ERR(dcp->rtk))
return dev_err_probe(dev, PTR_ERR(dcp->rtk),
"Failed to initialize RTKit\n");
ret = apple_rtkit_wake(dcp->rtk);
if (ret)
return dev_err_probe(dev, ret,
"Failed to boot RTKit: %d\n", ret);
return ret;
}
static void dcp_comp_unbind(struct device *dev, struct device *main, void *data)
{
struct apple_dcp *dcp = dev_get_drvdata(dev);
if (dcp->hdmi_hpd_irq)
disable_irq(dcp->hdmi_hpd_irq);
if (dcp && dcp->shmem)
iomfb_shutdown(dcp);
if (dcp->piodma) {
iommu_detach_device(dcp->iommu_dom, &dcp->piodma->dev);
iommu_domain_free(dcp->iommu_dom);
dcp->piodma = NULL;
}
devm_clk_put(dev, dcp->clk);
dcp->clk = NULL;
}
static const struct component_ops dcp_comp_ops = {
.bind = dcp_comp_bind,
.unbind = dcp_comp_unbind,
};
static int dcp_platform_probe(struct platform_device *pdev)
{
enum dcp_firmware_version fw_compat;
struct device *dev = &pdev->dev;
struct apple_dcp *dcp;
u32 mux_index;
fw_compat = dcp_check_firmware_version(dev);
if (fw_compat == DCP_FIRMWARE_UNKNOWN)
return -ENODEV;
if (!of_property_present(dev->of_node, "apple,bw-scratch"))
return dev_err_probe(dev, -ENODEV, "Incompatible devicetree! "
"Use devicetree matching this kernel.\n");
dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL);
if (!dcp)
return -ENOMEM;
dcp->fw_compat = fw_compat;
dcp->dev = dev;
dcp->hw = *(struct apple_dcp_hw_data *)of_device_get_match_data(dev);
platform_set_drvdata(pdev, dcp);
dcp->phy = devm_phy_optional_get(dev, "dp-phy");
if (IS_ERR(dcp->phy)) {
dev_err(dev, "Failed to get dp-phy: %ld\n", PTR_ERR(dcp->phy));
return PTR_ERR(dcp->phy);
}
if (dcp->phy) {
int ret;
dcp->hdmi_hpd = devm_gpiod_get_optional(dev, "hdmi-hpd", GPIOD_IN);
if (IS_ERR(dcp->hdmi_hpd))
return PTR_ERR(dcp->hdmi_hpd);
if (dcp->hdmi_hpd) {
int irq = gpiod_to_irq(dcp->hdmi_hpd);
if (irq < 0) {
dev_err(dev, "failed to translate HDMI hpd GPIO to IRQ\n");
return irq;
}
dcp->hdmi_hpd_irq = irq;
ret = devm_request_threaded_irq(dev, dcp->hdmi_hpd_irq,
NULL, dcp_dp2hdmi_hpd,
IRQF_ONESHOT | IRQF_NO_AUTOEN |
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"dp2hdmi-hpd-irq", dcp);
if (ret < 0) {
dev_err(dev, "failed to request HDMI hpd irq %d: %d\n",
irq, ret);
return ret;
}
}
dcp->hdmi_pwren = devm_gpiod_get_optional(dev, "hdmi-pwren", GPIOD_OUT_HIGH);
if (IS_ERR(dcp->hdmi_pwren))
return PTR_ERR(dcp->hdmi_pwren);
dcp->dp2hdmi_pwren = devm_gpiod_get_optional(dev, "dp2hdmi-pwren", GPIOD_OUT_HIGH);
if (IS_ERR(dcp->dp2hdmi_pwren))
return PTR_ERR(dcp->dp2hdmi_pwren);
ret = of_property_read_u32(dev->of_node, "mux-index", &mux_index);
if (!ret) {
dcp->xbar = devm_mux_control_get(dev, "dp-xbar");
if (IS_ERR(dcp->xbar)) {
dev_err(dev, "Failed to get dp-xbar: %ld\n", PTR_ERR(dcp->xbar));
return PTR_ERR(dcp->xbar);
}
ret = mux_control_select(dcp->xbar, mux_index);
if (ret)
dev_warn(dev, "mux_control_select failed: %d\n", ret);
}
}
return component_add(&pdev->dev, &dcp_comp_ops);
}
#ifdef __linux__
static int dcp_platform_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dcp_comp_ops);
return 0;
}
static void dcp_platform_shutdown(struct platform_device *pdev)
{
component_del(&pdev->dev, &dcp_comp_ops);
}
#endif
static int dcp_platform_suspend(struct device *dev)
{
struct apple_dcp *dcp = dev_get_drvdata(dev);
if (dcp->hdmi_hpd_irq) {
disable_irq(dcp->hdmi_hpd_irq);
dcp_dptx_disconnect(dcp, 0);
}
device_set_wakeup_path(dev);
return 0;
}
static int dcp_platform_resume(struct device *dev)
{
struct apple_dcp *dcp = dev_get_drvdata(dev);
if (dcp->hdmi_hpd_irq)
enable_irq(dcp->hdmi_hpd_irq);
if (dcp->hdmi_hpd) {
bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
dev_info(dcp->dev, "resume: HPD connected:%d\n", connected);
if (connected)
dcp_dptx_connect(dcp, 0);
}
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops,
dcp_platform_suspend, dcp_platform_resume);
static const struct apple_dcp_hw_data apple_dcp_hw_t6020 = {
.num_dptx_ports = 1,
};
static const struct apple_dcp_hw_data apple_dcp_hw_t8112 = {
.num_dptx_ports = 2,
};
static const struct apple_dcp_hw_data apple_dcp_hw_dcp = {
.num_dptx_ports = 0,
};
static const struct apple_dcp_hw_data apple_dcp_hw_dcpext = {
.num_dptx_ports = 2,
};
static const struct of_device_id of_match[] = {
{ .compatible = "apple,t6020-dcp", .data = &apple_dcp_hw_t6020, },
{ .compatible = "apple,t8112-dcp", .data = &apple_dcp_hw_t8112, },
{ .compatible = "apple,dcp", .data = &apple_dcp_hw_dcp, },
{ .compatible = "apple,dcpext", .data = &apple_dcp_hw_dcpext, },
{}
};
MODULE_DEVICE_TABLE(of, of_match);
#ifdef __linux__
static struct platform_driver apple_platform_driver = {
.probe = dcp_platform_probe,
.remove = dcp_platform_remove,
.shutdown = dcp_platform_shutdown,
.driver = {
.name = "apple-dcp",
.of_match_table = of_match,
.pm = pm_sleep_ptr(&dcp_platform_pm_ops),
},
};
drm_module_platform_driver(apple_platform_driver);
MODULE_AUTHOR("Alyssa Rosenzweig <alyssa@rosenzweig.io>");
MODULE_DESCRIPTION("Apple Display Controller DRM driver");
MODULE_LICENSE("Dual MIT/GPL");
#endif