diff options
| author | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2022-08-02 10:06:12 -0700 |
|---|---|---|
| committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2022-08-02 10:06:12 -0700 |
| commit | 8bb5e7f4dcd9b9ef22a3ea25c9066a8a968f12dd (patch) | |
| tree | 0f1383880607a227142f9388a066959926233ff1 /drivers/usb/typec | |
| parent | 2a96271fb66c499e4a89d76a89d3d01170c10bef (diff) | |
| parent | 7c744d00990ea999d27f306f6db5ccb61b1304b2 (diff) | |
Merge branch 'next' into for-linus
Prepare input updates for 5.20 (or 6.0) merge window.
Diffstat (limited to 'drivers/usb/typec')
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, ®val, 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, ®val, 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, ®val, 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"); |
