Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1628227 > unrolled thread
| Started by | Hans de Goede <hdegoede@redhat.com> |
|---|---|
| First post | 2017-04-21 15:10 +0200 |
| Last post | 2017-04-24 15:20 +0200 |
| Articles | 4 — 4 participants |
Back to article view | Back to linux.kernel
This discussion starts older than the indexed window; earlier articles aren't shown. The article labeled Started by
below is the oldest one visible, not the original post.
[PATCH 2/5] extcon: Add FUSB302 USB TYPE-C controller support Hans de Goede <hdegoede@redhat.com> - 2017-04-21 15:10 +0200
Re: [PATCH 2/5] extcon: Add FUSB302 USB TYPE-C controller support Andy Shevchenko <andy.shevchenko@gmail.com> - 2017-04-21 21:00 +0200
Re: [PATCH 2/5] extcon: Add FUSB302 USB TYPE-C controller support Heikki Krogerus <heikki.krogerus@linux.intel.com> - 2017-04-24 13:10 +0200
Re: [PATCH 2/5] extcon: Add FUSB302 USB TYPE-C controller support Guenter Roeck <linux@roeck-us.net> - 2017-04-24 15:20 +0200
| From | Hans de Goede <hdegoede@redhat.com> |
|---|---|
| Date | 2017-04-21 15:10 +0200 |
| Subject | [PATCH 2/5] extcon: Add FUSB302 USB TYPE-C controller support |
| Message-ID | <tyGtY-6d0-17@gated-at.bofh.it> |
Add support for USB TYPE-C cable detection on systems using a
FUSB302 USB TYPE-C controller.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
drivers/extcon/Kconfig | 8 +
drivers/extcon/Makefile | 1 +
drivers/extcon/extcon-fusb302.c | 782 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 791 insertions(+)
create mode 100644 drivers/extcon/extcon-fusb302.c
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index 32f2dc8..562db5b 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -35,6 +35,14 @@ config EXTCON_AXP288
Say Y here to enable support for USB peripheral detection
and USB MUX switching by X-Power AXP288 PMIC.
+config EXTCON_FUSB302
+ tristate "FUSB302 USB TYPE-C controller support"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y here to enable support for USB TYPE-C cable detection using
+ a FUSB302 USB TYPE-C controller.
+
config EXTCON_GPIO
tristate "GPIO extcon support"
depends on GPIOLIB || COMPILE_TEST
diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
index ecfa958..e5eb493 100644
--- a/drivers/extcon/Makefile
+++ b/drivers/extcon/Makefile
@@ -7,6 +7,7 @@ extcon-core-objs += extcon.o devres.o
obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o
obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o
+obj-$(CONFIG_EXTCON_FUSB302) += extcon-fusb302.o
obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o
diff --git a/drivers/extcon/extcon-fusb302.c b/drivers/extcon/extcon-fusb302.c
new file mode 100644
index 0000000..577adb9
--- /dev/null
+++ b/drivers/extcon/extcon-fusb302.c
@@ -0,0 +1,782 @@
+/*
+ * Extcon USB-C cable detection driver for FUSB302 USB TYPE-C controller
+ *
+ * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/extcon.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include "extcon.h"
+
+#define REG_DEVICE_ID 0x01
+#define DEVICE_ID_VER_MASK GENMASK(7, 4)
+#define DEVICE_ID_FUSB302_VER 0x80
+
+#define REG_SWITCHES0 0x02
+#define SWITCHES0_PDWN1 BIT(0)
+#define SWITCHES0_PDWN2 BIT(1)
+#define SWITCHES0_PDWN GENMASK(1, 0)
+#define SWITCHES0_MEAS_CC1 BIT(2)
+#define SWITCHES0_MEAS_CC2 BIT(3)
+#define SWITCHES0_VCONN_CC1 BIT(4)
+#define SWITCHES0_VCONN_CC2 BIT(5)
+#define SWITCHES0_PU_EN1 BIT(6)
+#define SWITCHES0_PU_EN2 BIT(7)
+
+#define REG_MEASURE 0x04
+#define MEASURE_MDAC_MASK GENMASK(5, 0)
+/* Datasheet says MDAC must be set to 0x34 / 2226mV for vRd-3.0 detection */
+#define MEASURE_MDAC_SNK_VRD30 0x34
+/* MDAC must be set to 0x25 / 1600mV for disconnect det. with 80uA host-cur */
+#define MEASURE_MDAC_SRC_80UA_HOST_CUR 0x25
+
+#define REG_CONTROL0 0x06
+#define CONTROL0_HOST_CUR_MASK GENMASK(3, 2)
+#define CONTROL0_HOST_CUR_DISABLED (0 << 2)
+#define CONTROL0_HOST_CUR_80UA (1 << 2)
+#define CONTROL0_HOST_CUR_180UA (2 << 2)
+#define CONTROL0_HOST_CUR_330UA (3 << 2)
+#define CONTROL0_INT_MASK BIT(5)
+
+#define REG_CONTROL2 0x08
+#define CONTROL2_TOGGLE BIT(0)
+#define CONTROL2_MODE_MASK GENMASK(2, 1)
+#define CONTROL2_MODE_DRP (1 << 1)
+#define CONTROL2_MODE_SNK (2 << 1)
+#define CONTROL2_MODE_SRC (3 << 1)
+#define CONTROL2_SAVE_PWR_MASK GENMASK(7, 6)
+#define CONTROL2_SAVE_PWR_DISABLED (0 << 6)
+#define CONTROL2_SAVE_PWR_40MS (1 << 6)
+#define CONTROL2_SAVE_PWR_80MS (2 << 6)
+#define CONTROL2_SAVE_PWR_160MS (3 << 6)
+
+#define REG_MASK1 0x0a
+/* REG_MASK1 value for low-power / disabled state from datasheet */
+#define MASK1_DISABLED 0xfe
+#define MASK1_COMP_CHNG BIT(5)
+#define MASK1_VBUSOK BIT(7)
+
+#define REG_POWER 0x0b
+/* REG_POWER values for disabled and normal state from datasheet */
+#define POWER_DISABLED BIT(0)
+#define POWER_NORMAL GENMASK(2, 0)
+
+#define REG_RESET 0x0c
+#define RESET_SW_RESET BIT(0)
+
+#define REG_OCP 0x0d
+
+#define REG_MASKA 0x0e
+/* REG_MASKA value for low-power / disabled state from datasheet */
+#define MASKA_DISABLED 0xbf
+
+#define REG_MASKB 0x0f
+/* REG_MASKB value for low-power / disabled state from datasheet */
+#define MASKB_DISABLED 0x01
+
+#define REG_STATUS1A 0x3d
+#define STATUS1A_TOGSS_MASK GENMASK(5, 3)
+#define STATUS1A_TOGSS_SRC_CC1 (1 << 3)
+#define STATUS1A_TOGSS_SRC_CC2 (2 << 3)
+#define STATUS1A_TOGSS_SNK_CC1 (5 << 3)
+#define STATUS1A_TOGSS_SNK_CC2 (6 << 3)
+
+#define REG_INTERRUPTA 0x3e
+#define INTERRUPTA_TOGDONE BIT(6)
+
+#define REG_STATUS0 0x40
+#define STATUS0_BC_LEVEL_MASK GENMASK(1, 0)
+#define STATUS0_BC_LEVEL_VRA 0
+#define STATUS0_BC_LEVEL_VRDUSB 1
+#define STATUS0_BC_LEVEL_VRD15 2
+#define STATUS0_BC_LEVEL_VRD30 3
+#define STATUS0_COMP BIT(5)
+#define STATUS0_VBUSOK BIT(7)
+
+#define REG_INTERRUPT 0x42
+#define INTERRUPT_BC_LVL BIT(0)
+#define INTERRUPT_COMP_CHNG BIT(5)
+#define INTERRUPT_VBUSOK BIT(7)
+
+/* Timeouts from the FUSB302 datasheet */
+#define TTOGCYCLE msecs_to_jiffies(40 + 60 + 40)
+
+/* Timeouts from the USB-C specification */
+#define TPDDEBOUNCE msecs_to_jiffies(20)
+#define TDRPTRY_MS 75
+#define TDRPTRY msecs_to_jiffies(TDRPTRY_MS)
+#define TDRPTRYWAIT msecs_to_jiffies(400)
+#define TVBUSON msecs_to_jiffies(275)
+
+enum typec_port_type {
+ TYPEC_PORT_DFP,
+ TYPEC_PORT_UFP,
+ TYPEC_PORT_DRP,
+};
+
+enum typec_role {
+ TYPEC_SINK,
+ TYPEC_SOURCE,
+};
+
+enum fusb302_state {
+ DISABLED_SNK, /* UFP */
+ DISABLED_SRC, /* DFP */
+ DISABLED_DRP,
+ UNATTACHED_SNK,
+ ATTACHED_SNK,
+ UNATTACHED_SRC,
+ ATTACHED_SRC,
+};
+/* For debugging */
+static __maybe_unused const char *fusb302_state_str[] = {
+ "DISABLED_SNK",
+ "DISABLED_SRC",
+ "DISABLED_DRP",
+ "UNATTACHED_SNK",
+ "ATTACHED_SNK",
+ "UNATTACHED_SRC",
+ "ATTACHED_SRC",
+};
+
+enum fusb302_event {
+ TOGGLE_DONE,
+ BC_LVL_CHANGE,
+ VBUS_VALID,
+ VBUS_INVALID,
+ COMP_LOW,
+ COMP_HIGH,
+ TIMEOUT,
+ FALLBACK_TIMEOUT,
+};
+/* For debugging */
+static __maybe_unused const char *fusb302_event_str[] = {
+ "TOGGLE_DONE",
+ "BC_LVL_CHANGE",
+ "VBUS_VALID",
+ "VBUS_INVALID",
+ "COMP_LOW",
+ "COMP_HIGH",
+ "TIMEOUT",
+ "FALLBACK_TIMEOUT",
+};
+
+static const unsigned int fusb302_extcon_cables[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_CHG_USB_SDP,
+ EXTCON_CHG_USB_CDP,
+ EXTCON_CHG_USB_FAST,
+ EXTCON_NONE,
+};
+
+struct fusb302_data {
+ struct device *dev;
+ struct regmap *regmap;
+ struct extcon_dev *edev;
+ struct mutex lock;
+ struct delayed_work timeout;
+ struct delayed_work fallback_timeout;
+ struct delayed_work bc_work;
+ enum fusb302_state state;
+ enum typec_port_type port_type;
+ enum typec_role preferred_role;
+ int status0;
+ int status1a;
+ unsigned int snk_cable_id;
+};
+
+static void fusb302_write_reg(struct fusb302_data *data, int reg, int val)
+{
+ int ret;
+
+ ret = regmap_write(data->regmap, reg, val);
+ if (ret)
+ dev_err(data->dev, "Error writing reg %02x: %d\n", reg, ret);
+}
+
+static int fusb302_read_reg(struct fusb302_data *data, int reg)
+{
+ int ret, val;
+
+ ret = regmap_read(data->regmap, reg, &val);
+ if (ret) {
+ dev_err(data->dev, "Error reading reg %02x: %d\n", reg, ret);
+ return 0;
+ }
+
+ return val;
+}
+
+static void fusb302_update_bits(struct fusb302_data *data, int reg,
+ int mask, int val)
+{
+ int ret;
+
+ ret = regmap_update_bits(data->regmap, reg, mask, val);
+ if (ret)
+ dev_err(data->dev, "Error modifying reg %02x: %d\n", reg, ret);
+}
+
+/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */
+static void fusb302_set_extcon_state(struct fusb302_data *data,
+ unsigned int cable, bool state)
+{
+ extcon_set_state_sync(data->edev, cable, state);
+ if (cable == EXTCON_CHG_USB_SDP)
+ extcon_set_state_sync(data->edev, EXTCON_USB, state);
+}
+
+/* Helper for the disabled states */
+static void fusb302_disable(struct fusb302_data *data)
+{
+ fusb302_write_reg(data, REG_POWER, POWER_DISABLED);
+ fusb302_write_reg(data, REG_MASK1, MASK1_DISABLED);
+ fusb302_write_reg(data, REG_MASKA, MASKA_DISABLED);
+ fusb302_write_reg(data, REG_MASKB, MASKB_DISABLED);
+ fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
+}
+
+/*
+ * fusb302_set_state() and fusb302_handle_event() implement the 3 state
+ * machines from the datasheet folded into 1 state-machine for code re-use.
+ */
+static void fusb302_set_state(struct fusb302_data *data,
+ enum fusb302_state state)
+{
+ int status, switches0 = 0;
+ enum fusb302_state old_state = data->state;
+ union extcon_property_value prop;
+
+ /* Kill pending timeouts from previous state */
+ cancel_delayed_work(&data->timeout);
+ cancel_delayed_work(&data->fallback_timeout);
+ cancel_delayed_work(&data->bc_work);
+
+ dev_dbg(data->dev, "New state %s\n", fusb302_state_str[state]);
+
+ switch (old_state) {
+ case ATTACHED_SNK:
+ fusb302_set_extcon_state(data, data->snk_cable_id, false);
+ break;
+ case ATTACHED_SRC:
+ fusb302_set_extcon_state(data, EXTCON_USB_HOST, false);
+ break;
+ default:
+ break; /* Do nothing */
+ }
+
+ switch (state) {
+ case DISABLED_SNK:
+ fusb302_disable(data);
+ fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
+ CONTROL2_MODE_SNK);
+ fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
+ CONTROL2_TOGGLE);
+ break;
+
+ case DISABLED_SRC:
+ fusb302_disable(data);
+ fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
+ CONTROL2_MODE_SRC);
+ fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
+ CONTROL2_TOGGLE);
+ break;
+
+ case DISABLED_DRP:
+ fusb302_disable(data);
+ fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
+ CONTROL2_MODE_DRP);
+ fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
+ CONTROL2_TOGGLE);
+ break;
+
+ case UNATTACHED_SNK:
+ fusb302_write_reg(data, REG_POWER, POWER_NORMAL);
+
+ /* Enable pull-down on CC1 / CC2 based on orientation */
+ switch (data->status1a & STATUS1A_TOGSS_MASK) {
+ case STATUS1A_TOGSS_SNK_CC1:
+ switches0 = SWITCHES0_PDWN1 | SWITCHES0_MEAS_CC1;
+ prop.intval = USB_TYPEC_POLARITY_NORMAL;
+ break;
+ case STATUS1A_TOGSS_SNK_CC2:
+ switches0 = SWITCHES0_PDWN2 | SWITCHES0_MEAS_CC2;
+ prop.intval = USB_TYPEC_POLARITY_FLIP;
+ break;
+ }
+ fusb302_write_reg(data, REG_SWITCHES0, switches0);
+ fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
+ extcon_set_property(data->edev, EXTCON_USB,
+ EXTCON_PROP_USB_TYPEC_POLARITY, prop);
+
+ /* Enable VBUSOK and COMP irq at 2.24V for BC detection */
+ fusb302_update_bits(data, REG_MASK1,
+ MASK1_VBUSOK | MASK1_COMP_CHNG, 0);
+ fusb302_update_bits(data, REG_MEASURE, MEASURE_MDAC_MASK,
+ MEASURE_MDAC_SNK_VRD30);
+
+ status = fusb302_read_reg(data, REG_STATUS0);
+ if (status & STATUS0_VBUSOK) {
+ /* Go straight to ATTACHED_SNK */
+ fusb302_set_state(data, ATTACHED_SNK);
+ return;
+ }
+
+ mod_delayed_work(system_wq, &data->timeout, TVBUSON);
+ break;
+
+ case ATTACHED_SNK:
+ mod_delayed_work(system_wq, &data->bc_work, TPDDEBOUNCE);
+ break;
+
+ case UNATTACHED_SRC:
+ fusb302_write_reg(data, REG_POWER, POWER_NORMAL);
+
+ /* Enable pull-up / Vconn on CC1 / CC2 based on orientation */
+ switch (data->status1a & STATUS1A_TOGSS_MASK) {
+ case STATUS1A_TOGSS_SRC_CC1:
+ switches0 = SWITCHES0_PU_EN1 | SWITCHES0_VCONN_CC2 |
+ SWITCHES0_MEAS_CC1;
+ prop.intval = USB_TYPEC_POLARITY_NORMAL;
+ break;
+ case STATUS1A_TOGSS_SRC_CC2:
+ switches0 = SWITCHES0_PU_EN2 | SWITCHES0_VCONN_CC1 |
+ SWITCHES0_MEAS_CC2;
+ prop.intval = USB_TYPEC_POLARITY_FLIP;
+ break;
+ }
+ fusb302_write_reg(data, REG_SWITCHES0, switches0);
+ fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
+ extcon_set_property(data->edev, EXTCON_USB,
+ EXTCON_PROP_USB_TYPEC_POLARITY, prop);
+
+ /* Enable COMP irq at 1.6V for detach detection */
+ fusb302_update_bits(data, REG_MASK1, MASK1_COMP_CHNG, 0);
+ fusb302_update_bits(data, REG_MEASURE, MEASURE_MDAC_MASK,
+ MEASURE_MDAC_SRC_80UA_HOST_CUR);
+
+ status = fusb302_read_reg(data, REG_STATUS0);
+ if (!(status & STATUS0_COMP)) {
+ /* Go straight to ATTACHED_SRC */
+ fusb302_set_state(data, ATTACHED_SRC);
+ return;
+ }
+
+ mod_delayed_work(system_wq, &data->timeout, TPDDEBOUNCE);
+ break;
+
+ case ATTACHED_SRC:
+ fusb302_set_extcon_state(data, EXTCON_USB_HOST, true);
+ break;
+ }
+
+ data->state = state;
+}
+
+static void fusb302_set_state_disabled(struct fusb302_data *data)
+{
+
+ switch (data->port_type) {
+ case TYPEC_PORT_UFP:
+ fusb302_set_state(data, DISABLED_SNK);
+ break;
+ case TYPEC_PORT_DFP:
+ fusb302_set_state(data, DISABLED_SRC);
+ break;
+ case TYPEC_PORT_DRP:
+ fusb302_set_state(data, DISABLED_DRP);
+ break;
+ }
+}
+
+static void fusb302_handle_disabled_snk_event(struct fusb302_data *data,
+ int event)
+{
+ switch (event) {
+ case TOGGLE_DONE:
+ switch (data->status1a & STATUS1A_TOGSS_MASK) {
+ case STATUS1A_TOGSS_SNK_CC1:
+ case STATUS1A_TOGSS_SNK_CC2:
+ fusb302_set_state(data, UNATTACHED_SNK);
+ break;
+ }
+ break;
+
+ case TIMEOUT:
+ /* Cannot become snk fallback to src */
+ fusb302_set_state(data, DISABLED_SRC);
+ mod_delayed_work(system_wq, &data->fallback_timeout,
+ TDRPTRYWAIT);
+ break;
+
+ case FALLBACK_TIMEOUT:
+ /* Both states failed return to disabled drp state */
+ fusb302_set_state(data, DISABLED_DRP);
+ break;
+ }
+}
+
+static void fusb302_handle_disabled_src_event(struct fusb302_data *data,
+ int event)
+{
+ switch (event) {
+ case TOGGLE_DONE:
+ switch (data->status1a & STATUS1A_TOGSS_MASK) {
+ case STATUS1A_TOGSS_SRC_CC1:
+ case STATUS1A_TOGSS_SRC_CC2:
+ fusb302_set_state(data, UNATTACHED_SRC);
+ break;
+ }
+ break;
+
+ case TIMEOUT:
+ /* Cannot become src fallback to snk */
+ fusb302_set_state(data, DISABLED_SNK);
+ mod_delayed_work(system_wq, &data->fallback_timeout,
+ TDRPTRYWAIT);
+ break;
+
+ case FALLBACK_TIMEOUT:
+ /* Both states failed return to disabled drp state */
+ fusb302_set_state(data, DISABLED_DRP);
+ break;
+ }
+}
+
+static void fusb302_handle_disabled_drp_event(struct fusb302_data *data,
+ int event)
+{
+ switch (event) {
+ case TOGGLE_DONE:
+ switch (data->status1a & STATUS1A_TOGSS_MASK) {
+ case STATUS1A_TOGSS_SNK_CC1:
+ case STATUS1A_TOGSS_SNK_CC2:
+ if (data->preferred_role == TYPEC_SINK) {
+ /* Jump directly to UNATTACHED_SNK */
+ fusb302_set_state(data, UNATTACHED_SNK);
+ } else {
+ /* Try to become src */
+ fusb302_set_state(data, DISABLED_SNK);
+ mod_delayed_work(system_wq, &data->timeout,
+ TDRPTRY);
+ }
+ break;
+ case STATUS1A_TOGSS_SRC_CC1:
+ case STATUS1A_TOGSS_SRC_CC2:
+ if (data->preferred_role == TYPEC_SOURCE) {
+ /* Jump directly to UNATTACHED_SRC */
+ fusb302_set_state(data, UNATTACHED_SRC);
+ } else {
+ /*
+ * The USB-C spec says we must enable pull-downs
+ * and then wait tDRPTry before checking to
+ * avoid endless role-bouncing if both ends
+ * prefer the snk role.
+ */
+ fusb302_write_reg(data, REG_SWITCHES0,
+ SWITCHES0_PDWN);
+ fusb302_update_bits(data, REG_CONTROL2,
+ CONTROL2_TOGGLE, 0);
+ msleep(TDRPTRY_MS);
+ /*
+ * Use the toggle engine to do the src
+ * detection to keep things the same as for
+ * directly entering the src role.
+ */
+ fusb302_set_state(data, DISABLED_SNK);
+ mod_delayed_work(system_wq, &data->timeout,
+ TTOGCYCLE);
+ }
+ break;
+ }
+ break;
+ }
+}
+
+static void fusb302_handle_unattached_snk_event(struct fusb302_data *data,
+ int event)
+{
+ switch (event) {
+ case VBUS_VALID: /* Cable attached */
+ fusb302_set_state(data, ATTACHED_SNK);
+ break;
+ case TIMEOUT:
+ fusb302_set_state_disabled(data);
+ break;
+ }
+}
+
+static void fusb302_handle_attached_snk_event(struct fusb302_data *data,
+ int event)
+{
+ switch (event) {
+ case BC_LVL_CHANGE:
+ case COMP_LOW:
+ case COMP_HIGH:
+ mod_delayed_work(system_wq, &data->bc_work, TPDDEBOUNCE);
+ break;
+ case VBUS_INVALID: /* Cable detached */
+ fusb302_set_state_disabled(data);
+ break;
+ }
+}
+
+static void fusb302_handle_unattached_src_event(struct fusb302_data *data,
+ int event)
+{
+ switch (event) {
+ case COMP_LOW: /* Cable attached */
+ fusb302_set_state(data, ATTACHED_SRC);
+ break;
+ case TIMEOUT:
+ fusb302_set_state_disabled(data);
+ break;
+ }
+}
+
+static void fusb302_handle_attached_src_event(struct fusb302_data *data,
+ int event)
+{
+ switch (event) {
+ case COMP_HIGH: /* Cable detached */
+ fusb302_set_state_disabled(data);
+ break;
+ }
+}
+
+static void fusb302_handle_event(struct fusb302_data *data, int event)
+{
+
+ mutex_lock(&data->lock);
+
+ dev_dbg(data->dev, "Handling state %s event %s status %02x %02x\n",
+ fusb302_state_str[data->state], fusb302_event_str[event],
+ fusb302_read_reg(data, REG_STATUS0),
+ fusb302_read_reg(data, REG_STATUS1A));
+
+ switch (data->state) {
+ case DISABLED_SNK:
+ fusb302_handle_disabled_snk_event(data, event);
+ break;
+ case DISABLED_SRC:
+ fusb302_handle_disabled_src_event(data, event);
+ break;
+ case DISABLED_DRP:
+ fusb302_handle_disabled_drp_event(data, event);
+ break;
+ case UNATTACHED_SNK:
+ fusb302_handle_unattached_snk_event(data, event);
+ break;
+ case ATTACHED_SNK:
+ fusb302_handle_attached_snk_event(data, event);
+ break;
+ case UNATTACHED_SRC:
+ fusb302_handle_unattached_src_event(data, event);
+ break;
+ case ATTACHED_SRC:
+ fusb302_handle_attached_src_event(data, event);
+ break;
+ }
+ mutex_unlock(&data->lock);
+}
+
+static void fusb302_timeout(struct work_struct *work)
+{
+ struct fusb302_data *data =
+ container_of(work, struct fusb302_data, timeout.work);
+
+ fusb302_handle_event(data, TIMEOUT);
+}
+
+static void fusb302_fallback_timeout(struct work_struct *work)
+{
+ struct fusb302_data *data =
+ container_of(work, struct fusb302_data, fallback_timeout.work);
+
+ fusb302_handle_event(data, FALLBACK_TIMEOUT);
+}
+
+static void fusb302_report_bc_level(struct work_struct *work)
+{
+ struct fusb302_data *data =
+ container_of(work, struct fusb302_data, bc_work.work);
+
+ /* Clear old charger cable id */
+ fusb302_set_extcon_state(data, data->snk_cable_id, false);
+
+ if (data->status0 & STATUS0_COMP) {
+ dev_warn(data->dev, "vRd over maximum, assuming 500mA source\n");
+ data->snk_cable_id = EXTCON_CHG_USB_SDP;
+ } else {
+ switch (data->status0 & STATUS0_BC_LEVEL_MASK) {
+ case STATUS0_BC_LEVEL_VRA:
+ case STATUS0_BC_LEVEL_VRDUSB:
+ data->snk_cable_id = EXTCON_CHG_USB_SDP;
+ break;
+ case STATUS0_BC_LEVEL_VRD15:
+ data->snk_cable_id = EXTCON_CHG_USB_CDP;
+ break;
+ case STATUS0_BC_LEVEL_VRD30:
+ /* Use CHG_USB_FAST to indicate 3A capability */
+ data->snk_cable_id = EXTCON_CHG_USB_FAST;
+ break;
+ }
+ }
+ fusb302_set_extcon_state(data, data->snk_cable_id, true);
+}
+
+static irqreturn_t fusb302_irq_handler_thread(int irq, void *handler_data)
+{
+ struct fusb302_data *data = handler_data;
+ int interrupt, interrupta;
+
+ interrupt = fusb302_read_reg(data, REG_INTERRUPT);
+ interrupta = fusb302_read_reg(data, REG_INTERRUPTA);
+
+ if (interrupt)
+ data->status0 = fusb302_read_reg(data, REG_STATUS0);
+
+ if (interrupta)
+ data->status1a = fusb302_read_reg(data, REG_STATUS1A);
+
+ dev_dbg(data->dev, "Handling interrupt %02x %02x status %02x %02x\n",
+ interrupt, interrupta, data->status0, data->status1a);
+
+ if (interrupt & INTERRUPT_BC_LVL)
+ fusb302_handle_event(data, BC_LVL_CHANGE);
+
+ if (interrupt & INTERRUPT_COMP_CHNG) {
+ if (data->status0 & STATUS0_COMP)
+ fusb302_handle_event(data, COMP_HIGH);
+ else
+ fusb302_handle_event(data, COMP_LOW);
+ }
+
+ if (interrupt & INTERRUPT_VBUSOK) {
+ if (data->status0 & STATUS0_VBUSOK)
+ fusb302_handle_event(data, VBUS_VALID);
+ else
+ fusb302_handle_event(data, VBUS_INVALID);
+ }
+
+ if (interrupta & INTERRUPTA_TOGDONE)
+ fusb302_handle_event(data, TOGGLE_DONE);
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_config fusb302_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+};
+
+static int fusb302_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct fusb302_data *data;
+ int ret;
+
+ if (!client->irq) {
+ dev_err(dev, "Error irq not specified\n");
+ return -EINVAL;
+ }
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = dev;
+ /* TODO make these 2 configurable using device-properties */
+ data->port_type = TYPEC_PORT_DRP;
+ data->preferred_role = TYPEC_SINK;
+
+ data->regmap = devm_regmap_init_i2c(client, &fusb302_regmap_config);
+ if (IS_ERR(data->regmap)) {
+ ret = PTR_ERR(data->regmap);
+ dev_err(dev, "Error to initializing regmap: %d\n", ret);
+ return ret;
+ }
+
+ mutex_init(&data->lock);
+ INIT_DELAYED_WORK(&data->timeout, fusb302_timeout);
+ INIT_DELAYED_WORK(&data->fallback_timeout, fusb302_fallback_timeout);
+ INIT_DELAYED_WORK(&data->bc_work, fusb302_report_bc_level);
+
+ data->edev = devm_extcon_dev_allocate(dev, fusb302_extcon_cables);
+ if (IS_ERR(data->edev))
+ return PTR_ERR(data->edev);
+
+ data->edev->name = "fusb302";
+
+ ret = devm_extcon_dev_register(dev, data->edev);
+ if (ret)
+ return ret;
+
+ extcon_set_property_capability(data->edev, EXTCON_USB,
+ EXTCON_PROP_USB_TYPEC_POLARITY);
+
+ fusb302_write_reg(data, REG_RESET, RESET_SW_RESET);
+ usleep_range(10000, 20000);
+
+ /* Enable power-saving */
+ fusb302_update_bits(data, REG_CONTROL2, CONTROL2_SAVE_PWR_MASK,
+ CONTROL2_SAVE_PWR_40MS);
+
+ fusb302_set_state_disabled(data);
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ fusb302_irq_handler_thread,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT, "fusb302", data);
+ if (ret) {
+ dev_err(dev, "Error requesting irq: %d\n", ret);
+ return ret;
+ }
+
+ fusb302_update_bits(data, REG_CONTROL0, CONTROL0_INT_MASK, 0);
+
+ i2c_set_clientdata(client, data);
+ return 0;
+}
+
+static int fusb302_remove(struct i2c_client *client)
+{
+ struct fusb302_data *data = i2c_get_clientdata(client);
+
+ devm_free_irq(data->dev, client->irq, data);
+ cancel_delayed_work_sync(&data->timeout);
+ cancel_delayed_work_sync(&data->fallback_timeout);
+
+ return 0;
+}
+
+static const struct i2c_device_id fusb302_i2c_ids[] = {
+ { "fusb302" },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, fusb302_i2c_ids);
+
+static struct i2c_driver fusb302_driver = {
+ .probe_new = fusb302_probe,
+ .remove = fusb302_remove,
+ .id_table = fusb302_i2c_ids,
+ .driver = {
+ .name = "fusb302",
+ },
+};
+module_i2c_driver(fusb302_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("FUSB302 USB TYPE-C controller Driver");
--
2.9.3
[toc] | [next] | [standalone]
| From | Andy Shevchenko <andy.shevchenko@gmail.com> |
|---|---|
| Date | 2017-04-21 21:00 +0200 |
| Message-ID | <tyLWF-Sq-7@gated-at.bofh.it> |
| In reply to | #1628227 |
+Cc: Heikki.
He might comment on this.
On Fri, Apr 21, 2017 at 4:01 PM, Hans de Goede <hdegoede@redhat.com> wrote:
> Add support for USB TYPE-C cable detection on systems using a
> FUSB302 USB TYPE-C controller.
>
> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> ---
> drivers/extcon/Kconfig | 8 +
> drivers/extcon/Makefile | 1 +
> drivers/extcon/extcon-fusb302.c | 782 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 791 insertions(+)
> create mode 100644 drivers/extcon/extcon-fusb302.c
>
> diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
> index 32f2dc8..562db5b 100644
> --- a/drivers/extcon/Kconfig
> +++ b/drivers/extcon/Kconfig
> @@ -35,6 +35,14 @@ config EXTCON_AXP288
> Say Y here to enable support for USB peripheral detection
> and USB MUX switching by X-Power AXP288 PMIC.
>
> +config EXTCON_FUSB302
> + tristate "FUSB302 USB TYPE-C controller support"
> + depends on I2C
> + select REGMAP_I2C
> + help
> + Say Y here to enable support for USB TYPE-C cable detection using
> + a FUSB302 USB TYPE-C controller.
> +
> config EXTCON_GPIO
> tristate "GPIO extcon support"
> depends on GPIOLIB || COMPILE_TEST
> diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
> index ecfa958..e5eb493 100644
> --- a/drivers/extcon/Makefile
> +++ b/drivers/extcon/Makefile
> @@ -7,6 +7,7 @@ extcon-core-objs += extcon.o devres.o
> obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o
> obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
> obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o
> +obj-$(CONFIG_EXTCON_FUSB302) += extcon-fusb302.o
> obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
> obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
> obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o
> diff --git a/drivers/extcon/extcon-fusb302.c b/drivers/extcon/extcon-fusb302.c
> new file mode 100644
> index 0000000..577adb9
> --- /dev/null
> +++ b/drivers/extcon/extcon-fusb302.c
> @@ -0,0 +1,782 @@
> +/*
> + * Extcon USB-C cable detection driver for FUSB302 USB TYPE-C controller
> + *
> + * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/extcon.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/workqueue.h>
> +#include "extcon.h"
> +
> +#define REG_DEVICE_ID 0x01
> +#define DEVICE_ID_VER_MASK GENMASK(7, 4)
> +#define DEVICE_ID_FUSB302_VER 0x80
> +
> +#define REG_SWITCHES0 0x02
> +#define SWITCHES0_PDWN1 BIT(0)
> +#define SWITCHES0_PDWN2 BIT(1)
> +#define SWITCHES0_PDWN GENMASK(1, 0)
> +#define SWITCHES0_MEAS_CC1 BIT(2)
> +#define SWITCHES0_MEAS_CC2 BIT(3)
> +#define SWITCHES0_VCONN_CC1 BIT(4)
> +#define SWITCHES0_VCONN_CC2 BIT(5)
> +#define SWITCHES0_PU_EN1 BIT(6)
> +#define SWITCHES0_PU_EN2 BIT(7)
> +
> +#define REG_MEASURE 0x04
> +#define MEASURE_MDAC_MASK GENMASK(5, 0)
> +/* Datasheet says MDAC must be set to 0x34 / 2226mV for vRd-3.0 detection */
> +#define MEASURE_MDAC_SNK_VRD30 0x34
> +/* MDAC must be set to 0x25 / 1600mV for disconnect det. with 80uA host-cur */
> +#define MEASURE_MDAC_SRC_80UA_HOST_CUR 0x25
> +
> +#define REG_CONTROL0 0x06
> +#define CONTROL0_HOST_CUR_MASK GENMASK(3, 2)
> +#define CONTROL0_HOST_CUR_DISABLED (0 << 2)
> +#define CONTROL0_HOST_CUR_80UA (1 << 2)
> +#define CONTROL0_HOST_CUR_180UA (2 << 2)
> +#define CONTROL0_HOST_CUR_330UA (3 << 2)
> +#define CONTROL0_INT_MASK BIT(5)
> +
> +#define REG_CONTROL2 0x08
> +#define CONTROL2_TOGGLE BIT(0)
> +#define CONTROL2_MODE_MASK GENMASK(2, 1)
> +#define CONTROL2_MODE_DRP (1 << 1)
> +#define CONTROL2_MODE_SNK (2 << 1)
> +#define CONTROL2_MODE_SRC (3 << 1)
> +#define CONTROL2_SAVE_PWR_MASK GENMASK(7, 6)
> +#define CONTROL2_SAVE_PWR_DISABLED (0 << 6)
> +#define CONTROL2_SAVE_PWR_40MS (1 << 6)
> +#define CONTROL2_SAVE_PWR_80MS (2 << 6)
> +#define CONTROL2_SAVE_PWR_160MS (3 << 6)
> +
> +#define REG_MASK1 0x0a
> +/* REG_MASK1 value for low-power / disabled state from datasheet */
> +#define MASK1_DISABLED 0xfe
> +#define MASK1_COMP_CHNG BIT(5)
> +#define MASK1_VBUSOK BIT(7)
> +
> +#define REG_POWER 0x0b
> +/* REG_POWER values for disabled and normal state from datasheet */
> +#define POWER_DISABLED BIT(0)
> +#define POWER_NORMAL GENMASK(2, 0)
> +
> +#define REG_RESET 0x0c
> +#define RESET_SW_RESET BIT(0)
> +
> +#define REG_OCP 0x0d
> +
> +#define REG_MASKA 0x0e
> +/* REG_MASKA value for low-power / disabled state from datasheet */
> +#define MASKA_DISABLED 0xbf
> +
> +#define REG_MASKB 0x0f
> +/* REG_MASKB value for low-power / disabled state from datasheet */
> +#define MASKB_DISABLED 0x01
> +
> +#define REG_STATUS1A 0x3d
> +#define STATUS1A_TOGSS_MASK GENMASK(5, 3)
> +#define STATUS1A_TOGSS_SRC_CC1 (1 << 3)
> +#define STATUS1A_TOGSS_SRC_CC2 (2 << 3)
> +#define STATUS1A_TOGSS_SNK_CC1 (5 << 3)
> +#define STATUS1A_TOGSS_SNK_CC2 (6 << 3)
> +
> +#define REG_INTERRUPTA 0x3e
> +#define INTERRUPTA_TOGDONE BIT(6)
> +
> +#define REG_STATUS0 0x40
> +#define STATUS0_BC_LEVEL_MASK GENMASK(1, 0)
> +#define STATUS0_BC_LEVEL_VRA 0
> +#define STATUS0_BC_LEVEL_VRDUSB 1
> +#define STATUS0_BC_LEVEL_VRD15 2
> +#define STATUS0_BC_LEVEL_VRD30 3
> +#define STATUS0_COMP BIT(5)
> +#define STATUS0_VBUSOK BIT(7)
> +
> +#define REG_INTERRUPT 0x42
> +#define INTERRUPT_BC_LVL BIT(0)
> +#define INTERRUPT_COMP_CHNG BIT(5)
> +#define INTERRUPT_VBUSOK BIT(7)
> +
> +/* Timeouts from the FUSB302 datasheet */
> +#define TTOGCYCLE msecs_to_jiffies(40 + 60 + 40)
> +
> +/* Timeouts from the USB-C specification */
> +#define TPDDEBOUNCE msecs_to_jiffies(20)
> +#define TDRPTRY_MS 75
> +#define TDRPTRY msecs_to_jiffies(TDRPTRY_MS)
> +#define TDRPTRYWAIT msecs_to_jiffies(400)
> +#define TVBUSON msecs_to_jiffies(275)
> +
> +enum typec_port_type {
> + TYPEC_PORT_DFP,
> + TYPEC_PORT_UFP,
> + TYPEC_PORT_DRP,
> +};
> +
> +enum typec_role {
> + TYPEC_SINK,
> + TYPEC_SOURCE,
> +};
> +
> +enum fusb302_state {
> + DISABLED_SNK, /* UFP */
> + DISABLED_SRC, /* DFP */
> + DISABLED_DRP,
> + UNATTACHED_SNK,
> + ATTACHED_SNK,
> + UNATTACHED_SRC,
> + ATTACHED_SRC,
> +};
> +/* For debugging */
> +static __maybe_unused const char *fusb302_state_str[] = {
> + "DISABLED_SNK",
> + "DISABLED_SRC",
> + "DISABLED_DRP",
> + "UNATTACHED_SNK",
> + "ATTACHED_SNK",
> + "UNATTACHED_SRC",
> + "ATTACHED_SRC",
> +};
> +
> +enum fusb302_event {
> + TOGGLE_DONE,
> + BC_LVL_CHANGE,
> + VBUS_VALID,
> + VBUS_INVALID,
> + COMP_LOW,
> + COMP_HIGH,
> + TIMEOUT,
> + FALLBACK_TIMEOUT,
> +};
> +/* For debugging */
> +static __maybe_unused const char *fusb302_event_str[] = {
> + "TOGGLE_DONE",
> + "BC_LVL_CHANGE",
> + "VBUS_VALID",
> + "VBUS_INVALID",
> + "COMP_LOW",
> + "COMP_HIGH",
> + "TIMEOUT",
> + "FALLBACK_TIMEOUT",
> +};
> +
> +static const unsigned int fusb302_extcon_cables[] = {
> + EXTCON_USB,
> + EXTCON_USB_HOST,
> + EXTCON_CHG_USB_SDP,
> + EXTCON_CHG_USB_CDP,
> + EXTCON_CHG_USB_FAST,
> + EXTCON_NONE,
> +};
> +
> +struct fusb302_data {
> + struct device *dev;
> + struct regmap *regmap;
> + struct extcon_dev *edev;
> + struct mutex lock;
> + struct delayed_work timeout;
> + struct delayed_work fallback_timeout;
> + struct delayed_work bc_work;
> + enum fusb302_state state;
> + enum typec_port_type port_type;
> + enum typec_role preferred_role;
> + int status0;
> + int status1a;
> + unsigned int snk_cable_id;
> +};
> +
> +static void fusb302_write_reg(struct fusb302_data *data, int reg, int val)
> +{
> + int ret;
> +
> + ret = regmap_write(data->regmap, reg, val);
> + if (ret)
> + dev_err(data->dev, "Error writing reg %02x: %d\n", reg, ret);
> +}
> +
> +static int fusb302_read_reg(struct fusb302_data *data, int reg)
> +{
> + int ret, val;
> +
> + ret = regmap_read(data->regmap, reg, &val);
> + if (ret) {
> + dev_err(data->dev, "Error reading reg %02x: %d\n", reg, ret);
> + return 0;
> + }
> +
> + return val;
> +}
> +
> +static void fusb302_update_bits(struct fusb302_data *data, int reg,
> + int mask, int val)
> +{
> + int ret;
> +
> + ret = regmap_update_bits(data->regmap, reg, mask, val);
> + if (ret)
> + dev_err(data->dev, "Error modifying reg %02x: %d\n", reg, ret);
> +}
> +
> +/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */
> +static void fusb302_set_extcon_state(struct fusb302_data *data,
> + unsigned int cable, bool state)
> +{
> + extcon_set_state_sync(data->edev, cable, state);
> + if (cable == EXTCON_CHG_USB_SDP)
> + extcon_set_state_sync(data->edev, EXTCON_USB, state);
> +}
> +
> +/* Helper for the disabled states */
> +static void fusb302_disable(struct fusb302_data *data)
> +{
> + fusb302_write_reg(data, REG_POWER, POWER_DISABLED);
> + fusb302_write_reg(data, REG_MASK1, MASK1_DISABLED);
> + fusb302_write_reg(data, REG_MASKA, MASKA_DISABLED);
> + fusb302_write_reg(data, REG_MASKB, MASKB_DISABLED);
> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
> +}
> +
> +/*
> + * fusb302_set_state() and fusb302_handle_event() implement the 3 state
> + * machines from the datasheet folded into 1 state-machine for code re-use.
> + */
> +static void fusb302_set_state(struct fusb302_data *data,
> + enum fusb302_state state)
> +{
> + int status, switches0 = 0;
> + enum fusb302_state old_state = data->state;
> + union extcon_property_value prop;
> +
> + /* Kill pending timeouts from previous state */
> + cancel_delayed_work(&data->timeout);
> + cancel_delayed_work(&data->fallback_timeout);
> + cancel_delayed_work(&data->bc_work);
> +
> + dev_dbg(data->dev, "New state %s\n", fusb302_state_str[state]);
> +
> + switch (old_state) {
> + case ATTACHED_SNK:
> + fusb302_set_extcon_state(data, data->snk_cable_id, false);
> + break;
> + case ATTACHED_SRC:
> + fusb302_set_extcon_state(data, EXTCON_USB_HOST, false);
> + break;
> + default:
> + break; /* Do nothing */
> + }
> +
> + switch (state) {
> + case DISABLED_SNK:
> + fusb302_disable(data);
> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
> + CONTROL2_MODE_SNK);
> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
> + CONTROL2_TOGGLE);
> + break;
> +
> + case DISABLED_SRC:
> + fusb302_disable(data);
> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
> + CONTROL2_MODE_SRC);
> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
> + CONTROL2_TOGGLE);
> + break;
> +
> + case DISABLED_DRP:
> + fusb302_disable(data);
> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
> + CONTROL2_MODE_DRP);
> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
> + CONTROL2_TOGGLE);
> + break;
> +
> + case UNATTACHED_SNK:
> + fusb302_write_reg(data, REG_POWER, POWER_NORMAL);
> +
> + /* Enable pull-down on CC1 / CC2 based on orientation */
> + switch (data->status1a & STATUS1A_TOGSS_MASK) {
> + case STATUS1A_TOGSS_SNK_CC1:
> + switches0 = SWITCHES0_PDWN1 | SWITCHES0_MEAS_CC1;
> + prop.intval = USB_TYPEC_POLARITY_NORMAL;
> + break;
> + case STATUS1A_TOGSS_SNK_CC2:
> + switches0 = SWITCHES0_PDWN2 | SWITCHES0_MEAS_CC2;
> + prop.intval = USB_TYPEC_POLARITY_FLIP;
> + break;
> + }
> + fusb302_write_reg(data, REG_SWITCHES0, switches0);
> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
> + extcon_set_property(data->edev, EXTCON_USB,
> + EXTCON_PROP_USB_TYPEC_POLARITY, prop);
> +
> + /* Enable VBUSOK and COMP irq at 2.24V for BC detection */
> + fusb302_update_bits(data, REG_MASK1,
> + MASK1_VBUSOK | MASK1_COMP_CHNG, 0);
> + fusb302_update_bits(data, REG_MEASURE, MEASURE_MDAC_MASK,
> + MEASURE_MDAC_SNK_VRD30);
> +
> + status = fusb302_read_reg(data, REG_STATUS0);
> + if (status & STATUS0_VBUSOK) {
> + /* Go straight to ATTACHED_SNK */
> + fusb302_set_state(data, ATTACHED_SNK);
> + return;
> + }
> +
> + mod_delayed_work(system_wq, &data->timeout, TVBUSON);
> + break;
> +
> + case ATTACHED_SNK:
> + mod_delayed_work(system_wq, &data->bc_work, TPDDEBOUNCE);
> + break;
> +
> + case UNATTACHED_SRC:
> + fusb302_write_reg(data, REG_POWER, POWER_NORMAL);
> +
> + /* Enable pull-up / Vconn on CC1 / CC2 based on orientation */
> + switch (data->status1a & STATUS1A_TOGSS_MASK) {
> + case STATUS1A_TOGSS_SRC_CC1:
> + switches0 = SWITCHES0_PU_EN1 | SWITCHES0_VCONN_CC2 |
> + SWITCHES0_MEAS_CC1;
> + prop.intval = USB_TYPEC_POLARITY_NORMAL;
> + break;
> + case STATUS1A_TOGSS_SRC_CC2:
> + switches0 = SWITCHES0_PU_EN2 | SWITCHES0_VCONN_CC1 |
> + SWITCHES0_MEAS_CC2;
> + prop.intval = USB_TYPEC_POLARITY_FLIP;
> + break;
> + }
> + fusb302_write_reg(data, REG_SWITCHES0, switches0);
> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
> + extcon_set_property(data->edev, EXTCON_USB,
> + EXTCON_PROP_USB_TYPEC_POLARITY, prop);
> +
> + /* Enable COMP irq at 1.6V for detach detection */
> + fusb302_update_bits(data, REG_MASK1, MASK1_COMP_CHNG, 0);
> + fusb302_update_bits(data, REG_MEASURE, MEASURE_MDAC_MASK,
> + MEASURE_MDAC_SRC_80UA_HOST_CUR);
> +
> + status = fusb302_read_reg(data, REG_STATUS0);
> + if (!(status & STATUS0_COMP)) {
> + /* Go straight to ATTACHED_SRC */
> + fusb302_set_state(data, ATTACHED_SRC);
> + return;
> + }
> +
> + mod_delayed_work(system_wq, &data->timeout, TPDDEBOUNCE);
> + break;
> +
> + case ATTACHED_SRC:
> + fusb302_set_extcon_state(data, EXTCON_USB_HOST, true);
> + break;
> + }
> +
> + data->state = state;
> +}
> +
> +static void fusb302_set_state_disabled(struct fusb302_data *data)
> +{
> +
> + switch (data->port_type) {
> + case TYPEC_PORT_UFP:
> + fusb302_set_state(data, DISABLED_SNK);
> + break;
> + case TYPEC_PORT_DFP:
> + fusb302_set_state(data, DISABLED_SRC);
> + break;
> + case TYPEC_PORT_DRP:
> + fusb302_set_state(data, DISABLED_DRP);
> + break;
> + }
> +}
> +
> +static void fusb302_handle_disabled_snk_event(struct fusb302_data *data,
> + int event)
> +{
> + switch (event) {
> + case TOGGLE_DONE:
> + switch (data->status1a & STATUS1A_TOGSS_MASK) {
> + case STATUS1A_TOGSS_SNK_CC1:
> + case STATUS1A_TOGSS_SNK_CC2:
> + fusb302_set_state(data, UNATTACHED_SNK);
> + break;
> + }
> + break;
> +
> + case TIMEOUT:
> + /* Cannot become snk fallback to src */
> + fusb302_set_state(data, DISABLED_SRC);
> + mod_delayed_work(system_wq, &data->fallback_timeout,
> + TDRPTRYWAIT);
> + break;
> +
> + case FALLBACK_TIMEOUT:
> + /* Both states failed return to disabled drp state */
> + fusb302_set_state(data, DISABLED_DRP);
> + break;
> + }
> +}
> +
> +static void fusb302_handle_disabled_src_event(struct fusb302_data *data,
> + int event)
> +{
> + switch (event) {
> + case TOGGLE_DONE:
> + switch (data->status1a & STATUS1A_TOGSS_MASK) {
> + case STATUS1A_TOGSS_SRC_CC1:
> + case STATUS1A_TOGSS_SRC_CC2:
> + fusb302_set_state(data, UNATTACHED_SRC);
> + break;
> + }
> + break;
> +
> + case TIMEOUT:
> + /* Cannot become src fallback to snk */
> + fusb302_set_state(data, DISABLED_SNK);
> + mod_delayed_work(system_wq, &data->fallback_timeout,
> + TDRPTRYWAIT);
> + break;
> +
> + case FALLBACK_TIMEOUT:
> + /* Both states failed return to disabled drp state */
> + fusb302_set_state(data, DISABLED_DRP);
> + break;
> + }
> +}
> +
> +static void fusb302_handle_disabled_drp_event(struct fusb302_data *data,
> + int event)
> +{
> + switch (event) {
> + case TOGGLE_DONE:
> + switch (data->status1a & STATUS1A_TOGSS_MASK) {
> + case STATUS1A_TOGSS_SNK_CC1:
> + case STATUS1A_TOGSS_SNK_CC2:
> + if (data->preferred_role == TYPEC_SINK) {
> + /* Jump directly to UNATTACHED_SNK */
> + fusb302_set_state(data, UNATTACHED_SNK);
> + } else {
> + /* Try to become src */
> + fusb302_set_state(data, DISABLED_SNK);
> + mod_delayed_work(system_wq, &data->timeout,
> + TDRPTRY);
> + }
> + break;
> + case STATUS1A_TOGSS_SRC_CC1:
> + case STATUS1A_TOGSS_SRC_CC2:
> + if (data->preferred_role == TYPEC_SOURCE) {
> + /* Jump directly to UNATTACHED_SRC */
> + fusb302_set_state(data, UNATTACHED_SRC);
> + } else {
> + /*
> + * The USB-C spec says we must enable pull-downs
> + * and then wait tDRPTry before checking to
> + * avoid endless role-bouncing if both ends
> + * prefer the snk role.
> + */
> + fusb302_write_reg(data, REG_SWITCHES0,
> + SWITCHES0_PDWN);
> + fusb302_update_bits(data, REG_CONTROL2,
> + CONTROL2_TOGGLE, 0);
> + msleep(TDRPTRY_MS);
> + /*
> + * Use the toggle engine to do the src
> + * detection to keep things the same as for
> + * directly entering the src role.
> + */
> + fusb302_set_state(data, DISABLED_SNK);
> + mod_delayed_work(system_wq, &data->timeout,
> + TTOGCYCLE);
> + }
> + break;
> + }
> + break;
> + }
> +}
> +
> +static void fusb302_handle_unattached_snk_event(struct fusb302_data *data,
> + int event)
> +{
> + switch (event) {
> + case VBUS_VALID: /* Cable attached */
> + fusb302_set_state(data, ATTACHED_SNK);
> + break;
> + case TIMEOUT:
> + fusb302_set_state_disabled(data);
> + break;
> + }
> +}
> +
> +static void fusb302_handle_attached_snk_event(struct fusb302_data *data,
> + int event)
> +{
> + switch (event) {
> + case BC_LVL_CHANGE:
> + case COMP_LOW:
> + case COMP_HIGH:
> + mod_delayed_work(system_wq, &data->bc_work, TPDDEBOUNCE);
> + break;
> + case VBUS_INVALID: /* Cable detached */
> + fusb302_set_state_disabled(data);
> + break;
> + }
> +}
> +
> +static void fusb302_handle_unattached_src_event(struct fusb302_data *data,
> + int event)
> +{
> + switch (event) {
> + case COMP_LOW: /* Cable attached */
> + fusb302_set_state(data, ATTACHED_SRC);
> + break;
> + case TIMEOUT:
> + fusb302_set_state_disabled(data);
> + break;
> + }
> +}
> +
> +static void fusb302_handle_attached_src_event(struct fusb302_data *data,
> + int event)
> +{
> + switch (event) {
> + case COMP_HIGH: /* Cable detached */
> + fusb302_set_state_disabled(data);
> + break;
> + }
> +}
> +
> +static void fusb302_handle_event(struct fusb302_data *data, int event)
> +{
> +
> + mutex_lock(&data->lock);
> +
> + dev_dbg(data->dev, "Handling state %s event %s status %02x %02x\n",
> + fusb302_state_str[data->state], fusb302_event_str[event],
> + fusb302_read_reg(data, REG_STATUS0),
> + fusb302_read_reg(data, REG_STATUS1A));
> +
> + switch (data->state) {
> + case DISABLED_SNK:
> + fusb302_handle_disabled_snk_event(data, event);
> + break;
> + case DISABLED_SRC:
> + fusb302_handle_disabled_src_event(data, event);
> + break;
> + case DISABLED_DRP:
> + fusb302_handle_disabled_drp_event(data, event);
> + break;
> + case UNATTACHED_SNK:
> + fusb302_handle_unattached_snk_event(data, event);
> + break;
> + case ATTACHED_SNK:
> + fusb302_handle_attached_snk_event(data, event);
> + break;
> + case UNATTACHED_SRC:
> + fusb302_handle_unattached_src_event(data, event);
> + break;
> + case ATTACHED_SRC:
> + fusb302_handle_attached_src_event(data, event);
> + break;
> + }
> + mutex_unlock(&data->lock);
> +}
> +
> +static void fusb302_timeout(struct work_struct *work)
> +{
> + struct fusb302_data *data =
> + container_of(work, struct fusb302_data, timeout.work);
> +
> + fusb302_handle_event(data, TIMEOUT);
> +}
> +
> +static void fusb302_fallback_timeout(struct work_struct *work)
> +{
> + struct fusb302_data *data =
> + container_of(work, struct fusb302_data, fallback_timeout.work);
> +
> + fusb302_handle_event(data, FALLBACK_TIMEOUT);
> +}
> +
> +static void fusb302_report_bc_level(struct work_struct *work)
> +{
> + struct fusb302_data *data =
> + container_of(work, struct fusb302_data, bc_work.work);
> +
> + /* Clear old charger cable id */
> + fusb302_set_extcon_state(data, data->snk_cable_id, false);
> +
> + if (data->status0 & STATUS0_COMP) {
> + dev_warn(data->dev, "vRd over maximum, assuming 500mA source\n");
> + data->snk_cable_id = EXTCON_CHG_USB_SDP;
> + } else {
> + switch (data->status0 & STATUS0_BC_LEVEL_MASK) {
> + case STATUS0_BC_LEVEL_VRA:
> + case STATUS0_BC_LEVEL_VRDUSB:
> + data->snk_cable_id = EXTCON_CHG_USB_SDP;
> + break;
> + case STATUS0_BC_LEVEL_VRD15:
> + data->snk_cable_id = EXTCON_CHG_USB_CDP;
> + break;
> + case STATUS0_BC_LEVEL_VRD30:
> + /* Use CHG_USB_FAST to indicate 3A capability */
> + data->snk_cable_id = EXTCON_CHG_USB_FAST;
> + break;
> + }
> + }
> + fusb302_set_extcon_state(data, data->snk_cable_id, true);
> +}
> +
> +static irqreturn_t fusb302_irq_handler_thread(int irq, void *handler_data)
> +{
> + struct fusb302_data *data = handler_data;
> + int interrupt, interrupta;
> +
> + interrupt = fusb302_read_reg(data, REG_INTERRUPT);
> + interrupta = fusb302_read_reg(data, REG_INTERRUPTA);
> +
> + if (interrupt)
> + data->status0 = fusb302_read_reg(data, REG_STATUS0);
> +
> + if (interrupta)
> + data->status1a = fusb302_read_reg(data, REG_STATUS1A);
> +
> + dev_dbg(data->dev, "Handling interrupt %02x %02x status %02x %02x\n",
> + interrupt, interrupta, data->status0, data->status1a);
> +
> + if (interrupt & INTERRUPT_BC_LVL)
> + fusb302_handle_event(data, BC_LVL_CHANGE);
> +
> + if (interrupt & INTERRUPT_COMP_CHNG) {
> + if (data->status0 & STATUS0_COMP)
> + fusb302_handle_event(data, COMP_HIGH);
> + else
> + fusb302_handle_event(data, COMP_LOW);
> + }
> +
> + if (interrupt & INTERRUPT_VBUSOK) {
> + if (data->status0 & STATUS0_VBUSOK)
> + fusb302_handle_event(data, VBUS_VALID);
> + else
> + fusb302_handle_event(data, VBUS_INVALID);
> + }
> +
> + if (interrupta & INTERRUPTA_TOGDONE)
> + fusb302_handle_event(data, TOGGLE_DONE);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static const struct regmap_config fusb302_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .val_format_endian = REGMAP_ENDIAN_NATIVE,
> +};
> +
> +static int fusb302_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct fusb302_data *data;
> + int ret;
> +
> + if (!client->irq) {
> + dev_err(dev, "Error irq not specified\n");
> + return -EINVAL;
> + }
> +
> + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + data->dev = dev;
> + /* TODO make these 2 configurable using device-properties */
> + data->port_type = TYPEC_PORT_DRP;
> + data->preferred_role = TYPEC_SINK;
> +
> + data->regmap = devm_regmap_init_i2c(client, &fusb302_regmap_config);
> + if (IS_ERR(data->regmap)) {
> + ret = PTR_ERR(data->regmap);
> + dev_err(dev, "Error to initializing regmap: %d\n", ret);
> + return ret;
> + }
> +
> + mutex_init(&data->lock);
> + INIT_DELAYED_WORK(&data->timeout, fusb302_timeout);
> + INIT_DELAYED_WORK(&data->fallback_timeout, fusb302_fallback_timeout);
> + INIT_DELAYED_WORK(&data->bc_work, fusb302_report_bc_level);
> +
> + data->edev = devm_extcon_dev_allocate(dev, fusb302_extcon_cables);
> + if (IS_ERR(data->edev))
> + return PTR_ERR(data->edev);
> +
> + data->edev->name = "fusb302";
> +
> + ret = devm_extcon_dev_register(dev, data->edev);
> + if (ret)
> + return ret;
> +
> + extcon_set_property_capability(data->edev, EXTCON_USB,
> + EXTCON_PROP_USB_TYPEC_POLARITY);
> +
> + fusb302_write_reg(data, REG_RESET, RESET_SW_RESET);
> + usleep_range(10000, 20000);
> +
> + /* Enable power-saving */
> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_SAVE_PWR_MASK,
> + CONTROL2_SAVE_PWR_40MS);
> +
> + fusb302_set_state_disabled(data);
> +
> + ret = devm_request_threaded_irq(dev, client->irq, NULL,
> + fusb302_irq_handler_thread,
> + IRQF_TRIGGER_LOW | IRQF_ONESHOT, "fusb302", data);
> + if (ret) {
> + dev_err(dev, "Error requesting irq: %d\n", ret);
> + return ret;
> + }
> +
> + fusb302_update_bits(data, REG_CONTROL0, CONTROL0_INT_MASK, 0);
> +
> + i2c_set_clientdata(client, data);
> + return 0;
> +}
> +
> +static int fusb302_remove(struct i2c_client *client)
> +{
> + struct fusb302_data *data = i2c_get_clientdata(client);
> +
> + devm_free_irq(data->dev, client->irq, data);
> + cancel_delayed_work_sync(&data->timeout);
> + cancel_delayed_work_sync(&data->fallback_timeout);
> +
> + return 0;
> +}
> +
> +static const struct i2c_device_id fusb302_i2c_ids[] = {
> + { "fusb302" },
> + { },
> +};
> +MODULE_DEVICE_TABLE(i2c, fusb302_i2c_ids);
> +
> +static struct i2c_driver fusb302_driver = {
> + .probe_new = fusb302_probe,
> + .remove = fusb302_remove,
> + .id_table = fusb302_i2c_ids,
> + .driver = {
> + .name = "fusb302",
> + },
> +};
> +module_i2c_driver(fusb302_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
> +MODULE_DESCRIPTION("FUSB302 USB TYPE-C controller Driver");
> --
> 2.9.3
>
--
With Best Regards,
Andy Shevchenko
[toc] | [prev] | [next] | [standalone]
| From | Heikki Krogerus <heikki.krogerus@linux.intel.com> |
|---|---|
| Date | 2017-04-24 13:10 +0200 |
| Message-ID | <tzK2u-6io-19@gated-at.bofh.it> |
| In reply to | #1628504 |
+Guenter
On Fri, Apr 21, 2017 at 09:51:50PM +0300, Andy Shevchenko wrote:
> +Cc: Heikki.
>
> He might comment on this.
Thanks Andy.
> On Fri, Apr 21, 2017 at 4:01 PM, Hans de Goede <hdegoede@redhat.com> wrote:
> > Add support for USB TYPE-C cable detection on systems using a
> > FUSB302 USB TYPE-C controller.
> >
> > Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> > ---
> > drivers/extcon/Kconfig | 8 +
> > drivers/extcon/Makefile | 1 +
> > drivers/extcon/extcon-fusb302.c | 782 ++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 791 insertions(+)
> > create mode 100644 drivers/extcon/extcon-fusb302.c
There is now the typec class in linux-next that really needs to be
used with all USB Type-C PHYs like fusb302.
Unless I'm mistaken, Guenter has also written a driver for fusb302. I
have not seen it, but if I understood correctly, that driver will
register itself with the upcoming tcpm (USB Type-C Port Manager) [1],
which in practice would mean we can take properly advantage of the USB
PD transceiver on fusb302.
Guenter! Can you publish the fusb302 driver?
[1] https://chromium-review.googlesource.com/c/389916/
> > diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
> > index 32f2dc8..562db5b 100644
> > --- a/drivers/extcon/Kconfig
> > +++ b/drivers/extcon/Kconfig
> > @@ -35,6 +35,14 @@ config EXTCON_AXP288
> > Say Y here to enable support for USB peripheral detection
> > and USB MUX switching by X-Power AXP288 PMIC.
> >
> > +config EXTCON_FUSB302
> > + tristate "FUSB302 USB TYPE-C controller support"
> > + depends on I2C
> > + select REGMAP_I2C
> > + help
> > + Say Y here to enable support for USB TYPE-C cable detection using
> > + a FUSB302 USB TYPE-C controller.
> > +
> > config EXTCON_GPIO
> > tristate "GPIO extcon support"
> > depends on GPIOLIB || COMPILE_TEST
> > diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
> > index ecfa958..e5eb493 100644
> > --- a/drivers/extcon/Makefile
> > +++ b/drivers/extcon/Makefile
> > @@ -7,6 +7,7 @@ extcon-core-objs += extcon.o devres.o
> > obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o
> > obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
> > obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o
> > +obj-$(CONFIG_EXTCON_FUSB302) += extcon-fusb302.o
> > obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
> > obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
> > obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o
> > diff --git a/drivers/extcon/extcon-fusb302.c b/drivers/extcon/extcon-fusb302.c
> > new file mode 100644
> > index 0000000..577adb9
> > --- /dev/null
> > +++ b/drivers/extcon/extcon-fusb302.c
> > @@ -0,0 +1,782 @@
> > +/*
> > + * Extcon USB-C cable detection driver for FUSB302 USB TYPE-C controller
> > + *
> > + * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > + */
> > +
> > +#include <linux/extcon.h>
> > +#include <linux/i2c.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/regmap.h>
> > +#include <linux/slab.h>
> > +#include <linux/workqueue.h>
> > +#include "extcon.h"
> > +
> > +#define REG_DEVICE_ID 0x01
> > +#define DEVICE_ID_VER_MASK GENMASK(7, 4)
> > +#define DEVICE_ID_FUSB302_VER 0x80
> > +
> > +#define REG_SWITCHES0 0x02
> > +#define SWITCHES0_PDWN1 BIT(0)
> > +#define SWITCHES0_PDWN2 BIT(1)
> > +#define SWITCHES0_PDWN GENMASK(1, 0)
> > +#define SWITCHES0_MEAS_CC1 BIT(2)
> > +#define SWITCHES0_MEAS_CC2 BIT(3)
> > +#define SWITCHES0_VCONN_CC1 BIT(4)
> > +#define SWITCHES0_VCONN_CC2 BIT(5)
> > +#define SWITCHES0_PU_EN1 BIT(6)
> > +#define SWITCHES0_PU_EN2 BIT(7)
> > +
> > +#define REG_MEASURE 0x04
> > +#define MEASURE_MDAC_MASK GENMASK(5, 0)
> > +/* Datasheet says MDAC must be set to 0x34 / 2226mV for vRd-3.0 detection */
> > +#define MEASURE_MDAC_SNK_VRD30 0x34
> > +/* MDAC must be set to 0x25 / 1600mV for disconnect det. with 80uA host-cur */
> > +#define MEASURE_MDAC_SRC_80UA_HOST_CUR 0x25
> > +
> > +#define REG_CONTROL0 0x06
> > +#define CONTROL0_HOST_CUR_MASK GENMASK(3, 2)
> > +#define CONTROL0_HOST_CUR_DISABLED (0 << 2)
> > +#define CONTROL0_HOST_CUR_80UA (1 << 2)
> > +#define CONTROL0_HOST_CUR_180UA (2 << 2)
> > +#define CONTROL0_HOST_CUR_330UA (3 << 2)
> > +#define CONTROL0_INT_MASK BIT(5)
> > +
> > +#define REG_CONTROL2 0x08
> > +#define CONTROL2_TOGGLE BIT(0)
> > +#define CONTROL2_MODE_MASK GENMASK(2, 1)
> > +#define CONTROL2_MODE_DRP (1 << 1)
> > +#define CONTROL2_MODE_SNK (2 << 1)
> > +#define CONTROL2_MODE_SRC (3 << 1)
> > +#define CONTROL2_SAVE_PWR_MASK GENMASK(7, 6)
> > +#define CONTROL2_SAVE_PWR_DISABLED (0 << 6)
> > +#define CONTROL2_SAVE_PWR_40MS (1 << 6)
> > +#define CONTROL2_SAVE_PWR_80MS (2 << 6)
> > +#define CONTROL2_SAVE_PWR_160MS (3 << 6)
> > +
> > +#define REG_MASK1 0x0a
> > +/* REG_MASK1 value for low-power / disabled state from datasheet */
> > +#define MASK1_DISABLED 0xfe
> > +#define MASK1_COMP_CHNG BIT(5)
> > +#define MASK1_VBUSOK BIT(7)
> > +
> > +#define REG_POWER 0x0b
> > +/* REG_POWER values for disabled and normal state from datasheet */
> > +#define POWER_DISABLED BIT(0)
> > +#define POWER_NORMAL GENMASK(2, 0)
> > +
> > +#define REG_RESET 0x0c
> > +#define RESET_SW_RESET BIT(0)
> > +
> > +#define REG_OCP 0x0d
> > +
> > +#define REG_MASKA 0x0e
> > +/* REG_MASKA value for low-power / disabled state from datasheet */
> > +#define MASKA_DISABLED 0xbf
> > +
> > +#define REG_MASKB 0x0f
> > +/* REG_MASKB value for low-power / disabled state from datasheet */
> > +#define MASKB_DISABLED 0x01
> > +
> > +#define REG_STATUS1A 0x3d
> > +#define STATUS1A_TOGSS_MASK GENMASK(5, 3)
> > +#define STATUS1A_TOGSS_SRC_CC1 (1 << 3)
> > +#define STATUS1A_TOGSS_SRC_CC2 (2 << 3)
> > +#define STATUS1A_TOGSS_SNK_CC1 (5 << 3)
> > +#define STATUS1A_TOGSS_SNK_CC2 (6 << 3)
> > +
> > +#define REG_INTERRUPTA 0x3e
> > +#define INTERRUPTA_TOGDONE BIT(6)
> > +
> > +#define REG_STATUS0 0x40
> > +#define STATUS0_BC_LEVEL_MASK GENMASK(1, 0)
> > +#define STATUS0_BC_LEVEL_VRA 0
> > +#define STATUS0_BC_LEVEL_VRDUSB 1
> > +#define STATUS0_BC_LEVEL_VRD15 2
> > +#define STATUS0_BC_LEVEL_VRD30 3
> > +#define STATUS0_COMP BIT(5)
> > +#define STATUS0_VBUSOK BIT(7)
> > +
> > +#define REG_INTERRUPT 0x42
> > +#define INTERRUPT_BC_LVL BIT(0)
> > +#define INTERRUPT_COMP_CHNG BIT(5)
> > +#define INTERRUPT_VBUSOK BIT(7)
> > +
> > +/* Timeouts from the FUSB302 datasheet */
> > +#define TTOGCYCLE msecs_to_jiffies(40 + 60 + 40)
> > +
> > +/* Timeouts from the USB-C specification */
> > +#define TPDDEBOUNCE msecs_to_jiffies(20)
> > +#define TDRPTRY_MS 75
> > +#define TDRPTRY msecs_to_jiffies(TDRPTRY_MS)
> > +#define TDRPTRYWAIT msecs_to_jiffies(400)
> > +#define TVBUSON msecs_to_jiffies(275)
> > +
> > +enum typec_port_type {
> > + TYPEC_PORT_DFP,
> > + TYPEC_PORT_UFP,
> > + TYPEC_PORT_DRP,
> > +};
> > +
> > +enum typec_role {
> > + TYPEC_SINK,
> > + TYPEC_SOURCE,
> > +};
> > +
> > +enum fusb302_state {
> > + DISABLED_SNK, /* UFP */
> > + DISABLED_SRC, /* DFP */
> > + DISABLED_DRP,
> > + UNATTACHED_SNK,
> > + ATTACHED_SNK,
> > + UNATTACHED_SRC,
> > + ATTACHED_SRC,
> > +};
> > +/* For debugging */
> > +static __maybe_unused const char *fusb302_state_str[] = {
> > + "DISABLED_SNK",
> > + "DISABLED_SRC",
> > + "DISABLED_DRP",
> > + "UNATTACHED_SNK",
> > + "ATTACHED_SNK",
> > + "UNATTACHED_SRC",
> > + "ATTACHED_SRC",
> > +};
> > +
> > +enum fusb302_event {
> > + TOGGLE_DONE,
> > + BC_LVL_CHANGE,
> > + VBUS_VALID,
> > + VBUS_INVALID,
> > + COMP_LOW,
> > + COMP_HIGH,
> > + TIMEOUT,
> > + FALLBACK_TIMEOUT,
> > +};
> > +/* For debugging */
> > +static __maybe_unused const char *fusb302_event_str[] = {
> > + "TOGGLE_DONE",
> > + "BC_LVL_CHANGE",
> > + "VBUS_VALID",
> > + "VBUS_INVALID",
> > + "COMP_LOW",
> > + "COMP_HIGH",
> > + "TIMEOUT",
> > + "FALLBACK_TIMEOUT",
> > +};
> > +
> > +static const unsigned int fusb302_extcon_cables[] = {
> > + EXTCON_USB,
> > + EXTCON_USB_HOST,
> > + EXTCON_CHG_USB_SDP,
> > + EXTCON_CHG_USB_CDP,
> > + EXTCON_CHG_USB_FAST,
> > + EXTCON_NONE,
> > +};
> > +
> > +struct fusb302_data {
> > + struct device *dev;
> > + struct regmap *regmap;
> > + struct extcon_dev *edev;
> > + struct mutex lock;
> > + struct delayed_work timeout;
> > + struct delayed_work fallback_timeout;
> > + struct delayed_work bc_work;
> > + enum fusb302_state state;
> > + enum typec_port_type port_type;
> > + enum typec_role preferred_role;
> > + int status0;
> > + int status1a;
> > + unsigned int snk_cable_id;
> > +};
> > +
> > +static void fusb302_write_reg(struct fusb302_data *data, int reg, int val)
> > +{
> > + int ret;
> > +
> > + ret = regmap_write(data->regmap, reg, val);
> > + if (ret)
> > + dev_err(data->dev, "Error writing reg %02x: %d\n", reg, ret);
> > +}
> > +
> > +static int fusb302_read_reg(struct fusb302_data *data, int reg)
> > +{
> > + int ret, val;
> > +
> > + ret = regmap_read(data->regmap, reg, &val);
> > + if (ret) {
> > + dev_err(data->dev, "Error reading reg %02x: %d\n", reg, ret);
> > + return 0;
> > + }
> > +
> > + return val;
> > +}
> > +
> > +static void fusb302_update_bits(struct fusb302_data *data, int reg,
> > + int mask, int val)
> > +{
> > + int ret;
> > +
> > + ret = regmap_update_bits(data->regmap, reg, mask, val);
> > + if (ret)
> > + dev_err(data->dev, "Error modifying reg %02x: %d\n", reg, ret);
> > +}
> > +
> > +/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */
> > +static void fusb302_set_extcon_state(struct fusb302_data *data,
> > + unsigned int cable, bool state)
> > +{
> > + extcon_set_state_sync(data->edev, cable, state);
> > + if (cable == EXTCON_CHG_USB_SDP)
> > + extcon_set_state_sync(data->edev, EXTCON_USB, state);
> > +}
> > +
> > +/* Helper for the disabled states */
> > +static void fusb302_disable(struct fusb302_data *data)
> > +{
> > + fusb302_write_reg(data, REG_POWER, POWER_DISABLED);
> > + fusb302_write_reg(data, REG_MASK1, MASK1_DISABLED);
> > + fusb302_write_reg(data, REG_MASKA, MASKA_DISABLED);
> > + fusb302_write_reg(data, REG_MASKB, MASKB_DISABLED);
> > + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
> > +}
> > +
> > +/*
> > + * fusb302_set_state() and fusb302_handle_event() implement the 3 state
> > + * machines from the datasheet folded into 1 state-machine for code re-use.
> > + */
> > +static void fusb302_set_state(struct fusb302_data *data,
> > + enum fusb302_state state)
> > +{
> > + int status, switches0 = 0;
> > + enum fusb302_state old_state = data->state;
> > + union extcon_property_value prop;
> > +
> > + /* Kill pending timeouts from previous state */
> > + cancel_delayed_work(&data->timeout);
> > + cancel_delayed_work(&data->fallback_timeout);
> > + cancel_delayed_work(&data->bc_work);
> > +
> > + dev_dbg(data->dev, "New state %s\n", fusb302_state_str[state]);
> > +
> > + switch (old_state) {
> > + case ATTACHED_SNK:
> > + fusb302_set_extcon_state(data, data->snk_cable_id, false);
> > + break;
> > + case ATTACHED_SRC:
> > + fusb302_set_extcon_state(data, EXTCON_USB_HOST, false);
> > + break;
> > + default:
> > + break; /* Do nothing */
> > + }
> > +
> > + switch (state) {
> > + case DISABLED_SNK:
> > + fusb302_disable(data);
> > + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
> > + CONTROL2_MODE_SNK);
> > + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
> > + CONTROL2_TOGGLE);
> > + break;
> > +
> > + case DISABLED_SRC:
> > + fusb302_disable(data);
> > + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
> > + CONTROL2_MODE_SRC);
> > + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
> > + CONTROL2_TOGGLE);
> > + break;
> > +
> > + case DISABLED_DRP:
> > + fusb302_disable(data);
> > + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
> > + CONTROL2_MODE_DRP);
> > + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
> > + CONTROL2_TOGGLE);
> > + break;
> > +
> > + case UNATTACHED_SNK:
> > + fusb302_write_reg(data, REG_POWER, POWER_NORMAL);
> > +
> > + /* Enable pull-down on CC1 / CC2 based on orientation */
> > + switch (data->status1a & STATUS1A_TOGSS_MASK) {
> > + case STATUS1A_TOGSS_SNK_CC1:
> > + switches0 = SWITCHES0_PDWN1 | SWITCHES0_MEAS_CC1;
> > + prop.intval = USB_TYPEC_POLARITY_NORMAL;
> > + break;
> > + case STATUS1A_TOGSS_SNK_CC2:
> > + switches0 = SWITCHES0_PDWN2 | SWITCHES0_MEAS_CC2;
> > + prop.intval = USB_TYPEC_POLARITY_FLIP;
> > + break;
> > + }
> > + fusb302_write_reg(data, REG_SWITCHES0, switches0);
> > + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
> > + extcon_set_property(data->edev, EXTCON_USB,
> > + EXTCON_PROP_USB_TYPEC_POLARITY, prop);
> > +
> > + /* Enable VBUSOK and COMP irq at 2.24V for BC detection */
> > + fusb302_update_bits(data, REG_MASK1,
> > + MASK1_VBUSOK | MASK1_COMP_CHNG, 0);
> > + fusb302_update_bits(data, REG_MEASURE, MEASURE_MDAC_MASK,
> > + MEASURE_MDAC_SNK_VRD30);
> > +
> > + status = fusb302_read_reg(data, REG_STATUS0);
> > + if (status & STATUS0_VBUSOK) {
> > + /* Go straight to ATTACHED_SNK */
> > + fusb302_set_state(data, ATTACHED_SNK);
> > + return;
> > + }
> > +
> > + mod_delayed_work(system_wq, &data->timeout, TVBUSON);
> > + break;
> > +
> > + case ATTACHED_SNK:
> > + mod_delayed_work(system_wq, &data->bc_work, TPDDEBOUNCE);
> > + break;
> > +
> > + case UNATTACHED_SRC:
> > + fusb302_write_reg(data, REG_POWER, POWER_NORMAL);
> > +
> > + /* Enable pull-up / Vconn on CC1 / CC2 based on orientation */
> > + switch (data->status1a & STATUS1A_TOGSS_MASK) {
> > + case STATUS1A_TOGSS_SRC_CC1:
> > + switches0 = SWITCHES0_PU_EN1 | SWITCHES0_VCONN_CC2 |
> > + SWITCHES0_MEAS_CC1;
> > + prop.intval = USB_TYPEC_POLARITY_NORMAL;
> > + break;
> > + case STATUS1A_TOGSS_SRC_CC2:
> > + switches0 = SWITCHES0_PU_EN2 | SWITCHES0_VCONN_CC1 |
> > + SWITCHES0_MEAS_CC2;
> > + prop.intval = USB_TYPEC_POLARITY_FLIP;
> > + break;
> > + }
> > + fusb302_write_reg(data, REG_SWITCHES0, switches0);
> > + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
> > + extcon_set_property(data->edev, EXTCON_USB,
> > + EXTCON_PROP_USB_TYPEC_POLARITY, prop);
> > +
> > + /* Enable COMP irq at 1.6V for detach detection */
> > + fusb302_update_bits(data, REG_MASK1, MASK1_COMP_CHNG, 0);
> > + fusb302_update_bits(data, REG_MEASURE, MEASURE_MDAC_MASK,
> > + MEASURE_MDAC_SRC_80UA_HOST_CUR);
> > +
> > + status = fusb302_read_reg(data, REG_STATUS0);
> > + if (!(status & STATUS0_COMP)) {
> > + /* Go straight to ATTACHED_SRC */
> > + fusb302_set_state(data, ATTACHED_SRC);
> > + return;
> > + }
> > +
> > + mod_delayed_work(system_wq, &data->timeout, TPDDEBOUNCE);
> > + break;
> > +
> > + case ATTACHED_SRC:
> > + fusb302_set_extcon_state(data, EXTCON_USB_HOST, true);
> > + break;
> > + }
> > +
> > + data->state = state;
> > +}
> > +
> > +static void fusb302_set_state_disabled(struct fusb302_data *data)
> > +{
> > +
> > + switch (data->port_type) {
> > + case TYPEC_PORT_UFP:
> > + fusb302_set_state(data, DISABLED_SNK);
> > + break;
> > + case TYPEC_PORT_DFP:
> > + fusb302_set_state(data, DISABLED_SRC);
> > + break;
> > + case TYPEC_PORT_DRP:
> > + fusb302_set_state(data, DISABLED_DRP);
> > + break;
> > + }
> > +}
> > +
> > +static void fusb302_handle_disabled_snk_event(struct fusb302_data *data,
> > + int event)
> > +{
> > + switch (event) {
> > + case TOGGLE_DONE:
> > + switch (data->status1a & STATUS1A_TOGSS_MASK) {
> > + case STATUS1A_TOGSS_SNK_CC1:
> > + case STATUS1A_TOGSS_SNK_CC2:
> > + fusb302_set_state(data, UNATTACHED_SNK);
> > + break;
> > + }
> > + break;
> > +
> > + case TIMEOUT:
> > + /* Cannot become snk fallback to src */
> > + fusb302_set_state(data, DISABLED_SRC);
> > + mod_delayed_work(system_wq, &data->fallback_timeout,
> > + TDRPTRYWAIT);
> > + break;
> > +
> > + case FALLBACK_TIMEOUT:
> > + /* Both states failed return to disabled drp state */
> > + fusb302_set_state(data, DISABLED_DRP);
> > + break;
> > + }
> > +}
> > +
> > +static void fusb302_handle_disabled_src_event(struct fusb302_data *data,
> > + int event)
> > +{
> > + switch (event) {
> > + case TOGGLE_DONE:
> > + switch (data->status1a & STATUS1A_TOGSS_MASK) {
> > + case STATUS1A_TOGSS_SRC_CC1:
> > + case STATUS1A_TOGSS_SRC_CC2:
> > + fusb302_set_state(data, UNATTACHED_SRC);
> > + break;
> > + }
> > + break;
> > +
> > + case TIMEOUT:
> > + /* Cannot become src fallback to snk */
> > + fusb302_set_state(data, DISABLED_SNK);
> > + mod_delayed_work(system_wq, &data->fallback_timeout,
> > + TDRPTRYWAIT);
> > + break;
> > +
> > + case FALLBACK_TIMEOUT:
> > + /* Both states failed return to disabled drp state */
> > + fusb302_set_state(data, DISABLED_DRP);
> > + break;
> > + }
> > +}
> > +
> > +static void fusb302_handle_disabled_drp_event(struct fusb302_data *data,
> > + int event)
> > +{
> > + switch (event) {
> > + case TOGGLE_DONE:
> > + switch (data->status1a & STATUS1A_TOGSS_MASK) {
> > + case STATUS1A_TOGSS_SNK_CC1:
> > + case STATUS1A_TOGSS_SNK_CC2:
> > + if (data->preferred_role == TYPEC_SINK) {
> > + /* Jump directly to UNATTACHED_SNK */
> > + fusb302_set_state(data, UNATTACHED_SNK);
> > + } else {
> > + /* Try to become src */
> > + fusb302_set_state(data, DISABLED_SNK);
> > + mod_delayed_work(system_wq, &data->timeout,
> > + TDRPTRY);
> > + }
> > + break;
> > + case STATUS1A_TOGSS_SRC_CC1:
> > + case STATUS1A_TOGSS_SRC_CC2:
> > + if (data->preferred_role == TYPEC_SOURCE) {
> > + /* Jump directly to UNATTACHED_SRC */
> > + fusb302_set_state(data, UNATTACHED_SRC);
> > + } else {
> > + /*
> > + * The USB-C spec says we must enable pull-downs
> > + * and then wait tDRPTry before checking to
> > + * avoid endless role-bouncing if both ends
> > + * prefer the snk role.
> > + */
> > + fusb302_write_reg(data, REG_SWITCHES0,
> > + SWITCHES0_PDWN);
> > + fusb302_update_bits(data, REG_CONTROL2,
> > + CONTROL2_TOGGLE, 0);
> > + msleep(TDRPTRY_MS);
> > + /*
> > + * Use the toggle engine to do the src
> > + * detection to keep things the same as for
> > + * directly entering the src role.
> > + */
> > + fusb302_set_state(data, DISABLED_SNK);
> > + mod_delayed_work(system_wq, &data->timeout,
> > + TTOGCYCLE);
> > + }
> > + break;
> > + }
> > + break;
> > + }
> > +}
> > +
> > +static void fusb302_handle_unattached_snk_event(struct fusb302_data *data,
> > + int event)
> > +{
> > + switch (event) {
> > + case VBUS_VALID: /* Cable attached */
> > + fusb302_set_state(data, ATTACHED_SNK);
> > + break;
> > + case TIMEOUT:
> > + fusb302_set_state_disabled(data);
> > + break;
> > + }
> > +}
> > +
> > +static void fusb302_handle_attached_snk_event(struct fusb302_data *data,
> > + int event)
> > +{
> > + switch (event) {
> > + case BC_LVL_CHANGE:
> > + case COMP_LOW:
> > + case COMP_HIGH:
> > + mod_delayed_work(system_wq, &data->bc_work, TPDDEBOUNCE);
> > + break;
> > + case VBUS_INVALID: /* Cable detached */
> > + fusb302_set_state_disabled(data);
> > + break;
> > + }
> > +}
> > +
> > +static void fusb302_handle_unattached_src_event(struct fusb302_data *data,
> > + int event)
> > +{
> > + switch (event) {
> > + case COMP_LOW: /* Cable attached */
> > + fusb302_set_state(data, ATTACHED_SRC);
> > + break;
> > + case TIMEOUT:
> > + fusb302_set_state_disabled(data);
> > + break;
> > + }
> > +}
> > +
> > +static void fusb302_handle_attached_src_event(struct fusb302_data *data,
> > + int event)
> > +{
> > + switch (event) {
> > + case COMP_HIGH: /* Cable detached */
> > + fusb302_set_state_disabled(data);
> > + break;
> > + }
> > +}
> > +
> > +static void fusb302_handle_event(struct fusb302_data *data, int event)
> > +{
> > +
> > + mutex_lock(&data->lock);
> > +
> > + dev_dbg(data->dev, "Handling state %s event %s status %02x %02x\n",
> > + fusb302_state_str[data->state], fusb302_event_str[event],
> > + fusb302_read_reg(data, REG_STATUS0),
> > + fusb302_read_reg(data, REG_STATUS1A));
> > +
> > + switch (data->state) {
> > + case DISABLED_SNK:
> > + fusb302_handle_disabled_snk_event(data, event);
> > + break;
> > + case DISABLED_SRC:
> > + fusb302_handle_disabled_src_event(data, event);
> > + break;
> > + case DISABLED_DRP:
> > + fusb302_handle_disabled_drp_event(data, event);
> > + break;
> > + case UNATTACHED_SNK:
> > + fusb302_handle_unattached_snk_event(data, event);
> > + break;
> > + case ATTACHED_SNK:
> > + fusb302_handle_attached_snk_event(data, event);
> > + break;
> > + case UNATTACHED_SRC:
> > + fusb302_handle_unattached_src_event(data, event);
> > + break;
> > + case ATTACHED_SRC:
> > + fusb302_handle_attached_src_event(data, event);
> > + break;
> > + }
> > + mutex_unlock(&data->lock);
> > +}
> > +
> > +static void fusb302_timeout(struct work_struct *work)
> > +{
> > + struct fusb302_data *data =
> > + container_of(work, struct fusb302_data, timeout.work);
> > +
> > + fusb302_handle_event(data, TIMEOUT);
> > +}
> > +
> > +static void fusb302_fallback_timeout(struct work_struct *work)
> > +{
> > + struct fusb302_data *data =
> > + container_of(work, struct fusb302_data, fallback_timeout.work);
> > +
> > + fusb302_handle_event(data, FALLBACK_TIMEOUT);
> > +}
> > +
> > +static void fusb302_report_bc_level(struct work_struct *work)
> > +{
> > + struct fusb302_data *data =
> > + container_of(work, struct fusb302_data, bc_work.work);
> > +
> > + /* Clear old charger cable id */
> > + fusb302_set_extcon_state(data, data->snk_cable_id, false);
> > +
> > + if (data->status0 & STATUS0_COMP) {
> > + dev_warn(data->dev, "vRd over maximum, assuming 500mA source\n");
> > + data->snk_cable_id = EXTCON_CHG_USB_SDP;
> > + } else {
> > + switch (data->status0 & STATUS0_BC_LEVEL_MASK) {
> > + case STATUS0_BC_LEVEL_VRA:
> > + case STATUS0_BC_LEVEL_VRDUSB:
> > + data->snk_cable_id = EXTCON_CHG_USB_SDP;
> > + break;
> > + case STATUS0_BC_LEVEL_VRD15:
> > + data->snk_cable_id = EXTCON_CHG_USB_CDP;
> > + break;
> > + case STATUS0_BC_LEVEL_VRD30:
> > + /* Use CHG_USB_FAST to indicate 3A capability */
> > + data->snk_cable_id = EXTCON_CHG_USB_FAST;
> > + break;
> > + }
> > + }
> > + fusb302_set_extcon_state(data, data->snk_cable_id, true);
> > +}
> > +
> > +static irqreturn_t fusb302_irq_handler_thread(int irq, void *handler_data)
> > +{
> > + struct fusb302_data *data = handler_data;
> > + int interrupt, interrupta;
> > +
> > + interrupt = fusb302_read_reg(data, REG_INTERRUPT);
> > + interrupta = fusb302_read_reg(data, REG_INTERRUPTA);
> > +
> > + if (interrupt)
> > + data->status0 = fusb302_read_reg(data, REG_STATUS0);
> > +
> > + if (interrupta)
> > + data->status1a = fusb302_read_reg(data, REG_STATUS1A);
> > +
> > + dev_dbg(data->dev, "Handling interrupt %02x %02x status %02x %02x\n",
> > + interrupt, interrupta, data->status0, data->status1a);
> > +
> > + if (interrupt & INTERRUPT_BC_LVL)
> > + fusb302_handle_event(data, BC_LVL_CHANGE);
> > +
> > + if (interrupt & INTERRUPT_COMP_CHNG) {
> > + if (data->status0 & STATUS0_COMP)
> > + fusb302_handle_event(data, COMP_HIGH);
> > + else
> > + fusb302_handle_event(data, COMP_LOW);
> > + }
> > +
> > + if (interrupt & INTERRUPT_VBUSOK) {
> > + if (data->status0 & STATUS0_VBUSOK)
> > + fusb302_handle_event(data, VBUS_VALID);
> > + else
> > + fusb302_handle_event(data, VBUS_INVALID);
> > + }
> > +
> > + if (interrupta & INTERRUPTA_TOGDONE)
> > + fusb302_handle_event(data, TOGGLE_DONE);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static const struct regmap_config fusb302_regmap_config = {
> > + .reg_bits = 8,
> > + .val_bits = 8,
> > + .val_format_endian = REGMAP_ENDIAN_NATIVE,
> > +};
> > +
> > +static int fusb302_probe(struct i2c_client *client)
> > +{
> > + struct device *dev = &client->dev;
> > + struct fusb302_data *data;
> > + int ret;
> > +
> > + if (!client->irq) {
> > + dev_err(dev, "Error irq not specified\n");
> > + return -EINVAL;
> > + }
> > +
> > + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> > + if (!data)
> > + return -ENOMEM;
> > +
> > + data->dev = dev;
> > + /* TODO make these 2 configurable using device-properties */
> > + data->port_type = TYPEC_PORT_DRP;
> > + data->preferred_role = TYPEC_SINK;
> > +
> > + data->regmap = devm_regmap_init_i2c(client, &fusb302_regmap_config);
> > + if (IS_ERR(data->regmap)) {
> > + ret = PTR_ERR(data->regmap);
> > + dev_err(dev, "Error to initializing regmap: %d\n", ret);
> > + return ret;
> > + }
> > +
> > + mutex_init(&data->lock);
> > + INIT_DELAYED_WORK(&data->timeout, fusb302_timeout);
> > + INIT_DELAYED_WORK(&data->fallback_timeout, fusb302_fallback_timeout);
> > + INIT_DELAYED_WORK(&data->bc_work, fusb302_report_bc_level);
> > +
> > + data->edev = devm_extcon_dev_allocate(dev, fusb302_extcon_cables);
> > + if (IS_ERR(data->edev))
> > + return PTR_ERR(data->edev);
> > +
> > + data->edev->name = "fusb302";
> > +
> > + ret = devm_extcon_dev_register(dev, data->edev);
> > + if (ret)
> > + return ret;
> > +
> > + extcon_set_property_capability(data->edev, EXTCON_USB,
> > + EXTCON_PROP_USB_TYPEC_POLARITY);
> > +
> > + fusb302_write_reg(data, REG_RESET, RESET_SW_RESET);
> > + usleep_range(10000, 20000);
> > +
> > + /* Enable power-saving */
> > + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_SAVE_PWR_MASK,
> > + CONTROL2_SAVE_PWR_40MS);
> > +
> > + fusb302_set_state_disabled(data);
> > +
> > + ret = devm_request_threaded_irq(dev, client->irq, NULL,
> > + fusb302_irq_handler_thread,
> > + IRQF_TRIGGER_LOW | IRQF_ONESHOT, "fusb302", data);
> > + if (ret) {
> > + dev_err(dev, "Error requesting irq: %d\n", ret);
> > + return ret;
> > + }
> > +
> > + fusb302_update_bits(data, REG_CONTROL0, CONTROL0_INT_MASK, 0);
> > +
> > + i2c_set_clientdata(client, data);
> > + return 0;
> > +}
> > +
> > +static int fusb302_remove(struct i2c_client *client)
> > +{
> > + struct fusb302_data *data = i2c_get_clientdata(client);
> > +
> > + devm_free_irq(data->dev, client->irq, data);
> > + cancel_delayed_work_sync(&data->timeout);
> > + cancel_delayed_work_sync(&data->fallback_timeout);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct i2c_device_id fusb302_i2c_ids[] = {
> > + { "fusb302" },
> > + { },
> > +};
> > +MODULE_DEVICE_TABLE(i2c, fusb302_i2c_ids);
> > +
> > +static struct i2c_driver fusb302_driver = {
> > + .probe_new = fusb302_probe,
> > + .remove = fusb302_remove,
> > + .id_table = fusb302_i2c_ids,
> > + .driver = {
> > + .name = "fusb302",
> > + },
> > +};
> > +module_i2c_driver(fusb302_driver);
> > +
> > +MODULE_LICENSE("GPL");
> > +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
> > +MODULE_DESCRIPTION("FUSB302 USB TYPE-C controller Driver");
> > --
> > 2.9.3
> >
Br,
--
heikki
[toc] | [prev] | [next] | [standalone]
| From | Guenter Roeck <linux@roeck-us.net> |
|---|---|
| Date | 2017-04-24 15:20 +0200 |
| Message-ID | <tzM4h-7tv-11@gated-at.bofh.it> |
| In reply to | #1629433 |
On 04/24/2017 04:02 AM, Heikki Krogerus wrote:
> +Guenter
>
> On Fri, Apr 21, 2017 at 09:51:50PM +0300, Andy Shevchenko wrote:
>> +Cc: Heikki.
>>
>> He might comment on this.
>
> Thanks Andy.
>
>> On Fri, Apr 21, 2017 at 4:01 PM, Hans de Goede <hdegoede@redhat.com> wrote:
>>> Add support for USB TYPE-C cable detection on systems using a
>>> FUSB302 USB TYPE-C controller.
>>>
>>> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
>>> ---
>>> drivers/extcon/Kconfig | 8 +
>>> drivers/extcon/Makefile | 1 +
>>> drivers/extcon/extcon-fusb302.c | 782 ++++++++++++++++++++++++++++++++++++++++
>>> 3 files changed, 791 insertions(+)
>>> create mode 100644 drivers/extcon/extcon-fusb302.c
>
> There is now the typec class in linux-next that really needs to be
> used with all USB Type-C PHYs like fusb302.
>
> Unless I'm mistaken, Guenter has also written a driver for fusb302. I
Not me; it was Nathan.
> have not seen it, but if I understood correctly, that driver will
> register itself with the upcoming tcpm (USB Type-C Port Manager) [1],
> which in practice would mean we can take properly advantage of the USB
> PD transceiver on fusb302.
>
> Guenter! Can you publish the fusb302 driver?
>
Working on it.
Guenter
>
> [1] https://chromium-review.googlesource.com/c/389916/
>
>
>>> diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
>>> index 32f2dc8..562db5b 100644
>>> --- a/drivers/extcon/Kconfig
>>> +++ b/drivers/extcon/Kconfig
>>> @@ -35,6 +35,14 @@ config EXTCON_AXP288
>>> Say Y here to enable support for USB peripheral detection
>>> and USB MUX switching by X-Power AXP288 PMIC.
>>>
>>> +config EXTCON_FUSB302
>>> + tristate "FUSB302 USB TYPE-C controller support"
>>> + depends on I2C
>>> + select REGMAP_I2C
>>> + help
>>> + Say Y here to enable support for USB TYPE-C cable detection using
>>> + a FUSB302 USB TYPE-C controller.
>>> +
>>> config EXTCON_GPIO
>>> tristate "GPIO extcon support"
>>> depends on GPIOLIB || COMPILE_TEST
>>> diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
>>> index ecfa958..e5eb493 100644
>>> --- a/drivers/extcon/Makefile
>>> +++ b/drivers/extcon/Makefile
>>> @@ -7,6 +7,7 @@ extcon-core-objs += extcon.o devres.o
>>> obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o
>>> obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
>>> obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o
>>> +obj-$(CONFIG_EXTCON_FUSB302) += extcon-fusb302.o
>>> obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
>>> obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
>>> obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o
>>> diff --git a/drivers/extcon/extcon-fusb302.c b/drivers/extcon/extcon-fusb302.c
>>> new file mode 100644
>>> index 0000000..577adb9
>>> --- /dev/null
>>> +++ b/drivers/extcon/extcon-fusb302.c
>>> @@ -0,0 +1,782 @@
>>> +/*
>>> + * Extcon USB-C cable detection driver for FUSB302 USB TYPE-C controller
>>> + *
>>> + * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License version 2 as
>>> + * published by the Free Software Foundation.
>>> + */
>>> +
>>> +#include <linux/extcon.h>
>>> +#include <linux/i2c.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/module.h>
>>> +#include <linux/regmap.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/workqueue.h>
>>> +#include "extcon.h"
>>> +
>>> +#define REG_DEVICE_ID 0x01
>>> +#define DEVICE_ID_VER_MASK GENMASK(7, 4)
>>> +#define DEVICE_ID_FUSB302_VER 0x80
>>> +
>>> +#define REG_SWITCHES0 0x02
>>> +#define SWITCHES0_PDWN1 BIT(0)
>>> +#define SWITCHES0_PDWN2 BIT(1)
>>> +#define SWITCHES0_PDWN GENMASK(1, 0)
>>> +#define SWITCHES0_MEAS_CC1 BIT(2)
>>> +#define SWITCHES0_MEAS_CC2 BIT(3)
>>> +#define SWITCHES0_VCONN_CC1 BIT(4)
>>> +#define SWITCHES0_VCONN_CC2 BIT(5)
>>> +#define SWITCHES0_PU_EN1 BIT(6)
>>> +#define SWITCHES0_PU_EN2 BIT(7)
>>> +
>>> +#define REG_MEASURE 0x04
>>> +#define MEASURE_MDAC_MASK GENMASK(5, 0)
>>> +/* Datasheet says MDAC must be set to 0x34 / 2226mV for vRd-3.0 detection */
>>> +#define MEASURE_MDAC_SNK_VRD30 0x34
>>> +/* MDAC must be set to 0x25 / 1600mV for disconnect det. with 80uA host-cur */
>>> +#define MEASURE_MDAC_SRC_80UA_HOST_CUR 0x25
>>> +
>>> +#define REG_CONTROL0 0x06
>>> +#define CONTROL0_HOST_CUR_MASK GENMASK(3, 2)
>>> +#define CONTROL0_HOST_CUR_DISABLED (0 << 2)
>>> +#define CONTROL0_HOST_CUR_80UA (1 << 2)
>>> +#define CONTROL0_HOST_CUR_180UA (2 << 2)
>>> +#define CONTROL0_HOST_CUR_330UA (3 << 2)
>>> +#define CONTROL0_INT_MASK BIT(5)
>>> +
>>> +#define REG_CONTROL2 0x08
>>> +#define CONTROL2_TOGGLE BIT(0)
>>> +#define CONTROL2_MODE_MASK GENMASK(2, 1)
>>> +#define CONTROL2_MODE_DRP (1 << 1)
>>> +#define CONTROL2_MODE_SNK (2 << 1)
>>> +#define CONTROL2_MODE_SRC (3 << 1)
>>> +#define CONTROL2_SAVE_PWR_MASK GENMASK(7, 6)
>>> +#define CONTROL2_SAVE_PWR_DISABLED (0 << 6)
>>> +#define CONTROL2_SAVE_PWR_40MS (1 << 6)
>>> +#define CONTROL2_SAVE_PWR_80MS (2 << 6)
>>> +#define CONTROL2_SAVE_PWR_160MS (3 << 6)
>>> +
>>> +#define REG_MASK1 0x0a
>>> +/* REG_MASK1 value for low-power / disabled state from datasheet */
>>> +#define MASK1_DISABLED 0xfe
>>> +#define MASK1_COMP_CHNG BIT(5)
>>> +#define MASK1_VBUSOK BIT(7)
>>> +
>>> +#define REG_POWER 0x0b
>>> +/* REG_POWER values for disabled and normal state from datasheet */
>>> +#define POWER_DISABLED BIT(0)
>>> +#define POWER_NORMAL GENMASK(2, 0)
>>> +
>>> +#define REG_RESET 0x0c
>>> +#define RESET_SW_RESET BIT(0)
>>> +
>>> +#define REG_OCP 0x0d
>>> +
>>> +#define REG_MASKA 0x0e
>>> +/* REG_MASKA value for low-power / disabled state from datasheet */
>>> +#define MASKA_DISABLED 0xbf
>>> +
>>> +#define REG_MASKB 0x0f
>>> +/* REG_MASKB value for low-power / disabled state from datasheet */
>>> +#define MASKB_DISABLED 0x01
>>> +
>>> +#define REG_STATUS1A 0x3d
>>> +#define STATUS1A_TOGSS_MASK GENMASK(5, 3)
>>> +#define STATUS1A_TOGSS_SRC_CC1 (1 << 3)
>>> +#define STATUS1A_TOGSS_SRC_CC2 (2 << 3)
>>> +#define STATUS1A_TOGSS_SNK_CC1 (5 << 3)
>>> +#define STATUS1A_TOGSS_SNK_CC2 (6 << 3)
>>> +
>>> +#define REG_INTERRUPTA 0x3e
>>> +#define INTERRUPTA_TOGDONE BIT(6)
>>> +
>>> +#define REG_STATUS0 0x40
>>> +#define STATUS0_BC_LEVEL_MASK GENMASK(1, 0)
>>> +#define STATUS0_BC_LEVEL_VRA 0
>>> +#define STATUS0_BC_LEVEL_VRDUSB 1
>>> +#define STATUS0_BC_LEVEL_VRD15 2
>>> +#define STATUS0_BC_LEVEL_VRD30 3
>>> +#define STATUS0_COMP BIT(5)
>>> +#define STATUS0_VBUSOK BIT(7)
>>> +
>>> +#define REG_INTERRUPT 0x42
>>> +#define INTERRUPT_BC_LVL BIT(0)
>>> +#define INTERRUPT_COMP_CHNG BIT(5)
>>> +#define INTERRUPT_VBUSOK BIT(7)
>>> +
>>> +/* Timeouts from the FUSB302 datasheet */
>>> +#define TTOGCYCLE msecs_to_jiffies(40 + 60 + 40)
>>> +
>>> +/* Timeouts from the USB-C specification */
>>> +#define TPDDEBOUNCE msecs_to_jiffies(20)
>>> +#define TDRPTRY_MS 75
>>> +#define TDRPTRY msecs_to_jiffies(TDRPTRY_MS)
>>> +#define TDRPTRYWAIT msecs_to_jiffies(400)
>>> +#define TVBUSON msecs_to_jiffies(275)
>>> +
>>> +enum typec_port_type {
>>> + TYPEC_PORT_DFP,
>>> + TYPEC_PORT_UFP,
>>> + TYPEC_PORT_DRP,
>>> +};
>>> +
>>> +enum typec_role {
>>> + TYPEC_SINK,
>>> + TYPEC_SOURCE,
>>> +};
>>> +
>>> +enum fusb302_state {
>>> + DISABLED_SNK, /* UFP */
>>> + DISABLED_SRC, /* DFP */
>>> + DISABLED_DRP,
>>> + UNATTACHED_SNK,
>>> + ATTACHED_SNK,
>>> + UNATTACHED_SRC,
>>> + ATTACHED_SRC,
>>> +};
>>> +/* For debugging */
>>> +static __maybe_unused const char *fusb302_state_str[] = {
>>> + "DISABLED_SNK",
>>> + "DISABLED_SRC",
>>> + "DISABLED_DRP",
>>> + "UNATTACHED_SNK",
>>> + "ATTACHED_SNK",
>>> + "UNATTACHED_SRC",
>>> + "ATTACHED_SRC",
>>> +};
>>> +
>>> +enum fusb302_event {
>>> + TOGGLE_DONE,
>>> + BC_LVL_CHANGE,
>>> + VBUS_VALID,
>>> + VBUS_INVALID,
>>> + COMP_LOW,
>>> + COMP_HIGH,
>>> + TIMEOUT,
>>> + FALLBACK_TIMEOUT,
>>> +};
>>> +/* For debugging */
>>> +static __maybe_unused const char *fusb302_event_str[] = {
>>> + "TOGGLE_DONE",
>>> + "BC_LVL_CHANGE",
>>> + "VBUS_VALID",
>>> + "VBUS_INVALID",
>>> + "COMP_LOW",
>>> + "COMP_HIGH",
>>> + "TIMEOUT",
>>> + "FALLBACK_TIMEOUT",
>>> +};
>>> +
>>> +static const unsigned int fusb302_extcon_cables[] = {
>>> + EXTCON_USB,
>>> + EXTCON_USB_HOST,
>>> + EXTCON_CHG_USB_SDP,
>>> + EXTCON_CHG_USB_CDP,
>>> + EXTCON_CHG_USB_FAST,
>>> + EXTCON_NONE,
>>> +};
>>> +
>>> +struct fusb302_data {
>>> + struct device *dev;
>>> + struct regmap *regmap;
>>> + struct extcon_dev *edev;
>>> + struct mutex lock;
>>> + struct delayed_work timeout;
>>> + struct delayed_work fallback_timeout;
>>> + struct delayed_work bc_work;
>>> + enum fusb302_state state;
>>> + enum typec_port_type port_type;
>>> + enum typec_role preferred_role;
>>> + int status0;
>>> + int status1a;
>>> + unsigned int snk_cable_id;
>>> +};
>>> +
>>> +static void fusb302_write_reg(struct fusb302_data *data, int reg, int val)
>>> +{
>>> + int ret;
>>> +
>>> + ret = regmap_write(data->regmap, reg, val);
>>> + if (ret)
>>> + dev_err(data->dev, "Error writing reg %02x: %d\n", reg, ret);
>>> +}
>>> +
>>> +static int fusb302_read_reg(struct fusb302_data *data, int reg)
>>> +{
>>> + int ret, val;
>>> +
>>> + ret = regmap_read(data->regmap, reg, &val);
>>> + if (ret) {
>>> + dev_err(data->dev, "Error reading reg %02x: %d\n", reg, ret);
>>> + return 0;
>>> + }
>>> +
>>> + return val;
>>> +}
>>> +
>>> +static void fusb302_update_bits(struct fusb302_data *data, int reg,
>>> + int mask, int val)
>>> +{
>>> + int ret;
>>> +
>>> + ret = regmap_update_bits(data->regmap, reg, mask, val);
>>> + if (ret)
>>> + dev_err(data->dev, "Error modifying reg %02x: %d\n", reg, ret);
>>> +}
>>> +
>>> +/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */
>>> +static void fusb302_set_extcon_state(struct fusb302_data *data,
>>> + unsigned int cable, bool state)
>>> +{
>>> + extcon_set_state_sync(data->edev, cable, state);
>>> + if (cable == EXTCON_CHG_USB_SDP)
>>> + extcon_set_state_sync(data->edev, EXTCON_USB, state);
>>> +}
>>> +
>>> +/* Helper for the disabled states */
>>> +static void fusb302_disable(struct fusb302_data *data)
>>> +{
>>> + fusb302_write_reg(data, REG_POWER, POWER_DISABLED);
>>> + fusb302_write_reg(data, REG_MASK1, MASK1_DISABLED);
>>> + fusb302_write_reg(data, REG_MASKA, MASKA_DISABLED);
>>> + fusb302_write_reg(data, REG_MASKB, MASKB_DISABLED);
>>> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
>>> +}
>>> +
>>> +/*
>>> + * fusb302_set_state() and fusb302_handle_event() implement the 3 state
>>> + * machines from the datasheet folded into 1 state-machine for code re-use.
>>> + */
>>> +static void fusb302_set_state(struct fusb302_data *data,
>>> + enum fusb302_state state)
>>> +{
>>> + int status, switches0 = 0;
>>> + enum fusb302_state old_state = data->state;
>>> + union extcon_property_value prop;
>>> +
>>> + /* Kill pending timeouts from previous state */
>>> + cancel_delayed_work(&data->timeout);
>>> + cancel_delayed_work(&data->fallback_timeout);
>>> + cancel_delayed_work(&data->bc_work);
>>> +
>>> + dev_dbg(data->dev, "New state %s\n", fusb302_state_str[state]);
>>> +
>>> + switch (old_state) {
>>> + case ATTACHED_SNK:
>>> + fusb302_set_extcon_state(data, data->snk_cable_id, false);
>>> + break;
>>> + case ATTACHED_SRC:
>>> + fusb302_set_extcon_state(data, EXTCON_USB_HOST, false);
>>> + break;
>>> + default:
>>> + break; /* Do nothing */
>>> + }
>>> +
>>> + switch (state) {
>>> + case DISABLED_SNK:
>>> + fusb302_disable(data);
>>> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
>>> + CONTROL2_MODE_SNK);
>>> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
>>> + CONTROL2_TOGGLE);
>>> + break;
>>> +
>>> + case DISABLED_SRC:
>>> + fusb302_disable(data);
>>> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
>>> + CONTROL2_MODE_SRC);
>>> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
>>> + CONTROL2_TOGGLE);
>>> + break;
>>> +
>>> + case DISABLED_DRP:
>>> + fusb302_disable(data);
>>> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_MODE_MASK,
>>> + CONTROL2_MODE_DRP);
>>> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE,
>>> + CONTROL2_TOGGLE);
>>> + break;
>>> +
>>> + case UNATTACHED_SNK:
>>> + fusb302_write_reg(data, REG_POWER, POWER_NORMAL);
>>> +
>>> + /* Enable pull-down on CC1 / CC2 based on orientation */
>>> + switch (data->status1a & STATUS1A_TOGSS_MASK) {
>>> + case STATUS1A_TOGSS_SNK_CC1:
>>> + switches0 = SWITCHES0_PDWN1 | SWITCHES0_MEAS_CC1;
>>> + prop.intval = USB_TYPEC_POLARITY_NORMAL;
>>> + break;
>>> + case STATUS1A_TOGSS_SNK_CC2:
>>> + switches0 = SWITCHES0_PDWN2 | SWITCHES0_MEAS_CC2;
>>> + prop.intval = USB_TYPEC_POLARITY_FLIP;
>>> + break;
>>> + }
>>> + fusb302_write_reg(data, REG_SWITCHES0, switches0);
>>> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
>>> + extcon_set_property(data->edev, EXTCON_USB,
>>> + EXTCON_PROP_USB_TYPEC_POLARITY, prop);
>>> +
>>> + /* Enable VBUSOK and COMP irq at 2.24V for BC detection */
>>> + fusb302_update_bits(data, REG_MASK1,
>>> + MASK1_VBUSOK | MASK1_COMP_CHNG, 0);
>>> + fusb302_update_bits(data, REG_MEASURE, MEASURE_MDAC_MASK,
>>> + MEASURE_MDAC_SNK_VRD30);
>>> +
>>> + status = fusb302_read_reg(data, REG_STATUS0);
>>> + if (status & STATUS0_VBUSOK) {
>>> + /* Go straight to ATTACHED_SNK */
>>> + fusb302_set_state(data, ATTACHED_SNK);
>>> + return;
>>> + }
>>> +
>>> + mod_delayed_work(system_wq, &data->timeout, TVBUSON);
>>> + break;
>>> +
>>> + case ATTACHED_SNK:
>>> + mod_delayed_work(system_wq, &data->bc_work, TPDDEBOUNCE);
>>> + break;
>>> +
>>> + case UNATTACHED_SRC:
>>> + fusb302_write_reg(data, REG_POWER, POWER_NORMAL);
>>> +
>>> + /* Enable pull-up / Vconn on CC1 / CC2 based on orientation */
>>> + switch (data->status1a & STATUS1A_TOGSS_MASK) {
>>> + case STATUS1A_TOGSS_SRC_CC1:
>>> + switches0 = SWITCHES0_PU_EN1 | SWITCHES0_VCONN_CC2 |
>>> + SWITCHES0_MEAS_CC1;
>>> + prop.intval = USB_TYPEC_POLARITY_NORMAL;
>>> + break;
>>> + case STATUS1A_TOGSS_SRC_CC2:
>>> + switches0 = SWITCHES0_PU_EN2 | SWITCHES0_VCONN_CC1 |
>>> + SWITCHES0_MEAS_CC2;
>>> + prop.intval = USB_TYPEC_POLARITY_FLIP;
>>> + break;
>>> + }
>>> + fusb302_write_reg(data, REG_SWITCHES0, switches0);
>>> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_TOGGLE, 0);
>>> + extcon_set_property(data->edev, EXTCON_USB,
>>> + EXTCON_PROP_USB_TYPEC_POLARITY, prop);
>>> +
>>> + /* Enable COMP irq at 1.6V for detach detection */
>>> + fusb302_update_bits(data, REG_MASK1, MASK1_COMP_CHNG, 0);
>>> + fusb302_update_bits(data, REG_MEASURE, MEASURE_MDAC_MASK,
>>> + MEASURE_MDAC_SRC_80UA_HOST_CUR);
>>> +
>>> + status = fusb302_read_reg(data, REG_STATUS0);
>>> + if (!(status & STATUS0_COMP)) {
>>> + /* Go straight to ATTACHED_SRC */
>>> + fusb302_set_state(data, ATTACHED_SRC);
>>> + return;
>>> + }
>>> +
>>> + mod_delayed_work(system_wq, &data->timeout, TPDDEBOUNCE);
>>> + break;
>>> +
>>> + case ATTACHED_SRC:
>>> + fusb302_set_extcon_state(data, EXTCON_USB_HOST, true);
>>> + break;
>>> + }
>>> +
>>> + data->state = state;
>>> +}
>>> +
>>> +static void fusb302_set_state_disabled(struct fusb302_data *data)
>>> +{
>>> +
>>> + switch (data->port_type) {
>>> + case TYPEC_PORT_UFP:
>>> + fusb302_set_state(data, DISABLED_SNK);
>>> + break;
>>> + case TYPEC_PORT_DFP:
>>> + fusb302_set_state(data, DISABLED_SRC);
>>> + break;
>>> + case TYPEC_PORT_DRP:
>>> + fusb302_set_state(data, DISABLED_DRP);
>>> + break;
>>> + }
>>> +}
>>> +
>>> +static void fusb302_handle_disabled_snk_event(struct fusb302_data *data,
>>> + int event)
>>> +{
>>> + switch (event) {
>>> + case TOGGLE_DONE:
>>> + switch (data->status1a & STATUS1A_TOGSS_MASK) {
>>> + case STATUS1A_TOGSS_SNK_CC1:
>>> + case STATUS1A_TOGSS_SNK_CC2:
>>> + fusb302_set_state(data, UNATTACHED_SNK);
>>> + break;
>>> + }
>>> + break;
>>> +
>>> + case TIMEOUT:
>>> + /* Cannot become snk fallback to src */
>>> + fusb302_set_state(data, DISABLED_SRC);
>>> + mod_delayed_work(system_wq, &data->fallback_timeout,
>>> + TDRPTRYWAIT);
>>> + break;
>>> +
>>> + case FALLBACK_TIMEOUT:
>>> + /* Both states failed return to disabled drp state */
>>> + fusb302_set_state(data, DISABLED_DRP);
>>> + break;
>>> + }
>>> +}
>>> +
>>> +static void fusb302_handle_disabled_src_event(struct fusb302_data *data,
>>> + int event)
>>> +{
>>> + switch (event) {
>>> + case TOGGLE_DONE:
>>> + switch (data->status1a & STATUS1A_TOGSS_MASK) {
>>> + case STATUS1A_TOGSS_SRC_CC1:
>>> + case STATUS1A_TOGSS_SRC_CC2:
>>> + fusb302_set_state(data, UNATTACHED_SRC);
>>> + break;
>>> + }
>>> + break;
>>> +
>>> + case TIMEOUT:
>>> + /* Cannot become src fallback to snk */
>>> + fusb302_set_state(data, DISABLED_SNK);
>>> + mod_delayed_work(system_wq, &data->fallback_timeout,
>>> + TDRPTRYWAIT);
>>> + break;
>>> +
>>> + case FALLBACK_TIMEOUT:
>>> + /* Both states failed return to disabled drp state */
>>> + fusb302_set_state(data, DISABLED_DRP);
>>> + break;
>>> + }
>>> +}
>>> +
>>> +static void fusb302_handle_disabled_drp_event(struct fusb302_data *data,
>>> + int event)
>>> +{
>>> + switch (event) {
>>> + case TOGGLE_DONE:
>>> + switch (data->status1a & STATUS1A_TOGSS_MASK) {
>>> + case STATUS1A_TOGSS_SNK_CC1:
>>> + case STATUS1A_TOGSS_SNK_CC2:
>>> + if (data->preferred_role == TYPEC_SINK) {
>>> + /* Jump directly to UNATTACHED_SNK */
>>> + fusb302_set_state(data, UNATTACHED_SNK);
>>> + } else {
>>> + /* Try to become src */
>>> + fusb302_set_state(data, DISABLED_SNK);
>>> + mod_delayed_work(system_wq, &data->timeout,
>>> + TDRPTRY);
>>> + }
>>> + break;
>>> + case STATUS1A_TOGSS_SRC_CC1:
>>> + case STATUS1A_TOGSS_SRC_CC2:
>>> + if (data->preferred_role == TYPEC_SOURCE) {
>>> + /* Jump directly to UNATTACHED_SRC */
>>> + fusb302_set_state(data, UNATTACHED_SRC);
>>> + } else {
>>> + /*
>>> + * The USB-C spec says we must enable pull-downs
>>> + * and then wait tDRPTry before checking to
>>> + * avoid endless role-bouncing if both ends
>>> + * prefer the snk role.
>>> + */
>>> + fusb302_write_reg(data, REG_SWITCHES0,
>>> + SWITCHES0_PDWN);
>>> + fusb302_update_bits(data, REG_CONTROL2,
>>> + CONTROL2_TOGGLE, 0);
>>> + msleep(TDRPTRY_MS);
>>> + /*
>>> + * Use the toggle engine to do the src
>>> + * detection to keep things the same as for
>>> + * directly entering the src role.
>>> + */
>>> + fusb302_set_state(data, DISABLED_SNK);
>>> + mod_delayed_work(system_wq, &data->timeout,
>>> + TTOGCYCLE);
>>> + }
>>> + break;
>>> + }
>>> + break;
>>> + }
>>> +}
>>> +
>>> +static void fusb302_handle_unattached_snk_event(struct fusb302_data *data,
>>> + int event)
>>> +{
>>> + switch (event) {
>>> + case VBUS_VALID: /* Cable attached */
>>> + fusb302_set_state(data, ATTACHED_SNK);
>>> + break;
>>> + case TIMEOUT:
>>> + fusb302_set_state_disabled(data);
>>> + break;
>>> + }
>>> +}
>>> +
>>> +static void fusb302_handle_attached_snk_event(struct fusb302_data *data,
>>> + int event)
>>> +{
>>> + switch (event) {
>>> + case BC_LVL_CHANGE:
>>> + case COMP_LOW:
>>> + case COMP_HIGH:
>>> + mod_delayed_work(system_wq, &data->bc_work, TPDDEBOUNCE);
>>> + break;
>>> + case VBUS_INVALID: /* Cable detached */
>>> + fusb302_set_state_disabled(data);
>>> + break;
>>> + }
>>> +}
>>> +
>>> +static void fusb302_handle_unattached_src_event(struct fusb302_data *data,
>>> + int event)
>>> +{
>>> + switch (event) {
>>> + case COMP_LOW: /* Cable attached */
>>> + fusb302_set_state(data, ATTACHED_SRC);
>>> + break;
>>> + case TIMEOUT:
>>> + fusb302_set_state_disabled(data);
>>> + break;
>>> + }
>>> +}
>>> +
>>> +static void fusb302_handle_attached_src_event(struct fusb302_data *data,
>>> + int event)
>>> +{
>>> + switch (event) {
>>> + case COMP_HIGH: /* Cable detached */
>>> + fusb302_set_state_disabled(data);
>>> + break;
>>> + }
>>> +}
>>> +
>>> +static void fusb302_handle_event(struct fusb302_data *data, int event)
>>> +{
>>> +
>>> + mutex_lock(&data->lock);
>>> +
>>> + dev_dbg(data->dev, "Handling state %s event %s status %02x %02x\n",
>>> + fusb302_state_str[data->state], fusb302_event_str[event],
>>> + fusb302_read_reg(data, REG_STATUS0),
>>> + fusb302_read_reg(data, REG_STATUS1A));
>>> +
>>> + switch (data->state) {
>>> + case DISABLED_SNK:
>>> + fusb302_handle_disabled_snk_event(data, event);
>>> + break;
>>> + case DISABLED_SRC:
>>> + fusb302_handle_disabled_src_event(data, event);
>>> + break;
>>> + case DISABLED_DRP:
>>> + fusb302_handle_disabled_drp_event(data, event);
>>> + break;
>>> + case UNATTACHED_SNK:
>>> + fusb302_handle_unattached_snk_event(data, event);
>>> + break;
>>> + case ATTACHED_SNK:
>>> + fusb302_handle_attached_snk_event(data, event);
>>> + break;
>>> + case UNATTACHED_SRC:
>>> + fusb302_handle_unattached_src_event(data, event);
>>> + break;
>>> + case ATTACHED_SRC:
>>> + fusb302_handle_attached_src_event(data, event);
>>> + break;
>>> + }
>>> + mutex_unlock(&data->lock);
>>> +}
>>> +
>>> +static void fusb302_timeout(struct work_struct *work)
>>> +{
>>> + struct fusb302_data *data =
>>> + container_of(work, struct fusb302_data, timeout.work);
>>> +
>>> + fusb302_handle_event(data, TIMEOUT);
>>> +}
>>> +
>>> +static void fusb302_fallback_timeout(struct work_struct *work)
>>> +{
>>> + struct fusb302_data *data =
>>> + container_of(work, struct fusb302_data, fallback_timeout.work);
>>> +
>>> + fusb302_handle_event(data, FALLBACK_TIMEOUT);
>>> +}
>>> +
>>> +static void fusb302_report_bc_level(struct work_struct *work)
>>> +{
>>> + struct fusb302_data *data =
>>> + container_of(work, struct fusb302_data, bc_work.work);
>>> +
>>> + /* Clear old charger cable id */
>>> + fusb302_set_extcon_state(data, data->snk_cable_id, false);
>>> +
>>> + if (data->status0 & STATUS0_COMP) {
>>> + dev_warn(data->dev, "vRd over maximum, assuming 500mA source\n");
>>> + data->snk_cable_id = EXTCON_CHG_USB_SDP;
>>> + } else {
>>> + switch (data->status0 & STATUS0_BC_LEVEL_MASK) {
>>> + case STATUS0_BC_LEVEL_VRA:
>>> + case STATUS0_BC_LEVEL_VRDUSB:
>>> + data->snk_cable_id = EXTCON_CHG_USB_SDP;
>>> + break;
>>> + case STATUS0_BC_LEVEL_VRD15:
>>> + data->snk_cable_id = EXTCON_CHG_USB_CDP;
>>> + break;
>>> + case STATUS0_BC_LEVEL_VRD30:
>>> + /* Use CHG_USB_FAST to indicate 3A capability */
>>> + data->snk_cable_id = EXTCON_CHG_USB_FAST;
>>> + break;
>>> + }
>>> + }
>>> + fusb302_set_extcon_state(data, data->snk_cable_id, true);
>>> +}
>>> +
>>> +static irqreturn_t fusb302_irq_handler_thread(int irq, void *handler_data)
>>> +{
>>> + struct fusb302_data *data = handler_data;
>>> + int interrupt, interrupta;
>>> +
>>> + interrupt = fusb302_read_reg(data, REG_INTERRUPT);
>>> + interrupta = fusb302_read_reg(data, REG_INTERRUPTA);
>>> +
>>> + if (interrupt)
>>> + data->status0 = fusb302_read_reg(data, REG_STATUS0);
>>> +
>>> + if (interrupta)
>>> + data->status1a = fusb302_read_reg(data, REG_STATUS1A);
>>> +
>>> + dev_dbg(data->dev, "Handling interrupt %02x %02x status %02x %02x\n",
>>> + interrupt, interrupta, data->status0, data->status1a);
>>> +
>>> + if (interrupt & INTERRUPT_BC_LVL)
>>> + fusb302_handle_event(data, BC_LVL_CHANGE);
>>> +
>>> + if (interrupt & INTERRUPT_COMP_CHNG) {
>>> + if (data->status0 & STATUS0_COMP)
>>> + fusb302_handle_event(data, COMP_HIGH);
>>> + else
>>> + fusb302_handle_event(data, COMP_LOW);
>>> + }
>>> +
>>> + if (interrupt & INTERRUPT_VBUSOK) {
>>> + if (data->status0 & STATUS0_VBUSOK)
>>> + fusb302_handle_event(data, VBUS_VALID);
>>> + else
>>> + fusb302_handle_event(data, VBUS_INVALID);
>>> + }
>>> +
>>> + if (interrupta & INTERRUPTA_TOGDONE)
>>> + fusb302_handle_event(data, TOGGLE_DONE);
>>> +
>>> + return IRQ_HANDLED;
>>> +}
>>> +
>>> +static const struct regmap_config fusb302_regmap_config = {
>>> + .reg_bits = 8,
>>> + .val_bits = 8,
>>> + .val_format_endian = REGMAP_ENDIAN_NATIVE,
>>> +};
>>> +
>>> +static int fusb302_probe(struct i2c_client *client)
>>> +{
>>> + struct device *dev = &client->dev;
>>> + struct fusb302_data *data;
>>> + int ret;
>>> +
>>> + if (!client->irq) {
>>> + dev_err(dev, "Error irq not specified\n");
>>> + return -EINVAL;
>>> + }
>>> +
>>> + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
>>> + if (!data)
>>> + return -ENOMEM;
>>> +
>>> + data->dev = dev;
>>> + /* TODO make these 2 configurable using device-properties */
>>> + data->port_type = TYPEC_PORT_DRP;
>>> + data->preferred_role = TYPEC_SINK;
>>> +
>>> + data->regmap = devm_regmap_init_i2c(client, &fusb302_regmap_config);
>>> + if (IS_ERR(data->regmap)) {
>>> + ret = PTR_ERR(data->regmap);
>>> + dev_err(dev, "Error to initializing regmap: %d\n", ret);
>>> + return ret;
>>> + }
>>> +
>>> + mutex_init(&data->lock);
>>> + INIT_DELAYED_WORK(&data->timeout, fusb302_timeout);
>>> + INIT_DELAYED_WORK(&data->fallback_timeout, fusb302_fallback_timeout);
>>> + INIT_DELAYED_WORK(&data->bc_work, fusb302_report_bc_level);
>>> +
>>> + data->edev = devm_extcon_dev_allocate(dev, fusb302_extcon_cables);
>>> + if (IS_ERR(data->edev))
>>> + return PTR_ERR(data->edev);
>>> +
>>> + data->edev->name = "fusb302";
>>> +
>>> + ret = devm_extcon_dev_register(dev, data->edev);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + extcon_set_property_capability(data->edev, EXTCON_USB,
>>> + EXTCON_PROP_USB_TYPEC_POLARITY);
>>> +
>>> + fusb302_write_reg(data, REG_RESET, RESET_SW_RESET);
>>> + usleep_range(10000, 20000);
>>> +
>>> + /* Enable power-saving */
>>> + fusb302_update_bits(data, REG_CONTROL2, CONTROL2_SAVE_PWR_MASK,
>>> + CONTROL2_SAVE_PWR_40MS);
>>> +
>>> + fusb302_set_state_disabled(data);
>>> +
>>> + ret = devm_request_threaded_irq(dev, client->irq, NULL,
>>> + fusb302_irq_handler_thread,
>>> + IRQF_TRIGGER_LOW | IRQF_ONESHOT, "fusb302", data);
>>> + if (ret) {
>>> + dev_err(dev, "Error requesting irq: %d\n", ret);
>>> + return ret;
>>> + }
>>> +
>>> + fusb302_update_bits(data, REG_CONTROL0, CONTROL0_INT_MASK, 0);
>>> +
>>> + i2c_set_clientdata(client, data);
>>> + return 0;
>>> +}
>>> +
>>> +static int fusb302_remove(struct i2c_client *client)
>>> +{
>>> + struct fusb302_data *data = i2c_get_clientdata(client);
>>> +
>>> + devm_free_irq(data->dev, client->irq, data);
>>> + cancel_delayed_work_sync(&data->timeout);
>>> + cancel_delayed_work_sync(&data->fallback_timeout);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct i2c_device_id fusb302_i2c_ids[] = {
>>> + { "fusb302" },
>>> + { },
>>> +};
>>> +MODULE_DEVICE_TABLE(i2c, fusb302_i2c_ids);
>>> +
>>> +static struct i2c_driver fusb302_driver = {
>>> + .probe_new = fusb302_probe,
>>> + .remove = fusb302_remove,
>>> + .id_table = fusb302_i2c_ids,
>>> + .driver = {
>>> + .name = "fusb302",
>>> + },
>>> +};
>>> +module_i2c_driver(fusb302_driver);
>>> +
>>> +MODULE_LICENSE("GPL");
>>> +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
>>> +MODULE_DESCRIPTION("FUSB302 USB TYPE-C controller Driver");
>>> --
>>> 2.9.3
>>>
>
> Br,
>
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web