Message ID | 20241112-x1e80100-ps8830-v5-2-4ad83af4d162@linaro.org |
---|---|
State | New |
Headers | show |
Series | usb: typec: Add new driver for Parade PS8830 Type-C Retimer | expand |
On Tue, Nov 12, 2024 at 07:01:11PM +0200, Abel Vesa wrote: > The Parade PS8830 is a USB4, DisplayPort and Thunderbolt 4 retimer, > controlled over I2C. It usually sits between a USB/DisplayPort PHY > and the Type-C connector, and provides orientation and altmode handling. > > The boards that use this retimer are the ones featuring the Qualcomm > Snapdragon X Elite SoCs. > +static int ps883x_sw_set(struct typec_switch_dev *sw, > + enum typec_orientation orientation) > +{ > + struct ps883x_retimer *retimer = typec_switch_get_drvdata(sw); > + int ret = 0; > + > + ret = typec_switch_set(retimer->typec_switch, orientation); > + if (ret) > + return ret; > + > + mutex_lock(&retimer->lock); > + > + if (retimer->orientation != orientation) { > + retimer->orientation = orientation; > + > + ret = ps883x_set(retimer); > + } > + > + mutex_unlock(&retimer->lock); > + > + return ret; > +} This seems to indicate a bigger problem, but I see this function called during early resume while the i2c controller is suspended: [ 54.213900] ------------[ cut here ]------------ [ 54.213942] i2c i2c-2: Transfer while suspended [ 54.214125] WARNING: CPU: 0 PID: 126 at drivers/i2c/i2c-core.h:56 __i2c_transfer+0x874/0x968 [i2c_core] ... [ 54.214833] CPU: 0 UID: 0 PID: 126 Comm: kworker/0:2 Not tainted 6.13.0-rc1 #11 [ 54.214844] Hardware name: Qualcomm CRD, BIOS 6.0.231221.BOOT.MXF.2.4-00348.1-HAMOA-1 12/21/2023 [ 54.214852] Workqueue: events pmic_glink_altmode_worker [pmic_glink_altmode] ... [ 54.215090] Call trace: [ 54.215097] __i2c_transfer+0x874/0x968 [i2c_core] (P) [ 54.215112] __i2c_transfer+0x874/0x968 [i2c_core] (L) [ 54.215126] i2c_transfer+0x94/0xf0 [i2c_core] [ 54.215140] i2c_transfer_buffer_flags+0x5c/0x90 [i2c_core] [ 54.215153] regmap_i2c_write+0x20/0x58 [regmap_i2c] [ 54.215166] _regmap_raw_write_impl+0x740/0x894 [ 54.215184] _regmap_bus_raw_write+0x60/0x7c [ 54.215192] _regmap_write+0x60/0x1b4 [ 54.215200] regmap_write+0x4c/0x78 [ 54.215207] ps883x_set+0xb0/0x10c [ps883x] [ 54.215219] ps883x_sw_set+0x74/0x98 [ps883x] [ 54.215227] typec_switch_set+0x58/0x90 [typec] [ 54.215248] pmic_glink_altmode_worker+0x3c/0x23c [pmic_glink_altmode] [ 54.215257] process_one_work+0x20c/0x610 [ 54.215274] worker_thread+0x23c/0x378 [ 54.215283] kthread+0x124/0x128 [ 54.215291] ret_from_fork+0x10/0x20 [ 54.215303] irq event stamp: 28140 [ 54.215309] hardirqs last enabled at (28139): [<ffffd15e3bc2a434>] __up_console_sem+0x6c/0x80 [ 54.215325] hardirqs last disabled at (28140): [<ffffd15e3c596aa4>] el1_dbg+0x24/0x8c [ 54.215341] softirqs last enabled at (28120): [<ffffd15e3bb9b82c>] handle_softirqs+0x4c4/0x4dc [ 54.215355] softirqs last disabled at (27961): [<ffffd15e3bb501ec>] __do_softirq+0x14/0x20 [ 54.215363] ---[ end trace 0000000000000000 ]--- [ 54.216889] Enabling non-boot CPUs ... This can be reproduced on the CRD (or T14s) by disconnecting, for example, a mass storage device while the laptop is suspended. > +static int ps883x_retimer_set(struct typec_retimer *rtmr, > + struct typec_retimer_state *state) > +{ > + struct ps883x_retimer *retimer = typec_retimer_get_drvdata(rtmr); > + struct typec_mux_state mux_state; > + int ret = 0; > + > + mutex_lock(&retimer->lock); > + > + if (state->mode != retimer->mode) { > + retimer->mode = state->mode; > + > + if (state->alt) > + retimer->svid = state->alt->svid; > + else > + retimer->svid = 0; // No SVID Nit: I'd prefer if you avoid c99 comments for consistency. > + ret = ps883x_set(retimer); > + } > + > + mutex_unlock(&retimer->lock); > + > + if (ret) > + return ret; > + > + mux_state.alt = state->alt; > + mux_state.data = state->data; > + mux_state.mode = state->mode; > + > + return typec_mux_set(retimer->typec_mux, &mux_state); > +} > +static int ps883x_retimer_probe(struct i2c_client *client) > +{ > + struct device *dev = &client->dev; > + struct typec_switch_desc sw_desc = { }; > + struct typec_retimer_desc rtmr_desc = { }; > + struct ps883x_retimer *retimer; > + int ret; > + > + retimer = devm_kzalloc(dev, sizeof(*retimer), GFP_KERNEL); > + if (!retimer) > + return -ENOMEM; > + > + retimer->client = client; > + > + mutex_init(&retimer->lock); > + > + retimer->regmap = devm_regmap_init_i2c(client, &ps883x_retimer_regmap); > + if (IS_ERR(retimer->regmap)) > + return dev_err_probe(dev, PTR_ERR(retimer->regmap), > + "failed to allocate register map\n"); > + > + ret = ps883x_get_vregs(retimer); > + if (ret) > + return ret; > + > + retimer->xo_clk = devm_clk_get(dev, NULL); > + if (IS_ERR(retimer->xo_clk)) > + return dev_err_probe(dev, PTR_ERR(retimer->xo_clk), > + "failed to get xo clock\n"); > + > + retimer->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_ASIS); GPIOD_ASIS is documented as requiring you to later set the direction, but this does not happen unconditionally below. > + if (IS_ERR(retimer->reset_gpio)) > + return dev_err_probe(dev, PTR_ERR(retimer->reset_gpio), > + "failed to get reset gpio\n"); > + > + retimer->typec_switch = typec_switch_get(dev); > + if (IS_ERR(retimer->typec_switch)) > + return dev_err_probe(dev, PTR_ERR(retimer->typec_switch), > + "failed to acquire orientation-switch\n"); > + > + retimer->typec_mux = typec_mux_get(dev); > + if (IS_ERR(retimer->typec_mux)) { > + ret = dev_err_probe(dev, PTR_ERR(retimer->typec_mux), > + "failed to acquire mode-mux\n"); > + goto err_switch_put; > + } > + > + ret = drm_aux_bridge_register(dev); > + if (ret) > + goto err_mux_put; > + > + ret = ps883x_enable_vregs(retimer); > + if (ret) > + goto err_mux_put; > + > + ret = clk_prepare_enable(retimer->xo_clk); > + if (ret) { > + dev_err(dev, "failed to enable XO: %d\n", ret); > + goto err_vregs_disable; > + } > + > + sw_desc.drvdata = retimer; > + sw_desc.fwnode = dev_fwnode(dev); > + sw_desc.set = ps883x_sw_set; > + > + retimer->sw = typec_switch_register(dev, &sw_desc); > + if (IS_ERR(retimer->sw)) { > + ret = dev_err_probe(dev, PTR_ERR(retimer->sw), > + "failed to register typec switch\n"); > + goto err_clk_disable; > + } > + > + rtmr_desc.drvdata = retimer; > + rtmr_desc.fwnode = dev_fwnode(dev); > + rtmr_desc.set = ps883x_retimer_set; > + > + retimer->retimer = typec_retimer_register(dev, &rtmr_desc); > + if (IS_ERR(retimer->retimer)) { > + ret = dev_err_probe(dev, PTR_ERR(retimer->sw), > + "failed to register typec retimer\n"); > + goto err_switch_unregister; > + } The registration functions do not return -EPROBE_DEFER so I'd prefer if you switch back to dev_err() here as we already discussed. A driver must not probe defer after having registered child devices so it's important to document which functions can actually trigger a probe deferral. I know there's been a recent change to the dev_err_probe() suggesting that it could be used anyway, but I think that's a really bad idea and I'm considering sending a revert for that. > + > + /* skip resetting if already configured */ > + if (regmap_test_bits(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, > + CONN_STATUS_0_CONNECTION_PRESENT)) > + return 0; What if the device is held in reset? This looks like it only works if the boot firmware has already enabled the retimer. Otherwise you may return success from probe here with the retimer still in reset. > + gpiod_direction_output(retimer->reset_gpio, 1); > + > + /* VDD IO supply enable to reset release delay */ > + usleep_range(4000, 14000); > + > + gpiod_set_value(retimer->reset_gpio, 0); > + > + /* firmware initialization delay */ > + msleep(60); > + > + return 0; > + > +err_switch_unregister: > + typec_switch_unregister(retimer->sw); > +err_vregs_disable: > + ps883x_disable_vregs(retimer); > +err_clk_disable: > + clk_disable_unprepare(retimer->xo_clk); > +err_mux_put: > + typec_mux_put(retimer->typec_mux); > +err_switch_put: > + typec_switch_put(retimer->typec_switch); > + > + return ret; > +} > + > +static void ps883x_retimer_remove(struct i2c_client *client) > +{ > + struct ps883x_retimer *retimer = i2c_get_clientdata(client); > + > + typec_retimer_unregister(retimer->retimer); > + typec_switch_unregister(retimer->sw); > + > + gpiod_set_value(retimer->reset_gpio, 1); > + > + clk_disable_unprepare(retimer->xo_clk); > + > + ps883x_disable_vregs(retimer); > + > + typec_mux_put(retimer->typec_mux); > + typec_switch_put(retimer->typec_switch); > +} Johan
On Wed, Dec 04, 2024 at 05:24:54PM +0100, Johan Hovold wrote: > On Tue, Nov 12, 2024 at 07:01:11PM +0200, Abel Vesa wrote: > > The Parade PS8830 is a USB4, DisplayPort and Thunderbolt 4 retimer, > > controlled over I2C. It usually sits between a USB/DisplayPort PHY > > and the Type-C connector, and provides orientation and altmode handling. > > > > The boards that use this retimer are the ones featuring the Qualcomm > > Snapdragon X Elite SoCs. > > > +static int ps883x_sw_set(struct typec_switch_dev *sw, > > + enum typec_orientation orientation) > > +{ > > + struct ps883x_retimer *retimer = typec_switch_get_drvdata(sw); > > + int ret = 0; > > + > > + ret = typec_switch_set(retimer->typec_switch, orientation); > > + if (ret) > > + return ret; > > + > > + mutex_lock(&retimer->lock); > > + > > + if (retimer->orientation != orientation) { > > + retimer->orientation = orientation; > > + > > + ret = ps883x_set(retimer); > > + } > > + > > + mutex_unlock(&retimer->lock); > > + > > + return ret; > > +} > > This seems to indicate a bigger problem, but I see this function called > during early resume while the i2c controller is suspended: > > [ 54.213900] ------------[ cut here ]------------ > [ 54.213942] i2c i2c-2: Transfer while suspended > [ 54.214125] WARNING: CPU: 0 PID: 126 at drivers/i2c/i2c-core.h:56 __i2c_transfer+0x874/0x968 [i2c_core] > ... > [ 54.214833] CPU: 0 UID: 0 PID: 126 Comm: kworker/0:2 Not tainted 6.13.0-rc1 #11 > [ 54.214844] Hardware name: Qualcomm CRD, BIOS 6.0.231221.BOOT.MXF.2.4-00348.1-HAMOA-1 12/21/2023 > [ 54.214852] Workqueue: events pmic_glink_altmode_worker [pmic_glink_altmode] > ... > [ 54.215090] Call trace: > [ 54.215097] __i2c_transfer+0x874/0x968 [i2c_core] (P) > [ 54.215112] __i2c_transfer+0x874/0x968 [i2c_core] (L) > [ 54.215126] i2c_transfer+0x94/0xf0 [i2c_core] > [ 54.215140] i2c_transfer_buffer_flags+0x5c/0x90 [i2c_core] > [ 54.215153] regmap_i2c_write+0x20/0x58 [regmap_i2c] > [ 54.215166] _regmap_raw_write_impl+0x740/0x894 > [ 54.215184] _regmap_bus_raw_write+0x60/0x7c > [ 54.215192] _regmap_write+0x60/0x1b4 > [ 54.215200] regmap_write+0x4c/0x78 > [ 54.215207] ps883x_set+0xb0/0x10c [ps883x] > [ 54.215219] ps883x_sw_set+0x74/0x98 [ps883x] > [ 54.215227] typec_switch_set+0x58/0x90 [typec] > [ 54.215248] pmic_glink_altmode_worker+0x3c/0x23c [pmic_glink_altmode] > [ 54.215257] process_one_work+0x20c/0x610 > [ 54.215274] worker_thread+0x23c/0x378 > [ 54.215283] kthread+0x124/0x128 > [ 54.215291] ret_from_fork+0x10/0x20 > [ 54.215303] irq event stamp: 28140 > [ 54.215309] hardirqs last enabled at (28139): [<ffffd15e3bc2a434>] __up_console_sem+0x6c/0x80 > [ 54.215325] hardirqs last disabled at (28140): [<ffffd15e3c596aa4>] el1_dbg+0x24/0x8c > [ 54.215341] softirqs last enabled at (28120): [<ffffd15e3bb9b82c>] handle_softirqs+0x4c4/0x4dc > [ 54.215355] softirqs last disabled at (27961): [<ffffd15e3bb501ec>] __do_softirq+0x14/0x20 > [ 54.215363] ---[ end trace 0000000000000000 ]--- > [ 54.216889] Enabling non-boot CPUs ... > > This can be reproduced on the CRD (or T14s) by disconnecting, for > example, a mass storage device while the laptop is suspended. > I wonder if this is because drivers/rpmsg/qcom_glink_smem.c line 309 registers the GLINK interrupt as IRQF_NO_SUSPEND as a remnant from being used for rpm communication... This is no longer needed (glink/rpm code path is now different), but iirc the cleanup got stuck in the question of dealing with wakeup capabilities (and priorities). Regards, Bjorn
On 24-12-05 17:05:17, Bjorn Andersson wrote: > On Wed, Dec 04, 2024 at 05:24:54PM +0100, Johan Hovold wrote: > > On Tue, Nov 12, 2024 at 07:01:11PM +0200, Abel Vesa wrote: > > > The Parade PS8830 is a USB4, DisplayPort and Thunderbolt 4 retimer, > > > controlled over I2C. It usually sits between a USB/DisplayPort PHY > > > and the Type-C connector, and provides orientation and altmode handling. > > > > > > The boards that use this retimer are the ones featuring the Qualcomm > > > Snapdragon X Elite SoCs. > > > > > +static int ps883x_sw_set(struct typec_switch_dev *sw, > > > + enum typec_orientation orientation) > > > +{ > > > + struct ps883x_retimer *retimer = typec_switch_get_drvdata(sw); > > > + int ret = 0; > > > + > > > + ret = typec_switch_set(retimer->typec_switch, orientation); > > > + if (ret) > > > + return ret; > > > + > > > + mutex_lock(&retimer->lock); > > > + > > > + if (retimer->orientation != orientation) { > > > + retimer->orientation = orientation; > > > + > > > + ret = ps883x_set(retimer); > > > + } > > > + > > > + mutex_unlock(&retimer->lock); > > > + > > > + return ret; > > > +} > > > > This seems to indicate a bigger problem, but I see this function called > > during early resume while the i2c controller is suspended: > > > > [ 54.213900] ------------[ cut here ]------------ > > [ 54.213942] i2c i2c-2: Transfer while suspended > > [ 54.214125] WARNING: CPU: 0 PID: 126 at drivers/i2c/i2c-core.h:56 __i2c_transfer+0x874/0x968 [i2c_core] > > ... > > [ 54.214833] CPU: 0 UID: 0 PID: 126 Comm: kworker/0:2 Not tainted 6.13.0-rc1 #11 > > [ 54.214844] Hardware name: Qualcomm CRD, BIOS 6.0.231221.BOOT.MXF.2.4-00348.1-HAMOA-1 12/21/2023 > > [ 54.214852] Workqueue: events pmic_glink_altmode_worker [pmic_glink_altmode] > > ... > > [ 54.215090] Call trace: > > [ 54.215097] __i2c_transfer+0x874/0x968 [i2c_core] (P) > > [ 54.215112] __i2c_transfer+0x874/0x968 [i2c_core] (L) > > [ 54.215126] i2c_transfer+0x94/0xf0 [i2c_core] > > [ 54.215140] i2c_transfer_buffer_flags+0x5c/0x90 [i2c_core] > > [ 54.215153] regmap_i2c_write+0x20/0x58 [regmap_i2c] > > [ 54.215166] _regmap_raw_write_impl+0x740/0x894 > > [ 54.215184] _regmap_bus_raw_write+0x60/0x7c > > [ 54.215192] _regmap_write+0x60/0x1b4 > > [ 54.215200] regmap_write+0x4c/0x78 > > [ 54.215207] ps883x_set+0xb0/0x10c [ps883x] > > [ 54.215219] ps883x_sw_set+0x74/0x98 [ps883x] > > [ 54.215227] typec_switch_set+0x58/0x90 [typec] > > [ 54.215248] pmic_glink_altmode_worker+0x3c/0x23c [pmic_glink_altmode] > > [ 54.215257] process_one_work+0x20c/0x610 > > [ 54.215274] worker_thread+0x23c/0x378 > > [ 54.215283] kthread+0x124/0x128 > > [ 54.215291] ret_from_fork+0x10/0x20 > > [ 54.215303] irq event stamp: 28140 > > [ 54.215309] hardirqs last enabled at (28139): [<ffffd15e3bc2a434>] __up_console_sem+0x6c/0x80 > > [ 54.215325] hardirqs last disabled at (28140): [<ffffd15e3c596aa4>] el1_dbg+0x24/0x8c > > [ 54.215341] softirqs last enabled at (28120): [<ffffd15e3bb9b82c>] handle_softirqs+0x4c4/0x4dc > > [ 54.215355] softirqs last disabled at (27961): [<ffffd15e3bb501ec>] __do_softirq+0x14/0x20 > > [ 54.215363] ---[ end trace 0000000000000000 ]--- > > [ 54.216889] Enabling non-boot CPUs ... > > > > This can be reproduced on the CRD (or T14s) by disconnecting, for > > example, a mass storage device while the laptop is suspended. > > > > I wonder if this is because drivers/rpmsg/qcom_glink_smem.c line 309 > registers the GLINK interrupt as IRQF_NO_SUSPEND as a remnant from being > used for rpm communication... Yes. Seems like dropping the flag fixes this. > > This is no longer needed (glink/rpm code path is now different), but > iirc the cleanup got stuck in the question of dealing with wakeup > capabilities (and priorities). I'll send a patch to drop the flag and we then can debate there if it's the right thing to do w.r.t. wakeup. > > Regards, > Bjorn
diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig index 67381b4ef4f68f4a6e73f157365ee24d0ab7109a..6dd8f961b593261fde1d39b238b981966e463599 100644 --- a/drivers/usb/typec/mux/Kconfig +++ b/drivers/usb/typec/mux/Kconfig @@ -56,6 +56,16 @@ config TYPEC_MUX_NB7VPQ904M Say Y or M if your system has a On Semiconductor NB7VPQ904M Type-C redriver chip found on some devices with a Type-C port. +config TYPEC_MUX_PS883X + tristate "Parade PS883x Type-C retimer driver" + depends on I2C + depends on DRM || DRM=n + select DRM_AUX_BRIDGE if DRM_BRIDGE && OF + select REGMAP_I2C + help + Say Y or M if your system has a Parade PS883x Type-C retimer chip + found on some devices with a Type-C port. + config TYPEC_MUX_PTN36502 tristate "NXP PTN36502 Type-C redriver driver" depends on I2C diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile index 60879446da9365183567d3374a2fb7b5171fb3d7..b4f599eb5053b8f20e9a41409b0a2d9a03d850b6 100644 --- a/drivers/usb/typec/mux/Makefile +++ b/drivers/usb/typec/mux/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o obj-$(CONFIG_TYPEC_MUX_INTEL_PMC) += intel_pmc_mux.o obj-$(CONFIG_TYPEC_MUX_IT5205) += it5205.o obj-$(CONFIG_TYPEC_MUX_NB7VPQ904M) += nb7vpq904m.o +obj-$(CONFIG_TYPEC_MUX_PS883X) += ps883x.o obj-$(CONFIG_TYPEC_MUX_PTN36502) += ptn36502.o obj-$(CONFIG_TYPEC_MUX_TUSB1046) += tusb1046.o obj-$(CONFIG_TYPEC_MUX_WCD939X_USBSS) += wcd939x-usbss.o diff --git a/drivers/usb/typec/mux/ps883x.c b/drivers/usb/typec/mux/ps883x.c new file mode 100644 index 0000000000000000000000000000000000000000..3650e5d124727d9b9833302092331bf7f6f7b003 --- /dev/null +++ b/drivers/usb/typec/mux/ps883x.c @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Parade ps883x usb retimer driver + * + * Copyright (C) 2024 Linaro Ltd. + */ + +#include <drm/bridge/aux-bridge.h> +#include <linux/clk.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/typec_altmode.h> +#include <linux/usb/typec_dp.h> +#include <linux/usb/typec_mux.h> +#include <linux/usb/typec_retimer.h> + +#define REG_USB_PORT_CONN_STATUS_0 0x00 + +#define CONN_STATUS_0_CONNECTION_PRESENT BIT(0) +#define CONN_STATUS_0_ORIENTATION_REVERSED BIT(1) +#define CONN_STATUS_0_USB_3_1_CONNECTED BIT(5) + +#define REG_USB_PORT_CONN_STATUS_1 0x01 + +#define CONN_STATUS_1_DP_CONNECTED BIT(0) +#define CONN_STATUS_1_DP_SINK_REQUESTED BIT(1) +#define CONN_STATUS_1_DP_PIN_ASSIGNMENT_C_D BIT(2) +#define CONN_STATUS_1_DP_HPD_LEVEL BIT(7) + +#define REG_USB_PORT_CONN_STATUS_2 0x02 + +struct ps883x_retimer { + struct i2c_client *client; + struct gpio_desc *reset_gpio; + struct regmap *regmap; + struct typec_switch_dev *sw; + struct typec_retimer *retimer; + struct clk *xo_clk; + struct regulator *vdd_supply; + struct regulator *vdd33_supply; + struct regulator *vdd33_cap_supply; + struct regulator *vddat_supply; + struct regulator *vddar_supply; + struct regulator *vddio_supply; + + struct typec_switch *typec_switch; + struct typec_mux *typec_mux; + + struct mutex lock; /* protect non-concurrent retimer & switch */ + + enum typec_orientation orientation; + unsigned long mode; + unsigned int svid; +}; + +static void ps883x_configure(struct ps883x_retimer *retimer, int cfg0, + int cfg1, int cfg2) +{ + regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, cfg0); + regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_1, cfg1); + regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_2, cfg2); +} + +static int ps883x_set(struct ps883x_retimer *retimer) +{ + int cfg0 = CONN_STATUS_0_CONNECTION_PRESENT; + int cfg1 = 0x00; + int cfg2 = 0x00; + + if (retimer->orientation == TYPEC_ORIENTATION_NONE || + retimer->mode == TYPEC_STATE_SAFE) { + ps883x_configure(retimer, cfg0, cfg1, cfg2); + return 0; + } + + if (retimer->mode != TYPEC_STATE_USB && retimer->svid != USB_TYPEC_DP_SID) + return -EINVAL; + + if (retimer->orientation == TYPEC_ORIENTATION_REVERSE) + cfg0 |= CONN_STATUS_0_ORIENTATION_REVERSED; + + switch (retimer->mode) { + case TYPEC_STATE_USB: + cfg0 |= CONN_STATUS_0_USB_3_1_CONNECTED; + break; + + case TYPEC_DP_STATE_C: + cfg1 = CONN_STATUS_1_DP_CONNECTED | + CONN_STATUS_1_DP_SINK_REQUESTED | + CONN_STATUS_1_DP_PIN_ASSIGNMENT_C_D | + CONN_STATUS_1_DP_HPD_LEVEL; + break; + + case TYPEC_DP_STATE_D: + cfg0 |= CONN_STATUS_0_USB_3_1_CONNECTED; + cfg1 = CONN_STATUS_1_DP_CONNECTED | + CONN_STATUS_1_DP_SINK_REQUESTED | + CONN_STATUS_1_DP_PIN_ASSIGNMENT_C_D | + CONN_STATUS_1_DP_HPD_LEVEL; + break; + + case TYPEC_DP_STATE_E: + cfg1 = CONN_STATUS_1_DP_CONNECTED | + CONN_STATUS_1_DP_HPD_LEVEL; + break; + + default: + return -EOPNOTSUPP; + } + + ps883x_configure(retimer, cfg0, cfg1, cfg2); + + return 0; +} + +static int ps883x_sw_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct ps883x_retimer *retimer = typec_switch_get_drvdata(sw); + int ret = 0; + + ret = typec_switch_set(retimer->typec_switch, orientation); + if (ret) + return ret; + + mutex_lock(&retimer->lock); + + if (retimer->orientation != orientation) { + retimer->orientation = orientation; + + ret = ps883x_set(retimer); + } + + mutex_unlock(&retimer->lock); + + return ret; +} + +static int ps883x_retimer_set(struct typec_retimer *rtmr, + struct typec_retimer_state *state) +{ + struct ps883x_retimer *retimer = typec_retimer_get_drvdata(rtmr); + struct typec_mux_state mux_state; + int ret = 0; + + mutex_lock(&retimer->lock); + + if (state->mode != retimer->mode) { + retimer->mode = state->mode; + + if (state->alt) + retimer->svid = state->alt->svid; + else + retimer->svid = 0; // No SVID + + ret = ps883x_set(retimer); + } + + mutex_unlock(&retimer->lock); + + if (ret) + return ret; + + mux_state.alt = state->alt; + mux_state.data = state->data; + mux_state.mode = state->mode; + + return typec_mux_set(retimer->typec_mux, &mux_state); +} + +static int ps883x_enable_vregs(struct ps883x_retimer *retimer) +{ + struct device *dev = &retimer->client->dev; + int ret; + + ret = regulator_enable(retimer->vdd33_supply); + if (ret) { + dev_err(dev, "cannot enable VDD 3.3V regulator: %d\n", ret); + return ret; + } + + ret = regulator_enable(retimer->vdd33_cap_supply); + if (ret) { + dev_err(dev, "cannot enable VDD 3.3V CAP regulator: %d\n", ret); + goto err_vdd33_disable; + } + + usleep_range(4000, 10000); + + ret = regulator_enable(retimer->vdd_supply); + if (ret) { + dev_err(dev, "cannot enable VDD regulator: %d\n", ret); + goto err_vdd33_cap_disable; + } + + ret = regulator_enable(retimer->vddar_supply); + if (ret) { + dev_err(dev, "cannot enable VDD AR regulator: %d\n", ret); + goto err_vdd_disable; + } + + ret = regulator_enable(retimer->vddat_supply); + if (ret) { + dev_err(dev, "cannot enable VDD AT regulator: %d\n", ret); + goto err_vddar_disable; + } + + ret = regulator_enable(retimer->vddio_supply); + if (ret) { + dev_err(dev, "cannot enable VDD IO regulator: %d\n", ret); + goto err_vddat_disable; + } + + return 0; + +err_vddat_disable: + regulator_disable(retimer->vddat_supply); +err_vddar_disable: + regulator_disable(retimer->vddar_supply); +err_vdd_disable: + regulator_disable(retimer->vdd_supply); +err_vdd33_cap_disable: + regulator_disable(retimer->vdd33_cap_supply); +err_vdd33_disable: + regulator_disable(retimer->vdd33_supply); + + return ret; +} + +static void ps883x_disable_vregs(struct ps883x_retimer *retimer) +{ + regulator_disable(retimer->vddio_supply); + regulator_disable(retimer->vddat_supply); + regulator_disable(retimer->vddar_supply); + regulator_disable(retimer->vdd_supply); + regulator_disable(retimer->vdd33_cap_supply); + regulator_disable(retimer->vdd33_supply); +} + +static int ps883x_get_vregs(struct ps883x_retimer *retimer) +{ + struct device *dev = &retimer->client->dev; + + retimer->vdd_supply = devm_regulator_get(dev, "vdd"); + if (IS_ERR(retimer->vdd_supply)) + return dev_err_probe(dev, PTR_ERR(retimer->vdd_supply), + "failed to get VDD\n"); + + retimer->vdd33_supply = devm_regulator_get(dev, "vdd33"); + if (IS_ERR(retimer->vdd33_supply)) + return dev_err_probe(dev, PTR_ERR(retimer->vdd33_supply), + "failed to get VDD 3.3V\n"); + + retimer->vdd33_cap_supply = devm_regulator_get(dev, "vdd33-cap"); + if (IS_ERR(retimer->vdd33_cap_supply)) + return dev_err_probe(dev, PTR_ERR(retimer->vdd33_cap_supply), + "failed to get VDD CAP 3.3V\n"); + + retimer->vddat_supply = devm_regulator_get(dev, "vddat"); + if (IS_ERR(retimer->vddat_supply)) + return dev_err_probe(dev, PTR_ERR(retimer->vddat_supply), + "failed to get VDD AT\n"); + + retimer->vddar_supply = devm_regulator_get(dev, "vddar"); + if (IS_ERR(retimer->vddar_supply)) + return dev_err_probe(dev, PTR_ERR(retimer->vddar_supply), + "failed to get VDD AR\n"); + + retimer->vddio_supply = devm_regulator_get(dev, "vddio"); + if (IS_ERR(retimer->vddio_supply)) + return dev_err_probe(dev, PTR_ERR(retimer->vddio_supply), + "failed to get VDD IO\n"); + + return 0; +} + +static const struct regmap_config ps883x_retimer_regmap = { + .max_register = 0x1f, + .reg_bits = 8, + .val_bits = 8, +}; + +static int ps883x_retimer_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct typec_switch_desc sw_desc = { }; + struct typec_retimer_desc rtmr_desc = { }; + struct ps883x_retimer *retimer; + int ret; + + retimer = devm_kzalloc(dev, sizeof(*retimer), GFP_KERNEL); + if (!retimer) + return -ENOMEM; + + retimer->client = client; + + mutex_init(&retimer->lock); + + retimer->regmap = devm_regmap_init_i2c(client, &ps883x_retimer_regmap); + if (IS_ERR(retimer->regmap)) + return dev_err_probe(dev, PTR_ERR(retimer->regmap), + "failed to allocate register map\n"); + + ret = ps883x_get_vregs(retimer); + if (ret) + return ret; + + retimer->xo_clk = devm_clk_get(dev, NULL); + if (IS_ERR(retimer->xo_clk)) + return dev_err_probe(dev, PTR_ERR(retimer->xo_clk), + "failed to get xo clock\n"); + + retimer->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_ASIS); + if (IS_ERR(retimer->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(retimer->reset_gpio), + "failed to get reset gpio\n"); + + retimer->typec_switch = typec_switch_get(dev); + if (IS_ERR(retimer->typec_switch)) + return dev_err_probe(dev, PTR_ERR(retimer->typec_switch), + "failed to acquire orientation-switch\n"); + + retimer->typec_mux = typec_mux_get(dev); + if (IS_ERR(retimer->typec_mux)) { + ret = dev_err_probe(dev, PTR_ERR(retimer->typec_mux), + "failed to acquire mode-mux\n"); + goto err_switch_put; + } + + ret = drm_aux_bridge_register(dev); + if (ret) + goto err_mux_put; + + ret = ps883x_enable_vregs(retimer); + if (ret) + goto err_mux_put; + + ret = clk_prepare_enable(retimer->xo_clk); + if (ret) { + dev_err(dev, "failed to enable XO: %d\n", ret); + goto err_vregs_disable; + } + + sw_desc.drvdata = retimer; + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = ps883x_sw_set; + + retimer->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(retimer->sw)) { + ret = dev_err_probe(dev, PTR_ERR(retimer->sw), + "failed to register typec switch\n"); + goto err_clk_disable; + } + + rtmr_desc.drvdata = retimer; + rtmr_desc.fwnode = dev_fwnode(dev); + rtmr_desc.set = ps883x_retimer_set; + + retimer->retimer = typec_retimer_register(dev, &rtmr_desc); + if (IS_ERR(retimer->retimer)) { + ret = dev_err_probe(dev, PTR_ERR(retimer->sw), + "failed to register typec retimer\n"); + goto err_switch_unregister; + } + + /* skip resetting if already configured */ + if (regmap_test_bits(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, + CONN_STATUS_0_CONNECTION_PRESENT)) + return 0; + + gpiod_direction_output(retimer->reset_gpio, 1); + + /* VDD IO supply enable to reset release delay */ + usleep_range(4000, 14000); + + gpiod_set_value(retimer->reset_gpio, 0); + + /* firmware initialization delay */ + msleep(60); + + return 0; + +err_switch_unregister: + typec_switch_unregister(retimer->sw); +err_vregs_disable: + ps883x_disable_vregs(retimer); +err_clk_disable: + clk_disable_unprepare(retimer->xo_clk); +err_mux_put: + typec_mux_put(retimer->typec_mux); +err_switch_put: + typec_switch_put(retimer->typec_switch); + + return ret; +} + +static void ps883x_retimer_remove(struct i2c_client *client) +{ + struct ps883x_retimer *retimer = i2c_get_clientdata(client); + + typec_retimer_unregister(retimer->retimer); + typec_switch_unregister(retimer->sw); + + gpiod_set_value(retimer->reset_gpio, 1); + + clk_disable_unprepare(retimer->xo_clk); + + ps883x_disable_vregs(retimer); + + typec_mux_put(retimer->typec_mux); + typec_switch_put(retimer->typec_switch); +} + +static const struct of_device_id ps883x_retimer_of_table[] = { + { .compatible = "parade,ps8830" }, + { } +}; +MODULE_DEVICE_TABLE(of, ps883x_retimer_of_table); + +static struct i2c_driver ps883x_retimer_driver = { + .driver = { + .name = "ps883x_retimer", + .of_match_table = ps883x_retimer_of_table, + }, + .probe = ps883x_retimer_probe, + .remove = ps883x_retimer_remove, +}; + +module_i2c_driver(ps883x_retimer_driver); + +MODULE_DESCRIPTION("Parade ps883x Type-C Retimer driver"); +MODULE_LICENSE("GPL");
The Parade PS8830 is a USB4, DisplayPort and Thunderbolt 4 retimer, controlled over I2C. It usually sits between a USB/DisplayPort PHY and the Type-C connector, and provides orientation and altmode handling. The boards that use this retimer are the ones featuring the Qualcomm Snapdragon X Elite SoCs. Add a driver with support for the following modes: - DisplayPort 4-lanes - DisplayPort 2-lanes + USB3 - USB3 There is another variant of this retimer which is called PS8833. It seems to be really similar to the PS8830, so future-proof this driver by naming it ps883x. Signed-off-by: Abel Vesa <abel.vesa@linaro.org> --- drivers/usb/typec/mux/Kconfig | 10 + drivers/usb/typec/mux/Makefile | 1 + drivers/usb/typec/mux/ps883x.c | 437 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 448 insertions(+)