On Fri, Nov 8, 2024 at 10:38 PM Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxx> wrote: > > On Thu, Nov 07, 2024 at 11:29:58AM -0800, Abhishek Pandit-Subedi wrote: > > Add support for entering and exiting displayport alt-mode on systems > > using AP driven alt-mode. > > > > Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@xxxxxxxxxxxx> > > --- > > > > Changes in v3: > > - Refactored typec_altmode_dp_data per review request > > - Removed unused vdm operations during altmode registration > > > > Changes in v2: > > - Refactored displayport into cros_typec_altmode.c to extract common > > implementation between altmodes > > > > MAINTAINERS | 3 + > > drivers/platform/chrome/Makefile | 4 + > > drivers/platform/chrome/cros_ec_typec.c | 12 +- > > drivers/platform/chrome/cros_ec_typec.h | 1 + > > drivers/platform/chrome/cros_typec_altmode.c | 275 +++++++++++++++++++ > > drivers/platform/chrome/cros_typec_altmode.h | 34 +++ > > 6 files changed, 326 insertions(+), 3 deletions(-) > > create mode 100644 drivers/platform/chrome/cros_typec_altmode.c > > create mode 100644 drivers/platform/chrome/cros_typec_altmode.h > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index cd6aa609deba..5f9d8b8f1cb3 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -5369,9 +5369,12 @@ F: include/linux/platform_data/cros_usbpd_notify.h > > > > CHROMEOS EC USB TYPE-C DRIVER > > M: Prashant Malani <pmalani@xxxxxxxxxxxx> > > +M: Benson Leung <bleung@xxxxxxxxxxxx> > > +M: Abhishek Pandit-Subedi <abhishekpandit@xxxxxxxxxxxx> > > L: chrome-platform@xxxxxxxxxxxxxxx > > S: Maintained > > F: drivers/platform/chrome/cros_ec_typec.* > > +F: drivers/platform/chrome/cros_typec_altmode.* > > F: drivers/platform/chrome/cros_typec_switch.c > > F: drivers/platform/chrome/cros_typec_vdm.* > > > > diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile > > index 2dcc6ccc2302..2f90d4db8099 100644 > > --- a/drivers/platform/chrome/Makefile > > +++ b/drivers/platform/chrome/Makefile > > @@ -18,7 +18,11 @@ obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o > > obj-$(CONFIG_CROS_EC_UART) += cros_ec_uart.o > > cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o > > cros-ec-typec-objs := cros_ec_typec.o cros_typec_vdm.o > > +ifneq ($(CONFIG_TYPEC_DP_ALTMODE),) > > + cros-ec-typec-objs += cros_typec_altmode.o > > +endif > > obj-$(CONFIG_CROS_EC_TYPEC) += cros-ec-typec.o > > + > > obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpcs.o > > obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o cros_ec_trace.o > > obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.o > > diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c > > index e3eabe5e42ac..3a6f5f2717b9 100644 > > --- a/drivers/platform/chrome/cros_ec_typec.c > > +++ b/drivers/platform/chrome/cros_ec_typec.c > > @@ -18,6 +18,7 @@ > > > > #include "cros_ec_typec.h" > > #include "cros_typec_vdm.h" > > +#include "cros_typec_altmode.h" > > > > #define DRV_NAME "cros-ec-typec" > > > > @@ -293,12 +294,11 @@ static int cros_typec_register_port_altmodes(struct cros_typec_data *typec, > > desc.svid = USB_TYPEC_DP_SID; > > desc.mode = USB_TYPEC_DP_MODE; > > desc.vdo = DP_PORT_VDO; > > - amode = typec_port_register_altmode(port->port, &desc); > > + amode = cros_typec_register_displayport(port, &desc, > > + typec->ap_driven_altmode); > > if (IS_ERR(amode)) > > return PTR_ERR(amode); > > port->port_altmode[CROS_EC_ALTMODE_DP] = amode; > > - typec_altmode_set_drvdata(amode, port); > > - amode->ops = &port_amode_ops; > > > > /* > > * Register TBT compatibility alt mode. The EC will not enter the mode > > @@ -575,6 +575,10 @@ static int cros_typec_enable_dp(struct cros_typec_data *typec, > > if (!ret) > > ret = typec_mux_set(port->mux, &port->state); > > > > + if (!ret) > > + cros_typec_displayport_status_update(port->state.alt, > > + port->state.data); > > + > > return ret; > > } > > > > @@ -1254,6 +1258,8 @@ static int cros_typec_probe(struct platform_device *pdev) > > > > typec->typec_cmd_supported = cros_ec_check_features(ec_dev, EC_FEATURE_TYPEC_CMD); > > typec->needs_mux_ack = cros_ec_check_features(ec_dev, EC_FEATURE_TYPEC_MUX_REQUIRE_AP_ACK); > > + typec->ap_driven_altmode = cros_ec_check_features( > > + ec_dev, EC_FEATURE_TYPEC_REQUIRE_AP_MODE_ENTRY); > > > > ret = cros_ec_cmd(typec->ec, 0, EC_CMD_USB_PD_PORTS, NULL, 0, > > &resp, sizeof(resp)); > > diff --git a/drivers/platform/chrome/cros_ec_typec.h b/drivers/platform/chrome/cros_ec_typec.h > > index deda180a646f..9fd5342bb0ad 100644 > > --- a/drivers/platform/chrome/cros_ec_typec.h > > +++ b/drivers/platform/chrome/cros_ec_typec.h > > @@ -39,6 +39,7 @@ struct cros_typec_data { > > struct work_struct port_work; > > bool typec_cmd_supported; > > bool needs_mux_ack; > > + bool ap_driven_altmode; > > }; > > > > /* Per port data. */ > > diff --git a/drivers/platform/chrome/cros_typec_altmode.c b/drivers/platform/chrome/cros_typec_altmode.c > > new file mode 100644 > > index 000000000000..3598b8a6ceee > > --- /dev/null > > +++ b/drivers/platform/chrome/cros_typec_altmode.c > > @@ -0,0 +1,275 @@ > > +// SPDX-License-Identifier: GPL-2.0-only > > +/* > > + * Alt-mode implementation on ChromeOS EC. > > + * > > + * Copyright 2024 Google LLC > > + * Author: Abhishek Pandit-Subedi <abhishekpandit@xxxxxxxxxxxx> > > + */ > > +#include "cros_ec_typec.h" > > + > > +#include <linux/usb/typec_dp.h> > > +#include <linux/usb/pd_vdo.h> > > + > > +#include "cros_typec_altmode.h" > > + > > +struct cros_typec_altmode_data { > > + struct work_struct work; > > + struct cros_typec_port *port; > > + struct typec_altmode *alt; > > + bool ap_mode_entry; > > + > > + u32 header; > > + u32 *vdo_data; > > + u8 vdo_size; > > + > > + u16 sid; > > + u8 mode; > > +}; > > + > > +struct cros_typec_dp_data { > > + struct cros_typec_altmode_data adata; > > + struct typec_displayport_data data; > > + bool configured; > > + bool pending_status_update; > > +}; > > + > > +static void cros_typec_altmode_work(struct work_struct *work) > > +{ > > + struct cros_typec_altmode_data *data = > > + container_of(work, struct cros_typec_altmode_data, work); > > + > > + if (typec_altmode_vdm(data->alt, data->header, data->vdo_data, > > + data->vdo_size)) > > + dev_err(&data->alt->dev, "VDM 0x%x failed", data->header); > > + > > + data->header = 0; > > + data->vdo_data = NULL; > > + data->vdo_size = 0; > > What protects data->header / vdo_data / vdo_size from concurrent > modification? Good catch! This needs a mutex -- will add for the next series. > > > +} > > + > > +static int cros_typec_altmode_enter(struct typec_altmode *alt, u32 *vdo) > > +{ > > + struct cros_typec_altmode_data *data = typec_altmode_get_drvdata(alt); > > + struct ec_params_typec_control req = { > > + .port = data->port->port_num, > > + .command = TYPEC_CONTROL_COMMAND_ENTER_MODE, > > + }; > > + int svdm_version; > > + int ret; > > + > > + if (!data->ap_mode_entry) { > > + const struct typec_altmode *partner = > > + typec_altmode_get_partner(alt); > > + dev_warn(&partner->dev, > > + "EC does not support ap driven mode entry\n"); > > Nit: AP, not ap Ack for next series. > > > + return -EOPNOTSUPP; > > + } > > + > > + if (data->sid == USB_TYPEC_DP_SID) > > + req.mode_to_enter = CROS_EC_ALTMODE_DP; > > + else > > + return -EOPNOTSUPP; > > + > > + ret = cros_ec_cmd(data->port->typec_data->ec, 0, EC_CMD_TYPEC_CONTROL, > > + &req, sizeof(req), NULL, 0); > > + if (ret < 0) > > + return ret; > > + > > + svdm_version = typec_altmode_get_svdm_version(alt); > > + if (svdm_version < 0) > > + return svdm_version; > > + > > + data->header = VDO(data->sid, 1, svdm_version, CMD_ENTER_MODE); > > + data->header |= VDO_OPOS(data->mode); > > + data->header |= VDO_CMDT(CMDT_RSP_ACK); > > + > > + data->vdo_data = NULL; > > + data->vdo_size = 1; > > + > > + schedule_work(&data->work); > > + > > + return ret; > > +} > > + > > +static int cros_typec_altmode_exit(struct typec_altmode *alt) > > +{ > > + struct cros_typec_altmode_data *data = typec_altmode_get_drvdata(alt); > > + struct ec_params_typec_control req = { > > + .port = data->port->port_num, > > + .command = TYPEC_CONTROL_COMMAND_EXIT_MODES, > > + }; > > + int svdm_version; > > + int ret; > > + > > + if (!data->ap_mode_entry) { > > + const struct typec_altmode *partner = > > + typec_altmode_get_partner(alt); > > + dev_warn(&partner->dev, > > + "EC does not support ap driven mode entry\n"); > > + return -EOPNOTSUPP; > > + } > > + > > + ret = cros_ec_cmd(data->port->typec_data->ec, 0, EC_CMD_TYPEC_CONTROL, > > + &req, sizeof(req), NULL, 0); > > + > > + if (ret < 0) > > + return ret; > > + > > + svdm_version = typec_altmode_get_svdm_version(alt); > > + if (svdm_version < 0) > > + return svdm_version; > > + > > + data->header = VDO(data->sid, 1, svdm_version, CMD_EXIT_MODE); > > + data->header |= VDO_OPOS(data->mode); > > + data->header |= VDO_CMDT(CMDT_RSP_ACK); > > + > > + data->vdo_data = NULL; > > + data->vdo_size = 1; > > + > > + schedule_work(&data->work); > > + > > + return ret; > > +} > > + > > +static int cros_typec_displayport_vdm(struct typec_altmode *alt, u32 header, > > + const u32 *data, int count) > > +{ > > + struct cros_typec_dp_data *dp_data = typec_altmode_get_drvdata(alt); > > + struct cros_typec_altmode_data *adata = &dp_data->adata; > > + > > + > > + int cmd_type = PD_VDO_CMDT(header); > > + int cmd = PD_VDO_CMD(header); > > + int svdm_version; > > + > > + if (!adata->ap_mode_entry) { > > + const struct typec_altmode *partner = > > + typec_altmode_get_partner(alt); > > + dev_warn(&partner->dev, > > + "EC does not support ap driven mode entry\n"); > > + return -EOPNOTSUPP; > > + } > > Move the ckeck to cros_typec_altmode_vdm() ? > > But this makes me wonder, should the driver use different ops in such a > case? Can't we just use a stubbed version of ops if > cros_typec_register_displayport() gets ap_mode_entry = false ? > I could leave out `.vdm` in typec_altmode_ops and I'd get the same result (-ENOTSUPP) but without the warning message. That'll remove the need to store the bool entirely. Expect it next series -- thanks! > > + > > + svdm_version = typec_altmode_get_svdm_version(alt); > > + if (svdm_version < 0) > > + return svdm_version; > > + > > + switch (cmd_type) { > > + case CMDT_INIT: > > + if (PD_VDO_SVDM_VER(header) < svdm_version) { > > + typec_partner_set_svdm_version(adata->port->partner, > > + PD_VDO_SVDM_VER(header)); > > + svdm_version = PD_VDO_SVDM_VER(header); > > + } > > + > > + adata->header = VDO(adata->sid, 1, svdm_version, cmd); > > + adata->header |= VDO_OPOS(adata->mode); > > + > > + /* > > + * DP_CMD_CONFIGURE: We can't actually do anything with the > > + * provided VDO yet so just send back an ACK. > > + * > > + * DP_CMD_STATUS_UPDATE: We wait for Mux changes to send > > + * DPStatus Acks. > > + */ > > + switch (cmd) { > > + case DP_CMD_CONFIGURE: > > + dp_data->data.conf = *data; > > + adata->header |= VDO_CMDT(CMDT_RSP_ACK); > > + dp_data->configured = true; > > + schedule_work(&adata->work); > > + break; > > + case DP_CMD_STATUS_UPDATE: > > + dp_data->pending_status_update = true; > > + break; > > + default: > > + adata->header |= VDO_CMDT(CMDT_RSP_ACK); > > + schedule_work(&adata->work); > > + break; > > + } > > + > > + break; > > + default: > > + break; > > + } > > + > > + return 0; > > +} > > + > > +static int cros_typec_altmode_vdm(struct typec_altmode *alt, u32 header, > > + const u32 *data, int count) > > +{ > > + struct cros_typec_altmode_data *adata = typec_altmode_get_drvdata(alt); > > + > > + if (adata->sid == USB_TYPEC_DP_SID) > > + return cros_typec_displayport_vdm(alt, header, data, count); > > + > > + return -EINVAL; > > +} > > + > > +static const struct typec_altmode_ops cros_typec_altmode_ops = { > > + .enter = cros_typec_altmode_enter, > > + .exit = cros_typec_altmode_exit, > > + .vdm = cros_typec_altmode_vdm, > > +}; > > + > > +#if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE) > > +int cros_typec_displayport_status_update(struct typec_altmode *altmode, > > + struct typec_displayport_data *data) > > +{ > > + struct cros_typec_dp_data *dp_data = > > + typec_altmode_get_drvdata(altmode); > > + struct cros_typec_altmode_data *adata = &dp_data->adata; > > + > > + if (!dp_data->pending_status_update) { > > + dev_dbg(&altmode->dev, > > + "Got DPStatus without a pending request"); > > + return 0; > > + } > > + > > + if (dp_data->configured && dp_data->data.conf != data->conf) > > + dev_dbg(&altmode->dev, > > + "DP Conf doesn't match. Requested 0x%04x, Actual 0x%04x", > > + dp_data->data.conf, data->conf); > > + > > + dp_data->data = *data; > > + dp_data->pending_status_update = false; > > + adata->header |= VDO_CMDT(CMDT_RSP_ACK); > > + adata->vdo_data = &dp_data->data.status; > > + adata->vdo_size = 2; > > + > > + schedule_work(&adata->work); > > + return 0; > > +} > > + > > +struct typec_altmode * > > +cros_typec_register_displayport(struct cros_typec_port *port, > > + struct typec_altmode_desc *desc, > > + bool ap_mode_entry) > > +{ > > + struct typec_altmode *alt; > > + struct cros_typec_altmode_data *data; > > + > > + alt = typec_port_register_altmode(port->port, desc); > > + if (IS_ERR(alt)) > > + return alt; > > + > > + data = devm_kzalloc(&alt->dev, sizeof(*data), GFP_KERNEL); > > + if (!data) { > > + typec_unregister_altmode(alt); > > + return ERR_PTR(-ENOMEM); > > + } > > + > > + INIT_WORK(&data->work, cros_typec_altmode_work); > > + data->alt = alt; > > + data->port = port; > > + data->ap_mode_entry = ap_mode_entry; > > + data->sid = desc->svid; > > + data->mode = desc->mode; > > + > > + typec_altmode_set_ops(alt, &cros_typec_altmode_ops); > > + typec_altmode_set_drvdata(alt, data); > > + > > + return alt; > > +} > > +#endif > > diff --git a/drivers/platform/chrome/cros_typec_altmode.h b/drivers/platform/chrome/cros_typec_altmode.h > > new file mode 100644 > > index 000000000000..c6f8fb02c99c > > --- /dev/null > > +++ b/drivers/platform/chrome/cros_typec_altmode.h > > @@ -0,0 +1,34 @@ > > +/* SPDX-License-Identifier: GPL-2.0-only */ > > + > > +#ifndef __CROS_TYPEC_ALTMODE_H__ > > +#define __CROS_TYPEC_ALTMODE_H__ > > + > > +struct cros_typec_port; > > +struct typec_altmode; > > +struct typec_altmode_desc; > > +struct typec_displayport_data; > > + > > +#if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE) > > +struct typec_altmode * > > +cros_typec_register_displayport(struct cros_typec_port *port, > > + struct typec_altmode_desc *desc, > > + bool ap_mode_entry); > > + > > +int cros_typec_displayport_status_update(struct typec_altmode *altmode, > > + struct typec_displayport_data *data); > > +#else > > +static inline struct typec_altmode * > > +cros_typec_register_displayport(struct cros_typec_port *port, > > + struct typec_altmode_desc *desc, > > + bool ap_mode_entry) > > +{ > > + return typec_port_register_altmode(port->port, desc); > > +} > > + > > +static inline int cros_typec_displayport_status_update(struct typec_altmode *altmode, > > + struct typec_displayport_data *data) > > +{ > > + return 0; > > +} > > +#endif > > +#endif /* __CROS_TYPEC_ALTMODE_H__ */ > > -- > > 2.47.0.277.g8800431eea-goog > > > > -- > With best wishes > Dmitry