| /* Copyright 2015 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* USB mux high-level driver. */ |
| |
| #include "common.h" |
| #include "console.h" |
| #include "host_command.h" |
| #include "usb_mux.h" |
| #include "util.h" |
| |
| #define CPRINTS(format, args...) cprints(CC_USBCHARGE, format, ## args) |
| #define CPRINTF(format, args...) cprintf(CC_USBCHARGE, format, ## args) |
| |
| static int enable_debug_prints; |
| |
| void usb_mux_init(int port) |
| { |
| const struct usb_mux *mux = &usb_muxes[port]; |
| int res; |
| |
| ASSERT(port >= 0 && port < CONFIG_USB_PD_PORT_COUNT); |
| res = mux->driver->init(mux->port_addr); |
| if (res) |
| CPRINTS("Error initializing mux port(%d): %d", port, res); |
| |
| /* Apply board specific initialization */ |
| if (mux->board_init) |
| mux->board_init(mux); |
| } |
| |
| /* |
| * TODO(crbug.com/505480): Setting muxes often involves I2C transcations, |
| * which can block. Consider implementing an asynchronous task. |
| */ |
| void usb_mux_set(int port, enum typec_mux mux_mode, |
| enum usb_switch usb_mode, int polarity) |
| { |
| const struct usb_mux *mux = &usb_muxes[port]; |
| int res; |
| mux_state_t mux_state; |
| |
| #ifdef CONFIG_USB_CHARGER |
| /* Configure USB2.0 */ |
| usb_charger_set_switches(port, usb_mode); |
| #endif |
| |
| /* Configure superspeed lanes */ |
| mux_state = polarity ? mux_mode | MUX_POLARITY_INVERTED : mux_mode; |
| res = mux->driver->set(mux->port_addr, mux_state); |
| if (res) { |
| CPRINTS("Error setting mux port(%d): %d", port, res); |
| return; |
| } |
| |
| if (enable_debug_prints) |
| CPRINTS( |
| "usb/dp mux: port(%d) typec_mux(%d) usb2(%d) polarity(%d)", |
| port, mux_mode, usb_mode, polarity); |
| } |
| |
| int usb_mux_get(int port, const char **dp_str, const char **usb_str) |
| { |
| const struct usb_mux *mux = &usb_muxes[port]; |
| int res; |
| mux_state_t mux_state; |
| const char *dp, *usb; |
| |
| res = mux->driver->get(mux->port_addr, &mux_state); |
| if (res) { |
| CPRINTS("Error getting mux port(%d): %d", port, res); |
| return 0; |
| } |
| |
| dp = mux_state & MUX_POLARITY_INVERTED ? "DP2" : "DP1"; |
| usb = mux_state & MUX_POLARITY_INVERTED ? "USB2" : "USB1"; |
| |
| *dp_str = mux_state & MUX_DP_ENABLED ? dp : NULL; |
| *usb_str = mux_state & MUX_USB_ENABLED ? usb : NULL; |
| |
| return *dp_str || *usb_str; |
| } |
| |
| void usb_mux_flip(int port) |
| { |
| const struct usb_mux *mux = &usb_muxes[port]; |
| int res; |
| mux_state_t mux_state; |
| |
| res = mux->driver->get(mux->port_addr, &mux_state); |
| if (res) { |
| CPRINTS("Error getting mux port(%d): %d", port, res); |
| return; |
| } |
| |
| if (mux_state & MUX_POLARITY_INVERTED) |
| mux_state &= ~MUX_POLARITY_INVERTED; |
| else |
| mux_state |= MUX_POLARITY_INVERTED; |
| |
| res = mux->driver->set(mux->port_addr, mux_state); |
| if (res) |
| CPRINTS("Error setting mux port(%d): %d", port, res); |
| } |
| |
| #ifdef CONFIG_CMD_TYPEC |
| static int command_typec(int argc, char **argv) |
| { |
| const char * const mux_name[] = {"none", "usb", "dp", "dock"}; |
| char *e; |
| int port; |
| enum typec_mux mux = TYPEC_MUX_NONE; |
| int i; |
| |
| if (argc == 2 && !strcasecmp(argv[1], "debug")) { |
| enable_debug_prints = 1; |
| return EC_SUCCESS; |
| } |
| |
| if (argc < 2) |
| return EC_ERROR_PARAM_COUNT; |
| |
| port = strtoi(argv[1], &e, 10); |
| if (*e || port >= CONFIG_USB_PD_PORT_COUNT) |
| return EC_ERROR_PARAM1; |
| |
| if (argc < 3) { |
| const char *dp_str, *usb_str; |
| ccprintf("Port C%d: polarity:CC%d\n", |
| port, pd_get_polarity(port) + 1); |
| if (usb_mux_get(port, &dp_str, &usb_str)) |
| ccprintf("Superspeed %s%s%s\n", |
| dp_str ? dp_str : "", |
| dp_str && usb_str ? "+" : "", |
| usb_str ? usb_str : ""); |
| else |
| ccprintf("No Superspeed connection\n"); |
| |
| return EC_SUCCESS; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(mux_name); i++) |
| if (!strcasecmp(argv[2], mux_name[i])) |
| mux = i; |
| usb_mux_set(port, mux, mux == TYPEC_MUX_NONE ? |
| USB_SWITCH_DISCONNECT : |
| USB_SWITCH_CONNECT, |
| pd_get_polarity(port)); |
| return EC_SUCCESS; |
| } |
| DECLARE_CONSOLE_COMMAND(typec, command_typec, |
| "[port|debug] [none|usb|dp|dock]", |
| "Control type-C connector muxing"); |
| #endif |
| |
| static int hc_usb_pd_mux_info(struct host_cmd_handler_args *args) |
| { |
| const struct ec_params_usb_pd_mux_info *p = args->params; |
| struct ec_response_usb_pd_mux_info *r = args->response; |
| int port = p->port; |
| const struct usb_mux *mux; |
| |
| if (port >= CONFIG_USB_PD_PORT_COUNT) |
| return EC_RES_INVALID_PARAM; |
| |
| mux = &usb_muxes[port]; |
| if (mux->driver->get(mux->port_addr, &r->flags) != EC_SUCCESS) |
| return EC_RES_ERROR; |
| |
| #ifdef CONFIG_USB_MUX_VIRTUAL |
| /* Clear HPD IRQ event since we're about to inform host of it. */ |
| if ((r->flags & USB_PD_MUX_HPD_IRQ) && |
| mux->driver == &virtual_usb_mux_driver) |
| mux->hpd_update(port, 0, 0); |
| #endif |
| |
| args->response_size = sizeof(*r); |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_USB_PD_MUX_INFO, |
| hc_usb_pd_mux_info, |
| EC_VER_MASK(0)); |