extcon: usbc-tusb320: Unregister typec port on driver removal
authorAlvin Šipraga <alsi@bang-olufsen.dk>
Wed, 15 Mar 2023 14:15:47 +0000 (15:15 +0100)
committerChanwoo Choi <cw00.choi@samsung.com>
Mon, 29 May 2023 14:30:15 +0000 (23:30 +0900)
The driver can register a typec port if suitable firmware properties are
present. But if the driver is removed through sysfs unbind, rmmod or
similar, then it does not clean up after itself and the typec port
device remains registered. This can be seen in sysfs, where stale typec
ports get left over in /sys/class/typec.

In order to fix this we have to add an i2c_driver remove function and
call typec_unregister_port(), which is a no-op in the case where no
typec port is created and the pointer remains NULL.

In the process we should also put the fwnode_handle when the typec port
isn't registered anymore, including if an error occurs during probe. The
typec subsystem does not increase or decrease the reference counter for
us, so we track it in the driver's private data.

Note that the conditional check on TYPEC_PWR_MODE_PD was removed in the
probe path because a call to tusb320_set_adv_pwr_mode() will perform an
even more robust validation immediately after, hence there is no
functional change here.

Fixes: bf7571c00dca ("extcon: usbc-tusb320: Add USB TYPE-C support")
Cc: stable@vger.kernel.org
Signed-off-by: Alvin Šipraga <alsi@bang-olufsen.dk>
Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
drivers/extcon/extcon-usbc-tusb320.c

index b408ce9..10dff1c 100644 (file)
@@ -78,6 +78,7 @@ struct tusb320_priv {
        struct typec_capability cap;
        enum typec_port_type port_type;
        enum typec_pwr_opmode pwr_opmode;
+       struct fwnode_handle *connector_fwnode;
 };
 
 static const char * const tusb_attached_states[] = {
@@ -391,27 +392,25 @@ static int tusb320_typec_probe(struct i2c_client *client,
        /* Type-C connector found. */
        ret = typec_get_fw_cap(&priv->cap, connector);
        if (ret)
-               return 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)
-               return ret;
+               goto err_put;
 
        ret = typec_find_pwr_opmode(cap_str);
        if (ret < 0)
-               return ret;
-       if (ret == TYPEC_PWR_MODE_PD)
-               return -EINVAL;
+               goto err_put;
 
        priv->pwr_opmode = ret;
 
        /* Initialize the hardware with the devicetree settings. */
        ret = tusb320_set_adv_pwr_mode(priv);
        if (ret)
-               return ret;
+               goto err_put;
 
        priv->cap.revision              = USB_TYPEC_REV_1_1;
        priv->cap.accessory[0]          = TYPEC_ACCESSORY_AUDIO;
@@ -422,10 +421,25 @@ static int tusb320_typec_probe(struct i2c_client *client,
        priv->cap.fwnode                = connector;
 
        priv->port = typec_register_port(&client->dev, &priv->cap);
-       if (IS_ERR(priv->port))
-               return PTR_ERR(priv->port);
+       if (IS_ERR(priv->port)) {
+               ret = PTR_ERR(priv->port);
+               goto err_put;
+       }
+
+       priv->connector_fwnode = connector;
 
        return 0;
+
+err_put:
+       fwnode_handle_put(connector);
+
+       return ret;
+}
+
+static void tusb320_typec_remove(struct tusb320_priv *priv)
+{
+       typec_unregister_port(priv->port);
+       fwnode_handle_put(priv->connector_fwnode);
 }
 
 static int tusb320_probe(struct i2c_client *client)
@@ -438,7 +452,9 @@ static int tusb320_probe(struct i2c_client *client)
        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))
@@ -489,10 +505,19 @@ static int tusb320_probe(struct i2c_client *client)
                                        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, },
@@ -502,6 +527,7 @@ MODULE_DEVICE_TABLE(of, tusb320_extcon_dt_match);
 
 static struct i2c_driver tusb320_extcon_driver = {
        .probe_new      = tusb320_probe,
+       .remove         = tusb320_remove,
        .driver         = {
                .name   = "extcon-tusb320",
                .of_match_table = tusb320_extcon_dt_match,