Skip to content

Commit e88152f

Browse files
svenpeter42john-cabaj
authored andcommitted
mux: apple DP xbar: Add Apple silicon DisplayPort crossbar
This drivers adds support for the display crossbar used to route display controller streams to the three different modes (DP AltMode, USB4 Tunnel #0/#1) of the Type-C ports. Signed-off-by: Sven Peter <sven@svenpeter.dev> (cherry picked from commit 865f23f https://github.com/AsahiLinux/linux) Signed-off-by: John Cabaj <john.cabaj@canonical.com>
1 parent 00457ba commit e88152f

File tree

3 files changed

+320
-0
lines changed

3 files changed

+320
-0
lines changed

drivers/mux/Kconfig

+13
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ config MUX_ADGS1408
3131
To compile the driver as a module, choose M here: the module will
3232
be called mux-adgs1408.
3333

34+
config MUX_APPLE_DPXBAR
35+
tristate "Apple Silicon Display Crossbar"
36+
depends on ARCH_APPLE
37+
help
38+
Apple Silicon Display Crossbar multiplexer.
39+
40+
This drivers adds support for the display crossbar used to route
41+
display controller streams to the three different modes
42+
(DP AltMode, USB4 Tunnel #0/#1) of the Type-C ports.
43+
44+
To compile this driver as a module, chose M here: the module will be
45+
called mux-apple-display-crossbar.
46+
3447
config MUX_GPIO
3548
tristate "GPIO-controlled Multiplexer"
3649
depends on GPIOLIB || COMPILE_TEST

drivers/mux/Makefile

+2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ mux-adg792a-objs := adg792a.o
88
mux-adgs1408-objs := adgs1408.o
99
mux-gpio-objs := gpio.o
1010
mux-mmio-objs := mmio.o
11+
mux-apple-display-crossbar-objs := apple-display-crossbar.o
1112

1213
obj-$(CONFIG_MULTIPLEXER) += mux-core.o
1314
obj-$(CONFIG_MUX_ADG792A) += mux-adg792a.o
1415
obj-$(CONFIG_MUX_ADGS1408) += mux-adgs1408.o
16+
obj-$(CONFIG_MUX_APPLE_DPXBAR) += mux-apple-display-crossbar.o
1517
obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
1618
obj-$(CONFIG_MUX_MMIO) += mux-mmio.o

drivers/mux/apple-display-crossbar.c

+305
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
// SPDX-License-Identifier: GPL-2.0-only OR MIT
2+
/*
3+
* Apple Silicon Display Crossbar multiplexer driver
4+
*
5+
* Copyright (C) Asahi Linux Contributors
6+
*
7+
* Author: Sven Peter <sven@svenpeter.dev>
8+
*/
9+
10+
#include <linux/bitmap.h>
11+
#include <linux/delay.h>
12+
#include <linux/err.h>
13+
#include <linux/io.h>
14+
#include <linux/mod_devicetable.h>
15+
#include <linux/module.h>
16+
#include <linux/mux/driver.h>
17+
#include <linux/of.h>
18+
#include <linux/of_device.h>
19+
#include <linux/platform_device.h>
20+
21+
#define FIFO_WR_DPTX_CLK_EN 0x000
22+
#define FIFO_WR_N_CLK_EN 0x004
23+
#define FIFO_WR_UNK_EN 0x008
24+
#define FIFO_RD_PCLK1_EN 0x020
25+
#define FIFO_RD_PCLK2_EN 0x024
26+
#define FIFO_RD_N_CLK_EN 0x028
27+
#define FIFO_RD_UNK_EN 0x02c
28+
29+
#define OUT_PCLK1_EN 0x040
30+
#define OUT_PCLK2_EN 0x044
31+
#define OUT_N_CLK_EN 0x048
32+
#define OUT_UNK_EN 0x04c
33+
34+
#define CROSSBAR_DISPEXT_EN 0x050
35+
#define CROSSBAR_MUX_CTRL 0x060
36+
#define CROSSBAR_MUX_CTRL_DPPHY_SELECT0 GENMASK(23, 20)
37+
#define CROSSBAR_MUX_CTRL_DPIN1_SELECT0 GENMASK(19, 16)
38+
#define CROSSBAR_MUX_CTRL_DPIN0_SELECT0 GENMASK(15, 12)
39+
#define CROSSBAR_MUX_CTRL_DPPHY_SELECT1 GENMASK(11, 8)
40+
#define CROSSBAR_MUX_CTRL_DPIN1_SELECT1 GENMASK(7, 4)
41+
#define CROSSBAR_MUX_CTRL_DPIN0_SELECT1 GENMASK(3, 0)
42+
#define CROSSBAR_ATC_EN 0x070
43+
44+
#define FIFO_WR_DPTX_CLK_EN_STAT 0x800
45+
#define FIFO_WR_N_CLK_EN_STAT 0x804
46+
#define FIFO_RD_PCLK1_EN_STAT 0x820
47+
#define FIFO_RD_PCLK2_EN_STAT 0x824
48+
#define FIFO_RD_N_CLK_EN_STAT 0x828
49+
50+
#define OUT_PCLK1_EN_STAT 0x840
51+
#define OUT_PCLK2_EN_STAT 0x844
52+
#define OUT_N_CLK_EN_STAT 0x848
53+
54+
#define UNK_TUNABLE 0xc00
55+
56+
#define ATC_DPIN0 BIT(0)
57+
#define ATC_DPIN1 BIT(4)
58+
#define ATC_DPPHY BIT(8)
59+
60+
enum { MUX_DPPHY = 0, MUX_DPIN0 = 1, MUX_DPIN1 = 2, MUX_MAX = 3 };
61+
static const char *apple_dpxbar_names[MUX_MAX] = { "dpphy", "dpin0", "dpin1" };
62+
63+
struct apple_dpxbar_hw {
64+
unsigned int n_ufp;
65+
u32 tunable;
66+
};
67+
68+
struct apple_dpxbar {
69+
struct device *dev;
70+
void __iomem *regs;
71+
int selected_dispext[MUX_MAX];
72+
spinlock_t lock;
73+
};
74+
75+
static inline void dpxbar_mask32(struct apple_dpxbar *xbar, u32 reg, u32 mask,
76+
u32 set)
77+
{
78+
u32 value = readl(xbar->regs + reg);
79+
value &= ~mask;
80+
value |= set;
81+
writel(value, xbar->regs + reg);
82+
}
83+
84+
static inline void dpxbar_set32(struct apple_dpxbar *xbar, u32 reg, u32 set)
85+
{
86+
dpxbar_mask32(xbar, reg, 0, set);
87+
}
88+
89+
static inline void dpxbar_clear32(struct apple_dpxbar *xbar, u32 reg, u32 clear)
90+
{
91+
dpxbar_mask32(xbar, reg, clear, 0);
92+
}
93+
94+
static int apple_dpxbar_set(struct mux_control *mux, int state)
95+
{
96+
struct apple_dpxbar *dpxbar = mux_chip_priv(mux->chip);
97+
unsigned int index = mux_control_get_index(mux);
98+
unsigned long flags;
99+
unsigned int mux_state;
100+
unsigned int dispext_bit;
101+
unsigned int atc_bit;
102+
bool enable;
103+
int ret = 0;
104+
u32 mux_mask, mux_set;
105+
106+
if (state == MUX_IDLE_DISCONNECT) {
107+
/*
108+
* Technically this will select dispext0,0 in the mux control
109+
* register. Practically that doesn't matter since everything
110+
* else is disabled.
111+
*/
112+
mux_state = 0;
113+
enable = false;
114+
} else if (state >= 0 && state < 9) {
115+
dispext_bit = 1 << state;
116+
mux_state = state;
117+
enable = true;
118+
} else {
119+
return -EINVAL;
120+
}
121+
122+
switch (index) {
123+
case MUX_DPPHY:
124+
mux_mask = CROSSBAR_MUX_CTRL_DPPHY_SELECT0 |
125+
CROSSBAR_MUX_CTRL_DPPHY_SELECT1;
126+
mux_set =
127+
FIELD_PREP(CROSSBAR_MUX_CTRL_DPPHY_SELECT0, mux_state) |
128+
FIELD_PREP(CROSSBAR_MUX_CTRL_DPPHY_SELECT1, mux_state);
129+
atc_bit = ATC_DPPHY;
130+
break;
131+
case MUX_DPIN0:
132+
mux_mask = CROSSBAR_MUX_CTRL_DPIN0_SELECT0 |
133+
CROSSBAR_MUX_CTRL_DPIN0_SELECT1;
134+
mux_set =
135+
FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN0_SELECT0, mux_state) |
136+
FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN0_SELECT1, mux_state);
137+
atc_bit = ATC_DPIN0;
138+
break;
139+
case MUX_DPIN1:
140+
mux_mask = CROSSBAR_MUX_CTRL_DPIN1_SELECT0 |
141+
CROSSBAR_MUX_CTRL_DPIN1_SELECT1;
142+
mux_set =
143+
FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN1_SELECT0, mux_state) |
144+
FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN1_SELECT1, mux_state);
145+
atc_bit = ATC_DPIN1;
146+
break;
147+
default:
148+
return -EINVAL;
149+
}
150+
151+
spin_lock_irqsave(&dpxbar->lock, flags);
152+
153+
/* ensure the selected dispext isn't already used in this crossbar */
154+
if (enable) {
155+
for (int i = 0; i < MUX_MAX; ++i) {
156+
if (i == index)
157+
continue;
158+
if (dpxbar->selected_dispext[i] == state) {
159+
spin_unlock_irqrestore(&dpxbar->lock, flags);
160+
return -EBUSY;
161+
}
162+
}
163+
}
164+
165+
dpxbar_set32(dpxbar, OUT_N_CLK_EN, atc_bit);
166+
dpxbar_clear32(dpxbar, OUT_UNK_EN, atc_bit);
167+
dpxbar_clear32(dpxbar, OUT_PCLK1_EN, atc_bit);
168+
dpxbar_clear32(dpxbar, CROSSBAR_ATC_EN, atc_bit);
169+
170+
if (dpxbar->selected_dispext[index] >= 0) {
171+
u32 prev_dispext_bit = 1 << dpxbar->selected_dispext[index];
172+
173+
dpxbar_set32(dpxbar, FIFO_WR_N_CLK_EN, prev_dispext_bit);
174+
dpxbar_set32(dpxbar, FIFO_RD_N_CLK_EN, prev_dispext_bit);
175+
dpxbar_clear32(dpxbar, FIFO_WR_UNK_EN, prev_dispext_bit);
176+
dpxbar_clear32(dpxbar, FIFO_RD_UNK_EN, prev_dispext_bit);
177+
dpxbar_clear32(dpxbar, FIFO_WR_DPTX_CLK_EN, prev_dispext_bit);
178+
dpxbar_clear32(dpxbar, FIFO_RD_PCLK1_EN, prev_dispext_bit);
179+
dpxbar_clear32(dpxbar, CROSSBAR_DISPEXT_EN, prev_dispext_bit);
180+
181+
dpxbar->selected_dispext[index] = -1;
182+
}
183+
184+
dpxbar_mask32(dpxbar, CROSSBAR_MUX_CTRL, mux_mask, mux_set);
185+
186+
if (enable) {
187+
dpxbar_clear32(dpxbar, FIFO_WR_N_CLK_EN, dispext_bit);
188+
dpxbar_clear32(dpxbar, FIFO_RD_N_CLK_EN, dispext_bit);
189+
dpxbar_clear32(dpxbar, OUT_N_CLK_EN, atc_bit);
190+
dpxbar_set32(dpxbar, FIFO_WR_UNK_EN, dispext_bit);
191+
dpxbar_set32(dpxbar, FIFO_RD_UNK_EN, dispext_bit);
192+
dpxbar_set32(dpxbar, OUT_UNK_EN, atc_bit);
193+
dpxbar_set32(dpxbar, FIFO_WR_DPTX_CLK_EN, dispext_bit);
194+
dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit);
195+
dpxbar_set32(dpxbar, OUT_PCLK1_EN, atc_bit);
196+
dpxbar_set32(dpxbar, CROSSBAR_ATC_EN, atc_bit);
197+
dpxbar_set32(dpxbar, CROSSBAR_DISPEXT_EN, dispext_bit);
198+
199+
/*
200+
* Work around some HW quirk:
201+
* Without toggling the RD_PCLK enable here the connection
202+
* doesn't come up. Testing has shown that a delay of about
203+
* 5 usec is required which is doubled here to be on the
204+
* safe side.
205+
*/
206+
dpxbar_clear32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit);
207+
udelay(10);
208+
dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit);
209+
210+
dpxbar->selected_dispext[index] = state;
211+
}
212+
213+
spin_unlock_irqrestore(&dpxbar->lock, flags);
214+
215+
if (enable)
216+
dev_info(dpxbar->dev, "Switched %s to dispext%u,%u\n",
217+
apple_dpxbar_names[index], mux_state >> 1,
218+
mux_state & 1);
219+
else
220+
dev_info(dpxbar->dev, "Switched %s to disconnected state\n",
221+
apple_dpxbar_names[index]);
222+
223+
return ret;
224+
}
225+
226+
static const struct mux_control_ops apple_dpxbar_ops = {
227+
.set = apple_dpxbar_set,
228+
};
229+
230+
static int apple_dpxbar_probe(struct platform_device *pdev)
231+
{
232+
struct device *dev = &pdev->dev;
233+
struct mux_chip *mux_chip;
234+
struct apple_dpxbar *dpxbar;
235+
const struct apple_dpxbar_hw *hw;
236+
int ret;
237+
238+
hw = of_device_get_match_data(dev);
239+
mux_chip = devm_mux_chip_alloc(dev, MUX_MAX, sizeof(*dpxbar));
240+
if (IS_ERR(mux_chip))
241+
return PTR_ERR(mux_chip);
242+
243+
dpxbar = mux_chip_priv(mux_chip);
244+
mux_chip->ops = &apple_dpxbar_ops;
245+
spin_lock_init(&dpxbar->lock);
246+
247+
dpxbar->dev = dev;
248+
dpxbar->regs = devm_platform_ioremap_resource(pdev, 0);
249+
if (IS_ERR(dpxbar->regs))
250+
return PTR_ERR(dpxbar->regs);
251+
252+
writel(hw->tunable, dpxbar->regs + UNK_TUNABLE);
253+
254+
for (unsigned int i = 0; i < MUX_MAX; ++i) {
255+
mux_chip->mux[i].states = hw->n_ufp;
256+
mux_chip->mux[i].idle_state = MUX_IDLE_DISCONNECT;
257+
dpxbar->selected_dispext[i] = -1;
258+
}
259+
260+
ret = devm_mux_chip_register(dev, mux_chip);
261+
if (ret < 0)
262+
return ret;
263+
264+
return 0;
265+
}
266+
267+
const static struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = {
268+
.n_ufp = 2,
269+
.tunable = 0,
270+
};
271+
272+
const static struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = {
273+
.n_ufp = 9,
274+
.tunable = 5,
275+
};
276+
277+
static const struct of_device_id apple_dpxbar_ids[] = {
278+
{
279+
.compatible = "apple,t8103-display-crossbar",
280+
.data = &apple_dpxbar_hw_t8103,
281+
},
282+
{
283+
.compatible = "apple,t8112-display-crossbar",
284+
.data = &apple_dpxbar_hw_t8103,
285+
},
286+
{
287+
.compatible = "apple,t6000-display-crossbar",
288+
.data = &apple_dpxbar_hw_t6000,
289+
},
290+
{}
291+
};
292+
MODULE_DEVICE_TABLE(of, apple_dpxbar_ids);
293+
294+
static struct platform_driver apple_dpxbar_driver = {
295+
.driver = {
296+
.name = "apple-display-crossbar",
297+
.of_match_table = apple_dpxbar_ids,
298+
},
299+
.probe = apple_dpxbar_probe,
300+
};
301+
module_platform_driver(apple_dpxbar_driver);
302+
303+
MODULE_DESCRIPTION("Apple Silicon display crossbar multiplexer driver");
304+
MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>");
305+
MODULE_LICENSE("GPL v2");

0 commit comments

Comments
 (0)