File Index
Files and directories
Sign in
FILE

tusb1046.c

File preview with syntax highlighting.

linux-6.19.14/drivers/usb/typec/mux/tusb1046.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Driver for the TUSB1046-DCI USB Type-C crosspoint switch
  4 *
  5 * Copyright (C) 2024 Bootlin
  6 */
  7
  8#include <linux/bits.h>
  9#include <linux/i2c.h>
 10#include <linux/usb/typec_mux.h>
 11#include <linux/usb/typec_dp.h>
 12#include <linux/usb/typec_altmode.h>
 13#include <linux/module.h>
 14#include <linux/mod_devicetable.h>
 15#include <linux/err.h>
 16#include <linux/of_device.h>
 17#include <linux/device.h>
 18#include <linux/mutex.h>
 19
 20#define TUSB1046_REG_GENERAL 0xa
 21
 22/* General register bits */
 23#define TUSB1046_GENERAL_FLIPSEL BIT(2)
 24#define TUSB1046_GENERAL_CTLSEL  GENMASK(1, 0)
 25
 26/* Mux modes */
 27#define TUSB1046_CTLSEL_DISABLED          0x0
 28#define TUSB1046_CTLSEL_USB3              0x1
 29#define TUSB1046_CTLSEL_4LANE_DP          0x2
 30#define TUSB1046_CTLSEL_USB3_AND_2LANE_DP 0x3
 31
 32struct tusb1046_priv {
 33	struct i2c_client *client;
 34	struct typec_switch_dev *sw;
 35	struct typec_mux_dev *mux;
 36
 37	/* Lock General register during accesses */
 38	struct mutex general_reg_lock;
 39};
 40
 41static int tusb1046_mux_set(struct typec_mux_dev *mux,
 42			    struct typec_mux_state *state)
 43{
 44	struct tusb1046_priv *priv = typec_mux_get_drvdata(mux);
 45	struct i2c_client *client = priv->client;
 46	struct device *dev = &client->dev;
 47	int mode, val, ret = 0;
 48
 49	if (state->mode >= TYPEC_STATE_MODAL &&
 50	    state->alt->svid != USB_TYPEC_DP_SID)
 51		return -EINVAL;
 52
 53	dev_dbg(dev, "mux mode requested: %lu\n", state->mode);
 54
 55	mutex_lock(&priv->general_reg_lock);
 56
 57	val = i2c_smbus_read_byte_data(client, TUSB1046_REG_GENERAL);
 58	if (val < 0) {
 59		dev_err(dev, "failed to read ctlsel status, err %d\n", val);
 60		ret = val;
 61		goto out_unlock;
 62	}
 63
 64	switch (state->mode) {
 65	case TYPEC_STATE_USB:
 66		mode = TUSB1046_CTLSEL_USB3;
 67		break;
 68	case TYPEC_DP_STATE_C:
 69	case TYPEC_DP_STATE_E:
 70		mode = TUSB1046_CTLSEL_4LANE_DP;
 71		break;
 72	case TYPEC_DP_STATE_D:
 73		mode = TUSB1046_CTLSEL_USB3_AND_2LANE_DP;
 74		break;
 75	case TYPEC_STATE_SAFE:
 76	default:
 77		mode = TUSB1046_CTLSEL_DISABLED;
 78		break;
 79	}
 80
 81	val &= ~TUSB1046_GENERAL_CTLSEL;
 82	val |= mode;
 83
 84	ret = i2c_smbus_write_byte_data(client, TUSB1046_REG_GENERAL, val);
 85
 86out_unlock:
 87	mutex_unlock(&priv->general_reg_lock);
 88	return ret;
 89}
 90
 91static int tusb1046_switch_set(struct typec_switch_dev *sw,
 92			       enum typec_orientation orientation)
 93{
 94	struct tusb1046_priv *priv = typec_switch_get_drvdata(sw);
 95	struct i2c_client *client = priv->client;
 96	struct device *dev = &client->dev;
 97	int val, ret = 0;
 98
 99	dev_dbg(dev, "setting USB3.0 lane flip for orientation %d\n", orientation);
100
101	mutex_lock(&priv->general_reg_lock);
102
103	val = i2c_smbus_read_byte_data(client, TUSB1046_REG_GENERAL);
104	if (val < 0) {
105		dev_err(dev, "failed to read flipsel status, err %d\n", val);
106		ret = val;
107		goto out_unlock;
108	}
109
110	if (orientation == TYPEC_ORIENTATION_REVERSE)
111		val |= TUSB1046_GENERAL_FLIPSEL;
112	else
113		val &= ~TUSB1046_GENERAL_FLIPSEL;
114
115	ret = i2c_smbus_write_byte_data(client, TUSB1046_REG_GENERAL, val);
116
117out_unlock:
118	mutex_unlock(&priv->general_reg_lock);
119	return ret;
120}
121
122static int tusb1046_i2c_probe(struct i2c_client *client)
123{
124	struct typec_switch_desc sw_desc = { };
125	struct typec_mux_desc mux_desc = { };
126	struct device *dev = &client->dev;
127	struct tusb1046_priv *priv;
128	int ret = 0;
129
130	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
131	if (!priv)
132		return -ENOMEM;
133
134	priv->client = client;
135
136	mutex_init(&priv->general_reg_lock);
137
138	sw_desc.drvdata = priv;
139	sw_desc.fwnode = dev_fwnode(dev);
140	sw_desc.set = tusb1046_switch_set;
141
142	priv->sw = typec_switch_register(dev, &sw_desc);
143	if (IS_ERR(priv->sw)) {
144		ret = dev_err_probe(dev, PTR_ERR(priv->sw), "failed to register type-c switch\n");
145		goto err_destroy_mutex;
146	}
147
148	mux_desc.drvdata = priv;
149	mux_desc.fwnode = dev_fwnode(dev);
150	mux_desc.set = tusb1046_mux_set;
151
152	priv->mux = typec_mux_register(dev, &mux_desc);
153	if (IS_ERR(priv->mux)) {
154		ret = dev_err_probe(dev, PTR_ERR(priv->mux), "failed to register type-c mux\n");
155		goto err_unregister_switch;
156	}
157
158	i2c_set_clientdata(client, priv);
159
160	return 0;
161
162err_unregister_switch:
163	typec_switch_unregister(priv->sw);
164err_destroy_mutex:
165	mutex_destroy(&priv->general_reg_lock);
166	return ret;
167}
168
169static void tusb1046_i2c_remove(struct i2c_client *client)
170{
171	struct tusb1046_priv *priv = i2c_get_clientdata(client);
172
173	typec_switch_unregister(priv->sw);
174	typec_mux_unregister(priv->mux);
175	mutex_destroy(&priv->general_reg_lock);
176}
177
178static const struct of_device_id tusb1046_match_table[] = {
179	{.compatible = "ti,tusb1046"},
180	{},
181};
182
183static struct i2c_driver tusb1046_driver = {
184	.driver = {
185		.name = "tusb1046",
186		.of_match_table = tusb1046_match_table,
187	},
188	.probe = tusb1046_i2c_probe,
189	.remove = tusb1046_i2c_remove,
190};
191
192module_i2c_driver(tusb1046_driver);
193
194MODULE_DESCRIPTION("TUSB1046 USB Type-C switch driver");
195MODULE_AUTHOR("Romain Gantois <romain.gantois@bootlin.com>");
196MODULE_LICENSE("GPL");
~/ Read-only file preview.