Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

extcon: usbc-tusb320: Unregister typec port on driver removal

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>

authored by

Alvin Šipraga and committed by
Chanwoo Choi
3adbaa30 7877cb91

+34 -8
+34 -8
drivers/extcon/extcon-usbc-tusb320.c
··· 78 78 struct typec_capability cap; 79 79 enum typec_port_type port_type; 80 80 enum typec_pwr_opmode pwr_opmode; 81 + struct fwnode_handle *connector_fwnode; 81 82 }; 82 83 83 84 static const char * const tusb_attached_states[] = { ··· 392 391 /* Type-C connector found. */ 393 392 ret = typec_get_fw_cap(&priv->cap, connector); 394 393 if (ret) 395 - return ret; 394 + goto err_put; 396 395 397 396 priv->port_type = priv->cap.type; 398 397 399 398 /* This goes into register 0x8 field CURRENT_MODE_ADVERTISE */ 400 399 ret = fwnode_property_read_string(connector, "typec-power-opmode", &cap_str); 401 400 if (ret) 402 - return ret; 401 + goto err_put; 403 402 404 403 ret = typec_find_pwr_opmode(cap_str); 405 404 if (ret < 0) 406 - return ret; 407 - if (ret == TYPEC_PWR_MODE_PD) 408 - return -EINVAL; 405 + goto err_put; 409 406 410 407 priv->pwr_opmode = ret; 411 408 412 409 /* Initialize the hardware with the devicetree settings. */ 413 410 ret = tusb320_set_adv_pwr_mode(priv); 414 411 if (ret) 415 - return ret; 412 + goto err_put; 416 413 417 414 priv->cap.revision = USB_TYPEC_REV_1_1; 418 415 priv->cap.accessory[0] = TYPEC_ACCESSORY_AUDIO; ··· 421 422 priv->cap.fwnode = connector; 422 423 423 424 priv->port = typec_register_port(&client->dev, &priv->cap); 424 - if (IS_ERR(priv->port)) 425 - return PTR_ERR(priv->port); 425 + if (IS_ERR(priv->port)) { 426 + ret = PTR_ERR(priv->port); 427 + goto err_put; 428 + } 429 + 430 + priv->connector_fwnode = connector; 426 431 427 432 return 0; 433 + 434 + err_put: 435 + fwnode_handle_put(connector); 436 + 437 + return ret; 438 + } 439 + 440 + static void tusb320_typec_remove(struct tusb320_priv *priv) 441 + { 442 + typec_unregister_port(priv->port); 443 + fwnode_handle_put(priv->connector_fwnode); 428 444 } 429 445 430 446 static int tusb320_probe(struct i2c_client *client) ··· 452 438 priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); 453 439 if (!priv) 454 440 return -ENOMEM; 441 + 455 442 priv->dev = &client->dev; 443 + i2c_set_clientdata(client, priv); 456 444 457 445 priv->regmap = devm_regmap_init_i2c(client, &tusb320_regmap_config); 458 446 if (IS_ERR(priv->regmap)) ··· 505 489 tusb320_irq_handler, 506 490 IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 507 491 client->name, priv); 492 + if (ret) 493 + tusb320_typec_remove(priv); 508 494 509 495 return ret; 496 + } 497 + 498 + static void tusb320_remove(struct i2c_client *client) 499 + { 500 + struct tusb320_priv *priv = i2c_get_clientdata(client); 501 + 502 + tusb320_typec_remove(priv); 510 503 } 511 504 512 505 static const struct of_device_id tusb320_extcon_dt_match[] = { ··· 527 502 528 503 static struct i2c_driver tusb320_extcon_driver = { 529 504 .probe_new = tusb320_probe, 505 + .remove = tusb320_remove, 530 506 .driver = { 531 507 .name = "extcon-tusb320", 532 508 .of_match_table = tusb320_extcon_dt_match,