[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <Z9q0Fgf5_fXcbiMi@kuha.fi.intel.com>
Date: Wed, 19 Mar 2025 14:09:58 +0200
From: Heikki Krogerus <heikki.krogerus@...ux.intel.com>
To: Pengyu Luo <mitltlatltl@...il.com>
Cc: Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
linux-kernel@...r.kernel.org, linux-usb@...r.kernel.org
Subject: Re: [PATCH v9] usb: typec: ucsi: add Huawei Matebook E Go ucsi driver
On Sun, Mar 16, 2025 at 05:43:55PM +0800, Pengyu Luo wrote:
> The Huawei Matebook E Go tablet implements the UCSI interface in the
> onboard EC. Add the glue driver to interface with the platform's UCSI
> implementation.
>
> This driver is inspired by the following drivers:
> drivers/usb/typec/ucsi/ucsi_yoga_c630.c
> drivers/usb/typec/ucsi/ucsi_glink.c
> drivers/soc/qcom/pmic_glink_altmode.c
>
> Signed-off-by: Pengyu Luo <mitltlatltl@...il.com>
Reviewed-by: Heikki Krogerus <heikki.krogerus@...ux.intel.com>
> ---
> base-commit: 613af589b566093ce7388bf3202fca70d742c166
> ---
> Changes in v9:
> - rebased on tag next-20250313
> - remove base-commit from changelog (Greg)
> - Link to v8: https://lore.kernel.org/linux-usb/20250308105356.53808-1-mitltlatltl@gmail.com
>
> Changes in v8:
> - since the base driver was mergerd by Ilpo, continue the UCSI driver
> - rebased on tag next-20250307
> - add the missing newline (Heikki)
> - comment the spin lock (Heikki)
> - Link to v7: https://lore.kernel.org/linux-arm-msm/20250214180656.28599-1-mitltlatltl@gmail.com
>
> Changes in v7:
> - rebased on tag next-20250214
> - fix Kconfig, this module depends on HWMON (kernel test robot)
> - Link to v6: https://lore.kernel.org/linux-arm-msm/20250123152559.52449-1-mitltlatltl@gmail.com
>
> Changes in v6 RESEND:
> - add Reviewed-by tag (Ilpo)
> - remove extra line
> - rebased on tag next-20250131
> - Link to v6: https://lore.kernel.org/linux-arm-msm/20250123152559.52449-1-mitltlatltl@gmail.com
>
> Changes in v6:
> - refactor one ternary operator expression (Ilpo)
> - replace one expression with &= (Ilpo)
> - use devm_mutex_init() instead of mutex_init() (Ilpo)
> - add Reviewed-by tag for devicetree (Konrad)
> - add explicit cast (void *) to fix warnings when compiling
> - rebased on tag next-20250123
> - Link to v5: https://lore.kernel.org/linux-arm-msm/20250117140348.180681-1-mitltlatltl@gmail.com
>
> Changes in v5 RESEND:
> - rebased on tag next-20250120
> - Link to v5: https://lore.kernel.org/linux-arm-msm/20250117140348.180681-1-mitltlatltl@gmail.com
>
> Changes in v5:
> - handle return code of i2c_transfer() (Bryan)
> - rename threshold validatition function (Bryan)
> - add enumerates and defines for registers (Bryan)
> - drop extra line in header (Heikki)
> - add Reviewed-by tag for devicetree (Krzysztof)
> - Link to v4: https://lore.kernel.org/linux-arm-msm/20250116111559.83641-1-mitltlatltl@gmail.com
>
> Changes in v4:
> - use new API to register hwmon device instead of the deprecated one. (Guenter)
> - add Reviewed-by tag for dt-binding (Krzysztof)
> - drop unnecessary header (Ilpo)
> - use guard mutex (Ilpo)
> - improve comments and naming (Ilpo)
> - add a shallow copy version of extr_resp() (Ilpo)
> - add functions to handle resp and req whose size is 1
> - drop PSY and UCSI subdrivers, commit them once the base driver is upstreamed
> - Link to v3: https://lore.kernel.org/linux-arm-msm/20250113175049.590511-1-mitltlatltl@gmail.com
>
> Changes in v3:
> - Link to v2: https://lore.kernel.org/linux-arm-msm/20250105174159.227831-1-mitltlatltl@gmail.com
>
> dt-binding:
> - drop generic compatibles. (Krzysztof)
> - remove '+' to use literal block style. (Krzysztof)
>
> ec:
> - take struct gaokun_ucsi_reg as parameter (Heikki)
> - add almost all kernel doc comments (Krzysztof, Heikki)
>
> ucsi:
> - drop unnecessary ucsi quirks (Dmitry)
> - add UCSI v1.0 to ucsi.h (Heikki)
> - use gaokun_ucsi_read_cci() to read cci directly (Heikki)
> - drop unnecessary gaokun_ucsi_get_port_num (Heikki)
> - rename member port_num => num_ports (Heikki)
> - fix completion, forgot to signal threads in previous version
>
> dt:
> - fix indentation (Konrad)
> - add a link between role switch and connector
>
> Changes in v2:
> - Link to v1: https://lore.kernel.org/linux-arm-msm/20241227171353.404432-1-mitltlatltl@gmail.com
>
> global:
> - drop qcom's products(i.e. sc8180x, sx8280xp) everywhere, use 'product'-based instead(Krzysztof, Bryan)
> - drop Cc Nikita Travkin, we had discussed the device in PM.
> - add myself to MAINTAINERS
>
> dt-binding:
> - fix building (Rob Herring (Arm))
> - remove unnecessary code (Krzysztof)
> - add bugzilla documentation, insights of gaokun(see [1] or patch[1/5]) (Krzysztof, Aiqun(Maria))
> - explain the difference between PMIC GLink and gaokun EC (Aiqun(Maria))
>
> ec:
> - use Linux style comments (Krzysztof)
> - add a comment for mutex lock (Krzysztof)
> - add more kerneldoc for exported functions (Krzysztof)
> - eliminate unnecessary conditions (Bryan)
> - add a macro for check thresholds (Bryan)
> - improve English (Bryan)
> - use existing sysfs interface(hwmon, psy) whenever possible (Krzysztof)
> - use __le16 and related endianess conversion function for temp data (Ilpo)
> - drop alias for packet headers (Ilpo)
> - avoid hardcoding i2c msgs size (Aiqun(Maria))
> - add a comment for the sleep in critial region (Bryan, Aiqun(Maria))
> - use macro to construct packet (Bryan, Aiqun(Maria))
>
> wmi:
> - dropped
>
> ucsi:
> - reorder headers (Bryan)
> - a comment for the orientation map macro (Bryan)
> - make mux mode map more explicit(minus six is very clear now) (Bryan, Dmitry)
> - handle port update exceptions return (Bryan)
> - a comment for the UCSI quirks (Dmitry)
> - use the inline hint for the short register function (Dmitry)
> - use the API with delay to handle register instead of a direct sleep (Bryan)
> - handle unfinished initialization early
>
> psy:
> - add charging related sysfs to here (Krzysztof, Dmitry)
> - document ABI for power_supply sysfs (Krzysztof)
> - drop charging threshold, use smart charging instead
>
> dts:
> - correct indentation, properties' order. (Konrad)
> ---
> MAINTAINERS | 1 +
> drivers/usb/typec/ucsi/Kconfig | 11 +
> drivers/usb/typec/ucsi/Makefile | 1 +
> drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 522 ++++++++++++++++++++
> 4 files changed, 535 insertions(+)
> create mode 100644 drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b20efd24a..1b06ba3aa 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -10884,6 +10884,7 @@ M: Pengyu Luo <mitltlatltl@...il.com>
> S: Maintained
> F: Documentation/devicetree/bindings/platform/huawei,gaokun-ec.yaml
> F: drivers/platform/arm64/huawei-gaokun-ec.c
> +F: drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> F: include/linux/platform_data/huawei-gaokun-ec.h
>
> HUGETLB SUBSYSTEM
> diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
> index 75559601f..e94956d27 100644
> --- a/drivers/usb/typec/ucsi/Kconfig
> +++ b/drivers/usb/typec/ucsi/Kconfig
> @@ -91,4 +91,15 @@ config UCSI_LENOVO_YOGA_C630
> To compile the driver as a module, choose M here: the module will be
> called ucsi_yoga_c630.
>
> +config UCSI_HUAWEI_GAOKUN
> + tristate "UCSI Interface Driver for Huawei Matebook E Go"
> + depends on EC_HUAWEI_GAOKUN
> + select DRM_AUX_HPD_BRIDGE
> + help
> + This driver enables UCSI support on the Huawei Matebook E Go tablet,
> + which is a sc8280xp-based 2-in-1 tablet.
> +
> + To compile the driver as a module, choose M here: the module will be
> + called ucsi_huawei_gaokun.
> +
> endif
> diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
> index be98a8791..dbc571763 100644
> --- a/drivers/usb/typec/ucsi/Makefile
> +++ b/drivers/usb/typec/ucsi/Makefile
> @@ -23,3 +23,4 @@ obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o
> obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o
> obj-$(CONFIG_CROS_EC_UCSI) += cros_ec_ucsi.o
> obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o
> +obj-$(CONFIG_UCSI_HUAWEI_GAOKUN) += ucsi_huawei_gaokun.o
> diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> new file mode 100644
> index 000000000..344aa7aea
> --- /dev/null
> +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
> @@ -0,0 +1,522 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * ucsi-huawei-gaokun - A UCSI driver for HUAWEI Matebook E Go
> + *
> + * Copyright (C) 2024-2025 Pengyu Luo <mitltlatltl@...il.com>
> + */
> +
> +#include <drm/bridge/aux-bridge.h>
> +#include <linux/auxiliary_bus.h>
> +#include <linux/bitops.h>
> +#include <linux/completion.h>
> +#include <linux/container_of.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/of.h>
> +#include <linux/platform_data/huawei-gaokun-ec.h>
> +#include <linux/string.h>
> +#include <linux/usb/pd_vdo.h>
> +#include <linux/usb/typec_altmode.h>
> +#include <linux/usb/typec_dp.h>
> +#include <linux/workqueue_types.h>
> +
> +#include "ucsi.h"
> +
> +#define EC_EVENT_UCSI 0x21
> +#define EC_EVENT_USB 0x22
> +
> +#define GAOKUN_CCX_MASK GENMASK(1, 0)
> +#define GAOKUN_MUX_MASK GENMASK(3, 2)
> +
> +#define GAOKUN_DPAM_MASK GENMASK(3, 0)
> +#define GAOKUN_HPD_STATE_MASK BIT(4)
> +#define GAOKUN_HPD_IRQ_MASK BIT(5)
> +
> +#define GET_IDX(updt) (ffs(updt) - 1)
> +
> +#define CCX_TO_ORI(ccx) (++(ccx) % 3) /* convert ccx to enum typec_orientation */
> +
> +/* Configuration Channel Extension */
> +enum gaokun_ucsi_ccx {
> + USBC_CCX_NORMAL,
> + USBC_CCX_REVERSE,
> + USBC_CCX_NONE,
> +};
> +
> +enum gaokun_ucsi_mux {
> + USBC_MUX_NONE,
> + USBC_MUX_USB_2L,
> + USBC_MUX_DP_4L,
> + USBC_MUX_USB_DP,
> +};
> +
> +/* based on pmic_glink_altmode_pin_assignment */
> +enum gaokun_ucsi_dpam_pan { /* DP Alt Mode Pin Assignments */
> + USBC_DPAM_PAN_NONE,
> + USBC_DPAM_PAN_A, /* Not supported after USB Type-C Standard v1.0b */
> + USBC_DPAM_PAN_B, /* Not supported after USB Type-C Standard v1.0b */
> + USBC_DPAM_PAN_C, /* USBC_DPAM_PAN_C_REVERSE - 6 */
> + USBC_DPAM_PAN_D,
> + USBC_DPAM_PAN_E,
> + USBC_DPAM_PAN_F, /* Not supported after USB Type-C Standard v1.0b */
> + USBC_DPAM_PAN_A_REVERSE,/* Not supported after USB Type-C Standard v1.0b */
> + USBC_DPAM_PAN_B_REVERSE,/* Not supported after USB Type-C Standard v1.0b */
> + USBC_DPAM_PAN_C_REVERSE,
> + USBC_DPAM_PAN_D_REVERSE,
> + USBC_DPAM_PAN_E_REVERSE,
> + USBC_DPAM_PAN_F_REVERSE,/* Not supported after USB Type-C Standard v1.0b */
> +};
> +
> +struct gaokun_ucsi_reg {
> + u8 num_ports;
> + u8 port_updt;
> + u8 port_data[4];
> + u8 checksum;
> + u8 reserved;
> +} __packed;
> +
> +struct gaokun_ucsi_port {
> + struct completion usb_ack;
> + spinlock_t lock; /* serializing port resource access */
> +
> + struct gaokun_ucsi *ucsi;
> + struct auxiliary_device *bridge;
> +
> + int idx;
> + enum gaokun_ucsi_ccx ccx;
> + enum gaokun_ucsi_mux mux;
> + u8 mode;
> + u16 svid;
> + u8 hpd_state;
> + u8 hpd_irq;
> +};
> +
> +struct gaokun_ucsi {
> + struct gaokun_ec *ec;
> + struct ucsi *ucsi;
> + struct gaokun_ucsi_port *ports;
> + struct device *dev;
> + struct delayed_work work;
> + struct notifier_block nb;
> + u16 version;
> + u8 num_ports;
> +};
> +
> +/* -------------------------------------------------------------------------- */
> +/* For UCSI */
> +
> +static int gaokun_ucsi_read_version(struct ucsi *ucsi, u16 *version)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> +
> + *version = uec->version;
> +
> + return 0;
> +}
> +
> +static int gaokun_ucsi_read_cci(struct ucsi *ucsi, u32 *cci)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> + u8 buf[GAOKUN_UCSI_READ_SIZE];
> + int ret;
> +
> + ret = gaokun_ec_ucsi_read(uec->ec, buf);
> + if (ret)
> + return ret;
> +
> + memcpy(cci, buf, sizeof(*cci));
> +
> + return 0;
> +}
> +
> +static int gaokun_ucsi_read_message_in(struct ucsi *ucsi,
> + void *val, size_t val_len)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> + u8 buf[GAOKUN_UCSI_READ_SIZE];
> + int ret;
> +
> + ret = gaokun_ec_ucsi_read(uec->ec, buf);
> + if (ret)
> + return ret;
> +
> + memcpy(val, buf + GAOKUN_UCSI_CCI_SIZE,
> + min(val_len, GAOKUN_UCSI_MSGI_SIZE));
> +
> + return 0;
> +}
> +
> +static int gaokun_ucsi_async_control(struct ucsi *ucsi, u64 command)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
> + u8 buf[GAOKUN_UCSI_WRITE_SIZE] = {};
> +
> + memcpy(buf, &command, sizeof(command));
> +
> + return gaokun_ec_ucsi_write(uec->ec, buf);
> +}
> +
> +static void gaokun_ucsi_update_connector(struct ucsi_connector *con)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
> +
> + if (con->num > uec->num_ports)
> + return;
> +
> + con->typec_cap.orientation_aware = true;
> +}
> +
> +static void gaokun_set_orientation(struct ucsi_connector *con,
> + struct gaokun_ucsi_port *port)
> +{
> + enum gaokun_ucsi_ccx ccx;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&port->lock, flags);
> + ccx = port->ccx;
> + spin_unlock_irqrestore(&port->lock, flags);
> +
> + typec_set_orientation(con->port, CCX_TO_ORI(ccx));
> +}
> +
> +static void gaokun_ucsi_connector_status(struct ucsi_connector *con)
> +{
> + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
> + int idx;
> +
> + idx = con->num - 1;
> + if (con->num > uec->num_ports) {
> + dev_warn(uec->dev, "set orientation out of range: con%d\n", idx);
> + return;
> + }
> +
> + gaokun_set_orientation(con, &uec->ports[idx]);
> +}
> +
> +const struct ucsi_operations gaokun_ucsi_ops = {
> + .read_version = gaokun_ucsi_read_version,
> + .read_cci = gaokun_ucsi_read_cci,
> + .read_message_in = gaokun_ucsi_read_message_in,
> + .sync_control = ucsi_sync_control_common,
> + .async_control = gaokun_ucsi_async_control,
> + .update_connector = gaokun_ucsi_update_connector,
> + .connector_status = gaokun_ucsi_connector_status,
> +};
> +
> +/* -------------------------------------------------------------------------- */
> +/* For Altmode */
> +
> +static void gaokun_ucsi_port_update(struct gaokun_ucsi_port *port,
> + const u8 *port_data)
> +{
> + struct gaokun_ucsi *uec = port->ucsi;
> + int offset = port->idx * 2; /* every port has 2 Bytes data */
> + unsigned long flags;
> + u8 dcc, ddi;
> +
> + dcc = port_data[offset];
> + ddi = port_data[offset + 1];
> +
> + spin_lock_irqsave(&port->lock, flags);
> +
> + port->ccx = FIELD_GET(GAOKUN_CCX_MASK, dcc);
> + port->mux = FIELD_GET(GAOKUN_MUX_MASK, dcc);
> + port->mode = FIELD_GET(GAOKUN_DPAM_MASK, ddi);
> + port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi);
> + port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi);
> +
> + /* Mode and SVID are unused; keeping them to make things clearer */
> + switch (port->mode) {
> + case USBC_DPAM_PAN_C:
> + case USBC_DPAM_PAN_C_REVERSE:
> + port->mode = DP_PIN_ASSIGN_C; /* correct it for usb later */
> + break;
> + case USBC_DPAM_PAN_D:
> + case USBC_DPAM_PAN_D_REVERSE:
> + port->mode = DP_PIN_ASSIGN_D;
> + break;
> + case USBC_DPAM_PAN_E:
> + case USBC_DPAM_PAN_E_REVERSE:
> + port->mode = DP_PIN_ASSIGN_E;
> + break;
> + case USBC_DPAM_PAN_NONE:
> + port->mode = TYPEC_STATE_SAFE;
> + break;
> + default:
> + dev_warn(uec->dev, "unknown mode %d\n", port->mode);
> + break;
> + }
> +
> + switch (port->mux) {
> + case USBC_MUX_NONE:
> + port->svid = 0;
> + break;
> + case USBC_MUX_USB_2L:
> + port->svid = USB_SID_PD;
> + port->mode = TYPEC_STATE_USB; /* same as PAN_C, correct it */
> + break;
> + case USBC_MUX_DP_4L:
> + case USBC_MUX_USB_DP:
> + port->svid = USB_SID_DISPLAYPORT;
> + break;
> + default:
> + dev_warn(uec->dev, "unknown mux state %d\n", port->mux);
> + break;
> + }
> +
> + spin_unlock_irqrestore(&port->lock, flags);
> +}
> +
> +static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec)
> +{
> + struct gaokun_ucsi_reg ureg;
> + int ret, idx;
> +
> + ret = gaokun_ec_ucsi_get_reg(uec->ec, &ureg);
> + if (ret)
> + return GAOKUN_UCSI_NO_PORT_UPDATE;
> +
> + uec->num_ports = ureg.num_ports;
> + idx = GET_IDX(ureg.port_updt);
> +
> + if (idx < 0 || idx >= ureg.num_ports)
> + return GAOKUN_UCSI_NO_PORT_UPDATE;
> +
> + gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data);
> + return idx;
> +}
> +
> +static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port)
> +{
> + struct gaokun_ucsi *uec = port->ucsi;
> + int idx = port->idx;
> +
> + if (idx >= uec->ucsi->cap.num_connectors) {
> + dev_warn(uec->dev, "altmode port out of range: %d\n", idx);
> + return;
> + }
> +
> + /* UCSI callback .connector_status() have set orientation */
> + if (port->bridge)
> + drm_aux_hpd_bridge_notify(&port->bridge->dev,
> + port->hpd_state ?
> + connector_status_connected :
> + connector_status_disconnected);
> +
> + gaokun_ec_ucsi_pan_ack(uec->ec, port->idx);
> +}
> +
> +static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec)
> +{
> + int idx;
> +
> + if (!uec->ucsi->connector) { /* slow to register */
> + dev_err_ratelimited(uec->dev, "ucsi connector is not initialized yet\n");
> + return;
> + }
> +
> + idx = gaokun_ucsi_refresh(uec);
> + if (idx == GAOKUN_UCSI_NO_PORT_UPDATE)
> + gaokun_ec_ucsi_pan_ack(uec->ec, idx); /* ack directly if no update */
> + else
> + gaokun_ucsi_handle_altmode(&uec->ports[idx]);
> +}
> +
> +/*
> + * When inserting, 2 UCSI events(connector change) are followed by USB events.
> + * If we received one USB event, that means USB events are not blocked, so we
> + * can complelte for all ports, and we should signal all events.
> + */
> +static void gaokun_ucsi_complete_usb_ack(struct gaokun_ucsi *uec)
> +{
> + struct gaokun_ucsi_port *port;
> + int idx = 0;
> +
> + while (idx < uec->num_ports) {
> + port = &uec->ports[idx++];
> + if (!completion_done(&port->usb_ack))
> + complete_all(&port->usb_ack);
> + }
> +}
> +
> +/*
> + * USB event is necessary for enabling altmode, the event should follow
> + * UCSI event, if not after timeout(this notify may be disabled somehow),
> + * then force to enable altmode.
> + */
> +static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx)
> +{
> + struct gaokun_ucsi_port *port;
> +
> + port = &uec->ports[idx];
> + if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) {
> + dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT");
> + gaokun_ucsi_altmode_notify_ind(uec);
> + }
> +}
> +
> +static int gaokun_ucsi_notify(struct notifier_block *nb,
> + unsigned long action, void *data)
> +{
> + u32 cci;
> + struct gaokun_ucsi *uec = container_of(nb, struct gaokun_ucsi, nb);
> +
> + switch (action) {
> + case EC_EVENT_USB:
> + gaokun_ucsi_complete_usb_ack(uec);
> + gaokun_ucsi_altmode_notify_ind(uec);
> + return NOTIFY_OK;
> +
> + case EC_EVENT_UCSI:
> + gaokun_ucsi_read_cci(uec->ucsi, &cci);
> + ucsi_notify_common(uec->ucsi, cci);
> + if (UCSI_CCI_CONNECTOR(cci))
> + gaokun_ucsi_handle_no_usb_event(uec, UCSI_CCI_CONNECTOR(cci) - 1);
> +
> + return NOTIFY_OK;
> +
> + default:
> + return NOTIFY_DONE;
> + }
> +}
> +
> +static int gaokun_ucsi_ports_init(struct gaokun_ucsi *uec)
> +{
> + struct gaokun_ucsi_port *ucsi_port;
> + struct gaokun_ucsi_reg ureg = {};
> + struct device *dev = uec->dev;
> + struct fwnode_handle *fwnode;
> + int i, ret, num_ports;
> + u32 port;
> +
> + gaokun_ec_ucsi_get_reg(uec->ec, &ureg);
> + num_ports = ureg.num_ports;
> + uec->ports = devm_kcalloc(dev, num_ports, sizeof(*uec->ports),
> + GFP_KERNEL);
> + if (!uec->ports)
> + return -ENOMEM;
> +
> + for (i = 0; i < num_ports; ++i) {
> + ucsi_port = &uec->ports[i];
> + ucsi_port->ccx = USBC_CCX_NONE;
> + ucsi_port->idx = i;
> + ucsi_port->ucsi = uec;
> + init_completion(&ucsi_port->usb_ack);
> + spin_lock_init(&ucsi_port->lock);
> + }
> +
> + device_for_each_child_node(dev, fwnode) {
> + ret = fwnode_property_read_u32(fwnode, "reg", &port);
> + if (ret < 0) {
> + dev_err(dev, "missing reg property of %pOFn\n", fwnode);
> + fwnode_handle_put(fwnode);
> + return ret;
> + }
> +
> + if (port >= num_ports) {
> + dev_warn(dev, "invalid connector number %d, ignoring\n", port);
> + continue;
> + }
> +
> + ucsi_port = &uec->ports[port];
> + ucsi_port->bridge = devm_drm_dp_hpd_bridge_alloc(dev, to_of_node(fwnode));
> + if (IS_ERR(ucsi_port->bridge)) {
> + fwnode_handle_put(fwnode);
> + return PTR_ERR(ucsi_port->bridge);
> + }
> + }
> +
> + for (i = 0; i < num_ports; i++) {
> + if (!uec->ports[i].bridge)
> + continue;
> +
> + ret = devm_drm_dp_hpd_bridge_add(dev, uec->ports[i].bridge);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void gaokun_ucsi_register_worker(struct work_struct *work)
> +{
> + struct gaokun_ucsi *uec;
> + struct ucsi *ucsi;
> + int ret;
> +
> + uec = container_of(work, struct gaokun_ucsi, work.work);
> + ucsi = uec->ucsi;
> +
> + ret = gaokun_ec_register_notify(uec->ec, &uec->nb);
> + if (ret) {
> + dev_err_probe(ucsi->dev, ret, "notifier register failed\n");
> + return;
> + }
> +
> + ret = ucsi_register(ucsi);
> + if (ret)
> + dev_err_probe(ucsi->dev, ret, "ucsi register failed\n");
> +}
> +
> +static int gaokun_ucsi_probe(struct auxiliary_device *adev,
> + const struct auxiliary_device_id *id)
> +{
> + struct gaokun_ec *ec = adev->dev.platform_data;
> + struct device *dev = &adev->dev;
> + struct gaokun_ucsi *uec;
> + int ret;
> +
> + uec = devm_kzalloc(dev, sizeof(*uec), GFP_KERNEL);
> + if (!uec)
> + return -ENOMEM;
> +
> + uec->ec = ec;
> + uec->dev = dev;
> + uec->version = UCSI_VERSION_1_0;
> + uec->nb.notifier_call = gaokun_ucsi_notify;
> +
> + INIT_DELAYED_WORK(&uec->work, gaokun_ucsi_register_worker);
> +
> + ret = gaokun_ucsi_ports_init(uec);
> + if (ret)
> + return ret;
> +
> + uec->ucsi = ucsi_create(dev, &gaokun_ucsi_ops);
> + if (IS_ERR(uec->ucsi))
> + return PTR_ERR(uec->ucsi);
> +
> + ucsi_set_drvdata(uec->ucsi, uec);
> + auxiliary_set_drvdata(adev, uec);
> +
> + /* EC can't handle UCSI properly in the early stage */
> + schedule_delayed_work(&uec->work, 3 * HZ);
> +
> + return 0;
> +}
> +
> +static void gaokun_ucsi_remove(struct auxiliary_device *adev)
> +{
> + struct gaokun_ucsi *uec = auxiliary_get_drvdata(adev);
> +
> + gaokun_ec_unregister_notify(uec->ec, &uec->nb);
> + ucsi_unregister(uec->ucsi);
> + ucsi_destroy(uec->ucsi);
> +}
> +
> +static const struct auxiliary_device_id gaokun_ucsi_id_table[] = {
> + { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_UCSI, },
> + {}
> +};
> +MODULE_DEVICE_TABLE(auxiliary, gaokun_ucsi_id_table);
> +
> +static struct auxiliary_driver gaokun_ucsi_driver = {
> + .name = GAOKUN_DEV_UCSI,
> + .id_table = gaokun_ucsi_id_table,
> + .probe = gaokun_ucsi_probe,
> + .remove = gaokun_ucsi_remove,
> +};
> +
> +module_auxiliary_driver(gaokun_ucsi_driver);
> +
> +MODULE_DESCRIPTION("HUAWEI Matebook E Go UCSI driver");
> +MODULE_LICENSE("GPL");
> --
> 2.48.1
--
heikki
Powered by blists - more mailing lists