Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 54 additions & 15 deletions arch/arm64/boot/dts/apple/t6041.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@
/delete-node/ &jpeg0_dart;
/delete-node/ &jpeg1_dart;
/delete-node/ &scaler0_dart;
/delete-node/ &usb4_0_mbox;
/delete-node/ &usb4_1_mbox;
/delete-node/ &usb4_2_mbox;
/delete-node/ &sio_mbox;
/delete-node/ &pinctrl_smc;
/delete-node/ &ps_gpx;
Expand Down Expand Up @@ -128,19 +125,7 @@
/delete-node/ &ps_dispext0_fe;
/delete-node/ &ps_dispext0_cpu;
/delete-node/ &ps_pcpu0_3;
/delete-node/ &ps_atc0_cio;
/delete-node/ &ps_atc0_pcie;
/delete-node/ &ps_atc0_cio_pcie;
/delete-node/ &ps_atc0_cio_usb;
/delete-node/ &ps_atc1_cio;
/delete-node/ &ps_atc1_pcie;
/delete-node/ &ps_atc1_cio_pcie;
/delete-node/ &ps_atc1_cio_usb;
/delete-node/ &ps_atc2_cio;
/delete-node/ &ps_atc2_pcie;
/delete-node/ &ps_atc2_cio_pcie;
/delete-node/ &ps_pcpu0_4;
/delete-node/ &ps_atc2_cio_usb;
/delete-node/ &ps_atc3_cio;
/delete-node/ &ps_atc3_pcie;
/delete-node/ &ps_atc3_cio_pcie;
Expand Down Expand Up @@ -358,6 +343,60 @@
/delete-node/ &opp_p0;
/delete-node/ &opp_p1;

#include "t6040-usb4.dtsi"

/ {
/* ADT pmgr reg[44]: AppleT6041PMGR::enableCioReconfig at +0x2024. */
pmgr_cio: power-management@502820000 {
compatible = "apple,t6041-pmgr-cio", "syscon", "simple-mfd";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x5 0x02820000 0 0x4000>;

cio_reset: reset-controller@2024 {
compatible = "apple,t6041-cio-reset", "apple,t6000-cio-reset";
reg = <0x2024 0x4>;
#reset-cells = <1>;
};
};
};

&pinctrl_aop {
/* ADT acioN gpio-lstx. */
usb4_0_lstx_pins: usb4-0-lstx-pins {
pinmux = <APPLE_PINMUX(49, 1)>;
};

usb4_1_lstx_pins: usb4-1-lstx-pins {
pinmux = <APPLE_PINMUX(51, 1)>;
};

usb4_2_lstx_pins: usb4-2-lstx-pins {
pinmux = <APPLE_PINMUX(53, 1)>;
};
};

&usb4_0_acio {
resets = <&cio_reset 0>;
pinctrl-names = "lstx";
pinctrl-0 = <&usb4_0_lstx_pins>;
lstx-gpios = <&pinctrl_aop 49 GPIO_ACTIVE_HIGH>;
};

&usb4_1_acio {
resets = <&cio_reset 1>;
pinctrl-names = "lstx";
pinctrl-0 = <&usb4_1_lstx_pins>;
lstx-gpios = <&pinctrl_aop 51 GPIO_ACTIVE_HIGH>;
};

&usb4_2_acio {
resets = <&cio_reset 2>;
pinctrl-names = "lstx";
pinctrl-0 = <&usb4_2_lstx_pins>;
lstx-gpios = <&pinctrl_aop 53 GPIO_ACTIVE_HIGH>;
};

/ {
opp_p0: opp-table-p0 {
compatible = "operating-points-v2";
Expand Down
214 changes: 193 additions & 21 deletions drivers/phy/apple/t6040-usb2.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
#include <linux/usb/typec_mux.h>
#include <linux/usb/typec_altmode.h>
#include <linux/usb/typec_dp.h>
#include <linux/usb/typec_tbt.h>
#include <linux/usb/pd_vdo.h>

#define T6040_USB2_USBCTL 0x00
#define T6040_USB2_USBCTL_MODE GENMASK(2, 0)
Expand Down Expand Up @@ -120,7 +122,10 @@
#define T6040_CORE_CMN_OVRD_SLEEP_B_BIG BIT(0)
#define T6040_CORE_CMN_OVRD_SLEEP_B_SML BIT(2)
#define T6040_CORE_CMN_OVRD_CLAMP BIT(4)
#define T6040_CORE_LANE_RX 0x10 /* CFG1_TOP */
#define T6040_CORE_SLP_CTRL 0x120 /* CFG_SLP_CTRL */
#define T6040_CORE_LN0_RX_TOP_CIO 0x935c
#define T6040_CORE_LN1_RX_TOP_CIO 0x1935c
#define T6040_CORE_CMN_BIAS 0xa00 /* AUS40CMN_SHM CMN_BIAS_REG */
#define T6040_CORE_CMN_BIAS_BGBIAS_EN_OV BIT(1)
#define T6040_CORE_UC_CTRL 0x260 /* ACIOPHY_TOP_BLK_UC_CTRL_REG */
Expand Down Expand Up @@ -241,6 +246,7 @@ struct t6040_usb2_phy {
bool usb3_pipe; /* dwc3 asked for the PIPE switch this power cycle */
bool usb3_lent; /* DP mode took the lanes of this Type-C port */
bool pipe_switched; /* the controller's PIPE runs on this PHY */
bool cio_up; /* the lanes run USB4/Thunderbolt */

/* DisplayPort; dp_ok is false when the DT lacks a DP route */
struct phy *dp;
Expand Down Expand Up @@ -598,14 +604,15 @@ static int t6040_usb3_cpu_start(struct t6040_usb2_phy *tphy)
* every detach; each start makes the firmware tune the PHY. Then release the
* PHY reset (D).
*/
static int t6040_usb3_run_firmware(struct t6040_usb2_phy *tphy, bool swap)
static int t6040_usb3_run_firmware(struct t6040_usb2_phy *tphy, bool usb, bool swap)
{
void __iomem *cpu = t6040_cpu(tphy);
u32 uc_before;
int ret;

t6040_usb2_mask32(t6040_cfg(tphy) + T6040_CFG_SPARE0, T6040_CFG_SPARE0_USB_LN_SWAP,
swap ? T6040_CFG_SPARE0_USB_LN_SWAP : 0);
if (usb)
t6040_usb2_mask32(t6040_cfg(tphy) + T6040_CFG_SPARE0, T6040_CFG_SPARE0_USB_LN_SWAP,
swap ? T6040_CFG_SPARE0_USB_LN_SWAP : 0);
fsleep(20);
t6040_usb2_set32(cpu + T6040_CPU_IDLE_CTRL, T6040_CPU_IDLE_CTRL_EN);
fsleep(10);
Expand Down Expand Up @@ -750,24 +757,12 @@ static void t6040_usb3_bist_off(struct t6040_usb2_phy *tphy)
* aciophy_phy_shutdown_partA: the PHY's USB link side down (the same BIST
* handshake as the PIPE switch), both TX lanes, then the common block.
*/
static void t6040_usb3_shutdown_lanes(struct t6040_usb2_phy *tphy)
/* aciophy_tx_powerdown(0), (1), then aciophy_cmn_shutdown: USB and CIO alike. */
static void t6040_phy_tx_cmn_down(struct t6040_usb2_phy *tphy)
{
void __iomem *bist = t6040_bist(tphy);
void __iomem *core = t6040_core(tphy);
unsigned int lane;

t6040_usb2_set32(bist + T6040_BIST_CFG0, BIT(0));
t6040_usb2_clear32(bist + T6040_BIST_OVRD0, 0x100000);
t6040_usb2_mask32(bist + T6040_BIST_OVRD1, 0x60, 0x20);
t6040_usb3_bist_pulse(tphy, T6040_BIST_CFG0, BIT(3));
t6040_usb3_bist_wait(tphy, T6040_BIST_STATUS_PHY_2, true, 1000);
t6040_usb2_mask32(bist + T6040_BIST_OVRD0, ~0xe1ffffffu, 0x4000000);
t6040_usb2_mask32(bist + T6040_BIST_OVRD1, 0x600, 0x200);
t6040_usb3_bist_pulse(tphy, T6040_BIST_CFG0, BIT(3));
t6040_usb3_bist_wait(tphy, T6040_BIST_STATUS_PHY_2, true, 1000);
t6040_usb2_clear32(bist + T6040_BIST_CFG0, BIT(0));

/* aciophy_tx_powerdown(0), (1) */
for (lane = 0; lane < 2; lane++) {
t6040_usb2_set32(core + T6040_CORE_SLP_CTRL, 0x100 << lane);
udelay(1);
Expand All @@ -786,6 +781,25 @@ static void t6040_usb3_shutdown_lanes(struct t6040_usb2_phy *tphy)
udelay(1);
t6040_usb2_clear32(core + T6040_CORE_CMN_OVRD, T6040_CORE_CMN_OVRD_SLEEP_B_SML);
udelay(1);
}


static void t6040_usb3_shutdown_lanes(struct t6040_usb2_phy *tphy)
{
void __iomem *bist = t6040_bist(tphy);

t6040_usb2_set32(bist + T6040_BIST_CFG0, BIT(0));
t6040_usb2_clear32(bist + T6040_BIST_OVRD0, 0x100000);
t6040_usb2_mask32(bist + T6040_BIST_OVRD1, 0x60, 0x20);
t6040_usb3_bist_pulse(tphy, T6040_BIST_CFG0, BIT(3));
t6040_usb3_bist_wait(tphy, T6040_BIST_STATUS_PHY_2, true, 1000);
t6040_usb2_mask32(bist + T6040_BIST_OVRD0, ~0xe1ffffffu, 0x4000000);
t6040_usb2_mask32(bist + T6040_BIST_OVRD1, 0x600, 0x200);
t6040_usb3_bist_pulse(tphy, T6040_BIST_CFG0, BIT(3));
t6040_usb3_bist_wait(tphy, T6040_BIST_STATUS_PHY_2, true, 1000);
t6040_usb2_clear32(bist + T6040_BIST_CFG0, BIT(0));

t6040_phy_tx_cmn_down(tphy);
fsleep(200);
}

Expand Down Expand Up @@ -896,8 +910,8 @@ static void t6040_usb3_up_locked(struct t6040_usb2_phy *tphy)
lockdep_assert_held(&tphy->lock);
if (!tphy->usb3_ok || tphy->usb3_up)
return;
/* DP mode owns all four lanes; USB3 comes back in t6040_usb3_reclaim_locked(). */
if (tphy->dp_up) {
/* DP and CIO own all four lanes; USB3 comes back in t6040_usb3_reclaim_locked(). */
if (tphy->dp_up || tphy->cio_up) {
dev_dbg(tphy->dev, "USB3: lanes held by DP, USB2 only for now\n");
return;
}
Expand All @@ -922,7 +936,7 @@ static void t6040_usb3_up_locked(struct t6040_usb2_phy *tphy)
goto sleep; /* the coprocessor block may be unclocked: never touch it */
t6040_usb3_tunables(tphy);
t6040_usb3_lanes(tphy, swap);
ret = t6040_usb3_run_firmware(tphy, swap);
ret = t6040_usb3_run_firmware(tphy, true, swap);
if (ret)
goto usb2_only;

Expand Down Expand Up @@ -1409,7 +1423,7 @@ static void t6040_dp_drive(struct t6040_usb2_phy *tphy, unsigned int lane, u32 p
static void t6040_usb3_reclaim_locked(struct t6040_usb2_phy *tphy)
{
lockdep_assert_held(&tphy->lock);
if (!tphy->usb3_lent || tphy->dp_up || tphy->dp_alt)
if (!tphy->usb3_lent || tphy->dp_up || tphy->dp_alt || tphy->cio_up)
return;
tphy->usb3_lent = false;
if (!tphy->usb3_on || tphy->usb3_up)
Expand All @@ -1422,6 +1436,156 @@ static void t6040_usb3_reclaim_locked(struct t6040_usb2_phy *tphy)
dev_info(tphy->dev, "USB3 %s after DP\n", tphy->usb3_up ? "back" : "not restored");
}

/* ---- CIO: USB4 and Thunderbolt on both lanes ---- */

/* The ADT tables of the common set, then the CIO ones; no DPTX writes. */
static void t6040_cio_tunables(struct t6040_usb2_phy *tphy)
{
void __iomem *core = t6040_core(tphy);
size_t i;

for (i = 0; i < T6040_USB3_NR_TUNABLES; i++)
apple_tunable_apply(tphy->regs[t6040_usb3_tunable_list[i].region],
tphy->tunables[i]);
/* tunable_LN0/1_RX_TOP_CIO_DFLT; UC_REGS_CIO and RX_EQ_CIO are empty. */
t6040_usb2_clear32(core + T6040_CORE_LN0_RX_TOP_CIO, 0xe00);
t6040_usb2_clear32(core + T6040_CORE_LN1_RX_TOP_CIO, 0xe00);
}

/* aciophy_xbar_config, mode 2: orientation is one crossbar bit. */
static void t6040_cio_xbar(struct t6040_usb2_phy *tphy, bool flip)
{
void __iomem *core = t6040_core(tphy);

t6040_usb2_clear32(core + T6040_CORE_XBAR, 0xfc);
t6040_usb2_mask32(core + T6040_CORE_XBAR_LANES, 0x1f, flip ? 1 : 0);
t6040_usb2_mask32(core + T6040_CORE_XBAR, 0x3, 0x2);
}

/* aciophy_slpctrl_init, mode 2: both lanes, whatever the orientation. */
static void t6040_cio_lanes_on(struct t6040_usb2_phy *tphy)
{
static const struct { u32 off, bit; bool set; } steps[] = {
{ T6040_CORE_LANE_RX, 0x10, true }, { T6040_CORE_LANE_RX, 0x40, true },
{ T6040_CORE_LANE_RX, 0x1, true }, { T6040_CORE_LANE_RX, 0x4, true },
{ T6040_CORE_CMN_OVRD, 0x400, true }, { T6040_CORE_CMN_OVRD, 0x1000, true },
{ T6040_CORE_CMN_OVRD, 0x40, true }, { T6040_CORE_CMN_OVRD, 0x100, true },
{ T6040_CORE_CMN_OVRD, 0x4000, false }, { T6040_CORE_CMN_OVRD, 0x10000, true },
{ T6040_CORE_SLP_CTRL, 0x40, false }, { T6040_CORE_SLP_CTRL, 0x4, false },
{ T6040_CORE_SLP_CTRL, 0x400, false },
};
void __iomem *core = t6040_core(tphy);
unsigned int lane;
size_t i;

t6040_usb2_clear32(core + T6040_CORE_CMN_OVRD, 0x2a);
udelay(1);
for (lane = 0; lane < 2; lane++) {
for (i = 0; i < ARRAY_SIZE(steps); i++) {
if (steps[i].set)
t6040_usb2_set32(core + steps[i].off, steps[i].bit << lane);
else
t6040_usb2_clear32(core + steps[i].off, steps[i].bit << lane);
udelay(1);
}
}
}

/* aciophy_rx_powerdown(0), (1): CIO powers the receivers down, USB does not. */
static void t6040_cio_rx_down(struct t6040_usb2_phy *tphy)
{
void __iomem *core = t6040_core(tphy);
unsigned int lane;

for (lane = 0; lane < 2; lane++) {
t6040_usb2_set32(core + T6040_CORE_CMN_OVRD, 0x4000 << lane);
udelay(1);
t6040_usb2_clear32(core + T6040_CORE_CMN_OVRD, 0x40 << lane);
udelay(1);
t6040_usb2_clear32(core + T6040_CORE_CMN_OVRD, 0x400 << lane);
udelay(1);
t6040_usb2_clear32(core + T6040_CORE_LANE_RX, 0x1 << lane);
udelay(1);
t6040_usb2_clear32(core + T6040_CORE_LANE_RX, 0x10 << lane);
udelay(1);
}
}

/* The Type-C state that needs CIO: USB4 entered, or the Thunderbolt alt mode. */
static bool t6040_cio_state(const struct typec_mux_state *state)
{
if (state->alt)
return state->alt->svid == USB_TYPEC_TBT_SID;
if (state->mode != TYPEC_MODE_USB4 || !state->data)
return false;
return FIELD_GET(EUDO_USB_MODE_MASK,
((const struct enter_usb_data *)state->data)->eudo) == EUDO_USB_MODE_USB4;
}

static int t6040_cio_up_locked(struct t6040_usb2_phy *tphy)
{
bool flip;
int ret;

lockdep_assert_held(&tphy->lock);
if (!tphy->usb3_ok || tphy->dp_up)
return -EBUSY;
switch (tphy->orientation) {
case TYPEC_ORIENTATION_NORMAL:
flip = false;
break;
case TYPEC_ORIENTATION_REVERSE:
flip = true;
break;
default:
dev_warn(tphy->dev, "CIO: no plug orientation\n");
return -EINVAL;
}
if (tphy->usb3_up || tphy->pipe_switched)
t6040_usb3_down_locked(tphy);
tphy->usb3_lent = tphy->usb3_on;

ret = pm_runtime_resume_and_get(tphy->dev);
if (ret)
return ret;
ret = t6040_usb3_wake(tphy);
if (ret)
goto sleep; /* the coprocessor block may be unclocked: never touch it */
t6040_cio_tunables(tphy);
t6040_cio_xbar(tphy, flip);
t6040_cio_lanes_on(tphy);
ret = t6040_usb3_run_firmware(tphy, false, flip);
if (ret) {
t6040_usb3_firmware_power_down(tphy);
goto sleep;
}
tphy->cio_up = true;
dev_info(tphy->dev, "CIO PHY up, %s orientation\n", flip ? "reverse" : "normal");
return 0;

sleep:
t6040_usb3_sleep(tphy);
pm_runtime_put(tphy->dev);
dev_warn(tphy->dev, "CIO PHY bring-up failed: %d\n", ret);
return ret;
}

static void t6040_cio_down_locked(struct t6040_usb2_phy *tphy)
{
lockdep_assert_held(&tphy->lock);
if (!tphy->cio_up)
return;
t6040_phy_tx_cmn_down(tphy);
t6040_cio_rx_down(tphy);
fsleep(200);
t6040_usb3_firmware_power_down(tphy);
t6040_usb3_sleep(tphy);
tphy->cio_up = false;
pm_runtime_put(tphy->dev);
dev_info(tphy->dev, "CIO PHY asleep\n");
t6040_usb3_reclaim_locked(tphy);
}

/* ---- DP phy_ops ---- */

static int t6040_dp_power_on(struct phy *phy)
Expand All @@ -1432,6 +1596,8 @@ static int t6040_dp_power_on(struct phy *phy)
guard(mutex)(&tphy->lock);
if (!tphy->dp_ok || tphy->dp_up)
return 0;
if (tphy->cio_up)
return -EBUSY;
/* Pin D shares the running PHY with USB3. Keep its PIPE and firmware. */
if (tphy->dp_two_lanes) {
if (!tphy->usb3_ok || !tphy->dp_alt)
Expand Down Expand Up @@ -1581,6 +1747,12 @@ static int t6040_dp_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *s
bool alt = false;

guard(mutex)(&tphy->lock);
if (t6040_cio_state(state)) {
if (tphy->cio_up)
return 0;
return t6040_cio_up_locked(tphy);
}
t6040_cio_down_locked(tphy);
tphy->dp_flip = (tphy->orientation == TYPEC_ORIENTATION_REVERSE);
/*
* Latch the DP alt mode state for the DCP's Type-C route. Pin
Expand Down