Commit a0881596 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'tag-chrome-platform-for-v5.11' of...

Merge tag 'tag-chrome-platform-for-v5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux

Pull chrome platform updates from Benson Leung:
 "cros_ec_typec:

   - A series from Prashant for Type-C to implement TYPEC_STATUS,
     parsing USB PD Partner ID VDOs, and registering partner altmodes.

  cros_ec misc:

   - Don't treat RTC events as wakeup sources in cros_ec_proto"

* tag 'tag-chrome-platform-for-v5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux:
  platform/chrome: cros_ec_typec: Tolerate unrecognized mux flags
  platform/chrome: cros_ec_typec: Register partner altmodes
  platform/chrome: cros_ec_typec: Parse partner PD ID VDOs
  platform/chrome: cros_ec_typec: Introduce TYPEC_STATUS
  platform/chrome: cros_ec: Import Type C host commands
  platform/chrome: cros_ec_typec: Clear partner identity on device removal
  platform/chrome: cros_ec_typec: Fix remove partner logic
  platform/chrome: cros_ec_typec: Relocate set_port_params_v*() functions
  platform/chrome: Don't treat RTC events as wakeup sources
parents 6755f456 6ae9b5ff
Loading
Loading
Loading
Loading
+9 −5
Original line number Diff line number Diff line
@@ -742,13 +742,17 @@ int cros_ec_get_next_event(struct cros_ec_device *ec_dev,
		 * Sensor events need to be parsed by the sensor sub-device.
		 * Defer them, and don't report the wakeup here.
		 */
		if (event_type == EC_MKBP_EVENT_SENSOR_FIFO)
		if (event_type == EC_MKBP_EVENT_SENSOR_FIFO) {
			*wake_event = false;
		} else if (host_event) {
			/* rtc_update_irq() already handles wakeup events. */
			if (host_event & EC_HOST_EVENT_MASK(EC_HOST_EVENT_RTC))
				*wake_event = false;
			/* Masked host-events should not count as wake events. */
		else if (host_event &&
			 !(host_event & ec_dev->host_event_wake_mask))
			if (!(host_event & ec_dev->host_event_wake_mask))
				*wake_event = false;
		}
	}

	return ret;
}
+263 −73
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@
 */

#include <linux/acpi.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_data/cros_ec_commands.h>
@@ -14,6 +15,7 @@
#include <linux/platform_data/cros_usbpd_notify.h>
#include <linux/platform_device.h>
#include <linux/usb/pd.h>
#include <linux/usb/pd_vdo.h>
#include <linux/usb/typec.h>
#include <linux/usb/typec_altmode.h>
#include <linux/usb/typec_dp.h>
@@ -30,6 +32,12 @@ enum {
	CROS_EC_ALTMODE_MAX,
};

/* Container for altmode pointer nodes. */
struct cros_typec_altmode_node {
	struct typec_altmode *amode;
	struct list_head list;
};

/* Per port data. */
struct cros_typec_port {
	struct typec_port *port;
@@ -48,6 +56,11 @@ struct cros_typec_port {

	/* Port alt modes. */
	struct typec_altmode p_altmode[CROS_EC_ALTMODE_MAX];

	/* Flag indicating that PD discovery data parsing is completed. */
	bool disc_done;
	struct ec_response_typec_discovery *sop_disc;
	struct list_head partner_mode_list;
};

/* Platform-specific data for the Chrome OS EC Type C controller. */
@@ -60,6 +73,7 @@ struct cros_typec_data {
	struct cros_typec_port *ports[EC_USB_PD_MAX_PORTS];
	struct notifier_block nb;
	struct work_struct port_work;
	bool typec_cmd_supported;
};

static int cros_typec_parse_port_props(struct typec_capability *cap,
@@ -166,11 +180,25 @@ static int cros_typec_add_partner(struct cros_typec_data *typec, int port_num,
	return ret;
}

static void cros_typec_unregister_altmodes(struct cros_typec_data *typec, int port_num)
{
	struct cros_typec_port *port = typec->ports[port_num];
	struct cros_typec_altmode_node *node, *tmp;

	list_for_each_entry_safe(node, tmp, &port->partner_mode_list, list) {
		list_del(&node->list);
		typec_unregister_altmode(node->amode);
		devm_kfree(typec->dev, node);
	}
}

static void cros_typec_remove_partner(struct cros_typec_data *typec,
				     int port_num)
{
	struct cros_typec_port *port = typec->ports[port_num];

	cros_typec_unregister_altmodes(typec, port_num);

	port->state.alt = NULL;
	port->state.mode = TYPEC_STATE_USB;
	port->state.data = NULL;
@@ -181,6 +209,8 @@ static void cros_typec_remove_partner(struct cros_typec_data *typec,

	typec_unregister_partner(port->partner);
	port->partner = NULL;
	memset(&port->p_identity, 0, sizeof(port->p_identity));
	port->disc_done = false;
}

static void cros_unregister_ports(struct cros_typec_data *typec)
@@ -190,7 +220,10 @@ static void cros_unregister_ports(struct cros_typec_data *typec)
	for (i = 0; i < typec->num_ports; i++) {
		if (!typec->ports[i])
			continue;

		if (typec->ports[i]->partner)
			cros_typec_remove_partner(typec, i);

		usb_role_switch_put(typec->ports[i]->role_sw);
		typec_switch_put(typec->ports[i]->ori_sw);
		typec_mux_put(typec->ports[i]->mux);
@@ -289,6 +322,14 @@ static int cros_typec_init_ports(struct cros_typec_data *typec)
				port_num);

		cros_typec_register_port_altmodes(typec, port_num);

		cros_port->sop_disc = devm_kzalloc(dev, EC_PROTO2_MAX_RESPONSE_SIZE, GFP_KERNEL);
		if (!cros_port->sop_disc) {
			ret = -ENOMEM;
			goto unregister_ports;
		}

		INIT_LIST_HEAD(&cros_port->partner_mode_list);
	}

	return 0;
@@ -329,74 +370,6 @@ static int cros_typec_ec_command(struct cros_typec_data *typec,
	return ret;
}

static void cros_typec_set_port_params_v0(struct cros_typec_data *typec,
		int port_num, struct ec_response_usb_pd_control *resp)
{
	struct typec_port *port = typec->ports[port_num]->port;
	enum typec_orientation polarity;

	if (!resp->enabled)
		polarity = TYPEC_ORIENTATION_NONE;
	else if (!resp->polarity)
		polarity = TYPEC_ORIENTATION_NORMAL;
	else
		polarity = TYPEC_ORIENTATION_REVERSE;

	typec_set_pwr_role(port, resp->role ? TYPEC_SOURCE : TYPEC_SINK);
	typec_set_orientation(port, polarity);
}

static void cros_typec_set_port_params_v1(struct cros_typec_data *typec,
		int port_num, struct ec_response_usb_pd_control_v1 *resp)
{
	struct typec_port *port = typec->ports[port_num]->port;
	enum typec_orientation polarity;
	bool pd_en;
	int ret;

	if (!(resp->enabled & PD_CTRL_RESP_ENABLED_CONNECTED))
		polarity = TYPEC_ORIENTATION_NONE;
	else if (!resp->polarity)
		polarity = TYPEC_ORIENTATION_NORMAL;
	else
		polarity = TYPEC_ORIENTATION_REVERSE;
	typec_set_orientation(port, polarity);
	typec_set_data_role(port, resp->role & PD_CTRL_RESP_ROLE_DATA ?
			TYPEC_HOST : TYPEC_DEVICE);
	typec_set_pwr_role(port, resp->role & PD_CTRL_RESP_ROLE_POWER ?
			TYPEC_SOURCE : TYPEC_SINK);
	typec_set_vconn_role(port, resp->role & PD_CTRL_RESP_ROLE_VCONN ?
			TYPEC_SOURCE : TYPEC_SINK);

	/* Register/remove partners when a connect/disconnect occurs. */
	if (resp->enabled & PD_CTRL_RESP_ENABLED_CONNECTED) {
		if (typec->ports[port_num]->partner)
			return;

		pd_en = resp->enabled & PD_CTRL_RESP_ENABLED_PD_CAPABLE;
		ret = cros_typec_add_partner(typec, port_num, pd_en);
		if (ret)
			dev_warn(typec->dev,
				 "Failed to register partner on port: %d\n",
				 port_num);
	} else {
		if (!typec->ports[port_num]->partner)
			return;
		cros_typec_remove_partner(typec, port_num);
	}
}

static int cros_typec_get_mux_info(struct cros_typec_data *typec, int port_num,
				   struct ec_response_usb_pd_mux_info *resp)
{
	struct ec_params_usb_pd_mux_info req = {
		.port = port_num,
	};

	return cros_typec_ec_command(typec, 0, EC_CMD_USB_PD_MUX_INFO, &req,
				     sizeof(req), resp, sizeof(*resp));
}

static int cros_typec_usb_safe_state(struct cros_typec_port *port)
{
	port->state.mode = TYPEC_STATE_SAFE;
@@ -563,15 +536,210 @@ static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num,
		port->state.mode = TYPEC_STATE_USB;
		ret = typec_mux_set(port->mux, &port->state);
	} else {
		dev_info(typec->dev,
			 "Unsupported mode requested, mux flags: %x\n",
		dev_dbg(typec->dev,
			"Unrecognized mode requested, mux flags: %x\n",
			mux_flags);
		ret = -ENOTSUPP;
	}

	return ret;
}

static void cros_typec_set_port_params_v0(struct cros_typec_data *typec,
		int port_num, struct ec_response_usb_pd_control *resp)
{
	struct typec_port *port = typec->ports[port_num]->port;
	enum typec_orientation polarity;

	if (!resp->enabled)
		polarity = TYPEC_ORIENTATION_NONE;
	else if (!resp->polarity)
		polarity = TYPEC_ORIENTATION_NORMAL;
	else
		polarity = TYPEC_ORIENTATION_REVERSE;

	typec_set_pwr_role(port, resp->role ? TYPEC_SOURCE : TYPEC_SINK);
	typec_set_orientation(port, polarity);
}

static void cros_typec_set_port_params_v1(struct cros_typec_data *typec,
		int port_num, struct ec_response_usb_pd_control_v1 *resp)
{
	struct typec_port *port = typec->ports[port_num]->port;
	enum typec_orientation polarity;
	bool pd_en;
	int ret;

	if (!(resp->enabled & PD_CTRL_RESP_ENABLED_CONNECTED))
		polarity = TYPEC_ORIENTATION_NONE;
	else if (!resp->polarity)
		polarity = TYPEC_ORIENTATION_NORMAL;
	else
		polarity = TYPEC_ORIENTATION_REVERSE;
	typec_set_orientation(port, polarity);
	typec_set_data_role(port, resp->role & PD_CTRL_RESP_ROLE_DATA ?
			TYPEC_HOST : TYPEC_DEVICE);
	typec_set_pwr_role(port, resp->role & PD_CTRL_RESP_ROLE_POWER ?
			TYPEC_SOURCE : TYPEC_SINK);
	typec_set_vconn_role(port, resp->role & PD_CTRL_RESP_ROLE_VCONN ?
			TYPEC_SOURCE : TYPEC_SINK);

	/* Register/remove partners when a connect/disconnect occurs. */
	if (resp->enabled & PD_CTRL_RESP_ENABLED_CONNECTED) {
		if (typec->ports[port_num]->partner)
			return;

		pd_en = resp->enabled & PD_CTRL_RESP_ENABLED_PD_CAPABLE;
		ret = cros_typec_add_partner(typec, port_num, pd_en);
		if (ret)
			dev_warn(typec->dev,
				 "Failed to register partner on port: %d\n",
				 port_num);
	} else {
		if (!typec->ports[port_num]->partner)
			return;
		cros_typec_remove_partner(typec, port_num);
	}
}

static int cros_typec_get_mux_info(struct cros_typec_data *typec, int port_num,
				   struct ec_response_usb_pd_mux_info *resp)
{
	struct ec_params_usb_pd_mux_info req = {
		.port = port_num,
	};

	return cros_typec_ec_command(typec, 0, EC_CMD_USB_PD_MUX_INFO, &req,
				     sizeof(req), resp, sizeof(*resp));
}

static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_num)
{
	struct cros_typec_port *port = typec->ports[port_num];
	struct ec_response_typec_discovery *sop_disc = port->sop_disc;
	struct cros_typec_altmode_node *node;
	struct typec_altmode_desc desc;
	struct typec_altmode *amode;
	int ret = 0;
	int i, j;

	for (i = 0; i < sop_disc->svid_count; i++) {
		for (j = 0; j < sop_disc->svids[i].mode_count; j++) {
			memset(&desc, 0, sizeof(desc));
			desc.svid = sop_disc->svids[i].svid;
			desc.mode = j;
			desc.vdo = sop_disc->svids[i].mode_vdo[j];

			amode = typec_partner_register_altmode(port->partner, &desc);
			if (IS_ERR(amode)) {
				ret = PTR_ERR(amode);
				goto err_cleanup;
			}

			/* If no memory is available we should unregister and exit. */
			node = devm_kzalloc(typec->dev, sizeof(*node), GFP_KERNEL);
			if (!node) {
				ret = -ENOMEM;
				typec_unregister_altmode(amode);
				goto err_cleanup;
			}

			node->amode = amode;
			list_add_tail(&node->list, &port->partner_mode_list);
		}
	}

	return 0;

err_cleanup:
	cros_typec_unregister_altmodes(typec, port_num);
	return ret;
}

static int cros_typec_handle_sop_disc(struct cros_typec_data *typec, int port_num)
{
	struct cros_typec_port *port = typec->ports[port_num];
	struct ec_response_typec_discovery *sop_disc = port->sop_disc;
	struct ec_params_typec_discovery req = {
		.port = port_num,
		.partner_type = TYPEC_PARTNER_SOP,
	};
	int ret = 0;
	int i;

	if (!port->partner) {
		dev_err(typec->dev,
			"SOP Discovery received without partner registered, port: %d\n",
			port_num);
		ret = -EINVAL;
		goto disc_exit;
	}

	memset(sop_disc, 0, EC_PROTO2_MAX_RESPONSE_SIZE);
	ret = cros_typec_ec_command(typec, 0, EC_CMD_TYPEC_DISCOVERY, &req, sizeof(req),
				    sop_disc, EC_PROTO2_MAX_RESPONSE_SIZE);
	if (ret < 0) {
		dev_err(typec->dev, "Failed to get SOP discovery data for port: %d\n", port_num);
		goto disc_exit;
	}

	/* First, update the PD identity VDOs for the partner. */
	if (sop_disc->identity_count > 0)
		port->p_identity.id_header = sop_disc->discovery_vdo[0];
	if (sop_disc->identity_count > 1)
		port->p_identity.cert_stat = sop_disc->discovery_vdo[1];
	if (sop_disc->identity_count > 2)
		port->p_identity.product = sop_disc->discovery_vdo[2];

	/* Copy the remaining identity VDOs till a maximum of 6. */
	for (i = 3; i < sop_disc->identity_count && i < VDO_MAX_OBJECTS; i++)
		port->p_identity.vdo[i - 3] = sop_disc->discovery_vdo[i];

	ret = typec_partner_set_identity(port->partner);
	if (ret < 0) {
		dev_err(typec->dev, "Failed to update partner PD identity, port: %d\n", port_num);
		goto disc_exit;
	}

	ret = cros_typec_register_altmodes(typec, port_num);
	if (ret < 0) {
		dev_err(typec->dev, "Failed to register partner altmodes, port: %d\n", port_num);
		goto disc_exit;
	}

disc_exit:
	return ret;
}

static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num)
{
	struct ec_response_typec_status resp;
	struct ec_params_typec_status req = {
		.port = port_num,
	};
	int ret;

	ret = cros_typec_ec_command(typec, 0, EC_CMD_TYPEC_STATUS, &req, sizeof(req),
				    &resp, sizeof(resp));
	if (ret < 0) {
		dev_warn(typec->dev, "EC_CMD_TYPEC_STATUS failed for port: %d\n", port_num);
		return;
	}

	if (typec->ports[port_num]->disc_done)
		return;

	/* Handle any events appropriately. */
	if (resp.events & PD_STATUS_EVENT_SOP_DISC_DONE) {
		ret = cros_typec_handle_sop_disc(typec, port_num);
		if (ret < 0) {
			dev_err(typec->dev, "Couldn't parse SOP Disc data, port: %d\n", port_num);
			return;
		}

		typec->ports[port_num]->disc_done = true;
	}
}

static int cros_typec_port_update(struct cros_typec_data *typec, int port_num)
{
	struct ec_params_usb_pd_control req;
@@ -608,6 +776,9 @@ static int cros_typec_port_update(struct cros_typec_data *typec, int port_num)
		cros_typec_set_port_params_v0(typec, port_num,
			(struct ec_response_usb_pd_control *) &resp);

	if (typec->typec_cmd_supported)
		cros_typec_handle_status(typec, port_num);

	/* Update the switches if they exist, according to requested state */
	ret = cros_typec_get_mux_info(typec, port_num, &mux_resp);
	if (ret < 0) {
@@ -656,6 +827,23 @@ static int cros_typec_get_cmd_version(struct cros_typec_data *typec)
	return 0;
}

/* Check the EC feature flags to see if TYPEC_* commands are supported. */
static int cros_typec_cmds_supported(struct cros_typec_data *typec)
{
	struct ec_response_get_features resp = {};
	int ret;

	ret = cros_typec_ec_command(typec, 0, EC_CMD_GET_FEATURES, NULL, 0,
				    &resp, sizeof(resp));
	if (ret < 0) {
		dev_warn(typec->dev,
			 "Failed to get features, assuming typec commands unsupported.\n");
		return 0;
	}

	return resp.flags[EC_FEATURE_TYPEC_CMD / 32] & EC_FEATURE_MASK_1(EC_FEATURE_TYPEC_CMD);
}

static void cros_typec_port_work(struct work_struct *work)
{
	struct cros_typec_data *typec = container_of(work, struct cros_typec_data, port_work);
@@ -715,6 +903,8 @@ static int cros_typec_probe(struct platform_device *pdev)
		return ret;
	}

	typec->typec_cmd_supported = !!cros_typec_cmds_supported(typec);

	ret = cros_typec_ec_command(typec, 0, EC_CMD_USB_PD_PORTS, NULL, 0,
				    &resp, sizeof(resp));
	if (ret < 0)
+155 −0
Original line number Diff line number Diff line
@@ -1284,6 +1284,8 @@ enum ec_feature_code {
	EC_FEATURE_SCP = 39,
	/* The MCU is an Integrated Sensor Hub */
	EC_FEATURE_ISH = 40,
	/* New TCPMv2 TYPEC_ prefaced commands supported */
	EC_FEATURE_TYPEC_CMD = 41,
};

#define EC_FEATURE_MASK_0(event_code) BIT(event_code % 32)
@@ -5528,6 +5530,159 @@ struct ec_response_regulator_get_voltage {
	uint32_t voltage_mv;
} __ec_align4;

/*
 * Gather all discovery information for the given port and partner type.
 *
 * Note that if discovery has not yet completed, only the currently completed
 * responses will be filled in.   If the discovery data structures are changed
 * in the process of the command running, BUSY will be returned.
 *
 * VDO field sizes are set to the maximum possible number of VDOs a VDM may
 * contain, while the number of SVIDs here is selected to fit within the PROTO2
 * maximum parameter size.
 */
#define EC_CMD_TYPEC_DISCOVERY 0x0131

enum typec_partner_type {
	TYPEC_PARTNER_SOP = 0,
	TYPEC_PARTNER_SOP_PRIME = 1,
};

struct ec_params_typec_discovery {
	uint8_t port;
	uint8_t partner_type; /* enum typec_partner_type */
} __ec_align1;

struct svid_mode_info {
	uint16_t svid;
	uint16_t mode_count;  /* Number of modes partner sent */
	uint32_t mode_vdo[6]; /* Max VDOs allowed after VDM header is 6 */
};

struct ec_response_typec_discovery {
	uint8_t identity_count;    /* Number of identity VDOs partner sent */
	uint8_t svid_count;	   /* Number of SVIDs partner sent */
	uint16_t reserved;
	uint32_t discovery_vdo[6]; /* Max VDOs allowed after VDM header is 6 */
	struct svid_mode_info svids[0];
} __ec_align1;

/*
 * Gather all status information for a port.
 *
 * Note: this covers many of the return fields from the deprecated
 * EC_CMD_USB_PD_CONTROL command, except those that are redundant with the
 * discovery data.  The "enum pd_cc_states" is defined with the deprecated
 * EC_CMD_USB_PD_CONTROL command.
 *
 * This also combines in the EC_CMD_USB_PD_MUX_INFO flags.
 */
#define EC_CMD_TYPEC_STATUS 0x0133

/*
 * Power role.
 *
 * Note this is also used for PD header creation, and values align to those in
 * the Power Delivery Specification Revision 3.0 (See
 * 6.2.1.1.4 Port Power Role).
 */
enum pd_power_role {
	PD_ROLE_SINK = 0,
	PD_ROLE_SOURCE = 1
};

/*
 * Data role.
 *
 * Note this is also used for PD header creation, and the first two values
 * align to those in the Power Delivery Specification Revision 3.0 (See
 * 6.2.1.1.6 Port Data Role).
 */
enum pd_data_role {
	PD_ROLE_UFP = 0,
	PD_ROLE_DFP = 1,
	PD_ROLE_DISCONNECTED = 2,
};

enum pd_vconn_role {
	PD_ROLE_VCONN_OFF = 0,
	PD_ROLE_VCONN_SRC = 1,
};

/*
 * Note: BIT(0) may be used to determine whether the polarity is CC1 or CC2,
 * regardless of whether a debug accessory is connected.
 */
enum tcpc_cc_polarity {
	/*
	 * _CCx: is used to indicate the polarity while not connected to
	 * a Debug Accessory.  Only one CC line will assert a resistor and
	 * the other will be open.
	 */
	POLARITY_CC1 = 0,
	POLARITY_CC2 = 1,

	/*
	 * _CCx_DTS is used to indicate the polarity while connected to a
	 * SRC Debug Accessory.  Assert resistors on both lines.
	 */
	POLARITY_CC1_DTS = 2,
	POLARITY_CC2_DTS = 3,

	/*
	 * The current TCPC code relies on these specific POLARITY values.
	 * Adding in a check to verify if the list grows for any reason
	 * that this will give a hint that other places need to be
	 * adjusted.
	 */
	POLARITY_COUNT
};

#define PD_STATUS_EVENT_SOP_DISC_DONE		BIT(0)
#define PD_STATUS_EVENT_SOP_PRIME_DISC_DONE	BIT(1)

struct ec_params_typec_status {
	uint8_t port;
} __ec_align1;

struct ec_response_typec_status {
	uint8_t pd_enabled;		/* PD communication enabled - bool */
	uint8_t dev_connected;		/* Device connected - bool */
	uint8_t sop_connected;		/* Device is SOP PD capable - bool */
	uint8_t source_cap_count;	/* Number of Source Cap PDOs */

	uint8_t power_role;		/* enum pd_power_role */
	uint8_t data_role;		/* enum pd_data_role */
	uint8_t vconn_role;		/* enum pd_vconn_role */
	uint8_t sink_cap_count;		/* Number of Sink Cap PDOs */

	uint8_t polarity;		/* enum tcpc_cc_polarity */
	uint8_t cc_state;		/* enum pd_cc_states */
	uint8_t dp_pin;			/* DP pin mode (MODE_DP_IN_[A-E]) */
	uint8_t mux_state;		/* USB_PD_MUX* - encoded mux state */

	char tc_state[32];		/* TC state name */

	uint32_t events;		/* PD_STATUS_EVENT bitmask */

	/*
	 * BCD PD revisions for partners
	 *
	 * The format has the PD major reversion in the upper nibble, and PD
	 * minor version in the next nibble.  Following two nibbles are
	 * currently 0.
	 * ex. PD 3.2 would map to 0x3200
	 *
	 * PD major/minor will be 0 if no PD device is connected.
	 */
	uint16_t sop_revision;
	uint16_t sop_prime_revision;

	uint32_t source_cap_pdos[7];	/* Max 7 PDOs can be present */

	uint32_t sink_cap_pdos[7];	/* Max 7 PDOs can be present */
} __ec_align1;

/*****************************************************************************/
/* The command range 0x200-0x2FF is reserved for Rotor. */