diff --git a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml index fe61df65c5c27..9f6f992410fe1 100644 --- a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml +++ b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml @@ -70,18 +70,30 @@ properties: - const: sram - const: cpu - interrupts: - maxItems: 1 - description: Type 7 SRAM parity interrupt - - interrupt-names: - items: - - const: sram-parity - mboxes: maxItems: 1 description: Mailbox used for RTKit communication with the co-processor + interrupts: + minItems: 1 + maxItems: 4 + description: | + Type 7 SRAM parity interrupt, or interrupts of the router's DP IN + adapter blocks, one per DP IN adapter: + a change of the HPD level the adapter receives over the tunnel, and + IRQ_HPD from the sink. + + interrupt-names: + oneOf: + - items: + - const: sram-parity + - minItems: 1 + items: + - const: dpin0 + - const: dpin1 + - const: dpin2 + - const: dpin3 + resets: maxItems: 1 description: Reset used to start the ACIO block before booting the @@ -252,6 +264,12 @@ allOf: contains: const: apple,t6040-usb4-acio then: + properties: + interrupts: + maxItems: 1 + interrupt-names: + items: + - const: sram-parity required: - iommu@1a80000 - nhi@1f00000 diff --git a/drivers/thunderbolt/apple-dpin-state.c b/drivers/thunderbolt/apple-dpin-state.c index 1a2721d6e84a4..afaa64e3f6ba6 100644 --- a/drivers/thunderbolt/apple-dpin-state.c +++ b/drivers/thunderbolt/apple-dpin-state.c @@ -49,6 +49,27 @@ const struct apple_dpin_policy apple_dpin_m4 = { .lazy_idle = true, }; +/* + * T6050: the M3 flow (tunnel changed notifications, with the 10 ms HPD + * Output Set pulse and HPD wait before them; capacity retry, deferred + * bringup). Once a tunnel uses a DP IN block, its pending events are + * acknowledged (+0x04 written back, and +0x00 after a plug event) and its + * two events enabled (+0x08), as the block is prepared for a display. The + * DP IN handshake writes only DPTX_INACTIVE (+0x0c bit 0) and polls its ACK + * (+0x10 bit 0), gated on HPD. A tunnel whose DP IN block DCP never + * activated is torn down without the inactive handshake. A DPRX that is not + * done in time keeps the tunnel: the display may still train. + */ +const struct apple_dpin_policy apple_dpin_m5 = { + .flow = APPLE_DPIN_CHANGED, + .settle_ms = 100, + .capacity_retry = true, + .setup_irqs = true, + .defer_new_bringup = true, + .host_policy = TB_HOST_DP_NOTIFY | TB_HOST_DP_KEEP_DPRX_TIMEOUT, + .lazy_idle = true, +}; + enum apple_dpin_flow apple_dpin_hooks(const struct apple_dpin_policy *policy, bool queue_present, bool display_enabled) { diff --git a/drivers/thunderbolt/apple-dpin-state.h b/drivers/thunderbolt/apple-dpin-state.h index dcf3e53c0a1d6..38b8ec7eb439e 100644 --- a/drivers/thunderbolt/apple-dpin-state.h +++ b/drivers/thunderbolt/apple-dpin-state.h @@ -25,6 +25,7 @@ struct apple_dpin_policy { unsigned long host_policy; /* teardown touches the DP IN block only after DCP's Activate/Deactivate did */ bool lazy_idle; + unsigned int settle_ms; }; extern const struct apple_dpin_policy apple_dpin_disabled; @@ -32,6 +33,7 @@ extern const struct apple_dpin_policy apple_dpin_m1; extern const struct apple_dpin_policy apple_dpin_m2; extern const struct apple_dpin_policy apple_dpin_m3; extern const struct apple_dpin_policy apple_dpin_m4; +extern const struct apple_dpin_policy apple_dpin_m5; enum apple_dpin_phase { APPLE_DPIN_IDLE, diff --git a/drivers/thunderbolt/apple-dpin-test.c b/drivers/thunderbolt/apple-dpin-test.c index 0d4891ad74e14..dd532a2a051b5 100644 --- a/drivers/thunderbolt/apple-dpin-test.c +++ b/drivers/thunderbolt/apple-dpin-test.c @@ -360,6 +360,8 @@ static void apple_dpin_qualification_profiles(struct kunit *test) { "apple,t6031", &apple_dpin_m3, true, &apple_dpin_disabled }, { "apple,t8132", &apple_dpin_m4, true, &apple_dpin_m4 }, { "apple,t8132", &apple_dpin_m4, false, &apple_dpin_m4 }, + { "apple,t6050", &apple_dpin_m5, true, &apple_dpin_m5 }, + { "apple,t6050", &apple_dpin_m5, false, &apple_dpin_m5 }, { "apple,unknown", &apple_dpin_m1, true, &apple_dpin_disabled }, }; unsigned int i; diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c index 9cfd12a33dc06..e0cd146997d25 100644 --- a/drivers/thunderbolt/apple.c +++ b/drivers/thunderbolt/apple.c @@ -131,7 +131,12 @@ #define APPLE_MXWRAP_CPU_CTRL_RUN BIT(4) #define APPLE_MXWRAP_CPU_STATUS 0x44 +/* DP IN adapter blocks a host router may have (T6050 has four) */ +#define APPLE_DPIN_MAX 4 + struct apple_cio_hw { + /* DP IN adapter blocks of the router; 0 means the two of t8103 */ + unsigned int dpin_count; bool mxwrap; bool lstx_explicit; /* Leave MxWrap RUN alone: the CIO reconfig restarts the CPU. */ @@ -141,6 +146,8 @@ struct apple_cio_hw { bool strict_fw_ready; /* Keep the links of the host router out of CL states. */ bool no_host_clx; + /* Keep the domain's links symmetric. */ + bool no_asym; /* Type 7 router: Enter_USB cable words, bootloader RC tuning, Type 7 NHI. */ bool type7; /* @@ -152,6 +159,10 @@ struct apple_cio_hw { bool lstx_hold_from_probe; /* The router trains Gen4 only on a cable word that says Gen4. */ bool gen4_cable; + /* Run Gen4 links with link recovery enabled at both ends. */ + bool gen4_link_recovery; + /* Leave the host router's TMU to its firmware; host TMU constants. */ + bool host_tmu; const struct apple_dpin_policy *dp; }; @@ -197,9 +208,13 @@ static const struct apple_cio_hw apple_cio_t8142_hw = { * T6050 is Type 7 with an MxWrap CPU wrapper that is only clocked once the * CIO reset (the reconfig request) has run. Its CPU is released with a plain * store of RUN; as on T8132 it takes LSTX over only once its RTKit is up, and - * the AP holds the pin while the router is off. DP tunnels stay off for now. + * the AP holds the pin while the router is off. Its four DP IN adapters take + * the M5 policy, see apple_dpin_m5. A Gen4 link times out CL training once CL + * states are enabled, then drops; it also dropped in the middle of a + * transition to an asymmetric width. Its links stay in CL0 and symmetric. */ static const struct apple_cio_hw apple_cio_t6050_hw = { + .dpin_count = 4, .mxwrap = true, .type7 = true, .run_store = true, @@ -207,7 +222,11 @@ static const struct apple_cio_hw apple_cio_t6050_hw = { .lstx_hold_from_probe = true, .strict_fw_ready = true, .gen4_cable = true, - .dp = &apple_dpin_disabled, + .no_host_clx = true, + .no_asym = true, + .gen4_link_recovery = true, + .host_tmu = true, + .dp = &apple_dpin_m5, }; static const struct apple_cio_hw apple_cio_t6040_hw = { @@ -296,6 +315,9 @@ module_param(dpin_mode_value, uint, 0644); MODULE_PARM_DESC(dpin_mode_value, "DPIN0 MODE_A/MODE_B lab value (0-" __stringify(APPLE_DPIN_MODE_VALUE_MAX) "); read at each activate"); +/* DP IN adapters without an interrupt are polled this often (ms). */ +#define APPLE_DPIN_POLL_MS 20 + struct apple_dpin_binding; /** @@ -395,6 +417,7 @@ struct apple_cio { /* Type-C connector this router is wired to, for DP tunnel routing */ struct device_node *connector_np; bool dp_dual_stream; + unsigned int dpin_count; struct workqueue_struct *dp_wq; struct apple_dpin_ctx { struct apple_cio *acio; @@ -405,9 +428,19 @@ struct apple_cio { unsigned int idx; struct apple_dpin_state state; struct apple_dpin_tokens tokens; + unsigned long settle_until; /* new tunnel's admission deadline */ struct apple_dpin_binding *binding; /* worker-owned, handed to DCP */ bool dcp_touched; /* DCP ran the handshake on this mapping */ - } dpin[2]; + bool dcp_active; /* DCP activated the link (DPTX_INACTIVE clear) */ + /* DP IN interrupts: the line, or a poll where there is none */ + int irq; + bool irq_enabled; + struct delayed_work poll; + struct work_struct event_work; /* passes events on, on dp_wq */ + unsigned int events; /* APPLE_DP_TUNNEL_*, under lock */ + bool hpd; /* HPD level last seen, under lock */ + unsigned int irq_count; + } dpin[APPLE_DPIN_MAX]; }; /* Immutable identity for one callback handoff; freed only after provider drain. */ @@ -418,21 +451,27 @@ struct apple_dpin_binding { }; /* - * The ATC's DP IN adapter blocks ("atcN-dpin0/1" in the Apple device tree). + * The ATC's DP IN adapter blocks ("atcN-dpin0..3" in the Apple device tree). * Registers on t8103: * 0x00 status, bit 2 = HPD level * 0x04 interrupt status (write 1 to clear) * 0x08 interrupt enable [1:0] * 0x0c bit 0 DPTX_INACTIVE * 0x10 bit 0 DPTX_INACTIVE_ACK - * The block sits 0x390000 after the ACIO "rc" region; dpin1 is 0x8000 after dpin0. + * The blocks sit 0x390000 after the ACIO "rc" region: dpin1 0x8000 after + * dpin0, and on routers with four, dpin2 and dpin3 0x18000 and 0x20000 after. */ #define APPLE_DPIN_OFFSET 0x390000 -#define APPLE_DPIN_STRIDE 0x8000 #define APPLE_DPIN_SIZE 0x4000 + +static const u32 apple_dpin_offsets[APPLE_DPIN_MAX] = { + 0x0, 0x8000, 0x18000, 0x20000, +}; #define APPLE_DPIN_STATUS 0x00 #define APPLE_DPIN_STATUS_HPD BIT(2) #define APPLE_DPIN_IRQ_STATUS 0x04 +#define APPLE_DPIN_IRQ_HPD_CHANGE BIT(0) +#define APPLE_DPIN_IRQ_SINK BIT(1) #define APPLE_DPIN_IRQ_ENABLE 0x08 #define APPLE_DPIN_CTRL 0x0c #define APPLE_DPIN_CTRL_INACTIVE BIT(0) @@ -456,6 +495,8 @@ static const struct of_device_id apple_dpin_qualified_soc[] = { { .compatible = "apple,t6021" }, { .compatible = "apple,t6030" }, { .compatible = "apple,t8132" }, + /* T6050: t8103 DP IN handshake, M3 flow, four DP IN adapters */ + { .compatible = "apple,t6050" }, {}, }; @@ -583,6 +624,146 @@ static void apple_dpin_request_inactive(void *data) apple_dpin_set_active(c->acio, c->regs, c->idx, false); } +/* + * DP IN interrupts. +0x4 holds the events, write-1-to-clear: bit 0 the HPD + * level in +0x0 changed (+0x0 latches status bits that are cleared by + * writing it back), bit 1 the sink raised IRQ_HPD. Read +0x4 until it is + * stable, clear it, and hand what happened to appledrm: the display's DPTX + * port has no HPD input of its own on a tunnel. c->lock held. + */ +static unsigned int apple_dpin_irq_collect(struct apple_dpin_ctx *c, bool *seen) +{ + unsigned int events = 0, tries; + u32 irq, again, st; + bool hpd; + + lockdep_assert_held(&c->lock); + *seen = false; + /* A handler or poll may have waited on the lock while teardown stopped it. */ + if (!c->irq_enabled || !c->state.alive || !c->regs) + return 0; + irq = readl(c->regs + APPLE_DPIN_IRQ_STATUS); + for (tries = 0; tries < 8; tries++) { + again = readl(c->regs + APPLE_DPIN_IRQ_STATUS); + if (again == irq) + break; + irq = again; + } + if (!irq) + return 0; + *seen = true; + writel(irq, c->regs + APPLE_DPIN_IRQ_STATUS); + st = readl(c->regs + APPLE_DPIN_STATUS); + if (irq & APPLE_DPIN_IRQ_HPD_CHANGE) + writel(st, c->regs + APPLE_DPIN_STATUS); + hpd = st & APPLE_DPIN_STATUS_HPD; + if (hpd != c->hpd) + events |= APPLE_DP_TUNNEL_HPD_CHANGED; + c->hpd = hpd; + if (irq & APPLE_DPIN_IRQ_SINK) + events |= APPLE_DP_TUNNEL_IRQ_HPD; + c->irq_count++; + dev_dbg(c->acio->dev, "dpin%u: interrupt %#x (#%u), status %#x: HPD %d%s%s\n", + c->idx, irq, c->irq_count, st, hpd, + events & APPLE_DP_TUNNEL_IRQ_HPD ? ", IRQ_HPD" : "", + events & APPLE_DP_TUNNEL_HPD_CHANGED ? ", HPD changed" : ""); + events &= APPLE_DP_TUNNEL_IRQ_HPD | APPLE_DP_TUNNEL_HPD_CHANGED; + c->events |= events; + return events; +} + +/* Returns false if the adapter had nothing pending. */ +static bool apple_dpin_irq_dispatch(struct apple_dpin_ctx *c) +{ + unsigned int events; + bool seen; + + scoped_guard(mutex, &c->lock) + events = apple_dpin_irq_collect(c, &seen); + if (events) + queue_work(c->acio->dp_wq, &c->event_work); + return seen; +} + +static irqreturn_t apple_dpin_irq_thread(int irq, void *data) +{ + return apple_dpin_irq_dispatch(data) ? IRQ_HANDLED : IRQ_NONE; +} + +static void apple_dpin_poll_fn(struct work_struct *work) +{ + struct apple_dpin_ctx *c = container_of(to_delayed_work(work), + struct apple_dpin_ctx, poll); + bool mapped; + + apple_dpin_irq_dispatch(c); + scoped_guard(mutex, &c->lock) + mapped = c->irq_enabled && c->regs; + if (mapped) + queue_delayed_work(system_dfl_wq, &c->poll, + msecs_to_jiffies(APPLE_DPIN_POLL_MS)); +} + +/* Start taking the interrupts of a mapped adapter. c->lock held. */ +static void apple_dpin_irq_start(struct apple_dpin_ctx *c) +{ + lockdep_assert_held(&c->lock); + if (c->irq_enabled || !c->regs) + return; + c->hpd = readl(c->regs + APPLE_DPIN_STATUS) & APPLE_DPIN_STATUS_HPD; + c->events = 0; + c->irq_enabled = true; + if (c->irq > 0) + enable_irq(c->irq); + else + queue_delayed_work(system_dfl_wq, &c->poll, + msecs_to_jiffies(APPLE_DPIN_POLL_MS)); +} + +/* The handler takes c->lock: never wait for it here. c->lock held. */ +static void apple_dpin_irq_stop(struct apple_dpin_ctx *c) +{ + lockdep_assert_held(&c->lock); + if (!c->irq_enabled) + return; + c->irq_enabled = false; + c->events = 0; + if (c->irq > 0) + disable_irq_nosync(c->irq); + else + cancel_delayed_work(&c->poll); +} + +/* On dp_wq, ordered with bring-up and teardown: the binding is stable. */ +static void apple_dpin_event_fn(struct work_struct *work) +{ + struct apple_dpin_ctx *c = container_of(work, struct apple_dpin_ctx, event_work); + typeof(apple_dcp_tb_dp_event) *event; + unsigned int events; + bool hpd, active; + int ret; + + scoped_guard(mutex, &c->lock) { + events = c->events; + c->events = 0; + hpd = c->hpd; + active = c->dcp_active && c->state.alive; + } + /* The sink's requests matter once the display has activated the link. */ + if (!active) + events &= ~APPLE_DP_TUNNEL_IRQ_HPD; + if (!events || !c->binding) + return; + event = symbol_get(apple_dcp_tb_dp_event); + if (!event) + return; + ret = event(c->acio->connector_np, c->idx, c->binding->generation, events, hpd); + symbol_put(apple_dcp_tb_dp_event); + if (ret) + dev_info(c->acio->dev, "dpin%u: display did not take events %#x: %d\n", + c->idx, events, ret); +} + static void apple_dpin_request_mask_irqs(void *data) { struct apple_dpin_ctx *c = data; @@ -593,6 +774,7 @@ static void apple_dpin_request_mask_irqs(void *data) writel(readl(c->regs + APPLE_DPIN_IRQ_ENABLE) & ~3, c->regs + APPLE_DPIN_IRQ_ENABLE); + apple_dpin_irq_stop(c); } static bool apple_dpin_request(struct apple_dpin_ctx *c, u64 generation, bool active) @@ -602,10 +784,15 @@ static bool apple_dpin_request(struct apple_dpin_ctx *c, u64 generation, bool ac .mask_irqs = apple_dpin_request_mask_irqs, .ctx = c, }; + bool replacement = active && c->tokens.requested != generation; lockdep_assert_held(&c->lock); - return apple_dpin_powered_request(&c->state, &c->tokens, c->acio->dp, - generation, active, !!c->regs, &ops); + if (!apple_dpin_powered_request(&c->state, &c->tokens, c->acio->dp, + generation, active, !!c->regs, &ops)) + return false; + if (replacement) + c->settle_until = jiffies + msecs_to_jiffies(c->acio->dp->settle_ms); + return true; } /* Called by appledrm from DCP's Activate/Deactivate calls. */ @@ -613,6 +800,7 @@ static int apple_dpin_dcp_set_active(void *data, bool active) { const struct apple_dpin_binding *binding = data; struct apple_dpin_ctx *c = binding->ctx; + int ret; guard(mutex)(&c->lock); if (!apple_dpin_token_access(&c->tokens, binding->generation)) @@ -621,7 +809,10 @@ static int apple_dpin_dcp_set_active(void *data, bool active) if (!c->state.alive || !c->regs) return -ENODEV; c->dcp_touched = true; - return apple_dpin_set_active(c->acio, c->regs, c->idx, active); + ret = apple_dpin_set_active(c->acio, c->regs, c->idx, active); + if (!ret) + c->dcp_active = active; + return ret; } static bool apple_dpin_provider_get(void *data) @@ -721,14 +912,45 @@ static int apple_dpin_connect(struct apple_dpin_ctx *c, } } +/* + * A USB4 dock can withdraw and re-advertise its DP OUT adapters while the + * initial capability exchange completes. On T6050, starting DCP on the + * temporary tunnel can leave its AUX request stuck after that tunnel goes + * away. Let the request settle before admitting any DCP callback or touching + * the adapter. Teardown still idles immediately in apple_dpin_request(). + * A replacement owns a new deadline; this worker never admits the old token. + */ +static int apple_dpin_wait_settled(struct apple_dpin_ctx *c, u64 generation) +{ + unsigned long delay, now; + + if (!c->acio->dp->settle_ms) + return 0; + for (;;) { + scoped_guard(mutex, &c->lock) { + if (!c->state.alive || c->tokens.requested != generation) + return -ESTALE; + now = jiffies; + if (time_after_eq(now, c->settle_until)) + return 0; + delay = c->settle_until - now; + } + msleep(jiffies_to_msecs(delay) + 1); + } +} + static int apple_dpin_up(struct apple_dpin_ctx *c, struct apple_dpin_binding *binding, bool *deferred) { struct apple_cio *acio = c->acio; void __iomem *regs = c->regs; + int ret; u32 irq; *deferred = false; + ret = apple_dpin_wait_settled(c, binding->generation); + if (ret) + return ret; scoped_guard(mutex, &c->lock) { if (!c->state.alive || c->tokens.requested != binding->generation) return -ESTALE; @@ -739,7 +961,7 @@ static int apple_dpin_up(struct apple_dpin_ctx *c, /* This admission also owns the first DCP call after dropping the lock. */ if (!regs) { regs = ioremap_np(acio->rc_res->start + APPLE_DPIN_OFFSET + - c->idx * APPLE_DPIN_STRIDE, APPLE_DPIN_SIZE); + apple_dpin_offsets[c->idx], APPLE_DPIN_SIZE); if (!regs) return -ENOMEM; c->regs = regs; @@ -748,8 +970,7 @@ static int apple_dpin_up(struct apple_dpin_ctx *c, * Acknowledge what is pending and enable the DP IN interrupts: * +0x4 is write-1-to-clear, and a plug event (bit 0) also * latches status bits in +0x0 that are cleared by writing it - * back. Nothing handles these interrupts yet; they are enabled - * as part of bringing the adapter up. + * back. See apple_dpin_irq_collect() for what they report. */ if (acio->dp->setup_irqs) { irq = readl(regs + APPLE_DPIN_IRQ_STATUS); @@ -757,6 +978,7 @@ static int apple_dpin_up(struct apple_dpin_ctx *c, if (irq & BIT(0)) writel(readl(regs + APPLE_DPIN_STATUS), regs + APPLE_DPIN_STATUS); writel(readl(regs + APPLE_DPIN_IRQ_ENABLE) | 3, regs + APPLE_DPIN_IRQ_ENABLE); + apple_dpin_irq_start(c); } /* Activate may run before attach returns, so admit its immutable cookie now. */ apple_dpin_token_admit(&c->tokens, binding->generation); @@ -791,9 +1013,11 @@ static void apple_dpin_down(struct apple_dpin_ctx *c) } /* The powered request hook idled the adapter before queued teardown. */ scoped_guard(mutex, &c->lock) { + apple_dpin_irq_stop(c); regs = c->regs; c->regs = NULL; c->dcp_touched = false; + c->dcp_active = false; apple_dpin_step(&c->state, c->acio->dp, APPLE_DPIN_DROPPED, false, 0); } if (regs) @@ -921,8 +1145,10 @@ static void apple_cio_destroy_wq(void *data) struct apple_cio *acio = data; /* a pending retry would queue onto the destroyed queue */ - for (unsigned int i = 0; i < ARRAY_SIZE(acio->dpin); i++) + for (unsigned int i = 0; i < ARRAY_SIZE(acio->dpin); i++) { cancel_delayed_work_sync(&acio->dpin[i].retry); + cancel_delayed_work_sync(&acio->dpin[i].poll); + } destroy_workqueue(acio->dp_wq); } @@ -1910,7 +2136,7 @@ static void apple_dp_aux_work(struct work_struct *work) } /* - * Which DP IN adapter (0/1) on the host router this port is -- same + * Which DP IN adapter (0..3) on the host router this port is -- same * counting order as apple_dp_in_analog_base() above and the port-to-dpin * mapping in apple_nhi_dp_tunnel_changed(). */ @@ -1932,6 +2158,12 @@ static int apple_dpin_index_for_port(struct apple_nhi *anhi, struct tb_port *in) return -1; } +/* A DP IN adapter index with a block of its own on this router */ +static bool apple_dpin_valid(const struct apple_cio *acio, int idx) +{ + return acio && idx >= 0 && idx < acio->dpin_count; +} + static int apple_nhi_dp_tunnel_pre_activate(struct tb_nhi *nhi, struct tb_port *in, struct tb_port *out) @@ -1951,7 +2183,7 @@ static int apple_nhi_dp_tunnel_post_activate(struct tb_nhi *nhi, /* Validate the token before changing analog/AUX or display state. */ idx = apple_dpin_index_for_port(anhi, in); - if (idx >= 0 && idx <= 1 && anhi->acio && anhi->acio->connector_np && + if (apple_dpin_valid(anhi->acio, idx) && anhi->acio->connector_np && anhi->acio->dp_wq) { c = &anhi->acio->dpin[idx]; scoped_guard(mutex, &c->lock) { @@ -1978,7 +2210,7 @@ static int apple_nhi_dp_tunnel_post_activate(struct tb_nhi *nhi, apple_dp_dump_vse(in->sw); /* Route the display pipeline through the selected host DP IN adapter. */ - if (idx < 0 || idx > 1 || !anhi->acio) { + if (!apple_dpin_valid(anhi->acio, idx)) { dev_warn(anhi->dev, "DP IN tunnel: could not map port %u to a dpin index\n", in->port); @@ -2018,7 +2250,7 @@ static void apple_nhi_dp_tunnel_deactivate(struct tb_nhi *nhi, /* Sleep the adapter before tunnel teardown can remove its power. */ idx = apple_dpin_index_for_port(anhi, in); - if (idx >= 0 && idx <= 1 && anhi->acio && anhi->acio->dp_wq) { + if (apple_dpin_valid(anhi->acio, idx) && anhi->acio->dp_wq) { struct apple_dpin_ctx *c = &anhi->acio->dpin[idx]; scoped_guard(mutex, &c->lock) { @@ -2041,7 +2273,7 @@ static void apple_nhi_dp_tunnel_changed(struct tb_nhi *nhi, u8 in_port, unsigned int dpin = 0; bool found = false; - /* The crossbar's dpin0/dpin1 follow the host router's DP IN adapters in order. */ + /* The crossbar's dpin0..3 follow the host router's DP IN adapters in order. */ tb_switch_for_each_port(anhi->tb->root_switch, port) { if (!tb_port_is_dpin(port)) continue; @@ -2051,7 +2283,7 @@ static void apple_nhi_dp_tunnel_changed(struct tb_nhi *nhi, u8 in_port, } dpin++; } - if (!found || dpin > 1) { + if (!found || dpin >= acio->dpin_count) { dev_warn(acio->dev, "DP tunnel from unexpected adapter %u\n", in_port); return; } @@ -2079,7 +2311,7 @@ static bool apple_nhi_dp_tunnel_awaits_display(struct tb_nhi *nhi, int idx; idx = apple_dpin_index_for_port(anhi, in); - if (idx < 0 || idx > 1 || !anhi->acio) + if (!apple_dpin_valid(anhi->acio, idx)) return false; c = &anhi->acio->dpin[idx]; guard(mutex)(&c->lock); @@ -2220,6 +2452,12 @@ static int apple_nhi_probe(struct platform_device *pdev) anhi->nhi.quirks |= QUIRK_HOST_DP_NFC_CREDITS; if (acio->hw->no_host_clx) anhi->nhi.quirks |= QUIRK_NO_HOST_CLX; + if (acio->hw->no_asym) + anhi->nhi.quirks |= QUIRK_NO_ASYM; + if (acio->hw->gen4_link_recovery) + anhi->nhi.quirks |= QUIRK_GEN4_LINK_RECOVERY; + if (acio->hw->host_tmu) + anhi->nhi.quirks |= QUIRK_HOST_TMU; anhi->nhi.hop_count = readl(anhi->nhi_base + APPLE_CIO_NHI_HOP_COUNT) & APPLE_CIO_NHI_HOP_COUNT_MASK; @@ -3592,6 +3830,7 @@ static int apple_cio_probe(struct platform_device *pdev) else if (ret < 3) return dev_err_probe(dev, -EINVAL, "Not enough PM domains\n"); + acio->dpin_count = acio->hw->dpin_count ?: 2; acio->connector_np = of_graph_get_remote_node(dev->of_node, 1, -1); acio->dp_dual_stream = apple_dp_tunnel_dual_stream(acio->connector_np); /* Register layout, root qualification and connector wiring are separate. */ @@ -3606,6 +3845,8 @@ static int apple_cio_probe(struct platform_device *pdev) acio->dpin[i].idx = i; INIT_WORK(&acio->dpin[i].work, apple_dpin_work_fn); INIT_DELAYED_WORK(&acio->dpin[i].retry, apple_dpin_retry_fn); + INIT_DELAYED_WORK(&acio->dpin[i].poll, apple_dpin_poll_fn); + INIT_WORK(&acio->dpin[i].event_work, apple_dpin_event_fn); ret = devm_mutex_init(dev, &acio->dpin[i].lock); if (ret) return ret; @@ -3617,6 +3858,25 @@ static int apple_cio_probe(struct platform_device *pdev) ret = devm_add_action_or_reset(dev, apple_cio_destroy_wq, acio); if (ret) return ret; + /* Freed before the queue: started and stopped with the mapping. */ + for (unsigned int i = 0; i < acio->dpin_count && i < APPLE_DPIN_MAX; i++) { + struct apple_dpin_ctx *c = &acio->dpin[i]; + char name[8]; + + snprintf(name, sizeof(name), "dpin%u", i); + c->irq = platform_get_irq_byname_optional(to_platform_device(dev), name); + if (c->irq <= 0) + continue; + ret = devm_request_threaded_irq(dev, c->irq, NULL, + apple_dpin_irq_thread, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + devm_kasprintf(dev, GFP_KERNEL, "%s-%s", + dev_name(dev), name), + c); + if (ret) + return dev_err_probe(dev, ret, + "cannot request %s interrupt\n", name); + } } /* And finally register the OOB notification for Thunderbolt/USB4 cables */ diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 75795fbf865a1..6ec6d61f02759 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -222,6 +222,15 @@ struct tb_nhi_ops { #define QUIRK_KEEP_TUNNELS BIT(7) /* The links of the host router must not enter CL states. */ #define QUIRK_NO_HOST_CLX BIT(8) +/* The links of the domain must stay symmetric. */ +#define QUIRK_NO_ASYM BIT(9) +/* Gen4 links of the domain run with link recovery enabled on both ends. */ +#define QUIRK_GEN4_LINK_RECOVERY BIT(10) +/* + * The host router's firmware runs its TMU, and enhanced uni-directional + * mode takes the host's own constants, see tmu.c. + */ +#define QUIRK_HOST_TMU BIT(11) /* Host display policies are independent of which optional hooks are installed. */ #define TB_HOST_DP_NOTIFY BIT(0) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index a83bbcbca0194..1bc7165678e9d 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -3206,6 +3206,40 @@ int tb_switch_set_link_width(struct tb_switch *sw, enum tb_link_width width) return ret; } +/* + * Some hosts (Apple silicon) run their Gen4 links with link recovery + * enabled at both ends. Without it, the router firmware has no way back + * from a Gen4 link error but to disconnect the link. + */ +static void tb_switch_gen4_link_recovery(struct tb_switch *sw, + struct tb_port *up, struct tb_port *down) +{ + struct tb_port *ports[] = { down, up }; + unsigned int i; + u32 val; + + if (!(sw->tb->nhi->quirks & QUIRK_GEN4_LINK_RECOVERY) || + tb_port_get_link_generation(up) < 4) + return; + + for (i = 0; i < ARRAY_SIZE(ports); i++) { + struct tb_port *port = ports[i]; + + if (!port->cap_usb4 || + tb_port_read(port, &val, TB_CFG_PORT, + port->cap_usb4 + PORT_CS_19, 1)) + continue; + if (val & PORT_CS_19_GEN4_LINK_RECOVERY) + continue; + val |= PORT_CS_19_GEN4_LINK_RECOVERY; + if (tb_port_write(port, &val, TB_CFG_PORT, + port->cap_usb4 + PORT_CS_19, 1)) + tb_port_warn(port, "cannot enable Gen4 link recovery\n"); + else + tb_port_dbg(port, "Gen4 link recovery enabled\n"); + } +} + /** * tb_switch_configure_link() - Set link configured * @sw: Switch whose link is configured @@ -3236,8 +3270,15 @@ int tb_switch_configure_link(struct tb_switch *sw) down = up->remote; if (tb_switch_is_usb4(down->sw)) - return usb4_port_configure(down); - return tb_lc_configure_port(down); + ret = usb4_port_configure(down); + else + ret = tb_lc_configure_port(down); + if (ret) + return ret; + + if (tb_switch_is_usb4(up->sw) && tb_switch_is_usb4(down->sw)) + tb_switch_gen4_link_recovery(sw, up, down); + return 0; } /** diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index a4d439f2394df..f6e750743206b 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -355,6 +355,10 @@ static int tb_enable_tmu(struct tb_switch *sw) { int ret; + /* The host router's firmware runs its TMU on these hosts. */ + if (!tb_route(sw) && tb_switch_tmu_host_managed(sw)) + return 0; + /* * If both routers at the end of the link are v2 we simply * enable the enhanced uni-directional mode. That covers all @@ -746,9 +750,10 @@ static int tb_maximum_bandwidth(struct tb *tb, struct tb_port *src_port, /* * Can include asymmetric, only if it is actually supported by - * the lane adapter. + * the lane adapter and allowed on the host. */ - if (!tb_asym_supported(src_port, dst_port, port)) + if (!tb_asym_supported(src_port, dst_port, port) || + (tb->nhi->quirks & QUIRK_NO_ASYM)) include_asym = false; if (tb_is_upstream_port(port)) { @@ -1078,7 +1083,7 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port, struct tb_port *up; int ret = 0; - if (!asym_threshold) + if (!asym_threshold || (tb->nhi->quirks & QUIRK_NO_ASYM)) return 0; downstream = tb_port_path_direction_downstream(src_port, dst_port); @@ -3943,9 +3948,11 @@ static int tb_start(struct tb *tb, bool reset) * To support highest CLx state, we set host router's TMU to * Normal mode. */ - tb_switch_tmu_configure(tb->root_switch, TB_SWITCH_TMU_MODE_LOWRES); - /* Enable TMU if it is off */ - tb_switch_tmu_enable(tb->root_switch); + if (!tb_switch_tmu_host_managed(tb->root_switch)) { + tb_switch_tmu_configure(tb->root_switch, TB_SWITCH_TMU_MODE_LOWRES); + /* Enable TMU if it is off */ + tb_switch_tmu_enable(tb->root_switch); + } /* * Boot firmware might have created tunnels of its own. Since we diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 841d1ccb27042..a10d8d12f3d5d 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1061,6 +1061,7 @@ int tb_switch_tmu_post_time(struct tb_switch *sw); int tb_switch_tmu_disable(struct tb_switch *sw); int tb_switch_tmu_enable(struct tb_switch *sw); int tb_switch_tmu_configure(struct tb_switch *sw, enum tb_switch_tmu_mode mode); +bool tb_switch_tmu_host_managed(const struct tb_switch *sw); /** * tb_switch_tmu_is_configured() - Is given TMU mode configured diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index 54057a331a596..827cbb739fb7e 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -398,6 +398,7 @@ struct tb_regs_port_header { #define PORT_CS_19_WOD BIT(17) #define PORT_CS_19_WOU4 BIT(18) #define PORT_CS_19_START_ASYM BIT(24) +#define PORT_CS_19_GEN4_LINK_RECOVERY BIT(31) /* Display Port adapter registers */ #define ADP_DP_CS_0 0x00 diff --git a/drivers/thunderbolt/tmu.c b/drivers/thunderbolt/tmu.c index cf779874c6756..ce494a044e4f0 100644 --- a/drivers/thunderbolt/tmu.c +++ b/drivers/thunderbolt/tmu.c @@ -19,7 +19,7 @@ static const unsigned int tmu_rates[] = { [TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI] = 16, }; -static const struct { +static const struct tmu_mode_params { unsigned int freq_meas_window; unsigned int avg_const; unsigned int delta_avg_const; @@ -37,6 +37,36 @@ static const struct { }, }; +/* + * Enhanced uni-directional mode as Apple silicon hosts run it: averaging + * and delta averaging constants 8, and replenish timeout 1875, threshold 0 + * and count 222, written to the parent side of the link only. + */ +static const struct tmu_mode_params tmu_params_host_enhanced = { + 800, 8, 8, 1875, 0, 222, 255, +}; + +/** + * tb_switch_tmu_host_managed() - Is the TMU set up the host's way + * @sw: Any router of the domain + * + * Return: %true if the host router's TMU is left to its firmware and + * enhanced uni-directional mode uses the host's constants. + */ +bool tb_switch_tmu_host_managed(const struct tb_switch *sw) +{ + return sw->tb->nhi->quirks & QUIRK_HOST_TMU; +} + +static const struct tmu_mode_params * +tb_tmu_params(const struct tb_switch *sw, enum tb_switch_tmu_mode mode) +{ + if (mode == TB_SWITCH_TMU_MODE_MEDRES_ENHANCED_UNI && + tb_switch_tmu_host_managed(sw)) + return &tmu_params_host_enhanced; + return &tmu_params[mode]; +} + static const char *tmu_mode_name(enum tb_switch_tmu_mode mode) { switch (mode) { @@ -66,8 +96,8 @@ static int tb_switch_set_tmu_mode_params(struct tb_switch *sw, u32 freq, avg, val; int ret; - freq = tmu_params[mode].freq_meas_window; - avg = tmu_params[mode].avg_const; + freq = tb_tmu_params(sw, mode)->freq_meas_window; + avg = tb_tmu_params(sw, mode)->avg_const; ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, sw->tmu.cap + TMU_RTR_CS_0, 1); @@ -102,7 +132,7 @@ static int tb_switch_set_tmu_mode_params(struct tb_switch *sw, return ret; if (tb_switch_tmu_enhanced_is_supported(sw)) { - u32 delta_avg = tmu_params[mode].delta_avg_const; + u32 delta_avg = tb_tmu_params(sw, mode)->delta_avg_const; ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, sw->tmu.cap + TMU_RTR_CS_18, 1); @@ -257,10 +287,10 @@ static int tb_port_set_tmu_mode_params(struct tb_port *port, u32 repl_timeout, repl_threshold, repl_n, dirswitch_n, val; int ret; - repl_timeout = tmu_params[mode].repl_timeout; - repl_threshold = tmu_params[mode].repl_threshold; - repl_n = tmu_params[mode].repl_n; - dirswitch_n = tmu_params[mode].dirswitch_n; + repl_timeout = tb_tmu_params(port->sw, mode)->repl_timeout; + repl_threshold = tb_tmu_params(port->sw, mode)->repl_threshold; + repl_n = tb_tmu_params(port->sw, mode)->repl_n; + dirswitch_n = tb_tmu_params(port->sw, mode)->dirswitch_n; ret = tb_port_read(port, &val, TB_CFG_PORT, port->cap_tmu + TMU_ADP_CS_8, 1); @@ -291,6 +321,23 @@ static int tb_port_set_tmu_mode_params(struct tb_port *port, port->cap_tmu + TMU_ADP_CS_9, 1); } +static int tb_port_tmu_dirswitch_write(struct tb_port *port, u32 dirswitch_n) +{ + int ret; + u32 val; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_tmu + TMU_ADP_CS_9, 1); + if (ret) + return ret; + + val &= ~TMU_ADP_CS_9_DIRSWITCH_N_MASK; + val |= FIELD_PREP(TMU_ADP_CS_9_DIRSWITCH_N_MASK, dirswitch_n); + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_tmu + TMU_ADP_CS_9, 1); +} + /* Can be called to non-v2 lane adapters too */ static int tb_port_tmu_rate_write(struct tb_port *port, int rate) { @@ -786,7 +833,29 @@ static int tb_switch_tmu_enable_enhanced(struct tb_switch *sw) up = tb_upstream_port(sw); down = tb_switch_downstream_port(sw); - ret = tb_port_set_tmu_mode_params(up, sw->tmu.mode_request); + /* + * The host's way: time sync off on both lane adapters first, then + * on again once the mode is programmed (below). The host router + * applies its TMU configuration only when its lane adapter's time + * sync goes from off to on, and it comes out of reset on. + */ + if (tb_switch_tmu_host_managed(sw)) { + ret = tb_port_tmu_time_sync_disable(up); + if (!ret) + ret = tb_port_tmu_time_sync_disable(down); + if (ret) + goto out; + } + + /* + * The host's way: replenish parameters on the parent side only, + * but the direction switch count on both. + */ + if (tb_switch_tmu_host_managed(sw)) + ret = tb_port_tmu_dirswitch_write(up, + tb_tmu_params(sw, sw->tmu.mode_request)->dirswitch_n); + else + ret = tb_port_set_tmu_mode_params(up, sw->tmu.mode_request); if (ret) goto out; @@ -810,6 +879,16 @@ static int tb_switch_tmu_enable_enhanced(struct tb_switch *sw) if (ret) goto out; + /* Time sync on again: device side first, then the host router's. */ + if (tb_switch_tmu_host_managed(sw)) { + ret = tb_port_tmu_time_sync_enable(up); + if (ret) + goto out; + ret = tb_port_tmu_time_sync_enable(down); + if (ret) + goto out; + } + return 0; out: @@ -962,9 +1041,12 @@ int tb_switch_tmu_enable(struct tb_switch *sw) return ret; } - ret = tb_switch_tmu_set_time_disruption(sw, true); - if (ret) - return ret; + /* The host's way leaves the router's time disruption bit alone. */ + if (!tb_switch_tmu_host_managed(sw)) { + ret = tb_switch_tmu_set_time_disruption(sw, true); + if (ret) + return ret; + } if (tb_route(sw)) { /* @@ -1014,6 +1096,8 @@ int tb_switch_tmu_enable(struct tb_switch *sw) tb_sw_dbg(sw, "TMU: mode set to: %s\n", tmu_mode_name(sw->tmu.mode)); } + if (tb_switch_tmu_host_managed(sw)) + return ret; return tb_switch_tmu_set_time_disruption(sw, false); } diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 947a297f4d09e..94b1cac077e97 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -1319,6 +1319,37 @@ static int tb_dp_bandwidth_alloc_mode_enable(struct tb_tunnel *tunnel) return 0; } +/* + * An Apple silicon host sets the SSC offset that the DP OUT adapter's + * stream clock recovery allows, the low half of TMU capability word 6, to + * 1500 before it touches the adapter otherwise. Docks come up with their + * own value (2500 on a Barlow Ridge dock). + */ +#define TB_DP_APPLE_SSC_OFFSET 1500 + +static void tb_dp_apple_ssc_offset(struct tb_tunnel *tunnel) +{ + struct tb_port *out = tunnel->dst_port; + u32 v, old; + + if (!tb_port_is_apple_host_dpin(tunnel->src_port) || + !tb_port_is_dpout(out) || !out->cap_tmu) + return; + /* Only inside the TMU capability: before its next-capability pointer. */ + if (tb_port_read(out, &v, TB_CFG_PORT, out->cap_tmu, 1) || + ((v & 0xff) && (v & 0xff) <= out->cap_tmu + 6) || + tb_port_read(out, &v, TB_CFG_PORT, out->cap_tmu + 6, 1)) + return; + old = v; + v = (v & ~GENMASK(15, 0)) | TB_DP_APPLE_SSC_OFFSET; + if (v != old && + tb_port_write(out, &v, TB_CFG_PORT, out->cap_tmu + 6, 1)) { + tb_port_warn(out, "cannot set the DP SSC offset\n"); + return; + } + tb_port_dbg(out, "DP SSC offset %#x (was %#x)\n", v & 0xffff, old & 0xffff); +} + static int tb_dp_pre_activate(struct tb_tunnel *tunnel) { struct tb_port *in = tunnel->src_port; @@ -1326,6 +1357,8 @@ static int tb_dp_pre_activate(struct tb_tunnel *tunnel) struct tb *tb = in->sw->tb; int ret; + tb_dp_apple_ssc_offset(tunnel); + ret = tb_dp_xchg_caps(tunnel); if (ret) return ret; @@ -1609,8 +1642,9 @@ static int tb_dp_activate(struct tb_tunnel *tunnel, bool active) /* * Apple silicon host: the sink link is trained by the * host's DPTX through the tunnel; keep this DP OUT adapter - * from starting link training on its own, and hand it back - * once the tunnel is gone. + * from starting link training on its own. The bit is left + * as it is when the tunnel goes: the next tunnel to this + * adapter sets it again before enabling it. */ if (active && apple_dpin && !tb_port_read(out, &v, TB_CFG_PORT, out->cap_adap + ADP_DP_CS_3, 1)) { @@ -1619,13 +1653,6 @@ static int tb_dp_activate(struct tb_tunnel *tunnel, bool active) tb_port_warn(out, "Apple: cannot hold off DP link training\n"); } ret = tb_dp_port_enable(out, active); - /* hand link training back even if the disable failed */ - if (!active && apple_dpin && - !tb_port_read(out, &v, TB_CFG_PORT, out->cap_adap + ADP_DP_CS_3, 1)) { - v &= ~ADP_DP_CS_3_NO_AUTO_LT; - if (tb_port_write(out, &v, TB_CFG_PORT, out->cap_adap + ADP_DP_CS_3, 1)) - tb_port_warn(out, "Apple: cannot restore DP link training\n"); - } if (ret && active) return ret; }