mirror of
https://github.com/brain-hackers/linux-brain.git
synced 2024-06-09 23:36:23 +09:00
Merge remote-tracking branch 'origin/usb/typec' into usb/next
* origin/usb/typec: (11 commits) usb: typec: tcpci: add pm ops to disable irq MLK-16013-43 usb: typec: tcpm: add extcon support for data role switch MLK-16013-17 usb: typec: tcpm: set cc when attached MLK-22452-2 usb: typec: mux: add nxp cbtl04gp super speed mux compatible MLK-22452-1 usb: typec: mux: add reset gpio for gpio switch ...
This commit is contained in:
commit
8baeb551e7
30
Documentation/devicetree/bindings/usb/typec-switch-gpio.txt
Normal file
30
Documentation/devicetree/bindings/usb/typec-switch-gpio.txt
Normal file
|
@ -0,0 +1,30 @@
|
|||
Typec orientation switch via a GPIO
|
||||
-----------------------------------
|
||||
|
||||
Required properties:
|
||||
- compatible: should be set one of following:
|
||||
- "nxp,ptn36043" for NXP Type-C SuperSpeed active switch.
|
||||
|
||||
- gpios: the GPIO used to switch the super speed active channel,
|
||||
GPIO_ACTIVE_HIGH: GPIO state high for cc1;
|
||||
GPIO_ACTIVE_LOW: GPIO state low for cc1.
|
||||
- orientation-switch: must be present.
|
||||
|
||||
Required sub-node:
|
||||
- port: specify the remote endpoint of typec switch consumer.
|
||||
|
||||
Example:
|
||||
|
||||
ptn36043 {
|
||||
compatible = "nxp,ptn36043";
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pinctrl_ss_sel>;
|
||||
gpios = <&gpio3 15 GPIO_ACTIVE_HIGH>;
|
||||
orientation-switch;
|
||||
|
||||
port {
|
||||
usb3_data_ss: endpoint {
|
||||
remote-endpoint = <&typec_con_ss>;
|
||||
};
|
||||
};
|
||||
};
|
|
@ -9,4 +9,10 @@ config TYPEC_MUX_PI3USB30532
|
|||
Say Y or M if your system has a Pericom PI3USB30532 Type-C cross
|
||||
switch / mux chip found on some devices with a Type-C port.
|
||||
|
||||
config TYPEC_SWITCH_GPIO
|
||||
tristate "Simple Super Speed Active Switch via GPIO"
|
||||
help
|
||||
Say Y or M if your system has a typec super speed channel
|
||||
switch via a simple GPIO control.
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o
|
||||
obj-$(CONFIG_TYPEC_SWITCH_GPIO) += gpio-switch.o
|
||||
|
|
117
drivers/usb/typec/mux/gpio-switch.c
Normal file
117
drivers/usb/typec/mux/gpio-switch.c
Normal file
|
@ -0,0 +1,117 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/**
|
||||
* gpio-switch.c - typec switch via a simple GPIO control.
|
||||
*
|
||||
* Copyright 2019 NXP
|
||||
* Author: Jun Li <jun.li@nxp.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/usb/typec_mux.h>
|
||||
|
||||
struct gpio_typec_switch {
|
||||
struct typec_switch *sw;
|
||||
struct mutex lock;
|
||||
struct gpio_desc *ss_sel;
|
||||
struct gpio_desc *ss_reset;
|
||||
};
|
||||
|
||||
static int switch_gpio_set(struct typec_switch *sw,
|
||||
enum typec_orientation orientation)
|
||||
{
|
||||
struct gpio_typec_switch *gpio_sw = typec_switch_get_drvdata(sw);
|
||||
|
||||
mutex_lock(&gpio_sw->lock);
|
||||
|
||||
switch (orientation) {
|
||||
case TYPEC_ORIENTATION_NORMAL:
|
||||
gpiod_set_value_cansleep(gpio_sw->ss_sel, 1);
|
||||
break;
|
||||
case TYPEC_ORIENTATION_REVERSE:
|
||||
gpiod_set_value_cansleep(gpio_sw->ss_sel, 0);
|
||||
break;
|
||||
case TYPEC_ORIENTATION_NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&gpio_sw->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int typec_switch_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_typec_switch *gpio_sw;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct typec_switch_desc sw_desc;
|
||||
|
||||
gpio_sw = devm_kzalloc(dev, sizeof(*gpio_sw), GFP_KERNEL);
|
||||
if (!gpio_sw)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, gpio_sw);
|
||||
|
||||
sw_desc.drvdata = gpio_sw;
|
||||
sw_desc.fwnode = dev->fwnode;
|
||||
sw_desc.set = switch_gpio_set;
|
||||
mutex_init(&gpio_sw->lock);
|
||||
|
||||
/* Get the super speed mux reset GPIO, it's optional */
|
||||
gpio_sw->ss_reset = devm_gpiod_get_optional(dev, "reset",
|
||||
GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(gpio_sw->ss_reset))
|
||||
return PTR_ERR(gpio_sw->ss_reset);
|
||||
|
||||
if (gpio_sw->ss_reset)
|
||||
usleep_range(700, 1000);
|
||||
|
||||
/* Get the super speed active channel selection GPIO */
|
||||
gpio_sw->ss_sel = devm_gpiod_get(dev, "switch", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(gpio_sw->ss_sel))
|
||||
return PTR_ERR(gpio_sw->ss_sel);
|
||||
|
||||
gpio_sw->sw = typec_switch_register(dev, &sw_desc);
|
||||
if (IS_ERR(gpio_sw->sw)) {
|
||||
dev_err(dev, "Error registering typec switch: %ld\n", PTR_ERR(gpio_sw->sw));
|
||||
return PTR_ERR(gpio_sw->sw);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int typec_switch_gpio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_typec_switch *gpio_sw = platform_get_drvdata(pdev);
|
||||
|
||||
typec_switch_unregister(gpio_sw->sw);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id of_typec_switch_gpio_match[] = {
|
||||
{ .compatible = "nxp,ptn36043" },
|
||||
{ .compatible = "nxp,cbtl04gp" },
|
||||
{ /* Sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_typec_switch_gpio_match);
|
||||
|
||||
static struct platform_driver typec_switch_gpio_driver = {
|
||||
.probe = typec_switch_gpio_probe,
|
||||
.remove = typec_switch_gpio_remove,
|
||||
.driver = {
|
||||
.name = "typec-switch-gpio",
|
||||
.of_match_table = of_typec_switch_gpio_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(typec_switch_gpio_driver);
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("TypeC Super Speed Switch GPIO driver");
|
||||
MODULE_AUTHOR("Jun Li <jun.li@nxp.com>");
|
|
@ -10,6 +10,7 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/usb/pd.h>
|
||||
|
@ -108,9 +109,6 @@ static int tcpci_start_toggling(struct tcpc_dev *tcpc,
|
|||
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
|
||||
unsigned int reg = TCPC_ROLE_CTRL_DRP;
|
||||
|
||||
if (port_type != TYPEC_PORT_DRP)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* Handle vendor drp toggling */
|
||||
if (tcpci->data->start_drp_toggling) {
|
||||
ret = tcpci->data->start_drp_toggling(tcpci, tcpci->data, cc);
|
||||
|
@ -566,6 +564,7 @@ static int tcpci_probe(struct i2c_client *client,
|
|||
if (IS_ERR(chip->tcpci))
|
||||
return PTR_ERR(chip->tcpci);
|
||||
|
||||
irq_set_status_flags(client->irq, IRQ_DISABLE_UNLAZY);
|
||||
err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
|
||||
_tcpci_irq,
|
||||
IRQF_ONESHOT | IRQF_TRIGGER_LOW,
|
||||
|
@ -575,6 +574,8 @@ static int tcpci_probe(struct i2c_client *client,
|
|||
return err;
|
||||
}
|
||||
|
||||
device_set_wakeup_capable(chip->tcpci->dev, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -583,10 +584,40 @@ static int tcpci_remove(struct i2c_client *client)
|
|||
struct tcpci_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
tcpci_unregister_port(chip->tcpci);
|
||||
irq_clear_status_flags(client->irq, IRQ_DISABLE_UNLAZY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused tcpci_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *i2c = to_i2c_client(dev);
|
||||
|
||||
if (device_may_wakeup(dev))
|
||||
enable_irq_wake(i2c->irq);
|
||||
else
|
||||
disable_irq(i2c->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int __maybe_unused tcpci_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *i2c = to_i2c_client(dev);
|
||||
|
||||
if (device_may_wakeup(dev))
|
||||
disable_irq_wake(i2c->irq);
|
||||
else
|
||||
enable_irq(i2c->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops tcpci_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(tcpci_suspend, tcpci_resume)
|
||||
};
|
||||
|
||||
static const struct i2c_device_id tcpci_id[] = {
|
||||
{ "tcpci", 0 },
|
||||
{ }
|
||||
|
@ -604,6 +635,7 @@ MODULE_DEVICE_TABLE(of, tcpci_of_match);
|
|||
static struct i2c_driver tcpci_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "tcpci",
|
||||
.pm = &tcpci_pm_ops,
|
||||
.of_match_table = of_match_ptr(tcpci_of_match),
|
||||
},
|
||||
.probe = tcpci_probe,
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
#include <linux/usb/tcpm.h>
|
||||
#include <linux/usb/typec_altmode.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/extcon.h>
|
||||
#include <linux/extcon-provider.h>
|
||||
|
||||
#define FOREACH_STATE(S) \
|
||||
S(INVALID_STATE), \
|
||||
|
@ -139,6 +141,12 @@ static const char * const tcpm_states[] = {
|
|||
FOREACH_STATE(GENERATE_STRING)
|
||||
};
|
||||
|
||||
static const unsigned int tcpm_extcon_cable[] = {
|
||||
EXTCON_USB_HOST,
|
||||
EXTCON_USB,
|
||||
EXTCON_NONE,
|
||||
};
|
||||
|
||||
enum vdm_states {
|
||||
VDM_STATE_ERR_BUSY = -3,
|
||||
VDM_STATE_ERR_SEND = -2,
|
||||
|
@ -193,6 +201,7 @@ struct pd_pps_data {
|
|||
|
||||
struct tcpm_port {
|
||||
struct device *dev;
|
||||
struct extcon_dev *edev;
|
||||
|
||||
struct mutex lock; /* tcpm state machine lock */
|
||||
struct workqueue_struct *wq;
|
||||
|
@ -670,6 +679,20 @@ static int tcpm_mux_set(struct tcpm_port *port, int state,
|
|||
ret = usb_role_switch_set_role(port->role_sw, usb_role);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else if (port->edev) {
|
||||
if (usb_role == USB_ROLE_NONE) {
|
||||
extcon_set_state_sync(port->edev, EXTCON_USB_HOST,
|
||||
false);
|
||||
extcon_set_state_sync(port->edev, EXTCON_USB, false);
|
||||
} else if (usb_role == USB_ROLE_DEVICE) {
|
||||
extcon_set_state_sync(port->edev, EXTCON_USB_HOST,
|
||||
false);
|
||||
extcon_set_state_sync(port->edev, EXTCON_USB, true);
|
||||
} else {
|
||||
extcon_set_state_sync(port->edev, EXTCON_USB, false);
|
||||
extcon_set_state_sync(port->edev, EXTCON_USB_HOST,
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
return typec_set_mode(port->typec_port, state);
|
||||
|
@ -2623,6 +2646,8 @@ static int tcpm_src_attach(struct tcpm_port *port)
|
|||
if (port->attached)
|
||||
return 0;
|
||||
|
||||
tcpm_set_cc(port, tcpm_rp_cc(port));
|
||||
|
||||
ret = tcpm_set_polarity(port, polarity);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
@ -2744,6 +2769,8 @@ static int tcpm_snk_attach(struct tcpm_port *port)
|
|||
if (port->attached)
|
||||
return 0;
|
||||
|
||||
tcpm_set_cc(port, TYPEC_CC_RD);
|
||||
|
||||
ret = tcpm_set_polarity(port, port->cc2 != TYPEC_CC_OPEN ?
|
||||
TYPEC_POLARITY_CC2 : TYPEC_POLARITY_CC1);
|
||||
if (ret < 0)
|
||||
|
@ -3146,7 +3173,11 @@ static void run_state_machine(struct tcpm_port *port)
|
|||
ret = tcpm_snk_attach(port);
|
||||
if (ret < 0)
|
||||
tcpm_set_state(port, SNK_UNATTACHED, 0);
|
||||
else
|
||||
else if (port->port_type == TYPEC_PORT_SRC &&
|
||||
port->typec_caps.data == TYPEC_PORT_DRD) {
|
||||
tcpm_typec_connect(port);
|
||||
tcpm_log(port, "Keep at SNK_ATTACHED for USB data.");
|
||||
} else
|
||||
tcpm_set_state(port, SNK_STARTUP, 0);
|
||||
break;
|
||||
case SNK_STARTUP:
|
||||
|
@ -4744,7 +4775,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
|
|||
mutex_init(&port->lock);
|
||||
mutex_init(&port->swap_lock);
|
||||
|
||||
port->wq = create_singlethread_workqueue(dev_name(dev));
|
||||
port->wq = create_freezable_workqueue(dev_name(dev));
|
||||
if (!port->wq)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
INIT_DELAYED_WORK(&port->state_machine, tcpm_state_machine_work);
|
||||
|
@ -4820,6 +4851,19 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
|
|||
}
|
||||
}
|
||||
|
||||
port->edev = devm_extcon_dev_allocate(port->dev, tcpm_extcon_cable);
|
||||
if (IS_ERR(port->edev)) {
|
||||
dev_err(port->dev, "failed to allocate extcon dev.\n");
|
||||
err = -ENOMEM;
|
||||
goto out_role_sw_put;
|
||||
}
|
||||
|
||||
err = devm_extcon_dev_register(port->dev, port->edev);
|
||||
if (err) {
|
||||
dev_err(port->dev, "failed to register extcon dev.\n");
|
||||
goto out_role_sw_put;
|
||||
}
|
||||
|
||||
mutex_lock(&port->lock);
|
||||
tcpm_init(port);
|
||||
mutex_unlock(&port->lock);
|
||||
|
@ -4840,6 +4884,9 @@ void tcpm_unregister_port(struct tcpm_port *port)
|
|||
{
|
||||
int i;
|
||||
|
||||
cancel_delayed_work_sync(&port->state_machine);
|
||||
cancel_delayed_work_sync(&port->vdm_state_machine);
|
||||
|
||||
tcpm_reset_port(port);
|
||||
for (i = 0; i < ARRAY_SIZE(port->port_altmode); i++)
|
||||
typec_unregister_altmode(port->port_altmode[i]);
|
||||
|
|
Loading…
Reference in New Issue
Block a user