linux-stable/drivers/extcon/extcon-usbc-tusb320.c

619 lines
16 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* drivers/extcon/extcon-tusb320.c - TUSB320 extcon driver
*
* Copyright (C) 2020 National Instruments Corporation
* Author: Michael Auchter <michael.auchter@ni.com>
*/
#include <linux/bitfield.h>
#include <linux/extcon-provider.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/usb/typec.h>
#include <linux/usb/typec_altmode.h>
#include <linux/usb/role.h>
#define TUSB320_REG8 0x8
#define TUSB320_REG8_CURRENT_MODE_ADVERTISE GENMASK(7, 6)
#define TUSB320_REG8_CURRENT_MODE_ADVERTISE_USB 0x0
#define TUSB320_REG8_CURRENT_MODE_ADVERTISE_15A 0x1
#define TUSB320_REG8_CURRENT_MODE_ADVERTISE_30A 0x2
#define TUSB320_REG8_CURRENT_MODE_DETECT GENMASK(5, 4)
#define TUSB320_REG8_CURRENT_MODE_DETECT_DEF 0x0
#define TUSB320_REG8_CURRENT_MODE_DETECT_MED 0x1
#define TUSB320_REG8_CURRENT_MODE_DETECT_ACC 0x2
#define TUSB320_REG8_CURRENT_MODE_DETECT_HI 0x3
#define TUSB320_REG8_ACCESSORY_CONNECTED GENMASK(3, 1)
#define TUSB320_REG8_ACCESSORY_CONNECTED_NONE 0x0
#define TUSB320_REG8_ACCESSORY_CONNECTED_AUDIO 0x4
#define TUSB320_REG8_ACCESSORY_CONNECTED_ACHRG 0x5
#define TUSB320_REG8_ACCESSORY_CONNECTED_DBGDFP 0x6
#define TUSB320_REG8_ACCESSORY_CONNECTED_DBGUFP 0x7
#define TUSB320_REG8_ACTIVE_CABLE_DETECTION BIT(0)
#define TUSB320_REG9 0x9
#define TUSB320_REG9_ATTACHED_STATE GENMASK(7, 6)
#define TUSB320_REG9_CABLE_DIRECTION BIT(5)
#define TUSB320_REG9_INTERRUPT_STATUS BIT(4)
#define TUSB320_REGA 0xa
#define TUSB320L_REGA_DISABLE_TERM BIT(0)
#define TUSB320_REGA_I2C_SOFT_RESET BIT(3)
#define TUSB320_REGA_MODE_SELECT_SHIFT 4
#define TUSB320_REGA_MODE_SELECT_MASK 0x3
#define TUSB320L_REGA0_REVISION 0xa0
enum tusb320_attached_state {
TUSB320_ATTACHED_STATE_NONE,
TUSB320_ATTACHED_STATE_DFP,
TUSB320_ATTACHED_STATE_UFP,
TUSB320_ATTACHED_STATE_ACC,
};
enum tusb320_mode {
TUSB320_MODE_PORT,
TUSB320_MODE_UFP,
TUSB320_MODE_DFP,
TUSB320_MODE_DRP,
};
struct tusb320_priv;
struct tusb320_ops {
int (*set_mode)(struct tusb320_priv *priv, enum tusb320_mode mode);
int (*get_revision)(struct tusb320_priv *priv, unsigned int *revision);
};
struct tusb320_priv {
struct device *dev;
struct regmap *regmap;
struct extcon_dev *edev;
struct tusb320_ops *ops;
enum tusb320_attached_state state;
struct typec_port *port;
struct typec_capability cap;
enum typec_port_type port_type;
enum typec_pwr_opmode pwr_opmode;
struct fwnode_handle *connector_fwnode;
struct usb_role_switch *role_sw;
};
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 tusb320_priv *priv)
{
static const char sig[] = { '\0', 'T', 'U', 'S', 'B', '3', '2', '0' };
unsigned val;
int i, ret;
for (i = 0; i < sizeof(sig); i++) {
ret = regmap_read(priv->regmap, sizeof(sig) - 1 - i, &val);
if (ret < 0)
return ret;
if (val != sig[i]) {
dev_err(priv->dev, "signature mismatch!\n");
return -ENODEV;
}
}
return 0;
}
static int tusb320_set_mode(struct tusb320_priv *priv, enum tusb320_mode mode)
{
int ret;
/* Mode cannot be changed while cable is attached */
if (priv->state != TUSB320_ATTACHED_STATE_NONE)
return -EBUSY;
/* Write mode */
ret = regmap_write_bits(priv->regmap, TUSB320_REGA,
TUSB320_REGA_MODE_SELECT_MASK << TUSB320_REGA_MODE_SELECT_SHIFT,
mode << TUSB320_REGA_MODE_SELECT_SHIFT);
if (ret) {
dev_err(priv->dev, "failed to write mode: %d\n", ret);
return ret;
}
return 0;
}
static int tusb320l_set_mode(struct tusb320_priv *priv, enum tusb320_mode mode)
{
int ret;
/* Disable CC state machine */
ret = regmap_write_bits(priv->regmap, TUSB320_REGA,
TUSB320L_REGA_DISABLE_TERM, 1);
if (ret) {
dev_err(priv->dev,
"failed to disable CC state machine: %d\n", ret);
return ret;
}
/* Write mode */
ret = regmap_write_bits(priv->regmap, TUSB320_REGA,
TUSB320_REGA_MODE_SELECT_MASK << TUSB320_REGA_MODE_SELECT_SHIFT,
mode << TUSB320_REGA_MODE_SELECT_SHIFT);
if (ret) {
dev_err(priv->dev, "failed to write mode: %d\n", ret);
goto err;
}
msleep(5);
err:
/* Re-enable CC state machine */
ret = regmap_write_bits(priv->regmap, TUSB320_REGA,
TUSB320L_REGA_DISABLE_TERM, 0);
if (ret)
dev_err(priv->dev,
"failed to re-enable CC state machine: %d\n", ret);
return ret;
}
static int tusb320_reset(struct tusb320_priv *priv)
{
int ret;
/* Set mode to default (follow PORT pin) */
ret = priv->ops->set_mode(priv, TUSB320_MODE_PORT);
if (ret && ret != -EBUSY) {
dev_err(priv->dev,
"failed to set mode to PORT: %d\n", ret);
return ret;
}
/* Perform soft reset */
ret = regmap_write_bits(priv->regmap, TUSB320_REGA,
TUSB320_REGA_I2C_SOFT_RESET, 1);
if (ret) {
dev_err(priv->dev,
"failed to write soft reset bit: %d\n", ret);
return ret;
}
/* Wait for chip to go through reset */
msleep(95);
return 0;
}
static int tusb320l_get_revision(struct tusb320_priv *priv, unsigned int *revision)
{
return regmap_read(priv->regmap, TUSB320L_REGA0_REVISION, revision);
}
static struct tusb320_ops tusb320_ops = {
.set_mode = tusb320_set_mode,
};
static struct tusb320_ops tusb320l_ops = {
.set_mode = tusb320l_set_mode,
.get_revision = tusb320l_get_revision,
};
static int tusb320_set_adv_pwr_mode(struct tusb320_priv *priv)
{
u8 mode;
if (priv->pwr_opmode == TYPEC_PWR_MODE_USB)
mode = TUSB320_REG8_CURRENT_MODE_ADVERTISE_USB;
else if (priv->pwr_opmode == TYPEC_PWR_MODE_1_5A)
mode = TUSB320_REG8_CURRENT_MODE_ADVERTISE_15A;
else if (priv->pwr_opmode == TYPEC_PWR_MODE_3_0A)
mode = TUSB320_REG8_CURRENT_MODE_ADVERTISE_30A;
else /* No other mode is supported. */
return -EINVAL;
return regmap_write_bits(priv->regmap, TUSB320_REG8,
TUSB320_REG8_CURRENT_MODE_ADVERTISE,
FIELD_PREP(TUSB320_REG8_CURRENT_MODE_ADVERTISE,
mode));
}
static int tusb320_port_type_set(struct typec_port *port,
enum typec_port_type type)
{
struct tusb320_priv *priv = typec_get_drvdata(port);
if (type == TYPEC_PORT_SRC)
return priv->ops->set_mode(priv, TUSB320_MODE_DFP);
else if (type == TYPEC_PORT_SNK)
return priv->ops->set_mode(priv, TUSB320_MODE_UFP);
else if (type == TYPEC_PORT_DRP)
return priv->ops->set_mode(priv, TUSB320_MODE_DRP);
else
return priv->ops->set_mode(priv, TUSB320_MODE_PORT);
}
static const struct typec_operations tusb320_typec_ops = {
.port_type_set = tusb320_port_type_set,
};
static void tusb320_extcon_irq_handler(struct tusb320_priv *priv, u8 reg)
{
int state, polarity;
state = FIELD_GET(TUSB320_REG9_ATTACHED_STATE, reg);
polarity = !!(reg & TUSB320_REG9_CABLE_DIRECTION);
dev_dbg(priv->dev, "attached state: %s, polarity: %d\n",
tusb_attached_states[state], polarity);
extcon_set_state(priv->edev, EXTCON_USB,
state == TUSB320_ATTACHED_STATE_UFP);
extcon_set_state(priv->edev, EXTCON_USB_HOST,
state == TUSB320_ATTACHED_STATE_DFP);
extcon_set_property(priv->edev, EXTCON_USB,
EXTCON_PROP_USB_TYPEC_POLARITY,
(union extcon_property_value)polarity);
extcon_set_property(priv->edev, EXTCON_USB_HOST,
EXTCON_PROP_USB_TYPEC_POLARITY,
(union extcon_property_value)polarity);
extcon_sync(priv->edev, EXTCON_USB);
extcon_sync(priv->edev, EXTCON_USB_HOST);
priv->state = state;
}
static void tusb320_typec_irq_handler(struct tusb320_priv *priv, u8 reg9)
{
struct typec_port *port = priv->port;
struct device *dev = priv->dev;
int typec_mode;
enum usb_role usb_role;
enum typec_role pwr_role;
enum typec_data_role data_role;
u8 state, mode, accessory;
int ret, reg8;
bool ori;
ret = regmap_read(priv->regmap, TUSB320_REG8, &reg8);
if (ret) {
dev_err(dev, "error during reg8 i2c read, ret=%d!\n", ret);
return;
}
ori = reg9 & TUSB320_REG9_CABLE_DIRECTION;
typec_set_orientation(port, ori ? TYPEC_ORIENTATION_REVERSE :
TYPEC_ORIENTATION_NORMAL);
state = FIELD_GET(TUSB320_REG9_ATTACHED_STATE, reg9);
accessory = FIELD_GET(TUSB320_REG8_ACCESSORY_CONNECTED, reg8);
switch (state) {
case TUSB320_ATTACHED_STATE_DFP:
typec_mode = TYPEC_MODE_USB2;
usb_role = USB_ROLE_HOST;
pwr_role = TYPEC_SOURCE;
data_role = TYPEC_HOST;
break;
case TUSB320_ATTACHED_STATE_UFP:
typec_mode = TYPEC_MODE_USB2;
usb_role = USB_ROLE_DEVICE;
pwr_role = TYPEC_SINK;
data_role = TYPEC_DEVICE;
break;
case TUSB320_ATTACHED_STATE_ACC:
/*
* Accessory detected. For debug accessories, just make some
* qualified guesses as to the role for lack of a better option.
*/
if (accessory == TUSB320_REG8_ACCESSORY_CONNECTED_AUDIO ||
accessory == TUSB320_REG8_ACCESSORY_CONNECTED_ACHRG) {
typec_mode = TYPEC_MODE_AUDIO;
usb_role = USB_ROLE_NONE;
pwr_role = TYPEC_SINK;
data_role = TYPEC_DEVICE;
break;
} else if (accessory ==
TUSB320_REG8_ACCESSORY_CONNECTED_DBGDFP) {
typec_mode = TYPEC_MODE_DEBUG;
pwr_role = TYPEC_SOURCE;
usb_role = USB_ROLE_HOST;
data_role = TYPEC_HOST;
break;
} else if (accessory ==
TUSB320_REG8_ACCESSORY_CONNECTED_DBGUFP) {
typec_mode = TYPEC_MODE_DEBUG;
pwr_role = TYPEC_SINK;
usb_role = USB_ROLE_DEVICE;
data_role = TYPEC_DEVICE;
break;
}
dev_warn(priv->dev, "unexpected ACCESSORY_CONNECTED state %d\n",
accessory);
fallthrough;
default:
typec_mode = TYPEC_MODE_USB2;
usb_role = USB_ROLE_NONE;
pwr_role = TYPEC_SINK;
data_role = TYPEC_DEVICE;
break;
}
typec_set_vconn_role(port, pwr_role);
typec_set_pwr_role(port, pwr_role);
typec_set_data_role(port, data_role);
typec_set_mode(port, typec_mode);
usb_role_switch_set_role(priv->role_sw, usb_role);
mode = FIELD_GET(TUSB320_REG8_CURRENT_MODE_DETECT, reg8);
if (mode == TUSB320_REG8_CURRENT_MODE_DETECT_DEF)
typec_set_pwr_opmode(port, TYPEC_PWR_MODE_USB);
else if (mode == TUSB320_REG8_CURRENT_MODE_DETECT_MED)
typec_set_pwr_opmode(port, TYPEC_PWR_MODE_1_5A);
else if (mode == TUSB320_REG8_CURRENT_MODE_DETECT_HI)
typec_set_pwr_opmode(port, TYPEC_PWR_MODE_3_0A);
else /* Charge through accessory */
typec_set_pwr_opmode(port, TYPEC_PWR_MODE_USB);
}
extcon: usbc-tusb320: Update state on probe even if no IRQ pending Currently this driver triggers extcon and typec state update in its probe function, to read out current state reported by the chip and report the correct state to upper layers. This synchronization is performed correctly, but only in case the chip indicates a pending interrupt in reg09 register. This fails to cover the situation where all interrupts reported by the chip were already handled by Linux before reboot, then the system rebooted, and then Linux starts again. In this case, the TUSB320 no longer reports any interrupts in reg09, and the state update does not perform any update as it depends on that interrupt indication. Fix this by turning tusb320_irq_handler() into a thin wrapper around tusb320_state_update_handler(), where the later now contains the bulk of the code of tusb320_irq_handler(), but adds new function parameter "force_update". The "force_update" parameter can be used by the probe function to assure that the state synchronization is always performed, independent of the interrupt indicated in reg09. The interrupt handler tusb320_irq_handler() callback uses force_update=false to avoid state updates on potential spurious interrupts and retain current behavior. Fixes: 06bc4ca115cdd ("extcon: Add driver for TI TUSB320") Signed-off-by: Marek Vasut <marex@denx.de> Reviewed-by: Alvin Šipraga <alsi@bang-olufsen.dk> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Link: https://lore.kernel.org/r/20221120141509.81012-1-marex@denx.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2022-11-20 14:15:09 +00:00
static irqreturn_t tusb320_state_update_handler(struct tusb320_priv *priv,
bool force_update)
{
unsigned int reg;
if (regmap_read(priv->regmap, TUSB320_REG9, &reg)) {
dev_err(priv->dev, "error during i2c read!\n");
return IRQ_NONE;
}
extcon: usbc-tusb320: Update state on probe even if no IRQ pending Currently this driver triggers extcon and typec state update in its probe function, to read out current state reported by the chip and report the correct state to upper layers. This synchronization is performed correctly, but only in case the chip indicates a pending interrupt in reg09 register. This fails to cover the situation where all interrupts reported by the chip were already handled by Linux before reboot, then the system rebooted, and then Linux starts again. In this case, the TUSB320 no longer reports any interrupts in reg09, and the state update does not perform any update as it depends on that interrupt indication. Fix this by turning tusb320_irq_handler() into a thin wrapper around tusb320_state_update_handler(), where the later now contains the bulk of the code of tusb320_irq_handler(), but adds new function parameter "force_update". The "force_update" parameter can be used by the probe function to assure that the state synchronization is always performed, independent of the interrupt indicated in reg09. The interrupt handler tusb320_irq_handler() callback uses force_update=false to avoid state updates on potential spurious interrupts and retain current behavior. Fixes: 06bc4ca115cdd ("extcon: Add driver for TI TUSB320") Signed-off-by: Marek Vasut <marex@denx.de> Reviewed-by: Alvin Šipraga <alsi@bang-olufsen.dk> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Link: https://lore.kernel.org/r/20221120141509.81012-1-marex@denx.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2022-11-20 14:15:09 +00:00
if (!force_update && !(reg & TUSB320_REG9_INTERRUPT_STATUS))
return IRQ_NONE;
tusb320_extcon_irq_handler(priv, reg);
/*
* Type-C support is optional. Only call the Type-C handler if a
* port had been registered previously.
*/
if (priv->port)
tusb320_typec_irq_handler(priv, reg);
regmap_write(priv->regmap, TUSB320_REG9, reg);
return IRQ_HANDLED;
}
extcon: usbc-tusb320: Update state on probe even if no IRQ pending Currently this driver triggers extcon and typec state update in its probe function, to read out current state reported by the chip and report the correct state to upper layers. This synchronization is performed correctly, but only in case the chip indicates a pending interrupt in reg09 register. This fails to cover the situation where all interrupts reported by the chip were already handled by Linux before reboot, then the system rebooted, and then Linux starts again. In this case, the TUSB320 no longer reports any interrupts in reg09, and the state update does not perform any update as it depends on that interrupt indication. Fix this by turning tusb320_irq_handler() into a thin wrapper around tusb320_state_update_handler(), where the later now contains the bulk of the code of tusb320_irq_handler(), but adds new function parameter "force_update". The "force_update" parameter can be used by the probe function to assure that the state synchronization is always performed, independent of the interrupt indicated in reg09. The interrupt handler tusb320_irq_handler() callback uses force_update=false to avoid state updates on potential spurious interrupts and retain current behavior. Fixes: 06bc4ca115cdd ("extcon: Add driver for TI TUSB320") Signed-off-by: Marek Vasut <marex@denx.de> Reviewed-by: Alvin Šipraga <alsi@bang-olufsen.dk> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Link: https://lore.kernel.org/r/20221120141509.81012-1-marex@denx.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2022-11-20 14:15:09 +00:00
static irqreturn_t tusb320_irq_handler(int irq, void *dev_id)
{
struct tusb320_priv *priv = dev_id;
return tusb320_state_update_handler(priv, false);
}
static const struct regmap_config tusb320_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
};
static int tusb320_extcon_probe(struct tusb320_priv *priv)
{
int ret;
priv->edev = devm_extcon_dev_allocate(priv->dev, tusb320_extcon_cable);
if (IS_ERR(priv->edev)) {
dev_err(priv->dev, "failed to allocate extcon device\n");
return PTR_ERR(priv->edev);
}
ret = devm_extcon_dev_register(priv->dev, priv->edev);
if (ret < 0) {
dev_err(priv->dev, "failed to register extcon device\n");
return ret;
}
extcon_set_property_capability(priv->edev, EXTCON_USB,
EXTCON_PROP_USB_TYPEC_POLARITY);
extcon_set_property_capability(priv->edev, EXTCON_USB_HOST,
EXTCON_PROP_USB_TYPEC_POLARITY);
return 0;
}
static int tusb320_typec_probe(struct i2c_client *client,
struct tusb320_priv *priv)
{
struct fwnode_handle *connector;
const char *cap_str;
int ret;
/* The Type-C connector is optional, for backward compatibility. */
connector = device_get_named_child_node(&client->dev, "connector");
if (!connector)
return 0;
/* Type-C connector found. */
ret = typec_get_fw_cap(&priv->cap, connector);
if (ret)
goto err_put;
priv->port_type = priv->cap.type;
/* This goes into register 0x8 field CURRENT_MODE_ADVERTISE */
ret = fwnode_property_read_string(connector, "typec-power-opmode", &cap_str);
if (ret)
goto err_put;
ret = typec_find_pwr_opmode(cap_str);
if (ret < 0)
goto err_put;
priv->pwr_opmode = ret;
/* Initialize the hardware with the devicetree settings. */
ret = tusb320_set_adv_pwr_mode(priv);
if (ret)
goto err_put;
priv->cap.revision = USB_TYPEC_REV_1_1;
priv->cap.accessory[0] = TYPEC_ACCESSORY_AUDIO;
priv->cap.accessory[1] = TYPEC_ACCESSORY_DEBUG;
priv->cap.orientation_aware = true;
priv->cap.driver_data = priv;
priv->cap.ops = &tusb320_typec_ops;
priv->cap.fwnode = connector;
priv->port = typec_register_port(&client->dev, &priv->cap);
if (IS_ERR(priv->port)) {
ret = PTR_ERR(priv->port);
goto err_put;
}
/* Find any optional USB role switch that needs reporting to */
priv->role_sw = fwnode_usb_role_switch_get(connector);
if (IS_ERR(priv->role_sw)) {
ret = PTR_ERR(priv->role_sw);
goto err_unreg;
}
priv->connector_fwnode = connector;
return 0;
err_unreg:
typec_unregister_port(priv->port);
err_put:
fwnode_handle_put(connector);
return ret;
}
static void tusb320_typec_remove(struct tusb320_priv *priv)
{
usb_role_switch_put(priv->role_sw);
typec_unregister_port(priv->port);
fwnode_handle_put(priv->connector_fwnode);
}
static int tusb320_probe(struct i2c_client *client)
{
struct tusb320_priv *priv;
const void *match_data;
unsigned int revision;
int ret;
priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = &client->dev;
i2c_set_clientdata(client, priv);
priv->regmap = devm_regmap_init_i2c(client, &tusb320_regmap_config);
if (IS_ERR(priv->regmap))
return PTR_ERR(priv->regmap);
ret = tusb320_check_signature(priv);
if (ret)
return ret;
match_data = device_get_match_data(&client->dev);
if (!match_data)
return -EINVAL;
priv->ops = (struct tusb320_ops*)match_data;
if (priv->ops->get_revision) {
ret = priv->ops->get_revision(priv, &revision);
if (ret)
dev_warn(priv->dev,
"failed to read revision register: %d\n", ret);
else
dev_info(priv->dev, "chip revision %d\n", revision);
}
ret = tusb320_extcon_probe(priv);
if (ret)
return ret;
ret = tusb320_typec_probe(client, priv);
if (ret)
return ret;
/* update initial state */
extcon: usbc-tusb320: Update state on probe even if no IRQ pending Currently this driver triggers extcon and typec state update in its probe function, to read out current state reported by the chip and report the correct state to upper layers. This synchronization is performed correctly, but only in case the chip indicates a pending interrupt in reg09 register. This fails to cover the situation where all interrupts reported by the chip were already handled by Linux before reboot, then the system rebooted, and then Linux starts again. In this case, the TUSB320 no longer reports any interrupts in reg09, and the state update does not perform any update as it depends on that interrupt indication. Fix this by turning tusb320_irq_handler() into a thin wrapper around tusb320_state_update_handler(), where the later now contains the bulk of the code of tusb320_irq_handler(), but adds new function parameter "force_update". The "force_update" parameter can be used by the probe function to assure that the state synchronization is always performed, independent of the interrupt indicated in reg09. The interrupt handler tusb320_irq_handler() callback uses force_update=false to avoid state updates on potential spurious interrupts and retain current behavior. Fixes: 06bc4ca115cdd ("extcon: Add driver for TI TUSB320") Signed-off-by: Marek Vasut <marex@denx.de> Reviewed-by: Alvin Šipraga <alsi@bang-olufsen.dk> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Link: https://lore.kernel.org/r/20221120141509.81012-1-marex@denx.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2022-11-20 14:15:09 +00:00
tusb320_state_update_handler(priv, true);
/* Reset chip to its default state */
ret = tusb320_reset(priv);
if (ret)
dev_warn(priv->dev, "failed to reset chip: %d\n", ret);
else
/*
* State and polarity might change after a reset, so update
* them again and make sure the interrupt status bit is cleared.
*/
extcon: usbc-tusb320: Update state on probe even if no IRQ pending Currently this driver triggers extcon and typec state update in its probe function, to read out current state reported by the chip and report the correct state to upper layers. This synchronization is performed correctly, but only in case the chip indicates a pending interrupt in reg09 register. This fails to cover the situation where all interrupts reported by the chip were already handled by Linux before reboot, then the system rebooted, and then Linux starts again. In this case, the TUSB320 no longer reports any interrupts in reg09, and the state update does not perform any update as it depends on that interrupt indication. Fix this by turning tusb320_irq_handler() into a thin wrapper around tusb320_state_update_handler(), where the later now contains the bulk of the code of tusb320_irq_handler(), but adds new function parameter "force_update". The "force_update" parameter can be used by the probe function to assure that the state synchronization is always performed, independent of the interrupt indicated in reg09. The interrupt handler tusb320_irq_handler() callback uses force_update=false to avoid state updates on potential spurious interrupts and retain current behavior. Fixes: 06bc4ca115cdd ("extcon: Add driver for TI TUSB320") Signed-off-by: Marek Vasut <marex@denx.de> Reviewed-by: Alvin Šipraga <alsi@bang-olufsen.dk> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Link: https://lore.kernel.org/r/20221120141509.81012-1-marex@denx.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2022-11-20 14:15:09 +00:00
tusb320_state_update_handler(priv, true);
ret = devm_request_threaded_irq(priv->dev, client->irq, NULL,
tusb320_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
client->name, priv);
if (ret)
tusb320_typec_remove(priv);
return ret;
}
static void tusb320_remove(struct i2c_client *client)
{
struct tusb320_priv *priv = i2c_get_clientdata(client);
tusb320_typec_remove(priv);
}
static const struct of_device_id tusb320_extcon_dt_match[] = {
{ .compatible = "ti,tusb320", .data = &tusb320_ops, },
{ .compatible = "ti,tusb320l", .data = &tusb320l_ops, },
{ }
};
MODULE_DEVICE_TABLE(of, tusb320_extcon_dt_match);
static struct i2c_driver tusb320_extcon_driver = {
.probe = tusb320_probe,
.remove = tusb320_remove,
.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 <michael.auchter@ni.com>");
MODULE_DESCRIPTION("TI TUSB320 extcon driver");
MODULE_LICENSE("GPL v2");