summaryrefslogtreecommitdiff
path: root/drivers/usb/typec
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/typec')
-rw-r--r--drivers/usb/typec/Kconfig23
-rw-r--r--drivers/usb/typec/Makefile2
-rw-r--r--drivers/usb/typec/bus.c2
-rw-r--r--drivers/usb/typec/class.c43
-rw-r--r--drivers/usb/typec/mux.c271
-rw-r--r--drivers/usb/typec/mux.h12
-rw-r--r--drivers/usb/typec/mux/Kconfig10
-rw-r--r--drivers/usb/typec/mux/Makefile1
-rw-r--r--drivers/usb/typec/mux/fsa4480.c218
-rw-r--r--drivers/usb/typec/mux/intel_pmc_mux.c29
-rw-r--r--drivers/usb/typec/mux/pi3usb30532.c8
-rw-r--r--drivers/usb/typec/port-mapper.c2
-rw-r--r--drivers/usb/typec/rt1719.c961
-rw-r--r--drivers/usb/typec/tcpm/Kconfig1
-rw-r--r--drivers/usb/typec/tcpm/fusb302.c4
-rw-r--r--drivers/usb/typec/tcpm/tcpci.c2
-rw-r--r--drivers/usb/typec/tcpm/tcpci_mt6360.c26
-rw-r--r--drivers/usb/typec/tcpm/tcpm.c24
-rw-r--r--drivers/usb/typec/tipd/core.c37
-rw-r--r--drivers/usb/typec/tipd/tps6598x.h1
-rw-r--r--drivers/usb/typec/ucsi/ucsi.c109
-rw-r--r--drivers/usb/typec/ucsi/ucsi.h6
-rw-r--r--drivers/usb/typec/ucsi/ucsi_acpi.c23
-rw-r--r--drivers/usb/typec/wusb3801.c437
24 files changed, 2056 insertions, 196 deletions
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index ab480f38523a..ba24847fb245 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -52,6 +52,19 @@ source "drivers/usb/typec/ucsi/Kconfig"
source "drivers/usb/typec/tipd/Kconfig"
+config TYPEC_RT1719
+ tristate "Richtek RT1719 Sink Only Type-C controller driver"
+ depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH
+ depends on I2C
+ depends on POWER_SUPPLY
+ select REGMAP_I2C
+ help
+ Say Y or M here if your system has Richtek RT1719 sink only
+ Type-C port controller driver.
+
+ If you choose to build this driver as a dynamically linked module, the
+ module will be called rt1719.ko
+
config TYPEC_HD3SS3220
tristate "TI HD3SS3220 Type-C DRP Port controller driver"
depends on I2C
@@ -88,6 +101,16 @@ config TYPEC_QCOM_PMIC
It will also enable the VBUS output to connected devices when a
DFP connection is made.
+config TYPEC_WUSB3801
+ tristate "Willsemi WUSB3801 Type-C port controller driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y or M here if your system has a WUSB3801 Type-C port controller.
+
+ If you choose to build this driver as a dynamically linked module, the
+ module will be called wusb3801.ko.
+
source "drivers/usb/typec/mux/Kconfig"
source "drivers/usb/typec/altmodes/Kconfig"
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index 57870a2bd787..43626acc0aaf 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -9,4 +9,6 @@ obj-$(CONFIG_TYPEC_TPS6598X) += tipd/
obj-$(CONFIG_TYPEC_HD3SS3220) += hd3ss3220.o
obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom-pmic-typec.o
obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o
+obj-$(CONFIG_TYPEC_RT1719) += rt1719.o
+obj-$(CONFIG_TYPEC_WUSB3801) += wusb3801.o
obj-$(CONFIG_TYPEC) += mux/
diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c
index 78e0e78954f2..26ea2fdec17d 100644
--- a/drivers/usb/typec/bus.c
+++ b/drivers/usb/typec/bus.c
@@ -24,7 +24,7 @@ typec_altmode_set_mux(struct altmode *alt, unsigned long conf, void *data)
state.mode = conf;
state.data = data;
- return alt->mux->set(alt->mux, &state);
+ return typec_mux_set(alt->mux, &state);
}
static int typec_altmode_set_state(struct typec_altmode *adev,
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 45a6f0c807cb..ee0e520707dd 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -1894,6 +1894,49 @@ void *typec_get_drvdata(struct typec_port *port)
}
EXPORT_SYMBOL_GPL(typec_get_drvdata);
+int typec_get_fw_cap(struct typec_capability *cap,
+ struct fwnode_handle *fwnode)
+{
+ const char *cap_str;
+ int ret;
+
+ cap->fwnode = fwnode;
+
+ ret = fwnode_property_read_string(fwnode, "power-role", &cap_str);
+ if (ret < 0)
+ return ret;
+
+ ret = typec_find_port_power_role(cap_str);
+ if (ret < 0)
+ return ret;
+ cap->type = ret;
+
+ /* USB data support is optional */
+ ret = fwnode_property_read_string(fwnode, "data-role", &cap_str);
+ if (ret == 0) {
+ ret = typec_find_port_data_role(cap_str);
+ if (ret < 0)
+ return ret;
+ cap->data = ret;
+ }
+
+ /* Get the preferred power role for a DRP */
+ if (cap->type == TYPEC_PORT_DRP) {
+ cap->prefer_role = TYPEC_NO_PREFERRED_ROLE;
+
+ ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str);
+ if (ret == 0) {
+ ret = typec_find_power_role(cap_str);
+ if (ret < 0)
+ return ret;
+ cap->prefer_role = ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(typec_get_fw_cap);
+
/**
* typec_port_register_altmode - Register USB Type-C Port Alternate Mode
* @port: USB Type-C Port that supports the alternate mode
diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c
index c8340de0ed49..fd55c2c516a5 100644
--- a/drivers/usb/typec/mux.c
+++ b/drivers/usb/typec/mux.c
@@ -17,9 +17,16 @@
#include "class.h"
#include "mux.h"
+#define TYPEC_MUX_MAX_DEVS 3
+
+struct typec_switch {
+ struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS];
+ unsigned int num_sw_devs;
+};
+
static int switch_fwnode_match(struct device *dev, const void *fwnode)
{
- if (!is_typec_switch(dev))
+ if (!is_typec_switch_dev(dev))
return 0;
return dev_fwnode(dev) == fwnode;
@@ -49,7 +56,7 @@ static void *typec_switch_match(struct fwnode_handle *fwnode, const char *id,
dev = class_find_device(&typec_mux_class, NULL, fwnode,
switch_fwnode_match);
- return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER);
+ return dev ? to_typec_switch_dev(dev) : ERR_PTR(-EPROBE_DEFER);
}
/**
@@ -63,14 +70,50 @@ static void *typec_switch_match(struct fwnode_handle *fwnode, const char *id,
*/
struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode)
{
+ struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS];
struct typec_switch *sw;
+ int count;
+ int err;
+ int i;
+
+ sw = kzalloc(sizeof(*sw), GFP_KERNEL);
+ if (!sw)
+ return ERR_PTR(-ENOMEM);
+
+ count = fwnode_connection_find_matches(fwnode, "orientation-switch", NULL,
+ typec_switch_match,
+ (void **)sw_devs,
+ ARRAY_SIZE(sw_devs));
+ if (count <= 0) {
+ kfree(sw);
+ return NULL;
+ }
- sw = fwnode_connection_find_match(fwnode, "orientation-switch", NULL,
- typec_switch_match);
- if (!IS_ERR_OR_NULL(sw))
- WARN_ON(!try_module_get(sw->dev.parent->driver->owner));
+ for (i = 0; i < count; i++) {
+ if (IS_ERR(sw_devs[i])) {
+ err = PTR_ERR(sw_devs[i]);
+ goto put_sw_devs;
+ }
+ }
+
+ for (i = 0; i < count; i++) {
+ WARN_ON(!try_module_get(sw_devs[i]->dev.parent->driver->owner));
+ sw->sw_devs[i] = sw_devs[i];
+ }
+
+ sw->num_sw_devs = count;
return sw;
+
+put_sw_devs:
+ for (i = 0; i < count; i++) {
+ if (!IS_ERR(sw_devs[i]))
+ put_device(&sw_devs[i]->dev);
+ }
+
+ kfree(sw);
+
+ return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(fwnode_typec_switch_get);
@@ -82,16 +125,25 @@ EXPORT_SYMBOL_GPL(fwnode_typec_switch_get);
*/
void typec_switch_put(struct typec_switch *sw)
{
- if (!IS_ERR_OR_NULL(sw)) {
- module_put(sw->dev.parent->driver->owner);
- put_device(&sw->dev);
+ struct typec_switch_dev *sw_dev;
+ unsigned int i;
+
+ if (IS_ERR_OR_NULL(sw))
+ return;
+
+ for (i = 0; i < sw->num_sw_devs; i++) {
+ sw_dev = sw->sw_devs[i];
+
+ module_put(sw_dev->dev.parent->driver->owner);
+ put_device(&sw_dev->dev);
}
+ kfree(sw);
}
EXPORT_SYMBOL_GPL(typec_switch_put);
static void typec_switch_release(struct device *dev)
{
- kfree(to_typec_switch(dev));
+ kfree(to_typec_switch_dev(dev));
}
const struct device_type typec_switch_dev_type = {
@@ -109,82 +161,102 @@ const struct device_type typec_switch_dev_type = {
* connector to the USB controllers. USB Type-C plugs can be inserted
* right-side-up or upside-down.
*/
-struct typec_switch *
+struct typec_switch_dev *
typec_switch_register(struct device *parent,
const struct typec_switch_desc *desc)
{
- struct typec_switch *sw;
+ struct typec_switch_dev *sw_dev;
int ret;
if (!desc || !desc->set)
return ERR_PTR(-EINVAL);
- sw = kzalloc(sizeof(*sw), GFP_KERNEL);
- if (!sw)
+ sw_dev = kzalloc(sizeof(*sw_dev), GFP_KERNEL);
+ if (!sw_dev)
return ERR_PTR(-ENOMEM);
- sw->set = desc->set;
+ sw_dev->set = desc->set;
- device_initialize(&sw->dev);
- sw->dev.parent = parent;
- sw->dev.fwnode = desc->fwnode;
- sw->dev.class = &typec_mux_class;
- sw->dev.type = &typec_switch_dev_type;
- sw->dev.driver_data = desc->drvdata;
- dev_set_name(&sw->dev, "%s-switch",
- desc->name ? desc->name : dev_name(parent));
+ device_initialize(&sw_dev->dev);
+ sw_dev->dev.parent = parent;
+ sw_dev->dev.fwnode = desc->fwnode;
+ sw_dev->dev.class = &typec_mux_class;
+ sw_dev->dev.type = &typec_switch_dev_type;
+ sw_dev->dev.driver_data = desc->drvdata;
+ ret = dev_set_name(&sw_dev->dev, "%s-switch", desc->name ? desc->name : dev_name(parent));
+ if (ret) {
+ put_device(&sw_dev->dev);
+ return ERR_PTR(ret);
+ }
- ret = device_add(&sw->dev);
+ ret = device_add(&sw_dev->dev);
if (ret) {
dev_err(parent, "failed to register switch (%d)\n", ret);
- put_device(&sw->dev);
+ put_device(&sw_dev->dev);
return ERR_PTR(ret);
}
- return sw;
+ return sw_dev;
}
EXPORT_SYMBOL_GPL(typec_switch_register);
int typec_switch_set(struct typec_switch *sw,
enum typec_orientation orientation)
{
+ struct typec_switch_dev *sw_dev;
+ unsigned int i;
+ int ret;
+
if (IS_ERR_OR_NULL(sw))
return 0;
- return sw->set(sw, orientation);
+ for (i = 0; i < sw->num_sw_devs; i++) {
+ sw_dev = sw->sw_devs[i];
+
+ ret = sw_dev->set(sw_dev, orientation);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
EXPORT_SYMBOL_GPL(typec_switch_set);
/**
* typec_switch_unregister - Unregister USB Type-C orientation switch
- * @sw: USB Type-C orientation switch
+ * @sw_dev: USB Type-C orientation switch
*
* Unregister switch that was registered with typec_switch_register().
*/
-void typec_switch_unregister(struct typec_switch *sw)
+void typec_switch_unregister(struct typec_switch_dev *sw_dev)
{
- if (!IS_ERR_OR_NULL(sw))
- device_unregister(&sw->dev);
+ if (!IS_ERR_OR_NULL(sw_dev))
+ device_unregister(&sw_dev->dev);
}
EXPORT_SYMBOL_GPL(typec_switch_unregister);
-void typec_switch_set_drvdata(struct typec_switch *sw, void *data)
+void typec_switch_set_drvdata(struct typec_switch_dev *sw_dev, void *data)
{
- dev_set_drvdata(&sw->dev, data);
+ dev_set_drvdata(&sw_dev->dev, data);
}
EXPORT_SYMBOL_GPL(typec_switch_set_drvdata);
-void *typec_switch_get_drvdata(struct typec_switch *sw)
+void *typec_switch_get_drvdata(struct typec_switch_dev *sw_dev)
{
- return dev_get_drvdata(&sw->dev);
+ return dev_get_drvdata(&sw_dev->dev);
}
EXPORT_SYMBOL_GPL(typec_switch_get_drvdata);
/* ------------------------------------------------------------------------- */
+struct typec_mux {
+ struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS];
+ unsigned int num_mux_devs;
+};
+
static int mux_fwnode_match(struct device *dev, const void *fwnode)
{
- if (!is_typec_mux(dev))
+ if (!is_typec_mux_dev(dev))
return 0;
return dev_fwnode(dev) == fwnode;
@@ -246,7 +318,7 @@ find_mux:
dev = class_find_device(&typec_mux_class, NULL, fwnode,
mux_fwnode_match);
- return dev ? to_typec_mux(dev) : ERR_PTR(-EPROBE_DEFER);
+ return dev ? to_typec_mux_dev(dev) : ERR_PTR(-EPROBE_DEFER);
}
/**
@@ -262,14 +334,50 @@ find_mux:
struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode,
const struct typec_altmode_desc *desc)
{
+ struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS];
struct typec_mux *mux;
+ int count;
+ int err;
+ int i;
+
+ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return ERR_PTR(-ENOMEM);
+
+ count = fwnode_connection_find_matches(fwnode, "mode-switch",
+ (void *)desc, typec_mux_match,
+ (void **)mux_devs,
+ ARRAY_SIZE(mux_devs));
+ if (count <= 0) {
+ kfree(mux);
+ return NULL;
+ }
- mux = fwnode_connection_find_match(fwnode, "mode-switch", (void *)desc,
- typec_mux_match);
- if (!IS_ERR_OR_NULL(mux))
- WARN_ON(!try_module_get(mux->dev.parent->driver->owner));
+ for (i = 0; i < count; i++) {
+ if (IS_ERR(mux_devs[i])) {
+ err = PTR_ERR(mux_devs[i]);
+ goto put_mux_devs;
+ }
+ }
+
+ for (i = 0; i < count; i++) {
+ WARN_ON(!try_module_get(mux_devs[i]->dev.parent->driver->owner));
+ mux->mux_devs[i] = mux_devs[i];
+ }
+
+ mux->num_mux_devs = count;
return mux;
+
+put_mux_devs:
+ for (i = 0; i < count; i++) {
+ if (!IS_ERR(mux_devs[i]))
+ put_device(&mux_devs[i]->dev);
+ }
+
+ kfree(mux);
+
+ return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(fwnode_typec_mux_get);
@@ -281,25 +389,45 @@ EXPORT_SYMBOL_GPL(fwnode_typec_mux_get);
*/
void typec_mux_put(struct typec_mux *mux)
{
- if (!IS_ERR_OR_NULL(mux)) {
- module_put(mux->dev.parent->driver->owner);
- put_device(&mux->dev);
+ struct typec_mux_dev *mux_dev;
+ unsigned int i;
+
+ if (IS_ERR_OR_NULL(mux))
+ return;
+
+ for (i = 0; i < mux->num_mux_devs; i++) {
+ mux_dev = mux->mux_devs[i];
+ module_put(mux_dev->dev.parent->driver->owner);
+ put_device(&mux_dev->dev);
}
+ kfree(mux);
}
EXPORT_SYMBOL_GPL(typec_mux_put);
int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state)
{
+ struct typec_mux_dev *mux_dev;
+ unsigned int i;
+ int ret;
+
if (IS_ERR_OR_NULL(mux))
return 0;
- return mux->set(mux, state);
+ for (i = 0; i < mux->num_mux_devs; i++) {
+ mux_dev = mux->mux_devs[i];
+
+ ret = mux_dev->set(mux_dev, state);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
EXPORT_SYMBOL_GPL(typec_mux_set);
static void typec_mux_release(struct device *dev)
{
- kfree(to_typec_mux(dev));
+ kfree(to_typec_mux_dev(dev));
}
const struct device_type typec_mux_dev_type = {
@@ -317,63 +445,66 @@ const struct device_type typec_mux_dev_type = {
* the pins on the connector need to be reconfigured. This function registers
* multiplexer switches routing the pins on the connector.
*/
-struct typec_mux *
+struct typec_mux_dev *
typec_mux_register(struct device *parent, const struct typec_mux_desc *desc)
{
- struct typec_mux *mux;
+ struct typec_mux_dev *mux_dev;
int ret;
if (!desc || !desc->set)
return ERR_PTR(-EINVAL);
- mux = kzalloc(sizeof(*mux), GFP_KERNEL);
- if (!mux)
+ mux_dev = kzalloc(sizeof(*mux_dev), GFP_KERNEL);
+ if (!mux_dev)
return ERR_PTR(-ENOMEM);
- mux->set = desc->set;
+ mux_dev->set = desc->set;
- device_initialize(&mux->dev);
- mux->dev.parent = parent;
- mux->dev.fwnode = desc->fwnode;
- mux->dev.class = &typec_mux_class;
- mux->dev.type = &typec_mux_dev_type;
- mux->dev.driver_data = desc->drvdata;
- dev_set_name(&mux->dev, "%s-mux",
- desc->name ? desc->name : dev_name(parent));
+ device_initialize(&mux_dev->dev);
+ mux_dev->dev.parent = parent;
+ mux_dev->dev.fwnode = desc->fwnode;
+ mux_dev->dev.class = &typec_mux_class;
+ mux_dev->dev.type = &typec_mux_dev_type;
+ mux_dev->dev.driver_data = desc->drvdata;
+ ret = dev_set_name(&mux_dev->dev, "%s-mux", desc->name ? desc->name : dev_name(parent));
+ if (ret) {
+ put_device(&mux_dev->dev);
+ return ERR_PTR(ret);
+ }
- ret = device_add(&mux->dev);
+ ret = device_add(&mux_dev->dev);
if (ret) {
dev_err(parent, "failed to register mux (%d)\n", ret);
- put_device(&mux->dev);
+ put_device(&mux_dev->dev);
return ERR_PTR(ret);
}
- return mux;
+ return mux_dev;
}
EXPORT_SYMBOL_GPL(typec_mux_register);
/**
* typec_mux_unregister - Unregister Multiplexer Switch
- * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
+ * @mux_dev: USB Type-C Connector Multiplexer/DeMultiplexer
*
* Unregister mux that was registered with typec_mux_register().
*/
-void typec_mux_unregister(struct typec_mux *mux)
+void typec_mux_unregister(struct typec_mux_dev *mux_dev)
{
- if (!IS_ERR_OR_NULL(mux))
- device_unregister(&mux->dev);
+ if (!IS_ERR_OR_NULL(mux_dev))
+ device_unregister(&mux_dev->dev);
}
EXPORT_SYMBOL_GPL(typec_mux_unregister);
-void typec_mux_set_drvdata(struct typec_mux *mux, void *data)
+void typec_mux_set_drvdata(struct typec_mux_dev *mux_dev, void *data)
{
- dev_set_drvdata(&mux->dev, data);
+ dev_set_drvdata(&mux_dev->dev, data);
}
EXPORT_SYMBOL_GPL(typec_mux_set_drvdata);
-void *typec_mux_get_drvdata(struct typec_mux *mux)
+void *typec_mux_get_drvdata(struct typec_mux_dev *mux_dev)
{
- return dev_get_drvdata(&mux->dev);
+ return dev_get_drvdata(&mux_dev->dev);
}
EXPORT_SYMBOL_GPL(typec_mux_get_drvdata);
diff --git a/drivers/usb/typec/mux.h b/drivers/usb/typec/mux.h
index b1d6e837cb74..58f0f28b6dc8 100644
--- a/drivers/usb/typec/mux.h
+++ b/drivers/usb/typec/mux.h
@@ -5,23 +5,23 @@
#include <linux/usb/typec_mux.h>
-struct typec_switch {
+struct typec_switch_dev {
struct device dev;
typec_switch_set_fn_t set;
};
-struct typec_mux {
+struct typec_mux_dev {
struct device dev;
typec_mux_set_fn_t set;
};
-#define to_typec_switch(_dev_) container_of(_dev_, struct typec_switch, dev)
-#define to_typec_mux(_dev_) container_of(_dev_, struct typec_mux, dev)
+#define to_typec_switch_dev(_dev_) container_of(_dev_, struct typec_switch_dev, dev)
+#define to_typec_mux_dev(_dev_) container_of(_dev_, struct typec_mux_dev, dev)
extern const struct device_type typec_switch_dev_type;
extern const struct device_type typec_mux_dev_type;
-#define is_typec_switch(dev) ((dev)->type == &typec_switch_dev_type)
-#define is_typec_mux(dev) ((dev)->type == &typec_mux_dev_type)
+#define is_typec_switch_dev(dev) ((dev)->type == &typec_switch_dev_type)
+#define is_typec_mux_dev(dev) ((dev)->type == &typec_mux_dev_type)
#endif /* __USB_TYPEC_MUX__ */
diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig
index edead555835e..5eb2c17d72c1 100644
--- a/drivers/usb/typec/mux/Kconfig
+++ b/drivers/usb/typec/mux/Kconfig
@@ -2,6 +2,16 @@
menu "USB Type-C Multiplexer/DeMultiplexer Switch support"
+config TYPEC_MUX_FSA4480
+ tristate "ON Semi FSA4480 Analog Audio Switch driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Driver for the ON Semiconductor FSA4480 Analog Audio Switch, which
+ provides support for muxing analog audio and sideband signals on a
+ common USB Type-C connector.
+ If compiled as a module, the module will be named fsa4480.
+
config TYPEC_MUX_PI3USB30532
tristate "Pericom PI3USB30532 Type-C cross switch driver"
depends on I2C
diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile
index 280a6f553115..e52a56c16bfb 100644
--- a/drivers/usb/typec/mux/Makefile
+++ b/drivers/usb/typec/mux/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_TYPEC_MUX_FSA4480) += fsa4480.o
obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o
obj-$(CONFIG_TYPEC_MUX_INTEL_PMC) += intel_pmc_mux.o
diff --git a/drivers/usb/typec/mux/fsa4480.c b/drivers/usb/typec/mux/fsa4480.c
new file mode 100644
index 000000000000..6184f5367190
--- /dev/null
+++ b/drivers/usb/typec/mux/fsa4480.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021-2022 Linaro Ltd.
+ * Copyright (C) 2018-2020 The Linux Foundation
+ */
+
+#include <linux/bits.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
+
+#define FSA4480_SWITCH_ENABLE 0x04
+#define FSA4480_SWITCH_SELECT 0x05
+#define FSA4480_SWITCH_STATUS1 0x07
+#define FSA4480_SLOW_L 0x08
+#define FSA4480_SLOW_R 0x09
+#define FSA4480_SLOW_MIC 0x0a
+#define FSA4480_SLOW_SENSE 0x0b
+#define FSA4480_SLOW_GND 0x0c
+#define FSA4480_DELAY_L_R 0x0d
+#define FSA4480_DELAY_L_MIC 0x0e
+#define FSA4480_DELAY_L_SENSE 0x0f
+#define FSA4480_DELAY_L_AGND 0x10
+#define FSA4480_RESET 0x1e
+#define FSA4480_MAX_REGISTER 0x1f
+
+#define FSA4480_ENABLE_DEVICE BIT(7)
+#define FSA4480_ENABLE_SBU GENMASK(6, 5)
+#define FSA4480_ENABLE_USB GENMASK(4, 3)
+
+#define FSA4480_SEL_SBU_REVERSE GENMASK(6, 5)
+#define FSA4480_SEL_USB GENMASK(4, 3)
+
+struct fsa4480 {
+ struct i2c_client *client;
+
+ /* used to serialize concurrent change requests */
+ struct mutex lock;
+
+ struct typec_switch_dev *sw;
+ struct typec_mux_dev *mux;
+
+ struct regmap *regmap;
+
+ u8 cur_enable;
+ u8 cur_select;
+};
+
+static const struct regmap_config fsa4480_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = FSA4480_MAX_REGISTER,
+ /* Accesses only done under fsa4480->lock */
+ .disable_locking = true,
+};
+
+static int fsa4480_switch_set(struct typec_switch_dev *sw,
+ enum typec_orientation orientation)
+{
+ struct fsa4480 *fsa = typec_switch_get_drvdata(sw);
+ u8 new_sel;
+
+ mutex_lock(&fsa->lock);
+ new_sel = FSA4480_SEL_USB;
+ if (orientation == TYPEC_ORIENTATION_REVERSE)
+ new_sel |= FSA4480_SEL_SBU_REVERSE;
+
+ if (new_sel == fsa->cur_select)
+ goto out_unlock;
+
+ if (fsa->cur_enable & FSA4480_ENABLE_SBU) {
+ /* Disable SBU output while re-configuring the switch */
+ regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE,
+ fsa->cur_enable & ~FSA4480_ENABLE_SBU);
+
+ /* 35us to allow the SBU switch to turn off */
+ usleep_range(35, 1000);
+ }
+
+ regmap_write(fsa->regmap, FSA4480_SWITCH_SELECT, new_sel);
+ fsa->cur_select = new_sel;
+
+ if (fsa->cur_enable & FSA4480_ENABLE_SBU) {
+ regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, fsa->cur_enable);
+
+ /* 15us to allow the SBU switch to turn on again */
+ usleep_range(15, 1000);
+ }
+
+out_unlock:
+ mutex_unlock(&fsa->lock);
+
+ return 0;
+}
+
+static int fsa4480_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state)
+{
+ struct fsa4480 *fsa = typec_mux_get_drvdata(mux);
+ u8 new_enable;
+
+ mutex_lock(&fsa->lock);
+
+ new_enable = FSA4480_ENABLE_DEVICE | FSA4480_ENABLE_USB;
+ if (state->mode >= TYPEC_DP_STATE_A)
+ new_enable |= FSA4480_ENABLE_SBU;
+
+ if (new_enable == fsa->cur_enable)
+ goto out_unlock;
+
+ regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, new_enable);
+ fsa->cur_enable = new_enable;
+
+ if (new_enable & FSA4480_ENABLE_SBU) {
+ /* 15us to allow the SBU switch to turn off */
+ usleep_range(15, 1000);
+ }
+
+out_unlock:
+ mutex_unlock(&fsa->lock);
+
+ return 0;
+}
+
+static int fsa4480_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct typec_switch_desc sw_desc = { };
+ struct typec_mux_desc mux_desc = { };
+ struct fsa4480 *fsa;
+
+ fsa = devm_kzalloc(dev, sizeof(*fsa), GFP_KERNEL);
+ if (!fsa)
+ return -ENOMEM;
+
+ fsa->client = client;
+ mutex_init(&fsa->lock);
+
+ fsa->regmap = devm_regmap_init_i2c(client, &fsa4480_regmap_config);
+ if (IS_ERR(fsa->regmap))
+ return dev_err_probe(dev, PTR_ERR(fsa->regmap), "failed to initialize regmap\n");
+
+ fsa->cur_enable = FSA4480_ENABLE_DEVICE | FSA4480_ENABLE_USB;
+ fsa->cur_select = FSA4480_SEL_USB;
+
+ /* set default settings */
+ regmap_write(fsa->regmap, FSA4480_SLOW_L, 0x00);
+ regmap_write(fsa->regmap, FSA4480_SLOW_R, 0x00);
+ regmap_write(fsa->regmap, FSA4480_SLOW_MIC, 0x00);
+ regmap_write(fsa->regmap, FSA4480_SLOW_SENSE, 0x00);
+ regmap_write(fsa->regmap, FSA4480_SLOW_GND, 0x00);
+ regmap_write(fsa->regmap, FSA4480_DELAY_L_R, 0x00);
+ regmap_write(fsa->regmap, FSA4480_DELAY_L_MIC, 0x00);
+ regmap_write(fsa->regmap, FSA4480_DELAY_L_SENSE, 0x00);
+ regmap_write(fsa->regmap, FSA4480_DELAY_L_AGND, 0x09);
+ regmap_write(fsa->regmap, FSA4480_SWITCH_SELECT, fsa->cur_select);
+ regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, fsa->cur_enable);
+
+ sw_desc.drvdata = fsa;
+ sw_desc.fwnode = dev_fwnode(dev);
+ sw_desc.set = fsa4480_switch_set;
+
+ fsa->sw = typec_switch_register(dev, &sw_desc);
+ if (IS_ERR(fsa->sw))
+ return dev_err_probe(dev, PTR_ERR(fsa->sw), "failed to register typec switch\n");
+
+ mux_desc.drvdata = fsa;
+ mux_desc.fwnode = dev_fwnode(dev);
+ mux_desc.set = fsa4480_mux_set;
+
+ fsa->mux = typec_mux_register(dev, &mux_desc);
+ if (IS_ERR(fsa->mux)) {
+ typec_switch_unregister(fsa->sw);
+ return dev_err_probe(dev, PTR_ERR(fsa->mux), "failed to register typec mux\n");
+ }
+
+ i2c_set_clientdata(client, fsa);
+ return 0;
+}
+
+static int fsa4480_remove(struct i2c_client *client)
+{
+ struct fsa4480 *fsa = i2c_get_clientdata(client);
+
+ typec_mux_unregister(fsa->mux);
+ typec_switch_unregister(fsa->sw);
+
+ return 0;
+}
+
+static const struct i2c_device_id fsa4480_table[] = {
+ { "fsa4480" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, fsa4480_table);
+
+static const struct of_device_id fsa4480_of_table[] = {
+ { .compatible = "fcs,fsa4480" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, fsa4480_of_table);
+
+static struct i2c_driver fsa4480_driver = {
+ .driver = {
+ .name = "fsa4480",
+ .of_match_table = fsa4480_of_table,
+ },
+ .probe_new = fsa4480_probe,
+ .remove = fsa4480_remove,
+ .id_table = fsa4480_table,
+};
+module_i2c_driver(fsa4480_driver);
+
+MODULE_DESCRIPTION("ON Semiconductor FSA4480 driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c
index 2cdd22130834..47b733f78fb0 100644
--- a/drivers/usb/typec/mux/intel_pmc_mux.c
+++ b/drivers/usb/typec/mux/intel_pmc_mux.c
@@ -121,8 +121,8 @@ struct pmc_usb_port {
int num;
u32 iom_status;
struct pmc_usb *pmc;
- struct typec_mux *typec_mux;
- struct typec_switch *typec_sw;
+ struct typec_mux_dev *typec_mux;
+ struct typec_switch_dev *typec_sw;
struct usb_role_switch *usb_sw;
enum typec_orientation orientation;
@@ -173,7 +173,7 @@ static int hsl_orientation(struct pmc_usb_port *port)
return port->orientation - 1;
}
-static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len)
+static int pmc_usb_send_command(struct intel_scu_ipc_dev *ipc, u8 *msg, u32 len)
{
u8 response[4];
u8 status_res;
@@ -184,7 +184,7 @@ static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len)
* Status can be checked from the response message if the
* function intel_scu_ipc_dev_command succeeds.
*/
- ret = intel_scu_ipc_dev_command(port->pmc->ipc, PMC_USBC_CMD, 0, msg,
+ ret = intel_scu_ipc_dev_command(ipc, PMC_USBC_CMD, 0, msg,
len, response, sizeof(response));
if (ret)
@@ -203,6 +203,23 @@ static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len)
return 0;
}
+static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len)
+{
+ int retry_count = 3;
+ int ret;
+
+ /*
+ * If PMC is busy then retry the command once again
+ */
+ while (retry_count--) {
+ ret = pmc_usb_send_command(port->pmc->ipc, msg, len);
+ if (ret != -EBUSY)
+ break;
+ }
+
+ return ret;
+}
+
static int
pmc_usb_mux_dp_hpd(struct pmc_usb_port *port, struct typec_displayport_data *dp)
{
@@ -416,7 +433,7 @@ static int pmc_usb_connect(struct pmc_usb_port *port, enum usb_role role)
}
static int
-pmc_usb_mux_set(struct typec_mux *mux, struct typec_mux_state *state)
+pmc_usb_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state)
{
struct pmc_usb_port *port = typec_mux_get_drvdata(mux);
@@ -452,7 +469,7 @@ pmc_usb_mux_set(struct typec_mux *mux, struct typec_mux_state *state)
return -EOPNOTSUPP;
}
-static int pmc_usb_set_orientation(struct typec_switch *sw,
+static int pmc_usb_set_orientation(struct typec_switch_dev *sw,
enum typec_orientation orientation)
{
struct pmc_usb_port *port = typec_switch_get_drvdata(sw);
diff --git a/drivers/usb/typec/mux/pi3usb30532.c b/drivers/usb/typec/mux/pi3usb30532.c
index 7afe275b17d0..6ce9f282594e 100644
--- a/drivers/usb/typec/mux/pi3usb30532.c
+++ b/drivers/usb/typec/mux/pi3usb30532.c
@@ -23,8 +23,8 @@
struct pi3usb30532 {
struct i2c_client *client;
struct mutex lock; /* protects the cached conf register */
- struct typec_switch *sw;
- struct typec_mux *mux;
+ struct typec_switch_dev *sw;
+ struct typec_mux_dev *mux;
u8 conf;
};
@@ -45,7 +45,7 @@ static int pi3usb30532_set_conf(struct pi3usb30532 *pi, u8 new_conf)
return 0;
}
-static int pi3usb30532_sw_set(struct typec_switch *sw,
+static int pi3usb30532_sw_set(struct typec_switch_dev *sw,
enum typec_orientation orientation)
{
struct pi3usb30532 *pi = typec_switch_get_drvdata(sw);
@@ -74,7 +74,7 @@ static int pi3usb30532_sw_set(struct typec_switch *sw,
}
static int
-pi3usb30532_mux_set(struct typec_mux *mux, struct typec_mux_state *state)
+pi3usb30532_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state)
{
struct pi3usb30532 *pi = typec_mux_get_drvdata(mux);
u8 new_conf;
diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c
index a7d507802509..a929e000d0e2 100644
--- a/drivers/usb/typec/port-mapper.c
+++ b/drivers/usb/typec/port-mapper.c
@@ -59,7 +59,7 @@ int typec_link_ports(struct typec_port *con)
if (!has_acpi_companion(&con->dev))
return 0;
- bus_for_each_dev(&acpi_bus_type, NULL, &arg, typec_port_match);
+ acpi_bus_for_each_dev(typec_port_match, &arg);
if (!arg.match)
return 0;
diff --git a/drivers/usb/typec/rt1719.c b/drivers/usb/typec/rt1719.c
new file mode 100644
index 000000000000..f1b698edd7eb
--- /dev/null
+++ b/drivers/usb/typec/rt1719.c
@@ -0,0 +1,961 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/usb/pd.h>
+#include <linux/usb/role.h>
+#include <linux/usb/typec.h>
+
+#define RT1719_REG_TXCTRL1 0x03
+#define RT1719_REG_TXCTRL2 0x04
+#define RT1719_REG_POLICYINFO 0x0E
+#define RT1719_REG_SRCPDO1 0x11
+#define RT1719_REG_MASKS 0x2D
+#define RT1719_REG_EVENTS 0x33
+#define RT1719_REG_STATS 0x37
+#define RT1719_REG_PSELINFO 0x3C
+#define RT1719_REG_USBSETINFO 0x3E
+#define RT1719_REG_VENID 0x82
+
+#define RT1719_UNIQUE_PID 0x1719
+#define RT1719_REQDRSWAP_MASK BIT(7)
+#define RT1719_EVALMODE_MASK BIT(4)
+#define RT1719_REQSRCPDO_MASK GENMASK(2, 0)
+#define RT1719_TXSPDOREQ_MASK BIT(7)
+#define RT1719_INT_DRSW_ACCEPT BIT(23)
+#define RT1719_INT_RX_SRCCAP BIT(21)
+#define RT1719_INT_VBUS_DCT BIT(6)
+#define RT1719_INT_VBUS_PRESENT BIT(5)
+#define RT1719_INT_PE_SNK_RDY BIT(2)
+#define RT1719_CC1_STAT GENMASK(9, 8)
+#define RT1719_CC2_STAT GENMASK(11, 10)
+#define RT1719_POLARITY_MASK BIT(23)
+#define RT1719_DATAROLE_MASK BIT(22)
+#define RT1719_PDSPECREV_MASK GENMASK(21, 20)
+#define RT1719_SPDOSEL_MASK GENMASK(18, 16)
+#define RT1719_SPDONUM_MASK GENMASK(15, 13)
+#define RT1719_ATTACH_VBUS BIT(12)
+#define RT1719_ATTACH_DBG BIT(10)
+#define RT1719_ATTACH_SNK BIT(9)
+#define RT1719_ATTACHDEV_MASK (RT1719_ATTACH_VBUS | RT1719_ATTACH_DBG | \
+ RT1719_ATTACH_SNK)
+#define RT1719_PE_EXP_CONTRACT BIT(2)
+#define RT1719_PSEL_SUPPORT BIT(15)
+#define RT1719_TBLSEL_MASK BIT(6)
+#define RT1719_LATPSEL_MASK GENMASK(5, 0)
+#define RT1719_USBINFO_MASK GENMASK(1, 0)
+#define RT1719_USB_DFPUFP 3
+#define RT1719_MAX_SRCPDO 7
+
+enum {
+ SNK_PWR_OPEN = 0,
+ SNK_PWR_DEF,
+ SNK_PWR_1P5A,
+ SNK_PWR_3A
+};
+
+enum {
+ USBPD_SPECREV_1_0 = 0,
+ USBPD_SPECREV_2_0,
+ USBPD_SPECREV_3_0
+};
+
+enum rt1719_snkcap {
+ RT1719_SNKCAP_5V = 0,
+ RT1719_SNKCAP_9V,
+ RT1719_SNKCAP_12V,
+ RT1719_SNKCAP_15V,
+ RT1719_SNKCAP_20V,
+ RT1719_MAX_SNKCAP
+};
+
+struct rt1719_psel_cap {
+ u8 lomask;
+ u8 himask;
+ u32 milliwatt;
+ u32 milliamp;
+};
+
+struct rt1719_data {
+ struct device *dev;
+ struct regmap *regmap;
+ struct typec_port *port;
+ struct usb_role_switch *role_sw;
+ struct power_supply *psy;
+ struct typec_partner *partner;
+ struct power_supply_desc psy_desc;
+ struct usb_pd_identity partner_ident;
+ struct typec_partner_desc partner_desc;
+ struct completion req_completion;
+ enum power_supply_usb_type usb_type;
+ bool attached;
+ bool pd_capable;
+ bool drswap_support;
+ u32 voltage;
+ u32 req_voltage;
+ u32 max_current;
+ u32 op_current;
+ u32 spdos[RT1719_MAX_SRCPDO];
+ u16 snkcaps[RT1719_MAX_SNKCAP];
+ int spdo_num;
+ int spdo_sel;
+ u32 conn_info;
+ u16 conn_stat;
+};
+
+static const enum power_supply_usb_type rt1719_psy_usb_types[] = {
+ POWER_SUPPLY_USB_TYPE_C,
+ POWER_SUPPLY_USB_TYPE_PD,
+ POWER_SUPPLY_USB_TYPE_PD_PPS
+};
+
+static const enum power_supply_property rt1719_psy_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_USB_TYPE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CURRENT_NOW
+};
+
+static int rt1719_read16(struct rt1719_data *data, unsigned int reg, u16 *val)
+{
+ __le16 regval;
+ int ret;
+
+ ret = regmap_raw_read(data->regmap, reg, &regval, sizeof(regval));
+ if (ret)
+ return ret;
+
+ *val = le16_to_cpu(regval);
+ return 0;
+}
+
+static int rt1719_read32(struct rt1719_data *data, unsigned int reg, u32 *val)
+{
+ __le32 regval;
+ int ret;
+
+ ret = regmap_raw_read(data->regmap, reg, &regval, sizeof(regval));
+ if (ret)
+ return ret;
+
+ *val = le32_to_cpu(regval);
+ return 0;
+}
+
+static int rt1719_write32(struct rt1719_data *data, unsigned int reg, u32 val)
+{
+ __le32 regval = cpu_to_le32(val);
+
+ return regmap_raw_write(data->regmap, reg, &regval, sizeof(regval));
+}
+
+static enum typec_pwr_opmode rt1719_get_pwr_opmode(u32 conn, u16 stat)
+{
+ u16 cc1, cc2, cc_stat;
+
+ cc1 = FIELD_GET(RT1719_CC1_STAT, stat);
+ cc2 = FIELD_GET(RT1719_CC2_STAT, stat);
+
+ if (conn & RT1719_ATTACH_SNK) {
+ if (conn & RT1719_POLARITY_MASK)
+ cc_stat = cc2;
+ else
+ cc_stat = cc1;
+
+ switch (cc_stat) {
+ case SNK_PWR_3A:
+ return TYPEC_PWR_MODE_3_0A;
+ case SNK_PWR_1P5A:
+ return TYPEC_PWR_MODE_1_5A;
+ }
+ } else if (conn & RT1719_ATTACH_DBG) {
+ if ((cc1 == SNK_PWR_1P5A && cc2 == SNK_PWR_DEF) ||
+ (cc1 == SNK_PWR_DEF && cc2 == SNK_PWR_1P5A))
+ return TYPEC_PWR_MODE_1_5A;
+ else if ((cc1 == SNK_PWR_3A && cc2 == SNK_PWR_DEF) ||
+ (cc1 == SNK_PWR_DEF && cc2 == SNK_PWR_3A))
+ return TYPEC_PWR_MODE_3_0A;
+ }
+
+ return TYPEC_PWR_MODE_USB;
+}
+
+static enum typec_data_role rt1719_get_data_role(u32 conn)
+{
+ if (conn & RT1719_DATAROLE_MASK)
+ return TYPEC_HOST;
+ return TYPEC_DEVICE;
+}
+
+static void rt1719_set_data_role(struct rt1719_data *data,
+ enum typec_data_role data_role,
+ bool attached)
+{
+ enum usb_role usb_role = USB_ROLE_NONE;
+
+ if (attached) {
+ if (data_role == TYPEC_HOST)
+ usb_role = USB_ROLE_HOST;
+ else
+ usb_role = USB_ROLE_DEVICE;
+ }
+
+ usb_role_switch_set_role(data->role_sw, usb_role);
+ typec_set_data_role(data->port, data_role);
+}
+
+static void rt1719_update_data_role(struct rt1719_data *data)
+{
+ if (!data->attached)
+ return;
+
+ rt1719_set_data_role(data, rt1719_get_data_role(data->conn_info), true);
+}
+
+static void rt1719_register_partner(struct rt1719_data *data)
+{
+ u16 spec_rev = 0;
+
+ if (data->pd_capable) {
+ u32 rev;
+
+ rev = FIELD_GET(RT1719_PDSPECREV_MASK, data->conn_info);
+ switch (rev) {
+ case USBPD_SPECREV_3_0:
+ spec_rev = 0x0300;
+ break;
+ case USBPD_SPECREV_2_0:
+ spec_rev = 0x0200;
+ break;
+ default:
+ spec_rev = 0x0100;
+ break;
+ }
+ }
+
+ /* Just to prevent multiple times attach */
+ if (data->partner)
+ typec_unregister_partner(data->partner);
+
+ memset(&data->partner_ident, 0, sizeof(data->partner_ident));
+ data->partner_desc.usb_pd = data->pd_capable;
+ data->partner_desc.pd_revision = spec_rev;
+
+ if (data->conn_info & RT1719_ATTACH_DBG)
+ data->partner_desc.accessory = TYPEC_ACCESSORY_DEBUG;
+ else
+ data->partner_desc.accessory = TYPEC_ACCESSORY_NONE;
+
+ data->partner = typec_register_partner(data->port, &data->partner_desc);
+}
+
+static void rt1719_attach(struct rt1719_data *data)
+{
+ enum typec_pwr_opmode pwr_opmode;
+ enum typec_data_role data_role;
+ u32 volt = 5000, curr = 500;
+
+ if (!(data->conn_info & RT1719_ATTACHDEV_MASK))
+ return;
+
+ pwr_opmode = rt1719_get_pwr_opmode(data->conn_info, data->conn_stat);
+ data_role = rt1719_get_data_role(data->conn_info);
+
+ typec_set_pwr_opmode(data->port, pwr_opmode);
+ rt1719_set_data_role(data, data_role, true);
+
+ if (data->conn_info & RT1719_ATTACH_SNK)
+ rt1719_register_partner(data);
+
+ if (pwr_opmode == TYPEC_PWR_MODE_3_0A)
+ curr = 3000;
+ else if (pwr_opmode == TYPEC_PWR_MODE_1_5A)
+ curr = 1500;
+
+ data->voltage = volt * 1000;
+ data->max_current = data->op_current = curr * 1000;
+ data->attached = true;
+
+ power_supply_changed(data->psy);
+}
+
+static void rt1719_detach(struct rt1719_data *data)
+{
+ if (!data->attached || (data->conn_info & RT1719_ATTACHDEV_MASK))
+ return;
+
+ typec_unregister_partner(data->partner);
+ data->partner = NULL;
+
+ typec_set_pwr_opmode(data->port, TYPEC_PWR_MODE_USB);
+ rt1719_set_data_role(data, TYPEC_DEVICE, false);
+
+ memset32(data->spdos, 0, RT1719_MAX_SRCPDO);
+ data->spdo_num = 0;
+ data->voltage = data->max_current = data->op_current = 0;
+ data->attached = data->pd_capable = false;
+
+ data->usb_type = POWER_SUPPLY_USB_TYPE_C;
+
+ power_supply_changed(data->psy);
+}
+
+static void rt1719_update_operating_status(struct rt1719_data *data)
+{
+ enum power_supply_usb_type usb_type = POWER_SUPPLY_USB_TYPE_PD;
+ u32 voltage, max_current, op_current;
+ int i, snk_sel;
+
+ for (i = 0; i < data->spdo_num; i++) {
+ u32 pdo = data->spdos[i];
+ enum pd_pdo_type type = pdo_type(pdo);
+
+ if (type == PDO_TYPE_APDO) {
+ usb_type = POWER_SUPPLY_USB_TYPE_PD_PPS;
+ break;
+ }
+ }
+
+ data->spdo_sel = FIELD_GET(RT1719_SPDOSEL_MASK, data->conn_info);
+ if (data->spdo_sel <= 0)
+ return;
+
+ data->usb_type = usb_type;
+
+ voltage = pdo_fixed_voltage(data->spdos[data->spdo_sel - 1]);
+ max_current = pdo_max_current(data->spdos[data->spdo_sel - 1]);
+
+ switch (voltage) {
+ case 5000:
+ snk_sel = RT1719_SNKCAP_5V;
+ break;
+ case 9000:
+ snk_sel = RT1719_SNKCAP_9V;
+ break;
+ case 12000:
+ snk_sel = RT1719_SNKCAP_12V;
+ break;
+ case 15000:
+ snk_sel = RT1719_SNKCAP_15V;
+ break;
+ case 20000:
+ snk_sel = RT1719_SNKCAP_20V;
+ break;
+ default:
+ return;
+ }
+
+ op_current = min(max_current, pdo_max_current(data->snkcaps[snk_sel]));
+
+ /* covert mV/mA to uV/uA */
+ data->voltage = voltage * 1000;
+ data->max_current = max_current * 1000;
+ data->op_current = op_current * 1000;
+
+ power_supply_changed(data->psy);
+}
+
+static void rt1719_update_pwr_opmode(struct rt1719_data *data)
+{
+ if (!data->attached)
+ return;
+
+ if (!data->pd_capable) {
+ data->pd_capable = true;
+
+ typec_set_pwr_opmode(data->port, TYPEC_PWR_MODE_PD);
+ rt1719_register_partner(data);
+ }
+
+ rt1719_update_operating_status(data);
+}
+
+static void rt1719_update_source_pdos(struct rt1719_data *data)
+{
+ int spdo_num = FIELD_GET(RT1719_SPDONUM_MASK, data->conn_info);
+ __le32 src_pdos[RT1719_MAX_SRCPDO] = { };
+ int i, ret;
+
+ if (!data->attached)
+ return;
+
+ ret = regmap_raw_read(data->regmap, RT1719_REG_SRCPDO1, src_pdos,
+ sizeof(__le32) * spdo_num);
+ if (ret)
+ return;
+
+ data->spdo_num = spdo_num;
+ for (i = 0; i < spdo_num; i++)
+ data->spdos[i] = le32_to_cpu(src_pdos[i]);
+}
+
+static int rt1719_dr_set(struct typec_port *port, enum typec_data_role role)
+{
+ struct rt1719_data *data = typec_get_drvdata(port);
+ enum typec_data_role cur_role;
+ int ret;
+
+ if (!data->attached || !data->pd_capable || !data->drswap_support)
+ return -EOPNOTSUPP;
+
+ if (data->spdo_num > 0 && !(data->spdos[0] & PDO_FIXED_DATA_SWAP))
+ return -EINVAL;
+
+ cur_role = rt1719_get_data_role(data->conn_info);
+ if (cur_role == role)
+ return 0;
+
+ ret = regmap_update_bits(data->regmap, RT1719_REG_TXCTRL1,
+ RT1719_REQDRSWAP_MASK, RT1719_REQDRSWAP_MASK);
+ if (ret)
+ return ret;
+
+ reinit_completion(&data->req_completion);
+ ret = wait_for_completion_timeout(&data->req_completion,
+ msecs_to_jiffies(400));
+ if (ret == 0)
+ return -ETIMEDOUT;
+
+ cur_role = rt1719_get_data_role(data->conn_info);
+ if (cur_role != role)
+ return -EAGAIN;
+
+ rt1719_set_data_role(data, role, true);
+ return 0;
+}
+
+static const struct typec_operations rt1719_port_ops = {
+ .dr_set = rt1719_dr_set,
+};
+
+static int rt1719_usbpd_request_voltage(struct rt1719_data *data)
+{
+ u32 src_voltage;
+ int snk_sel, src_sel = -1;
+ int i, ret;
+
+ if (!data->attached || !data->pd_capable || data->spdo_sel <= 0)
+ return -EINVAL;
+
+ src_voltage = pdo_fixed_voltage(data->spdos[data->spdo_sel - 1]);
+ if (src_voltage == data->req_voltage)
+ return 0;
+
+ switch (data->req_voltage) {
+ case 5000:
+ snk_sel = RT1719_SNKCAP_5V;
+ break;
+ case 9000:
+ snk_sel = RT1719_SNKCAP_9V;
+ break;
+ case 12000:
+ snk_sel = RT1719_SNKCAP_12V;
+ break;
+ case 15000:
+ snk_sel = RT1719_SNKCAP_15V;
+ break;
+ case 20000:
+ snk_sel = RT1719_SNKCAP_20V;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!(data->snkcaps[snk_sel] & RT1719_PSEL_SUPPORT))
+ return -EINVAL;
+
+ for (i = 0; i < data->spdo_num; i++) {
+ enum pd_pdo_type type = pdo_type(data->spdos[i]);
+
+ if (type != PDO_TYPE_FIXED)
+ continue;
+
+ src_voltage = pdo_fixed_voltage(data->spdos[i]);
+ if (src_voltage == data->req_voltage) {
+ src_sel = i;
+ break;
+ }
+ }
+
+ if (src_sel == -1)
+ return -EOPNOTSUPP;
+
+ ret = regmap_update_bits(data->regmap, RT1719_REG_TXCTRL1,
+ RT1719_EVALMODE_MASK | RT1719_REQSRCPDO_MASK,
+ RT1719_EVALMODE_MASK | (src_sel + 1));
+ ret |= regmap_update_bits(data->regmap, RT1719_REG_TXCTRL2,
+ RT1719_TXSPDOREQ_MASK, RT1719_TXSPDOREQ_MASK);
+ if (ret)
+ return ret;
+
+ reinit_completion(&data->req_completion);
+ ret = wait_for_completion_timeout(&data->req_completion,
+ msecs_to_jiffies(400));
+ if (!ret)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int rt1719_psy_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct rt1719_data *data = power_supply_get_drvdata(psy);
+
+ if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW) {
+ data->req_voltage = val->intval / 1000;
+ return rt1719_usbpd_request_voltage(data);
+ }
+
+ return -EINVAL;
+}
+
+static int rt1719_psy_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct rt1719_data *data = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = data->attached ? 1 : 0;
+ break;
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ val->intval = data->usb_type;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = data->voltage;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = data->max_current;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = data->op_current;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int rt1719_psy_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW)
+ return 1;
+ return 0;
+}
+
+static int devm_rt1719_psy_register(struct rt1719_data *data)
+{
+ struct power_supply_config psy_cfg = { };
+ char *psy_name;
+
+ psy_cfg.fwnode = dev_fwnode(data->dev);
+ psy_cfg.drv_data = data;
+
+ psy_name = devm_kasprintf(data->dev, GFP_KERNEL, "rt1719-source-psy-%s",
+ dev_name(data->dev));
+ if (!psy_name)
+ return -ENOMEM;
+
+ data->psy_desc.name = psy_name;
+ data->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+ data->psy_desc.usb_types = rt1719_psy_usb_types;
+ data->psy_desc.num_usb_types = ARRAY_SIZE(rt1719_psy_usb_types);
+ data->psy_desc.properties = rt1719_psy_properties;
+ data->psy_desc.num_properties = ARRAY_SIZE(rt1719_psy_properties);
+ data->psy_desc.get_property = rt1719_psy_get_property;
+ data->psy_desc.set_property = rt1719_psy_set_property;
+ data->psy_desc.property_is_writeable = rt1719_psy_property_is_writeable;
+
+ data->usb_type = POWER_SUPPLY_USB_TYPE_C;
+
+ data->psy = devm_power_supply_register(data->dev, &data->psy_desc,
+ &psy_cfg);
+
+ return PTR_ERR_OR_ZERO(data->psy);
+}
+
+static irqreturn_t rt1719_irq_handler(int irq, void *priv)
+{
+ struct rt1719_data *data = priv;
+ u32 events, conn_info;
+ u16 conn_stat;
+ int ret;
+
+ ret = rt1719_read32(data, RT1719_REG_EVENTS, &events);
+ ret |= rt1719_read32(data, RT1719_REG_POLICYINFO, &conn_info);
+ ret |= rt1719_read16(data, RT1719_REG_STATS, &conn_stat);
+ if (ret)
+ return IRQ_NONE;
+
+ data->conn_info = conn_info;
+ data->conn_stat = conn_stat;
+
+ events &= (RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP |
+ RT1719_INT_VBUS_PRESENT | RT1719_INT_VBUS_DCT |
+ RT1719_INT_PE_SNK_RDY);
+
+ if (events & RT1719_INT_DRSW_ACCEPT)
+ rt1719_update_data_role(data);
+
+ if (events & RT1719_INT_VBUS_PRESENT)
+ rt1719_attach(data);
+
+ if (events & RT1719_INT_VBUS_DCT)
+ rt1719_detach(data);
+
+ if (events & RT1719_INT_RX_SRCCAP)
+ rt1719_update_source_pdos(data);
+
+ if (events & RT1719_INT_PE_SNK_RDY) {
+ complete(&data->req_completion);
+ rt1719_update_pwr_opmode(data);
+ }
+
+ /* Write 1 to clear already handled events */
+ rt1719_write32(data, RT1719_REG_EVENTS, events);
+
+ return IRQ_HANDLED;
+}
+
+static int rt1719_irq_init(struct rt1719_data *data)
+{
+ struct i2c_client *i2c = to_i2c_client(data->dev);
+ u32 irq_enable;
+ int ret;
+
+ irq_enable = RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP |
+ RT1719_INT_VBUS_DCT | RT1719_INT_VBUS_PRESENT |
+ RT1719_INT_PE_SNK_RDY;
+
+ ret = rt1719_write32(data, RT1719_REG_MASKS, irq_enable);
+ if (ret) {
+ dev_err(&i2c->dev, "Failed to config irq enable\n");
+ return ret;
+ }
+
+ return devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL,
+ rt1719_irq_handler, IRQF_ONESHOT,
+ dev_name(&i2c->dev), data);
+}
+
+static int rt1719_init_attach_state(struct rt1719_data *data)
+{
+ u32 conn_info, irq_clear;
+ u16 conn_stat;
+ int ret;
+
+ irq_clear = RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP |
+ RT1719_INT_VBUS_DCT | RT1719_INT_VBUS_PRESENT |
+ RT1719_INT_PE_SNK_RDY;
+
+ ret = rt1719_read32(data, RT1719_REG_POLICYINFO, &conn_info);
+ ret |= rt1719_read16(data, RT1719_REG_STATS, &conn_stat);
+ ret |= rt1719_write32(data, RT1719_REG_EVENTS, irq_clear);
+ if (ret)
+ return ret;
+
+ data->conn_info = conn_info;
+ data->conn_stat = conn_stat;
+
+ if (conn_info & RT1719_ATTACHDEV_MASK)
+ rt1719_attach(data);
+
+ if (conn_info & RT1719_PE_EXP_CONTRACT) {
+ rt1719_update_source_pdos(data);
+ rt1719_update_pwr_opmode(data);
+ }
+
+ return 0;
+}
+
+#define RT1719_PSEL_CAPINFO(_lomask, _milliwatt, _himask, _milliamp) { \
+ .lomask = _lomask, \
+ .milliwatt = _milliwatt, \
+ .himask = _himask, \
+ .milliamp = _milliamp, \
+}
+
+static const struct rt1719_psel_cap rt1719_psel_caps[] = {
+ RT1719_PSEL_CAPINFO(0x18, 75000, 0x10, 5000),
+ RT1719_PSEL_CAPINFO(0x18, 60000, 0x10, 4500),
+ RT1719_PSEL_CAPINFO(0x18, 45000, 0x10, 4000),
+ RT1719_PSEL_CAPINFO(0x18, 30000, 0x10, 3500),
+ RT1719_PSEL_CAPINFO(0x18, 25000, 0x10, 3000),
+ RT1719_PSEL_CAPINFO(0x18, 20000, 0x10, 2500),
+ RT1719_PSEL_CAPINFO(0x18, 15000, 0x10, 2000),
+ RT1719_PSEL_CAPINFO(0x18, 10000, 0x10, 1000),
+ RT1719_PSEL_CAPINFO(0x1C, 60000, 0x1F, 5000),
+ RT1719_PSEL_CAPINFO(0x1C, 45000, 0x1F, 4500),
+ RT1719_PSEL_CAPINFO(0x1C, 30000, 0x1F, 4000),
+ RT1719_PSEL_CAPINFO(0x1C, 24000, 0x1F, 3500),
+ RT1719_PSEL_CAPINFO(0x1C, 15000, 0x1F, 3000),
+ RT1719_PSEL_CAPINFO(0x1C, 10000, 0x1F, 2500),
+ RT1719_PSEL_CAPINFO(0x0C, 60000, 0x1F, 2000),
+ RT1719_PSEL_CAPINFO(0x0C, 45000, 0x1F, 1000),
+ RT1719_PSEL_CAPINFO(0x0C, 36000, 0x08, 5000),
+ RT1719_PSEL_CAPINFO(0x0C, 30000, 0x08, 4500),
+ RT1719_PSEL_CAPINFO(0x0C, 24000, 0x08, 4000),
+ RT1719_PSEL_CAPINFO(0x0C, 15000, 0x08, 3500),
+ RT1719_PSEL_CAPINFO(0x0C, 10000, 0x08, 3000),
+ RT1719_PSEL_CAPINFO(0x1E, 45000, 0x08, 2500),
+ RT1719_PSEL_CAPINFO(0x1E, 36000, 0x08, 2000),
+ RT1719_PSEL_CAPINFO(0x1E, 27000, 0x08, 1500),
+ RT1719_PSEL_CAPINFO(0x1E, 20000, 0x08, 1000),
+ RT1719_PSEL_CAPINFO(0x1E, 15000, 0x0F, 5000),
+ RT1719_PSEL_CAPINFO(0x1E, 9000, 0x0F, 4500),
+ RT1719_PSEL_CAPINFO(0x0E, 45000, 0x0F, 4000),
+ RT1719_PSEL_CAPINFO(0x0E, 36000, 0x0F, 3500),
+ RT1719_PSEL_CAPINFO(0x0E, 27000, 0x0F, 3000),
+ RT1719_PSEL_CAPINFO(0x0E, 20000, 0x0F, 2500),
+ RT1719_PSEL_CAPINFO(0x0E, 15000, 0x0F, 2000),
+ RT1719_PSEL_CAPINFO(0x0E, 9000, 0x0F, 1500),
+ RT1719_PSEL_CAPINFO(0x06, 45000, 0x0F, 1000),
+ RT1719_PSEL_CAPINFO(0x06, 36000, 0x0F, 500),
+ RT1719_PSEL_CAPINFO(0x06, 27000, 0x04, 5000),
+ RT1719_PSEL_CAPINFO(0x06, 24000, 0x04, 4500),
+ RT1719_PSEL_CAPINFO(0x06, 18000, 0x04, 4000),
+ RT1719_PSEL_CAPINFO(0x06, 12000, 0x04, 3500),
+ RT1719_PSEL_CAPINFO(0x06, 9000, 0x04, 3000),
+ RT1719_PSEL_CAPINFO(0x1F, 25000, 0x04, 2500),
+ RT1719_PSEL_CAPINFO(0x1F, 20000, 0x04, 2000),
+ RT1719_PSEL_CAPINFO(0x1F, 15000, 0x04, 1500),
+ RT1719_PSEL_CAPINFO(0x1F, 10000, 0x04, 1000),
+ RT1719_PSEL_CAPINFO(0x1F, 7500, 0x07, 5000),
+ RT1719_PSEL_CAPINFO(0x0F, 25000, 0x07, 4500),
+ RT1719_PSEL_CAPINFO(0x0F, 20000, 0x07, 4000),
+ RT1719_PSEL_CAPINFO(0x0F, 15000, 0x07, 3500),
+ RT1719_PSEL_CAPINFO(0x0F, 10000, 0x07, 3000),
+ RT1719_PSEL_CAPINFO(0x0F, 7500, 0x07, 2500),
+ RT1719_PSEL_CAPINFO(0x07, 25000, 0x07, 2000),
+ RT1719_PSEL_CAPINFO(0x07, 20000, 0x07, 1500),
+ RT1719_PSEL_CAPINFO(0x07, 15000, 0x07, 1000),
+ RT1719_PSEL_CAPINFO(0x07, 10000, 0x07, 500),
+ RT1719_PSEL_CAPINFO(0x07, 7500, 0x03, 5000),
+ RT1719_PSEL_CAPINFO(0x03, 25000, 0x03, 4500),
+ RT1719_PSEL_CAPINFO(0x03, 20000, 0x03, 4000),
+ RT1719_PSEL_CAPINFO(0x03, 15000, 0x03, 3500),
+ RT1719_PSEL_CAPINFO(0x03, 10000, 0x03, 3000),
+ RT1719_PSEL_CAPINFO(0x03, 7500, 0x03, 2500),
+ RT1719_PSEL_CAPINFO(0x01, 15000, 0x03, 2000),
+ RT1719_PSEL_CAPINFO(0x01, 10000, 0x03, 1500),
+ RT1719_PSEL_CAPINFO(0x01, 7500, 0x03, 1000),
+ RT1719_PSEL_CAPINFO(0x01, 2500, 0x03, 500)
+};
+
+static u16 rt1719_gen_snkcap_by_current(const struct rt1719_psel_cap *psel_cap,
+ enum rt1719_snkcap capsel)
+{
+ u16 cap = RT1719_PSEL_SUPPORT;
+
+ if (!(psel_cap->himask & BIT(capsel)))
+ return 0;
+
+ cap |= psel_cap->milliamp / 10;
+ return cap;
+}
+
+static u16 rt1719_gen_snkcap_by_watt(const struct rt1719_psel_cap *psel_cap,
+ enum rt1719_snkcap capsel)
+{
+ u32 volt_div[RT1719_MAX_SNKCAP] = { 5, 9, 12, 15, 20 };
+ u16 cap = RT1719_PSEL_SUPPORT;
+
+ if (!(psel_cap->lomask & BIT(capsel)))
+ return 0;
+
+ cap |= min(psel_cap->milliwatt / volt_div[capsel], (u32)5000) / 10;
+ return cap;
+}
+
+static u16 rt1719_gen_snkcap(unsigned int pselinfo, enum rt1719_snkcap capsel)
+{
+ int psel = FIELD_GET(RT1719_LATPSEL_MASK, pselinfo);
+ const struct rt1719_psel_cap *psel_cap;
+ bool by_current = false;
+
+ if (pselinfo & RT1719_TBLSEL_MASK)
+ by_current = true;
+
+ psel_cap = rt1719_psel_caps + psel;
+ if (by_current)
+ return rt1719_gen_snkcap_by_current(psel_cap, capsel);
+
+ return rt1719_gen_snkcap_by_watt(psel_cap, capsel);
+}
+
+static int rt1719_get_caps(struct rt1719_data *data)
+{
+ unsigned int pselinfo, usbinfo;
+ int i, ret;
+
+ ret = regmap_read(data->regmap, RT1719_REG_PSELINFO, &pselinfo);
+ ret |= regmap_read(data->regmap, RT1719_REG_USBSETINFO, &usbinfo);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < RT1719_MAX_SNKCAP; i++)
+ data->snkcaps[i] = rt1719_gen_snkcap(pselinfo, i);
+
+ usbinfo = FIELD_GET(RT1719_USBINFO_MASK, usbinfo);
+ if (usbinfo == RT1719_USB_DFPUFP)
+ data->drswap_support = true;
+
+ return 0;
+}
+
+static int rt1719_check_exist(struct rt1719_data *data)
+{
+ u16 pid;
+ int ret;
+
+ ret = rt1719_read16(data, RT1719_REG_VENID, &pid);
+ if (ret)
+ return ret;
+
+ if (pid != RT1719_UNIQUE_PID) {
+ dev_err(data->dev, "Incorrect PID 0x%04x\n", pid);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static const struct regmap_config rt1719_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xff,
+};
+
+static int rt1719_probe(struct i2c_client *i2c)
+{
+ struct rt1719_data *data;
+ struct fwnode_handle *fwnode;
+ struct typec_capability typec_cap = { };
+ int ret;
+
+ data = devm_kzalloc(&i2c->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = &i2c->dev;
+ init_completion(&data->req_completion);
+
+ data->regmap = devm_regmap_init_i2c(i2c, &rt1719_regmap_config);
+ if (IS_ERR(data->regmap)) {
+ ret = PTR_ERR(data->regmap);
+ dev_err(&i2c->dev, "Failed to init regmap (%d)\n", ret);
+ return ret;
+ }
+
+ ret = rt1719_check_exist(data);
+ if (ret)
+ return ret;
+
+ ret = rt1719_get_caps(data);
+ if (ret)
+ return ret;
+
+ fwnode = device_get_named_child_node(&i2c->dev, "connector");
+ if (!fwnode)
+ return -ENODEV;
+
+ data->role_sw = fwnode_usb_role_switch_get(fwnode);
+ if (IS_ERR(data->role_sw)) {
+ ret = PTR_ERR(data->role_sw);
+ dev_err(&i2c->dev, "Failed to get usb role switch (%d)\n", ret);
+ goto err_fwnode_put;
+ }
+
+ ret = devm_rt1719_psy_register(data);
+ if (ret) {
+ dev_err(&i2c->dev, "Failed to register psy (%d)\n", ret);
+ goto err_role_put;
+ }
+
+ typec_cap.revision = USB_TYPEC_REV_1_2;
+ typec_cap.pd_revision = 0x300; /* USB-PD spec release 3.0 */
+ typec_cap.type = TYPEC_PORT_SNK;
+ typec_cap.data = TYPEC_PORT_DRD;
+ typec_cap.ops = &rt1719_port_ops;
+ typec_cap.fwnode = fwnode;
+ typec_cap.driver_data = data;
+ typec_cap.accessory[0] = TYPEC_ACCESSORY_DEBUG;
+
+ data->partner_desc.identity = &data->partner_ident;
+
+ data->port = typec_register_port(&i2c->dev, &typec_cap);
+ if (IS_ERR(data->port)) {
+ ret = PTR_ERR(data->port);
+ dev_err(&i2c->dev, "Failed to register typec port (%d)\n", ret);
+ goto err_role_put;
+ }
+
+ ret = rt1719_init_attach_state(data);
+ if (ret) {
+ dev_err(&i2c->dev, "Failed to init attach state (%d)\n", ret);
+ goto err_role_put;
+ }
+
+ ret = rt1719_irq_init(data);
+ if (ret) {
+ dev_err(&i2c->dev, "Failed to init irq\n");
+ goto err_role_put;
+ }
+
+ fwnode_handle_put(fwnode);
+
+ i2c_set_clientdata(i2c, data);
+
+ return 0;
+
+err_role_put:
+ usb_role_switch_put(data->role_sw);
+err_fwnode_put:
+ fwnode_handle_put(fwnode);
+
+ return ret;
+}
+
+static int rt1719_remove(struct i2c_client *i2c)
+{
+ struct rt1719_data *data = i2c_get_clientdata(i2c);
+
+ typec_unregister_port(data->port);
+ usb_role_switch_put(data->role_sw);
+
+ return 0;
+}
+
+static const struct of_device_id __maybe_unused rt1719_device_table[] = {
+ { .compatible = "richtek,rt1719", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rt1719_device_table);
+
+static struct i2c_driver rt1719_driver = {
+ .driver = {
+ .name = "rt1719",
+ .of_match_table = rt1719_device_table,
+ },
+ .probe_new = rt1719_probe,
+ .remove = rt1719_remove,
+};
+module_i2c_driver(rt1719_driver);
+
+MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
+MODULE_DESCRIPTION("Richtek RT1719 Sink Only USBPD Controller Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/typec/tcpm/Kconfig b/drivers/usb/typec/tcpm/Kconfig
index 557f392fe24d..073fd2ea5e0b 100644
--- a/drivers/usb/typec/tcpm/Kconfig
+++ b/drivers/usb/typec/tcpm/Kconfig
@@ -56,7 +56,6 @@ config TYPEC_WCOVE
tristate "Intel WhiskeyCove PMIC USB Type-C PHY driver"
depends on ACPI
depends on MFD_INTEL_PMC_BXT
- depends on INTEL_SOC_PMIC
depends on BXT_WC_PMIC_OPREGION
help
This driver adds support for USB Type-C on Intel Broxton platforms
diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c
index 72f9001b0792..96c55eaf3f80 100644
--- a/drivers/usb/typec/tcpm/fusb302.c
+++ b/drivers/usb/typec/tcpm/fusb302.c
@@ -1708,8 +1708,8 @@ static int fusb302_probe(struct i2c_client *client,
*/
if (device_property_read_string(dev, "linux,extcon-name", &name) == 0) {
chip->extcon = extcon_get_extcon_dev(name);
- if (!chip->extcon)
- return -EPROBE_DEFER;
+ if (IS_ERR(chip->extcon))
+ return PTR_ERR(chip->extcon);
}
chip->vbus = devm_regulator_get(chip->dev, "vbus");
diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
index e07d26a3cd8e..f33e08eb7670 100644
--- a/drivers/usb/typec/tcpm/tcpci.c
+++ b/drivers/usb/typec/tcpm/tcpci.c
@@ -877,7 +877,7 @@ static int tcpci_remove(struct i2c_client *client)
/* Disable chip interrupts before unregistering port */
err = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, 0);
if (err < 0)
- return err;
+ dev_warn(&client->dev, "Failed to disable irqs (%pe)\n", ERR_PTR(err));
tcpci_unregister_port(chip->tcpci);
diff --git a/drivers/usb/typec/tcpm/tcpci_mt6360.c b/drivers/usb/typec/tcpm/tcpci_mt6360.c
index f1bd9e09bc87..8a952eaf9016 100644
--- a/drivers/usb/typec/tcpm/tcpci_mt6360.c
+++ b/drivers/usb/typec/tcpm/tcpci_mt6360.c
@@ -15,6 +15,9 @@
#include "tcpci.h"
+#define MT6360_REG_PHYCTRL1 0x80
+#define MT6360_REG_PHYCTRL3 0x82
+#define MT6360_REG_PHYCTRL7 0x86
#define MT6360_REG_VCONNCTRL1 0x8C
#define MT6360_REG_MODECTRL2 0x8F
#define MT6360_REG_SWRESET 0xA0
@@ -22,6 +25,8 @@
#define MT6360_REG_DRPCTRL1 0xA2
#define MT6360_REG_DRPCTRL2 0xA3
#define MT6360_REG_I2CTORST 0xBF
+#define MT6360_REG_PHYCTRL11 0xCA
+#define MT6360_REG_RXCTRL1 0xCE
#define MT6360_REG_RXCTRL2 0xCF
#define MT6360_REG_CTDCTRL2 0xEC
@@ -106,6 +111,27 @@ static int mt6360_tcpc_init(struct tcpci *tcpci, struct tcpci_data *tdata)
if (ret)
return ret;
+ /* BMC PHY */
+ ret = mt6360_tcpc_write16(regmap, MT6360_REG_PHYCTRL1, 0x3A70);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(regmap, MT6360_REG_PHYCTRL3, 0x82);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(regmap, MT6360_REG_PHYCTRL7, 0x36);
+ if (ret)
+ return ret;
+
+ ret = mt6360_tcpc_write16(regmap, MT6360_REG_PHYCTRL11, 0x3C60);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(regmap, MT6360_REG_RXCTRL1, 0xE8);
+ if (ret)
+ return ret;
+
/* Set shipping mode off, AUTOIDLE on */
return regmap_write(regmap, MT6360_REG_MODECTRL2, 0x7A);
}
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 5fce795b69c7..3bc2f4ebd1fe 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -5928,7 +5928,6 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
struct fwnode_handle *fwnode)
{
const char *opmode_str;
- const char *cap_str;
int ret;
u32 mw, frs_current;
@@ -5944,23 +5943,10 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
*/
fw_devlink_purge_absent_suppliers(fwnode);
- /* USB data support is optional */
- ret = fwnode_property_read_string(fwnode, "data-role", &cap_str);
- if (ret == 0) {
- ret = typec_find_port_data_role(cap_str);
- if (ret < 0)
- return ret;
- port->typec_caps.data = ret;
- }
-
- ret = fwnode_property_read_string(fwnode, "power-role", &cap_str);
+ ret = typec_get_fw_cap(&port->typec_caps, fwnode);
if (ret < 0)
return ret;
- ret = typec_find_port_power_role(cap_str);
- if (ret < 0)
- return ret;
- port->typec_caps.type = ret;
port->port_type = port->typec_caps.type;
port->pd_supported = !fwnode_property_read_bool(fwnode, "pd-disable");
@@ -5997,14 +5983,6 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
if (port->port_type == TYPEC_PORT_SRC)
return 0;
- /* Get the preferred power role for DRP */
- ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str);
- if (ret < 0)
- return ret;
-
- port->typec_caps.prefer_role = typec_find_power_role(cap_str);
- if (port->typec_caps.prefer_role < 0)
- return -EINVAL;
sink:
port->self_powered = fwnode_property_read_bool(fwnode, "self-powered");
diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index 7ffcda94d323..dfbba5ae9487 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -93,6 +93,8 @@ struct tps6598x {
struct power_supply *psy;
struct power_supply_desc psy_desc;
enum power_supply_usb_type usb_type;
+
+ u16 pwr_status;
};
static enum power_supply_property tps6598x_psy_props[] = {
@@ -230,17 +232,12 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status)
{
struct typec_partner_desc desc;
enum typec_pwr_opmode mode;
- u16 pwr_status;
int ret;
if (tps->partner)
return 0;
- ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status);
- if (ret < 0)
- return ret;
-
- mode = TPS_POWER_STATUS_PWROPMODE(pwr_status);
+ mode = TPS_POWER_STATUS_PWROPMODE(tps->pwr_status);
desc.usb_pd = mode == TYPEC_PWR_MODE_PD;
desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */
@@ -256,6 +253,10 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status)
typec_set_pwr_opmode(tps->port, mode);
typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(status));
typec_set_vconn_role(tps->port, TPS_STATUS_TO_TYPEC_VCONN(status));
+ if (TPS_STATUS_TO_UPSIDE_DOWN(status))
+ typec_set_orientation(tps->port, TYPEC_ORIENTATION_REVERSE);
+ else
+ typec_set_orientation(tps->port, TYPEC_ORIENTATION_NORMAL);
tps6598x_set_data_role(tps, TPS_STATUS_TO_TYPEC_DATAROLE(status), true);
tps->partner = typec_register_partner(tps->port, &desc);
@@ -278,6 +279,7 @@ static void tps6598x_disconnect(struct tps6598x *tps, u32 status)
typec_set_pwr_opmode(tps->port, TYPEC_PWR_MODE_USB);
typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(status));
typec_set_vconn_role(tps->port, TPS_STATUS_TO_TYPEC_VCONN(status));
+ typec_set_orientation(tps->port, TYPEC_ORIENTATION_NONE);
tps6598x_set_data_role(tps, TPS_STATUS_TO_TYPEC_DATAROLE(status), false);
power_supply_changed(tps->psy);
@@ -450,6 +452,7 @@ static bool tps6598x_read_power_status(struct tps6598x *tps)
dev_err(tps->dev, "failed to read power status: %d\n", ret);
return false;
}
+ tps->pwr_status = pwr_status;
trace_tps6598x_power_status(pwr_status);
return true;
@@ -596,15 +599,8 @@ static const struct regmap_config tps6598x_regmap_config = {
static int tps6598x_psy_get_online(struct tps6598x *tps,
union power_supply_propval *val)
{
- int ret;
- u16 pwr_status;
-
- ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status);
- if (ret < 0)
- return ret;
-
- if (TPS_POWER_STATUS_CONNECTION(pwr_status) &&
- TPS_POWER_STATUS_SOURCESINK(pwr_status)) {
+ if (TPS_POWER_STATUS_CONNECTION(tps->pwr_status) &&
+ TPS_POWER_STATUS_SOURCESINK(tps->pwr_status)) {
val->intval = 1;
} else {
val->intval = 0;
@@ -617,15 +613,11 @@ static int tps6598x_psy_get_prop(struct power_supply *psy,
union power_supply_propval *val)
{
struct tps6598x *tps = power_supply_get_drvdata(psy);
- u16 pwr_status;
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_USB_TYPE:
- ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status);
- if (ret < 0)
- return ret;
- if (TPS_POWER_STATUS_PWROPMODE(pwr_status) == TYPEC_PWR_MODE_PD)
+ if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD)
val->intval = POWER_SUPPLY_USB_TYPE_PD;
else
val->intval = POWER_SUPPLY_USB_TYPE_C;
@@ -832,6 +824,11 @@ static int tps6598x_probe(struct i2c_client *client)
fwnode_handle_put(fwnode);
if (status & TPS_STATUS_PLUG_PRESENT) {
+ ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &tps->pwr_status);
+ if (ret < 0) {
+ dev_err(tps->dev, "failed to read power status: %d\n", ret);
+ goto err_role_put;
+ }
ret = tps6598x_connect(tps, status);
if (ret)
dev_err(&client->dev, "failed to register partner\n");
diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h
index 3dae84c524fb..527857549d69 100644
--- a/drivers/usb/typec/tipd/tps6598x.h
+++ b/drivers/usb/typec/tipd/tps6598x.h
@@ -17,6 +17,7 @@
/* TPS_REG_STATUS bits */
#define TPS_STATUS_PLUG_PRESENT BIT(0)
#define TPS_STATUS_PLUG_UPSIDE_DOWN BIT(4)
+#define TPS_STATUS_TO_UPSIDE_DOWN(s) (!!((s) & TPS_STATUS_PLUG_UPSIDE_DOWN))
#define TPS_STATUS_PORTROLE BIT(5)
#define TPS_STATUS_TO_TYPEC_PORTROLE(s) (!!((s) & TPS_STATUS_PORTROLE))
#define TPS_STATUS_DATAROLE BIT(6)
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index f0c2fa19f3e0..cbd862f9f2a1 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -949,6 +949,8 @@ static int ucsi_dr_swap(struct typec_port *port, enum typec_data_role role)
role == TYPEC_HOST))
goto out_unlock;
+ reinit_completion(&con->complete);
+
command = UCSI_SET_UOR | UCSI_CONNECTOR_NUMBER(con->num);
command |= UCSI_SET_UOR_ROLE(role);
command |= UCSI_SET_UOR_ACCEPT_ROLE_SWAPS;
@@ -956,14 +958,18 @@ static int ucsi_dr_swap(struct typec_port *port, enum typec_data_role role)
if (ret < 0)
goto out_unlock;
+ mutex_unlock(&con->lock);
+
if (!wait_for_completion_timeout(&con->complete,
- msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS)))
- ret = -ETIMEDOUT;
+ msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS)))
+ return -ETIMEDOUT;
+
+ return 0;
out_unlock:
mutex_unlock(&con->lock);
- return ret < 0 ? ret : 0;
+ return ret;
}
static int ucsi_pr_swap(struct typec_port *port, enum typec_role role)
@@ -985,6 +991,8 @@ static int ucsi_pr_swap(struct typec_port *port, enum typec_role role)
if (cur_role == role)
goto out_unlock;
+ reinit_completion(&con->complete);
+
command = UCSI_SET_PDR | UCSI_CONNECTOR_NUMBER(con->num);
command |= UCSI_SET_PDR_ROLE(role);
command |= UCSI_SET_PDR_ACCEPT_ROLE_SWAPS;
@@ -992,11 +1000,13 @@ static int ucsi_pr_swap(struct typec_port *port, enum typec_role role)
if (ret < 0)
goto out_unlock;
+ mutex_unlock(&con->lock);
+
if (!wait_for_completion_timeout(&con->complete,
- msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS))) {
- ret = -ETIMEDOUT;
- goto out_unlock;
- }
+ msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS)))
+ return -ETIMEDOUT;
+
+ mutex_lock(&con->lock);
/* Something has gone wrong while swapping the role */
if (UCSI_CONSTAT_PWR_OPMODE(con->status.flags) !=
@@ -1053,6 +1063,14 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
con->num = index + 1;
con->ucsi = ucsi;
+ cap->fwnode = ucsi_find_fwnode(con);
+ con->usb_role_sw = fwnode_usb_role_switch_get(cap->fwnode);
+ if (IS_ERR(con->usb_role_sw)) {
+ dev_err(ucsi->dev, "con%d: failed to get usb role switch\n",
+ con->num);
+ return PTR_ERR(con->usb_role_sw);
+ }
+
/* Delay other interactions with the con until registration is complete */
mutex_lock(&con->lock);
@@ -1088,7 +1106,6 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DEBUG_ACCESSORY)
*accessory = TYPEC_ACCESSORY_DEBUG;
- cap->fwnode = ucsi_find_fwnode(con);
cap->driver_data = con;
cap->ops = &ucsi_ops;
@@ -1146,13 +1163,6 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
ucsi_port_psy_changed(con);
}
- con->usb_role_sw = fwnode_usb_role_switch_get(cap->fwnode);
- if (IS_ERR(con->usb_role_sw)) {
- dev_err(ucsi->dev, "con%d: failed to get usb role switch\n",
- con->num);
- con->usb_role_sw = NULL;
- }
-
/* Only notify USB controller if partner supports USB data */
if (!(UCSI_CONSTAT_PARTNER_FLAGS(con->status.flags) & UCSI_CONSTAT_PARTNER_FLAG_USB))
u_role = USB_ROLE_NONE;
@@ -1186,6 +1196,32 @@ out_unlock:
return ret;
}
+static void ucsi_unregister_connectors(struct ucsi *ucsi)
+{
+ struct ucsi_connector *con;
+ int i;
+
+ if (!ucsi->connector)
+ return;
+
+ for (i = 0; i < ucsi->cap.num_connectors; i++) {
+ con = &ucsi->connector[i];
+
+ if (!con->wq)
+ break;
+
+ cancel_work_sync(&con->work);
+ ucsi_unregister_partner(con);
+ ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON);
+ ucsi_unregister_port_psy(con);
+ destroy_workqueue(con->wq);
+ typec_unregister_port(con->port);
+ }
+
+ kfree(ucsi->connector);
+ ucsi->connector = NULL;
+}
+
/**
* ucsi_init - Initialize UCSI interface
* @ucsi: UCSI to be initialized
@@ -1194,7 +1230,6 @@ out_unlock:
*/
static int ucsi_init(struct ucsi *ucsi)
{
- struct ucsi_connector *con;
u64 command;
int ret;
int i;
@@ -1225,7 +1260,7 @@ static int ucsi_init(struct ucsi *ucsi)
}
/* Allocate the connectors. Released in ucsi_unregister() */
- ucsi->connector = kcalloc(ucsi->cap.num_connectors + 1,
+ ucsi->connector = kcalloc(ucsi->cap.num_connectors,
sizeof(*ucsi->connector), GFP_KERNEL);
if (!ucsi->connector) {
ret = -ENOMEM;
@@ -1249,15 +1284,7 @@ static int ucsi_init(struct ucsi *ucsi)
return 0;
err_unregister:
- for (con = ucsi->connector; con->port; con++) {
- ucsi_unregister_partner(con);
- ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON);
- ucsi_unregister_port_psy(con);
- if (con->wq)
- destroy_workqueue(con->wq);
- typec_unregister_port(con->port);
- con->port = NULL;
- }
+ ucsi_unregister_connectors(ucsi);
err_reset:
memset(&ucsi->cap, 0, sizeof(ucsi->cap));
@@ -1268,12 +1295,20 @@ err:
static void ucsi_init_work(struct work_struct *work)
{
- struct ucsi *ucsi = container_of(work, struct ucsi, work);
+ struct ucsi *ucsi = container_of(work, struct ucsi, work.work);
int ret;
ret = ucsi_init(ucsi);
if (ret)
dev_err(ucsi->dev, "PPM init failed (%d)\n", ret);
+
+ if (ret == -EPROBE_DEFER) {
+ if (ucsi->work_count++ > UCSI_ROLE_SWITCH_WAIT_COUNT)
+ return;
+
+ queue_delayed_work(system_long_wq, &ucsi->work,
+ UCSI_ROLE_SWITCH_INTERVAL);
+ }
}
/**
@@ -1313,7 +1348,7 @@ struct ucsi *ucsi_create(struct device *dev, const struct ucsi_operations *ops)
if (!ucsi)
return ERR_PTR(-ENOMEM);
- INIT_WORK(&ucsi->work, ucsi_init_work);
+ INIT_DELAYED_WORK(&ucsi->work, ucsi_init_work);
mutex_init(&ucsi->ppm_lock);
ucsi->dev = dev;
ucsi->ops = ops;
@@ -1348,7 +1383,7 @@ int ucsi_register(struct ucsi *ucsi)
if (!ucsi->version)
return -ENODEV;
- queue_work(system_long_wq, &ucsi->work);
+ queue_delayed_work(system_long_wq, &ucsi->work, 0);
return 0;
}
@@ -1363,26 +1398,14 @@ EXPORT_SYMBOL_GPL(ucsi_register);
void ucsi_unregister(struct ucsi *ucsi)
{
u64 cmd = UCSI_SET_NOTIFICATION_ENABLE;
- int i;
/* Make sure that we are not in the middle of driver initialization */
- cancel_work_sync(&ucsi->work);
+ cancel_delayed_work_sync(&ucsi->work);
/* Disable notifications */
ucsi->ops->async_write(ucsi, UCSI_CONTROL, &cmd, sizeof(cmd));
- for (i = 0; i < ucsi->cap.num_connectors; i++) {
- cancel_work_sync(&ucsi->connector[i].work);
- ucsi_unregister_partner(&ucsi->connector[i]);
- ucsi_unregister_altmodes(&ucsi->connector[i],
- UCSI_RECIPIENT_CON);
- ucsi_unregister_port_psy(&ucsi->connector[i]);
- if (ucsi->connector[i].wq)
- destroy_workqueue(ucsi->connector[i].wq);
- typec_unregister_port(ucsi->connector[i].port);
- }
-
- kfree(ucsi->connector);
+ ucsi_unregister_connectors(ucsi);
}
EXPORT_SYMBOL_GPL(ucsi_unregister);
diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
index 280f1e1bda2c..8eb391e3e592 100644
--- a/drivers/usb/typec/ucsi/ucsi.h
+++ b/drivers/usb/typec/ucsi/ucsi.h
@@ -287,7 +287,11 @@ struct ucsi {
struct ucsi_capability cap;
struct ucsi_connector *connector;
- struct work_struct work;
+ struct delayed_work work;
+ int work_count;
+#define UCSI_ROLE_SWITCH_RETRY_PER_HZ 10
+#define UCSI_ROLE_SWITCH_INTERVAL (HZ / UCSI_ROLE_SWITCH_RETRY_PER_HZ)
+#define UCSI_ROLE_SWITCH_WAIT_COUNT (10 * UCSI_ROLE_SWITCH_RETRY_PER_HZ)
/* PPM Communication lock */
struct mutex ppm_lock;
diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c
index 6771f05e32c2..8873c1644a29 100644
--- a/drivers/usb/typec/ucsi/ucsi_acpi.c
+++ b/drivers/usb/typec/ucsi/ucsi_acpi.c
@@ -19,7 +19,7 @@
struct ucsi_acpi {
struct device *dev;
struct ucsi *ucsi;
- void __iomem *base;
+ void *base;
struct completion complete;
unsigned long flags;
guid_t guid;
@@ -51,7 +51,7 @@ static int ucsi_acpi_read(struct ucsi *ucsi, unsigned int offset,
if (ret)
return ret;
- memcpy(val, (const void __force *)(ua->base + offset), val_len);
+ memcpy(val, ua->base + offset, val_len);
return 0;
}
@@ -61,7 +61,7 @@ static int ucsi_acpi_async_write(struct ucsi *ucsi, unsigned int offset,
{
struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi);
- memcpy((void __force *)(ua->base + offset), val, val_len);
+ memcpy(ua->base + offset, val, val_len);
return ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_WRITE);
}
@@ -132,20 +132,9 @@ static int ucsi_acpi_probe(struct platform_device *pdev)
return -ENODEV;
}
- /* This will make sure we can use ioremap() */
- status = acpi_release_memory(ACPI_HANDLE(&pdev->dev), res, 1);
- if (ACPI_FAILURE(status))
- return -ENOMEM;
-
- /*
- * NOTE: The memory region for the data structures is used also in an
- * operation region, which means ACPI has already reserved it. Therefore
- * it can not be requested here, and we can not use
- * devm_ioremap_resource().
- */
- ua->base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
- if (!ua->base)
- return -ENOMEM;
+ ua->base = devm_memremap(&pdev->dev, res->start, resource_size(res), MEMREMAP_WB);
+ if (IS_ERR(ua->base))
+ return PTR_ERR(ua->base);
ret = guid_parse(UCSI_DSM_UUID, &ua->guid);
if (ret)
diff --git a/drivers/usb/typec/wusb3801.c b/drivers/usb/typec/wusb3801.c
new file mode 100644
index 000000000000..e63509f8b01e
--- /dev/null
+++ b/drivers/usb/typec/wusb3801.c
@@ -0,0 +1,437 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Willsemi WUSB3801 Type-C port controller driver
+ *
+ * Copyright (C) 2022 Samuel Holland <samuel@sholland.org>
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/typec.h>
+
+#define WUSB3801_REG_DEVICE_ID 0x01
+#define WUSB3801_REG_CTRL0 0x02
+#define WUSB3801_REG_INT 0x03
+#define WUSB3801_REG_STAT 0x04
+#define WUSB3801_REG_CTRL1 0x05
+#define WUSB3801_REG_TEST00 0x06
+#define WUSB3801_REG_TEST01 0x07
+#define WUSB3801_REG_TEST02 0x08
+#define WUSB3801_REG_TEST03 0x09
+#define WUSB3801_REG_TEST04 0x0a
+#define WUSB3801_REG_TEST05 0x0b
+#define WUSB3801_REG_TEST06 0x0c
+#define WUSB3801_REG_TEST07 0x0d
+#define WUSB3801_REG_TEST08 0x0e
+#define WUSB3801_REG_TEST09 0x0f
+#define WUSB3801_REG_TEST0A 0x10
+#define WUSB3801_REG_TEST0B 0x11
+#define WUSB3801_REG_TEST0C 0x12
+#define WUSB3801_REG_TEST0D 0x13
+#define WUSB3801_REG_TEST0E 0x14
+#define WUSB3801_REG_TEST0F 0x15
+#define WUSB3801_REG_TEST10 0x16
+#define WUSB3801_REG_TEST11 0x17
+#define WUSB3801_REG_TEST12 0x18
+
+#define WUSB3801_DEVICE_ID_VERSION_ID GENMASK(7, 3)
+#define WUSB3801_DEVICE_ID_VENDOR_ID GENMASK(2, 0)
+
+#define WUSB3801_CTRL0_DIS_ACC_SUPPORT BIT(7)
+#define WUSB3801_CTRL0_TRY GENMASK(6, 5)
+#define WUSB3801_CTRL0_TRY_NONE (0x0 << 5)
+#define WUSB3801_CTRL0_TRY_SNK (0x1 << 5)
+#define WUSB3801_CTRL0_TRY_SRC (0x2 << 5)
+#define WUSB3801_CTRL0_CURRENT GENMASK(4, 3) /* SRC */
+#define WUSB3801_CTRL0_CURRENT_DEFAULT (0x0 << 3)
+#define WUSB3801_CTRL0_CURRENT_1_5A (0x1 << 3)
+#define WUSB3801_CTRL0_CURRENT_3_0A (0x2 << 3)
+#define WUSB3801_CTRL0_ROLE GENMASK(2, 1)
+#define WUSB3801_CTRL0_ROLE_SNK (0x0 << 1)
+#define WUSB3801_CTRL0_ROLE_SRC (0x1 << 1)
+#define WUSB3801_CTRL0_ROLE_DRP (0x2 << 1)
+#define WUSB3801_CTRL0_INT_MASK BIT(0)
+
+#define WUSB3801_INT_ATTACHED BIT(0)
+#define WUSB3801_INT_DETACHED BIT(1)
+
+#define WUSB3801_STAT_VBUS_DETECTED BIT(7)
+#define WUSB3801_STAT_CURRENT GENMASK(6, 5) /* SNK */
+#define WUSB3801_STAT_CURRENT_STANDBY (0x0 << 5)
+#define WUSB3801_STAT_CURRENT_DEFAULT (0x1 << 5)
+#define WUSB3801_STAT_CURRENT_1_5A (0x2 << 5)
+#define WUSB3801_STAT_CURRENT_3_0A (0x3 << 5)
+#define WUSB3801_STAT_PARTNER GENMASK(4, 2)
+#define WUSB3801_STAT_PARTNER_STANDBY (0x0 << 2)
+#define WUSB3801_STAT_PARTNER_SNK (0x1 << 2)
+#define WUSB3801_STAT_PARTNER_SRC (0x2 << 2)
+#define WUSB3801_STAT_PARTNER_AUDIO (0x3 << 2)
+#define WUSB3801_STAT_PARTNER_DEBUG (0x4 << 2)
+#define WUSB3801_STAT_ORIENTATION GENMASK(1, 0)
+#define WUSB3801_STAT_ORIENTATION_NONE (0x0 << 0)
+#define WUSB3801_STAT_ORIENTATION_CC1 (0x1 << 0)
+#define WUSB3801_STAT_ORIENTATION_CC2 (0x2 << 0)
+#define WUSB3801_STAT_ORIENTATION_BOTH (0x3 << 0)
+
+#define WUSB3801_CTRL1_SM_RESET BIT(0)
+
+#define WUSB3801_TEST01_VENDOR_SUB_ID (BIT(8) | BIT(6))
+
+#define WUSB3801_TEST02_FORCE_ERR_RCY BIT(8)
+
+#define WUSB3801_TEST0A_WAIT_VBUS BIT(5)
+
+struct wusb3801 {
+ struct typec_capability cap;
+ struct device *dev;
+ struct typec_partner *partner;
+ struct typec_port *port;
+ struct regmap *regmap;
+ struct regulator *vbus_supply;
+ unsigned int partner_type;
+ enum typec_port_type port_type;
+ enum typec_pwr_opmode pwr_opmode;
+ bool vbus_on;
+};
+
+static enum typec_role wusb3801_get_default_role(struct wusb3801 *wusb3801)
+{
+ switch (wusb3801->port_type) {
+ case TYPEC_PORT_SRC:
+ return TYPEC_SOURCE;
+ case TYPEC_PORT_SNK:
+ return TYPEC_SINK;
+ case TYPEC_PORT_DRP:
+ default:
+ if (wusb3801->cap.prefer_role == TYPEC_SOURCE)
+ return TYPEC_SOURCE;
+ return TYPEC_SINK;
+ }
+}
+
+static int wusb3801_map_port_type(enum typec_port_type type)
+{
+ switch (type) {
+ case TYPEC_PORT_SRC:
+ return WUSB3801_CTRL0_ROLE_SRC;
+ case TYPEC_PORT_SNK:
+ return WUSB3801_CTRL0_ROLE_SNK;
+ case TYPEC_PORT_DRP:
+ default:
+ return WUSB3801_CTRL0_ROLE_DRP;
+ }
+}
+
+static int wusb3801_map_pwr_opmode(enum typec_pwr_opmode mode)
+{
+ switch (mode) {
+ case TYPEC_PWR_MODE_USB:
+ default:
+ return WUSB3801_CTRL0_CURRENT_DEFAULT;
+ case TYPEC_PWR_MODE_1_5A:
+ return WUSB3801_CTRL0_CURRENT_1_5A;
+ case TYPEC_PWR_MODE_3_0A:
+ return WUSB3801_CTRL0_CURRENT_3_0A;
+ }
+}
+
+static unsigned int wusb3801_map_try_role(int role)
+{
+ switch (role) {
+ case TYPEC_NO_PREFERRED_ROLE:
+ default:
+ return WUSB3801_CTRL0_TRY_NONE;
+ case TYPEC_SINK:
+ return WUSB3801_CTRL0_TRY_SNK;
+ case TYPEC_SOURCE:
+ return WUSB3801_CTRL0_TRY_SRC;
+ }
+}
+
+static enum typec_orientation wusb3801_unmap_orientation(unsigned int status)
+{
+ switch (status & WUSB3801_STAT_ORIENTATION) {
+ case WUSB3801_STAT_ORIENTATION_NONE:
+ case WUSB3801_STAT_ORIENTATION_BOTH:
+ default:
+ return TYPEC_ORIENTATION_NONE;
+ case WUSB3801_STAT_ORIENTATION_CC1:
+ return TYPEC_ORIENTATION_NORMAL;
+ case WUSB3801_STAT_ORIENTATION_CC2:
+ return TYPEC_ORIENTATION_REVERSE;
+ }
+}
+
+static enum typec_pwr_opmode wusb3801_unmap_pwr_opmode(unsigned int status)
+{
+ switch (status & WUSB3801_STAT_CURRENT) {
+ case WUSB3801_STAT_CURRENT_STANDBY:
+ case WUSB3801_STAT_CURRENT_DEFAULT:
+ default:
+ return TYPEC_PWR_MODE_USB;
+ case WUSB3801_STAT_CURRENT_1_5A:
+ return TYPEC_PWR_MODE_1_5A;
+ case WUSB3801_STAT_CURRENT_3_0A:
+ return TYPEC_PWR_MODE_3_0A;
+ }
+}
+
+static int wusb3801_try_role(struct typec_port *port, int role)
+{
+ struct wusb3801 *wusb3801 = typec_get_drvdata(port);
+
+ return regmap_update_bits(wusb3801->regmap, WUSB3801_REG_CTRL0,
+ WUSB3801_CTRL0_TRY,
+ wusb3801_map_try_role(role));
+}
+
+static int wusb3801_port_type_set(struct typec_port *port,
+ enum typec_port_type type)
+{
+ struct wusb3801 *wusb3801 = typec_get_drvdata(port);
+ int ret;
+
+ ret = regmap_update_bits(wusb3801->regmap, WUSB3801_REG_CTRL0,
+ WUSB3801_CTRL0_ROLE,
+ wusb3801_map_port_type(type));
+ if (ret)
+ return ret;
+
+ wusb3801->port_type = type;
+
+ return 0;
+}
+
+static const struct typec_operations wusb3801_typec_ops = {
+ .try_role = wusb3801_try_role,
+ .port_type_set = wusb3801_port_type_set,
+};
+
+static int wusb3801_hw_init(struct wusb3801 *wusb3801)
+{
+ return regmap_write(wusb3801->regmap, WUSB3801_REG_CTRL0,
+ wusb3801_map_try_role(wusb3801->cap.prefer_role) |
+ wusb3801_map_pwr_opmode(wusb3801->pwr_opmode) |
+ wusb3801_map_port_type(wusb3801->port_type));
+}
+
+static void wusb3801_hw_update(struct wusb3801 *wusb3801)
+{
+ struct typec_port *port = wusb3801->port;
+ struct device *dev = wusb3801->dev;
+ unsigned int partner_type, status;
+ int ret;
+
+ ret = regmap_read(wusb3801->regmap, WUSB3801_REG_STAT, &status);
+ if (ret) {
+ dev_warn(dev, "Failed to read port status: %d\n", ret);
+ status = 0;
+ }
+ dev_dbg(dev, "status = 0x%02x\n", status);
+
+ partner_type = status & WUSB3801_STAT_PARTNER;
+
+ if (partner_type == WUSB3801_STAT_PARTNER_SNK) {
+ if (!wusb3801->vbus_on) {
+ ret = regulator_enable(wusb3801->vbus_supply);
+ if (ret)
+ dev_warn(dev, "Failed to enable VBUS: %d\n", ret);
+ wusb3801->vbus_on = true;
+ }
+ } else {
+ if (wusb3801->vbus_on) {
+ regulator_disable(wusb3801->vbus_supply);
+ wusb3801->vbus_on = false;
+ }
+ }
+
+ if (partner_type != wusb3801->partner_type) {
+ struct typec_partner_desc desc = {};
+ enum typec_data_role data_role;
+ enum typec_role pwr_role = wusb3801_get_default_role(wusb3801);
+
+ switch (partner_type) {
+ case WUSB3801_STAT_PARTNER_STANDBY:
+ break;
+ case WUSB3801_STAT_PARTNER_SNK:
+ pwr_role = TYPEC_SOURCE;
+ break;
+ case WUSB3801_STAT_PARTNER_SRC:
+ pwr_role = TYPEC_SINK;
+ break;
+ case WUSB3801_STAT_PARTNER_AUDIO:
+ desc.accessory = TYPEC_ACCESSORY_AUDIO;
+ break;
+ case WUSB3801_STAT_PARTNER_DEBUG:
+ desc.accessory = TYPEC_ACCESSORY_DEBUG;
+ break;
+ }
+
+ if (wusb3801->partner) {
+ typec_unregister_partner(wusb3801->partner);
+ wusb3801->partner = NULL;
+ }
+
+ if (partner_type != WUSB3801_STAT_PARTNER_STANDBY) {
+ wusb3801->partner = typec_register_partner(port, &desc);
+ if (IS_ERR(wusb3801->partner))
+ dev_err(dev, "Failed to register partner: %ld\n",
+ PTR_ERR(wusb3801->partner));
+ }
+
+ data_role = pwr_role == TYPEC_SOURCE ? TYPEC_HOST : TYPEC_DEVICE;
+ typec_set_data_role(port, data_role);
+ typec_set_pwr_role(port, pwr_role);
+ typec_set_vconn_role(port, pwr_role);
+ }
+
+ typec_set_pwr_opmode(wusb3801->port,
+ partner_type == WUSB3801_STAT_PARTNER_SRC
+ ? wusb3801_unmap_pwr_opmode(status)
+ : wusb3801->pwr_opmode);
+ typec_set_orientation(wusb3801->port,
+ wusb3801_unmap_orientation(status));
+
+ wusb3801->partner_type = partner_type;
+}
+
+static irqreturn_t wusb3801_irq(int irq, void *data)
+{
+ struct wusb3801 *wusb3801 = data;
+ unsigned int dummy;
+
+ /*
+ * The interrupt register must be read in order to clear the IRQ,
+ * but all of the useful information is in the status register.
+ */
+ regmap_read(wusb3801->regmap, WUSB3801_REG_INT, &dummy);
+
+ wusb3801_hw_update(wusb3801);
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_config config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = WUSB3801_REG_TEST12,
+};
+
+static int wusb3801_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct fwnode_handle *connector;
+ struct wusb3801 *wusb3801;
+ const char *cap_str;
+ int ret;
+
+ wusb3801 = devm_kzalloc(dev, sizeof(*wusb3801), GFP_KERNEL);
+ if (!wusb3801)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, wusb3801);
+
+ wusb3801->dev = dev;
+
+ wusb3801->regmap = devm_regmap_init_i2c(client, &config);
+ if (IS_ERR(wusb3801->regmap))
+ return PTR_ERR(wusb3801->regmap);
+
+ wusb3801->vbus_supply = devm_regulator_get(dev, "vbus");
+ if (IS_ERR(wusb3801->vbus_supply))
+ return PTR_ERR(wusb3801->vbus_supply);
+
+ connector = device_get_named_child_node(dev, "connector");
+ if (!connector)
+ return -ENODEV;
+
+ ret = typec_get_fw_cap(&wusb3801->cap, connector);
+ if (ret)
+ goto err_put_connector;
+ wusb3801->port_type = wusb3801->cap.type;
+
+ ret = fwnode_property_read_string(connector, "typec-power-opmode", &cap_str);
+ if (ret)
+ goto err_put_connector;
+
+ ret = typec_find_pwr_opmode(cap_str);
+ if (ret < 0 || ret == TYPEC_PWR_MODE_PD)
+ goto err_put_connector;
+ wusb3801->pwr_opmode = ret;
+
+ /* Initialize the hardware with the devicetree settings. */
+ ret = wusb3801_hw_init(wusb3801);
+ if (ret)
+ return ret;
+
+ wusb3801->cap.revision = USB_TYPEC_REV_1_2;
+ wusb3801->cap.accessory[0] = TYPEC_ACCESSORY_AUDIO;
+ wusb3801->cap.accessory[1] = TYPEC_ACCESSORY_DEBUG;
+ wusb3801->cap.orientation_aware = true;
+ wusb3801->cap.driver_data = wusb3801;
+ wusb3801->cap.ops = &wusb3801_typec_ops;
+
+ wusb3801->port = typec_register_port(dev, &wusb3801->cap);
+ if (IS_ERR(wusb3801->port)) {
+ ret = PTR_ERR(wusb3801->port);
+ goto err_put_connector;
+ }
+
+ /* Initialize the port attributes from the hardware state. */
+ wusb3801_hw_update(wusb3801);
+
+ ret = request_threaded_irq(client->irq, NULL, wusb3801_irq,
+ IRQF_ONESHOT, dev_name(dev), wusb3801);
+ if (ret)
+ goto err_unregister_port;
+
+ fwnode_handle_put(connector);
+
+ return 0;
+
+err_unregister_port:
+ typec_unregister_port(wusb3801->port);
+err_put_connector:
+ fwnode_handle_put(connector);
+
+ return ret;
+}
+
+static int wusb3801_remove(struct i2c_client *client)
+{
+ struct wusb3801 *wusb3801 = i2c_get_clientdata(client);
+
+ free_irq(client->irq, wusb3801);
+
+ if (wusb3801->partner)
+ typec_unregister_partner(wusb3801->partner);
+ typec_unregister_port(wusb3801->port);
+
+ if (wusb3801->vbus_on)
+ regulator_disable(wusb3801->vbus_supply);
+
+ return 0;
+}
+
+static const struct of_device_id wusb3801_of_match[] = {
+ { .compatible = "willsemi,wusb3801" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, wusb3801_of_match);
+
+static struct i2c_driver wusb3801_driver = {
+ .probe_new = wusb3801_probe,
+ .remove = wusb3801_remove,
+ .driver = {
+ .name = "wusb3801",
+ .of_match_table = wusb3801_of_match,
+ },
+};
+
+module_i2c_driver(wusb3801_driver);
+
+MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
+MODULE_DESCRIPTION("Willsemi WUSB3801 Type-C port controller driver");
+MODULE_LICENSE("GPL");