diff --git a/Documentation/devicetree/bindings/phy/apple,t6040-atcphy.yaml b/Documentation/devicetree/bindings/phy/apple,t6040-atcphy.yaml index ce11949c34455..04c8907584946 100644 --- a/Documentation/devicetree/bindings/phy/apple,t6040-atcphy.yaml +++ b/Documentation/devicetree/bindings/phy/apple,t6040-atcphy.yaml @@ -11,10 +11,13 @@ maintainers: description: > The Type-C PHY of the Apple T6040 (M4 Pro) SoC, as used for USB2 and USB3 - host operation. USB3 is brought up by a PHY coprocessor running RTKit - firmware, reached through an ASC mailbox. The PHY also provides the reset - line of the attached DWC3 controller and acts as the Type-C orientation - switch. + host operation and for DisplayPort. USB3 is brought up by a PHY coprocessor + running RTKit firmware, reached through an ASC mailbox. The PHY also + provides the reset line of the attached DWC3 controller and acts as the + Type-C orientation switch and, where the port carries DisplayPort alt mode, + as its mode switch. The same PHY drives the fixed DisplayPort link of an + HDMI converter; that instance has no USB controller behind it and therefore + no PIPE interface. $defs: apple,tunable: @@ -35,6 +38,7 @@ properties: const: apple,t6040-atcphy reg: + minItems: 10 items: - description: USB2 PHY controls - description: USB2 event counter controls @@ -49,21 +53,37 @@ properties: - description: ATC common configuration controls reg-names: - items: - - const: bank0 - - const: bank1 - - const: pipehandler - - const: core - - const: cfg - - const: cpu - - const: cpu-data - - const: axi2af - - const: axi2af-lioa - - const: fabric - - const: common-cfg + oneOf: + - items: + - const: bank0 + - const: bank1 + - const: pipehandler + - const: core + - const: cfg + - const: cpu + - const: cpu-data + - const: axi2af + - const: axi2af-lioa + - const: fabric + - const: common-cfg + - description: A fixed DisplayPort PHY without a USB controller + items: + - const: bank0 + - const: bank1 + - const: core + - const: cfg + - const: cpu + - const: cpu-data + - const: axi2af + - const: axi2af-lioa + - const: fabric + - const: common-cfg "#phy-cells": const: 1 + description: > + The PHY type (dt-bindings/phy/phy.h): PHY_TYPE_USB2, PHY_TYPE_USB3 or + PHY_TYPE_DP. "#reset-cells": const: 0 @@ -81,9 +101,13 @@ properties: orientation-switch: true + mode-switch: true + port: $ref: /schemas/graph.yaml#/properties/port - description: Link to the SuperSpeed lanes of the Type-C connector. + description: > + Link to the SuperSpeed lanes of the Type-C connector. Absent on a fixed + DisplayPort PHY. apple,tunable-axi2af: $ref: "#/$defs/apple,tunable" @@ -134,9 +158,24 @@ required: - reg - reg-names - "#phy-cells" - - "#reset-cells" - power-domains +allOf: + - if: + properties: + reg-names: + contains: + const: pipehandler + then: + required: + - "#reset-cells" + else: + properties: + "#reset-cells": false + orientation-switch: false + mode-switch: false + port: false + additionalProperties: false examples: @@ -170,4 +209,30 @@ examples: orientation-switch; }; }; + - | + soc { + #address-cells = <2>; + #size-cells = <2>; + + phy@39aa90000 { + compatible = "apple,t6040-atcphy"; + reg = <0x3 0x9aa90000 0x0 0x4000>, + <0x3 0x9a800000 0x0 0x4000>, + <0x3 0x9b000000 0x0 0x30000>, + <0x3 0x9b200000 0x0 0x4000>, + <0x3 0x9b140000 0x0 0x4000>, + <0x3 0x9b0c0000 0x0 0x10000>, + <0x3 0x98000000 0x0 0x8000>, + <0x3 0x98020000 0x0 0x4000>, + <0x3 0x9b244000 0x0 0x4000>, + <0x3 0x9b250000 0x0 0x4000>; + reg-names = "bank0", "bank1", "core", "cfg", "cpu", "cpu-data", + "axi2af", "axi2af-lioa", "fabric", "common-cfg"; + #phy-cells = <1>; + power-domains = <&ps_atc3_common_dp>; + mboxes = <&atcphy3_mbox>; + mbox-names = "phy-cpu"; + nonposted-mmio; + }; + }; ... diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig index 83fa85f4864cc..06415026853f9 100644 --- a/drivers/gpu/drm/apple/Kconfig +++ b/drivers/gpu/drm/apple/Kconfig @@ -14,6 +14,8 @@ config DRM_APPLE select DRM_GEM_SHMEM_HELPER select DRM_GEM_DMA_HELPER select VIDEOMODE_HELPERS + # write-combined maps of firmware-placed carveouts (desktop 26.x) + select VMAP_PFN depends on BACKLIGHT_CLASS_DEVICE select MULTIPLEXER select DRM_APPLE_FABRIC_CORE diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 91b6aba0c8bbd..a592e776e99ab 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -14,6 +14,7 @@ appledrm-y += dcpext_drm.o appledrm-y += iomfb_v12_3.o appledrm-y += iomfb_v13_3.o appledrm-y += iomfb_v14_7_tpl.o +appledrm-y += iomfb_v26_6_desktop.o appledrm-y += iomfb_v14_7.o iomfb_v14_7_link.o appledrm-$(CONFIG_DRM_APPLE_DCP_T8132) += iomfb_m4.o appledrm-y += iomfb_h17p.o diff --git a/drivers/gpu/drm/apple/afk-fragment.h b/drivers/gpu/drm/apple/afk-fragment.h new file mode 100644 index 0000000000000..63467b39c01de --- /dev/null +++ b/drivers/gpu/drm/apple/afk-fragment.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef __APPLE_DCP_AFK_FRAGMENT_H__ +#define __APPLE_DCP_AFK_FRAGMENT_H__ + +#include +#include + +/* Native 26.6 AV traffic: repeated 8-byte header, then up to 1028 bytes + * of the logical message (timestamp, subheader, descriptor and payload). + * The total length is repeated and the endpoint sequence advances on every + * fragment, including 255 -> 0. Messages are not interleaved on an endpoint. + */ +#define AFK_COMPACT_FRAGMENT_SIZE 1028U +#define AFK_COMPACT_MESSAGE_MAX (1024U * 1024U) + +struct afk_fragment_state { + u32 total; + u32 received; + u16 intf_id; + u8 next_seq; +}; + +/* Validate before allocating/copying. Returns 1 on completion, 0 while + * collecting, or an error. The caller discards the whole message on error. + */ +static inline int afk_fragment_accept(struct afk_fragment_state *s, + u16 intf_id, u8 seq, u32 total, u32 size) +{ + if (total < 16 || total > AFK_COMPACT_MESSAGE_MAX || !size || size > total) + return -EMSGSIZE; + if (s->received && (s->total != total || s->intf_id != intf_id || + s->next_seq != seq)) + return -EPROTO; + if (size > total - s->received) + return -EMSGSIZE; + + s->total = total; + s->intf_id = intf_id; + s->next_seq = seq + 1; + s->received += size; + return s->received == total; +} + +#endif diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index de9c3846b15b8..248ca42640b40 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,8 @@ enum rbep_msg_type { RBEP_START_ACK = 0x86, RBEP_SEND = 0xa2, RBEP_RECV = 0x85, + RBEP_SPACE_AVAILABLE = 0xa7, + RBEP_WAIT_FOR_SPACE = 0xa8, RBEP_SHUTDOWN = 0xc0, RBEP_SHUTDOWN_ACK = 0xc1, }; @@ -59,6 +62,7 @@ enum rbep_msg_type { #define SEND_WPTR GENMASK(31, 0) static void afk_service_worker(struct work_struct *work); +static bool afk_compact_capable(struct apple_dcp_afkep *ep); static void afk_discard_pending_open(struct apple_epic_service *service); void afk_service_retry(struct apple_dcp_afkep *ep) @@ -110,7 +114,19 @@ struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, goto out_free_afkep; } + /* Compact EPIC replies to firmware calls go out from their own queue. */ + if (afk_compact_capable(afkep)) { + afkep->tx_wq = alloc_ordered_workqueue("apple-dcp-afkep%02x-tx", + WQ_MEM_RECLAIM, endpoint); + if (!afkep->tx_wq) { + destroy_workqueue(afkep->wq); + ret = -ENOMEM; + goto out_free_afkep; + } + } INIT_WORK(&afkep->service_work, afk_service_worker); + INIT_LIST_HEAD(&afkep->compact_cmds); + mutex_init(&afkep->compact_tx_lock); init_completion(&afkep->started); init_completion(&afkep->stopped); spin_lock_init(&afkep->lock); @@ -137,10 +153,17 @@ void afk_quiesce(struct apple_dcp_afkep *afkep) spin_lock_irqsave(&afkep->lock, flags); afkep->stopping = true; spin_unlock_irqrestore(&afkep->lock, flags); + afk_cancel_commands(afkep); if (afkep->wq) { destroy_workqueue(afkep->wq); afkep->wq = NULL; } + if (afkep->tx_wq) { + destroy_workqueue(afkep->tx_wq); + afkep->tx_wq = NULL; + } + kvfree(afkep->compact_rx_data); + afkep->compact_rx_data = NULL; for (i = 0; i < afkep->num_channels; i++) if (afkep->services[i].pending_open) afk_discard_pending_open(&afkep->services[i]); @@ -398,6 +421,822 @@ static bool afk_epic_channel_retired(struct apple_dcp_afkep *ep, u32 channel) return false; } +/* + * Compact EPIC, as the desktop 26.x firmware (T6041, M4 Pro/Max) speaks it + * on its system and auxiliary endpoints. A message is a transport header + * (sequence, flags, interface, total length) followed by the timestamp, a + * sub-header and the payload; messages longer than a fragment arrive in + * fragments that repeat the header. Services announce themselves with + * type 0x11 and are opened with type 0x12; commands, replies and calls + * carry a status/length descriptor whose status the reply echoes. + * + * Only DCP_FIRMWARE_V_26_6_DESKTOP endpoints switch to it, on the first + * compact message they receive. + */ +struct afk_compact_cmd { + struct list_head node; + struct completion done; + u16 channel; + u8 type; + bool service_call; + u16 group; + u32 command; + u32 request_status; + void *output; + size_t output_len; + size_t copied; + u32 retcode; + int status; +}; + +static bool afk_compact_capable(struct apple_dcp_afkep *ep) +{ + return ep->dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP; +} + +static u32 afk_compact_next_status(struct apple_epic_service *service) +{ + unsigned long flags; + u32 status; + + spin_lock_irqsave(&service->lock, flags); + status = (u32)service->cmd_tag++ << 8; + spin_unlock_irqrestore(&service->lock, flags); + + return status; +} + +/* The caller holds ep->lock. */ +static void afk_fail_commands_locked(struct apple_dcp_afkep *ep, int error) +{ + struct afk_compact_cmd *cmd, *tmp; + + list_for_each_entry_safe(cmd, tmp, &ep->compact_cmds, node) { + list_del_init(&cmd->node); + cmd->status = error; + complete(&cmd->done); + } +} + +void afk_cancel_commands(struct apple_dcp_afkep *ep) +{ + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + ep->compact_shutting_down = true; + afk_fail_commands_locked(ep, -ESHUTDOWN); + spin_unlock_irqrestore(&ep->lock, flags); +} + +/* + * A message was sent only in part: the firmware waits for the rest, and a + * new message header would be taken as its continuation. Fail what is in + * flight and refuse further sends; the endpoint stays unusable until it is + * started again. + */ +static void afk_compact_tx_desync(struct apple_dcp_afkep *ep, int error) +{ + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + if (!ep->compact_tx_desync) + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: compact message sent in part (%d); endpoint unusable until restarted\n", + ep->endpoint, error); + ep->compact_tx_desync = true; + afk_fail_commands_locked(ep, -EIO); + spin_unlock_irqrestore(&ep->lock, flags); +} + +static int afk_send_compact_fragment(struct apple_dcp_afkep *ep, u16 intf_id, + const void *body, size_t size, u32 total) +{ + struct epic_compact_hdr *chdr; + struct afk_qe *qhdr; + unsigned long flags; + void *message; + size_t message_size, advance; + u32 rptr, wptr, old_wptr; + int ret = 0; + + if (!ep->txbfr.ready || !ep->txbfr.stride) + return -EIO; + + message_size = offsetof(struct epic_compact_hdr, timestamp) + size; + message = kzalloc(message_size, GFP_KERNEL); + if (!message) + return -ENOMEM; + + chdr = message; + chdr->intf_id = cpu_to_le16(intf_id); + chdr->length = cpu_to_le32(total); + memcpy(message + offsetof(struct epic_compact_hdr, timestamp), body, size); + + spin_lock_irqsave(&ep->lock, flags); + if (ep->compact_shutting_down || ep->stopping) { + ret = -ESHUTDOWN; + goto out_unlock; + } + if (ep->compact_tx_desync) { + ret = -EIO; + goto out_unlock; + } + + dma_rmb(); + rptr = le32_to_cpu(READ_ONCE(*ep->txbfr.rptr)); + wptr = le32_to_cpu(READ_ONCE(*ep->txbfr.wptr)); + old_wptr = wptr; + advance = ALIGN(sizeof(*qhdr) + message_size, ep->txbfr.stride); + + /* Both pointers live in shared memory: never trust them unchecked. */ + if (!afk_ring_ptrs_valid(rptr, wptr, ep->txbfr.bufsz, ep->txbfr.stride) || + ep->txbfr.bufsz - wptr < sizeof(*qhdr)) { + dev_warn_ratelimited(ep->dcp->dev, + "AFK[ep:%02x]: invalid transmit ring pointers: rptr 0x%x wptr 0x%x\n", + ep->endpoint, rptr, wptr); + ret = -EPROTO; + goto out_unlock; + } + if (advance > ep->txbfr.bufsz) { + ret = -EMSGSIZE; + goto out_unlock; + } + if (wptr < rptr && advance >= rptr - wptr) { + ret = -EAGAIN; + goto out_unlock; + } + if (wptr >= rptr) { + bool fits_above = advance < ep->txbfr.bufsz - wptr || + (advance == ep->txbfr.bufsz - wptr && rptr != 0); + + if (!fits_above && advance >= rptr) { + ret = -EAGAIN; + goto out_unlock; + } + } + + chdr->seq = ep->qe_seq++; + qhdr = ep->txbfr.buf + wptr; + qhdr->magic = cpu_to_le32(QE_MAGIC); + qhdr->size = cpu_to_le32(message_size); + qhdr->channel = cpu_to_le32(0); + qhdr->type = cpu_to_le32(0); + wptr += sizeof(*qhdr); + + /* No room for the payload: wrap, repeating the queue entry at 0. */ + if (message_size > ep->txbfr.bufsz - wptr) { + struct afk_qe *qhdr2 = ep->txbfr.buf; + + memcpy(qhdr2, qhdr, sizeof(*qhdr)); + qhdr = qhdr2; + wptr = sizeof(*qhdr); + } + + memcpy(qhdr->data, message, message_size); + wptr = ALIGN(wptr + message_size, ep->txbfr.stride); + if (wptr == ep->txbfr.bufsz) + wptr = 0; + + dma_wmb(); + WRITE_ONCE(*ep->txbfr.wptr, cpu_to_le32(wptr)); + dma_wmb(); + /* The firmware's own client rings with RECV and both pointers. */ + afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_RECV) | + ((u64)(rptr & 0xffff) << 32) | + ((u64)(wptr & 0xffff) << 16) | (old_wptr & 0xffff)); + +out_unlock: + spin_unlock_irqrestore(&ep->lock, flags); + kfree(message); + return ret; +} + +/* + * Send one complete message. Fragments carry no offset or tag beyond the + * repeated interface/length and the per-fragment sequence number, so the + * fragments of one message must not interleave with another's. + */ +static int afk_send_compact(struct apple_dcp_afkep *ep, u16 intf_id, u8 type, + u8 category, const void *payload, size_t payload_size, + size_t wire_size) +{ + struct epic_compact_sub_hdr *sub; + size_t total, offset = 0, chunk; + unsigned long deadline; + bool waiting_for_space = false; + void *body; + int ret = 0; + + if (payload_size > wire_size || wire_size > AFK_COMPACT_MESSAGE_MAX - 16) + return -EMSGSIZE; + total = 8 + sizeof(*sub) + wire_size; + body = kvzalloc(total, GFP_KERNEL); + if (!body) + return -ENOMEM; + sub = body + 8; /* the timestamp precedes the sub-header */ + sub->type = type; + sub->category = category; + sub->unk = type == 0x12; + if (payload_size) + memcpy(sub + 1, payload, payload_size); + + mutex_lock(&ep->compact_tx_lock); + deadline = jiffies + msecs_to_jiffies(5000); + while (offset < total) { + chunk = min_t(size_t, total - offset, AFK_COMPACT_FRAGMENT_SIZE); + ret = afk_send_compact_fragment(ep, intf_id, body + offset, + chunk, total); + if (ret == -EAGAIN) { + if (!waiting_for_space) { + afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_WAIT_FOR_SPACE) | + (chunk + offsetof(struct epic_compact_hdr, timestamp))); + waiting_for_space = true; + } + if (time_after_eq(jiffies, deadline)) { + ret = -ETIMEDOUT; + break; + } + /* The firmware advances the shared consumer pointer itself. */ + usleep_range(500, 1000); + continue; + } + if (ret) + break; + waiting_for_space = false; + offset += chunk; + } + /* Never start a new header into an unfinished remote assembly. */ + if (ret && offset) + afk_compact_tx_desync(ep, ret); + mutex_unlock(&ep->compact_tx_lock); + kvfree(body); + return ret; +} + +int afk_start_service(struct apple_epic_service *service) +{ + int ret; + + if (!service || !service->enabled) + return -EINVAL; + if (!READ_ONCE(service->ep->compact) || service->started) + return 0; + + ret = afk_send_compact(service->ep, service->channel, 0x12, 0, + NULL, 0, 0); + if (!ret) + service->started = true; + return ret; +} + +/* "prefix:name[:suffix]": match on name; "*" takes every service. */ +static const struct apple_epic_service_ops * +afk_compact_match_service(struct apple_dcp_afkep *ep, const char *name) +{ + const struct apple_epic_service_ops *ops; + const char *service_name; + size_t len; + + if (!name[0] || !ep->ops) + return NULL; + + service_name = strchr(name, ':'); + if (service_name) + service_name++; + else + service_name = name; + + for (ops = ep->ops; ops->name[0]; ops++) { + if (!strcmp(ops->name, "*")) + return ops; + + len = strlen(ops->name); + if (strncmp(ops->name, service_name, len)) + continue; + if (service_name[len] && service_name[len] != ':') + continue; + + return ops; + } + + return NULL; +} + +/* A free slot for a newly announced service, or the one it replaces. */ +static struct apple_epic_service * +afk_compact_slot(struct apple_dcp_afkep *ep, + const struct apple_epic_service_ops *ops, + const char *name, s64 unit) +{ + struct apple_epic_service *service; + unsigned long flags; + bool free; + u32 i; + + /* + * The DPDEV, DPAVSERV and AV services are announced again under a + * fresh interface after every external hotplug, without a teardown + * for the previous one: replace the same logical service in place. + * Other endpoints (DPAVCTRL) announce several services with the same + * name and no unit, so the name is no key there. + */ + if (ep->endpoint >= 0x27 && ep->endpoint <= 0x29) { + for (i = 0; i < ep->num_channels; i++) { + service = &ep->services[i]; + if (service->enabled && service->ops == ops && + service->unit == unit && !strcmp(service->name, name)) { + dev_info(ep->dcp->dev, + "AFK[ep:%02x]: replacing compact service %s interface %u\n", + ep->endpoint, name, service->channel); + afk_remove_service_debugfs(service); + afk_service_retire(service); + if (service->ops->teardown) + service->ops->teardown(service); + return service; + } + } + } + + for (i = 0; i < ep->num_channels; i++) { + service = &ep->services[i]; + spin_lock_irqsave(&service->lock, flags); + free = !service->enabled && !service->users && + bitmap_empty(service->cmd_map, MAX_PENDING_CMDS); + spin_unlock_irqrestore(&service->lock, flags); + if (free) + return service; + } + + if (ep->num_channels >= AFK_MAX_CHANNEL) + return NULL; + return &ep->services[ep->num_channels++]; +} + +static void afk_recv_handle_compact_init(struct apple_dcp_afkep *ep, + u16 channel, const void *payload, + size_t payload_size) +{ + char wire_name[33], service_name[33]; + const struct apple_epic_service_ops *ops; + struct apple_epic_service *service; + unsigned long flags; + const char *name; + char *unit_suffix; + s64 unit = -1; + int ret; + + if (payload_size < 32) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: short compact service announcement\n", + ep->endpoint); + return; + } + if (afk_epic_find_service(ep, channel)) { + dev_warn(ep->dcp->dev, + "AFK[ep:%02x]: duplicate compact service on interface %u\n", + ep->endpoint, channel); + return; + } + memcpy(wire_name, payload, 32); + wire_name[32] = '\0'; + name = strchr(wire_name, ':'); + name = name ? name + 1 : wire_name; + strscpy(service_name, name, sizeof(service_name)); + + unit_suffix = strrchr(service_name, ':'); + if (unit_suffix && !kstrtos64(unit_suffix + 1, 10, &unit)) + *unit_suffix = '\0'; + else + unit = -1; + + ops = afk_compact_match_service(ep, wire_name); + if (!ops) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: unable to match compact service %s on interface %u\n", + ep->endpoint, wire_name, channel); + return; + } + + service = afk_compact_slot(ep, ops, service_name, unit); + if (!service) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: too many enabled compact services\n", + ep->endpoint); + return; + } + + spin_lock_irqsave(&service->lock, flags); + service->ops = ops; + service->ep = ep; + service->channel = channel; + service->cookie = NULL; + service->cmd_tag = 0; + strscpy(service->name, service_name, sizeof(service->name)); + service->unit = unit; + service->defer_start = false; + service->started = false; + service->torndown = false; + /* A service with an admission step is published once admitted. */ + service->enabled = !ops->prepare; + spin_unlock_irqrestore(&service->lock, flags); + + if (ops->prepare) { + ret = ops->prepare(service, service_name, service_name, unit); + if (ret) { + dev_info(ep->dcp->dev, + "AFK[ep:%02x]: compact service %s on interface %u not admitted: %d\n", + ep->endpoint, wire_name, channel, ret); + afk_service_discard(service); + return; + } + afk_service_publish(service); + } + + if (ops->init) + ops->init(service, service_name, service_name, unit); + + dev_info(ep->dcp->dev, + "AFK[ep:%02x]: new compact service %s on interface %u%s\n", + ep->endpoint, wire_name, channel, + service->defer_start ? " (deferred)" : ""); + afk_populate_service_debugfs(service); + + if (!service->defer_start) { + ret = afk_start_service(service); + if (ret) + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: failed to start compact service %u: %d\n", + ep->endpoint, channel, ret); + } +} + +static void afk_recv_handle_compact_reply(struct apple_dcp_afkep *ep, + u16 channel, u8 type, + const void *payload, + size_t payload_size) +{ + const struct epic_compact_desc *desc = payload; + const struct epic_service_call *call = NULL; + struct afk_compact_cmd *pending, *match = NULL; + unsigned long flags; + size_t copy_size; + + if (payload_size < sizeof(*desc)) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: compact reply is missing its descriptor\n", + ep->endpoint); + return; + } + + if (payload_size >= sizeof(*desc) + sizeof(*call)) { + call = (const void *)(desc + 1); + if (le32_to_cpu(call->magic) != EPIC_SERVICE_CALL_MAGIC) + call = NULL; + } + + spin_lock_irqsave(&ep->lock, flags); + list_for_each_entry(pending, &ep->compact_cmds, node) { + if (pending->channel != channel || pending->type != type) + continue; + if (pending->request_status != le32_to_cpu(desc->status)) + continue; + if (pending->service_call && + (!call || pending->group != le16_to_cpu(call->group) || + pending->command != le32_to_cpu(call->command))) + continue; + match = pending; + break; + } + if (!match) { + spin_unlock_irqrestore(&ep->lock, flags); + dev_warn(ep->dcp->dev, + "AFK[ep:%02x]: unexpected compact reply %02x on interface %u\n", + ep->endpoint, type, channel); + return; + } + + list_del_init(&match->node); + match->retcode = le32_to_cpu(desc->status); + match->status = 0; + if (call && le32_to_cpu(call->data_len) > + payload_size - sizeof(*desc) - sizeof(*call)) { + match->status = -EMSGSIZE; + } else { + copy_size = min(match->output_len, payload_size - sizeof(*desc)); + if (copy_size && match->output) + memcpy(match->output, desc + 1, copy_size); + match->copied = copy_size; + } + complete(&match->done); + spin_unlock_irqrestore(&ep->lock, flags); +} + +/* + * A reply to a firmware call, sent from the endpoint's transmit queue: a + * send may wait for ring space, which the receive worker frees by draining + * the other direction. + */ +struct afk_compact_reply { + struct work_struct work; + struct apple_dcp_afkep *ep; + u16 channel; + size_t size; + u8 data[]; +}; + +static void afk_compact_reply_worker(struct work_struct *work) +{ + struct afk_compact_reply *r = container_of(work, struct afk_compact_reply, work); + int ret; + + ret = afk_send_compact(r->ep, r->channel, EPIC_SUBTYPE_STD_SERVICE, 2, + r->data, r->size, r->size); + if (ret) + dev_err(r->ep->dcp->dev, + "AFK[ep:%02x]: failed to reply to compact service call: %d\n", + r->ep->endpoint, ret); + kvfree(r); +} + +static void afk_recv_handle_compact_call(struct apple_dcp_afkep *ep, + u16 channel, const void *payload, + size_t payload_size) +{ + const struct epic_compact_desc *request_desc = payload; + const struct epic_service_call *call; + struct epic_compact_desc *reply_desc; + struct epic_service_call *reply_call; + struct apple_epic_service *service; + size_t inner_size, call_size; + struct afk_compact_reply *r; + u16 group; + u32 command; + void *reply; + int ret; + + if (payload_size < sizeof(*request_desc) + sizeof(*call) || !ep->tx_wq) + return; + + call = payload + sizeof(*request_desc); + inner_size = payload_size - sizeof(*request_desc); + call_size = le32_to_cpu(call->data_len); + if (le32_to_cpu(call->magic) != EPIC_SERVICE_CALL_MAGIC || + inner_size < sizeof(*call) + call_size) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: malformed compact service call on interface %u\n", + ep->endpoint, channel); + return; + } + group = le16_to_cpu(call->group); + command = le32_to_cpu(call->command); + + r = kvzalloc(struct_size(r, data, payload_size), GFP_KERNEL); + if (!r) + return; + INIT_WORK(&r->work, afk_compact_reply_worker); + r->ep = ep; + r->channel = channel; + r->size = payload_size; + reply = r->data; + reply_desc = reply; + reply_desc->status = request_desc->status; + reply_call = reply + sizeof(*reply_desc); + memcpy(reply_call, call, sizeof(*call)); + + service = afk_service_get(afk_epic_find_service(ep, channel)); + if (service && service->ops->call_v2) + ret = service->ops->call_v2(service, group, command, call + 1, + call_size, reply_call + 1, call_size); + else if (service && service->ops->call && !group) + ret = service->ops->call(service, command, call + 1, call_size, + reply_call + 1, call_size); + else + ret = -EOPNOTSUPP; + if (service) + afk_service_put(service); + if (ret == -EOPNOTSUPP) + dev_warn(ep->dcp->dev, + "AFK[ep:%02x]: no handler for compact service %u call %u:%u\n", + ep->endpoint, channel, group, command); + if (ret) + reply_desc->status = cpu_to_le32(ret); + + queue_work(ep->tx_wq, &r->work); +} + +static void afk_recv_compact_message(struct apple_dcp_afkep *ep, u16 intf_id, + const void *body, size_t size) +{ + const struct epic_compact_sub_hdr *sub = body + 8; + struct apple_epic_service *service; + const void *payload = sub + 1; + size_t payload_size = size - 8 - sizeof(*sub); + + if (sub->category == 0 && sub->type == 0x11) { + afk_recv_handle_compact_init(ep, intf_id, payload, payload_size); + return; + } + if (sub->category == 1 && sub->type == EPIC_SUBTYPE_STD_SERVICE) { + afk_recv_handle_compact_call(ep, intf_id, payload, payload_size); + return; + } + if (sub->category == 2) { + afk_recv_handle_compact_reply(ep, intf_id, sub->type, payload, + payload_size); + return; + } + if (sub->category == 0) { + /* A standard-service report carries a descriptor first. */ + if (sub->type == EPIC_SUBTYPE_STD_SERVICE && + payload_size >= sizeof(struct epic_compact_desc)) { + payload += sizeof(struct epic_compact_desc); + payload_size -= sizeof(struct epic_compact_desc); + } + service = afk_service_get(afk_epic_find_service(ep, intf_id)); + if (service && service->ops->report) + service->ops->report(service, sub->type, payload, + payload_size); + if (service) + afk_service_put(service); + return; + } + + dev_dbg(ep->dcp->dev, + "AFK[ep:%02x]: unhandled compact message %02x:%02x on interface %u\n", + ep->endpoint, sub->category, sub->type, intf_id); +} + +/* True if @data was a compact message (or fragment) and has been handled. */ +static bool afk_recv_handle_compact(struct apple_dcp_afkep *ep, u32 channel, + u32 type, const void *data, size_t data_size) +{ + const struct epic_compact_hdr *hdr = data; + const size_t header_size = offsetof(struct epic_compact_hdr, timestamp); + struct afk_fragment_state *s = &ep->compact_rx; + const struct epic_compact_sub_hdr *sub; + u32 offset, total; + u16 intf_id; + int ret; + + if (!afk_compact_capable(ep) || channel || type || + data_size < header_size) + return false; + total = le32_to_cpu(hdr->length); + intf_id = le16_to_cpu(hdr->intf_id); +retry: + offset = s->received; + /* Eight-byte empty control messages occur in the firmware's traffic. */ + if (!offset && total == 8 && data_size == header_size + 8) + return true; + /* A message starts with its timestamp and a plausible sub-header. */ + if (!offset) { + sub = data + header_size + 8; + if (data_size < header_size + 8 + sizeof(*sub) || sub->category > 2) { + if (!READ_ONCE(ep->compact)) + return false; + ret = -EPROTO; + goto discard; + } + } + ret = afk_fragment_accept(s, intf_id, hdr->seq, total, + data_size - header_size); + if (ret < 0) { + if (!READ_ONCE(ep->compact) && !offset) + return false; + /* + * A fragment that does not continue the message in assembly + * may start the next one, e.g. after a firmware restart: drop + * the partial message and take this one as a fresh start. + */ + if (offset) { + dev_err_ratelimited(ep->dcp->dev, + "AFK[ep:%02x]: compact message on interface %u abandoned at %u of %u bytes: %d\n", + ep->endpoint, s->intf_id, offset, s->total, ret); + kvfree(ep->compact_rx_data); + ep->compact_rx_data = NULL; + memset(s, 0, sizeof(*s)); + goto retry; + } + goto discard; + } + WRITE_ONCE(ep->compact, true); + if (!offset && ret == 1) { + memset(s, 0, sizeof(*s)); + afk_recv_compact_message(ep, intf_id, data + header_size, total); + return true; + } + if (!offset) { + ep->compact_rx_data = kvzalloc(total, GFP_KERNEL); + if (!ep->compact_rx_data) { + ret = -ENOMEM; + goto discard; + } + } + memcpy(ep->compact_rx_data + offset, data + header_size, + data_size - header_size); + if (ret == 1) { + void *message = ep->compact_rx_data; + + ep->compact_rx_data = NULL; + memset(s, 0, sizeof(*s)); + afk_recv_compact_message(ep, intf_id, message, total); + kvfree(message); + } + return true; + +discard: + dev_err_ratelimited(ep->dcp->dev, + "AFK[ep:%02x]: invalid compact fragment intf=%u seq=%u total=%u offset=%u: %d\n", + ep->endpoint, intf_id, hdr->seq, total, offset, ret); + kvfree(ep->compact_rx_data); + ep->compact_rx_data = NULL; + memset(s, 0, sizeof(*s)); + return true; +} + +/* + * A command on a compact endpoint: the payload behind a descriptor whose + * status the reply echoes. @retcode is the reply's status. + */ +static int afk_send_compact_command(struct apple_epic_service *service, u8 type, + const void *payload, size_t payload_len, + void *output, size_t output_len, + u32 request_status, u32 *retcode, + size_t *copied, unsigned long timeout) +{ + struct apple_dcp_afkep *ep = service->ep; + const struct epic_service_call *call = payload; + struct afk_compact_cmd pending; + struct epic_compact_desc *desc; + size_t wire_size = max(payload_len, output_len); + unsigned long waited, flags; + void *wrapped; + int ret; + + wrapped = kvzalloc(sizeof(*desc) + wire_size, GFP_KERNEL); + if (!wrapped) + return -ENOMEM; + desc = wrapped; + desc->status = cpu_to_le32(request_status); + desc->length = cpu_to_le32(wire_size); + if (payload_len) + memcpy(desc + 1, payload, payload_len); + + memset(&pending, 0, sizeof(pending)); + INIT_LIST_HEAD(&pending.node); + init_completion(&pending.done); + pending.channel = service->channel; + pending.type = type; + pending.request_status = request_status; + pending.output = output; + pending.output_len = output_len; + pending.status = -EINPROGRESS; + if (payload_len >= sizeof(*call) && + le32_to_cpu(call->magic) == EPIC_SERVICE_CALL_MAGIC) { + pending.service_call = true; + pending.group = le16_to_cpu(call->group); + pending.command = le32_to_cpu(call->command); + } + + spin_lock_irqsave(&ep->lock, flags); + if (ep->compact_shutting_down || ep->stopping) { + spin_unlock_irqrestore(&ep->lock, flags); + ret = -ESHUTDOWN; + goto out; + } + list_add_tail(&pending.node, &ep->compact_cmds); + spin_unlock_irqrestore(&ep->lock, flags); + + ret = afk_send_compact(ep, service->channel, type, 1, wrapped, + sizeof(*desc) + payload_len, + sizeof(*desc) + wire_size); + if (ret) + goto remove_pending; + + waited = wait_for_completion_timeout(&pending.done, timeout); + spin_lock_irqsave(&ep->lock, flags); + if (!waited && !list_empty(&pending.node)) { + list_del_init(&pending.node); + pending.status = -ETIMEDOUT; + } + ret = pending.status; + if (retcode) + *retcode = pending.retcode; + if (copied) + *copied = pending.copied; + spin_unlock_irqrestore(&ep->lock, flags); + goto out; + +remove_pending: + spin_lock_irqsave(&ep->lock, flags); + if (!list_empty(&pending.node)) + list_del_init(&pending.node); + spin_unlock_irqrestore(&ep->lock, flags); +out: + kvfree(wrapped); + return ret; +} + static int afk_send_epic_token(struct apple_dcp_afkep *ep, u32 channel, u16 tag, enum epic_type etype, enum epic_category ecat, u8 stype, const void *payload, size_t payload_len, u64 token); @@ -1156,6 +1995,9 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type, u8 *payload; size_t payload_size; + if (afk_recv_handle_compact(ep, channel, type, data, data_size)) + return; + if (ep->wire_v2) { afk_v2_receive(ep, channel, type, data, data_size); return; @@ -1434,7 +2276,26 @@ static void afk_receive_message_worker(struct work_struct *work_) afk_recv_drain(work->ep); break; + case RBEP_SPACE_AVAILABLE: + /* The compact sender polls the shared consumer pointer. */ + if (!afk_compact_capable(work->ep)) + goto unknown; + break; + + case RBEP_WAIT_FOR_SPACE: + /* + * Compact endpoints ask for a wakeup when their ring fills: + * drain and publish rptr before acknowledging. + */ + if (!afk_compact_capable(work->ep)) + goto unknown; + afk_recv_drain(work->ep); + dma_wmb(); + afk_send(work->ep, FIELD_PREP(RBEP_TYPE, RBEP_SPACE_AVAILABLE)); + break; + default: +unknown: dev_err(work->ep->dcp->dev, "Received unknown AFK message type: 0x%x\n", type); } @@ -1807,6 +2668,24 @@ static int afk_send_command_common(struct apple_epic_service *service, u8 type, if (!service) return -ENODEV; ep = service->ep; + if (READ_ONCE(ep->compact)) { + u32 request_status = afk_compact_next_status(service); + size_t copied = 0; + u32 status = 0; + + ret = afk_send_compact_command(service, type, payload, payload_len, + output, output_len, request_status, + &status, &copied, + msecs_to_jiffies(timeout_ms)); + /* Success is the reply echoing the request's status. */ + if (!ret && retcode) + *retcode = status == request_status ? 0 : (status ?: ~0U); + if (!ret && received) + *received = copied; + if (!ret && reply_len) + *reply_len = copied; + goto err_put_service; + } /* AFKv2 replies report their length through reply_length instead. */ if (reply_len && ep->wire_v2) { ret = -EOPNOTSUPP; diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index 2b6609abe361a..291ee12402292 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "dcp.h" @@ -72,6 +73,12 @@ struct apple_epic_service { const char *pending_name, *pending_class; s64 pending_unit; + /* Compact EPIC (desktop 26.x): the announced identity and OPEN state. */ + char name[33]; + s64 unit; + bool defer_start; + bool started; + struct { struct dentry *entry; u8 *scratch; @@ -379,6 +386,35 @@ struct epic_sub_hdr_h17p { #define EPIC_SUBTYPE_ANNOUNCE_H17P 0x11 +/* + * Compact EPIC, as the desktop 26.x firmware (T6041) speaks it on its + * auxiliary endpoints: an 8-byte transport header, the 8-byte timestamp, + * an 8-byte sub-header and, for commands and replies, a status/length + * descriptor. Ring entries carry channel and type 0. + */ +struct epic_compact_hdr { + u8 seq; + u8 flags; + __le16 intf_id; + __le32 length; + __le64 timestamp; +} __packed; + +struct epic_compact_sub_hdr { + u8 type; + u8 category; + u8 unk; + u8 flags; + __le32 reserved; +} __packed; + +struct epic_compact_desc { + __le32 status; + __le32 length; +} __packed; + +#include "afk-fragment.h" + struct epic_cmd { __le32 retcode; __le64 rxbuf; @@ -460,6 +496,18 @@ struct apple_dcp_afkep { u16 qe_seq; bool stopping; /* lock: no new receive work after shutdown. */ + /* Compact EPIC, detected from the first compact message (desktop 26.x). */ + bool compact; + struct list_head compact_cmds; /* lock */ + bool compact_shutting_down; /* lock */ + /* A message went out in part; no further sends (lock). */ + bool compact_tx_desync; + /* Replies to firmware calls, see afk_compact_reply_worker(). */ + struct workqueue_struct *tx_wq; + struct mutex compact_tx_lock; + struct afk_fragment_state compact_rx; + void *compact_rx_data; + const struct apple_epic_service_ops *ops; struct apple_epic_service services[AFK_MAX_CHANNEL]; u32 num_channels; @@ -474,6 +522,8 @@ struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, int afk_v2_copy_owned(struct apple_epic_service *service, u64 dva, void *buffer, size_t size, bool to_peer); int afk_start(struct apple_dcp_afkep *ep); +int afk_start_service(struct apple_epic_service *service); +void afk_cancel_commands(struct apple_dcp_afkep *ep); void afk_shutdown(struct apple_dcp_afkep *ep); void afk_quiesce(struct apple_dcp_afkep *ep); int afk_receive_message(struct apple_dcp_afkep *ep, u64 message); diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 2bd0a4f443e99..2ce9fb420f2f4 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,11 @@ struct apple_drm_private { struct drm_device drm; + /* + * A T6041 panel refused the takeover trial: the boot framebuffer keeps + * the screen, the DRM device stays unregistered and its DCPs running. + */ + bool aborted; }; /* Component readiness is later than platform probe/bind. */ @@ -708,8 +714,13 @@ static int apple_probe_per_dcp(struct device *dev, drm_crtc_helper_add(&crtc->base, &apple_crtc_helper_funcs); /* T8132 has no qualified CTM transaction. */ - if (!iomfb_is_m4(dcp_data)) - drm_crtc_enable_color_mgmt(&crtc->base, 0, true, 0); + if (!iomfb_is_m4(dcp_data)) { + unsigned int gamma_size = dcp_gamma_lut_size(dcp); + + drm_crtc_enable_color_mgmt(&crtc->base, 0, true, gamma_size); + if (gamma_size) + drm_mode_crtc_set_gamma_size(&crtc->base, gamma_size); + } crtc->dcp = dcp; *crtc_mask = drm_crtc_mask(&crtc->base); @@ -723,7 +734,9 @@ static int apple_probe_per_dcp(struct device *dev, * and no encoder either, because the port's encoder spans every * pipeline that can drive it. */ - if (connector_type == DRM_MODE_CONNECTOR_USB) { + /* Desktop 26.x drives its Type-C route itself, as a pipeline output. */ + if (connector_type == DRM_MODE_CONNECTOR_USB && + !dcp_fw_compat_is_desktop(dcp)) { dcp_link(dcp, crtc, NULL); *encoder = NULL; return 0; @@ -941,7 +954,10 @@ static bool apple_dcp_usable(struct device_node *np) (!of_device_is_compatible(np, "apple,t6030-dcpext") || dcp_t6030_ext_native(np)) && (!of_device_is_compatible(np, "apple,t8140-dcpext") || - dcp_h17p_external_supported(np)); + dcp_h17p_external_supported(np)) && + /* t6041_dcpext=0 keeps the T6041 external processors out. */ + (!of_device_is_compatible(np, "apple,t6041-dcpext") || + dcp_t6041_ext_enabled()); } static void apple_drm_quiesce_connectors(struct drm_device *drm) @@ -1041,9 +1057,14 @@ static int apple_drm_init_dcp(struct device *dev) } /* - * Starting DPTX might take some time. + * Starting DPTX might take some time. The T6041 panel runs its + * client-open, main-display and power chain inside the start: give it + * as long as that takes. */ timeout = get_jiffies_64() + msecs_to_jiffies(3000); + for (i = 0; i < num_dcp; i++) + if (of_device_is_compatible(dcp[i]->dev.of_node, "apple,t6041-dcp")) + timeout = get_jiffies_64() + msecs_to_jiffies(10000); for (i = 0; i < num_dcp; ++i) { u64 jiffies = get_jiffies_64(); @@ -1162,6 +1183,34 @@ static int apple_drm_init(struct device *dev) * swap. Hold the modeset locks across activation so an early userspace * commit cannot race the retained startup surface. */ + /* + * T6041 panel: everything that can still be refused is tried while + * the boot framebuffer owns the screen; past the eviction there is no + * way back. On refusal leave the DCP running (its shutdown sequence + * can crash it and blank the panel), pin the module and keep the boot + * framebuffer. + */ + drm_for_each_crtc(crtc, &apple->drm) { + const char *reason = "none"; + + ret = dcp_takeover_trial(to_apple_crtc(crtc)->dcp, &reason); + if (!ret) + continue; + dev_err(dev, "display takeover refused (%s: %d); the boot framebuffer keeps the screen\n", + reason, ret); + /* + * The DCPs keep running and keep their pipelines: pin the + * module and the DRM device their CRTCs and connectors live + * in, and keep them out of shutdown and sleep. + */ + __module_get(THIS_MODULE); + drm_dev_get(&apple->drm); + drm_for_each_crtc(crtc, &apple->drm) + dcp_takeover_aborted(to_apple_crtc(crtc)->dcp); + apple->aborted = true; + return 0; + } + if (retained_modeset) drm_modeset_lock_all(&apple->drm); fb_size = fb_r.end - fb_r.start + 1; @@ -1191,6 +1240,10 @@ static int apple_drm_init(struct device *dev) /* the fabric keeps order only once registered: catch up on probe */ dcp_typec_reorder(); + /* A panel that publishes no level registers its backlight now. */ + drm_for_each_crtc(crtc, &apple->drm) + dcp_backlight_takeover_registered(to_apple_crtc(crtc)->dcp); + drm_client_setup_with_fourcc(&apple->drm, DRM_FORMAT_XRGB8888); /* A subsequent successful platform probe retries deferred GPU probes. */ @@ -1218,6 +1271,10 @@ static void apple_drm_uninit(struct device *dev) apple_t6030_display_withdraw(dev); + /* An aborted takeover leaves its DCPs running until the reboot. */ + if (apple->aborted) + return; + apple_drm_quiesce_connectors(&apple->drm); drm_dev_unplug(&apple->drm); drm_atomic_helper_shutdown(&apple->drm); @@ -1327,7 +1384,7 @@ static int apple_platform_suspend(struct device *dev) { struct apple_drm_private *apple = dev_get_drvdata(dev); - if (apple) + if (apple && !apple->aborted) return drm_mode_config_helper_suspend(&apple->drm); return 0; @@ -1355,7 +1412,7 @@ static int apple_platform_resume(struct device *dev) struct apple_drm_private *apple = dev_get_drvdata(dev); u32 dropped = 0; - if (apple) { + if (apple && !apple->aborted) { if (apple->drm.mode_config.suspend_state) dropped = apple_drm_drop_stale_crtcs(apple->drm.mode_config.suspend_state); /* Have userspace set the mode of a CRTC resumed disabled again. */ diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c index 461d162e2c6e9..d439ce69368b0 100644 --- a/drivers/gpu/drm/apple/av.c +++ b/drivers/gpu/drm/apple/av.c @@ -937,6 +937,7 @@ int avep_init(struct apple_dcp *dcp) break; case DCP_FIRMWARE_V_13_5: case DCP_FIRMWARE_V_14_7_TEMPLATE: + case DCP_FIRMWARE_V_26_6_DESKTOP: audiosrv_data->cmds = dcp_av_audio_cmds_v13_5; break; default: diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index 975db300e1652..b5e2eafb8c090 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -102,6 +102,9 @@ static bool dcp_hdmi_borrowed(void *ctx); bool dcp_is_typec_output(struct apple_dcp *dcp) { + /* Desktop 26.x keeps its own Type-C route, outside this fabric. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + return false; return dcp->active_typec_route || dcp->fixed_connector_type == DRM_MODE_CONNECTOR_USB; } @@ -1580,7 +1583,7 @@ static bool dcp_t602x_tunnel_route(struct apple_dcp_typec_route *route) * Crossbar connection up/down, looked up at runtime so appledrm does not * require the crossbar driver to be built. */ -static int dcp_dpxbar_link(struct mux_control *mux, bool up) +int dcp_dpxbar_link(struct mux_control *mux, bool up) { typeof(&apple_dpxbar_link_up) fn; int ret; @@ -3692,6 +3695,9 @@ irqreturn_t dcp_dp2hdmi_hpd_edge(int irq, void *data) { struct apple_dcp *dcp = data; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + return IRQ_WAKE_THREAD; + WRITE_ONCE(dcp->hdmi_edge_jiffies, jiffies); WRITE_ONCE(dcp->hdmi_edge_seen, true); dcp_hdmi_hpd_edge(data); @@ -4013,6 +4019,9 @@ irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data) { struct apple_dcp *dcp = data; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + return dcp_desktop_dp2hdmi_hpd(dcp); + guard(mutex)(&dcp_typec_fabric_lock); dcp_hdmi_update_locked(dcp); /* The GPIO edge detector is off throughout an oneshot handler. */ diff --git a/drivers/gpu/drm/apple/dcp-fabric.h b/drivers/gpu/drm/apple/dcp-fabric.h index 1176ef56874f3..869a2c3716cfe 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.h +++ b/drivers/gpu/drm/apple/dcp-fabric.h @@ -91,6 +91,7 @@ void dcp_unlink(struct drm_device *drm); int dcp_tunnel_crossbar_up(struct apple_dcp *dcp, u32 unit); int dcp_tunnel_crossbar_down(struct apple_dcp *dcp, u32 unit); int dcp_direct_crossbar_link(struct apple_dcp *dcp, bool up); +int dcp_dpxbar_link(struct mux_control *mux, bool up); /* * T6030 ATC PHY: keep the source owning the pixel clock across rate changes * and stop (linux/soc/apple/dp-tunnel.h, 170-usb). Declared here until that diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 2b831a51b2bdf..5950b5883d28f 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -4,6 +4,7 @@ #ifndef __APPLE_DCP_INTERNAL_H__ #define __APPLE_DCP_INTERNAL_H__ +#include #include #include #include @@ -33,6 +34,7 @@ #include "iomfb_v14_7.h" #include "iomfb_v14_7_tpl.h" #include "iomfb_v26_6.h" +#include "iomfb_v26_6_desktop.h" #include "epic/dpavservep.h" #include "version_utils.h" @@ -85,6 +87,11 @@ enum { * publishes the port service used by DPTX 0x2a. */ DPAV_CTRL_ENDPOINT = 0x24, + /* Desktop 26.x (T6041) auxiliary endpoints, see dcp_start_desktop(). */ + EPIC25_ENDPOINT = 0x25, + DPSAC_ENDPOINT = 0x26, + DPDEV_ENDPOINT = 0x27, + EPIC2C_ENDPOINT = 0x2c, DPAVSERV_ENDPOINT = 0x28, DPAVSERV_ENDPOINT_V2 = 0x2a, AV_ENDPOINT = 0x29, @@ -164,6 +171,8 @@ struct dcp_brightness { int nits; int scale; bool update; + /* A level was written through the backlight device (desktop 26.x). */ + bool requested; }; struct audiosrv_data; @@ -214,6 +223,66 @@ enum dcp_test_point { DCP_TEST_HOTPLUG_EVENT, /* the hotplug worker sends its event */ }; +/* + * DCP_FIRMWARE_V_26_6_DESKTOP state, see iomfb_v26_6_desktop.c; allocated + * for such a DCP only. + */ +struct dcp_desktop_state { + /* Queued swap; too large for the shared swap union. */ + struct dcp_swap_submit_req_v26_6_desktop swap; + /* Opaque client identity the firmware's IOMFB calls carry. */ + u64 client; + /* + * A mode was set since the display last went away. Until then the + * firmware's generic pipe is bootstrapped through a virtual color + * element before the real one is selected. + */ + bool mode_live; + /* + * GAMMA_LUT: the firmware's own table, read before the first change; + * the table being sent; the LUT it is composed from, if any. + */ + struct iomfb_gamma_table_req *gamma_fw, *gamma_out; + struct drm_color_lut gamma_lut[IOMFB_GAMMA_ENTRIES]; + bool gamma_fw_read, gamma_user, gamma_pending, gamma_unusable; + /* + * Endpoints 0x20..0x3f this processor's start brought up beside the + * system endpoint (bit = endpoint - 0x20), and those it skipped. + */ + struct apple_dcp_afkep *graph[0x20]; + u32 graph_started, graph_skipped; + /* DPAVCTRL (0x24): its two AV controllers and the deferred service. */ + struct apple_epic_service *dpavctrl_av_controller[2]; + struct apple_epic_service *dpavctrl_late_service; + struct completion dpavctrl_ready; + /* + * Takeover trial: power-on and a mode set ran before the boot + * framebuffer was evicted; the first atomic enable reuses them. + */ + bool trial_powered, trial_modeset_ok; + u32 trial_timing_mode_id, trial_color_mode_id; + /* Result of the last power-on: 0 ok, 1 timeout, 2 failed. */ + u8 poweron_result; + /* The takeover was refused: leave the DCP alone until the reboot. */ + bool takeover_aborted; + /* + * External processors: the firmware powered the pipe off with the + * display (unplug); the next swap needs a power-on and a mode set. + */ + bool ext_pipe_off; + /* + * Dynamic Type-C DP route (T6041 dcpext1): the DP PHYs and crossbars + * of every Type-C port, the one in use (or -1), the DPTX target PHY + * of each and the crossbar state selecting this processor. + */ + unsigned int ndyn; + int dyn_active; + struct phy *dyn_phy[3]; + struct mux_control *dyn_xbar[3]; + u32 dyn_dptx_phy[3]; + u32 dyn_mux_index; +}; + /* TODO: move IOMFB members to its own struct */ struct dcpext_scanout; struct dcp_hdmi; @@ -644,6 +713,9 @@ struct apple_dcp { struct delayed_work hdmi_recheck_wq; /* T6030 HDMI converter service requests, see dcp-hdmi.c */ struct dcp_hdmi *hdmi; + + /* Kept last: the template instances share the layout above. */ + struct dcp_desktop_state *desktop; }; /* T8132 firmware uses its own wire codec, with the common DRM/KMS owner. */ @@ -709,6 +781,7 @@ void dcp_announce_arm(struct apple_dcp *dcp); void dcp_announce_work(struct work_struct *work); void dcp_external_crtc_released(struct apple_dcp *dcp); void dcp_external_link_fault(struct apple_dcp *dcp); +irqreturn_t dcp_desktop_dp2hdmi_hpd(struct apple_dcp *dcp); /* A KUnit test can wait here for the work it races with. */ static inline void dcp_test_point(struct apple_dcp *dcp, enum dcp_test_point point) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index e2b7613897259..8a2e2c5bcbf10 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -33,8 +34,10 @@ #include #include #include +#include #include +#include #include #include #include @@ -212,6 +215,191 @@ static bool unstable_edid = true; module_param(unstable_edid, bool, 0644); MODULE_PARM_DESC(unstable_edid, "Enable unstable EDID retrival support"); +/* + * T6041 (M4 Pro/Max) external display processors, whose nodes the boot + * environment enables before the module loads. t6041_dcpext=0 keeps them + * out of the display's DRM device and off the hardware. + */ +static bool t6041_dcpext = true; +module_param(t6041_dcpext, bool, 0444); +MODULE_PARM_DESC(t6041_dcpext, + "T6041: drive the external display processors (default 1; 0 leaves them alone)"); + +bool dcp_t6041_ext_enabled(void) +{ + return t6041_dcpext; +} + +/* + * The backlight maximum of a desktop 26.x panel whose node has no + * apple,max-brightness: the J616s panel's user-accessible SDR maximum (ADT + * user-accessible-max-nits, 0x02580000 in 16.16). + */ +#define DCP_DESKTOP_MAX_NITS 600 + +static bool dcp_desktop_external(struct apple_dcp *dcp) +{ + return of_device_is_compatible(dcp->dev->of_node, "apple,t6041-dcpext"); +} + +/* + * The register map of the T6041 internal DCP, from the macOS ADT capture + * (/arm-io/dcp reg[0], /arm-io/disp0 reg[0..3]). The firmware maps these + * windows by index, so the device tree must describe exactly these. + */ +static const struct { + const char *name; + u64 start; + u64 size; +} t6041_dcp_regs[] = { + { "coproc", 0x402e00000ULL, 0x4000 }, + { "disp-0", 0x400000000ULL, 0x4000000 }, + { "disp-1", 0x402240000ULL, 0x10000 }, + { "disp-2", 0x4023a8000ULL, 0x4000 }, + { "disp-3", 0x402800000ULL, 0x800000 }, +}; + +/* Refuse to touch the coprocessor on any mismatch with the captured ADT. */ +static int dcp_validate_t6041_dt(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct device_node *piodma __free(device_node) = + of_get_child_by_name(dev->of_node, "piodma"); + int i; + + if (!of_device_is_compatible(dev->of_node, "apple,t6041-dcp")) + return 0; + + if (pdev->num_resources != ARRAY_SIZE(t6041_dcp_regs)) + return dev_err_probe(dev, -ENODEV, + "T6041 DCP: %u register windows, the ADT has %zu\n", + pdev->num_resources, ARRAY_SIZE(t6041_dcp_regs)); + + for (i = 0; i < ARRAY_SIZE(t6041_dcp_regs); i++) { + struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, i); + + if (!r || r->start != t6041_dcp_regs[i].start || + resource_size(r) != t6041_dcp_regs[i].size || + of_property_match_string(dev->of_node, "reg-names", + t6041_dcp_regs[i].name) != i) + return dev_err_probe(dev, -ENODEV, + "T6041 DCP: register window %d (%s) differs from the ADT\n", + i, t6041_dcp_regs[i].name); + } + + if (!of_property_present(dev->of_node, "iommus") || + !of_property_present(dev->of_node, "mboxes") || !piodma) + return dev_err_probe(dev, -ENODEV, + "T6041 DCP: iommus, mboxes or piodma missing\n"); + + return 0; +} + +/* + * Map a buffer the desktop 26.x firmware placed itself write-combined. + * It may be accessed through a realtime path, and a write-back alias + * creates AMCC directory state that can fault when the DCP then accesses + * the same memory. The buffers may lie in RAM or in carveouts without + * struct pages. + */ +static int dcp_vmap_wc(phys_addr_t phys, size_t size, + void __iomem **iomem, void **map_base) +{ + unsigned long offset = offset_in_page(phys); + unsigned long first_pfn = PHYS_PFN(phys); + phys_addr_t last; + size_t span, nr_pages; + unsigned int count, i; + void *base; + + if (!size) + return -EINVAL; + if (check_add_overflow(phys, (phys_addr_t)size - 1, &last) || + check_add_overflow(size, (size_t)offset, &span)) + return -EOVERFLOW; + if (last > PHYS_MASK) + return -EOVERFLOW; + nr_pages = (span - 1) / PAGE_SIZE + 1; + if (nr_pages > UINT_MAX) + return -E2BIG; + count = nr_pages; + + if (pfn_valid(first_pfn)) { + struct page **pages = kmalloc_array(count, sizeof(*pages), GFP_KERNEL); + + if (!pages) + return -ENOMEM; + for (i = 0; i < count; i++) { + if (!pfn_valid(first_pfn + i)) { + kfree(pages); + return -EINVAL; + } + pages[i] = pfn_to_page(first_pfn + i); + } + base = vmap(pages, count, VM_MAP, pgprot_writecombine(PAGE_KERNEL)); + kfree(pages); + } else { + unsigned long *pfns = kmalloc_array(count, sizeof(*pfns), GFP_KERNEL); + + if (!pfns) + return -ENOMEM; + for (i = 0; i < count; i++) { + if (pfn_valid(first_pfn + i)) { + kfree(pfns); + return -EINVAL; + } + pfns[i] = first_pfn + i; + } + base = vmap_pfn(pfns, count, pgprot_writecombine(PAGE_KERNEL)); + kfree(pfns); + } + if (!base) + return -ENOMEM; + + *map_base = base; + *iomem = (void __iomem *)(base + offset); + return 0; +} + +static int dcp_check_iova_contiguous(struct iommu_domain *domain, + dma_addr_t iova, size_t size, + phys_addr_t phys); + +static int dcp_desktop_shmem_setup(struct apple_dcp *dcp, + struct apple_rtkit_shmem *bfr) +{ + struct iommu_domain *domain = iommu_get_domain_for_dev(dcp->dev); + phys_addr_t phy_addr; + int ret; + + if (!domain) + return -ENODEV; + if (!bfr->size) + return -EINVAL; + if (bfr->size - 1 > (dma_addr_t)-1 - bfr->iova) + return -EOVERFLOW; + phy_addr = iommu_iova_to_phys(domain, bfr->iova); + if (!phy_addr) + return -EINVAL; + if (offset_in_page(phy_addr) != offset_in_page(bfr->iova)) + return -EINVAL; + if (phy_addr > PHYS_MASK || bfr->size - 1 > PHYS_MASK - phy_addr) + return -EOVERFLOW; + /* The CPU alias is contiguous: check the whole mapping. */ + ret = dcp_check_iova_contiguous(domain, bfr->iova, bfr->size, phy_addr); + if (ret) + return ret; + + ret = dcp_vmap_wc(phy_addr, bfr->size, &bfr->iomem, &bfr->private); + if (ret) + return ret; + + bfr->is_mapped = true; + dev_dbg(dcp->dev, "shmem_setup: iova %pad -> pa %pa\n", &bfr->iova, + &phy_addr); + return 0; +} + static void disconnected_hpd_event(struct apple_connector *connector); bool dcp_is_external_h17p(struct apple_dcp *dcp) @@ -675,6 +863,15 @@ static void dcp_delayed_vblank(struct work_struct *work) static struct apple_dcp_afkep *dcp_afkep(struct apple_dcp *dcp, u8 endpoint) { + /* The auxiliary endpoints of a desktop 26.x processor. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + endpoint >= 0x20 && endpoint < 0x40) { + struct apple_dcp_afkep *ep = + READ_ONCE(dcp->desktop->graph[endpoint - 0x20]); + + if (ep) + return ep; + } /* DPAV 0x2a on H17P overlaps the legacy DPTX endpoint number. */ if (dcp->dcpavservep && endpoint == dcp->dcpavservep->endpoint) return dcp->dcpavservep; @@ -860,6 +1057,9 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) struct apple_dcp *dcp = cookie; int ret; + if (bfr->iova && dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + return dcp_desktop_shmem_setup(dcp, bfr); + if (bfr->iova) { struct iommu_domain *domain = iommu_get_domain_for_dev(dcp->dev); @@ -910,7 +1110,9 @@ static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) { struct apple_dcp *dcp = cookie; - if (bfr->is_mapped) + if (bfr->is_mapped && bfr->private) + vunmap(bfr->private); + else if (bfr->is_mapped) memunmap(bfr->buffer); else dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova); @@ -1001,6 +1203,11 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_commit *commi crtc_state->vrr_enabled) return -EINVAL; + /* DRM core only caps the blob size; the gamma update copies a full table. */ + if (crtc_state->color_mgmt_changed && crtc_state->gamma_lut && + drm_color_lut_size(crtc_state->gamma_lut) != dcp_gamma_lut_size(pdev)) + return -EINVAL; + /* * A Type-C display routed to another pipeline: its route follows * this CRTC if it can (see dcp_typec_follow_check()), and the mode is @@ -1684,11 +1891,237 @@ static int dcp_dptx_connect_tile(struct apple_dcp *dcp) return ret; } +/* + * Desktop 26.x external processors (T6041 dcpext0 HDMI, dcpext1 USB-C). + * They keep the classic external model of the driver this was ported from: + * no Type-C display fabric, the DPTX link set up on HPD (HDMI) or on the + * DP alt mode hotplug of the port (USB-C), and the Type-C port chosen + * dynamically from the PHY that entered DP alt mode. + */ +/* The caller holds hpd_mutex, which also orders dcp->phy against a connect. */ +static int dcp_desktop_dyn_route_select_locked(struct apple_dcp *dcp) +{ + struct dcp_desktop_state *d = dcp->desktop; + typeof(&apple_t6040_phy_dp_alt) dp_alt; + unsigned int i; + int ret; + + lockdep_assert_held(&dcp->hpd_mutex); + if (!d->ndyn || d->dyn_active >= 0) + return 0; + /* Looked up at runtime so appledrm does not require the T6040 PHY driver. */ +#if IS_ENABLED(CONFIG_PHY_APPLE_T6040_USB2) + dp_alt = symbol_get(apple_t6040_phy_dp_alt); +#else + dp_alt = NULL; +#endif + if (!dp_alt) { + dev_dbg(dcp->dev, "T6040 PHY driver not loaded\n"); + return -ENODEV; + } + for (i = 0; i < d->ndyn; i++) + if (dp_alt(d->dyn_phy[i])) + break; +#if IS_ENABLED(CONFIG_PHY_APPLE_T6040_USB2) + symbol_put(apple_t6040_phy_dp_alt); +#endif + if (i == d->ndyn) { + dev_dbg(dcp->dev, "no Type-C port in DP alt mode\n"); + return -ENODEV; + } + ret = mux_control_select(d->dyn_xbar[i], d->dyn_mux_index); + if (ret) { + dev_warn(dcp->dev, "Type-C port %u: crossbar select: %d\n", i, ret); + return ret; + } + ret = phy_init(d->dyn_phy[i]); + if (!ret) { + ret = phy_power_on(d->dyn_phy[i]); + if (ret) + phy_exit(d->dyn_phy[i]); + } + if (ret) { + dev_warn(dcp->dev, "Type-C port %u: DP PHY init/power: %d\n", i, ret); + mux_control_deselect(d->dyn_xbar[i]); + return ret; + } + msleep(25); + dcp->phy = d->dyn_phy[i]; + dcp->xbar = d->dyn_xbar[i]; + dcp->dptx_phy = d->dyn_dptx_phy[i]; + d->dyn_active = i; + dev_dbg(dcp->dev, "Type-C port %u selected (DPTX target PHY %u, crossbar state %u)\n", + i, dcp->dptx_phy, d->dyn_mux_index); + return 0; +} + +static void dcp_desktop_dyn_route_release_locked(struct apple_dcp *dcp) +{ + struct dcp_desktop_state *d = dcp->desktop; + int i = d->dyn_active; + + lockdep_assert_held(&dcp->hpd_mutex); + if (!d->ndyn || i < 0) + return; + d->dyn_active = -1; + dcp->phy = NULL; + dcp->xbar = NULL; + phy_power_off(d->dyn_phy[i]); + phy_exit(d->dyn_phy[i]); + mux_control_deselect(d->dyn_xbar[i]); + dev_dbg(dcp->dev, "Type-C port %u released\n", i); +} + +/* Unbind: a selected route holds its crossbar and its powered PHY. */ +static void dcp_desktop_dyn_route_release(struct apple_dcp *dcp) +{ + if (!dcp->desktop) + return; + guard(mutex)(&dcp->hpd_mutex); + dcp_desktop_dyn_route_release_locked(dcp); +} + +static int dcp_desktop_dptx_connect(struct apple_dcp *dcp, u32 port) +{ + struct dptx_port *dptx = &dcp->dptxport[port]; + unsigned long left; + bool selected; + int ret = 0; + + mutex_lock(&dcp->hpd_mutex); + /* A route selected here is released again if the connect fails. */ + selected = !dcp->phy && dcp->desktop->ndyn; + if (selected) { + ret = dcp_desktop_dyn_route_select_locked(dcp); + if (ret) + goto out_unlock; + } + if (!dcp->phy) { + dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n"); + ret = -ENODEV; + goto out_unlock; + } + if (!dptx_service_available(dptx)) { + dev_warn(dcp->dev, "dcp_dptx_connect: dptx service for port %u not enabled\n", + port); + ret = -ENODEV; + goto out_release; + } + if (dptx->connected) + goto out_unlock; + + reinit_completion(&dptx->linkcfg_completion); + dptx->atcphy = dcp->phy; + ret = dptxport_connect(dptx->service, 0, dcp->dptx_phy, dcp->dptx_die, true); + if (ret) { + dev_warn(dcp->dev, "DPTX port %u: connect failed: %d\n", port, ret); + goto out_release; + } + ret = dptxport_request_display(dptx->service); + if (ret) { + dev_warn(dcp->dev, "DPTX port %u: display request failed: %d\n", + port, ret); + goto out_release; + } + dptx->connected = true; + + /* + * The firmware does not take HPD from the DP2HDMI converter on its + * own ("HPD not present, skipping mode export"): assert it right + * after the display request. + */ + if (READ_ONCE(dptx->service->ep->compact)) { + ret = dptxport_set_hpd(dptx->service, true); + if (ret) + dev_warn(dcp->dev, "DPTX port %u: set_hpd(1): %d\n", port, ret); + ret = 0; + } + mutex_unlock(&dcp->hpd_mutex); + + /* Without a sink the link is configured once one arrives. */ + left = wait_for_completion_timeout(&dptx->linkcfg_completion, + DPTX_CONNECT_TIMEOUT); + if (!left) + dev_dbg(dcp->dev, "DPTX port %u: no link configuration yet\n", port); + + usleep_range(5, 10); + + if (dcp->avep) + av_service_connect(dcp); + return 0; + +out_release: + if (selected) + dcp_desktop_dyn_route_release_locked(dcp); +out_unlock: + mutex_unlock(&dcp->hpd_mutex); + return ret; +} + +static int dcp_desktop_dptx_disconnect_oob(struct apple_dcp *dcp, u32 port) +{ + int ret; + + disconnected_hpd_event(dcp->connector); + if (dcp->avep) + av_service_disconnect(dcp); + scoped_guard(mutex, &dcp->hpd_mutex) { + if (dptx_service_available(&dcp->dptxport[port])) + dptxport_set_hpd(dcp->dptxport[port].service, false); + } + ret = dcp_dptx_disconnect(dcp, port); + scoped_guard(mutex, &dcp->hpd_mutex) + dcp_desktop_dyn_route_release_locked(dcp); + if (!dcp->main_display) + dcp->desktop->ext_pipe_off = true; + return ret; +} + +/* + * HDMI HPD of a desktop 26.x external processor. A disconnect is left to + * the firmware, which detects it itself. A cold plug after boot finds the + * port connected without a sink: the firmware never saw this HPD, so pulse + * the AP-asserted HPD to make it export the modes now. + */ +irqreturn_t dcp_desktop_dp2hdmi_hpd(struct apple_dcp *dcp) +{ + struct dptx_port *dptx = &dcp->dptxport[0]; + bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + int ret; + + if (connected) { + msleep(500); + connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + } + dev_dbg(dcp->dev, "DP2HDMI HPD irq, connected:%d after 500 ms\n", connected); + if (!connected) + return IRQ_HANDLED; + + mutex_lock(&dcp->hpd_mutex); + if (dptx_service_available(dptx) && dptx->connected && + READ_ONCE(dptx->service->ep->compact)) { + ret = dptxport_set_hpd(dptx->service, false); + msleep(100); + ret = dptxport_set_hpd(dptx->service, true) ?: ret; + mutex_unlock(&dcp->hpd_mutex); + dev_dbg(dcp->dev, "HDMI cold plug: HPD pulse on the connected port: %d\n", + ret); + return IRQ_HANDLED; + } + mutex_unlock(&dcp->hpd_mutex); + + dcp_dptx_connect(dcp, 0); + return IRQ_HANDLED; +} + int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) { struct dcp_fabric_session session = {}; int ret; + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + return dcp_desktop_dptx_connect(dcp, port); + if (!dcp_is_external_h17p(dcp)) { if (dcp_typec_sessionless(dcp)) { ret = dcp_dptx_connect_raw(dcp, port, NULL, NULL, false, false); @@ -2273,6 +2706,9 @@ int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + return dcp_desktop_dptx_connect(dcp, port); + if (dcp_is_external_h17p(dcp)) { bool new_cable; @@ -2314,6 +2750,9 @@ int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + return dcp_desktop_dptx_disconnect_oob(dcp, port); + if (dcp_is_external_h17p(dcp)) return dcp_external_disconnect(dcp, true); @@ -2473,6 +2912,13 @@ bool dcp_ext_unusable(struct platform_device *pdev) return !dcp || READ_ONCE(dcp->ext_dead); } +bool dcp_fw_compat_is_desktop(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP; +} + bool dcp_fw_compat_is_14_7(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); @@ -2481,6 +2927,15 @@ bool dcp_fw_compat_is_14_7(struct platform_device *pdev) dcp->fw_compat == DCP_FIRMWARE_V_26_6; } +/* GAMMA_LUT entries, 0 where the driver cannot program the gamma table */ +unsigned int dcp_gamma_lut_size(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP ? + IOMFB_GAMMA_ENTRIES : 0; +} + unsigned long* dcp_get_iomfb_surfaces(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); @@ -2496,6 +2951,306 @@ int dcp_activate(struct platform_device *pdev) return iomfb_is_m4(dcp) ? iomfb_m4_activate(dcp) : 0; } +/* + * Desktop 26.x (T6041) start. The firmware does not tolerate being left + * running with its endpoints unserviced, so RTKit is woken only here, once + * every handler is in place, not at component bind. The internal panel's + * processor then starts the system endpoint, the IOMFB transport and the + * auxiliary endpoints the firmware advertises, all speaking compact EPIC, + * and binds the IOMFB shared memory last. An auxiliary endpoint that fails + * to start is skipped: IOMFB can run without it. + */ +static void dcp_compact_aux_init(struct apple_epic_service *service, + const char *name, const char *class, s64 unit) +{ + dev_dbg(service->ep->dcp->dev, + "AFK[ep:%02x]: compact auxiliary service %s (unit %lld)\n", + service->ep->endpoint, name ?: "", unit); +} + +/* + * These endpoints are only serviced: no call of theirs was ever seen. A + * call is answered with -EOPNOTSUPP and logged by the AFK core. + */ +static const struct apple_epic_service_ops dcp_compact_aux_ops[] = { + { + .name = "*", + .init = dcp_compact_aux_init, + }, + {}, +}; + +static void dcp_dpavctrl_service_init(struct apple_epic_service *service, + const char *name, const char *class, + s64 unit) +{ + struct apple_dcp *dcp = service->ep->dcp; + struct dcp_desktop_state *d = dcp->desktop; + const char *service_class = class ?: name; + + if (service_class && strstr(service_class, "dcpav-controller-epic")) + d->dpavctrl_av_controller[!!(service->channel & BIT(2))] = service; + + /* Interface 7 is opened only once the AV controllers are. */ + if ((service->channel & 7) == 7) { + service->defer_start = true; + d->dpavctrl_late_service = service; + } + + /* + * The internal panel announces only the AV controller on interface 5 + * and the deferred DP controller on interface 7. + */ + if (dcp_has_panel(dcp)) { + if (d->dpavctrl_av_controller[1] && d->dpavctrl_late_service) + complete_all(&d->dpavctrl_ready); + } else if (service->ep->num_channels >= 4) { + complete_all(&d->dpavctrl_ready); + } +} + +static const struct apple_epic_service_ops dcp_dpavctrl_ops[] = { + { + .name = "AppleDCPDPTXController", + .init = dcp_dpavctrl_service_init, + }, + { + .name = "dcpav-controller-epic", + .init = dcp_dpavctrl_service_init, + }, + { + .name = "dcpdp-controller-epic", + .init = dcp_dpavctrl_service_init, + }, + {}, +}; + +static int dcp_desktop_start_ep(struct apple_dcp *dcp, u32 endpoint, + const struct apple_epic_service_ops *ops) +{ + struct apple_dcp_afkep *ep; + int ret; + + ep = afk_init(dcp, endpoint, ops); + if (IS_ERR(ep)) + return PTR_ERR(ep); + + /* + * Route its messages before the start: afk_start() waits for the + * firmware's reply on this endpoint. The route stays if the start + * times out, the firmware may still talk. + */ + WRITE_ONCE(dcp->desktop->graph[endpoint - 0x20], ep); + ret = afk_start(ep); + return ret; +} + +static int dcp_desktop_start_dpavctrl(struct apple_dcp *dcp) +{ + int ret; + + init_completion(&dcp->desktop->dpavctrl_ready); + ret = dcp_desktop_start_ep(dcp, DPAV_CTRL_ENDPOINT, dcp_dpavctrl_ops); + if (ret) + return ret; + + if (!wait_for_completion_timeout(&dcp->desktop->dpavctrl_ready, + msecs_to_jiffies(1000))) + return -ETIMEDOUT; + return 0; +} + +/* The auxiliary endpoints of the panel's processor, started if advertised. */ +static const struct { + u32 endpoint; + const char *name; +} dcp_desktop_panel_eps[] = { + { DPAV_CTRL_ENDPOINT, "DPAVCTRL" }, + { EPIC25_ENDPOINT, "EPIC25" }, + { DPSAC_ENDPOINT, "DPSAC" }, + { DPDEV_ENDPOINT, "DPDEV" }, + { DPAVSERV_ENDPOINT, "DPAVSERV" }, + { AV_ENDPOINT, "AV" }, + { DPTX_ENDPOINT, "DPTX" }, + { HDCP_ENDPOINT, "HDCP" }, + { EPIC2C_ENDPOINT, "EPIC2C" }, + { REMOTE_ALLOC_ENDPOINT, "REMOTEALLOC" }, +}; + +static void dcp_desktop_graph_skip(struct apple_dcp *dcp, u32 endpoint, + const char *name, const char *reason, int rc) +{ + dcp->desktop->graph_skipped |= BIT(endpoint - 0x20); + if (rc) + dev_warn(dcp->dev, "endpoint %#04x (%s) not started: %s (%d)\n", + endpoint, name, reason, rc); + else + dev_dbg(dcp->dev, "endpoint %#04x (%s) not started: %s\n", + endpoint, name, reason); +} + +/* + * The external processors (T6041 dcpext): DPDEV, DPAVCTRL (all four + * controller interfaces), DPSAC, AV and REMOTEALLOC as advertised, then + * with a DP PHY or a Type-C route iBoot, the DPTX port services on EPIC2C, + * DPAVSERV for the EDID, EXPERT and EPIC25, and the DPTX connect of port 0. + * A failure fails this processor's start; the panel is not affected. + */ +static int dcp_desktop_start_ext_ep(struct apple_dcp *dcp, u32 map, u32 endpoint, + const char *name) +{ + int ret; + + if (!(map & BIT(endpoint - 0x20))) { + dcp_desktop_graph_skip(dcp, endpoint, name, "not advertised", 0); + return 0; + } + ret = dcp_desktop_start_ep(dcp, endpoint, dcp_compact_aux_ops); + if (ret) + return dev_err_probe(dcp->dev, ret, "Failed to start %s endpoint\n", + name); + dcp->desktop->graph_started |= BIT(endpoint - 0x20); + return 0; +} + +static int dcp_start_desktop_external(struct apple_dcp *dcp, u32 map) +{ + struct dcp_desktop_state *d = dcp->desktop; + int ret, dptx_ret; + + ret = dcp_desktop_start_ext_ep(dcp, map, DPDEV_ENDPOINT, "DPDEV"); + if (ret) + return ret; + ret = dcp_desktop_start_dpavctrl(dcp); + if (ret) + return dev_err_probe(dcp->dev, ret, + "Failed to initialize DPAVCTRL endpoint\n"); + d->graph_started |= BIT(DPAV_CTRL_ENDPOINT - 0x20); + ret = dcp_desktop_start_ext_ep(dcp, map, DPSAC_ENDPOINT, "DPSAC"); + if (ret) + return ret; +#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) + if (hdmi_audio) + ret = avep_init(dcp); + else +#endif + ret = dcp_desktop_start_ext_ep(dcp, map, AV_ENDPOINT, "AV"); + if (ret) + return ret; + ret = dcp_desktop_start_ext_ep(dcp, map, REMOTE_ALLOC_ENDPOINT, + "REMOTEALLOC"); + if (ret) + return ret; + + if (!dcp->phy && !d->ndyn) { + dev_warn(dcp->dev, "no DP PHY and no Type-C route: no display link\n"); + return 0; + } + + ret = ibootep_init(dcp); + if (ret) + dev_warn(dcp->dev, "Failed to start IBOOT endpoint: %d\n", ret); + + /* The DPTX port services live on EPIC2C here. */ + dptx_ret = dptxep_init_desktop(dcp, EPIC2C_ENDPOINT); + if (dptx_ret) + dev_warn(dcp->dev, "Failed to start DPTX port services: %d\n", + dptx_ret); + + if (unstable_edid) { + ret = dpavservep_init(dcp); + if (ret) + return dev_err_probe(dcp->dev, ret, + "Failed to start DPAVSERV endpoint\n"); + } + ret = dcp_desktop_start_ext_ep(dcp, map, DCP_EXPERT_ENDPOINT, "EXPERT"); + if (ret) + return ret; + ret = dcp_desktop_start_ext_ep(dcp, map, EPIC25_ENDPOINT, "EPIC25"); + if (ret) + return ret; + + if (!dptx_ret && dptx_service_available(&dcp->dptxport[0])) { + ret = dcp_dptx_connect(dcp, 0); + if (ret) + dev_info(dcp->dev, "no DPTX link at startup: %d\n", ret); + } + return 0; +} + +static int dcp_start_desktop(struct apple_dcp *dcp) +{ + struct dcp_desktop_state *d = dcp->desktop; + u32 map = 0; + int i, ep, ret; + + if (IS_ERR_OR_NULL(dcp->rtk)) + return -ENODEV; + + ret = apple_rtkit_wake(dcp->rtk); + if (ret) + return dev_err_probe(dcp->dev, ret, "Failed to boot RTKit: %d\n", + ret); + + for (ep = 0x20; ep < 0x40; ep++) + if (apple_rtkit_has_endpoint(dcp->rtk, ep)) + map |= BIT(ep - 0x20); + dev_dbg(dcp->dev, "firmware endpoints 0x20+: %#010x\n", map); + + ret = systemep_init(dcp); + if (ret) + dev_warn(dcp->dev, "Failed to start system endpoint: %d\n", ret); + + /* + * The IOMFB transport starts before the auxiliary endpoints; its + * shared memory is bound only after them (iomfb_start_rtkit()). + */ + ret = apple_rtkit_start_ep(dcp->rtk, IOMFB_ENDPOINT); + if (ret) + return dev_err_probe(dcp->dev, ret, + "Failed to start IOMFB transport endpoint\n"); + + if (dcp_desktop_external(dcp)) { + ret = dcp_start_desktop_external(dcp, map); + if (ret) + return ret; + goto start_iomfb; + } + + for (i = 0; i < ARRAY_SIZE(dcp_desktop_panel_eps); i++) { + u32 endpoint = dcp_desktop_panel_eps[i].endpoint; + const char *name = dcp_desktop_panel_eps[i].name; + + if (!(map & BIT(endpoint - 0x20))) { + dcp_desktop_graph_skip(dcp, endpoint, name, "not advertised", 0); + continue; + } + + if (endpoint == DPAV_CTRL_ENDPOINT) + ret = dcp_desktop_start_dpavctrl(dcp); +#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) + else if (endpoint == AV_ENDPOINT && hdmi_audio) + ret = avep_init(dcp); +#endif + else + ret = dcp_desktop_start_ep(dcp, endpoint, + dcp_compact_aux_ops); + if (ret) { + dcp_desktop_graph_skip(dcp, endpoint, name, "start failed", ret); + continue; + } + d->graph_started |= BIT(endpoint - 0x20); + } +start_iomfb: + dev_info(dcp->dev, "endpoints started %#010x, skipped %#010x\n", + d->graph_started, d->graph_skipped); + + ret = iomfb_start_rtkit(dcp); + if (ret) + dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d\n", ret); + return ret; +} + int dcp_start(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); @@ -2511,6 +3266,8 @@ int dcp_start(struct platform_device *pdev) init_completion(&dcp->start_done); if (dcp->fw_compat == DCP_FIRMWARE_V_26_6) return iomfb_v26_6_start(dcp); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + return dcp_start_desktop(dcp); /* * The T6030 firmware session is adopted for the internal panel. @@ -2677,6 +3434,9 @@ static void _dcp_poweroff(struct apple_dcp *dcp) case DCP_FIRMWARE_V_14_7_TEMPLATE: iomfb_poweroff_v14_7_0(dcp); break; + case DCP_FIRMWARE_V_26_6_DESKTOP: + iomfb_poweroff_v26_6_desktop(dcp); + break; case DCP_FIRMWARE_V_26_6: iomfb_v26_6_poweroff(dcp); break; @@ -2771,6 +3531,9 @@ static void __maybe_unused dcp_sleep(struct apple_dcp *dcp) case DCP_FIRMWARE_V_14_7_TEMPLATE: iomfb_sleep_v14_7_0(dcp); break; + case DCP_FIRMWARE_V_26_6_DESKTOP: + iomfb_sleep_v26_6_desktop(dcp); + break; case DCP_FIRMWARE_V_26_6: iomfb_v26_6_poweroff(dcp); break; @@ -2824,6 +3587,15 @@ void dcp_poweron(struct platform_device *pdev) unsigned long remaining; int ret; + /* The takeover trial powered it on before the eviction. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + dcp->desktop->trial_powered) { + dcp->desktop->trial_powered = false; + return; + } + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + dcp->desktop->ext_pipe_off = false; + if (iomfb_is_m4(dcp)) { ret = dcp_backlight_dpms(dcp, true); if (ret) @@ -2928,6 +3700,9 @@ void dcp_poweron(struct platform_device *pdev) case DCP_FIRMWARE_V_14_7_TEMPLATE: iomfb_poweron_v14_7_0(dcp); break; + case DCP_FIRMWARE_V_26_6_DESKTOP: + iomfb_poweron_v26_6_desktop(dcp); + break; case DCP_FIRMWARE_V_26_6: iomfb_v26_6_poweron(dcp); break; @@ -3074,6 +3849,17 @@ void dcp_poweroff(struct platform_device *pdev) } } +/* Desktop 26.x: the DRM device exists now, register the panel backlight. */ +void dcp_backlight_takeover_registered(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + if (!dcp || dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || + !dcp_has_panel(dcp) || !dcp->brightness.maximum) + return; + schedule_work(&dcp->bl_register_wq); +} + static void dcp_work_register_backlight(struct work_struct *work) { int ret; @@ -3523,7 +4309,26 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) return hw->firmware_compat; /* firmware version is just informative */ - dcp_read_fw_version(dev, "apple,firmware-version", fw_str); + ret = dcp_read_fw_version(dev, "apple,firmware-version", fw_str); + + /* + * T6041 (M4 Pro/Max) is driven only with the 26.6.2 firmware its + * desktop IOMFB instance was written for: both the boot firmware and + * its compatibility version must be exactly that release. + */ + if (of_device_is_compatible(dev->of_node, "apple,t6041-dcp") || + of_device_is_compatible(dev->of_node, "apple,t6041-dcpext")) { + int compat_ret = dcp_read_fw_version(dev, "apple,firmware-compat", + compat_str); + + if (ret >= 0 && compat_ret >= 0 && !strcmp(fw_str, "26.6.2") && + !strcmp(compat_str, "26.6.2")) + return DCP_FIRMWARE_V_26_6_DESKTOP; + dev_err(dev, "T6041 display not started: DCP firmware %s (compat %s) is not 26.6.2\n", + ret >= 0 ? fw_str : "missing", + compat_ret >= 0 ? compat_str : "missing"); + return DCP_FIRMWARE_UNKNOWN; + } ret = dcp_read_fw_version(dev, "apple,firmware-compat", compat_str); if (ret < 0) { @@ -3835,6 +4640,12 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat return PTR_ERR(dcp->coproc_reg); } + /* Record the state the boot loader handed the coprocessor over in. */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + dev_info(dev, "coprocessor handoff: CPU_CONTROL %#x CPU_STATUS %#x\n", + readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL), + readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_STATUS)); + /* * Display coprocessors running H17-generation firmware are left running * by the bootloader with their firmware mapped through a locked DART. @@ -3879,7 +4690,17 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat if (panel_np) { const char height_prop[2][16] = { "adj-height-mm", "height-mm" }; - if (of_device_is_available(panel_np)) { + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + /* + * Desktop 26.x swaps carry nits, and the panel node + * describes the panel whether or not the boot loader + * enabled it. Without apple,max-brightness take the + * J616s panel's user-accessible SDR maximum. + */ + if (of_property_read_u32(panel_np, "apple,max-brightness", + &dcp->brightness.maximum)) + dcp->brightness.maximum = DCP_DESKTOP_MAX_NITS; + } else if (of_device_is_available(panel_np)) { ret = of_property_read_u32(panel_np, "apple,max-brightness", &dcp->brightness.maximum); if (ret) @@ -3948,7 +4769,15 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat goto err_piodma; } - dcp->clk = devm_clk_get(dev, NULL); + /* + * T6041: the device tree carries no DCP clock, and the boot loader + * left the coprocessor clocked; D408 then reports 0 Hz, as the + * driver this instance was ported from did. + */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + dcp->clk = devm_clk_get_optional(dev, NULL); + else + dcp->clk = devm_clk_get(dev, NULL); if (IS_ERR(dcp->clk)) { ret = dev_err_probe(dev, PTR_ERR(dcp->clk), "Unable to find clock\n"); @@ -4025,6 +4854,8 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL) ret = apple_rtkit_wake_early_ap(dcp->rtk); + else if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) + ret = 0; /* woken in dcp_start_desktop() */ else ret = apple_rtkit_wake(dcp->rtk); if (ret) { @@ -4214,6 +5045,21 @@ VISIBLE_IF_KUNIT void dcp_comp_unbind(struct device *dev, struct device *main, v dcp->systemep = NULL; } + dcp_desktop_dyn_route_release(dcp); + + /* + * The auxiliary endpoints a desktop 26.x start brought up, still + * routed while their shutdown is acknowledged. + */ + for (int i = 0; dcp->desktop && i < ARRAY_SIZE(dcp->desktop->graph); i++) { + struct apple_dcp_afkep *ep = dcp->desktop->graph[i]; + + if (ep) { + afk_shutdown(ep); + WRITE_ONCE(dcp->desktop->graph[i], NULL); + } + } + if (dcp->dcpavservep) { afk_shutdown(dcp->dcpavservep); dpavservep_detach(dcp); @@ -4302,6 +5148,74 @@ static int dcp_native_routes_error(struct apple_dcp *dcp, int err) return 0; } +static void dcp_desktop_phy_off(void *data) +{ + struct phy *phy = data; + + phy_power_off(phy); + phy_exit(phy); +} + +/* + * The outputs of a desktop 26.x external processor (T6041 dcpext): the + * fixed HDMI route's DP PHY is powered here, as the boot loader's working + * sequence does; a dynamic Type-C route lists the DP PHY and crossbar of + * every port (dp-phyN, dp-xbarN, apple,dptx-phys, apple,dptx-mux-index), + * and the one in DP alt mode is claimed at connect. + */ +static int dcp_desktop_probe_routes(struct apple_dcp *dcp) +{ + struct dcp_desktop_state *d = dcp->desktop; + struct device *dev = dcp->dev; + unsigned int i; + int nd, ret; + + d->dyn_active = -1; + if (!dcp_desktop_external(dcp)) + return 0; + + if (dcp->phy) { + ret = phy_init(dcp->phy); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize dp-phy\n"); + ret = phy_power_on(dcp->phy); + if (ret) { + phy_exit(dcp->phy); + return dev_err_probe(dev, ret, "Failed to power on dp-phy\n"); + } + ret = devm_add_action_or_reset(dev, dcp_desktop_phy_off, dcp->phy); + if (ret) + return ret; + msleep(25); + } + + nd = of_property_count_u32_elems(dev->of_node, "apple,dptx-phys"); + if (nd <= 0) + return 0; + if (nd > ARRAY_SIZE(d->dyn_phy)) + return dev_err_probe(dev, -EINVAL, "too many Type-C routes: %d\n", nd); + + for (i = 0; i < nd; i++) { + char name[16]; + + snprintf(name, sizeof(name), "dp-phy%u", i); + d->dyn_phy[i] = devm_phy_get(dev, name); + if (IS_ERR(d->dyn_phy[i])) + return dev_err_probe(dev, PTR_ERR(d->dyn_phy[i]), "%s\n", name); + snprintf(name, sizeof(name), "dp-xbar%u", i); + d->dyn_xbar[i] = devm_mux_control_get(dev, name); + if (IS_ERR(d->dyn_xbar[i])) + return dev_err_probe(dev, PTR_ERR(d->dyn_xbar[i]), "%s\n", name); + of_property_read_u32_index(dev->of_node, "apple,dptx-phys", i, + &d->dyn_dptx_phy[i]); + } + of_property_read_u32(dev->of_node, "apple,dptx-mux-index", &d->dyn_mux_index); + d->ndyn = nd; + dev_info(dev, "%u Type-C DP routes, crossbar state %u\n", d->ndyn, + d->dyn_mux_index); + return 0; +} + static int dcp_platform_probe_pipe(struct platform_device *pdev) { const struct apple_dcp_hw_data *hw = of_device_get_match_data(&pdev->dev); @@ -4326,6 +5240,11 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) fw_compat = dcp_check_firmware_version(dev); if (fw_compat == DCP_FIRMWARE_UNKNOWN) return -ENODEV; + if (fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + ret = dcp_validate_t6041_dt(dev); + if (ret) + return ret; + } /* Check for "apple,bw-scratch" to avoid probing appledrm with outdated * device trees. This prevents replacing simpledrm and ending up without @@ -4354,7 +5273,20 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) iomfb_queue_init(dcp); dcp->fw_compat = fw_compat; - dcp->ext_failsafe = of_device_is_compatible(dev->of_node, "apple,t8122-dcpext"); + if (fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + dcp->desktop = devm_kzalloc(dev, sizeof(*dcp->desktop), GFP_KERNEL); + if (!dcp->desktop) + return -ENOMEM; + /* + * The opaque client identity of the IOMFB calls. The firmware's + * own client passes a kernel pointer there; pass a random value + * of the same shape instead. + */ + dcp->desktop->client = GENMASK_ULL(63, 48) | + (get_random_u64() & GENMASK_ULL(47, 4)); + } + dcp->ext_failsafe = of_device_is_compatible(dev->of_node, "apple,t8122-dcpext") || + of_device_is_compatible(dev->of_node, "apple,t6041-dcpext"); dcp->external = of_device_is_compatible(dev->of_node, "apple,t6030-dcpext"); dcp->external_native = dcp->external && dcp_t6030_ext_native(dev->of_node); dcp_external_audio_init(dcp); @@ -4470,6 +5402,12 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) } dcp->fixed_phy = dcp->phy; + if (fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + ret = dcp_desktop_probe_routes(dcp); + if (ret) + return ret; + } + bitmap_zero(dcp->iomfb_surfaces, DCP_MAX_PLANES); if (!of_property_present(dev->of_node, "apple,iomfb-surfaces")) num_surfs = 0; @@ -4580,6 +5518,10 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) } if (IS_ERR(dcp->dp2hdmi_pwren)) return PTR_ERR(dcp->dp2hdmi_pwren); + /* The converter rails must be stable before DCP requests the link. */ + if (fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + (dcp->hdmi_pwren || dcp->dp2hdmi_pwren)) + msleep(100); /* * A DCP may have both a fixed HDMI/DP route and allocatable Type-C @@ -4763,7 +5705,15 @@ static int dcp_platform_probe(struct platform_device *pdev) void *group; int ret; - if (!of_device_is_compatible(dev->of_node, "apple,t8122-dcpext")) + /* t6041_dcpext=0: stay off the external processors' hardware. */ + if (of_device_is_compatible(dev->of_node, "apple,t6041-dcpext") && + !t6041_dcpext) { + dev_info(dev, "T6041 external display processor left alone (t6041_dcpext=0)\n"); + return -ENODEV; + } + + if (!of_device_is_compatible(dev->of_node, "apple,t8122-dcpext") && + !of_device_is_compatible(dev->of_node, "apple,t6041-dcpext")) return dcp_platform_probe_pipe(pdev); ret = dcp_ext_route_suppliers(dev); @@ -4834,10 +5784,27 @@ static void dcp_platform_remove(struct platform_device *pdev) iomfb_v14_7_remove(dcp); } +/* A refused takeover left the DCP running untouched, see apple_drv.c. */ +static bool dcp_takeover_was_aborted(struct apple_dcp *dcp) +{ + return dcp && dcp->desktop && READ_ONCE(dcp->desktop->takeover_aborted); +} + +void dcp_takeover_aborted(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + if (dcp && dcp->desktop) + WRITE_ONCE(dcp->desktop->takeover_aborted, true); +} + VISIBLE_IF_KUNIT void dcp_platform_shutdown(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + if (dcp_takeover_was_aborted(dcp)) + return; + if (dcp && iomfb_is_m4(dcp)) { dcp_comp_unbind(&pdev->dev, NULL, NULL); return; @@ -5027,6 +5994,10 @@ static int dcp_platform_suspend(struct device *dev) { struct apple_dcp *dcp = dev_get_drvdata(dev); + /* Its power sequences may crash the DCP and blank the panel. */ + if (dcp_takeover_was_aborted(dcp)) + return -EBUSY; + /* The retained T8132 session has no qualified system-sleep handshake. */ if (iomfb_is_m4(dcp)) return -EBUSY; @@ -5105,6 +6076,9 @@ static int dcp_platform_resume(struct device *dev) { struct apple_dcp *dcp = dev_get_drvdata(dev); + if (dcp_takeover_was_aborted(dcp)) + return 0; + if (iomfb_is_m4(dcp)) return iomfb_m4_health(dcp); if (READ_ONCE(dcp->ext_dead)) @@ -5222,7 +6196,18 @@ static const struct apple_dcp_hw_data apple_dcp_hw_t8132 = { }; #endif +/* T6041 (M4 Pro/Max), desktop 26.6 firmware */ +static const struct apple_dcp_hw_data apple_dcp_hw_t6041 = { + .num_dptx_ports = 2, +}; + +static const struct apple_dcp_hw_data apple_dcp_hw_t6041_dcpext = { + .num_dptx_ports = 2, +}; + static const struct of_device_id of_match[] = { + { .compatible = "apple,t6041-dcp", .data = &apple_dcp_hw_t6041, }, + { .compatible = "apple,t6041-dcpext", .data = &apple_dcp_hw_t6041_dcpext, }, { .compatible = "apple,t6020-dcp", .data = &apple_dcp_hw_t6020, }, { .compatible = "apple,t6020-dcpext", .data = &apple_dcp_hw_t6020_dcpext, }, { .compatible = "apple,t8112-dcp", .data = &apple_dcp_hw_t8112, }, diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 33f39b7645c4e..30b728820e2c7 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -63,6 +63,13 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_commit *commi int dcp_get_connector_type(struct platform_device *pdev); bool dcp_fw_compat_is_12_x(struct platform_device *pdev); bool dcp_fw_compat_is_14_7(struct platform_device *pdev); +bool dcp_fw_compat_is_desktop(struct platform_device *pdev); +unsigned int dcp_gamma_lut_size(struct platform_device *pdev); +/* T6041 external display processors may join the DRM device. */ +bool dcp_t6041_ext_enabled(void); +void dcp_backlight_takeover_registered(struct platform_device *pdev); +int dcp_takeover_trial(struct platform_device *pdev, const char **reason); +void dcp_takeover_aborted(struct platform_device *pdev); bool dcp_ext_unusable(struct platform_device *pdev); unsigned long* dcp_get_iomfb_surfaces(struct platform_device *pdev); int dcp_start(struct platform_device *pdev); @@ -118,6 +125,7 @@ void iomfb_recv_msg(struct apple_dcp *dcp, u64 message); int systemep_init(struct apple_dcp *dcp); int dptxep_init(struct apple_dcp *dcp); +int dptxep_init_desktop(struct apple_dcp *dcp, u32 endpoint); int dptx_controller_init(struct apple_dcp *dcp); int ibootep_init(struct apple_dcp *dcp); int dpavservep_init(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 6d56199a4fe8d..e9bc471654cde 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -22,6 +22,9 @@ #define MIN_BRIGHTNESS_PART2 103U #define MAX_BRIGHTNESS_PART2 510U +/* Initial level of a desktop 26.x panel, which reports none. */ +#define DCP_DESKTOP_DEFAULT_NITS 250U + /* * lookup for display brightness 2 to 99 nits * */ @@ -507,11 +510,24 @@ static int dcp_set_brightness(struct backlight_device *bd) return ret; } + /* + * Desktop 26.x swaps carry nits. A level of 0 must not turn the + * panel off (DPMS does that); only a blanked panel still gets 0. + */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !brightness && + !bd->props.brightness) + brightness = min(bd->props.max_brightness, 2); + ret = drm_modeset_lock_single_interruptible(&dcp->crtc->base.mutex); if (ret) return ret; - dcp->brightness.dac = calculate_dac(dcp, brightness); + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + dcp->brightness.nits = brightness; + dcp->brightness.requested = true; + } else { + dcp->brightness.dac = calculate_dac(dcp, brightness); + } dcp->brightness.update = true; drm_modeset_unlock(&dcp->crtc->base.mutex); @@ -564,13 +580,28 @@ int dcp_backlight_register(struct apple_dcp *dcp) spin_unlock_irqrestore(&dcp->backlight.lock, flags); } + /* + * Desktop 26.x takes nits directly and publishes no initial level: + * start from a default, which is not sent until userspace writes one. + */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + if (!dcp->brightness.maximum || dcp->brightness.maximum > INT_MAX) + return -ERANGE; + props.max_brightness = dcp->brightness.maximum; + if (!props.brightness) + props.brightness = min_t(u32, DCP_DESKTOP_DEFAULT_NITS, + props.max_brightness); + dcp->brightness.nits = props.brightness; + } + bl_dev = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp, &dcp_backlight_ops, &props); if (IS_ERR(bl_dev)) return PTR_ERR(bl_dev); dcp->brightness.bl_dev = bl_dev; - if (!dcp_backlight_active(dcp)) + if (!dcp_backlight_active(dcp) && + dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP) dcp->brightness.dac = calculate_dac(dcp, dcp->brightness.nits); return 0; diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 741f40bc8c9ed..a7620457f1f46 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -224,6 +224,15 @@ int dptxport_set_hpd_timeout(struct apple_epic_service *service, bool hpd, &received); if (ret) return ret; + /* Compact EPIC returns an IOReturn status in the first word instead. */ + if (READ_ONCE(service->ep->compact)) { + u32 status = get_unaligned_le32(resp._pad0); + + if (status) + dev_warn(service->ep->dcp->dev, + "set_hpd(%d): firmware status %#x\n", hpd, status); + return status ? -EIO : 0; + } /* Past the end of a short reply there is only zero fill, not an echo. */ if (received < sizeof(resp)) { dev_warn(service->ep->dcp->dev, @@ -473,6 +482,23 @@ static int dptxport_call_get_link_rate(struct apple_epic_service *service, return 0; } +/* + * Compact DPTX (T6041): the direct DP PHY's pixel clock is the crossbar's + * PCLK1. Take the crossbar link down before a link configuration change + * and bring it up after it, the PHY rate having been set in between. + */ +static int dptxport_compact_xbar_link(struct apple_epic_service *service, bool up) +{ + struct apple_dcp *dcp = service->ep->dcp; + int ret; + + if (!READ_ONCE(service->ep->compact) || !dcp->xbar) + return 0; + ret = dcp_dpxbar_link(dcp->xbar, up); + dev_dbg(dcp->dev, "DPTXPort: crossbar link %s: %d\n", up ? "up" : "down", ret); + return ret; +} + static int dptxport_call_will_change_link_config(struct apple_epic_service *service) { @@ -483,6 +509,7 @@ dptxport_call_will_change_link_config(struct apple_epic_service *service) dptx->phy_ops.dp.set_rate = 0; dptx->phy_ops.dp.set_voltages = 0; + dptxport_compact_xbar_link(service, false); return 0; } @@ -497,6 +524,7 @@ dptxport_call_did_change_link_config(struct apple_epic_service *service) /* assume the link config did change and wait a little bit */ mdelay(10); + dptxport_compact_xbar_link(service, true); return 0; } @@ -604,7 +632,8 @@ static int dptxport_call_get_supports_hpd(struct apple_epic_service *service, * that path instead of waiting for a PHY start. */ reply->supported = cpu_to_le32(dcp_is_typec_output(dcp) && - !dcp_ext_dptx_607(dcp) ? 1 : 0); + !dcp_ext_dptx_607(dcp) && + !READ_ONCE(service->ep->compact) ? 1 : 0); dev_dbg(dcp->dev, "DPTXPort: GET_SUPPORTS_HPD %u usb4=%d\n", le32_to_cpu(reply->supported), dcp_is_usb4_output(dcp)); return 0; @@ -916,6 +945,19 @@ static int dptxport_prepare(struct apple_epic_service *service, const char *name * T6030 14.7 also announces the low-power port service, and may leave * its class empty. */ + /* + * Desktop 26.x announces dcpdptx-port-epic:0 and :1 in compact EPIC, + * without a class. Unit 0 is opened last, see dptxep_init_desktop(). + */ + if (READ_ONCE(service->ep->compact)) { + if (unit != 0 && unit != 1) + return -EINVAL; + if (!unit) + service->defer_start = true; + trace_dptxport_init(dcp, unit); + return dptx_service_bind(service, &dcp->dptxport[unit], unit); + } + if (dcp_fw_is_14_7(dcp) && name && (!strcmp(name, "dcpdptx-port-epic") || !strcmp(name, "dcp-lpdptx-port-epic")) && @@ -1075,6 +1117,98 @@ static const struct apple_epic_service_ops dptxep_ops[] = { {} }; +/* + * Group 1 calls 3 and 4 of compact DPTX carry an 80-byte record: keep the + * first 32 bytes, clear the result word (at 0 for 3, at 16 for 4) and never + * echo the pointer-bearing tail. + */ +static int dptxport_compact_group1(u32 command, const void *data, size_t size, + void *reply, size_t room) +{ + size_t preserved = min_t(size_t, 32, min(size, room)); + size_t result = command == 3 ? 0 : 16; + + if (command != 3 && command != 4) + return -EOPNOTSUPP; + memcpy(reply, data, preserved); + if (room > preserved) + memset(reply + preserved, 0, room - preserved); + if (room < result + sizeof(__le32)) + return -EINVAL; + memset(reply + result, 0, sizeof(__le32)); + return 0; +} + +static int dptxport_compact_call(struct apple_epic_service *service, u16 group, + u32 command, const void *data, size_t size, + void *reply, size_t room) +{ + if (group == 1) + return dptxport_compact_group1(command, data, size, reply, room); + if (group) + return -EOPNOTSUPP; + /* Asked on every connect; no virtual device is supported. */ + if (command == DPTX_APCALL_GET_VIRTUAL_DEVICE_MODE) { + if (room < sizeof(struct dptxport_apcall_get_support)) + return -EINVAL; + memset(reply, 0, room); + return 0; + } + return dptxport_call(service, command, data, size, reply, room); +} + +static const struct apple_epic_service_ops dptxep_compact_ops[] = { + { + .name = "dcpdptx-port-epic", + .prepare = dptxport_prepare, + .teardown = dptxport_teardown, + .init = dptxport_init, + .call_v2 = dptxport_compact_call, + }, + {} +}; + +/* + * The DPTX port services of a desktop 26.x external processor (T6041 + * dcpext), on EPIC2C (0x2c): wait for both ports, then open port 0. + */ +int dptxep_init_desktop(struct apple_dcp *dcp, u32 endpoint) +{ + struct apple_dcp_afkep *ep; + unsigned long timeout = msecs_to_jiffies(1000); + u32 port; + int ret; + + init_completion(&dcp->dptxport[0].enable_completion); + init_completion(&dcp->dptxport[1].enable_completion); + init_completion(&dcp->dptxport[0].linkcfg_completion); + init_completion(&dcp->dptxport[1].linkcfg_completion); + + ep = afk_init(dcp, endpoint, dptxep_compact_ops); + if (IS_ERR(ep)) + return PTR_ERR(ep); + /* Routed by its endpoint number: set before the start replies arrive. */ + dcp->dptxep = ep; + + ret = afk_start(ep); + if (ret) + return ret; + + for (port = 0; port < dcp->hw.num_dptx_ports; port++) { + ret = wait_for_completion_timeout(&dcp->dptxport[port].enable_completion, + timeout); + if (!ret) { + dev_warn(dcp->dev, "DPTX port %u service not announced\n", port); + return -ETIMEDOUT; + } + timeout = ret; + } + + if (READ_ONCE(ep->compact) && dcp->dptxport[0].service) + return afk_start_service(dcp->dptxport[0].service); + return 0; +} + static void dptx_controller_service_init(struct apple_epic_service *service, const char *name, const char *class, s64 unit) { diff --git a/drivers/gpu/drm/apple/epic/dpavservep.c b/drivers/gpu/drm/apple/epic/dpavservep.c index ed94c48c80d1a..a92af3295fcf8 100644 --- a/drivers/gpu/drm/apple/epic/dpavservep.c +++ b/drivers/gpu/drm/apple/epic/dpavservep.c @@ -218,6 +218,9 @@ static int dcpdpserv_prepare(struct apple_epic_service *service, const char *nam static void dcpdpserv_init(struct apple_epic_service *service, const char *name, const char *class, s64 unit) { + /* Compact EPIC (desktop 26.x): the firmware's own client never opens it. */ + if (READ_ONCE(service->ep->compact)) + service->defer_start = true; } static void dcpdpserv_teardown(struct apple_epic_service *service) @@ -489,11 +492,20 @@ static int dcpdpserv_report(struct apple_epic_service *service, return parse_report(service, type, data, data_size); } +/* + * A compact EPIC call goes inline through the endpoint's ring, which a + * 32 KiB EDID request can never fit: ask for 2 KiB there, enough for 15 + * extension blocks. + */ +#define EDID_COMPACT_BUF_SIZE (EDID_LEADING_DATA_SIZE + SZ_2K) + static const struct drm_edid *dcpavserv_read_edid_legacy(struct apple_epic_service *service) { struct dpavserv_copy_edid_cmd cmd; struct dpavserv_copy_edid_resp *resp __free(kfree) = NULL; - size_t resp_size = sizeof(*resp) + EDID_BUF_SIZE; + size_t edid_buf_size = READ_ONCE(service->ep->compact) ? EDID_COMPACT_BUF_SIZE : + EDID_BUF_SIZE; + size_t resp_size = sizeof(*resp) + edid_buf_size; size_t reply_size; int num_blocks; u64 data_size; @@ -501,25 +513,25 @@ static const struct drm_edid *dcpavserv_read_edid_legacy(struct apple_epic_servi KUNIT_STATIC_STUB_REDIRECT(dcpavserv_read_edid_legacy, service); memset(&cmd, 0, sizeof(cmd)); - cmd.max_size = cpu_to_le64(EDID_BUF_SIZE); + cmd.max_size = cpu_to_le64(edid_buf_size); resp = kzalloc(resp_size, GFP_KERNEL); if (!resp) return ERR_PTR(-ENOMEM); ret = afk_service_call_with_reply_len(service, 1, 7, &cmd, sizeof(cmd), - EDID_BUF_SIZE, resp, resp_size, 0, + edid_buf_size, resp, resp_size, 0, &reply_size); if (ret < 0) return ERR_PTR(ret); if (reply_size < sizeof(*resp) || - le64_to_cpu(resp->max_size) != EDID_BUF_SIZE) + le64_to_cpu(resp->max_size) != edid_buf_size) return ERR_PTR(-EIO); /* Only complete EDID blocks within the received reply may be copied. */ data_size = le64_to_cpu(resp->used_size); if (data_size < EDID_LEADING_DATA_SIZE + EDID_BLOCK_SIZE || - data_size > EDID_BUF_SIZE || data_size > reply_size - sizeof(*resp)) + data_size > edid_buf_size || data_size > reply_size - sizeof(*resp)) return ERR_PTR(-EIO); /* diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 9857332b60a47..c0bef660c25ec 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -90,6 +90,17 @@ static inline u64 dcpep_ack(enum dcp_context_id id) return dcpep_msg(id, 0, 0) | IOMFB_MSG_ACK; } +/* + * Adaptive sync through IOMFBParameter 14 black-screens the J616s panel on + * the desktop 26.6 firmware: the firmware accepts the VRR mode, then no swap + * completes. Do not offer it there. + */ +static bool dcp_mode_vrr_supported(struct apple_dcp *dcp, + const struct dcp_display_mode *mode) +{ + return dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || !mode->vrr; +} + /* * A channel is busy if we have sent a message that has yet to be * acked. The driver must not sent a message to a busy channel. @@ -1936,6 +1947,8 @@ int dcp_get_modes(struct drm_connector *connector) continue; published_180hz |= modes[i].automatic_rgb8_quirk; published++; + if (!dcp_mode_vrr_supported(dcp, &modes[i])) + continue; if (modes[i].vrr) { u16 lo = modes[i].min_vrr >> 16; u16 hi = modes[i].max_vrr >> 16; @@ -1972,6 +1985,153 @@ int dcp_get_modes(struct drm_connector *connector) return published; } +/* The pixel clock of the J616s boot loader's panel mode. */ +#define DCP_DESKTOP_BOOT_PIXEL_CLOCK_KHZ 521065 +#define DCP_DESKTOP_TRIAL_MAX_CAND 2 + +/* Reuses the mode the takeover trial set, once; see dcp_takeover_trial(). */ +static int dcp_desktop_modeset(struct apple_dcp *dcp, + struct drm_crtc_state *crtc_state) +{ + struct dcp_desktop_state *d = dcp->desktop; + struct dcp_display_mode matched; + + if (d->trial_modeset_ok) { + d->trial_modeset_ok = false; + if (d->mode_live && lookup_mode(dcp, &crtc_state->mode, &matched) && + matched.timing_mode_id == d->trial_timing_mode_id && + matched.color_mode_id == d->trial_color_mode_id) { + dev_dbg(dcp->dev, "keeping the takeover mode " DRM_MODE_FMT "\n", + DRM_MODE_ARG(&crtc_state->mode)); + return 0; + } + } + return iomfb_modeset_v26_6_desktop(dcp, crtc_state); +} + +/* + * Takeover gate of a desktop 26.x panel, called after the DCP started and + * before the boot framebuffer is evicted. Everything that can still be + * refused is tried while the boot framebuffer owns the screen: DCP ready, + * modes known, panel connected, power on, one mode set. Returns 0 when it + * is safe to evict, otherwise a negative errno and a short reason. A + * failure leaves the DCP running: its shutdown sequence can crash it. + */ +int dcp_takeover_trial(struct platform_device *pdev, const char **reason) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct drm_display_mode cand[DCP_DESKTOP_TRIAL_MAX_CAND]; + unsigned int i, k, n = 0, pref = UINT_MAX, boot = UINT_MAX; + u32 best = U32_MAX; + struct dcp_desktop_state *d; + struct drm_crtc_state *st; + int win = -1, ret = -EIO; + + *reason = "none"; + if (!dcp || dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP || + !dcp_has_panel(dcp)) + return 0; + d = dcp->desktop; + if (dcp->crashed) { + *reason = "dcp-crashed"; + return -ENODEV; + } + if (!dcp->active) { + *reason = "dcp-not-ready"; + return -ETIMEDOUT; + } + if (!READ_ONCE(dcp->nr_modes)) { + *reason = "no-modes"; + return -ENODEV; + } + if (!dcp->connector || !dcp->connector->connected) { + *reason = "not-connected"; + return -ENODEV; + } + /* The boot-like mode first, then the preferred one. */ + mutex_lock(&dcp->modes_lock); + for (i = 0; i < dcp->nr_modes; i++) { + const struct dcp_display_mode *m = &dcp->modes[i]; + + if ((m->mode.type & DRM_MODE_TYPE_PREFERRED) && pref == UINT_MAX) + pref = i; + if (!m->vrr) { + u32 delta = abs((int)m->mode.clock - + DCP_DESKTOP_BOOT_PIXEL_CLOCK_KHZ); + + if (delta < best) { + best = delta; + boot = i; + } + } + } + if (pref == UINT_MAX) + pref = 0; + /* Only a mode within 1% of the boot loader's clock counts as boot-like. */ + if (boot != UINT_MAX && best <= DCP_DESKTOP_BOOT_PIXEL_CLOCK_KHZ / 100) + cand[n++] = dcp->modes[boot].mode; + else + boot = UINT_MAX; + if (pref != boot) + cand[n++] = dcp->modes[pref].mode; + mutex_unlock(&dcp->modes_lock); + + st = kzalloc_obj(*st); + if (!st) { + *reason = "no-memory"; + return -ENOMEM; + } + + dcp_poweron(pdev); + if (dcp->crashed || d->poweron_result) { + *reason = dcp->crashed ? "dcp-crashed-in-poweron" : "poweron-failed"; + goto out; + } + + for (k = 0; k < n; k++) { + int r; + + st->mode = cand[k]; + dev_dbg(dcp->dev, "takeover trial mode set " DRM_MODE_FMT "\n", + DRM_MODE_ARG(&st->mode)); + r = iomfb_modeset_v26_6_desktop(dcp, st); + if (!r && d->mode_live) { + win = k; + break; + } + if (dcp->crashed) + break; + } + if (win < 0) { + *reason = dcp->crashed ? "dcp-crashed-in-modeset" : "modeset-failed"; + goto out; + } + + d->trial_timing_mode_id = dcp->mode.timing_mode_id; + d->trial_color_mode_id = dcp->mode.color_mode_id; + d->trial_powered = true; + d->trial_modeset_ok = true; + + /* Make the mode that worked the one the console client picks. */ + mutex_lock(&dcp->modes_lock); + for (i = 0; i < dcp->nr_modes; i++) { + struct dcp_display_mode *m = &dcp->modes[i]; + + if (m->timing_mode_id == d->trial_timing_mode_id && + m->color_mode_id == d->trial_color_mode_id) + m->mode.type |= DRM_MODE_TYPE_PREFERRED; + else + m->mode.type &= ~DRM_MODE_TYPE_PREFERRED; + } + mutex_unlock(&dcp->modes_lock); + dev_info(dcp->dev, "takeover trial mode " DRM_MODE_FMT " accepted\n", + DRM_MODE_ARG(&cand[win])); + ret = 0; +out: + kfree(st); + return ret; +} + /* The user may own drm_display_mode, so we need to search for our copy */ bool lookup_mode(struct apple_dcp *dcp, const struct drm_display_mode *mode, struct dcp_display_mode *out) @@ -2118,6 +2278,9 @@ int dcp_crtc_atomic_modeset(struct drm_crtc *crtc, case DCP_FIRMWARE_V_14_7_TEMPLATE: ret = iomfb_modeset_v14_7_0(dcp, crtc_state); break; + case DCP_FIRMWARE_V_26_6_DESKTOP: + ret = dcp_desktop_modeset(dcp, crtc_state); + break; case DCP_FIRMWARE_V_26_6: ret = iomfb_v26_6_modeset(dcp, crtc_state); break; @@ -2627,6 +2790,25 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_commit *commit) iomfb_v26_6_flush(dcp, crtc, commit); return; } + + /* + * Desktop 26.x external display replugged while the compositor kept + * its CRTC active: no atomic enable follows, and the firmware, which + * powered the pipe off with the unplug, swallows every swap. Run the + * power-on and mode set the enable would have run, once. + */ + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + dcp->desktop->ext_pipe_off && !dcp->main_display && + !READ_ONCE(dcp->mode_state.valid) && dcp->connector && + READ_ONCE(dcp->connector->connected)) { + struct drm_crtc_state *cs = drm_atomic_get_new_crtc_state(commit, crtc); + + if (cs && cs->active && cs->mode.hdisplay) { + dev_dbg(dcp->dev, "re-powering the external pipe of an active CRTC\n"); + dcp_poweron(pdev); + dcp_crtc_atomic_modeset(crtc, commit); + } + } if (dcp->fw_compat == DCP_FIRMWARE_V_14_7) { iomfb_v14_7_flush(dcp, crtc, commit); return; @@ -2678,6 +2860,9 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_commit *commit) case DCP_FIRMWARE_V_14_7_TEMPLATE: iomfb_flush_v14_7_0(dcp, crtc, commit); break; + case DCP_FIRMWARE_V_26_6_DESKTOP: + iomfb_flush_v26_6_desktop(dcp, crtc, commit); + break; case DCP_FIRMWARE_H17P: iomfb_flush_h17p(dcp, crtc, commit); break; @@ -2699,6 +2884,9 @@ static void iomfb_start(struct apple_dcp *dcp) case DCP_FIRMWARE_V_14_7_TEMPLATE: iomfb_start_v14_7_0(dcp); break; + case DCP_FIRMWARE_V_26_6_DESKTOP: + iomfb_start_v26_6_desktop(dcp); + break; case DCP_FIRMWARE_H17P: iomfb_start_h17p(dcp); break; @@ -2783,9 +2971,12 @@ int iomfb_start_rtkit(struct apple_dcp *dcp) if (ret) return ret; } - ret = apple_rtkit_start_ep(dcp->rtk, IOMFB_ENDPOINT); - if (ret) - return ret; + /* Desktop 26.x started the transport with its auxiliary endpoints. */ + if (dcp->fw_compat != DCP_FIRMWARE_V_26_6_DESKTOP) { + ret = apple_rtkit_start_ep(dcp->rtk, IOMFB_ENDPOINT); + if (ret) + return ret; + } dcp->shmem = dma_alloc_coherent(dcp->dev, DCP_SHMEM_SIZE, &shmem_iova, GFP_KERNEL); @@ -2813,6 +3004,9 @@ void iomfb_shutdown(struct apple_dcp *dcp) case DCP_FIRMWARE_V_14_7_TEMPLATE: iomfb_shutdown_v14_7_0(dcp); break; + case DCP_FIRMWARE_V_26_6_DESKTOP: + iomfb_shutdown_v26_6_desktop(dcp); + break; case DCP_FIRMWARE_H17P: iomfb_shutdown_h17p(dcp); break; diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 25c2e0254183f..7e6e16f1dddf7 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -406,4 +406,88 @@ struct dcpep_get_tiling_state_resp { u32 ret; } __packed; +/* + * Records of the desktop 26.6 IOMFB (T6041, DCP_FIRMWARE_V_26_6_DESKTOP) + * that differ from the shared ones above. + */ + +/* A364 get_mode_info: fills the mode record A413 then selects. */ +struct dcp_get_mode_info_req_v26_6 { + u8 mode[0x64]; + u8 mode_null; + u8 padding0[3]; + u64 opaque; + u32 timing_mode_id; + u32 color_mode_id; + u8 result_null; + u8 padding1[3]; +} __packed; + +struct dcp_get_mode_info_resp_v26_6 { + u8 opaque[0x74]; +} __packed; + +/* A469 abort_swaps_dcp takes the bare client identity. */ +struct iomfb_abort_swaps_dcp_req_v26_6 { + u64 client; +} __packed; + +struct iomfb_abort_swaps_dcp_resp_v26_6 { + u32 ret; +} __packed; + +/* D592 swap_complete_intent_gated, padded to 0x14. */ +struct dcp_swap_complete_intent_gated_v26_6 { + u32 swap_id; + u8 unkBool; + u32 unkInt; + u32 width; + u32 height; + u8 padding[3]; +} __packed; + +/* D125 read_edt_data reply, padded to 0x24. */ +struct dcp_read_edt_data_resp_v26_6 { + u32 value[8]; + u8 ret; + u8 padding[3]; +} __packed; + +/* + * D575 hotplug_gated: the connection state and a tiled-display record in, + * a 0x4c-byte record out. + */ +struct dcp_hotplug_req_v26_6 { + u64 connected; + u8 opaque[0x50]; +} __packed; + +struct dcp_hotplug_resp_v26_6 { + u8 opaque[0x4c]; +} __packed; + +/* A boolean argument, padded to four bytes. */ +struct dcp_bool_arg { + u8 value; + u8 padding[3]; +} __packed; + +/* + * A420 get_gamma_table / A421 set_gamma_table: three channels (red, green, + * blue) of 257 u32 entries followed by a null flag, 3088 bytes in and out. + */ +#define IOMFB_GAMMA_ENTRIES 257 + +struct iomfb_gamma_table_req { + __le32 table[3][IOMFB_GAMMA_ENTRIES]; + u8 table_null; + u8 padding[3]; +} __packed; + +struct iomfb_gamma_table_resp { + __le32 table[3][IOMFB_GAMMA_ENTRIES]; + u8 table_null; + u8 padding[3]; +} __packed; + #endif diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index c1b0e21b65e5e..246498a8e843c 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -41,6 +42,166 @@ static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), timestamp[6]) == 0x30); static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), flags1) == 0x40); +/* The queued swap of this instance. */ +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +#define DCP_FW_SWAP(dcp) ((dcp)->desktop->swap) +#else +#define DCP_FW_SWAP(dcp) DCP_FW_UNION((dcp)->swap) +#endif + +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +enum dcp_power_transition { + DCP_POWER_TRANSITION_NONE, + DCP_POWER_TRANSITION_DISPLAY_OFF, + DCP_POWER_TRANSITION_RESTARTABLE_SLEEP, + DCP_POWER_TRANSITION_SHUTDOWN, +}; + +/* The records of this instance that differ from the shared ones. */ +#define dcp_bool_arg_t struct dcp_bool_arg +#define dcp_abort_swaps_req_t struct iomfb_abort_swaps_dcp_req_v26_6 +#define dcp_read_edt_data_resp_t struct dcp_read_edt_data_resp_v26_6 +#define dcp_swap_intent_t struct dcp_swap_complete_intent_gated_v26_6 + +/* + * set_power_state (A473) carries four independent transition flags. The + * firmware's own sleep sets the first and the last, its shutdown the second; + * both then pass no result. + */ +static struct DCP_FW_NAME(dcp_set_power_state_req) +dcp_power_state_req(u64 state, enum dcp_power_transition transition) +{ + struct DCP_FW_NAME(dcp_set_power_state_req) req = { + .unklong = state, + .padding = { 0xaa, 0xaa, 0xaa }, + }; + + switch (transition) { + case DCP_POWER_TRANSITION_RESTARTABLE_SLEEP: + req.flags[0] = 1; + req.flags[3] = 1; + req.result_null = 1; + break; + case DCP_POWER_TRANSITION_SHUTDOWN: + req.flags[1] = 1; + req.result_null = 1; + break; + case DCP_POWER_TRANSITION_NONE: + case DCP_POWER_TRANSITION_DISPLAY_OFF: + break; + } + + return req; +} + +static dcp_bool_arg_t dcp_bool_arg(bool value) +{ + return (dcp_bool_arg_t) { + .value = value, + .padding = { 0xaa, 0xaa, 0xaa }, + }; +} + +/* + * Brightness and display-off swaps carry the level in nits, as a binary64. + * Every u32 is exactly representable, so no kernel floating point is needed. + */ +static void dcp_swap_set_brightness(struct DCP_FW_NAME(dcp_swap) *swap, u32 nits) +{ + u64 bits = 0; + + if (nits) { + unsigned int exponent = fls(nits) - 1; + + bits = (u64)(exponent + 1023) << 52; + bits |= ((u64)nits << (52 - exponent)) & GENMASK_ULL(51, 0); + } + + swap->bl_update = 1; + swap->bl_flags[1] = 1; + put_unaligned_le64(bits, &swap->bl_nits); +} + +/* + * The firmware's own client passes its IOUserClient pointer as an opaque + * client identity; this one passes a cookie chosen at probe. + */ +static struct DCP_FW_NAME(dcp_swap_start_req) +dcp_swap_start_request(struct apple_dcp *dcp) +{ + return (struct DCP_FW_NAME(dcp_swap_start_req)) { + .client = dcp->desktop->client, + .padding = { 0xaa, 0xaa, 0xaa }, + }; +} + +static dcp_abort_swaps_req_t dcp_abort_swaps_request(struct apple_dcp *dcp) +{ + return (dcp_abort_swaps_req_t) { .client = dcp->desktop->client }; +} + +/* + * A414 and A474, with these arguments, bracket the display power changes. + * The request is five zero bytes followed by three bytes of 0xaa. + */ +struct dcp_wake_state_req_v26_6 { + u8 data[8]; +} __packed; + +static const struct dcp_wake_state_req_v26_6 dcp_wake_state_req = { + .data = { 0, 0, 0, 0, 0, 0xaa, 0xaa, 0xaa }, +}; + +/* Methods without a slot in the shared method table. */ +static const struct dcp_method_entry dcp_get_mode_info_method = { + .name = "dcpep_get_mode_info", + .tag = { 'A', '3', '6', '4' }, +}; + +static const struct dcp_method_entry dcp_get_gamma_table_method = { + .name = "iomfbep_get_gamma_table", + .tag = { 'A', '4', '2', '0' }, +}; + +static const struct dcp_method_entry dcp_set_gamma_table_method = { + .name = "iomfbep_set_gamma_table", + .tag = { 'A', '4', '2', '1' }, +}; + +static_assert(sizeof(struct DCP_FW_NAME(dcp_swap)) == 0x588); +static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), swap_id) == 0x98); +static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), bl_update) == 0x354); +static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), bl_flags) == 0x358); +static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), bl_nits) == 0x35e); +static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), presents_surface) == 0x51e); +static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), has_completion) == 0x570); +static_assert(sizeof(struct DCP_FW_NAME(dcp_surface)) == 0x22c); +static_assert(sizeof(struct DCP_FW_NAME(dcp_swap_submit_req)) == 0x1bd8); +static_assert(sizeof(struct DCP_FW_NAME(dcp_swap_submit_resp)) == 0xc); +static_assert(sizeof(struct DCP_FW_NAME(dc_swap_complete_resp)) == 0x730); +static_assert(sizeof(struct DCP_FW_NAME(dcp_map_reg_req)) == 0x10); +static_assert(sizeof(struct dcp_set_parameter_dcp) == 0x28); +static_assert(sizeof(struct dcp_get_mode_info_req_v26_6) == 0x7c); +static_assert(sizeof(struct dcp_get_mode_info_resp_v26_6) == 0x74); +static_assert(sizeof(struct iomfb_abort_swaps_dcp_req_v26_6) == 0x8); +static_assert(sizeof(struct iomfb_abort_swaps_dcp_resp_v26_6) == 0x4); +static_assert(sizeof(struct dcp_swap_complete_intent_gated_v26_6) == 0x14); +static_assert(sizeof(struct dcp_read_edt_data_resp_v26_6) == 0x24); +static_assert(sizeof(struct dcp_hotplug_req_v26_6) == 0x58); +static_assert(sizeof(struct dcp_hotplug_resp_v26_6) == 0x4c); +static_assert(sizeof(struct dcp_bool_arg) == 0x4); +static_assert(sizeof(struct dcp_wake_state_req_v26_6) == 0x8); +static_assert(sizeof(struct iomfb_gamma_table_req) == 3088); +static_assert(sizeof(struct iomfb_gamma_table_resp) == 3088); +static_assert(sizeof(struct dcp_set_frame_sync_props_req_v26_6_desktop) == 0x54); +static_assert(sizeof(struct dcp_set_frame_sync_props_resp_v26_6_desktop) == 0x50); +#else +#define dcp_bool_arg_t u32 +#define dcp_abort_swaps_req_t struct iomfb_abort_swaps_dcp_req +#define dcp_read_edt_data_resp_t struct dcp_read_edt_data_resp +#define dcp_swap_intent_t struct dcp_swap_complete_intent_gated +#endif + struct dcp_wait_cookie { struct kref refcount; struct completion done; @@ -63,9 +224,19 @@ DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperatur IOMFB_THUNK_INOUT(set_matrix); IOMFB_THUNK_INOUT(get_color_remap_mode); IOMFB_THUNK_INOUT(last_client_close); +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +DCP_THUNK_INOUT(iomfb_abort_swaps_dcp, iomfbep_abort_swaps_dcp, + struct iomfb_abort_swaps_dcp_req_v26_6, + struct iomfb_abort_swaps_dcp_resp_v26_6); +/* The A414/A474 slots of the shared table, see dcp_wake_state_req. */ +DCP_THUNK_INOUT(dcp_prepare_wake, dcpep_pipe_cfg_414, + struct dcp_wake_state_req_v26_6, u64); +DCP_THUNK_OUT(dcp_finish_wake, dcpep_pipe_query_474, u32); +#else IOMFB_THUNK_INOUT(abort_swaps_dcp); +#endif -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) static void dcp_swap_submit(struct apple_dcp *dcp, bool oob, struct dcp_swap_submit_req_h17p *request, dcp_callback_t cb, void *cookie) @@ -103,7 +274,7 @@ DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state, * channel. Sent on the ordinary command channel it is never answered, which * strands the power transition and every later call behind it. */ -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) #define DCP_POWER_OOB true #else #define DCP_POWER_OOB false @@ -116,7 +287,7 @@ DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state, * is no vblank and no swap completion. Keep the mode the bootloader * programmed, which is the panel's native mode, instead of requesting one. */ -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) #define DCP_INHERIT_BOOT_MODE true #else #define DCP_INHERIT_BOOT_MODE false @@ -129,7 +300,7 @@ DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state, * straight to set_power_state, whose number and sizes are known. Power-off * still sends its clear swap first. */ -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) #define DCP_HAS_CLIENT_TEARDOWN false #else #define DCP_HAS_CLIENT_TEARDOWN true @@ -144,17 +315,19 @@ 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); +DCP_THUNK_INOUT(dcp_late_init_signal, dcpep_late_init_signal, dcp_bool_arg_t, + 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_IN(dcp_flush_supports_power, dcpep_flush_supports_power, + dcp_bool_arg_t); 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); -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* * Default-framebuffer queries issued from inside start_hardware_boot (D121). * The getters take a zeroed input of the expected size; the firmware only @@ -252,7 +425,7 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp, ktime_t now = ktime_get(); bool retire = true; -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G && !dcp_present_complete_h17p(&dcp->present_state_h17p, resp->swap_id)) { /* @@ -294,7 +467,7 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp, dcp_drm_crtc_page_flip(dcp, now); } if (READ_ONCE(dcp->crc_enabled)) { -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) { iomfb_queue_crc_h17p(dcp, resp->swap_id); return; @@ -323,7 +496,7 @@ static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *o return false; } -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* * H17P asks the AP to make the default framebuffer surface (D582). The * firmware expects the AP to register a surface from inside that callback, @@ -470,6 +643,8 @@ static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, v static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *prop) { switch (prop->id) { +#if !DCP_FW_DESKTOP_26(DCP_FW_VER) + /* The desktop 26.x firmware publishes no panel level here. */ case IOMFB_PROPERTY_NITS_V13_3: /* * 13.x firmware publishes another property under the 14.7 ID, with @@ -480,7 +655,7 @@ static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *pr case IOMFB_PROPERTY_NITS_V14_7: #endif { -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) /* The H17P takeover level comes from the powerlog hint interface. */ if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) break; @@ -494,6 +669,7 @@ static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *pr } break; } +#endif default: dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value); } @@ -577,7 +753,7 @@ static bool dcpep_map_piodma(struct apple_dcp *dcp, goto reject; } -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) memdesc->piodma_mapped = true; /* the H17P reply echoes the buffer index */ @@ -585,6 +761,12 @@ static bool dcpep_map_piodma(struct apple_dcp *dcp, .buffer = req->buffer, .dva = memdesc->dva, }; +#elif DCP_FW_DESKTOP_26(DCP_FW_VER) + /* the desktop 26.x reply echoes the buffer index too */ + *resp = (struct DCP_FW_NAME(dcp_map_buf_resp)) { + .buffer = req->buffer, + .dva = memdesc->dva, + }; #else *resp = (struct DCP_FW_NAME(dcp_map_buf_resp)) { .dva = memdesc->dva }; #endif @@ -604,8 +786,11 @@ static struct DCP_FW_NAME(dcp_map_buf_resp) dcpep_cb_map_piodma(struct apple_dcp if (dcpep_map_piodma(dcp, req, &resp)) return resp; -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) resp.buffer = req->buffer; +#elif DCP_FW_DESKTOP_26(DCP_FW_VER) + resp.buffer = req->buffer; + resp.ret = EINVAL; #else resp.ret = EINVAL; #endif @@ -629,7 +814,7 @@ static bool dcpep_unmap_piodma(struct apple_dcp *dcp, if (!test_bit(request->buffer, dcp->memdesc_map) || !memdesc->buf || !dcp->iommu_dom) goto not_mapped; -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (!memdesc->piodma_mapped) goto not_mapped; #endif @@ -716,7 +901,7 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp, resp.dva_size = allocated.size; resp.dva = allocated.dva; -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* * Linux allocation traces correlate the first H17P reply address with * the first SG page. The remaining pages need not be contiguous. @@ -860,7 +1045,8 @@ static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp) return clk_get_rate(dcp->clk); } -#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) && DCP_FW_VER < DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) && !DCP_FW_H17(DCP_FW_VER) && \ + !DCP_FW_DESKTOP_26(DCP_FW_VER) /* The DVA that map_reg hands out for display register range @index. */ static dma_addr_t dcpep_map_reg_dva(struct apple_dcp *dcp, u32 index, struct resource *rsrc) @@ -898,7 +1084,7 @@ static struct DCP_FW_NAME(dcp_map_reg_resp) dcpep_cb_map_reg(struct apple_dcp *d 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(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* * H17P reaches these apertures physically, not through its * DART, and expects dva == addr in the reply. Trying to @@ -906,6 +1092,14 @@ static struct DCP_FW_NAME(dcp_map_reg_resp) dcpep_cb_map_reg(struct apple_dcp *d * DMA_MAPPING_ERROR as a device address. */ dma_addr_t dva = rsrc->start; +#elif DCP_FW_DESKTOP_26(DCP_FW_VER) + /* + * The desktop 26.x firmware takes the register windows' + * physical addresses as well. It reaches them on its + * DART-bypass stream; a mapping in its DMA stream would not + * affect those accesses. + */ + dma_addr_t dva = rsrc->start; #elif DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) dma_addr_t dva = dcpep_map_reg_dva(dcp, req->index, rsrc); WARN_ON(dva == DMA_MAPPING_ERROR); @@ -921,7 +1115,7 @@ static struct DCP_FW_NAME(dcp_map_reg_resp) dcpep_cb_map_reg(struct apple_dcp *d } } -static struct dcp_read_edt_data_resp +static dcp_read_edt_data_resp_t dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) { /* Observe boot-property requests without inventing firmware timings. */ @@ -931,7 +1125,7 @@ dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) (int)sizeof(req->key), req->key, req->count, req->value[0]); - return (struct dcp_read_edt_data_resp){ + return (dcp_read_edt_data_resp_t){ .value[0] = req->value[0], .ret = 0, }; @@ -940,7 +1134,7 @@ dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, u8 *enabled) { -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) /* H17P carries the level only in presents; ask for one more. */ if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) { if (dcp_has_panel(dcp) && dcp_backlight_active(dcp) && @@ -967,7 +1161,7 @@ static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) return false; } -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* * This length comes straight off the wire. A bogus value would turn * the allocation below into a multi-gigabyte GFP_KERNEL request. @@ -1168,7 +1362,7 @@ static void boot_5b(struct apple_dcp *dcp, void *out, void *cookie) static void boot_5(struct apple_dcp *dcp, void *out, void *cookie) { -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* H17P expects A380 right before set_display_refresh_properties */ dcp_dfb_ready_query(dcp, false, boot_5b, NULL); #else @@ -1179,8 +1373,10 @@ static void boot_5(struct apple_dcp *dcp, void *out, void *cookie) static void boot_4b(struct apple_dcp *dcp, void *out, void *cookie) { #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) u32 value = dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17G; +#elif DCP_FW_DESKTOP_26(DCP_FW_VER) + dcp_bool_arg_t value = dcp_bool_arg(true); #else u32 value = 1; #endif @@ -1192,7 +1388,7 @@ static void boot_4b(struct apple_dcp *dcp, void *out, void *cookie) static void boot_4(struct apple_dcp *dcp, void *out, void *cookie) { -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* H17P expects A033 between flush_supports_power and late_init_signal */ dcp_dfb_query(dcp, false, boot_4b, NULL); #else @@ -1202,7 +1398,11 @@ static void boot_4(struct apple_dcp *dcp, void *out, void *cookie) static void boot_3(struct apple_dcp *dcp, void *out, void *cookie) { +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + dcp_bool_arg_t v_true = dcp_bool_arg(true); +#else u32 v_true = true; +#endif dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL); } @@ -1212,7 +1412,7 @@ static void boot_2(struct apple_dcp *dcp, void *out, void *cookie) dcp_setup_video_limits(dcp, false, boot_3, NULL); } -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* * H17P expects the default framebuffer to be described between * create_default_fb and setup_video_limits: @@ -1280,14 +1480,14 @@ static void boot_1_6(struct apple_dcp *dcp, void *out, void *cookie) static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) { -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) dcp_create_default_fb(dcp, false, boot_1_6, NULL); #else dcp_create_default_fb(dcp, false, boot_2, NULL); #endif } -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) static void d000_done(struct apple_dcp *dcp, void *out, void *cookie) { struct dcp_channel *ch = &dcp->ch_cb; @@ -1356,7 +1556,7 @@ static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) return rt_bw; } -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* * H17P's D006 is 0x54 in / 0x50 out, far wider than the 28-byte * frame_sync_props of older firmware, so the fields that matter sit past @@ -1396,12 +1596,26 @@ dcpep_cb_set_frame_sync_props_h17p(struct apple_dcp *dcp, } #endif +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +/* D006: echo the properties with the flag bytes 73..75 cleared. */ +static struct dcp_set_frame_sync_props_resp_v26_6_desktop +dcpep_cb_set_frame_sync_props(struct apple_dcp *dcp, + struct dcp_set_frame_sync_props_req_v26_6_desktop *req) +{ + struct dcp_set_frame_sync_props_resp_v26_6_desktop resp; + + memcpy(resp.props, req->props, sizeof(resp.props)); + memset(&resp.props[73], 0, 3); + return resp; +} +#else 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){}; } +#endif /* Callback to get the current time as milliseconds since the UNIX epoch */ static u64 dcpep_cb_get_time(struct apple_dcp *dcp) @@ -1424,7 +1638,7 @@ static void release_swap_cookie(struct kref *ref) kfree(cookie); } -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) #define DCP_SWAP_FLAGS1 0x34865202ULL #define DCP_SWAP_FLAGS2 0x4ULL @@ -1440,7 +1654,7 @@ static void release_swap_cookie(struct kref *ref) */ static void dcp_h17p_prepare_swap(struct apple_dcp *dcp) { - struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_UNION(dcp->swap); + struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_SWAP(dcp); unsigned int i; /* @@ -1496,7 +1710,7 @@ static void dcp_h17p_prepare_swap(struct apple_dcp *dcp) { } static bool dcp_present_retires(struct apple_dcp *dcp) { -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) return !iomfb_present_brightness_only_h17p(dcp); #endif @@ -1505,7 +1719,7 @@ static bool dcp_present_retires(struct apple_dcp *dcp) static void dcp_present_failed(struct apple_dcp *dcp) { -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) iomfb_present_failed_h17p(dcp); #endif @@ -1513,7 +1727,7 @@ static void dcp_present_failed(struct apple_dcp *dcp) static bool dcp_present_begin(struct apple_dcp *dcp, u32 swap_id) { -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G && !dcp_present_begin_h17p(&dcp->present_state_h17p, swap_id)) { dev_err(dcp->dev, "overlapping present %u\n", swap_id); @@ -1531,7 +1745,7 @@ static bool dcp_present_begin(struct apple_dcp *dcp, u32 swap_id) */ static int dcp_present_submit(struct apple_dcp *dcp, u32 swap_id, bool accepted) { -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G && !dcp_present_submit_h17p(&dcp->present_state_h17p, swap_id, accepted)) { dev_err(dcp->dev, "unexpected present submission %u\n", swap_id); @@ -1551,7 +1765,7 @@ static int dcp_present_submit(struct apple_dcp *dcp, u32 swap_id, bool accepted) static void dcp_prepare_clear_swap(struct apple_dcp *dcp, void *request) { - typeof(DCP_FW_UNION(dcp->swap)) *swap = request; + typeof(DCP_FW_SWAP(dcp)) *swap = request; /* Clear surfaces. */ memset(swap, 0, sizeof(*swap)); @@ -1568,6 +1782,9 @@ static void dcp_prepare_clear_swap(struct apple_dcp *dcp, void *request) * brightness. */ if (dcp_has_panel(dcp)) { +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + dcp_swap_set_brightness(&swap->swap, 0); +#else #if !DCP_FW_LAYOUT_14_7(DCP_FW_VER) swap->swap.bl_unk = 1; #else @@ -1575,14 +1792,15 @@ static void dcp_prepare_clear_swap(struct apple_dcp *dcp, void *request) #endif swap->swap.bl_value = 0; swap->swap.bl_power = 0; +#endif } /* Null all surfaces */ for (int l = 0; l < SWAP_SURFACES; l++) swap->surf_null[l] = true; #if DCP_FW_VERSION(13, 2, 0) <= DCP_FW_VER -#if DCP_FW_VERSION(26, 0, 0) > DCP_FW_VER - for (int l = 0; l < 5; l++) +#if !DCP_FW_H17(DCP_FW_VER) + for (int l = 0; l < ARRAY_SIZE(swap->surf2_null); l++) swap->surf2_null[l] = true; #endif swap->unknown_pointer_null = true; @@ -1594,7 +1812,7 @@ static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) { struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data; struct dcp_swap_cookie *info = cookie; - u32 swap_id = DCP_FW_UNION(dcp->swap).swap.swap_id; + u32 swap_id = DCP_FW_SWAP(dcp).swap.swap_id; u32 status = resp ? resp->ret : ~0U; int ret; @@ -1643,11 +1861,11 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, dcp_drm_crtc_vblank(dcp->crtc); return; } -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) - dcp_prepare_clear_swap(dcp, &DCP_FW_UNION(dcp->swap)); + dcp_prepare_clear_swap(dcp, &DCP_FW_SWAP(dcp)); #endif - DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id; + DCP_FW_SWAP(dcp).swap.swap_id = resp->swap_id; if (dcp_present_retires(dcp)) dcp_arm_retained_framebuffers(dcp, resp->swap_id); @@ -1655,14 +1873,14 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, info->swap_id = resp->swap_id; dcp_h17p_prepare_swap(dcp); - dcp_swap_submit(dcp, false, &DCP_FW_UNION(dcp->swap), dcp_swap_cleared, cookie); + dcp_swap_submit(dcp, false, &DCP_FW_SWAP(dcp), dcp_swap_cleared, cookie); } static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) { struct dcp_wait_cookie *wait = cookie; -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G && out && get_unaligned_le64(out) == 1) WRITE_ONCE(dcp->pipe_enabled_h17p, true); @@ -1674,6 +1892,61 @@ static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) } } +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie); + +/* After set_power_state(1): select the display, then adaptive sync off. */ +static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cookie) +{ + u32 handle = dcp->main_display ? 0 : 2; + + dcp_set_display_device(dcp, false, &handle, + dcp->main_display ? dcp_on_final : + dcp_on_set_parameter, + cookie); +} + +static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_parameter_dcp param; + + /* minRR, mediaTargetRate and fractional rate, as three u64 */ + memset(¶m, 0xaa, sizeof(param)); + param.param = IOMFBPARAM_ADAPTIVE_SYNC; + memset(param.value, 0, 3 * sizeof(u64)); + param.count = 3; + + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_final, cookie); +} + +static void dcp_poweron_after_main_display(struct apple_dcp *dcp, void *out, + void *cookie) +{ + struct DCP_FW_NAME(dcp_set_power_state_req) req = + dcp_power_state_req(1, DCP_POWER_TRANSITION_NONE); + + dcp_set_power_state(dcp, DCP_POWER_OOB, &req, dcp_on_set_power_state, + cookie); +} + +static void dcp_poweron_after_finish_wake(struct apple_dcp *dcp, void *out, + void *cookie) +{ + dcp_is_main_display(dcp, false, dcp_poweron_after_main_display, cookie); +} + +static void dcp_poweron_after_first_client(struct apple_dcp *dcp, void *out, + void *cookie) +{ + dcp_finish_wake(dcp, false, dcp_poweron_after_finish_wake, cookie); +} + +static void dcp_poweron_after_prepare_wake(struct apple_dcp *dcp, void *out, + void *cookie) +{ + dcp_first_client_open(dcp, false, dcp_poweron_after_first_client, cookie); +} +#else static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cookie) { struct DCP_FW_NAME(dcp_set_power_state_req) req = { @@ -1697,15 +1970,18 @@ static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_set_power_state, cookie); } +#endif void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) { struct dcp_wait_cookie *cookie; int ret; +#if !DCP_FW_DESKTOP_26(DCP_FW_VER) u32 handle; +#endif dev_info(dcp->dev, "dcp_poweron() starting\n"); -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) iomfb_opaque_x_reset_h17p(dcp); #endif @@ -1720,8 +1996,22 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) /* increase refcount to ensure the receiver has a reference */ kref_get(&cookie->refcount); +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + /* + * The firmware's own wake order: A414, first_client_open, A474, + * is_main_display, set_power_state(1), then select the display. + * Without A414/A474, or with the display selected before A473, the + * controller stays powered off and every swap is swallowed. + */ + { + struct dcp_wake_state_req_v26_6 req = dcp_wake_state_req; + + dcp_prepare_wake(dcp, false, &req, dcp_poweron_after_prepare_wake, + cookie); + } +#else if (dcp->main_display) { -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* * H17P takes set_display_device (A411) once, in the * client-open sequence. Repeating it after the client is open @@ -1739,7 +2029,11 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) dcp_set_display_device(dcp, false, &handle, dcp_on_set_parameter, cookie); } +#endif ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(10000)); +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + dcp->desktop->poweron_result = ret > 0 ? 0 : ret == 0 ? 1 : 2; +#endif if (ret == 0) { dev_warn(dcp->dev, "wait for power timed out, connector will be broken\n"); @@ -1777,13 +2071,30 @@ static void complete_set_powerstate(struct apple_dcp *dcp, void *out, static bool dcp_power_stop_confirmed(struct dcp_wait_cookie *cookie) { /* H17P does not confirm that scanout has stopped. */ -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) return false; #else return READ_ONCE(cookie->status) == 0; #endif } +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +static void dcp_poweroff_after_main_display(struct apple_dcp *dcp, void *out, + void *cookie) +{ + struct DCP_FW_NAME(dcp_set_power_state_req) power_req = + dcp_power_state_req(0, DCP_POWER_TRANSITION_DISPLAY_OFF); + + dcp_set_power_state(dcp, DCP_POWER_OOB, &power_req, + complete_set_powerstate, cookie); +} + +static void dcp_poweroff_after_prepare_state(struct apple_dcp *dcp, void *out, + void *cookie) +{ + dcp_is_main_display(dcp, false, dcp_poweroff_after_main_display, cookie); +} +#else static void last_client_closed_poff(struct apple_dcp *dcp, void *out, void *cookie) { struct DCP_FW_NAME(dcp_set_power_state_req) power_req = { @@ -1804,21 +2115,27 @@ static void aborted_swaps_dcp_poff(struct apple_dcp *dcp, void *out, void *cooki last_client_closed_poff, cookie); #endif } +#endif void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) { int ret, swap_id; - struct iomfb_abort_swaps_dcp_req abort_req = { +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + struct DCP_FW_NAME(dcp_swap_start_req) swap_req = + dcp_swap_start_request(dcp); +#else + dcp_abort_swaps_req_t abort_req = { .client = { .flag2 = 1, }, }; + struct DCP_FW_NAME(dcp_swap_start_req) swap_req = { 0 }; +#endif struct dcp_swap_cookie *cookie; struct dcp_wait_cookie *poff_cookie; - struct DCP_FW_NAME(dcp_swap_start_req) swap_req = { 0 }; - typeof(DCP_FW_UNION(dcp->swap)) *swap = &DCP_FW_UNION(dcp->swap); + typeof(DCP_FW_SWAP(dcp)) *swap = &DCP_FW_SWAP(dcp); -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) /* H17P has no known safe pipe-off or surface-free transition. */ if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) return; @@ -1833,7 +2150,7 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) /* increase refcount to ensure the receiver has a reference */ kref_get(&cookie->refcount); -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17G) dcp_prepare_clear_swap(dcp, swap); #else @@ -1869,6 +2186,20 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) /* increase refcount to ensure the receiver has a reference */ kref_get(&poff_cookie->refcount); +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + /* + * The firmware's own display power-off goes from the clear swap + * through A414, is_main_display and set_power_state(0). It sends + * abort_swaps and last_client_close only for sleep and shutdown; + * here they could tear down state the link power-off still needs. + */ + { + struct dcp_wake_state_req_v26_6 req = dcp_wake_state_req; + + dcp_prepare_wake(dcp, false, &req, dcp_poweroff_after_prepare_state, + poff_cookie); + } +#else if (DCP_HAS_CLIENT_TEARDOWN) { iomfb_abort_swaps_dcp(dcp, false, &abort_req, aborted_swaps_dcp_poff, poff_cookie); @@ -1877,6 +2208,7 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) "power-off: skipping abort_swaps/last_client_close\n"); last_client_closed_poff(dcp, NULL, poff_cookie); } +#endif ret = wait_for_completion_timeout(&poff_cookie->done, msecs_to_jiffies(1000)); @@ -1899,9 +2231,14 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) static void last_client_closed_sleep(struct apple_dcp *dcp, void *out, void *cookie) { +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + struct DCP_FW_NAME(dcp_set_power_state_req) power_req = + dcp_power_state_req(0, DCP_POWER_TRANSITION_RESTARTABLE_SLEEP); +#else struct DCP_FW_NAME(dcp_set_power_state_req) power_req = { .unklong = 0, }; +#endif dcp_set_power_state(dcp, DCP_POWER_OOB, &power_req, complete_set_powerstate, cookie); } @@ -1914,15 +2251,20 @@ static void aborted_swaps_dcp_sleep(struct apple_dcp *dcp, void *out, void *cook void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp) { int ret; - struct iomfb_abort_swaps_dcp_req req = { +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + /* abort_swaps, last_client_close, set_power_state(0) as for sleep */ + dcp_abort_swaps_req_t req = dcp_abort_swaps_request(dcp); +#else + dcp_abort_swaps_req_t req = { .client = { .flag2 = 1, }, }; +#endif struct dcp_wait_cookie *cookie; -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) return; #endif @@ -2031,15 +2373,46 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) dcp_handle_hotplug_actions(dcp, action); } +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +/* + * D575 carries a tiled-display record next to the connection state and + * wants a 0x4c-byte record back. Every captured connected reply has an + * unaligned 0xffffffff at 71..74; byte 75 is padding. The disconnected + * reply holds more state, which is not decoded. + */ +static struct dcp_hotplug_resp_v26_6 +dcpep_cb_hotplug_v26_6(struct apple_dcp *dcp, struct dcp_hotplug_req_v26_6 *req) +{ + struct dcp_hotplug_resp_v26_6 resp = { 0 }; + u64 connected = req->connected; + + /* A display that went away is bootstrapped again on its next mode. */ + if (!dcp->main_display && + (!connected || (dcp->connector && + READ_ONCE(dcp->connector->connected) != !!connected))) + dcp->desktop->mode_live = false; + /* The firmware shuts an external pipe down with its display. */ + if (!dcp->main_display && !connected) + dcp->desktop->ext_pipe_off = true; + + dcpep_cb_hotplug(dcp, &connected); + + if (connected) + memset(&resp.opaque[0x47], 0xff, sizeof(u32)); + + return resp; +} +#endif + static void dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, - struct dcp_swap_complete_intent_gated *info) + dcp_swap_intent_t *info) { trace_iomfb_swap_complete_intent_gated(dcp, info->swap_id, info->width, info->height); } -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) /* * An aborted H17P present never completes. Finish it like a rejected one: * keep the displaced framebuffers for the next completed present and signal @@ -2072,7 +2445,7 @@ static void dcpep_cb_abort_swap_ap_gated(struct apple_dcp *dcp, u32 *swap_id) { trace_iomfb_abort_swap_ap_gated(dcp, *swap_id); -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) dcp_present_aborted(dcp, *swap_id); #endif @@ -2090,7 +2463,7 @@ dcpep_cb_get_tiling_state(struct apple_dcp *dcp, static u8 dcpep_cb_create_backlight_service(struct apple_dcp *dcp) { -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* * H17P asks during start_ios_gated, before any mode is known, but * dcp_has_panel() only becomes true once the panel dimensions have been @@ -2132,7 +2505,7 @@ 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); + struct dcp_read_edt_data_req, dcp_read_edt_data_resp_t); 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); @@ -2142,10 +2515,16 @@ 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); +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +TRAMPOLINE_INOUT(trampoline_set_frame_sync_props, dcpep_cb_set_frame_sync_props, + struct dcp_set_frame_sync_props_req_v26_6_desktop, + struct dcp_set_frame_sync_props_resp_v26_6_desktop); +#else 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); -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#endif +#if DCP_FW_H17(DCP_FW_VER) TRAMPOLINE_INOUT(trampoline_set_frame_sync_props_h17p, dcpep_cb_set_frame_sync_props_h17p, struct dcp_set_frame_sync_props_req_h17p, @@ -2153,10 +2532,15 @@ TRAMPOLINE_INOUT(trampoline_set_frame_sync_props_h17p, #endif TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +TRAMPOLINE_INOUT(trampoline_hotplug, dcpep_cb_hotplug_v26_6, + struct dcp_hotplug_req_v26_6, struct dcp_hotplug_resp_v26_6); +#else TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); +#endif TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, dcpep_cb_swap_complete_intent_gated, - struct dcp_swap_complete_intent_gated); + dcp_swap_intent_t); 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); @@ -2175,7 +2559,7 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) { int ret; struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data; - u32 swap_id = DCP_FW_UNION(dcp->swap).swap.swap_id; + u32 swap_id = DCP_FW_SWAP(dcp).swap.swap_id; u32 status = resp ? resp->ret : ~0U; ret = dcp_present_submit(dcp, swap_id, !status); @@ -2213,20 +2597,30 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) dcp_drm_crtc_vblank(dcp->crtc); return; } - DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id; + DCP_FW_SWAP(dcp).swap.swap_id = resp->swap_id; if (dcp_present_retires(dcp)) dcp_arm_retained_framebuffers(dcp, resp->swap_id); trace_iomfb_swap_submit(dcp, resp->swap_id); dcp_h17p_prepare_swap(dcp); - dcp_swap_submit(dcp, false, &DCP_FW_UNION(dcp->swap), dcp_swapped, NULL); + dcp_swap_submit(dcp, false, &DCP_FW_SWAP(dcp), dcp_swapped, NULL); } /* Helpers to modeset and swap, used to flush */ static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) { +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + struct DCP_FW_NAME(dcp_swap_start_req) start_req = + dcp_swap_start_request(dcp); + + if (READ_ONCE(dcp->mode_state.valid) && dcp->connector && + READ_ONCE(dcp->connector->connected)) + dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); + else + dcp_drm_crtc_vblank(dcp->crtc); +#else struct DCP_FW_NAME(dcp_swap_start_req) start_req = { 0 }; - struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_UNION(dcp->swap); + struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_SWAP(dcp); if (READ_ONCE(dcp->mode_state.valid) && dcp->connector && READ_ONCE(dcp->connector->connected)) { @@ -2251,14 +2645,128 @@ static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) } else { dcp_drm_crtc_vblank(dcp->crtc); } +#endif } -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +/* + * GAMMA_LUT. The scale of the firmware's gamma table is not documented, so + * the LUT is applied through the firmware's own table, read once: entry i of + * the result is that table at LUT[i]. This keeps whatever the firmware's + * table does on top, and a NULL LUT restores it unchanged. + */ +static u32 dcp_gamma_lookup(const __le32 *fw, u16 v) +{ + u32 pos = v * (IOMFB_GAMMA_ENTRIES - 1), i = pos / 0xffff; + s64 a = le32_to_cpu(fw[i]); + + if (i == IOMFB_GAMMA_ENTRIES - 1) + return a; + return a + div_s64((le32_to_cpu(fw[i + 1]) - a) * (pos % 0xffff), 0xffff); +} + +static void dcp_gamma_set_done(struct apple_dcp *dcp, void *out, void *cookie) +{ + do_swap(dcp, NULL, NULL); +} + +static void dcp_gamma_set(struct apple_dcp *dcp) +{ + struct dcp_desktop_state *d = dcp->desktop; + struct iomfb_gamma_table_req *req = d->gamma_out; + u32 i; + + *req = *d->gamma_fw; + for (i = 0; d->gamma_user && i < IOMFB_GAMMA_ENTRIES; i++) { + req->table[0][i] = cpu_to_le32(dcp_gamma_lookup(d->gamma_fw->table[0], + d->gamma_lut[i].red)); + req->table[1][i] = cpu_to_le32(dcp_gamma_lookup(d->gamma_fw->table[1], + d->gamma_lut[i].green)); + req->table[2][i] = cpu_to_le32(dcp_gamma_lookup(d->gamma_fw->table[2], + d->gamma_lut[i].blue)); + } + dcp_push(dcp, false, &dcp_set_gamma_table_method, sizeof(*req), + sizeof(struct iomfb_gamma_table_resp), req, dcp_gamma_set_done, + NULL); +} + +static void dcp_gamma_read(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_desktop_state *d = dcp->desktop; + struct iomfb_gamma_table_resp *resp = out; + u32 ch, i; + + /* A table to compose with rises from its first to its last entry. */ + for (ch = 0; resp && ch < 3; ch++) { + for (i = 1; i < IOMFB_GAMMA_ENTRIES; i++) + if (le32_to_cpu(resp->table[ch][i]) < + le32_to_cpu(resp->table[ch][i - 1])) + break; + if (resp->table_null || i < IOMFB_GAMMA_ENTRIES || + !le32_to_cpu(resp->table[ch][IOMFB_GAMMA_ENTRIES - 1])) + break; + } + if (!resp || ch < 3) { + dev_warn(dcp->dev, "unusable firmware gamma table, GAMMA_LUT ignored\n"); + d->gamma_unusable = true; + do_swap(dcp, NULL, NULL); + return; + } + memcpy(d->gamma_fw, resp, sizeof(*d->gamma_fw)); + d->gamma_fw_read = true; + dcp_gamma_set(dcp); +} + +/* After the colour matrix: send a changed gamma table, then the swap. */ +static void dcp_gamma_then_swap(struct apple_dcp *dcp, void *out, void *cookie) +{ + static struct iomfb_gamma_table_req empty; + struct dcp_desktop_state *d = dcp->desktop; + + if (!d->gamma_pending || d->gamma_unusable) { + do_swap(dcp, NULL, NULL); + return; + } + d->gamma_pending = false; + if (d->gamma_fw_read) + dcp_gamma_set(dcp); + else + dcp_push(dcp, false, &dcp_get_gamma_table_method, sizeof(empty), + sizeof(struct iomfb_gamma_table_resp), &empty, + dcp_gamma_read, NULL); +} + +/* Takes a GAMMA_LUT change from the CRTC state; false if there is nothing to send. */ +static bool dcp_gamma_update(struct apple_dcp *dcp, struct drm_crtc_state *crtc_state) +{ + struct dcp_desktop_state *d = dcp->desktop; + bool user = crtc_state->gamma_lut; + + if (!crtc_state->color_mgmt_changed || d->gamma_unusable || + (!user && !d->gamma_user)) + return false; + if (user && drm_color_lut_size(crtc_state->gamma_lut) != IOMFB_GAMMA_ENTRIES) + return false; + if (!d->gamma_fw) { + d->gamma_fw = devm_kcalloc(dcp->dev, 2, sizeof(*d->gamma_fw), + GFP_KERNEL); + if (!d->gamma_fw) + return false; + d->gamma_out = d->gamma_fw + 1; + } + if (user) + memcpy(d->gamma_lut, crtc_state->gamma_lut->data, sizeof(d->gamma_lut)); + d->gamma_user = user; + return true; +} +#endif + +#if DCP_FW_H17(DCP_FW_VER) /* * H17P expects A428 between set_matrix and swap_start, with 0x10000 (1.0 in * 16.16 fixed point), the same identity value the D006 reply carries. */ -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) void iomfb_present_backlight_h17p(struct apple_dcp *dcp) { do_swap(dcp, NULL, NULL); @@ -2293,6 +2801,212 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, } } +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +struct dcp_modeset_cookie { + /* Must be first: release_wait_cookie() frees this allocation. */ + struct dcp_wait_cookie wait; + struct dcp_set_digital_out_mode_req actual; + struct dcp_set_digital_out_mode_req bootstrap; + bool use_bootstrap; +}; + +/* + * get_mode_info (A364) fills the firmware's mode record for a timing and + * color pair before set_digital_out_mode (A413) selects it. The request + * bytes the firmware does not read are 0xaa, as the firmware's own client + * leaves them. + */ +static void dcp_get_mode_info(struct apple_dcp *dcp, + const struct dcp_set_digital_out_mode_req *mode, + dcp_callback_t cb, void *cookie) +{ + struct dcp_get_mode_info_req_v26_6 req; + + memset(&req, 0xaa, sizeof(req)); + memset(req.mode, 0, sizeof(req.mode)); + req.mode_null = 0; + req.timing_mode_id = mode->timing_mode_id; + req.color_mode_id = mode->color_mode_id; + req.result_null = 0; + memset(req.padding1, 0, sizeof(req.padding1)); + + dcp_push(dcp, false, &dcp_get_mode_info_method, sizeof(req), + sizeof(struct dcp_get_mode_info_resp_v26_6), &req, cb, cookie); +} + +static void dcp_modeset_after_actual_info(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_modeset_cookie *modeset = cookie; + + if (!data) { + complete_set_digital_out_mode(dcp, NULL, cookie); + return; + } + dcp_set_digital_out_mode(dcp, false, &modeset->actual, + complete_set_digital_out_mode, cookie); +} + +static void dcp_modeset_after_bootstrap(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_modeset_cookie *modeset = cookie; + + if (!data || get_unaligned_le32(data)) { + complete_set_digital_out_mode(dcp, data, cookie); + return; + } + dcp_get_mode_info(dcp, &modeset->actual, dcp_modeset_after_actual_info, + cookie); +} + +static void dcp_modeset_after_bootstrap_info(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_modeset_cookie *modeset = cookie; + + if (!data) { + complete_set_digital_out_mode(dcp, NULL, cookie); + return; + } + dcp_set_digital_out_mode(dcp, false, &modeset->bootstrap, + dcp_modeset_after_bootstrap, cookie); +} + +/* Selects the cookie's mode, through the bootstrap color element first. */ +static void dcp_modeset_select(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_modeset_cookie *modeset = cookie; + + if (modeset->use_bootstrap) + dcp_get_mode_info(dcp, &modeset->bootstrap, + dcp_modeset_after_bootstrap_info, cookie); + else + dcp_get_mode_info(dcp, &modeset->actual, + dcp_modeset_after_actual_info, cookie); +} + +int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, + struct drm_crtc_state *crtc_state) +{ + struct dcp_display_mode selected, *mode = &selected; + struct dcp_modeset_cookie *modeset; + struct dcp_wait_cookie *cookie; + struct dcp_color_mode *cmode = NULL; + int ret; + + if (dcp->crashed) + return -ENODEV; + + if (!lookup_mode(dcp, &crtc_state->mode, mode)) { + dev_err(dcp->dev, "no match for " DRM_MODE_FMT "\n", + DRM_MODE_ARG(&crtc_state->mode)); + return -EIO; + } + + dev_dbg(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_dbg(dcp->dev, + "color mode depth:%hhu format:%u colorimetry:%u eotf:%u range:%u vrr:%u\n", + cmode->depth, cmode->format, cmode->colorimetry, + cmode->eotf, cmode->range, mode->vrr); + + dcp->mode = (struct dcp_set_digital_out_mode_req) { + .color_mode_id = mode->color_mode_id, + .timing_mode_id = mode->timing_mode_id + }; + + /* + * Suspected VRR modes need swap timestamps, see iomfb_flush(). + * Adaptive sync itself stays off, see dcp_mode_vrr_supported(). + */ + dcp->use_timestamps = mode->vrr; + + /* + * The firmware changes a fixed panel's mode through the swap geometry, + * not through get_mode_info and set_digital_out_mode. Accept the + * single fixed timing it advertises, already matched above. + */ + if (dcp_has_panel(dcp) && dcp->main_display && + READ_ONCE(dcp->nr_modes) == 1 && !mode->vrr) { + dcp->desktop->mode_live = true; + return 0; + } + + modeset = kzalloc_obj(*modeset); + if (!modeset) + return -ENOMEM; + cookie = &modeset->wait; + modeset->actual = dcp->mode; + + cookie->status = ~0U; + init_completion(&cookie->done); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); + + dcp->swap_submit_timestamp = 0; + + /* A pipe without a live mode is bootstrapped first. */ + if (!dcp->desktop->mode_live && mode->has_bootstrap) { + modeset->use_bootstrap = true; + modeset->bootstrap = (struct dcp_set_digital_out_mode_req) { + .color_mode_id = mode->bootstrap_color_id, + .timing_mode_id = mode->timing_mode_id, + }; + dev_dbg(dcp->dev, + "bootstrapping the display pipe (color:%u timing:%u)\n", + modeset->bootstrap.color_mode_id, + modeset->bootstrap.timing_mode_id); + } + + dcp_modeset_select(dcp, NULL, 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)); + + if (ret == 0) { + dev_err(dcp->dev, "set_digital_out_mode timed out\n"); + ret = -EIO; + } else if (ret < 0) { + dev_err(dcp->dev, + "waiting on set_digital_out_mode failed:%d\n", ret); + ret = -EIO; + } else if (!READ_ONCE(cookie->replied)) { + dev_err(dcp->dev, "set_digital_out_mode got no reply\n"); + ret = -EIO; + } else if (READ_ONCE(cookie->status)) { + dev_err(dcp->dev, "set_digital_out_mode returned 0x%08x\n", + READ_ONCE(cookie->status)); + ret = -EIO; + } else { + dev_dbg(dcp->dev, + "set_digital_out_mode finished with %d to spare\n", + jiffies_to_msecs(ret)); + ret = 0; + } + kref_put(&cookie->refcount, release_wait_cookie); + + dcp->desktop->mode_live = !ret; + return ret; +} +#else /* DCP applies Adaptive Sync changes when the display mode is reselected. */ static void dcp_on_set_adaptive_sync(struct apple_dcp *dcp, void *out, void *cookie) @@ -2437,13 +3151,14 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, return 0; } +#endif void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_commit *commit) { 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); + struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_SWAP(dcp); int plane_idx, l; int has_surface = 0; bool update_brightness; @@ -2455,13 +3170,18 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru for (l = 0; l < SWAP_SURFACES; l++) req->surf_null[l] = true; #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) -#if DCP_FW_VER < DCP_FW_VERSION(26, 0, 0) - for (l = 0; l < 5; l++) +#if !DCP_FW_H17(DCP_FW_VER) + for (l = 0; l < ARRAY_SIZE(req->surf2_null); l++) req->surf2_null[l] = true; #endif req->unknown_pointer_null = true; req->unknown_output_null = true; #endif +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + /* The firmware's own client sets both on every visible swap. */ + req->swap.presents_surface = 1; + req->swap.has_completion = 1; +#endif /* * Clear all surfaces on startup. The boot framebuffer in surface 0 @@ -2493,7 +3213,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->swap.src_rect[l] = apple_state->src_rect; req->swap.dst_rect[l] = apple_state->dst_rect; -#if DCP_FW_VER < DCP_FW_VERSION(26, 0, 0) +#if !DCP_FW_H17(DCP_FW_VER) && !DCP_FW_DESKTOP_26(DCP_FW_VER) req->swap.surf_ids[apl_plane->iomfb_surf] = plane->base.id; #endif @@ -2502,7 +3222,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->surf_iova[l] = apple_state->iova; req->surf[l].base = apple_state->surf; -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) req->surf[l].base.surface_id = 0; #endif @@ -2515,7 +3235,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->surf[l].base.colorspace == DCP_COLORSPACE_BG_SRGB) req->surf[l].base.colorspace = DCP_COLORSPACE_NATIVE; -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* H17P external SDR RGB needs an sRGB source tag. Native * tagging washes out the same pixel values on this profile. * Keep YUV, other source spaces and other profiles unchanged. @@ -2541,6 +3261,22 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->clear = 1; } +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + if (has_surface && dcp->use_timestamps) { + /* + * Timestamp types 1, 2 and 7 hold the time the swap should be + * shown, in the firmware's clock. A target long past shows it + * at the next refresh: 120 Hz in the fixed mode, as soon as + * the panel allows with adaptive sync. The architectural + * counter must not go here: after a VRR mode set the firmware + * then never shows the swap, and it holds a swap whose target + * it reads as more than about a frame ahead. + */ + req->swap.timestamp[0] = 120; + req->swap.timestamp[1] = 120; + req->swap.timestamp[6] = 120; + } +#else if (has_surface && (dcp->use_timestamps || dcp->vrr_enabled)) { u64 submit_timestamp = dcp->swap_submit_timestamp; u64 timestamp = arch_timer_read_counter(); @@ -2562,16 +3298,33 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru trace_iomfb_vrr_timestamps(dcp, dcp->vrr_enabled, timestamp, submit_timestamp); } +#endif /* These fields should be set together */ req->swap.swap_completed = req->swap.swap_enabled; /* update brightness if changed */ update_brightness = dcp_has_panel(dcp) && dcp->brightness.update; -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) update_brightness = update_brightness && dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17G; #endif +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + /* + * The firmware publishes no initial level, so nits stays at the + * registration default until userspace writes the backlight. Sending + * that from the forced post-poweron update can turn the panel off: + * keep the level the boot loader set until one is requested. + */ + if (update_brightness && !dcp->brightness.requested) { + dcp->brightness.update = false; + update_brightness = false; + } + if (update_brightness) { + dcp_swap_set_brightness(&req->swap, dcp->brightness.nits); + dcp->brightness.update = false; + } +#else if (update_brightness) { #if DCP_FW_LAYOUT_14_7(DCP_FW_VER) req->swap.bl_update = 1; @@ -2582,6 +3335,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->swap.bl_power = 0x40; dcp->brightness.update = false; } +#endif if (crtc_state->color_mgmt_changed) { struct iomfb_set_matrix_req mat = { @@ -2595,8 +3349,11 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru mat.matrix[0] = mat.matrix[4] = mat.matrix[8] = 1LLU << 32; } -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) iomfb_set_matrix(dcp, false, &mat, dcp_pre_swap_a428, NULL); +#elif DCP_FW_DESKTOP_26(DCP_FW_VER) + dcp->desktop->gamma_pending = dcp_gamma_update(dcp, crtc_state); + iomfb_set_matrix(dcp, false, &mat, dcp_gamma_then_swap, NULL); #else iomfb_set_matrix(dcp, false, &mat, do_swap, NULL); #endif @@ -2632,6 +3389,11 @@ static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) } dcp->active = true; + /* + * Desktop 26.x publishes no initial panel level that would register + * the backlight (see iomfb_cb_pr_publish()): the DRM master registers + * it once its device is, see dcp_backlight_takeover_registered(). + */ complete(&dcp->start_done); } @@ -2640,7 +3402,7 @@ static void init_3(struct apple_dcp *dcp, void *out, void *cookie) dcp_is_main_display(dcp, false, res_is_main_display, NULL); } -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* * The client-open sequence H17P firmware expects before the first swap, in * H17P method numbers (dcp_push() applies the H17G aliases): @@ -2801,13 +3563,18 @@ static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) void DCP_FW_NAME(iomfb_shutdown)(struct apple_dcp *dcp) { +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + struct DCP_FW_NAME(dcp_set_power_state_req) req = + dcp_power_state_req(0, DCP_POWER_TRANSITION_SHUTDOWN); +#else struct DCP_FW_NAME(dcp_set_power_state_req) req = { /* defaults are ok */ }; +#endif struct dcp_wait_cookie *cookie; int ret; -#if DCP_FW_VERSION(26, 0, 0) <= DCP_FW_VER +#if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) { iomfb_queue_stop(dcp); return; diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index b987ddfbd8ce7..d37a013b50d90 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -28,7 +28,7 @@ struct DCP_FW_NAME(dcp_swap) { u8 unk_v14_7[0x48]; #endif -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* * H17P inserts 0x48 bytes between flags2 and the swap ID, which moves * to +0x98. Every following field shifts with it: surf_ids +0x9c, @@ -65,6 +65,15 @@ struct DCP_FW_NAME(dcp_swap) { #else u32 bl_update; #endif +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + /* + * The desktop 26.x firmware takes the panel level in nits, as an + * IEEE-754 binary64 at +0x35e (unaligned), flagged by bl_flags[1]. + */ + u8 bl_flags[6]; + __le64 bl_nits; + u8 unk_366[0x2b]; +#else #if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0) u16 unk_2e2; #else @@ -78,6 +87,7 @@ struct DCP_FW_NAME(dcp_swap) { u32 bl_value; // min value is 0x10000000 u8 bl_power; // constant 0x40 for on u8 unk_2f3[0x2d]; +#endif #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) u8 unk_320[0x13f]; #if DCP_FW_LAYOUT_14_7(DCP_FW_VER) @@ -86,7 +96,18 @@ struct DCP_FW_NAME(dcp_swap) { u32 unk_flags; u32 unk_flags2; #endif -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + /* + * Appended to the 14.7 record. The firmware expects both flags set + * on every visible swap; the other bytes are zero. + */ + u8 unk_26_6[0x16]; + u8 presents_surface; + u8 unk_26_6_17[0x51]; + u8 has_completion; + u8 unk_26_6_69[0x17]; +#endif +#if DCP_FW_H17(DCP_FW_VER) /* * The H17P A408 request is 0xe9c bytes; with a 0x22c-byte surface, * SWAP_SURFACES = 4 and no surf2 block, that leaves this tail. @@ -107,6 +128,13 @@ struct DCP_FW_NAME(dcp_surface) { /* Prototypes */ +/* The desktop 26.x request carries one more surf2 entry. */ +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +#define DCP_FW_SURF2_COUNT 6 +#else +#define DCP_FW_SURF2_COUNT 5 +#endif + struct DCP_FW_NAME(dcp_swap_submit_req) { struct DCP_FW_NAME(dcp_swap) swap; struct DCP_FW_NAME(dcp_surface) surf[SWAP_SURFACES]; @@ -117,9 +145,9 @@ struct DCP_FW_NAME(dcp_swap_submit_req) { * H17P carries no surf2 block: its trailing u8 null-flag run is 8 bytes * (swap_null + surf_null[4] + three tail bools). */ -#if DCP_FW_VER < DCP_FW_VERSION(26, 0, 0) - struct DCP_FW_NAME(dcp_surface) surf2[5]; - u64 surf2_iova[5]; +#if !DCP_FW_H17(DCP_FW_VER) + struct DCP_FW_NAME(dcp_surface) surf2[DCP_FW_SURF2_COUNT]; + u64 surf2_iova[DCP_FW_SURF2_COUNT]; #endif #endif u8 unkbool; @@ -132,14 +160,14 @@ struct DCP_FW_NAME(dcp_swap_submit_req) { #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) u32 unkU32Ptr; #endif -#if DCP_FW_LAYOUT_14_7(DCP_FW_VER) +#if DCP_FW_LAYOUT_14_7(DCP_FW_VER) && !DCP_FW_DESKTOP_26(DCP_FW_VER) u8 padding_14_7[0x234]; #endif u8 swap_null; u8 surf_null[SWAP_SURFACES]; #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) -#if DCP_FW_VER < DCP_FW_VERSION(26, 0, 0) - u8 surf2_null[5]; +#if !DCP_FW_H17(DCP_FW_VER) + u8 surf2_null[DCP_FW_SURF2_COUNT]; #endif #endif u8 unkoutbool_null; @@ -147,11 +175,13 @@ struct DCP_FW_NAME(dcp_swap_submit_req) { u8 unknown_pointer_null; u8 unknown_output_null; #endif -#if DCP_FW_VER < DCP_FW_VERSION(26, 0, 0) +#if !DCP_FW_H17(DCP_FW_VER) && !DCP_FW_DESKTOP_26(DCP_FW_VER) u8 padding[1]; #endif } __packed; +#undef DCP_FW_SURF2_COUNT + struct DCP_FW_NAME(dcp_swap_submit_resp) { u8 unkoutbool; #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) @@ -165,9 +195,33 @@ struct DCP_FW_NAME(dcp_swap_submit_resp) { * A406 swap_start / A473 set_power_state. H17P shrank both requests: the * 0x10-byte struct dcp_iouserclient became a bare u64 handle and the * swap_start response no longer echoes it. swap_start is 0x10 in / 0x8 out, - * set_power_state 0x10 in. + * set_power_state 0x10 in. The desktop 26.x firmware made the same + * swap_start change; its set_power_state carries four transition flags. */ -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_DESKTOP_26(DCP_FW_VER) +struct DCP_FW_NAME(dcp_swap_start_req) { + u32 swap_id; + u64 client; /* opaque client identity */ + u8 swap_id_null; + u8 padding[3]; +} __packed; + +struct DCP_FW_NAME(dcp_swap_start_resp) { + u32 swap_id; + u32 ret; +} __packed; + +struct DCP_FW_NAME(dcp_set_power_state_req) { + u64 unklong; /* the power state */ + u8 flags[4]; /* independent transition flags */ + u8 result_null; + u8 padding[3]; +} __packed; + +static_assert(sizeof(struct DCP_FW_NAME(dcp_swap_start_req)) == 0x10); +static_assert(sizeof(struct DCP_FW_NAME(dcp_swap_start_resp)) == 0x8); +static_assert(sizeof(struct DCP_FW_NAME(dcp_set_power_state_req)) == 0x10); +#elif DCP_FW_H17(DCP_FW_VER) struct DCP_FW_NAME(dcp_swap_start_req) { u32 swap_id; u64 client; /* IOUserClient kernel VA */ @@ -219,13 +273,21 @@ struct DCP_FW_NAME(dcp_set_power_state_req) { * u32 ret }. With the old layout the DVA lands four bytes late and the * display engine DMAs to a bogus address. */ -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) struct DCP_FW_NAME(dcp_map_buf_resp) { u32 buffer; u64 dva; u32 unk; } __packed; static_assert(sizeof(struct DCP_FW_NAME(dcp_map_buf_resp)) == 0x10); +#elif DCP_FW_DESKTOP_26(DCP_FW_VER) +/* The desktop 26.x reply has the H17P layout, with a status. */ +struct DCP_FW_NAME(dcp_map_buf_resp) { + u32 buffer; + u64 dva; + u32 ret; +} __packed; +static_assert(sizeof(struct DCP_FW_NAME(dcp_map_buf_resp)) == 0x10); #else struct DCP_FW_NAME(dcp_map_buf_resp) { u64 vaddr; @@ -236,8 +298,12 @@ struct DCP_FW_NAME(dcp_map_buf_resp) { struct DCP_FW_NAME(dc_swap_complete_resp) { u32 swap_id; /* +0x000 */ +#if DCP_FW_DESKTOP_26(DCP_FW_VER) + /* The desktop 26.x record is 0x730 bytes; the rest is not decoded. */ + u8 opaque[0x72c]; +#else u8 unkbool; /* +0x004 */ -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) /* * H17P: swap_complete_ap_gated(u32, bool, SwapCompleteData const *, * SwapInfoBlob const *, u32, bool) is 0x730 bytes on the wire, 89 more @@ -268,6 +334,7 @@ struct DCP_FW_NAME(dc_swap_complete_resp) { u32 unkint; u8 swap_info_null; #endif +#endif } __packed; struct DCP_FW_NAME(dcp_map_reg_req) { diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c new file mode 100644 index 0000000000000..98a3504a9a5f0 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +/* + * Desktop 26.6 firmware on T6041 (M4 Pro/Max) through the classic IOMFB + * template (DCP_FIRMWARE_V_26_6_DESKTOP). This firmware continues the 14.7 + * IOMFB, with its own method numbers, record layouts and power sequences. + * Not to be confused with iomfb_v26_6.c, the J613 DCPLink session, nor with + * the H17-generation 26.x firmware of iomfb_h17p.c. + */ + +/* Include all per-version headers before defining DCP_FW ourself. */ +#include "iomfb_v12_3.h" +#include "iomfb_v13_3.h" +#include "iomfb_v14_7_tpl.h" +#include "iomfb_v26_6_desktop.h" +#include "iomfb_h17p.h" +#include "version_utils.h" + +static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { + IOMFB_METHOD("A000", dcpep_late_init_signal), + IOMFB_METHOD("A029", dcpep_setup_video_limits), + IOMFB_METHOD("A131", iomfbep_a131_pmu_service_matched), + IOMFB_METHOD("A132", iomfbep_a132_backlight_service_matched), + IOMFB_METHOD("A385", dcpep_set_create_dfb), + IOMFB_METHOD("A386", iomfbep_a358_vi_set_temperature_hint), + IOMFB_METHOD("A401", dcpep_start_signal), + IOMFB_METHOD("A406", dcpep_swap_start), + IOMFB_METHOD("A407", dcpep_swap_submit), + IOMFB_METHOD("A411", dcpep_set_display_device), + IOMFB_METHOD("A412", dcpep_is_main_display), + IOMFB_METHOD("A413", dcpep_set_digital_out_mode), + /* prepare/finish around the display power changes */ + IOMFB_METHOD("A414", dcpep_pipe_cfg_414), + IOMFB_METHOD("A423", iomfbep_set_matrix), + IOMFB_METHOD("A427", iomfbep_get_color_remap_mode), + IOMFB_METHOD("A442", dcpep_set_parameter_dcp), + IOMFB_METHOD("A446", dcpep_create_default_fb), + IOMFB_METHOD("A450", dcpep_enable_disable_video_power_savings), + IOMFB_METHOD("A457", dcpep_first_client_open), + IOMFB_METHOD("A458", iomfbep_last_client_close), + IOMFB_METHOD("A465", dcpep_set_display_refresh_properties), + IOMFB_METHOD("A468", dcpep_flush_supports_power), + IOMFB_METHOD("A469", iomfbep_abort_swaps_dcp), + IOMFB_METHOD("A473", dcpep_set_power_state), + IOMFB_METHOD("A474", dcpep_pipe_query_474), + /* A364 get_mode_info and A420/A421 gamma: see iomfb_template.c */ +}; + +#define DCP_FW v26_6_desktop +#define DCP_FW_VER DCP_FW_VERSION_DESKTOP(26, 6, 0) + +#include "iomfb_template.c" + +struct dcp_dict_passthrough_req_v26_6 { + u8 key[0x40]; + u8 value[0x1000]; + u8 value_null; + u8 padding[3]; +} __packed; + +struct dcp_dict_passthrough_resp_v26_6 { + u8 value[0x1000]; + u8 ret; + u8 padding[3]; +} __packed; + +static_assert(sizeof(struct dcp_dict_passthrough_req_v26_6) == 0x1044); +static_assert(sizeof(struct dcp_dict_passthrough_resp_v26_6) == 0x1004); + +/* + * D114 periodically publishes dictionary-valued statistics. The value is an + * in/out pointer, which the firmware deserializes again: the firmware's own + * client returns the serialized buffer unchanged and reports false. An + * all-zero reply is not a harmless false. + */ +static bool trampoline_passthrough_dict_v26_6(struct apple_dcp *dcp, int tag, + void *out, void *in) +{ + const struct dcp_dict_passthrough_req_v26_6 *req = in; + struct dcp_dict_passthrough_resp_v26_6 *resp = out; + + trace_iomfb_callback(dcp, tag, "dcpep_cb_passthrough_dict"); + + if (!req->value_null) + memcpy(resp->value, req->value, sizeof(resp->value)); + + resp->ret = false; + memset(resp->padding, 0, sizeof(resp->padding)); + + return true; +} + +static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { + [0] = trampoline_true, /* did_boot_signal */ + [1] = trampoline_true, /* did_power_on_signal */ + [2] = trampoline_nop, /* will_power_off_signal */ + [3] = trampoline_rt_bandwidth, + [6] = trampoline_set_frame_sync_props, + [100] = iomfbep_cb_match_pmu_service, + [101] = trampoline_zero, /* get_display_default_stride */ + [102] = trampoline_nop, /* set_number_property */ + [104] = trampoline_nop, /* set_boolean_property */ + [107] = trampoline_nop, /* remove_property */ + [108] = trampoline_true, /* create_provider_service */ + [109] = trampoline_true, /* create_product_service */ + [110] = trampoline_true, /* create_pmu_service */ + [111] = trampoline_true, /* create_iomfb_service */ + [112] = trampoline_create_backlight_service, + [113] = trampoline_true, /* create_nvram_service? */ + [114] = trampoline_passthrough_dict_v26_6, + /* set_tiling_state: the value followed by success */ + [115] = trampoline_get_tiling_state, + [121] = dcpep_cb_boot_1, /* start_hardware_boot */ + [122] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate */ + [123] = trampoline_false, + [125] = trampoline_read_edt_data, + [127] = trampoline_prop_start, + [128] = trampoline_prop_chunk, + [129] = trampoline_prop_end, + [134] = trampoline_zero, + [201] = trampoline_map_piodma, + [206] = iomfbep_cb_match_pmu_service_2, + [207] = iomfbep_cb_match_backlight_service, + [208] = trampoline_nop, /* update_backlight_factor_prop */ + [209] = trampoline_get_time, + [300] = trampoline_pr_publish, + [400] = trampoline_nop, + [401] = trampoline_get_uint_prop, + [406] = trampoline_nop, /* set_fx_prop: a property notification */ + [408] = trampoline_get_frequency, + [411] = trampoline_map_reg, + [413] = trampoline_true, /* sr_set_property_dict */ + [414] = trampoline_sr_set_property_int, + [415] = trampoline_true, /* sr_set_property_bool */ + [451] = trampoline_allocate_buffer, + [552] = trampoline_true, /* set_property_dict_0 */ + [561] = trampoline_true, /* set_property_dict */ + [563] = trampoline_true, /* set_property_int */ + [565] = trampoline_true, /* set_property_bool */ + [567] = trampoline_true, /* set_property_str */ + /* a zero status for both its start and stop events */ + [572] = trampoline_zero, + [575] = trampoline_hotplug, + [576] = trampoline_nop, + [581] = trampoline_nop, + [582] = trampoline_true, /* create_default_fb_surface */ + [590] = trampoline_swap_complete, + [592] = trampoline_swap_complete_intent_gated, + [593] = trampoline_abort_swap_ap_gated, + /* a four-byte padded notification; only its first byte is read */ + [594] = trampoline_enable_backlight_message_ap_gated, + [595] = trampoline_nop, + [598] = trampoline_nop, + [599] = trampoline_nop, +}; + +void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp) +{ + dcp->cb_handlers = cb_handlers; + + dcp_start_signal(dcp, false, dcp_started, NULL); +} + +#undef DCP_FW_VER +#undef DCP_FW diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.h b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.h new file mode 100644 index 0000000000000..697edc47fde3b --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* Copyright The Asahi Linux Contributors */ + +/* + * The classic IOMFB (dcpep) protocol of the desktop 26.6 firmware on T6041 + * (M4 Pro/Max), from the shared IOMFB template + * (DCP_FIRMWARE_V_26_6_DESKTOP). It continues the 14.7 IOMFB. Not to be + * confused with iomfb_v26_6.h, the J613 DCPLink session. + */ + +#ifndef __APPLE_IOMFB_V26_6_DESKTOP_H__ +#define __APPLE_IOMFB_V26_6_DESKTOP_H__ + +#include + +#include "version_utils.h" + +/* + * dcp-internal.h includes this header, also from inside another template + * instantiation: keep that one's DCP_FW. + */ +#pragma push_macro("DCP_FW") +#pragma push_macro("DCP_FW_VER") +#undef DCP_FW +#undef DCP_FW_VER + +#define DCP_FW v26_6_desktop +#define DCP_FW_VER DCP_FW_VERSION_DESKTOP(26, 6, 0) + +#include "iomfb_template.h" + +#undef DCP_FW_VER +#undef DCP_FW + +#pragma pop_macro("DCP_FW_VER") +#pragma pop_macro("DCP_FW") + +/* + * D006 set_frame_sync_props is 0x54 bytes in and 0x50 out. The firmware + * expects the first 0x50 request bytes back, with the three flag bytes at + * 73..75 cleared. + */ +struct dcp_set_frame_sync_props_req_v26_6_desktop { + u8 props[0x50]; + u8 opaque[0x4]; +} __packed; + +struct dcp_set_frame_sync_props_resp_v26_6_desktop { + u8 props[0x50]; +} __packed; + +#endif /* __APPLE_IOMFB_V26_6_DESKTOP_H__ */ diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 1d0638360251c..cecad5a1fb866 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -558,12 +558,14 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, struct dcp_color_mode *unsafe_rgb8) { struct iterator outer_it; + struct dcp_color_mode bootstrap = { .score = -1 }; unsigned int best_sdr_rank = 0, best_unsafe_rank = 0; int ret = 0; out->sdr_444.score = -1; out->sdr_rgb.score = -1; out->sdr.score = -1; out->best.score = -1; + out->has_bootstrap = false; ret = iterator_begin(handle, &outer_it, false); if (ret) @@ -627,7 +629,16 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, return ret; } - /* Skip virtual or partial entries */ + /* + * Skip virtual or partial entries. Remember the best virtual + * one: the desktop 26.x firmware bootstraps its generic pipe + * through it. + */ + if (is_virtual && cmode.score >= 0 && cmode.id >= 0 && + !fill_color_mode(&bootstrap, &cmode) && bootstrap.score >= 0) { + out->has_bootstrap = true; + out->bootstrap_color_id = bootstrap.id; + } if (is_virtual || cmode.score < 0 || cmode.id < 0 || cmode.id > U32_MAX) continue; diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 2ba2c22a07026..97a75a8b046a0 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -124,9 +124,16 @@ struct dcp_display_mode { bool preferred; bool metadata_timing_valid; bool automatic_rgb8_quirk; + /* + * The best virtual color element of the timing, which the desktop + * 26.x firmware selects first to bootstrap its generic pipe (valid if + * has_bootstrap). Both sit in what was padding. + */ + u32 bootstrap_color_id; u64 automatic_connection_sequence; u64 automatic_catalog_sequence; bool automatic_service_pending; + bool has_bootstrap; /* Immutable receipt provenance; never retag a cached exception. */ struct dcp_metadata_ticket unsafe_rgb8_ticket; u64 unsafe_rgb8_service_epoch; diff --git a/drivers/gpu/drm/apple/version_utils.h b/drivers/gpu/drm/apple/version_utils.h index 053bc0a15a844..8851ee57c2daf 100644 --- a/drivers/gpu/drm/apple/version_utils.h +++ b/drivers/gpu/drm/apple/version_utils.h @@ -13,12 +13,28 @@ #define DCP_FW_VERSION(x, y, z) ( ((x) << 16) | ((y) << 8) | (z) ) /* - * The swap record layout introduced with the 14.7 firmware. The - * H17-generation firmware (26.0 and later) keeps the earlier layout and - * extends it with its own fields. + * 26.x firmware exists in two unrelated wire lineages. The H17-generation + * firmware (A18 Pro, M5) is a new protocol; the M4-generation desktop + * firmware (T6041, the M4 Pro/Max) continues the 14.7 IOMFB. A desktop + * 26.x template instance marks its version with DCP_FW_DESKTOP, so that the + * plain ordering against older versions still holds. + */ +#define DCP_FW_DESKTOP (1 << 24) +#define DCP_FW_VERSION_DESKTOP(x, y, z) (DCP_FW_VERSION(x, y, z) | DCP_FW_DESKTOP) +#define DCP_FW_H17(ver) \ + ((ver) >= DCP_FW_VERSION(26, 0, 0) && !((ver) & DCP_FW_DESKTOP)) +#define DCP_FW_DESKTOP_26(ver) \ + (((ver) & DCP_FW_DESKTOP) && \ + ((ver) & ~DCP_FW_DESKTOP) >= DCP_FW_VERSION(26, 0, 0)) + +/* + * The swap record layout introduced with the 14.7 firmware. The desktop + * 26.x firmware keeps it and appends to it. The H17-generation firmware + * (26.0 and later) keeps the earlier layout and extends it with its own + * fields. */ #define DCP_FW_LAYOUT_14_7(ver) \ - ((ver) >= DCP_FW_VERSION(14, 7, 0) && (ver) < DCP_FW_VERSION(26, 0, 0)) + ((ver) >= DCP_FW_VERSION(14, 7, 0) && !DCP_FW_H17(ver)) enum dcp_firmware_version { DCP_FIRMWARE_UNKNOWN, @@ -40,6 +56,11 @@ enum dcp_firmware_version { DCP_FIRMWARE_V_26_6, /* T8132/J713 retained EP37 session. */ DCP_FIRMWARE_M4, + /* + * T6041 (M4 Pro/Max) desktop 26.6 IOMFB through the classic template + * (iomfb_v26_6_desktop.c); not the J613 DCPLink DCP_FIRMWARE_V_26_6. + */ + DCP_FIRMWARE_V_26_6_DESKTOP, }; #endif /*__APPLE_VERSION_UTILS_H__*/ diff --git a/drivers/phy/apple/Kconfig b/drivers/phy/apple/Kconfig index 10c94455f0974..5348987cf1dc8 100644 --- a/drivers/phy/apple/Kconfig +++ b/drivers/phy/apple/Kconfig @@ -32,23 +32,26 @@ config PHY_APPLE_ATC_KUNIT_TEST If unsure, say N. config PHY_APPLE_T6040_USB2 - tristate "Apple T6040 Type-C PHY (USB2 and USB3 host)" + tristate "Apple T6040 Type-C PHY (USB2/USB3 host, DisplayPort)" depends on ARM64 && ARCH_APPLE depends on TYPEC depends on APPLE_RTKIT select GENERIC_PHY select APPLE_TUNABLE help - Build the PHY provider for the T6040 generation's Type-C ports. It - implements the USB2 host sequence and the USB3 host attach decoded - from the paired AppleT6040TypeCPhy driver, including the start of - the PHY coprocessor, and provides no device mode, DisplayPort, - USB4/Thunderbolt or retimer support. + Build the PHY provider for the T6040 generation's Type-C ports and + fixed DisplayPort PHYs. It implements the USB2 host sequence and the + USB3 host attach decoded from the paired AppleT6040TypeCPhy driver, + including the start of the PHY coprocessor, and the DisplayPort mode + (all four lanes, RBR to HBR3) a Type-C controller selects through the + PHY's mode switch or that an HDMI converter's PHY runs permanently. + It provides no device mode, DisplayPort pin assignment D, USB4, + Thunderbolt or retimer support. This driver performs no ATC-register writes at probe time. Enabling a - matching device-tree node causes MMIO writes when a USB2 consumer - powers on the PHY and therefore requires platform-specific safety - review. + matching device-tree node causes MMIO writes when a USB2, USB3 or + DisplayPort consumer powers on the PHY and therefore requires + platform-specific safety review. config PHY_APPLE_DPTX tristate "Apple DPTX PHY" diff --git a/drivers/phy/apple/atc-t6050-dp-data.h b/drivers/phy/apple/atc-t6050-dp-data.h new file mode 100644 index 0000000000000..e4a09dae6c3f5 --- /dev/null +++ b/drivers/phy/apple/atc-t6050-dp-data.h @@ -0,0 +1,513 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* T6050 fixed DisplayPort PHY register sequences. */ +static const struct atc_op dp_init[] = { + { ATC_MASK, 39, 0x0, 0x1, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x1, 0x1, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x2, 0x2, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x2, 0x2, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x4, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POWER, 2, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x2c, 0x1, 0x0, 0 }, + { ATC_POLL, 39, 0x4, 0x100, 0x0, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x8, 0x8, 0 }, + { ATC_TUNABLE, 41, 0x0, 0x0, 0x0, 0 }, + { ATC_TUNABLE, 40, 0x0, 0x0, 0x1, 0 }, + { ATC_TUNABLE, 43, 0x0, 0x0, 0x2, 0 }, + { ATC_TUNABLE, 42, 0x0, 0x0, 0x3, 0 }, + { ATC_TUNABLE, 20, 0x0, 0x0, 0x4, 0 }, + { ATC_TUNABLE, 29, 0x0, 0x0, 0x5, 0 }, + { ATC_TUNABLE, 18, 0x0, 0x0, 0x6, 0 }, + { ATC_TUNABLE, 27, 0x0, 0x0, 0x7, 0 }, + { ATC_MASK, 4, 0x0, 0x3f0, 0x1c0, 0 }, + { ATC_MASK, 4, 0x18, 0xf000, 0x3000, 0 }, + { ATC_MASK, 9, 0x88, 0xff, 0x27, 0 }, + { ATC_MASK, 33, 0x4, 0x38, 0x8, 0 }, + { ATC_MASK, 10, 0xdc, 0x3ff0, 0x1760, 0 }, + { ATC_MASK, 21, 0xcc, 0x1f80000, 0x380000, 0 }, + { ATC_MASK, 30, 0xcc, 0x1f80000, 0x380000, 0 }, + { ATC_MASK, 20, 0x90, 0x1f80000, 0x380000, 0 }, + { ATC_MASK, 29, 0x90, 0x1f80000, 0x380000, 0 }, + { ATC_TUNABLE, 8, 0x0, 0x0, 0x8, 0 }, + { ATC_MASK, 2, 0x6c, 0x3c000fc, 0x48, 0 }, + { ATC_MASK, 2, 0x78, 0x1f, 0x14, 0 }, + { ATC_MASK, 2, 0x78, 0x3ffe0, 0x22000, 0 }, + { ATC_MASK, 2, 0x6c, 0x3, 0x2, 0 }, + { ATC_MASK, 2, 0xc, 0x2a, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_MASK, 39, 0x0, 0x10, 0x10, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x64, 0 }, +}; + +static const struct atc_op aux_on[] = { + { ATC_MASK, 34, 0x0, 0x3, 0x3, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 34, 0x0, 0xc, 0xc, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 34, 0x0, 0x300, 0x100, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 33, 0x0, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, +}; + +static const struct atc_op aux_off[] = { + { ATC_MASK, 33, 0x0, 0x1, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 34, 0x0, 0x300, 0x300, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 34, 0x0, 0x8, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 34, 0x0, 0x2, 0x0, 0 }, +}; + +static const struct atc_op dp_poweroff[] = { + { ATC_MASK, 2, 0x120, 0x100, 0x100, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x200, 0x200, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x20, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 5, 0x0, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x10, 0x10, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x4, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xc8, 0 }, + { ATC_POWER, 2, 0x0, 0x0, 0x0, 0 }, + { ATC_MASK, 39, 0x2c, 0x1, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x100, 0x100, 1000 }, + { ATC_MASK, 39, 0x0, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x8, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x4, 0x4, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x2, 0x0, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x1, 0x0, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x8, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, +}; + +static const struct atc_op dp_link_off[] = { + { ATC_MASK, 39, 0x14, 0x1007, 0x7, 0 }, + { ATC_MASK, 39, 0x18, 0x1, 0x0, 0 }, + { ATC_POLL, 39, 0x1c, 0x1, 0x0, 5000 }, + { ATC_MASK, 14, 0x0, 0x1, 0x1, 0 }, + { ATC_MASK, 22, 0x1c, 0xc00000, 0xc00000, 0 }, + { ATC_MASK, 22, 0x10, 0x600, 0x400, 0 }, + { ATC_MASK, 22, 0x4, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x100000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x400000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x4000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x1000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x1000, 0x0, 0 }, + { ATC_MASK, 2, 0x120, 0x100, 0x100, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x1c, 0xc00000, 0xc00000, 0 }, + { ATC_MASK, 31, 0x10, 0x600, 0x400, 0 }, + { ATC_MASK, 31, 0x4, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x100000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x400000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x4000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x1000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x1000, 0x0, 0 }, + { ATC_MASK, 2, 0x120, 0x200, 0x200, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x20, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x44, 0x6000000, 0x4000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x44, 0x18000000, 0x10000000, 0 }, + { ATC_MASK, 19, 0x24, 0xc0000000, 0x80000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x24, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x28, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x40, 0xc000, 0xc000, 0 }, + { ATC_MASK, 16, 0x1d0, 0x3, 0x1, 0 }, + { ATC_MASK, 19, 0x40, 0x10000, 0x0, 0 }, + { ATC_MASK, 2, 0xc, 0x4000, 0x4000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x40, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x400, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x44, 0x6000000, 0x4000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x44, 0x18000000, 0x10000000, 0 }, + { ATC_MASK, 28, 0x24, 0xc0000000, 0x80000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x24, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x28, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x40, 0xc000, 0xc000, 0 }, + { ATC_MASK, 25, 0x1d0, 0x3, 0x1, 0 }, + { ATC_MASK, 28, 0x40, 0x10000, 0x0, 0 }, + { ATC_MASK, 2, 0xc, 0x8000, 0x8000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x80, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x800, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x20, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 14, 0x0, 0xc, 0x8, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x440000, 0x440000, 0 }, + { ATC_MASK, 3, 0x8, 0x5, 0x5, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x880000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0xa, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x880000, 0x880000, 0 }, + { ATC_MASK, 3, 0x8, 0xa, 0xa, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x440000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x5, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x400000, 0x400000, 0 }, + { ATC_MASK, 3, 0x8, 0x4, 0x4, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x800000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x8, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x800000, 0x800000, 0 }, + { ATC_MASK, 3, 0x8, 0x8, 0x8, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x400000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x4, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x40000, 0x40000, 0 }, + { ATC_MASK, 3, 0x8, 0x1, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x80000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x80000, 0x80000, 0 }, + { ATC_MASK, 3, 0x8, 0x2, 0x2, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x40000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_POLL, 14, 0x34, 0x8, 0x0, 500000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_MASK, 6, 0x34, 0xf, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x0, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_MASK, 8, 0x0, 0xaaa, 0x0, 0 }, +}; + +static const struct atc_rate_op dp_link_on[] = { + { { ATC_MASK, 39, 0x14, 0x7, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 39, 0x18, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x6c, 0xfc, 0, 0 }, 0xf, { 0x48, 0x48, 0x48, 0x48 } }, + { { ATC_MASK, 14, 0x0, 0x40fff0, 0, 0 }, 0xf, { 0x1110, 0x1110, 0x1110, 0x1110 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x1, { 0x1e0e021c, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x1, { 0x156600, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x1, { 0x70000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x1, { 0x1, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x2, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x2, { 0x0, 0x1e0e01c2, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x2, { 0x0, 0x7fffffe, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x2, { 0x0, 0x155200, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x2, { 0x0, 0x70000, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x2, { 0x0, 0x1, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x4, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x1e0e01c2, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x7fffffe, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x554800, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x4, { 0x0, 0x0, 0x70000, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x4, { 0x0, 0x0, 0x1, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1e0e02a3 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0xbff7ffc } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x564800 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x70000 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1 } }, + { { ATC_MASK, 8, 0x0, 0xaaa, 0, 0 }, 0xf, { 0xaaa, 0xaaa, 0xaaa, 0xaaa } }, + { { ATC_MASK, 6, 0x30, 0x4000, 0, 0 }, 0xf, { 0x4000, 0x4000, 0x4000, 0x4000 } }, + { { ATC_MASK, 6, 0x34, 0xf, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x18, 0x1fe000, 0, 0 }, 0xf, { 0x2000, 0x2000, 0x2000, 0x2000 } }, + { { ATC_MASK, 6, 0x20, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x1c, 0x7, 0, 0 }, 0xf, { 0x7, 0x7, 0x7, 0x7 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x100, 0x100, 0x100, 0x100 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 14, 0x34, 0x8, 0, 500000 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_MASK, 19, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 28, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 14, 0x0, 0xc, 0, 0 }, 0xf, { 0xc, 0xc, 0xc, 0xc } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x10, 0, 0 }, 0xf, { 0x10, 0x10, 0x10, 0x10 } }, + { { ATC_MASK, 2, 0x120, 0x40, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x4, 0, 0 }, 0xf, { 0x4, 0x4, 0x4, 0x4 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x100, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0x120, 0x400, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0xfffffc0, 0, 0 }, 0xf, { 0xaafb800, 0xaafb800, 0xaafb800, 0xaafb800 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x1000000, 0, 0 }, 0xf, { 0x1000000, 0x1000000, 0x1000000, 0x1000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x4000000, 0, 0 }, 0xf, { 0x4000000, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x7c0, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x6c000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 21, 0x100, 0x100000, 0, 0 }, 0xf, { 0x100000, 0x100000, 0x100000, 0x100000 } }, + { { ATC_MASK, 21, 0x58, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 21, 0x104, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 21, 0x100, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x100000, 0, 0 }, 0x7, { 0x100000, 0x100000, 0x100000, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0x7, { 0x1, 0x1, 0x1, 0x0 } }, + { { ATC_MASK, 22, 0x44, 0x400000, 0, 0 }, 0x7, { 0x400000, 0x400000, 0x400000, 0x0 } }, + { { ATC_MASK, 22, 0x4, 0x3000000, 0, 0 }, 0xf, { 0x2000000, 0x2000000, 0x2000000, 0x2000000 } }, + { { ATC_MASK, 22, 0x4, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_MASK, 22, 0xc, 0x7000000, 0, 0 }, 0x1, { 0x5000000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 22, 0xc, 0x7000000, 0, 0 }, 0xe, { 0x0, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_MASK, 22, 0x8, 0x3, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 22, 0x10, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_MASK, 22, 0x1c, 0xc00000, 0, 0 }, 0xf, { 0x800000, 0x800000, 0x800000, 0x800000 } }, + { { ATC_MASK, 2, 0x120, 0x20, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 2, 0x120, 0x80, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 2, 0x120, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x200, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0x120, 0x800, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0xfffffc0, 0, 0 }, 0xf, { 0xaafb800, 0xaafb800, 0xaafb800, 0xaafb800 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x1000000, 0, 0 }, 0xf, { 0x1000000, 0x1000000, 0x1000000, 0x1000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x4000000, 0, 0 }, 0xf, { 0x4000000, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x7c0, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x6c000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 30, 0x100, 0x100000, 0, 0 }, 0xf, { 0x100000, 0x100000, 0x100000, 0x100000 } }, + { { ATC_MASK, 30, 0x58, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 30, 0x104, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 30, 0x100, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x100000, 0, 0 }, 0x7, { 0x100000, 0x100000, 0x100000, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0x7, { 0x1, 0x1, 0x1, 0x0 } }, + { { ATC_MASK, 31, 0x44, 0x400000, 0, 0 }, 0x7, { 0x400000, 0x400000, 0x400000, 0x0 } }, + { { ATC_MASK, 31, 0x4, 0x3000000, 0, 0 }, 0xf, { 0x2000000, 0x2000000, 0x2000000, 0x2000000 } }, + { { ATC_MASK, 31, 0x4, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_MASK, 31, 0xc, 0x7000000, 0, 0 }, 0x1, { 0x5000000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 31, 0xc, 0x7000000, 0, 0 }, 0xe, { 0x0, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_MASK, 31, 0x8, 0x3, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 31, 0x10, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_MASK, 31, 0x1c, 0xc00000, 0, 0 }, 0xf, { 0x800000, 0x800000, 0x800000, 0x800000 } }, + { { ATC_MASK, 19, 0xf4, 0x3000, 0, 0 }, 0xf, { 0x3000, 0x3000, 0x3000, 0x3000 } }, + { { ATC_MASK, 16, 0x188, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 16, 0x1d4, 0x700, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_MASK, 19, 0x24, 0x3f0000, 0, 0 }, 0xf, { 0x2a0000, 0x2a0000, 0x2a0000, 0x2a0000 } }, + { { ATC_MASK, 19, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 19, 0x78, 0xc0000, 0, 0 }, 0xf, { 0x80000, 0x80000, 0x80000, 0x80000 } }, + { { ATC_MASK, 19, 0x7c, 0xc0, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_MASK, 19, 0x84, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_MASK, 19, 0xd4, 0x600000, 0, 0 }, 0xf, { 0x400000, 0x400000, 0x400000, 0x400000 } }, + { { ATC_MASK, 19, 0xd8, 0x300, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_MASK, 19, 0x40, 0xc00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 19, 0x48, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_MASK, 19, 0x28, 0x1ff030, 0, 0 }, 0xf, { 0x11c020, 0x11c020, 0x11c020, 0x11c020 } }, + { { ATC_MASK, 19, 0x2c, 0xfc, 0, 0 }, 0xf, { 0xb0, 0xb0, 0xb0, 0xb0 } }, + { { ATC_MASK, 19, 0x28, 0xe00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 19, 0x28, 0x1c0, 0, 0 }, 0xf, { 0x180, 0x180, 0x180, 0x180 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x10, 0, 0 }, 0xf, { 0x10, 0x10, 0x10, 0x10 } }, + { { ATC_MASK, 2, 0x10, 0x40, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x4, 0, 0 }, 0xf, { 0x4, 0x4, 0x4, 0x4 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x400, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_MASK, 2, 0xc, 0x1000, 0, 0 }, 0xf, { 0x1000, 0x1000, 0x1000, 0x1000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x40, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_MASK, 2, 0xc, 0x100, 0, 0 }, 0xf, { 0x100, 0x100, 0x100, 0x100 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x4000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0xc, 0x10000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 16, 0x1d0, 0x3, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 16, 0x1d0, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x40, 0x30000, 0, 0 }, 0xf, { 0x30000, 0x30000, 0x30000, 0x30000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x40, 0xc000, 0, 0 }, 0xf, { 0xc000, 0xc000, 0xc000, 0xc000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x3010, 0, 0 }, 0xf, { 0x1010, 0x1010, 0x1010, 0x1010 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x3000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x24, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x24, 0xc0000000, 0, 0 }, 0xf, { 0xc0000000, 0xc0000000, 0xc0000000, 0xc0000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 19, 0x44, 0x18000000, 0, 0 }, 0xf, { 0x18000000, 0x18000000, 0x18000000, 0x18000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 19, 0x40, 0x38, 0, 0 }, 0x1, { 0x28, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 19, 0x40, 0x38, 0, 0 }, 0xe, { 0x0, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 19, 0x44, 0x60000000, 0, 0 }, 0xf, { 0x40000000, 0x40000000, 0x40000000, 0x40000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x44, 0x6000000, 0, 0 }, 0xf, { 0x6000000, 0x6000000, 0x6000000, 0x6000000 } }, + { { ATC_MASK, 19, 0x40, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 20, 0x68, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 20, 0xac, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 20, 0xa8, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 20, 0x18, 0x38, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0xf4, 0x3000, 0, 0 }, 0xf, { 0x3000, 0x3000, 0x3000, 0x3000 } }, + { { ATC_MASK, 25, 0x188, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 25, 0x1d4, 0x700, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_MASK, 28, 0x24, 0x3f0000, 0, 0 }, 0xf, { 0x2a0000, 0x2a0000, 0x2a0000, 0x2a0000 } }, + { { ATC_MASK, 28, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 28, 0x78, 0xc0000, 0, 0 }, 0xf, { 0x80000, 0x80000, 0x80000, 0x80000 } }, + { { ATC_MASK, 28, 0x7c, 0xc0, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_MASK, 28, 0x84, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_MASK, 28, 0xd4, 0x600000, 0, 0 }, 0xf, { 0x400000, 0x400000, 0x400000, 0x400000 } }, + { { ATC_MASK, 28, 0xd8, 0x300, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_MASK, 28, 0x40, 0xc00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 28, 0x48, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_MASK, 28, 0x28, 0x1ff030, 0, 0 }, 0xf, { 0x11c020, 0x11c020, 0x11c020, 0x11c020 } }, + { { ATC_MASK, 28, 0x2c, 0xfc, 0, 0 }, 0xf, { 0xb0, 0xb0, 0xb0, 0xb0 } }, + { { ATC_MASK, 28, 0x28, 0xe00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 28, 0x28, 0x1c0, 0, 0 }, 0xf, { 0x180, 0x180, 0x180, 0x180 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x20, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 2, 0x10, 0x80, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 2, 0x10, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x800, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 2, 0xc, 0x2000, 0, 0 }, 0xf, { 0x2000, 0x2000, 0x2000, 0x2000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x80, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_MASK, 2, 0xc, 0x200, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x8000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0xc, 0x20000, 0, 0 }, 0xf, { 0x20000, 0x20000, 0x20000, 0x20000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 25, 0x1d0, 0x3, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 25, 0x1d0, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x40, 0x30000, 0, 0 }, 0xf, { 0x30000, 0x30000, 0x30000, 0x30000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x40, 0xc000, 0, 0 }, 0xf, { 0xc000, 0xc000, 0xc000, 0xc000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x3010, 0, 0 }, 0xf, { 0x1010, 0x1010, 0x1010, 0x1010 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x3000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x24, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x24, 0xc0000000, 0, 0 }, 0xf, { 0xc0000000, 0xc0000000, 0xc0000000, 0xc0000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 28, 0x44, 0x18000000, 0, 0 }, 0xf, { 0x18000000, 0x18000000, 0x18000000, 0x18000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 28, 0x40, 0x38, 0, 0 }, 0x1, { 0x28, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 28, 0x40, 0x38, 0, 0 }, 0xe, { 0x0, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 28, 0x44, 0x60000000, 0, 0 }, 0xf, { 0x40000000, 0x40000000, 0x40000000, 0x40000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x44, 0x6000000, 0, 0 }, 0xf, { 0x6000000, 0x6000000, 0x6000000, 0x6000000 } }, + { { ATC_MASK, 28, 0x40, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 29, 0x68, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 29, 0xac, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 29, 0xa8, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 29, 0x18, 0x38, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 14, 0x0, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 39, 0x1c, 0x1, 0, 5000 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, +}; + +static const u32 dp_training_default[16] = { + 0x3f024, 0x237013, 0x37200b, 0x46e004, 0x3f01c, 0x23700b, 0x372004, 0x3f024, 0x3f013, 0x237004, 0x3f024, 0x3f024, 0x3f004, 0x3f024, 0x3f024, 0x3f024 +}; diff --git a/drivers/phy/apple/t6040-usb2.c b/drivers/phy/apple/t6040-usb2.c index 1a1fe530f2111..868d4304b2395 100644 --- a/drivers/phy/apple/t6040-usb2.c +++ b/drivers/phy/apple/t6040-usb2.c @@ -43,9 +43,12 @@ #include #include #include +#include #include #include #include +#include +#include #define T6040_USB2_USBCTL 0x00 #define T6040_USB2_USBCTL_MODE GENMASK(2, 0) @@ -102,6 +105,16 @@ #define T6040_PIPEHANDLER_NONSELECTED_BUSY BIT(12) #define T6040_PIPEHANDLER_DUMMY_PHY_READY BIT(15) +/* + * DisplayPort (PHY mode 3). Offsets are into the "core" and "cfg" windows + * the USB3 path uses; names follow the paired AppleT6040TypeCPhy driver. + */ +#define T6040_CORE_AUX_CTRL 0x28000 /* ACIOPHY_AUX_TOP_BLK_AUX_CTRL */ +#define T6040_CORE_AUX_PWR 0x28400 /* ACIOPHY_AUX_SHM_BLK_AUX_PWR_REG */ + +#define T6040_CFG_DP_CLKSWITCH_CTRL 0x14 /* ATCPHY_DP_CLKSWITCH_CONTROL_REGS */ +#define T6040_CFG_DP_CLKSWITCH_STAT 0x18 /* ATCPHY_DP_CLKSWITCH_STATUS_REGS */ + /* PHY core ("core"), atc-phy reg[2] and the blocks behind it */ #define T6040_CORE_CMN_OVRD 0x0c /* CFG0_TOP */ #define T6040_CORE_CMN_OVRD_SLEEP_B_BIG BIT(0) @@ -214,6 +227,7 @@ struct t6040_usb2_phy { struct phy *usb3; bool usb3_ok; void __iomem *regs[T6040_USB3_NR_REGIONS]; + struct resource *res[T6040_USB3_NR_REGIONS]; struct resource *cpu_data; struct apple_tunable *tunables[T6040_USB3_NR_TUNABLES]; struct apple_tunable *tunable_pipehandler; @@ -224,6 +238,15 @@ struct t6040_usb2_phy { enum typec_orientation orientation; bool usb3_up; /* tuned by the coprocessor this power cycle */ bool pipe_switched; /* the controller's PIPE runs on this PHY */ + + /* DisplayPort (PHY mode 3); dp_ok is false when the DT lacks a DP route */ + struct phy *dp; + struct typec_mux_dev *mux; + bool dp_ok; + bool dp_up; /* DP mode is programmed on the PHY */ + bool dp_alt; /* the Type-C port is in DP alt mode, pin assignment C/E */ + bool dp_flip; /* plug orientation reversed for the DP lanes */ + int dp_rate; /* link rate index of the running link sequence, -1 = off */ }; static inline void t6040_usb2_mask32(void __iomem *reg, u32 mask, u32 set) @@ -906,16 +929,13 @@ static int t6040_usb3_power_on(struct phy *phy) return 0; } -static int t6040_usb3_power_off(struct phy *phy) +/* aciophy_phy_shutdown with the lock held: lanes, coprocessor, then the CFG block. */ +static void t6040_usb3_down_locked(struct t6040_usb2_phy *tphy) { - struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); - - guard(mutex)(&tphy->lock); if (tphy->pipe_switched) { t6040_usb3_pipe_park(tphy); tphy->pipe_switched = false; } - /* aciophy_phy_shutdown: lanes, coprocessor, then the CFG block */ if (tphy->usb3_up) { u32 uc = readl(t6040_core(tphy) + T6040_CORE_UC_CTRL); @@ -928,6 +948,14 @@ static int t6040_usb3_power_off(struct phy *phy) readl(t6040_cfg(tphy) + T6040_CFG_STATUS)); tphy->usb3_up = false; } +} + +static int t6040_usb3_power_off(struct phy *phy) +{ + struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); + + guard(mutex)(&tphy->lock); + t6040_usb3_down_locked(tphy); return 0; } @@ -974,6 +1002,592 @@ static void t6040_usb3_switch_unregister(void *sw) typec_switch_unregister(sw); } +/* ---- DisplayPort (PHY mode 3: DP on all four lanes) ---- + * + * The fixed DP PHY of an HDMI converter (no USB controller, no pipehandler) + * and a Type-C PHY whose port the Type-C controller has put into DP alt mode + * with pin assignment C or E are brought up the same way, as the paired + * AppleT6040TypeCPhy driver does for its mode 3: wake the CFG block, apply the + * generic ADT tunables and the DPTX tunables, set the lane crossbar to DP on + * all lanes, hold the common block and both TX lanes on (the PHY coprocessor + * does not run in this mode) and release the PHY reset. The DCP then sets the + * link rate through phy_configure(): the per-rate link sequence is the T6050 + * fixed-DP table (atc-t6050-dp-data.h; the T6050 ADT names its PHY + * "atc-phy,t6050", "atc-phy,t6040"), followed by the T6040 DP clock switch in + * the "cfg" window. Pin assignment D (two DP lanes next to USB3) is not + * supported. + */ + +/* The fixed DPTX tunable writes (apply_dptx_tunables, shared with USB3). */ +static void t6040_dp_fixed_tunables(void __iomem *core) +{ + static const struct { u32 off, clear, set; } dptx[] = { + { 0x00800, 0x000003f0, 0x000001c0 }, /* AUS40CMN_DIG RCAL0 */ + { 0x00818, 0x0000f000, 0x00003000 }, /* AUS40CMN_DIG RCAL5 */ + { 0x03888, 0x000000ff, 0x00000027 }, /* CIO4PLL FREQDESCRIPTOR_0_C */ + { 0x28004, 0x00000038, 0x00000008 }, /* AUX_LDO_CTRL */ + { 0x03adc, 0x00003ff0, 0x00001760 }, /* CIO4PLL CFG_FRACN_FUNC */ + { 0x0e0cc, 0x01f80000, 0x00380000 }, /* LANE0 TX_EQ_CTRL_REG11 */ + { 0x1e0cc, 0x01f80000, 0x00380000 }, /* LANE1 TX_EQ_CTRL_REG11 */ + { 0x0d090, 0x01f80000, 0x00380000 }, /* LANE0 RX_CFG TX_EQ_CTRL_REG11 */ + { 0x1d090, 0x01f80000, 0x00380000 }, /* LANE1 RX_CFG TX_EQ_CTRL_REG11 */ + }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(dptx); i++) + t6040_usb2_mask32(core + dptx[i].off, dptx[i].clear, dptx[i].set); +} + +/* aciophy_xbar_config for PHY mode 3 (DP on all four lanes). */ +static void t6040_dp_xbar_config(struct t6040_usb2_phy *tphy, bool flip) +{ + void __iomem *core = t6040_core(tphy); + + /* core+0x6c, orientation-independent for DP */ + t6040_usb2_mask32(core + T6040_CORE_XBAR, ~0xfc3fff03u, 0x48); + /* core+0x78: lanes[4:0] = 0x14, then [17:5] by orientation */ + t6040_usb2_mask32(core + T6040_CORE_XBAR_LANES, 0x1f, 0x14); + t6040_usb2_mask32(core + T6040_CORE_XBAR_LANES, 0x3ffe0, flip ? 0x100 : 0x22000); + /* core+0x6c[1:0] = 2 (common to every mode) */ + t6040_usb2_mask32(core + T6040_CORE_XBAR, 0x3, 0x2); +} + +/* + * aciophy_dptx_adjust_lane_mode: the core+0x6c lane fields [4:2] and [7:5] + * are 7 for every DP 1.x link rate (UHBR rates would use 2). + */ +static void t6040_dp_adjust_lane_mode(struct t6040_usb2_phy *tphy) +{ + t6040_usb2_set32(t6040_core(tphy) + T6040_CORE_XBAR, 0x1c | 0xe0); +} + +/* aciophy_aux_init: the DP AUX block. */ +static void t6040_dp_aux_init(struct t6040_usb2_phy *tphy) +{ + void __iomem *core = t6040_core(tphy); + + t6040_usb2_set32(core + T6040_CORE_AUX_PWR, 0x3); + t6040_usb2_set32(core + T6040_CORE_AUX_PWR, 0xc); + t6040_usb2_mask32(core + T6040_CORE_AUX_PWR, 0x300, 0x100); + t6040_usb2_clear32(core + T6040_CORE_AUX_CTRL, 0x1); +} + +/* + * aciophy_configure_dp_clks: the two DP clock switches of the "cfg" window + * (control at 0x14, status at 0x18). Each switch enables its pixel clock, + * requests the two PMGR DP clocks and releases the requests one by one once + * they are acknowledged. On the J616s the pixel-clock enable is acknowledged + * with the enable bit alone and the PMGR acknowledgements arrive with the + * 0x400 (SWITCH1) / 0x800 (SWITCH2) bit set next to the requests; the + * requests are retried without that bit should the acknowledgment not come. + */ +static int t6040_dp_configure_clks(struct t6040_usb2_phy *tphy) +{ + void __iomem *cfg = t6040_cfg(tphy); + u32 s; + int ret; + +#define DP_CLK_POLL(bit, set) \ + readl_poll_timeout(cfg + T6040_CFG_DP_CLKSWITCH_STAT, s, \ + !!(s & BIT(bit)) == (set), 10, 200000) + + /* SWITCH1: pixel clock, then the PMGR DP0/DP1 requests */ + t6040_usb2_mask32(cfg + T6040_CFG_DP_CLKSWITCH_CTRL, 0x473, 0x10); + ret = DP_CLK_POLL(0, true); + if (ret) + goto timeout; + t6040_usb2_set32(cfg + T6040_CFG_DP_CLKSWITCH_CTRL, 0x60 | 0x400); + ret = DP_CLK_POLL(2, true) ?: DP_CLK_POLL(1, true); + if (ret) { + dev_dbg(tphy->dev, "DP clock switch 1: no PMGR ack with 0x400 (status %#x)\n", s); + t6040_usb2_clear32(cfg + T6040_CFG_DP_CLKSWITCH_CTRL, 0x400); + ret = DP_CLK_POLL(2, true) ?: DP_CLK_POLL(1, true); + if (ret) + goto timeout; + } + t6040_usb2_clear32(cfg + T6040_CFG_DP_CLKSWITCH_CTRL, 0x20); + ret = DP_CLK_POLL(1, false); + if (ret) + goto timeout; + t6040_usb2_clear32(cfg + T6040_CFG_DP_CLKSWITCH_CTRL, 0x40); + ret = DP_CLK_POLL(2, false); + if (ret) + goto timeout; + + /* SWITCH2, the same way */ + t6040_usb2_clear32(cfg + T6040_CFG_DP_CLKSWITCH_CTRL, 0x403); + t6040_usb2_mask32(cfg + T6040_CFG_DP_CLKSWITCH_CTRL, 0xb8c, 0x80); + ret = DP_CLK_POLL(3, true); + if (ret) + goto timeout; + t6040_usb2_set32(cfg + T6040_CFG_DP_CLKSWITCH_CTRL, 0x300 | 0x800); + ret = DP_CLK_POLL(5, true) ?: DP_CLK_POLL(4, true); + if (ret) { + dev_dbg(tphy->dev, "DP clock switch 2: no PMGR ack with 0x800 (status %#x)\n", s); + t6040_usb2_clear32(cfg + T6040_CFG_DP_CLKSWITCH_CTRL, 0x800); + ret = DP_CLK_POLL(5, true) ?: DP_CLK_POLL(4, true); + if (ret) + goto timeout; + } + t6040_usb2_clear32(cfg + T6040_CFG_DP_CLKSWITCH_CTRL, 0x100); + ret = DP_CLK_POLL(4, false); + if (ret) + goto timeout; + t6040_usb2_clear32(cfg + T6040_CFG_DP_CLKSWITCH_CTRL, 0x200); + ret = DP_CLK_POLL(5, false); + if (ret) + goto timeout; + t6040_usb2_clear32(cfg + T6040_CFG_DP_CLKSWITCH_CTRL, 0x80c); + dev_dbg(tphy->dev, "DP clock switches on (ctrl %#x status %#x)\n", + readl(cfg + T6040_CFG_DP_CLKSWITCH_CTRL), + readl(cfg + T6040_CFG_DP_CLKSWITCH_STAT)); + return 0; + +timeout: + dev_err(tphy->dev, "DP clock switch timed out (ctrl %#x status %#x)\n", + readl(cfg + T6040_CFG_DP_CLKSWITCH_CTRL), + readl(cfg + T6040_CFG_DP_CLKSWITCH_STAT)); + return ret; +#undef DP_CLK_POLL +} + +/* A PHY without the USB3 pieces (the HDMI converter's) only ever does DP. */ +static bool t6040_dp_only(struct t6040_usb2_phy *tphy) +{ + return !tphy->usb3_ok; +} + +/* apply_tunables' generic tables: the first six of t6040_usb3_tunable_list. */ +#define T6040_DP_NR_GENERIC_TUNABLES 6 + +/* + * slpctrl_init for DP on both lanes: the cmn_rm_ovrd/txrx_rm_ovrd releases of + * t6040_usb3_lanes for lane 0 and lane 1 (USB3 releases only the plug's lane). + * Without the PHY coprocessor nothing powers the common block and the TX lanes + * once their overrides are released, so they are held on by override: the + * reverse of aciophy_cmn_shutdown / aciophy_tx_powerdown, with the override + * enables (0x2a; 0x4/0x40/0x400 per lane) set. + */ +static void t6040_dp_slpctrl(struct t6040_usb2_phy *tphy) +{ + void __iomem *core = t6040_core(tphy); + unsigned int lane; + + t6040_usb2_set32(core + T6040_CORE_CMN_BIAS, T6040_CORE_CMN_BIAS_BGBIAS_EN_OV); + udelay(1); + t6040_usb2_set32(core + T6040_CORE_CMN_OVRD, T6040_CORE_CMN_OVRD_SLEEP_B_SML | 0x2a); + udelay(1); + t6040_usb2_set32(core + T6040_CORE_CMN_OVRD, T6040_CORE_CMN_OVRD_SLEEP_B_BIG); + udelay(1); + t6040_usb2_clear32(core + T6040_CORE_CMN_OVRD, T6040_CORE_CMN_OVRD_CLAMP); + udelay(1); + for (lane = 0; lane < 2; lane++) { + t6040_usb2_set32(core + T6040_CORE_SLP_CTRL, (0x4 | 0x40 | 0x400) << lane); + t6040_usb2_set32(core + T6040_CORE_SLP_CTRL, 0x10 << lane); + udelay(1); + t6040_usb2_set32(core + T6040_CORE_SLP_CTRL, 0x1 << lane); + udelay(1); + t6040_usb2_clear32(core + T6040_CORE_SLP_CTRL, 0x100 << lane); + udelay(1); + } + dev_dbg(tphy->dev, "DP lanes held on: cmn_ovrd %#x slp_ctrl %#x bias %#x\n", + readl(core + T6040_CORE_CMN_OVRD), readl(core + T6040_CORE_SLP_CTRL), + readl(core + T6040_CORE_CMN_BIAS)); +} + +/* + * aciophy_phy_init for mode 3: wake the CFG block (shared with USB3), apply + * the generic ADT tables and the DPTX tunables, xbar_config, slpctrl_init and + * the reset deassert of partC (no coprocessor firmware in this mode). + */ +static int t6040_dp_phy_init(struct t6040_usb2_phy *tphy, bool flip) +{ + void __iomem *cfg = t6040_cfg(tphy); + size_t i; + int ret; + + ret = t6040_usb3_wake(tphy); + if (ret) + return ret; + for (i = 0; i < T6040_DP_NR_GENERIC_TUNABLES; i++) + apple_tunable_apply(tphy->regs[t6040_usb3_tunable_list[i].region], + tphy->tunables[i]); + t6040_dp_fixed_tunables(t6040_core(tphy)); + t6040_dp_xbar_config(tphy, flip); + t6040_dp_slpctrl(tphy); + t6040_usb2_set32(cfg + T6040_CFG_CTRL, T6040_CFG_CTRL_PHY_RESET_N); + fsleep(100); + dev_dbg(tphy->dev, "DP PHY init (mode 3): cfg %#x/%#x\n", + readl(cfg + T6040_CFG_CTRL), readl(cfg + T6040_CFG_STATUS)); + return 0; +} + +/* aciophy_dptx_main_init up to the link rate, which .configure sets. */ +static void t6040_dp_mode_setup(struct t6040_usb2_phy *tphy, bool flip) +{ + t6040_dp_fixed_tunables(t6040_core(tphy)); + t6040_dp_xbar_config(tphy, flip); + t6040_dp_adjust_lane_mode(tphy); + t6040_dp_aux_init(tphy); +} + +/* + * The per-rate link sequences of the T6050 fixed DP PHY, which shares the + * ATC PHY core with the T6040. Bank numbers are the atc-phy "reg" indices of + * the T6050/T6040 ADT; banks 2..34 are offsets into "core". Bank 39 is the + * T6050 clock shim; on the T6040 the DP clock switch of the "cfg" window + * stands in for it and runs after the lane program, as AppleT6040TypeCPhy + * does. The sequences hold no power-domain or tunable steps that apply here. + */ +enum atc_operation { ATC_MASK, ATC_TUNABLE, ATC_DELAY, ATC_POLL, ATC_POWER }; +struct atc_op { + u8 op, bank; + u16 offset; + u32 mask, value, timeout; +}; + +struct atc_rate_op { + struct atc_op op; + u8 rates; + u32 values[4]; +}; + +#include "atc-t6050-dp-data.h" + +static const struct { u8 bank; u32 offset; } t6040_dp_core_banks[] = { + {2, 0}, {3, 0x400}, {4, 0x800}, {5, 0xa00}, {6, 0x1000}, + {7, 0x3000}, {8, 0x3200}, {9, 0x3800}, {10, 0x3a00}, + {14, 0x7000}, {16, 0x9000}, {18, 0xb000}, {19, 0xc000}, + {20, 0xd000}, {21, 0xe000}, {22, 0xf000}, {25, 0x19000}, + {27, 0x1b000}, {28, 0x1c000}, {29, 0x1d000}, {30, 0x1e000}, + {31, 0x1f000}, {33, 0x28000}, {34, 0x28400}, +}; + +static void __iomem *t6040_dp_bank(struct t6040_usb2_phy *tphy, u8 bank) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(t6040_dp_core_banks); i++) + if (t6040_dp_core_banks[i].bank == bank) + return t6040_core(tphy) + t6040_dp_core_banks[i].offset; + return NULL; +} + +static int t6040_dp_seq_step(struct t6040_usb2_phy *tphy, const struct atc_op *s) +{ + void __iomem *reg; + u32 v; + int ret; + + if (s->op == ATC_DELAY) { + udelay(s->value); + return 0; + } + if (s->op == ATC_POWER || s->op == ATC_TUNABLE || s->bank == 39) + return 0; + reg = t6040_dp_bank(tphy, s->bank); + if (!reg) { + dev_err(tphy->dev, "DP link sequence: no bank %u\n", s->bank); + return -EINVAL; + } + reg += s->offset; + if (s->op == ATC_MASK) { + writel((readl(reg) & ~s->mask) | s->value, reg); + return 0; + } + ret = readl_poll_timeout(reg, v, (v & s->mask) == s->value, + s->timeout > 1000 ? 1000 : 1, s->timeout); + if (ret) + dev_err(tphy->dev, "DP link sequence: bank %u+%#x timed out: %#x & %#x != %#x\n", + s->bank, s->offset, v, s->mask, s->value); + return ret; +} + +/* Take the link down if it is up, then bring it up at rate index @idx (-1: off). */ +static int t6040_dp_set_rate(struct t6040_usb2_phy *tphy, int idx) +{ + size_t i; + int ret; + + if (tphy->dp_rate >= 0) { + for (i = 0; i < ARRAY_SIZE(dp_link_off); i++) { + ret = t6040_dp_seq_step(tphy, &dp_link_off[i]); + if (ret) + return ret; + } + tphy->dp_rate = -1; + } + if (idx < 0) + return 0; + for (i = 0; i < ARRAY_SIZE(dp_link_on); i++) { + struct atc_op op = dp_link_on[i].op; + + if (!(dp_link_on[i].rates & BIT(idx))) + continue; + op.value = dp_link_on[i].values[idx]; + ret = t6040_dp_seq_step(tphy, &op); + if (ret) + return ret; + } + ret = t6040_dp_configure_clks(tphy); + if (ret) + return ret; + tphy->dp_rate = idx; + dev_dbg(tphy->dev, "DP link on, rate index %d\n", idx); + return 0; +} + +/* Drive settings of the T6050 sequence: lanes 0/1 on PMA group 1, 2/3 on group 0. */ +static void t6040_dp_drive(struct t6040_usb2_phy *tphy, unsigned int lane, u32 preset) +{ + unsigned int group = lane < 2 ? 1 : 0; + bool tx = lane & 1; + void __iomem *base = t6040_dp_bank(tphy, (tx ? 21 : 20) + 9 * group); + u32 ctrl = ((preset >> 2) & 0xf) | ((preset & 3) << 14); + u32 eq = ((preset >> 5) & 0x7fffe) | 1; + + if (tx) + t6040_usb2_mask32(base + 0x100, BIT(20), BIT(20)); + t6040_usb2_mask32(base + (tx ? 0x58 : 0x68), 0xc00f, ctrl); + t6040_usb2_mask32(base + (tx ? 0x104 : 0xac), BIT(29), BIT(29)); + t6040_usb2_mask32(base + (tx ? 0x100 : 0xa8), 0x7ffff, eq); +} + +/* ---- DP phy_ops ---- */ + +static int t6040_dp_power_on(struct phy *phy) +{ + struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); + int ret; + + guard(mutex)(&tphy->lock); + if (!tphy->dp_ok || tphy->dp_up) + return 0; + /* + * A Type-C PHY in DP alt mode (pin assignment C/E) hands all four + * lanes to DP: aciophy_phy_shutdown of the USB3 side first, then the + * same mode-3 init as the fixed DP PHY. + */ + if (!t6040_dp_only(tphy)) + t6040_usb3_down_locked(tphy); + ret = t6040_dp_phy_init(tphy, tphy->dp_flip); + if (ret) { + dev_err(tphy->dev, "DP PHY init failed: %d\n", ret); + return ret; + } + t6040_dp_mode_setup(tphy, tphy->dp_flip); + tphy->dp_rate = -1; + tphy->dp_up = true; + dev_dbg(tphy->dev, "DP PHY mode set, lane map %s\n", + tphy->dp_flip ? "reversed" : "normal"); + return 0; +} + +static int t6040_dp_power_off(struct phy *phy) +{ + struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); + + guard(mutex)(&tphy->lock); + tphy->dp_up = false; + return 0; +} + +/* GET_MAX_LANE_COUNT: DP on all four lanes in mode 3. */ +static int t6040_dp_validate(struct phy *phy, enum phy_mode mode, int submode, + union phy_configure_opts *opts_) +{ + struct phy_configure_opts_dp *opts = &opts_->dp; + + if (mode != PHY_MODE_DP) + return -EINVAL; + opts->lanes = 4; + return 0; +} + +static int t6040_dp_configure(struct phy *phy, union phy_configure_opts *opts_) +{ + struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); + struct phy_configure_opts_dp *opts = &opts_->dp; + int idx = -1; + int ret; + + guard(mutex)(&tphy->lock); + if (!tphy->dp_up) + return -EINVAL; + + if (opts->set_rate) { + switch (opts->link_rate) { + case 1620: + idx = 0; + break; + case 2700: + idx = 1; + break; + case 5400: + idx = 2; + break; + case 8100: + idx = 3; + break; + case 0: + idx = -1; + break; + default: + return -EINVAL; + } + if (idx != tphy->dp_rate) { + ret = t6040_dp_set_rate(tphy, idx); + if (ret) + return ret; + } + } + if (opts->set_voltages && tphy->dp_rate >= 0) { + unsigned int i; + + for (i = 0; i < opts->lanes && i < 4; i++) { + unsigned int v = min_t(unsigned int, opts->voltage[i], 3); + unsigned int pe = min_t(unsigned int, opts->pre[i], 3); + + t6040_dp_drive(tphy, i, dp_training_default[4 * v + pe]); + } + } + return 0; +} + +/* ACTIVATE/DEACTIVATE: PHY_MODE_DP, submode = the DCP's DPTX port index. */ +static int t6040_dp_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + if (mode == PHY_MODE_DP || mode == PHY_MODE_INVALID) + return 0; + return -EOPNOTSUPP; +} + +static const struct phy_ops t6040_dp_phy_ops = { + .owner = THIS_MODULE, + .power_on = t6040_dp_power_on, + .power_off = t6040_dp_power_off, + .configure = t6040_dp_configure, + .validate = t6040_dp_validate, + .set_mode = t6040_dp_set_mode, +}; + +/* ---- Type-C mode switch: the Type-C controller asks for DP on a USB-C port ---- */ + +static int t6040_dp_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) +{ + struct t6040_usb2_phy *tphy = typec_mux_get_drvdata(mux); + bool dp = state->alt && state->alt->svid == USB_TYPEC_DP_SID; + bool alt = false; + + guard(mutex)(&tphy->lock); + tphy->dp_flip = (tphy->orientation == TYPEC_ORIENTATION_REVERSE); + /* + * Latch the DP alt mode state for the DCP's Type-C route. Pin + * assignments C and E put DP on all four lanes (PHY mode 3, as the + * fixed DP route); D (two DP lanes next to USB3, PHY mode 4 with the + * coprocessor) is not supported. The DP lanes themselves are brought + * up by the DCP through phy_power_on()/phy_configure() on the DP phy. + */ + if (dp) { + switch (state->mode) { + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + alt = true; + break; + case TYPEC_DP_STATE_D: + dev_warn_once(tphy->dev, "Type-C mux: DP pin assignment D (two lanes next to USB3) not supported\n"); + break; + default: + break; + } + } + tphy->dp_alt = alt; + dev_dbg(tphy->dev, "Type-C mux: %s state %lu -> DP alt mode %d, lane map %s\n", + dp ? "DP" : "non-DP", state->mode, alt, tphy->dp_flip ? "reversed" : "normal"); + if (alt && tphy->dp_up) + t6040_dp_xbar_config(tphy, tphy->dp_flip); + return 0; +} + +/* For the DCP's Type-C route: is this PHY's port in DP alt mode (C/E)? */ +bool apple_t6040_phy_dp_alt(struct phy *phy) +{ + struct t6040_usb2_phy *tphy = phy_get_drvdata(phy); + + return tphy && tphy->dp_alt; +} +EXPORT_SYMBOL_GPL(apple_t6040_phy_dp_alt); + +static void t6040_dp_mux_unregister(void *mux) +{ + typec_mux_unregister(mux); +} + +/* + * Everything the DP route needs from the device tree; a node without it gets + * no DP phy. A USB-C PHY offers DP through a Type-C mode switch; a fixed DP + * route (an HDMI converter's PHY) is driven straight by the DCP with no mux + * and uses the normal lane map. + */ +static int t6040_dp_probe(struct t6040_usb2_phy *tphy) +{ + struct device *dev = tphy->dev; + unsigned int i; + + tphy->dp_rate = -1; + + /* + * DP mode needs the "core" and "cfg" windows. A USB-C PHY has them + * alongside its USB regions; a fixed DP PHY has them without any + * pipehandler or USB3 coprocessor. A node with neither is USB2 only. + */ + if (!tphy->regs[T6040_USB3_CORE] || !tphy->regs[T6040_USB3_CFG]) + return 0; + + tphy->dp = devm_phy_create(dev, NULL, &t6040_dp_phy_ops); + if (IS_ERR(tphy->dp)) + return PTR_ERR(tphy->dp); + phy_set_drvdata(tphy->dp, tphy); + + /* DP-only node: the generic apply_tunables tables (absent = not applied). */ + if (t6040_dp_only(tphy)) { + for (i = 0; i < T6040_DP_NR_GENERIC_TUNABLES; i++) { + const char *prop = t6040_usb3_tunable_list[i].prop; + + if (!of_property_present(dev->of_node, prop) || + !tphy->res[t6040_usb3_tunable_list[i].region]) { + dev_warn(dev, "DP: no %s\n", prop); + tphy->tunables[i] = NULL; + continue; + } + struct resource *res = tphy->res[t6040_usb3_tunable_list[i].region]; + + tphy->tunables[i] = devm_apple_tunable_parse(dev, dev->of_node, prop, res); + if (IS_ERR(tphy->tunables[i])) { + dev_warn(dev, "DP: %s: %pe\n", prop, tphy->tunables[i]); + tphy->tunables[i] = NULL; + } + } + } + + if (of_property_present(dev->of_node, "mode-switch")) { + struct typec_mux_desc mux_desc = { + .drvdata = tphy, + .fwnode = dev_fwnode(dev), + .set = t6040_dp_mux_set, + }; + + tphy->mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(tphy->mux)) + return PTR_ERR(tphy->mux); + if (devm_add_action_or_reset(dev, t6040_dp_mux_unregister, tphy->mux)) + return -ENOMEM; + } + + tphy->dp_ok = true; + return 0; +} + /* ---- the coprocessor's RTKit ---- */ /* The coprocessor places its buffers in its own __DATA SRAM. */ @@ -1027,6 +1641,33 @@ static const struct apple_rtkit_ops t6040_cpu_rtkit_ops = { .shmem_destroy = t6040_cpu_shmem_destroy, }; +/* + * Map the PHY register windows shared by the USB3 and DisplayPort paths. Each + * is optional: a USB-C PHY has the full set, the fixed HDMI DP PHY (atcphy3) + * lacks none of core/cfg but has no pipehandler. Returns true once at least + * "core" and "cfg" are mapped, which is all DP mode needs. + */ +static bool t6040_map_regions(struct t6040_usb2_phy *tphy, struct platform_device *pdev) +{ + struct device *dev = tphy->dev; + size_t i; + + for (i = 0; i < T6040_USB3_NR_REGIONS; i++) { + tphy->res[i] = platform_get_resource_byname(pdev, IORESOURCE_MEM, + t6040_usb3_region_names[i]); + if (!tphy->res[i]) + continue; + tphy->regs[i] = devm_ioremap_resource(dev, tphy->res[i]); + if (IS_ERR(tphy->regs[i])) { + dev_warn(dev, "\"%s\" region: %pe\n", + t6040_usb3_region_names[i], tphy->regs[i]); + tphy->regs[i] = NULL; + tphy->res[i] = NULL; + } + } + return tphy->regs[T6040_USB3_CORE] && tphy->regs[T6040_USB3_CFG]; +} + /* * Everything USB3 needs from the device tree. A missing piece leaves the * port at USB2; only a mailbox that has not probed yet defers. @@ -1040,7 +1681,6 @@ static int t6040_usb3_probe(struct t6040_usb2_phy *tphy, struct platform_device .set = t6040_usb3_sw_set, }; struct typec_switch_dev *sw; - struct resource *res[T6040_USB3_NR_REGIONS]; struct resource *ph; size_t i; int ret; @@ -1066,19 +1706,11 @@ static int t6040_usb3_probe(struct t6040_usb2_phy *tphy, struct platform_device return ret; for (i = 0; i < T6040_USB3_NR_REGIONS; i++) { - res[i] = platform_get_resource_byname(pdev, IORESOURCE_MEM, - t6040_usb3_region_names[i]); - if (!res[i]) { + if (!tphy->regs[i]) { dev_warn(dev, "USB3: no \"%s\" region, USB2 only\n", t6040_usb3_region_names[i]); return 0; } - tphy->regs[i] = devm_ioremap_resource(dev, res[i]); - if (IS_ERR(tphy->regs[i])) { - dev_warn(dev, "USB3: \"%s\" region: %pe, USB2 only\n", - t6040_usb3_region_names[i], tphy->regs[i]); - return 0; - } } tphy->cpu_data = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cpu-data"); ph = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pipehandler"); @@ -1090,10 +1722,11 @@ static int t6040_usb3_probe(struct t6040_usb2_phy *tphy, struct platform_device for (i = 0; i < T6040_USB3_NR_TUNABLES; i++) { tphy->tunables[i] = devm_apple_tunable_parse(dev, dev->of_node, t6040_usb3_tunable_list[i].prop, - res[t6040_usb3_tunable_list[i].region]); + tphy->res[t6040_usb3_tunable_list[i].region]); if (IS_ERR(tphy->tunables[i])) { dev_warn(dev, "USB3: %s: %pe, USB2 only\n", t6040_usb3_tunable_list[i].prop, tphy->tunables[i]); + tphy->tunables[i] = NULL; return 0; } } @@ -1206,6 +1839,8 @@ static struct phy *t6040_usb2_xlate(struct device *dev, return tphy->phy; case PHY_TYPE_USB3: return tphy->usb3; + case PHY_TYPE_DP: + return tphy->dp; default: return ERR_PTR(-ENODEV); } @@ -1267,22 +1902,37 @@ static int t6040_usb2_probe(struct platform_device *pdev) return PTR_ERR(tphy->phy); phy_set_drvdata(tphy->phy, tphy); - tphy->pipehandler = devm_platform_ioremap_resource_byname(pdev, "pipehandler"); - if (IS_ERR(tphy->pipehandler)) - return dev_err_probe(dev, PTR_ERR(tphy->pipehandler), - "failed to map pipehandler bank\n"); - - tphy->rcdev.owner = THIS_MODULE; - tphy->rcdev.nr_resets = 1; - tphy->rcdev.ops = &t6040_usb2_reset_ops; - tphy->rcdev.of_node = dev->of_node; - tphy->rcdev.of_reset_n_cells = 0; - tphy->rcdev.of_xlate = t6040_usb2_reset_xlate; - ret = devm_reset_controller_register(dev, &tphy->rcdev); + /* + * The USB-C PHYs have a pipehandler and drive a dwc3 (reset provider); + * the fixed HDMI DP PHY (atcphy3) has neither. Map it and register the + * reset controller only when present, so a DP-only node still probes. + */ + if (platform_get_resource_byname(pdev, IORESOURCE_MEM, "pipehandler")) { + tphy->pipehandler = devm_platform_ioremap_resource_byname(pdev, + "pipehandler"); + if (IS_ERR(tphy->pipehandler)) + return dev_err_probe(dev, PTR_ERR(tphy->pipehandler), + "failed to map pipehandler bank\n"); + + tphy->rcdev.owner = THIS_MODULE; + tphy->rcdev.nr_resets = 1; + tphy->rcdev.ops = &t6040_usb2_reset_ops; + tphy->rcdev.of_node = dev->of_node; + tphy->rcdev.of_reset_n_cells = 0; + tphy->rcdev.of_xlate = t6040_usb2_reset_xlate; + ret = devm_reset_controller_register(dev, &tphy->rcdev); + if (ret) + return ret; + } + + /* Map the core/cfg (and USB3) windows once; both sub-probes share them. */ + t6040_map_regions(tphy, pdev); + + ret = t6040_usb3_probe(tphy, pdev); if (ret) return ret; - ret = t6040_usb3_probe(tphy, pdev); + ret = t6040_dp_probe(tphy); if (ret) return ret; @@ -1306,5 +1956,5 @@ static struct platform_driver t6040_usb2_driver = { module_platform_driver(t6040_usb2_driver); MODULE_AUTHOR("The Asahi Linux Contributors"); -MODULE_DESCRIPTION("Apple T6040 Type-C PHY, USB2 and USB3 host"); +MODULE_DESCRIPTION("Apple T6040 Type-C PHY, USB2/USB3 host and DisplayPort"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/include/linux/soc/apple/dp-tunnel.h b/include/linux/soc/apple/dp-tunnel.h index 540d48fe8e0dc..868de62db1156 100644 --- a/include/linux/soc/apple/dp-tunnel.h +++ b/include/linux/soc/apple/dp-tunnel.h @@ -61,6 +61,21 @@ int apple_atc_dp_tunnel_rate_source(struct phy *phy, unsigned int source, */ int apple_atc_dp_tunnel_open(struct phy *phy); +/* + * T6040 Type-C PHY: true while the Type-C controller holds the PHY's port in + * DisplayPort alt mode with pin assignment C or E (DP on all four lanes), so + * appledrm can route a DCP that lists several Type-C DP PHYs to the one that + * has a DisplayPort sink plugged. + */ +#if IS_ENABLED(CONFIG_PHY_APPLE_T6040_USB2) +bool apple_t6040_phy_dp_alt(struct phy *phy); +#else +static inline bool apple_t6040_phy_dp_alt(struct phy *phy) +{ + return false; +} +#endif + /* * Display crossbar: take the connection of an already selected output down or * bring it back up (clock gates and enables) around a link reconfiguration. diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/common.py b/tools/testing/selftests/drivers/gpu/apple-dcp/common.py index 4097d7db7d86a..cdf20256dfa4f 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/common.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/common.py @@ -200,6 +200,18 @@ def apple_function(root, name): return '' +def firmware_lineage(root): + """The 26.x lineage tests of version_utils.h (DCP_FW_H17() and friends). + + Units define their own DCP_FW_VERSION(); a plain 26 is the H17 lineage, + since only a desktop template instance carries DCP_FW_DESKTOP. + """ + text = (root / 'drivers/gpu/drm/apple/version_utils.h').read_text() + names = ('DCP_FW_DESKTOP', 'DCP_FW_VERSION_DESKTOP', 'DCP_FW_H17', 'DCP_FW_DESKTOP_26') + return ''.join(d + '\n' for d in source_defines(text) + if re.match(r'#define (\w+)', d).group(1) in names) + + def internal_inline(root, name): """A static inline helper from dcp-internal.h, marked unused; '' if absent. diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/edid.py b/tools/testing/selftests/drivers/gpu/apple-dcp/edid.py index 1cee66408389d..60ed9b365d099 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/edid.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/edid.py @@ -2,7 +2,7 @@ # Copyright 2026 Ryan Murray """Actual native EDID admission, service epochs, stale replies and RX progress.""" from pathlib import Path -from common import block, source_defines, apple_function, internal_inline +from common import block, source_defines, apple_function, internal_inline, firmware_lineage def rebind(root): base = root / 'drivers/gpu/drm/apple' @@ -600,6 +600,8 @@ def function(file, signature): #define DCP_FIRMWARE_H17P 2 /* The 14.7 open-before-EDID handshake is for T6030 external processors only. */ #define DCP_FIRMWARE_V_14_7 (DCP_FIRMWARE_H17P + 1) +/* Nor is any processor here a desktop 26.6 one: it has no auxiliary graph. */ +#define DCP_FIRMWARE_V_26_6_DESKTOP (DCP_FIRMWARE_H17P + 3) #define AV_ENDPOINT 0x29 #define SYSTEM_ENDPOINT 0x20 #define DISP0_ENDPOINT 0x23 @@ -623,6 +625,7 @@ def function(file, signature): u64 typec_generation; int fw_compat; void *dev; + struct dcp_desktop_state { struct apple_dcp_afkep *graph[0x20]; } *desktop; }; struct drm_edid { int identity; }; '''+internal_inline(root,'dcp_is_v14_7_external')+r'''static unsigned int reads, allocations, legacy_reads, receives; @@ -803,6 +806,8 @@ def function(text, signature): #define dev_dbg_ratelimited(...) ((void)0) #define trace_dcp_recv_msg(...) ((void)0) #define DCP_FIRMWARE_H17P 2 +/* The H17P external processor is not a desktop 26.6 one. */ +#define DCP_FIRMWARE_V_26_6_DESKTOP (DCP_FIRMWARE_H17P + 3) #define IOMFB_ENDPOINT 0x37 #define AV_ENDPOINT 0x29 #define SYSTEM_ENDPOINT 0x20 @@ -846,6 +851,7 @@ def function(text, signature): struct apple_connector *connector; struct apple_dcp_afkep *dcpavservep,*dptxep,*avep,*systemep,*ibootep,*dptx_controller_ep; struct { struct mutex lock; bool stopped; void *owner; } iomfb; + struct dcp_desktop_state { struct apple_dcp_afkep *graph[0x20]; } *desktop; }; struct afk_receive_message_work { struct apple_dcp_afkep *ep; u64 message; struct work_struct work; }; struct apple_rtkit_ops { void (*recv_message)(void *,u8,u64); }; @@ -1004,7 +1010,7 @@ def function(text, signature): old_join=''' assert(!pthread_join(rx,NULL) && !pthread_join(afk,NULL) && !pthread_join(dimensions,NULL));''' assert old_join in suffix suffix=suffix.replace(old_join,old_join+'\n assert(!pthread_join(hotplug,NULL));',1) - return prefix + '\n'.join(parts) + suffix + return prefix + firmware_lineage(root) + '\n'.join(parts) + suffix def startup(root): diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index 497d3f3cd2437..d7485a6681747 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -3,7 +3,7 @@ """Actual disconnect authority, DWC3 teardown and firmware resource admission.""" from pathlib import Path import re -from common import block, apple_function, internal_inline, atc_mux_without_pm, source_defines, ATC_POST_TUNABLE, tipd_pm_header +from common import block, apple_function, internal_inline, atc_mux_without_pm, source_defines, ATC_POST_TUNABLE, tipd_pm_header, firmware_lineage def disconnect(root): # Copyright 2026 Ryan Murray @@ -459,6 +459,15 @@ def function(file, name): static void __attribute__((unused)) dcp_relink_link_up(int *s, u64 generation, unsigned long now) { (void)s; (void)generation; (void)now; assert(!"legacy relink timestamp in an H17P scenario"); } """ + # The desktop 26.6 (T6041) connect has its own path; an H17P display is + # never on that firmware. + if 'dcp_desktop_dptx_connect(' in funcs: + old = ' struct dcp_announce announce;\n' + assert prefix.count(old) == 1 + prefix = prefix.replace(old, old + ' int fw_compat; /* zero: not the desktop 26.6 firmware */\n') + prefix += ('#define DCP_FIRMWARE_V_26_6_DESKTOP 100\n' + 'static int __attribute__((unused)) dcp_desktop_dptx_connect(struct apple_dcp *d, u32 port)\n' + '{ (void)d; (void)port; assert(!"desktop 26.6 connect in an H17P scenario"); return -EINVAL; }\n') # How long a held mux has been retried is measured on the driver's clock. if 'jiffies_to_msecs(jiffies)' in funcs: shim += ('\nstatic unsigned long jiffies;\n' @@ -492,7 +501,8 @@ def swap(old, new): ' void *phy, *dev, *active_typec_route;\n' ' int dptx_die, dptx_phy, dptx_dfp_port, connector_type;\n' ' bool external, external_link_ready, crashed;\n' - ' bool typec_work_enabled; int fw_compat; struct { bool fw_14_7_endpoints; } hw;\n' + ' bool typec_work_enabled;' + ('' if '\n int fw_compat;' in unit else ' int fw_compat;') + + ' struct { bool fw_14_7_endpoints; } hw;\n' ' struct { bool active; } split;') old_raw = unit[unit.index('static int dcp_dptx_connect_raw('):] old_raw = old_raw[:old_raw.index('return raw_error; }') + len('return raw_error; }')] @@ -1395,6 +1405,9 @@ def function(source, marker): { (void)n; assert(!"T6030 dcpext in a J700 scenario"); return false; } static int __attribute__((unused)) iomfb_v14_7_external_prepare(struct apple_dcp *d) { (void)d; assert(!"T6030 external processor in a J700 scenario"); return -ENODEV; } +/* Nor a T6041 dcpext. */ +static bool __attribute__((unused)) dcp_t6041_ext_enabled(void) +{ assert(!"T6041 dcpext in a J700 scenario"); return false; } static void __attribute__((unused)) iomfb_v14_7_external_refuse(struct apple_dcp *d, const char *why) { (void)d; (void)why; assert(!"T6030 external processor in a J700 scenario"); } struct drm_device { int unused; }; @@ -1668,7 +1681,7 @@ def vrr(root): struct platform_device { struct apple_dcp *data; }; struct drm_crtc { struct platform_device *dcp; void *primary; }; struct drm_display_mode { int unused; }; -struct drm_crtc_state { bool enable, vrr_enabled, modeset; struct drm_display_mode mode; void *ctm, *degamma_lut, *gamma_lut; unsigned plane_mask; }; +struct drm_crtc_state { bool enable, vrr_enabled, modeset, color_mgmt_changed; struct drm_display_mode mode; void *ctm, *degamma_lut, *gamma_lut; unsigned plane_mask; }; /* mainline 7.2 renamed struct drm_atomic_state to struct drm_atomic_commit */ struct drm_atomic_commit { struct drm_crtc_state crtc; }; #define to_apple_crtc(x) (x) @@ -1691,6 +1704,11 @@ def vrr(root): static unsigned drm_plane_mask(void *p) { (void)p; assert(false); return 0; } static bool drm_mode_equal(const struct drm_display_mode *a,const struct drm_display_mode *b) { (void)a; (void)b; assert(false); return false; } +/* These commits change no colour management, so no gamma table is sized. */ +static int __attribute__((unused)) drm_color_lut_size(const void *blob) +{ (void)blob; assert(!"gamma LUT without a colour-management change"); return 0; } +static unsigned int __attribute__((unused)) dcp_gamma_lut_size(struct platform_device *p) +{ (void)p; assert(!"gamma LUT without a colour-management change"); return 0; } /* Route-follow is for Type-C displays; this DCP has no Type-C routes. */ static bool __attribute__((unused)) typec_follow_crtc = true; static int __attribute__((unused)) dcp_typec_follow_check(struct apple_dcp *d, struct drm_crtc *c, diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index 3fff8378744bd..7bd087c364c52 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -3,7 +3,7 @@ """Actual parser, bounded rate admission, catalog replacement and HPD.""" from pathlib import Path import copy, re, struct -from common import block, source_defines, apple_function, internal_inline +from common import block, source_defines, apple_function, internal_inline, firmware_lineage def parser_fixture(root): @@ -677,7 +677,9 @@ def publication(root): #define DCP_FIRMWARE_V_14_7 (DCP_FIRMWARE_H17P + 1) #define DCP_FIRMWARE_V_14_7_TEMPLATE (DCP_FIRMWARE_H17P + 2) #endif -'''+internal_inline(root,'dcp_fw_is_14_7')+r'''/* Off T6030 machines the direct crossbar output is not clocked separately. */ +/* Nor the desktop 26.6 firmware, the one that withholds VRR modes. */ +#define DCP_FIRMWARE_V_26_6_DESKTOP (DCP_FIRMWARE_H17P + 3) +'''+firmware_lineage(root)+internal_inline(root,'dcp_fw_is_14_7')+r'''/* Off T6030 machines the direct crossbar output is not clocked separately. */ static int __attribute__((unused)) dcp_direct_crossbar_link(struct apple_dcp *d,bool up) { (void)d;(void)up;return 0; } ''' transport = '\n'.join(block(av,marker) for marker in [ @@ -698,7 +700,7 @@ def publication(root): + ''.join(apple_function(root, name) for name in ('dcp_queue_hotplug', 'dcp_modes_for_connector', 'apple_connector_invalidate_edid', 'apple_connector_edid_set_live')) + mode_helpers) - publication = block(iomfb,'static void dcp_clear_connector_edid(')+'\n'+block(iomfb,'static void dcp_consume_edid_invalidation(')+mode_helpers+block(iomfb,'int dcp_get_modes(')+block(iomfb,re.search(r'^[^\n;]*\blookup_mode\(',iomfb,re.M).start()) + publication = block(iomfb,'static void dcp_clear_connector_edid(')+'\n'+block(iomfb,'static void dcp_consume_edid_invalidation(')+mode_helpers+block(iomfb,'static bool dcp_mode_vrr_supported(')+'\n'+block(iomfb,'int dcp_get_modes(')+block(iomfb,re.search(r'^[^\n;(]*\blookup_mode\(',iomfb,re.M).start()) # Drive malformed replacements through the same chunk/end ingress as firmware. # The dictionary cache is a boundary substitute and takes ownership of the blob. receipt = r''' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/recovery.py b/tools/testing/selftests/drivers/gpu/apple-dcp/recovery.py index 0a26ac0e584f1..0f6a1591408c7 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/recovery.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/recovery.py @@ -258,6 +258,18 @@ def extract(text,signature): static bool apple_rtkit_is_running(void *r) { (void)r;assert(!"T6030 dcpext diagnostic on H17P");return false; } static bool apple_rtkit_is_crashed(void *r) { (void)r;assert(!"T6030 dcpext diagnostic on H17P");return false; } '''+sender + if 'afk_send_compact_command(' in sender: + # Compact EPIC is the desktop 26.6 (T6041) wire; this H17P endpoint + # never switches to it, so the classic sender runs. + unit=unit.replace('struct apple_epic_service services[AFK_MAX_CHANNEL]; };', + 'struct apple_epic_service services[AFK_MAX_CHANNEL];bool compact; };',1) + sender='''#ifndef DCP_FIRMWARE_V_26_6_DESKTOP + #define DCP_FIRMWARE_V_26_6_DESKTOP (DCP_FIRMWARE_H17P + 3) + #endif + static u32 afk_compact_next_status(struct apple_epic_service *s) { (void)s;assert(!"compact EPIC on H17P");return 0; } + static int afk_send_compact_command(struct apple_epic_service *s,u8 type,const void *payload,size_t payload_len,void *output,size_t output_len,u32 request_status,u32 *retcode,size_t *copied,unsigned long timeout) + { (void)s;(void)type;(void)payload;(void)payload_len;(void)output;(void)output_len;(void)request_status;(void)retcode;(void)copied;(void)timeout;assert(!"compact EPIC on H17P");return -EIO; } + '''+sender unit+=sender+'\n'+tests out.mkdir(parents=True,exist_ok=True) @@ -343,6 +355,19 @@ def run(name,text,cases): } ''' af=(root/'drivers/gpu/drm/apple/afk.c').read_text() + if 'static bool afk_compact_capable(' in af: + # The desktop 26.x ring messages and compact state; the actual + # capability check keeps this H17P endpoint on the classic path. + import re + queued=queued.replace('bool compact; };','bool compact;void *tx_wq,*compact_rx_data; };',1) + for message in ('RBEP_SPACE_AVAILABLE','RBEP_WAIT_FOR_SPACE'): + queued+='#define %s %s\n'%(message,re.search(r'\b'+message+r'\s*=\s*(\w+)',af).group(1)) + queued+=r''' + #define FIELD_PREP(mask,val) ((val)&(mask)) + #define dma_wmb() ((void)0) + static void afk_send(struct apple_dcp_afkep *e,u64 m) { (void)e;(void)m;assert(!"compact ring message on H17P"); } + static void afk_cancel_commands(struct apple_dcp_afkep *e) { assert(e && !e->compact); } + '''+extract(af,'static bool afk_compact_capable(')+'\n' for signature in ['static inline unsigned int afk_ring_drain_budget(', 'static inline unsigned int afk_ring_drain(']: queued+=extract(afh,signature)+'\n' for signature in ['static bool afk_recv_entry(', 'static void afk_recv_drain(',