1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * USB Power Delivery Vendor Defined Message (VDM) support code. 4 * 5 * Copyright 2023 Google LLC 6 * Author: Prashant Malani <pmalani@chromium.org> 7 */ 8 9 #include <linux/module.h> 10 #include <linux/platform_data/cros_ec_commands.h> 11 #include <linux/usb/pd_vdo.h> 12 13 #include "cros_ec_typec.h" 14 #include "cros_typec_vdm.h" 15 16 /* 17 * Retrieves pending VDM attention messages from the EC and forwards them to the altmode driver 18 * based on SVID. 19 */ 20 void cros_typec_handle_vdm_attention(struct cros_typec_data *typec, int port_num) 21 { 22 struct ec_response_typec_vdm_response resp; 23 struct ec_params_typec_vdm_response req = { 24 .port = port_num, 25 }; 26 struct typec_altmode *amode; 27 u16 svid; 28 u32 hdr; 29 int ret; 30 31 do { 32 ret = cros_ec_cmd(typec->ec, 0, EC_CMD_TYPEC_VDM_RESPONSE, &req, 33 sizeof(req), &resp, sizeof(resp)); 34 if (ret < 0) { 35 dev_warn(typec->dev, "Failed VDM response fetch, port: %d\n", port_num); 36 return; 37 } 38 39 hdr = resp.vdm_response[0]; 40 svid = PD_VDO_VID(hdr); 41 dev_dbg(typec->dev, "Received VDM Attention header: %x, port: %d\n", hdr, port_num); 42 43 amode = typec_match_altmode(typec->ports[port_num]->port_altmode, 44 CROS_EC_ALTMODE_MAX, svid, PD_VDO_OPOS(hdr)); 45 if (!amode) { 46 dev_err(typec->dev, 47 "Received VDM for unregistered altmode (SVID:%x), port: %d\n", 48 svid, port_num); 49 return; 50 } 51 52 typec_altmode_attention(amode, resp.vdm_attention[1]); 53 } while (resp.vdm_attention_left); 54 } 55 56 /* 57 * Retrieves a VDM response from the EC and forwards it to the altmode driver based on SVID. 58 */ 59 void cros_typec_handle_vdm_response(struct cros_typec_data *typec, int port_num) 60 { 61 struct ec_response_typec_vdm_response resp; 62 struct ec_params_typec_vdm_response req = { 63 .port = port_num, 64 }; 65 struct typec_altmode *amode; 66 u16 svid; 67 u32 hdr; 68 int ret; 69 70 ret = cros_ec_cmd(typec->ec, 0, EC_CMD_TYPEC_VDM_RESPONSE, &req, 71 sizeof(req), &resp, sizeof(resp)); 72 if (ret < 0) { 73 dev_warn(typec->dev, "Failed VDM response fetch, port: %d\n", port_num); 74 return; 75 } 76 77 hdr = resp.vdm_response[0]; 78 svid = PD_VDO_VID(hdr); 79 dev_dbg(typec->dev, "Received VDM header: %x, port: %d\n", hdr, port_num); 80 81 amode = typec_match_altmode(typec->ports[port_num]->port_altmode, CROS_EC_ALTMODE_MAX, 82 svid, PD_VDO_OPOS(hdr)); 83 if (!amode) { 84 dev_err(typec->dev, "Received VDM for unregistered altmode (SVID:%x), port: %d\n", 85 svid, port_num); 86 return; 87 } 88 89 ret = typec_altmode_vdm(amode, hdr, &resp.vdm_response[1], resp.vdm_data_objects); 90 if (ret) 91 dev_err(typec->dev, "Failed to forward VDM to altmode (SVID:%x), port: %d\n", 92 svid, port_num); 93 } 94 95 static int cros_typec_port_amode_enter(struct typec_altmode *amode, u32 *vdo) 96 { 97 struct cros_typec_port *port = typec_altmode_get_drvdata(amode); 98 struct ec_params_typec_control req = { 99 .port = port->port_num, 100 .command = TYPEC_CONTROL_COMMAND_SEND_VDM_REQ, 101 }; 102 struct typec_vdm_req vdm_req = {}; 103 u32 hdr; 104 105 hdr = VDO(amode->svid, 1, SVDM_VER_2_0, CMD_ENTER_MODE); 106 hdr |= VDO_OPOS(amode->mode); 107 108 vdm_req.vdm_data[0] = hdr; 109 vdm_req.vdm_data_objects = 1; 110 vdm_req.partner_type = TYPEC_PARTNER_SOP; 111 req.vdm_req_params = vdm_req; 112 113 dev_dbg(port->typec_data->dev, "Sending EnterMode VDM, hdr: %x, port: %d\n", 114 hdr, port->port_num); 115 116 return cros_ec_cmd(port->typec_data->ec, 0, EC_CMD_TYPEC_CONTROL, &req, 117 sizeof(req), NULL, 0); 118 } 119 120 static int cros_typec_port_amode_vdm(struct typec_altmode *amode, const u32 hdr, 121 const u32 *vdo, int cnt) 122 { 123 struct cros_typec_port *port = typec_altmode_get_drvdata(amode); 124 struct ec_params_typec_control req = { 125 .port = port->port_num, 126 .command = TYPEC_CONTROL_COMMAND_SEND_VDM_REQ, 127 }; 128 struct typec_vdm_req vdm_req = {}; 129 int i; 130 131 vdm_req.vdm_data[0] = hdr; 132 vdm_req.vdm_data_objects = cnt; 133 for (i = 1; i < cnt; i++) 134 vdm_req.vdm_data[i] = vdo[i-1]; 135 vdm_req.partner_type = TYPEC_PARTNER_SOP; 136 req.vdm_req_params = vdm_req; 137 138 dev_dbg(port->typec_data->dev, "Sending VDM, hdr: %x, num_objects: %d, port: %d\n", 139 hdr, cnt, port->port_num); 140 141 return cros_ec_cmd(port->typec_data->ec, 0, EC_CMD_TYPEC_CONTROL, &req, 142 sizeof(req), NULL, 0); 143 } 144 145 const struct typec_altmode_ops port_amode_ops = { 146 .enter = cros_typec_port_amode_enter, 147 .vdm = cros_typec_port_amode_vdm, 148 }; 149