summaryrefslogtreecommitdiff
path: root/src/acpi/acpigen_usb.c
blob: 90a9b77c60214d45a090d2b14903e715b77b585f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include <acpi/acpi.h>
#include <acpi/acpi_device.h>
#include <acpi/acpigen.h>
#include <acpi/acpigen_usb.h>

static const char *power_role_to_str(enum usb_typec_power_role power_role)
{
	switch (power_role) {
	case TYPEC_POWER_ROLE_SOURCE:
		return "source";
	case TYPEC_POWER_ROLE_SINK:
		return "sink";
	case TYPEC_POWER_ROLE_DUAL:
		return "dual";
	default:
		return "unknown";
	}
}

static const char *try_power_role_to_str(enum usb_typec_try_power_role try_power_role)
{
	switch (try_power_role) {
	case TYPEC_TRY_POWER_ROLE_NONE:
		/*
		 * This should never get returned; if there is no try-power role for a device,
		 * then the try-power-role field is not added to the DSD. Thus, this is just
		 * for completeness.
		 */
		return "none";
	case TYPEC_TRY_POWER_ROLE_SINK:
		return "sink";
	case TYPEC_TRY_POWER_ROLE_SOURCE:
		return "source";
	default:
		return "unknown";
	}
}

static const char *data_role_to_str(enum usb_typec_data_role data_role)
{
	switch (data_role) {
	case TYPEC_DATA_ROLE_DFP:
		return "host";
	case TYPEC_DATA_ROLE_UFP:
		return "device";
	case TYPEC_DATA_ROLE_DUAL:
		return "dual";
	default:
		return "unknown";
	}
}

/* Add port capabilities as DP properties */
static void add_port_caps(struct acpi_dp *dsd,
			  const struct typec_connector_class_config *config)
{
	acpi_dp_add_string(dsd, "power-role", power_role_to_str(config->power_role));
	acpi_dp_add_string(dsd, "data-role", data_role_to_str(config->data_role));

	if (config->try_power_role != TYPEC_TRY_POWER_ROLE_NONE)
		acpi_dp_add_string(dsd, "try-power-role",
				   try_power_role_to_str(config->try_power_role));
}

static void add_device_ref(struct acpi_dp *dsd,
			   const char *prop_name,
			   const struct device *dev)
{
	const char *path;
	char *fresh;

	if (!dev)
		return;

	/*
	 * Unfortunately, the acpi_dp_* API doesn't write out the data immediately, thus we need
	 * different storage areas for all of the strings, so strdup() is used for that. It is
	 * safe to use strdup() here, because the strings are generated at build-time and are
	 * guaranteed to be NUL-terminated (they come from the devicetree).
	 */
	path = acpi_device_path(dev);
	if (path) {
		fresh = strdup(path);
		if (fresh)
			acpi_dp_add_reference(dsd, prop_name, fresh);
	}
}

static void add_device_references(struct acpi_dp *dsd,
				  const struct typec_connector_class_config *config)
{
	/*
	 * Add references to the USB port objects so that the consumer of this information can
	 * know whether the port supports USB2, USB3, and/or USB4.
	 */
	add_device_ref(dsd, "usb2-port", config->usb2_port);
	add_device_ref(dsd, "usb3-port", config->usb3_port);
	add_device_ref(dsd, "usb4-port", config->usb4_port);

	/*
	 * Add references to the ACPI device(s) which control the orientation, USB data role and
	 * data muxing.
	 */
	add_device_ref(dsd, "orientation-switch", config->orientation_switch);
	add_device_ref(dsd, "usb-role-switch", config->usb_role_switch);
	add_device_ref(dsd, "mode-switch", config->mode_switch);
}

void acpigen_write_typec_connector(const struct typec_connector_class_config *config,
				   int port_number,
				   add_custom_dsd_property_cb add_custom_dsd_property)
{
	struct acpi_dp *dsd;
	char name[5];

	/* Create a CONx device */
	snprintf(name, sizeof(name), "CON%1X", port_number);
	acpigen_write_device(name);
	acpigen_write_name_integer("_ADR", port_number);

	dsd = acpi_dp_new_table("_DSD");

	/* Write out the _DSD table */
	acpi_dp_add_integer(dsd, "port-number", port_number);
	add_port_caps(dsd, config);
	add_device_references(dsd, config);

	/* Allow client to add custom properties if desired */
	if (add_custom_dsd_property)
		add_custom_dsd_property(dsd, port_number);
	acpi_dp_write(dsd);

	acpigen_pop_len(); /* Device */
}