xref: /aosp_15_r20/external/coreboot/src/acpi/acpigen_usb.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
3 #include <acpi/acpi_device.h>
4 #include <acpi/acpi_pld.h>
5 #include <acpi/acpigen.h>
6 #include <acpi/acpigen_usb.h>
7 #include <device/device.h>
8 #include <stdio.h>
9 
power_role_to_str(enum usb_typec_power_role power_role)10 static const char *power_role_to_str(enum usb_typec_power_role power_role)
11 {
12 	switch (power_role) {
13 	case TYPEC_POWER_ROLE_SOURCE:
14 		return "source";
15 	case TYPEC_POWER_ROLE_SINK:
16 		return "sink";
17 	case TYPEC_POWER_ROLE_DUAL:
18 		return "dual";
19 	default:
20 		return "unknown";
21 	}
22 }
23 
try_power_role_to_str(enum usb_typec_try_power_role try_power_role)24 static const char *try_power_role_to_str(enum usb_typec_try_power_role try_power_role)
25 {
26 	switch (try_power_role) {
27 	case TYPEC_TRY_POWER_ROLE_NONE:
28 		/*
29 		 * This should never get returned; if there is no try-power role for a device,
30 		 * then the try-power-role field is not added to the DSD. Thus, this is just
31 		 * for completeness.
32 		 */
33 		return "none";
34 	case TYPEC_TRY_POWER_ROLE_SINK:
35 		return "sink";
36 	case TYPEC_TRY_POWER_ROLE_SOURCE:
37 		return "source";
38 	default:
39 		return "unknown";
40 	}
41 }
42 
data_role_to_str(enum usb_typec_data_role data_role)43 static const char *data_role_to_str(enum usb_typec_data_role data_role)
44 {
45 	switch (data_role) {
46 	case TYPEC_DATA_ROLE_DFP:
47 		return "host";
48 	case TYPEC_DATA_ROLE_UFP:
49 		return "device";
50 	case TYPEC_DATA_ROLE_DUAL:
51 		return "dual";
52 	default:
53 		return "unknown";
54 	}
55 }
56 
57 /* Add port capabilities as DP properties */
add_port_caps(struct acpi_dp * dsd,const struct typec_connector_class_config * config)58 static void add_port_caps(struct acpi_dp *dsd,
59 			  const struct typec_connector_class_config *config)
60 {
61 	acpi_dp_add_string(dsd, "power-role", power_role_to_str(config->power_role));
62 	acpi_dp_add_string(dsd, "data-role", data_role_to_str(config->data_role));
63 
64 	if (config->try_power_role != TYPEC_TRY_POWER_ROLE_NONE)
65 		acpi_dp_add_string(dsd, "try-power-role",
66 				   try_power_role_to_str(config->try_power_role));
67 }
68 
add_device_ref(struct acpi_dp * dsd,const char * prop_name,const struct device * dev)69 static void add_device_ref(struct acpi_dp *dsd,
70 			   const char *prop_name,
71 			   const struct device *dev)
72 {
73 	const char *path;
74 	char *fresh;
75 
76 	if (!dev || !dev->enabled)
77 		return;
78 
79 	/*
80 	 * Unfortunately, the acpi_dp_* API doesn't write out the data immediately, thus we need
81 	 * different storage areas for all of the strings, so strdup() is used for that. It is
82 	 * safe to use strdup() here, because the strings are generated at build-time and are
83 	 * guaranteed to be NUL-terminated (they come from the devicetree).
84 	 */
85 	path = acpi_device_path(dev);
86 	if (path) {
87 		fresh = strdup(path);
88 		if (fresh)
89 			acpi_dp_add_reference(dsd, prop_name, fresh);
90 	}
91 }
92 
add_device_references(struct acpi_dp * dsd,const struct typec_connector_class_config * config)93 static void add_device_references(struct acpi_dp *dsd,
94 				  const struct typec_connector_class_config *config)
95 {
96 	/*
97 	 * Add references to the USB port objects so that the consumer of this information can
98 	 * know whether the port supports USB2, USB3, and/or USB4.
99 	 */
100 	add_device_ref(dsd, "usb2-port", config->usb2_port);
101 	add_device_ref(dsd, "usb3-port", config->usb3_port);
102 	add_device_ref(dsd, "usb4-port", config->usb4_port);
103 
104 	/*
105 	 * Add references to the ACPI device(s) which control the orientation, USB data role and
106 	 * data muxing.
107 	 */
108 	add_device_ref(dsd, "orientation-switch", config->orientation_switch);
109 	add_device_ref(dsd, "usb-role-switch", config->usb_role_switch);
110 	add_device_ref(dsd, "mode-switch", config->mode_switch);
111 	add_device_ref(dsd, "retimer-switch", config->retimer_switch);
112 }
113 
acpigen_write_typec_connector(const struct typec_connector_class_config * config,int port_number,add_custom_dsd_property_cb add_custom_dsd_property)114 void acpigen_write_typec_connector(const struct typec_connector_class_config *config,
115 				   int port_number,
116 				   add_custom_dsd_property_cb add_custom_dsd_property)
117 {
118 	struct acpi_dp *dsd;
119 	char name[5];
120 
121 	/* Create a CONx device */
122 	snprintf(name, sizeof(name), "CON%1X", port_number);
123 	acpigen_write_device(name);
124 	acpigen_write_name_integer("_ADR", port_number);
125 
126 	dsd = acpi_dp_new_table("_DSD");
127 
128 	/* Write out the _DSD table */
129 	acpi_dp_add_integer(dsd, "port-number", port_number);
130 	add_port_caps(dsd, config);
131 	add_device_references(dsd, config);
132 
133 	/* Allow client to add custom properties if desired */
134 	if (add_custom_dsd_property)
135 		add_custom_dsd_property(dsd, port_number);
136 	acpi_dp_write(dsd);
137 
138 	/* Add PLD */
139 	acpigen_write_pld(config->pld);
140 
141 	acpigen_pop_len(); /* Device */
142 }
143