aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/typec/mux/it5205.c
blob: 5535932e42cdeee1ffb84a47f2962c02bda1484a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
// SPDX-License-Identifier: GPL-2.0
/*
 * ITE IT5205 Type-C USB alternate mode passive mux
 *
 * Copyright (c) 2020 MediaTek Inc.
 * Copyright (c) 2024 Collabora Ltd.
 *                    AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
 *
 */

#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/usb/tcpm.h>
#include <linux/usb/typec.h>
#include <linux/usb/typec_dp.h>
#include <linux/usb/typec_mux.h>

#define IT5205_REG_CHIP_ID(x)	(0x4 + (x))
#define IT5205FN_CHIP_ID	0x35323035 /* "5205" */

/* MUX power down register */
#define IT5205_REG_MUXPDR        0x10
#define IT5205_MUX_POWER_DOWN    BIT(0)

/* MUX control register */
#define IT5205_REG_MUXCR         0x11
#define IT5205_POLARITY_INVERTED BIT(4)
#define IT5205_DP_USB_CTRL_MASK  GENMASK(3, 0)
#define IT5205_DP                0x0f
#define IT5205_DP_USB            0x03
#define IT5205_USB               0x07

/* Vref Select Register */
#define IT5205_REG_VSR            0x10
#define IT5205_VREF_SELECT_MASK   GENMASK(5, 4)
#define IT5205_VREF_SELECT_3_3V   0x00
#define IT5205_VREF_SELECT_OFF    0x20

/* CSBU Over Voltage Protection Register */
#define IT5205_REG_CSBUOVPSR      0x1e
#define IT5205_OVP_SELECT_MASK    GENMASK(5, 4)
#define IT5205_OVP_3_90V          0x00
#define IT5205_OVP_3_68V          0x10
#define IT5205_OVP_3_62V          0x20
#define IT5205_OVP_3_57V          0x30

/* CSBU Switch Register */
#define IT5205_REG_CSBUSR         0x22
#define IT5205_CSBUSR_SWITCH      BIT(0)

/* Interrupt Switch Register */
#define IT5205_REG_ISR            0x25
#define IT5205_ISR_CSBU_MASK      BIT(4)
#define IT5205_ISR_CSBU_OVP       BIT(0)

struct it5205 {
	struct i2c_client *client;
	struct regmap *regmap;
	struct typec_switch_dev *sw;
	struct typec_mux_dev *mux;
};

static int it5205_switch_set(struct typec_switch_dev *sw, enum typec_orientation orientation)
{
	struct it5205 *it = typec_switch_get_drvdata(sw);

	switch (orientation) {
	case TYPEC_ORIENTATION_NORMAL:
		regmap_update_bits(it->regmap, IT5205_REG_MUXCR,
				   IT5205_POLARITY_INVERTED, 0);
		break;
	case TYPEC_ORIENTATION_REVERSE:
		regmap_update_bits(it->regmap, IT5205_REG_MUXCR,
				   IT5205_POLARITY_INVERTED, IT5205_POLARITY_INVERTED);
		break;
	case TYPEC_ORIENTATION_NONE:
		fallthrough;
	default:
		regmap_write(it->regmap, IT5205_REG_MUXCR, 0);
		break;
	}

	return 0;
}

static int it5205_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state)
{
	struct it5205 *it = typec_mux_get_drvdata(mux);
	u8 val;

	if (state->mode >= TYPEC_STATE_MODAL &&
	    state->alt->svid != USB_TYPEC_DP_SID)
		return -EINVAL;

	switch (state->mode) {
	case TYPEC_STATE_USB:
		val = IT5205_USB;
		break;
	case TYPEC_DP_STATE_C:
		fallthrough;
	case TYPEC_DP_STATE_E:
		val = IT5205_DP;
		break;
	case TYPEC_DP_STATE_D:
		val = IT5205_DP_USB;
		break;
	case TYPEC_STATE_SAFE:
		fallthrough;
	default:
		val = 0;
		break;
	}

	return regmap_update_bits(it->regmap, IT5205_REG_MUXCR,
				  IT5205_DP_USB_CTRL_MASK, val);
}

static irqreturn_t it5205_irq_handler(int irq, void *data)
{
	struct it5205 *it = data;
	int ret;
	u32 val;

	ret = regmap_read(it->regmap, IT5205_REG_ISR, &val);
	if (ret)
		return IRQ_NONE;

	if (val & IT5205_ISR_CSBU_OVP) {
		dev_warn(&it->client->dev, "Overvoltage detected!\n");

		/* Reset CSBU */
		regmap_update_bits(it->regmap, IT5205_REG_CSBUSR,
				   IT5205_CSBUSR_SWITCH, 0);
		regmap_update_bits(it->regmap, IT5205_REG_CSBUSR,
				   IT5205_CSBUSR_SWITCH, IT5205_CSBUSR_SWITCH);
	}

	return IRQ_HANDLED;
}

static void it5205_enable_ovp(struct it5205 *it)
{
	/* Select Vref 3.3v */
	regmap_update_bits(it->regmap, IT5205_REG_VSR,
			   IT5205_VREF_SELECT_MASK, IT5205_VREF_SELECT_3_3V);

	/* Trigger OVP at 3.68V */
	regmap_update_bits(it->regmap, IT5205_REG_CSBUOVPSR,
			   IT5205_OVP_SELECT_MASK, IT5205_OVP_3_68V);

	/* Unmask OVP interrupt */
	regmap_update_bits(it->regmap, IT5205_REG_ISR,
			   IT5205_ISR_CSBU_MASK, 0);

	/* Enable CSBU Interrupt */
	regmap_update_bits(it->regmap, IT5205_REG_CSBUSR,
			   IT5205_CSBUSR_SWITCH, IT5205_CSBUSR_SWITCH);
}

static const struct regmap_config it5205_regmap = {
	.max_register = 0x2f,
	.reg_bits = 8,
	.val_bits = 8,
};

static int it5205_probe(struct i2c_client *client)
{
	struct typec_switch_desc sw_desc = { };
	struct typec_mux_desc mux_desc = { };
	struct device *dev = &client->dev;
	struct it5205 *it;
	u32 val, chipid = 0;
	int i, ret;

	it = devm_kzalloc(dev, sizeof(*it), GFP_KERNEL);
	if (!it)
		return -ENOMEM;

	ret = devm_regulator_get_enable(dev, "vcc");
	if (ret)
		return dev_err_probe(dev, ret, "Failed to get regulator\n");

	it->client = client;

	it->regmap = devm_regmap_init_i2c(client, &it5205_regmap);
	if (IS_ERR(it->regmap))
		return dev_err_probe(dev, PTR_ERR(it->regmap),
				     "Failed to init regmap\n");

	/* IT5205 needs a long time to power up after enabling regulator */
	msleep(50);

	/* Unset poweroff bit */
	ret = regmap_write(it->regmap, IT5205_REG_MUXPDR, 0);
	if (ret)
		return dev_err_probe(dev, ret, "Failed to set power on\n");

	/* Read the 32 bits ChipID */
	for (i = 3; i >= 0; i--) {
		ret = regmap_read(it->regmap, IT5205_REG_CHIP_ID(i), &val);
		if (ret)
			return ret;

		chipid |= val << (i * 8);
	}

	if (chipid != IT5205FN_CHIP_ID)
		return dev_err_probe(dev, -EINVAL,
				     "Unknown ChipID 0x%x\n", chipid);

	/* Initialize as USB mode with default (non-inverted) polarity */
	ret = regmap_write(it->regmap, IT5205_REG_MUXCR, IT5205_USB);
	if (ret)
		return dev_err_probe(dev, ret, "Cannot set mode to USB\n");

	sw_desc.drvdata = it;
	sw_desc.fwnode = dev_fwnode(dev);
	sw_desc.set = it5205_switch_set;

	it->sw = typec_switch_register(dev, &sw_desc);
	if (IS_ERR(it->sw))
		return dev_err_probe(dev, PTR_ERR(it->sw),
				     "failed to register typec switch\n");

	mux_desc.drvdata = it;
	mux_desc.fwnode = dev_fwnode(dev);
	mux_desc.set = it5205_mux_set;

	it->mux = typec_mux_register(dev, &mux_desc);
	if (IS_ERR(it->mux)) {
		typec_switch_unregister(it->sw);
		return dev_err_probe(dev, PTR_ERR(it->mux),
				     "failed to register typec mux\n");
	}

	i2c_set_clientdata(client, it);

	if (of_property_read_bool(dev->of_node, "ite,ovp-enable") && client->irq) {
		it5205_enable_ovp(it);

		ret = devm_request_threaded_irq(dev, client->irq, NULL,
						it5205_irq_handler,
						IRQF_ONESHOT, dev_name(dev), it);
		if (ret) {
			typec_mux_unregister(it->mux);
			typec_switch_unregister(it->sw);
			return dev_err_probe(dev, ret, "Failed to request irq\n");
		}
	}

	return 0;
}

static void it5205_remove(struct i2c_client *client)
{
	struct it5205 *it = i2c_get_clientdata(client);

	typec_mux_unregister(it->mux);
	typec_switch_unregister(it->sw);
}

static const struct i2c_device_id it5205_table[] = {
	{ "it5205" },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, it5205_table);

static const struct of_device_id it5205_of_table[] = {
	{ .compatible = "ite,it5205" },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, it5205_of_table);

static struct i2c_driver it5205_driver = {
	.driver = {
		.name = "it5205",
		.of_match_table = it5205_of_table,
	},
	.probe = it5205_probe,
	.remove = it5205_remove,
	.id_table = it5205_table,
};
module_i2c_driver(it5205_driver);

MODULE_AUTHOR("Tianping Fang <tianping.fang@mediatek.com>");
MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>");
MODULE_DESCRIPTION("ITE IT5205 alternate mode passive MUX driver");
MODULE_LICENSE("GPL");