+#include "greybus_trace.h"
+
+#define GB_INTERFACE_MODE_SWITCH_TIMEOUT 2000
+
+#define GB_INTERFACE_DEVICE_ID_BAD 0xff
+
+#define GB_INTERFACE_AUTOSUSPEND_MS 3000
+
+/* Time required for interface to enter standby before disabling REFCLK */
+#define GB_INTERFACE_SUSPEND_HIBERNATE_DELAY_MS 20
+
+/* Don't-care selector index */
+#define DME_SELECTOR_INDEX_NULL 0
+
+/* DME attributes */
+/* FIXME: remove ES2 support and DME_T_TST_SRC_INCREMENT */
+#define DME_T_TST_SRC_INCREMENT 0x4083
+
+#define DME_DDBL1_MANUFACTURERID 0x5003
+#define DME_DDBL1_PRODUCTID 0x5004
+
+#define DME_TOSHIBA_GMP_VID 0x6000
+#define DME_TOSHIBA_GMP_PID 0x6001
+#define DME_TOSHIBA_GMP_SN0 0x6002
+#define DME_TOSHIBA_GMP_SN1 0x6003
+#define DME_TOSHIBA_GMP_INIT_STATUS 0x6101
+
+/* DDBL1 Manufacturer and Product ids */
+#define TOSHIBA_DMID 0x0126
+#define TOSHIBA_ES2_BRIDGE_DPID 0x1000
+#define TOSHIBA_ES3_APBRIDGE_DPID 0x1001
+#define TOSHIBA_ES3_GBPHY_DPID 0x1002
+
+static int gb_interface_hibernate_link(struct gb_interface *intf);
+static int gb_interface_refclk_set(struct gb_interface *intf, bool enable);
+
+static int gb_interface_dme_attr_get(struct gb_interface *intf,
+ u16 attr, u32 *val)
+{
+ return gb_svc_dme_peer_get(intf->hd->svc, intf->interface_id,
+ attr, DME_SELECTOR_INDEX_NULL, val);
+}
+
+static int gb_interface_read_ara_dme(struct gb_interface *intf)
+{
+ u32 sn0, sn1;
+ int ret;
+
+ /*
+ * Unless this is a Toshiba bridge, bail out until we have defined
+ * standard GMP attributes.
+ */
+ if (intf->ddbl1_manufacturer_id != TOSHIBA_DMID) {
+ dev_err(&intf->dev, "unknown manufacturer %08x\n",
+ intf->ddbl1_manufacturer_id);
+ return -ENODEV;
+ }
+
+ ret = gb_interface_dme_attr_get(intf, DME_TOSHIBA_GMP_VID,
+ &intf->vendor_id);
+ if (ret)
+ return ret;
+
+ ret = gb_interface_dme_attr_get(intf, DME_TOSHIBA_GMP_PID,
+ &intf->product_id);
+ if (ret)
+ return ret;
+
+ ret = gb_interface_dme_attr_get(intf, DME_TOSHIBA_GMP_SN0, &sn0);
+ if (ret)
+ return ret;
+
+ ret = gb_interface_dme_attr_get(intf, DME_TOSHIBA_GMP_SN1, &sn1);
+ if (ret)
+ return ret;
+
+ intf->serial_number = (u64)sn1 << 32 | sn0;
+
+ return 0;
+}
+
+static int gb_interface_read_dme(struct gb_interface *intf)
+{
+ int ret;
+
+ /* DME attributes have already been read */
+ if (intf->dme_read)
+ return 0;
+
+ ret = gb_interface_dme_attr_get(intf, DME_DDBL1_MANUFACTURERID,
+ &intf->ddbl1_manufacturer_id);
+ if (ret)
+ return ret;
+
+ ret = gb_interface_dme_attr_get(intf, DME_DDBL1_PRODUCTID,
+ &intf->ddbl1_product_id);
+ if (ret)
+ return ret;
+
+ if (intf->ddbl1_manufacturer_id == TOSHIBA_DMID &&
+ intf->ddbl1_product_id == TOSHIBA_ES2_BRIDGE_DPID) {
+ intf->quirks |= GB_INTERFACE_QUIRK_NO_GMP_IDS;
+ intf->quirks |= GB_INTERFACE_QUIRK_NO_INIT_STATUS;
+ }
+
+ ret = gb_interface_read_ara_dme(intf);
+ if (ret)
+ return ret;
+
+ intf->dme_read = true;
+
+ return 0;
+}
+
+static int gb_interface_route_create(struct gb_interface *intf)
+{
+ struct gb_svc *svc = intf->hd->svc;
+ u8 intf_id = intf->interface_id;
+ u8 device_id;
+ int ret;
+
+ /* Allocate an interface device id. */
+ ret = ida_simple_get(&svc->device_id_map,
+ GB_SVC_DEVICE_ID_MIN, GB_SVC_DEVICE_ID_MAX + 1,
+ GFP_KERNEL);
+ if (ret < 0) {
+ dev_err(&intf->dev, "failed to allocate device id: %d\n", ret);
+ return ret;
+ }
+ device_id = ret;
+
+ ret = gb_svc_intf_device_id(svc, intf_id, device_id);
+ if (ret) {
+ dev_err(&intf->dev, "failed to set device id %u: %d\n",
+ device_id, ret);
+ goto err_ida_remove;
+ }
+
+ /* FIXME: Hard-coded AP device id. */
+ ret = gb_svc_route_create(svc, svc->ap_intf_id, GB_SVC_DEVICE_ID_AP,
+ intf_id, device_id);
+ if (ret) {
+ dev_err(&intf->dev, "failed to create route: %d\n", ret);
+ goto err_svc_id_free;
+ }
+
+ intf->device_id = device_id;
+
+ return 0;
+
+err_svc_id_free:
+ /*
+ * XXX Should we tell SVC that this id doesn't belong to interface
+ * XXX anymore.
+ */
+err_ida_remove:
+ ida_simple_remove(&svc->device_id_map, device_id);
+
+ return ret;
+}
+
+static void gb_interface_route_destroy(struct gb_interface *intf)
+{
+ struct gb_svc *svc = intf->hd->svc;
+
+ if (intf->device_id == GB_INTERFACE_DEVICE_ID_BAD)
+ return;
+
+ gb_svc_route_destroy(svc, svc->ap_intf_id, intf->interface_id);
+ ida_simple_remove(&svc->device_id_map, intf->device_id);
+ intf->device_id = GB_INTERFACE_DEVICE_ID_BAD;
+}
+
+/* Locking: Caller holds the interface mutex. */
+static int gb_interface_legacy_mode_switch(struct gb_interface *intf)
+{
+ int ret;
+
+ dev_info(&intf->dev, "legacy mode switch detected\n");
+
+ /* Mark as disconnected to prevent I/O during disable. */
+ intf->disconnected = true;
+ gb_interface_disable(intf);
+ intf->disconnected = false;
+
+ ret = gb_interface_enable(intf);
+ if (ret) {
+ dev_err(&intf->dev, "failed to re-enable interface: %d\n", ret);
+ gb_interface_deactivate(intf);
+ }
+
+ return ret;
+}
+
+void gb_interface_mailbox_event(struct gb_interface *intf, u16 result,
+ u32 mailbox)
+{
+ mutex_lock(&intf->mutex);
+
+ if (result) {
+ dev_warn(&intf->dev,
+ "mailbox event with UniPro error: 0x%04x\n",
+ result);
+ goto err_disable;
+ }
+
+ if (mailbox != GB_SVC_INTF_MAILBOX_GREYBUS) {
+ dev_warn(&intf->dev,
+ "mailbox event with unexpected value: 0x%08x\n",
+ mailbox);
+ goto err_disable;
+ }
+
+ if (intf->quirks & GB_INTERFACE_QUIRK_LEGACY_MODE_SWITCH) {
+ gb_interface_legacy_mode_switch(intf);
+ goto out_unlock;
+ }
+
+ if (!intf->mode_switch) {
+ dev_warn(&intf->dev, "unexpected mailbox event: 0x%08x\n",
+ mailbox);
+ goto err_disable;
+ }
+
+ dev_info(&intf->dev, "mode switch detected\n");
+
+ complete(&intf->mode_switch_completion);
+
+out_unlock:
+ mutex_unlock(&intf->mutex);
+
+ return;
+
+err_disable:
+ gb_interface_disable(intf);
+ gb_interface_deactivate(intf);
+ mutex_unlock(&intf->mutex);
+}
+
+static void gb_interface_mode_switch_work(struct work_struct *work)
+{
+ struct gb_interface *intf;
+ struct gb_control *control;
+ unsigned long timeout;
+ int ret;
+
+ intf = container_of(work, struct gb_interface, mode_switch_work);
+
+ mutex_lock(&intf->mutex);
+ /* Make sure interface is still enabled. */
+ if (!intf->enabled) {
+ dev_dbg(&intf->dev, "mode switch aborted\n");
+ intf->mode_switch = false;
+ mutex_unlock(&intf->mutex);
+ goto out_interface_put;
+ }
+
+ /*
+ * Prepare the control device for mode switch and make sure to get an
+ * extra reference before it goes away during interface disable.
+ */
+ control = gb_control_get(intf->control);
+ gb_control_mode_switch_prepare(control);
+ gb_interface_disable(intf);
+ mutex_unlock(&intf->mutex);
+
+ timeout = msecs_to_jiffies(GB_INTERFACE_MODE_SWITCH_TIMEOUT);
+ ret = wait_for_completion_interruptible_timeout(
+ &intf->mode_switch_completion, timeout);
+
+ /* Finalise control-connection mode switch. */
+ gb_control_mode_switch_complete(control);
+ gb_control_put(control);
+
+ if (ret < 0) {
+ dev_err(&intf->dev, "mode switch interrupted\n");
+ goto err_deactivate;
+ } else if (ret == 0) {
+ dev_err(&intf->dev, "mode switch timed out\n");
+ goto err_deactivate;
+ }
+
+ /* Re-enable (re-enumerate) interface if still active. */
+ mutex_lock(&intf->mutex);
+ intf->mode_switch = false;
+ if (intf->active) {
+ ret = gb_interface_enable(intf);
+ if (ret) {
+ dev_err(&intf->dev, "failed to re-enable interface: %d\n",
+ ret);
+ gb_interface_deactivate(intf);
+ }
+ }
+ mutex_unlock(&intf->mutex);
+
+out_interface_put:
+ gb_interface_put(intf);
+
+ return;
+
+err_deactivate:
+ mutex_lock(&intf->mutex);
+ intf->mode_switch = false;
+ gb_interface_deactivate(intf);
+ mutex_unlock(&intf->mutex);
+
+ gb_interface_put(intf);
+}
+
+int gb_interface_request_mode_switch(struct gb_interface *intf)
+{
+ int ret = 0;
+
+ mutex_lock(&intf->mutex);
+ if (intf->mode_switch) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ intf->mode_switch = true;
+ reinit_completion(&intf->mode_switch_completion);
+
+ /*
+ * Get a reference to the interface device, which will be put once the
+ * mode switch is complete.
+ */
+ get_device(&intf->dev);
+
+ if (!queue_work(system_long_wq, &intf->mode_switch_work)) {
+ put_device(&intf->dev);
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+out_unlock:
+ mutex_unlock(&intf->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gb_interface_request_mode_switch);
+
+/*
+ * T_TstSrcIncrement is written by the module on ES2 as a stand-in for the
+ * init-status attribute DME_TOSHIBA_INIT_STATUS. The AP needs to read and
+ * clear it after reading a non-zero value from it.
+ *
+ * FIXME: This is module-hardware dependent and needs to be extended for every
+ * type of module we want to support.
+ */
+static int gb_interface_read_and_clear_init_status(struct gb_interface *intf)
+{
+ struct gb_host_device *hd = intf->hd;
+ unsigned long bootrom_quirks;
+ unsigned long s2l_quirks;
+ int ret;
+ u32 value;
+ u16 attr;
+ u8 init_status;
+
+ /*
+ * ES2 bridges use T_TstSrcIncrement for the init status.
+ *
+ * FIXME: Remove ES2 support
+ */
+ if (intf->quirks & GB_INTERFACE_QUIRK_NO_INIT_STATUS)
+ attr = DME_T_TST_SRC_INCREMENT;
+ else
+ attr = DME_TOSHIBA_GMP_INIT_STATUS;
+
+ ret = gb_svc_dme_peer_get(hd->svc, intf->interface_id, attr,
+ DME_SELECTOR_INDEX_NULL, &value);
+ if (ret)
+ return ret;
+
+ /*
+ * A nonzero init status indicates the module has finished
+ * initializing.
+ */
+ if (!value) {
+ dev_err(&intf->dev, "invalid init status\n");
+ return -ENODEV;
+ }
+
+ /*
+ * Extract the init status.
+ *
+ * For ES2: We need to check lowest 8 bits of 'value'.
+ * For ES3: We need to check highest 8 bits out of 32 of 'value'.
+ *
+ * FIXME: Remove ES2 support
+ */
+ if (intf->quirks & GB_INTERFACE_QUIRK_NO_INIT_STATUS)
+ init_status = value & 0xff;
+ else
+ init_status = value >> 24;
+
+ /*
+ * Check if the interface is executing the quirky ES3 bootrom that,
+ * for example, requires E2EFC, CSD and CSV to be disabled.
+ */
+ bootrom_quirks = GB_INTERFACE_QUIRK_NO_CPORT_FEATURES |
+ GB_INTERFACE_QUIRK_FORCED_DISABLE |
+ GB_INTERFACE_QUIRK_LEGACY_MODE_SWITCH |
+ GB_INTERFACE_QUIRK_NO_BUNDLE_ACTIVATE;
+
+ s2l_quirks = GB_INTERFACE_QUIRK_NO_PM;
+
+ switch (init_status) {
+ case GB_INIT_BOOTROM_UNIPRO_BOOT_STARTED:
+ case GB_INIT_BOOTROM_FALLBACK_UNIPRO_BOOT_STARTED:
+ intf->quirks |= bootrom_quirks;
+ break;
+ case GB_INIT_S2_LOADER_BOOT_STARTED:
+ /* S2 Loader doesn't support runtime PM */
+ intf->quirks &= ~bootrom_quirks;
+ intf->quirks |= s2l_quirks;
+ break;
+ default:
+ intf->quirks &= ~bootrom_quirks;
+ intf->quirks &= ~s2l_quirks;
+ }
+
+ /* Clear the init status. */
+ return gb_svc_dme_peer_set(hd->svc, intf->interface_id, attr,
+ DME_SELECTOR_INDEX_NULL, 0);
+}