123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
- * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
- */
- #include <linux/usb/usbpd.h>
- #include <linux/slab.h>
- #include <linux/device.h>
- #include <linux/delay.h>
- #include "dp_usbpd.h"
- #include "dp_debug.h"
- /* DP specific VDM commands */
- #define DP_USBPD_VDM_STATUS 0x10
- #define DP_USBPD_VDM_CONFIGURE 0x11
- /* USBPD-TypeC specific Macros */
- #define VDM_VERSION 0x0
- #define USB_C_DP_SID 0xFF01
- enum dp_usbpd_pin_assignment {
- DP_USBPD_PIN_A,
- DP_USBPD_PIN_B,
- DP_USBPD_PIN_C,
- DP_USBPD_PIN_D,
- DP_USBPD_PIN_E,
- DP_USBPD_PIN_F,
- DP_USBPD_PIN_MAX,
- };
- enum dp_usbpd_events {
- DP_USBPD_EVT_DISCOVER,
- DP_USBPD_EVT_ENTER,
- DP_USBPD_EVT_STATUS,
- DP_USBPD_EVT_CONFIGURE,
- DP_USBPD_EVT_CC_PIN_POLARITY,
- DP_USBPD_EVT_EXIT,
- DP_USBPD_EVT_ATTENTION,
- };
- enum dp_usbpd_alt_mode {
- DP_USBPD_ALT_MODE_NONE = 0,
- DP_USBPD_ALT_MODE_INIT = BIT(0),
- DP_USBPD_ALT_MODE_DISCOVER = BIT(1),
- DP_USBPD_ALT_MODE_ENTER = BIT(2),
- DP_USBPD_ALT_MODE_STATUS = BIT(3),
- DP_USBPD_ALT_MODE_CONFIGURE = BIT(4),
- };
- struct dp_usbpd_capabilities {
- enum dp_usbpd_port port;
- bool receptacle_state;
- u8 ulink_pin_config;
- u8 dlink_pin_config;
- };
- struct dp_usbpd_private {
- bool forced_disconnect;
- u32 vdo;
- struct device *dev;
- struct usbpd *pd;
- struct usbpd_svid_handler svid_handler;
- struct dp_hpd_cb *dp_cb;
- struct dp_usbpd_capabilities cap;
- struct dp_usbpd dp_usbpd;
- enum dp_usbpd_alt_mode alt_mode;
- u32 dp_usbpd_config;
- };
- static const char *dp_usbpd_pin_name(u8 pin)
- {
- switch (pin) {
- case DP_USBPD_PIN_A: return "DP_USBPD_PIN_ASSIGNMENT_A";
- case DP_USBPD_PIN_B: return "DP_USBPD_PIN_ASSIGNMENT_B";
- case DP_USBPD_PIN_C: return "DP_USBPD_PIN_ASSIGNMENT_C";
- case DP_USBPD_PIN_D: return "DP_USBPD_PIN_ASSIGNMENT_D";
- case DP_USBPD_PIN_E: return "DP_USBPD_PIN_ASSIGNMENT_E";
- case DP_USBPD_PIN_F: return "DP_USBPD_PIN_ASSIGNMENT_F";
- default: return "UNKNOWN";
- }
- }
- static const char *dp_usbpd_port_name(enum dp_usbpd_port port)
- {
- switch (port) {
- case DP_USBPD_PORT_NONE: return "DP_USBPD_PORT_NONE";
- case DP_USBPD_PORT_UFP_D: return "DP_USBPD_PORT_UFP_D";
- case DP_USBPD_PORT_DFP_D: return "DP_USBPD_PORT_DFP_D";
- case DP_USBPD_PORT_D_UFP_D: return "DP_USBPD_PORT_D_UFP_D";
- default: return "DP_USBPD_PORT_NONE";
- }
- }
- static const char *dp_usbpd_cmd_name(u8 cmd)
- {
- switch (cmd) {
- case USBPD_SVDM_DISCOVER_MODES: return "USBPD_SVDM_DISCOVER_MODES";
- case USBPD_SVDM_ENTER_MODE: return "USBPD_SVDM_ENTER_MODE";
- case USBPD_SVDM_ATTENTION: return "USBPD_SVDM_ATTENTION";
- case DP_USBPD_VDM_STATUS: return "DP_USBPD_VDM_STATUS";
- case DP_USBPD_VDM_CONFIGURE: return "DP_USBPD_VDM_CONFIGURE";
- default: return "DP_USBPD_VDM_ERROR";
- }
- }
- static void dp_usbpd_init_port(enum dp_usbpd_port *port, u32 in_port)
- {
- switch (in_port) {
- case 0:
- *port = DP_USBPD_PORT_NONE;
- break;
- case 1:
- *port = DP_USBPD_PORT_UFP_D;
- break;
- case 2:
- *port = DP_USBPD_PORT_DFP_D;
- break;
- case 3:
- *port = DP_USBPD_PORT_D_UFP_D;
- break;
- default:
- *port = DP_USBPD_PORT_NONE;
- }
- DP_DEBUG("port:%s\n", dp_usbpd_port_name(*port));
- }
- static void dp_usbpd_get_capabilities(struct dp_usbpd_private *pd)
- {
- struct dp_usbpd_capabilities *cap = &pd->cap;
- u32 buf = pd->vdo;
- int port = buf & 0x3;
- cap->receptacle_state = (buf & BIT(6)) ? true : false;
- cap->dlink_pin_config = (buf >> 8) & 0xff;
- cap->ulink_pin_config = (buf >> 16) & 0xff;
- dp_usbpd_init_port(&cap->port, port);
- }
- static void dp_usbpd_get_status(struct dp_usbpd_private *pd)
- {
- struct dp_usbpd *status = &pd->dp_usbpd;
- u32 buf = pd->vdo;
- int port = buf & 0x3;
- status->low_pow_st = (buf & BIT(2)) ? true : false;
- status->adaptor_dp_en = (buf & BIT(3)) ? true : false;
- status->base.multi_func = (buf & BIT(4)) ? true : false;
- status->usb_config_req = (buf & BIT(5)) ? true : false;
- status->exit_dp_mode = (buf & BIT(6)) ? true : false;
- status->base.hpd_high = (buf & BIT(7)) ? true : false;
- status->base.hpd_irq = (buf & BIT(8)) ? true : false;
- DP_DEBUG("low_pow_st = %d, adaptor_dp_en = %d, multi_func = %d\n",
- status->low_pow_st, status->adaptor_dp_en,
- status->base.multi_func);
- DP_DEBUG("usb_config_req = %d, exit_dp_mode = %d, hpd_high =%d\n",
- status->usb_config_req,
- status->exit_dp_mode, status->base.hpd_high);
- DP_DEBUG("hpd_irq = %d\n", status->base.hpd_irq);
- dp_usbpd_init_port(&status->port, port);
- }
- static u32 dp_usbpd_gen_config_pkt(struct dp_usbpd_private *pd)
- {
- u8 pin_cfg, pin;
- u32 config = 0;
- const u32 ufp_d_config = 0x2, dp_ver = 0x1;
- if (pd->cap.receptacle_state)
- pin_cfg = pd->cap.ulink_pin_config;
- else
- pin_cfg = pd->cap.dlink_pin_config;
- for (pin = DP_USBPD_PIN_A; pin < DP_USBPD_PIN_MAX; pin++) {
- if (pin_cfg & BIT(pin)) {
- if (pd->dp_usbpd.base.multi_func) {
- if (pin == DP_USBPD_PIN_D)
- break;
- } else {
- break;
- }
- }
- }
- if (pin == DP_USBPD_PIN_MAX)
- pin = DP_USBPD_PIN_C;
- DP_DEBUG("pin assignment: %s\n", dp_usbpd_pin_name(pin));
- config |= BIT(pin) << 8;
- config |= (dp_ver << 2);
- config |= ufp_d_config;
- DP_DEBUG("config = 0x%x\n", config);
- return config;
- }
- static void dp_usbpd_send_event(struct dp_usbpd_private *pd,
- enum dp_usbpd_events event)
- {
- u32 config;
- switch (event) {
- case DP_USBPD_EVT_DISCOVER:
- usbpd_send_svdm(pd->pd, USB_C_DP_SID,
- USBPD_SVDM_DISCOVER_MODES,
- SVDM_CMD_TYPE_INITIATOR, 0x0, 0x0, 0x0);
- break;
- case DP_USBPD_EVT_ENTER:
- usbpd_send_svdm(pd->pd, USB_C_DP_SID,
- USBPD_SVDM_ENTER_MODE,
- SVDM_CMD_TYPE_INITIATOR, 0x1, 0x0, 0x0);
- break;
- case DP_USBPD_EVT_EXIT:
- usbpd_send_svdm(pd->pd, USB_C_DP_SID,
- USBPD_SVDM_EXIT_MODE,
- SVDM_CMD_TYPE_INITIATOR, 0x1, 0x0, 0x0);
- break;
- case DP_USBPD_EVT_STATUS:
- config = 0x1; /* DFP_D connected */
- usbpd_send_svdm(pd->pd, USB_C_DP_SID, DP_USBPD_VDM_STATUS,
- SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1);
- break;
- case DP_USBPD_EVT_CONFIGURE:
- config = dp_usbpd_gen_config_pkt(pd);
- usbpd_send_svdm(pd->pd, USB_C_DP_SID, DP_USBPD_VDM_CONFIGURE,
- SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1);
- break;
- default:
- DP_ERR("unknown event:%d\n", event);
- }
- }
- static void dp_usbpd_connect_cb(struct usbpd_svid_handler *hdlr,
- bool peer_usb_comm)
- {
- struct dp_usbpd_private *pd;
- pd = container_of(hdlr, struct dp_usbpd_private, svid_handler);
- if (!pd) {
- DP_ERR("get_usbpd phandle failed\n");
- return;
- }
- DP_DEBUG("peer_usb_comm: %d\n", peer_usb_comm);
- pd->dp_usbpd.base.peer_usb_comm = peer_usb_comm;
- dp_usbpd_send_event(pd, DP_USBPD_EVT_DISCOVER);
- }
- static void dp_usbpd_disconnect_cb(struct usbpd_svid_handler *hdlr)
- {
- struct dp_usbpd_private *pd;
- pd = container_of(hdlr, struct dp_usbpd_private, svid_handler);
- if (!pd) {
- DP_ERR("get_usbpd phandle failed\n");
- return;
- }
- pd->alt_mode = DP_USBPD_ALT_MODE_NONE;
- pd->dp_usbpd.base.alt_mode_cfg_done = false;
- DP_DEBUG("\n");
- if (pd->dp_cb && pd->dp_cb->disconnect)
- pd->dp_cb->disconnect(pd->dev);
- }
- static int dp_usbpd_validate_callback(u8 cmd,
- enum usbpd_svdm_cmd_type cmd_type, int num_vdos)
- {
- int ret = 0;
- if (cmd_type == SVDM_CMD_TYPE_RESP_NAK) {
- DP_ERR("error: NACK\n");
- ret = -EINVAL;
- goto end;
- }
- if (cmd_type == SVDM_CMD_TYPE_RESP_BUSY) {
- DP_ERR("error: BUSY\n");
- ret = -EBUSY;
- goto end;
- }
- if (cmd == USBPD_SVDM_ATTENTION) {
- if (cmd_type != SVDM_CMD_TYPE_INITIATOR) {
- DP_ERR("error: invalid cmd type for attention\n");
- ret = -EINVAL;
- goto end;
- }
- if (!num_vdos) {
- DP_ERR("error: no vdo provided\n");
- ret = -EINVAL;
- goto end;
- }
- } else {
- if (cmd_type != SVDM_CMD_TYPE_RESP_ACK) {
- DP_ERR("error: invalid cmd type\n");
- ret = -EINVAL;
- }
- }
- end:
- return ret;
- }
- static int dp_usbpd_get_ss_lanes(struct dp_usbpd_private *pd)
- {
- int rc = 0;
- int timeout = 250;
- /*
- * By default, USB reserves two lanes for Super Speed.
- * Which means DP has remaining two lanes to operate on.
- * If multi-function is not supported, request USB to
- * release the Super Speed lanes so that DP can use
- * all four lanes in case DPCD indicates support for
- * four lanes.
- */
- if (!pd->dp_usbpd.base.multi_func) {
- while (timeout) {
- rc = pd->svid_handler.request_usb_ss_lane(
- pd->pd, &pd->svid_handler);
- if (rc != -EBUSY)
- break;
- DP_WARN("USB busy, retry\n");
- /* wait for hw recommended delay for usb */
- msleep(20);
- timeout--;
- }
- }
- return rc;
- }
- static void dp_usbpd_response_cb(struct usbpd_svid_handler *hdlr, u8 cmd,
- enum usbpd_svdm_cmd_type cmd_type,
- const u32 *vdos, int num_vdos)
- {
- struct dp_usbpd_private *pd;
- int rc = 0;
- pd = container_of(hdlr, struct dp_usbpd_private, svid_handler);
- DP_DEBUG("callback -> cmd: %s, *vdos = 0x%x, num_vdos = %d\n",
- dp_usbpd_cmd_name(cmd), *vdos, num_vdos);
- if (dp_usbpd_validate_callback(cmd, cmd_type, num_vdos)) {
- DP_DEBUG("invalid callback received\n");
- return;
- }
- switch (cmd) {
- case USBPD_SVDM_DISCOVER_MODES:
- pd->vdo = *vdos;
- dp_usbpd_get_capabilities(pd);
- pd->alt_mode |= DP_USBPD_ALT_MODE_DISCOVER;
- if (pd->cap.port & BIT(0))
- dp_usbpd_send_event(pd, DP_USBPD_EVT_ENTER);
- break;
- case USBPD_SVDM_ENTER_MODE:
- pd->alt_mode |= DP_USBPD_ALT_MODE_ENTER;
- dp_usbpd_send_event(pd, DP_USBPD_EVT_STATUS);
- break;
- case USBPD_SVDM_ATTENTION:
- if (pd->forced_disconnect)
- break;
- pd->vdo = *vdos;
- dp_usbpd_get_status(pd);
- if (!pd->dp_usbpd.base.alt_mode_cfg_done) {
- if (pd->dp_usbpd.port & BIT(1))
- dp_usbpd_send_event(pd, DP_USBPD_EVT_CONFIGURE);
- break;
- }
- if (pd->dp_cb && pd->dp_cb->attention)
- pd->dp_cb->attention(pd->dev);
- break;
- case DP_USBPD_VDM_STATUS:
- pd->vdo = *vdos;
- dp_usbpd_get_status(pd);
- if (!(pd->alt_mode & DP_USBPD_ALT_MODE_CONFIGURE)) {
- pd->alt_mode |= DP_USBPD_ALT_MODE_STATUS;
- if (pd->dp_usbpd.port & BIT(1))
- dp_usbpd_send_event(pd, DP_USBPD_EVT_CONFIGURE);
- }
- break;
- case DP_USBPD_VDM_CONFIGURE:
- pd->alt_mode |= DP_USBPD_ALT_MODE_CONFIGURE;
- pd->dp_usbpd.base.alt_mode_cfg_done = true;
- pd->forced_disconnect = false;
- dp_usbpd_get_status(pd);
- pd->dp_usbpd.base.orientation =
- usbpd_get_plug_orientation(pd->pd);
- rc = dp_usbpd_get_ss_lanes(pd);
- if (rc) {
- DP_ERR("failed to get SuperSpeed lanes\n");
- break;
- }
- if (pd->dp_cb && pd->dp_cb->configure)
- pd->dp_cb->configure(pd->dev);
- break;
- default:
- DP_ERR("unknown cmd: %d\n", cmd);
- break;
- }
- }
- static int dp_usbpd_simulate_connect(struct dp_hpd *dp_hpd, bool hpd)
- {
- int rc = 0;
- struct dp_usbpd *dp_usbpd;
- struct dp_usbpd_private *pd;
- if (!dp_hpd) {
- DP_ERR("invalid input\n");
- rc = -EINVAL;
- goto error;
- }
- dp_usbpd = container_of(dp_hpd, struct dp_usbpd, base);
- pd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd);
- dp_usbpd->base.hpd_high = hpd;
- pd->forced_disconnect = !hpd;
- pd->dp_usbpd.base.alt_mode_cfg_done = hpd;
- DP_DEBUG("hpd_high=%d, forced_disconnect=%d, orientation=%d\n",
- dp_usbpd->base.hpd_high, pd->forced_disconnect,
- pd->dp_usbpd.base.orientation);
- if (hpd)
- pd->dp_cb->configure(pd->dev);
- else
- pd->dp_cb->disconnect(pd->dev);
- error:
- return rc;
- }
- static int dp_usbpd_simulate_attention(struct dp_hpd *dp_hpd, int vdo)
- {
- int rc = 0;
- struct dp_usbpd *dp_usbpd;
- struct dp_usbpd_private *pd;
- dp_usbpd = container_of(dp_hpd, struct dp_usbpd, base);
- if (!dp_usbpd) {
- DP_ERR("invalid input\n");
- rc = -EINVAL;
- goto error;
- }
- pd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd);
- pd->vdo = vdo;
- dp_usbpd_get_status(pd);
- if (pd->dp_cb && pd->dp_cb->attention)
- pd->dp_cb->attention(pd->dev);
- error:
- return rc;
- }
- int dp_usbpd_register(struct dp_hpd *dp_hpd)
- {
- struct dp_usbpd *dp_usbpd;
- struct dp_usbpd_private *usbpd;
- int rc = 0;
- if (!dp_hpd)
- return -EINVAL;
- dp_usbpd = container_of(dp_hpd, struct dp_usbpd, base);
- usbpd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd);
- rc = usbpd_register_svid(usbpd->pd, &usbpd->svid_handler);
- if (rc)
- DP_ERR("pd registration failed\n");
- return rc;
- }
- static void dp_usbpd_wakeup_phy(struct dp_hpd *dp_hpd, bool wakeup)
- {
- struct dp_usbpd *dp_usbpd;
- struct dp_usbpd_private *usbpd;
- dp_usbpd = container_of(dp_hpd, struct dp_usbpd, base);
- usbpd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd);
- if (!usbpd->pd) {
- DP_ERR("usbpd pointer invalid");
- return;
- }
- usbpd_vdm_in_suspend(usbpd->pd, wakeup);
- }
- struct dp_hpd *dp_usbpd_get(struct device *dev, struct dp_hpd_cb *cb)
- {
- int rc = 0;
- const char *pd_phandle = "qcom,dp-usbpd-detection";
- struct usbpd *pd = NULL;
- struct dp_usbpd_private *usbpd;
- struct dp_usbpd *dp_usbpd;
- struct usbpd_svid_handler svid_handler = {
- .svid = USB_C_DP_SID,
- .vdm_received = NULL,
- .connect = &dp_usbpd_connect_cb,
- .svdm_received = &dp_usbpd_response_cb,
- .disconnect = &dp_usbpd_disconnect_cb,
- };
- if (!cb) {
- DP_ERR("invalid cb data\n");
- rc = -EINVAL;
- goto error;
- }
- pd = devm_usbpd_get_by_phandle(dev, pd_phandle);
- if (IS_ERR(pd)) {
- DP_DEBUG("usbpd phandle failed (%ld)\n", PTR_ERR(pd));
- rc = PTR_ERR(pd);
- goto error;
- }
- usbpd = devm_kzalloc(dev, sizeof(*usbpd), GFP_KERNEL);
- if (!usbpd) {
- rc = -ENOMEM;
- goto error;
- }
- usbpd->dev = dev;
- usbpd->pd = pd;
- usbpd->svid_handler = svid_handler;
- usbpd->dp_cb = cb;
- dp_usbpd = &usbpd->dp_usbpd;
- dp_usbpd->base.simulate_connect = dp_usbpd_simulate_connect;
- dp_usbpd->base.simulate_attention = dp_usbpd_simulate_attention;
- dp_usbpd->base.register_hpd = dp_usbpd_register;
- dp_usbpd->base.wakeup_phy = dp_usbpd_wakeup_phy;
- return &dp_usbpd->base;
- error:
- return ERR_PTR(rc);
- }
- void dp_usbpd_put(struct dp_hpd *dp_hpd)
- {
- struct dp_usbpd *dp_usbpd;
- struct dp_usbpd_private *usbpd;
- dp_usbpd = container_of(dp_hpd, struct dp_usbpd, base);
- if (!dp_usbpd)
- return;
- usbpd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd);
- usbpd_unregister_svid(usbpd->pd, &usbpd->svid_handler);
- devm_kfree(usbpd->dev, usbpd);
- }
|