This patch adds an extcon driver for the TI TUSB320 USB Type-C device. This can be used to detect whether the port is configured as a downstream or upstream facing port.
Signed-off-by: Michael Auchter <[email protected]> --- drivers/extcon/Kconfig | 7 ++ drivers/extcon/Makefile | 1 + drivers/extcon/extcon-usbc-tusb320.c | 180 +++++++++++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 drivers/extcon/extcon-usbc-tusb320.c diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index aac507bff135..241acaf8b882 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -186,4 +186,11 @@ config EXTCON_USBC_CROS_EC Say Y here to enable USB Type C cable detection extcon support when using Chrome OS EC based USB Type-C ports. +config EXTCON_USBC_TUSB320 + tristate "TI TUSB320 USB-C extcon support" + depends on GPIOLIB || COMPILE_TEST + help + Say Y here to enable support for USB Type C cable detection extcon + support using a TUSB320. + endif diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 52096fd8a216..fe10a1b7d18b 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -25,3 +25,4 @@ obj-$(CONFIG_EXTCON_RT8973A) += extcon-rt8973a.o obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o obj-$(CONFIG_EXTCON_USB_GPIO) += extcon-usb-gpio.o obj-$(CONFIG_EXTCON_USBC_CROS_EC) += extcon-usbc-cros-ec.o +obj-$(CONFIG_EXTCON_USBC_TUSB320) += extcon-usbc-tusb320.o diff --git a/drivers/extcon/extcon-usbc-tusb320.c b/drivers/extcon/extcon-usbc-tusb320.c new file mode 100644 index 000000000000..217d6f416ff6 --- /dev/null +++ b/drivers/extcon/extcon-usbc-tusb320.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * drivers/extcon/extcon-tusb320.c - TUSB320 extcon driver + * + * Copyright (C) 2020 National Instruments Corporation + * Author: Michael Auchter <[email protected]> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/extcon-provider.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#define TUSB320_REG_9 0x9 +#define TUSB320_REG_9_INTERRUPT_STATUS BIT(4) +#define TUSB320_REG_9_GET_ATTACHED_STATE(reg) (((reg) >> 6) & 0x3) +#define TUSB320_REG_9_GET_CABLE_DIR(reg) (((reg) >> 5) & 1) +#define TUSB320_ATTACHED_STATE_NONE 0x0 +#define TUSB320_ATTACHED_STATE_DFP 0x1 +#define TUSB320_ATTACHED_STATE_UFP 0x2 +#define TUSB320_ATTACHED_STATE_ACC 0x3 + +static const char * const tusb_attached_states[] = { + [TUSB320_ATTACHED_STATE_NONE] = "not attached", + [TUSB320_ATTACHED_STATE_DFP] = "downstream facing port", + [TUSB320_ATTACHED_STATE_UFP] = "upstream facing port", + [TUSB320_ATTACHED_STATE_ACC] = "accessory", +}; + +static const unsigned int tusb320_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +static int tusb320_check_signature(struct i2c_client *client) +{ + static const char sig[] = { '\0', 'T', 'U', 'S', 'B', '3', '2', '0' }; + int i, ret; + + for (i = 0; i < sizeof(sig); i++) { + ret = i2c_smbus_read_byte_data(client, sizeof(sig) - 1 - i); + if (ret < 0) + return ret; + if (ret != sig[i]) { + dev_err(&client->dev, "signature mismatch!\n"); + return -ENODEV; + } + } + + return 0; +} + +static irqreturn_t tusb320_irq_handler(int irq, void *dev_id) +{ + struct i2c_client *client = dev_id; + struct extcon_dev *edev = i2c_get_clientdata(client); + int reg, state, polarity; + + reg = i2c_smbus_read_byte_data(client, TUSB320_REG_9); + if (reg < 0) { + dev_err(&client->dev, "error during i2c read: %d\n", reg); + return IRQ_NONE; + } + + if (!(reg & TUSB320_REG_9_INTERRUPT_STATUS)) + return IRQ_NONE; + + state = TUSB320_REG_9_GET_ATTACHED_STATE(reg); + polarity = TUSB320_REG_9_GET_CABLE_DIR(reg); + + dev_dbg(&client->dev, "attached state: %s, polarity: %d\n", + tusb_attached_states[state], polarity); + + extcon_set_state(edev, EXTCON_USB, + state == TUSB320_ATTACHED_STATE_UFP); + extcon_set_state(edev, EXTCON_USB_HOST, + state == TUSB320_ATTACHED_STATE_DFP); + extcon_set_property(edev, EXTCON_USB, + EXTCON_PROP_USB_TYPEC_POLARITY, + (union extcon_property_value)polarity); + extcon_set_property(edev, EXTCON_USB_HOST, + EXTCON_PROP_USB_TYPEC_POLARITY, + (union extcon_property_value)polarity); + extcon_sync(edev, EXTCON_USB); + extcon_sync(edev, EXTCON_USB_HOST); + + i2c_smbus_write_byte_data(client, TUSB320_REG_9, reg); + + return IRQ_HANDLED; +} + +static int tusb320_extcon_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct extcon_dev *edev; + int ret; + + ret = tusb320_check_signature(client); + if (ret) + return ret; + + edev = devm_extcon_dev_allocate(&client->dev, tusb320_extcon_cable); + if (IS_ERR(edev)) { + dev_err(&client->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + i2c_set_clientdata(client, edev); + + ret = devm_extcon_dev_register(&client->dev, edev); + if (ret < 0) { + dev_err(&client->dev, "failed to register extcon device\n"); + return ret; + } + + extcon_set_property_capability(edev, EXTCON_USB, + EXTCON_PROP_USB_TYPEC_POLARITY); + extcon_set_property_capability(edev, EXTCON_USB_HOST, + EXTCON_PROP_USB_TYPEC_POLARITY); + + /* update initial state */ + tusb320_irq_handler(client->irq, client); + + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, tusb320_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(&client->dev), client); + + return ret; +} + +static const struct of_device_id tusb320_extcon_dt_match[] = { + { .compatible = "ti,tusb320", }, + { } +}; +MODULE_DEVICE_TABLE(of, tusb320_extcon_dt_match); + +static const struct i2c_device_id tusb320_i2c_id[] = { + { "tusb320", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tusb320_i2c_id); + +static struct i2c_driver tusb320_extcon_driver = { + .probe = tusb320_extcon_probe, + .driver = { + .name = "extcon-tusb320", + .of_match_table = tusb320_extcon_dt_match, + }, +}; + +static int __init tusb320_init(void) +{ + return i2c_add_driver(&tusb320_extcon_driver); +} +subsys_initcall(tusb320_init); + +static void __exit tusb320_exit(void) +{ + i2c_del_driver(&tusb320_extcon_driver); +} +module_exit(tusb320_exit); + +MODULE_AUTHOR("Michael Auchter <[email protected]>"); +MODULE_DESCRIPTION("TI TUSB320 extcon driver"); +MODULE_LICENSE("GPL v2"); -- 2.25.4

