extcon: usbc-tusb320: Add support for TUSB320L
authorYassine Oudjana <y.oudjana@protonmail.com>
Sat, 25 Sep 2021 05:45:54 +0000 (05:45 +0000)
committerChanwoo Choi <cw00.choi@samsung.com>
Wed, 27 Oct 2021 05:13:39 +0000 (14:13 +0900)
TUSB320L is a newer chip with additional features, and it has additional steps
in its mode changing sequence:
 - Disable CC state machine,
 - Write to mode register,
 - Wait for 5 ms,
 - Re-enable CC state machine.
It also has an additional register that a revision number can be read from.

Add support for the mode changing sequence, and read the revision number during
probe and print it as info.

Signed-off-by: Yassine Oudjana <y.oudjana@protonmail.com>
Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
drivers/extcon/extcon-usbc-tusb320.c

index 1ed1dfe5420694e57621d8e1087ce04f33a1a8f9..6ba3d89b106d059ddd37c8b3a48b6314847e4f5e 100644 (file)
 #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,
@@ -39,11 +42,18 @@ enum tusb320_mode {
        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;
 };
 
@@ -99,12 +109,46 @@ static int tusb320_set_mode(struct tusb320_priv *priv, enum tusb320_mode mode)
        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 = tusb320_set_mode(priv, TUSB320_MODE_PORT);
+       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);
@@ -126,6 +170,20 @@ static int tusb320_reset(struct tusb320_priv *priv)
        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 irqreturn_t tusb320_irq_handler(int irq, void *dev_id)
 {
        struct tusb320_priv *priv = dev_id;
@@ -176,6 +234,8 @@ static int tusb320_extcon_probe(struct i2c_client *client,
                                const struct i2c_device_id *id)
 {
        struct tusb320_priv *priv;
+       const void *match_data;
+       unsigned int revision;
        int ret;
 
        priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
@@ -191,12 +251,27 @@ static int tusb320_extcon_probe(struct i2c_client *client,
        if (ret)
                return ret;
 
+       match_data = device_get_match_data(&client->dev);
+       if (!match_data)
+               return -EINVAL;
+
+       priv->ops = (struct tusb320_ops*)match_data;
+
        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);
        }
 
+       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 = devm_extcon_dev_register(priv->dev, priv->edev);
        if (ret < 0) {
                dev_err(priv->dev, "failed to register extcon device\n");
@@ -231,7 +306,8 @@ static int tusb320_extcon_probe(struct i2c_client *client,
 }
 
 static const struct of_device_id tusb320_extcon_dt_match[] = {
-       { .compatible = "ti,tusb320", },
+       { .compatible = "ti,tusb320", .data = &tusb320_ops, },
+       { .compatible = "ti,tusb320l", .data = &tusb320l_ops, },
        { }
 };
 MODULE_DEVICE_TABLE(of, tusb320_extcon_dt_match);