#include <linux/align.h>
#include <linux/bitfield.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/ratelimit.h>
#include <linux/slab.h>
#include <linux/soc/apple/rtkit.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"
static int dcp_tx_offset(enum dcp_context_id id)
{
switch (id) {
case DCP_CONTEXT_CB:
case DCP_CONTEXT_CMD:
return 0x00000;
case DCP_CONTEXT_OOBCB:
case DCP_CONTEXT_OOBCMD:
return 0x08000;
default:
return -EINVAL;
}
}
static int dcp_channel_offset(enum dcp_context_id id)
{
switch (id) {
case DCP_CONTEXT_ASYNC:
return 0x40000;
case DCP_CONTEXT_OOBASYNC:
return 0x48000;
case DCP_CONTEXT_CB:
return 0x60000;
case DCP_CONTEXT_OOBCB:
return 0x68000;
default:
return dcp_tx_offset(id);
}
}
static inline u64 dcpep_set_shmem(u64 dart_va)
{
return FIELD_PREP(IOMFB_MESSAGE_TYPE, IOMFB_MESSAGE_TYPE_SET_SHMEM) |
FIELD_PREP(IOMFB_SHMEM_FLAG, IOMFB_SHMEM_FLAG_VALUE) |
FIELD_PREP(IOMFB_SHMEM_DVA, dart_va);
}
static inline u64 dcpep_msg(enum dcp_context_id id, u32 length, u16 offset)
{
return FIELD_PREP(IOMFB_MESSAGE_TYPE, IOMFB_MESSAGE_TYPE_MSG) |
FIELD_PREP(IOMFB_MSG_CONTEXT, id) |
FIELD_PREP(IOMFB_MSG_OFFSET, offset) |
FIELD_PREP(IOMFB_MSG_LENGTH, length);
}
static inline u64 dcpep_ack(enum dcp_context_id id)
{
return dcpep_msg(id, 0, 0) | IOMFB_MSG_ACK;
}
static bool dcp_channel_busy(struct dcp_channel *ch)
{
return (ch->depth != 0);
}
static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob)
{
u8 depth = oob ? dcp->ch_oobcmd.depth : dcp->ch_cmd.depth;
if (depth)
return oob ? DCP_CONTEXT_OOBCB : DCP_CONTEXT_CB;
else
return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD;
}
static struct dcp_channel *dcp_get_channel(struct apple_dcp *dcp,
enum dcp_context_id context)
{
switch (context) {
case DCP_CONTEXT_CB:
return &dcp->ch_cb;
case DCP_CONTEXT_CMD:
return &dcp->ch_cmd;
case DCP_CONTEXT_OOBCB:
return &dcp->ch_oobcb;
case DCP_CONTEXT_OOBCMD:
return &dcp->ch_oobcmd;
case DCP_CONTEXT_ASYNC:
return &dcp->ch_async;
case DCP_CONTEXT_OOBASYNC:
return &dcp->ch_oobasync;
default:
return NULL;
}
}
static u16 dcp_packet_start(struct dcp_channel *ch, u8 depth)
{
if (depth > 0)
return ch->end[depth - 1];
else
return 0;
}
static u8 dcp_push_depth(u8 *depth)
{
u8 ret = (*depth)++;
WARN_ON(ret >= DCP_MAX_CALL_DEPTH);
return ret;
}
static u8 dcp_pop_depth(u8 *depth)
{
WARN_ON((*depth) == 0);
return --(*depth);
}
void dcp_push(struct apple_dcp *dcp, bool oob, const struct dcp_method_entry *call,
u32 in_len, u32 out_len, void *data, dcp_callback_t cb,
void *cookie)
{
enum dcp_context_id context = dcp_call_context(dcp, oob);
struct dcp_channel *ch = dcp_get_channel(dcp, context);
struct dcp_packet_header header = {
.in_len = in_len,
.out_len = out_len,
.tag[0] = call->tag[3],
.tag[1] = call->tag[2],
.tag[2] = call->tag[1],
.tag[3] = call->tag[0],
};
u8 depth = dcp_push_depth(&ch->depth);
u16 offset = dcp_packet_start(ch, depth);
void *out = dcp->shmem + dcp_tx_offset(context) + offset;
void *out_data = out + sizeof(header);
size_t data_len = sizeof(header) + in_len + out_len;
memcpy(out, &header, sizeof(header));
if (in_len > 0)
memcpy(out_data, data, in_len);
trace_iomfb_push(dcp, call, context, offset, depth);
ch->callbacks[depth] = cb;
ch->cookies[depth] = cookie;
ch->output[depth] = out + sizeof(header) + in_len;
ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT);
dcp_send_message(dcp, IOMFB_ENDPOINT,
dcpep_msg(context, data_len, offset));
}
int dcp_parse_tag(char tag[4])
{
u32 d[3];
int i;
if (tag[3] != 'D')
return -EINVAL;
for (i = 0; i < 3; ++i) {
d[i] = (u32)(tag[i] - '0');
if (d[i] > 9)
return -EINVAL;
}
return d[0] + (d[1] * 10) + (d[2] * 100);
}
void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context)
{
struct dcp_channel *ch = dcp_get_channel(dcp, context);
dcp_pop_depth(&ch->depth);
dcp_send_message(dcp, IOMFB_ENDPOINT,
dcpep_ack(context));
}
void dcp_hotplug(struct work_struct *work)
{
struct apple_connector *connector;
struct apple_dcp *dcp;
connector = container_of(work, struct apple_connector, hotplug_wq);
dcp = platform_get_drvdata(connector->dcp);
dev_info(dcp->dev, "%s() connected:%d valid_mode:%d nr_modes:%u\n", __func__,
connector->connected, dcp->valid_mode, dcp->nr_modes);
if (connector->base.state && !dcp->valid_mode && connector->connected)
drm_connector_set_link_status_property(&connector->base,
DRM_MODE_LINK_STATUS_BAD);
drm_kms_helper_connector_hotplug_event(&connector->base);
}
EXPORT_SYMBOL_GPL(dcp_hotplug);
static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context,
void *data, u32 length, u16 offset)
{
struct device *dev = dcp->dev;
struct dcp_packet_header *hdr = data;
void *in, *out;
int tag = dcp_parse_tag(hdr->tag);
struct dcp_channel *ch = dcp_get_channel(dcp, context);
u8 depth;
if (tag < 0 || tag >= IOMFB_MAX_CB || !dcp->cb_handlers || !dcp->cb_handlers[tag]) {
dev_warn(dev, "received unknown callback %c%c%c%c\n",
hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]);
return;
}
in = data + sizeof(*hdr);
out = in + hdr->in_len;
if (hdr->out_len)
memset(out, 0, hdr->out_len);
depth = dcp_push_depth(&ch->depth);
ch->output[depth] = out;
ch->end[depth] = offset + ALIGN(length, DCP_PACKET_ALIGNMENT);
if (dcp->cb_handlers[tag](dcp, tag, out, in))
dcp_ack(dcp, context);
}
static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context,
void *data, u32 length)
{
struct dcp_packet_header *header = data;
struct dcp_channel *ch = dcp_get_channel(dcp, context);
void *cookie;
dcp_callback_t cb;
if (!ch) {
dev_warn(dcp->dev, "ignoring ack on context %X\n", context);
return;
}
dcp_pop_depth(&ch->depth);
cb = ch->callbacks[ch->depth];
cookie = ch->cookies[ch->depth];
ch->callbacks[ch->depth] = NULL;
ch->cookies[ch->depth] = NULL;
if (cb)
cb(dcp, data + sizeof(*header) + header->in_len, cookie);
}
static void dcpep_got_msg(struct apple_dcp *dcp, u64 message)
{
enum dcp_context_id ctx_id;
u16 offset;
u32 length;
int channel_offset;
void *data;
ctx_id = FIELD_GET(IOMFB_MSG_CONTEXT, message);
offset = FIELD_GET(IOMFB_MSG_OFFSET, message);
length = FIELD_GET(IOMFB_MSG_LENGTH, message);
channel_offset = dcp_channel_offset(ctx_id);
if (channel_offset < 0) {
dev_warn(dcp->dev, "invalid context received %u\n", ctx_id);
return;
}
data = dcp->shmem + channel_offset + offset;
if (FIELD_GET(IOMFB_MSG_ACK, message))
dcpep_handle_ack(dcp, ctx_id, data, length);
else
dcpep_handle_cb(dcp, ctx_id, data, length, offset);
}
struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect)
{
return (struct dcp_rect){ .x = rect->x1,
.y = rect->y1,
.w = drm_rect_width(rect),
.h = drm_rect_height(rect) };
}
u32 drm_format_to_dcp(u32 drm)
{
switch (drm) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
return fourcc_code('A', 'R', 'G', 'B');
case DRM_FORMAT_XBGR8888:
case DRM_FORMAT_ABGR8888:
return fourcc_code('A', 'B', 'G', 'R');
case DRM_FORMAT_XRGB2101010:
return fourcc_code('r', '0', '3', 'w');
}
pr_warn("DRM format %X not supported in DCP\n", drm);
return 0;
}
int dcp_get_modes(struct drm_connector *connector)
{
struct apple_connector *apple_connector = to_apple_connector(connector);
struct platform_device *pdev = apple_connector->dcp;
struct apple_dcp *dcp = platform_get_drvdata(pdev);
struct drm_device *dev = connector->dev;
struct drm_display_mode *mode;
int i;
for (i = 0; i < dcp->nr_modes; ++i) {
mode = drm_mode_duplicate(dev, &dcp->modes[i].mode);
if (!mode) {
dev_err(dev->dev, "Failed to duplicate display mode\n");
return 0;
}
drm_mode_probed_add(connector, mode);
}
return dcp->nr_modes;
}
EXPORT_SYMBOL_GPL(dcp_get_modes);
struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp,
const struct drm_display_mode *mode)
{
int i;
for (i = 0; i < dcp->nr_modes; ++i) {
if (drm_mode_match(mode, &dcp->modes[i].mode,
DRM_MODE_MATCH_TIMINGS |
DRM_MODE_MATCH_CLOCK))
return &dcp->modes[i];
}
return NULL;
}
int dcp_mode_valid(struct drm_connector *connector,
const struct drm_display_mode *mode)
{
struct apple_connector *apple_connector = to_apple_connector(connector);
struct platform_device *pdev = apple_connector->dcp;
struct apple_dcp *dcp = platform_get_drvdata(pdev);
return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD;
}
EXPORT_SYMBOL_GPL(dcp_mode_valid);
int dcp_crtc_atomic_modeset(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
struct apple_dcp *dcp = platform_get_drvdata(apple_crtc->dcp);
struct drm_crtc_state *crtc_state;
int ret = -EIO;
bool modeset;
crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
if (!crtc_state)
return 0;
modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode;
if (!modeset)
return 0;
if (crtc_state->mode.hdisplay == 0 && crtc_state->mode.vdisplay == 0)
return 0;
switch (dcp->fw_compat) {
case DCP_FIRMWARE_V_12_3:
ret = iomfb_modeset_v12_3(dcp, crtc_state);
break;
case DCP_FIRMWARE_V_13_5:
ret = iomfb_modeset_v13_3(dcp, crtc_state);
break;
default:
WARN_ONCE(true, "Unexpected firmware version: %u\n",
dcp->fw_compat);
break;
}
return ret;
}
EXPORT_SYMBOL_GPL(dcp_crtc_atomic_modeset);
bool dcp_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
struct platform_device *pdev = apple_crtc->dcp;
struct apple_dcp *dcp = platform_get_drvdata(pdev);
return lookup_mode(dcp, mode) != NULL;
}
EXPORT_SYMBOL(dcp_crtc_mode_fixup);
void dcp_flush(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);
if (dcp_channel_busy(&dcp->ch_cmd))
{
dev_err(dcp->dev, "unexpected busy command channel\n");
schedule_work(&dcp->vblank_wq);
return;
}
switch (dcp->fw_compat) {
case DCP_FIRMWARE_V_12_3:
iomfb_flush_v12_3(dcp, crtc, state);
break;
case DCP_FIRMWARE_V_13_5:
iomfb_flush_v13_3(dcp, crtc, state);
break;
default:
WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
break;
}
}
EXPORT_SYMBOL_GPL(dcp_flush);
static void iomfb_start(struct apple_dcp *dcp)
{
switch (dcp->fw_compat) {
case DCP_FIRMWARE_V_12_3:
iomfb_start_v12_3(dcp);
break;
case DCP_FIRMWARE_V_13_5:
iomfb_start_v13_3(dcp);
break;
default:
WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
break;
}
}
bool dcp_is_initialized(struct platform_device *pdev)
{
struct apple_dcp *dcp = platform_get_drvdata(pdev);
return dcp->active;
}
EXPORT_SYMBOL_GPL(dcp_is_initialized);
void iomfb_recv_msg(struct apple_dcp *dcp, u64 message)
{
enum dcpep_type type = FIELD_GET(IOMFB_MESSAGE_TYPE, message);
if (type == IOMFB_MESSAGE_TYPE_INITIALIZED)
iomfb_start(dcp);
else if (type == IOMFB_MESSAGE_TYPE_MSG)
dcpep_got_msg(dcp, message);
else
dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message);
}
int iomfb_start_rtkit(struct apple_dcp *dcp)
{
dma_addr_t shmem_iova;
apple_rtkit_start_ep(dcp->rtk, IOMFB_ENDPOINT);
dcp->shmem = dma_alloc_coherent(dcp->dev, DCP_SHMEM_SIZE, &shmem_iova,
GFP_KERNEL);
dcp_send_message(dcp, IOMFB_ENDPOINT, dcpep_set_shmem(shmem_iova));
return 0;
}
void iomfb_shutdown(struct apple_dcp *dcp)
{
dcp->active = false;
dcp->valid_mode = false;
switch (dcp->fw_compat) {
case DCP_FIRMWARE_V_12_3:
iomfb_shutdown_v12_3(dcp);
break;
case DCP_FIRMWARE_V_13_5:
iomfb_shutdown_v13_3(dcp);
break;
default:
WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
break;
}
}