Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/extcon/extcon-ptn5150.c
26378 views
1
// SPDX-License-Identifier: GPL-2.0+
2
//
3
// extcon-ptn5150.c - PTN5150 CC logic extcon driver to support USB detection
4
//
5
// Based on extcon-sm5502.c driver
6
// Copyright (c) 2018-2019 by Vijai Kumar K
7
// Author: Vijai Kumar K <[email protected]>
8
// Copyright (c) 2020 Krzysztof Kozlowski <[email protected]>
9
10
#include <linux/bitfield.h>
11
#include <linux/err.h>
12
#include <linux/i2c.h>
13
#include <linux/interrupt.h>
14
#include <linux/kernel.h>
15
#include <linux/module.h>
16
#include <linux/regmap.h>
17
#include <linux/slab.h>
18
#include <linux/extcon-provider.h>
19
#include <linux/gpio/consumer.h>
20
#include <linux/usb/role.h>
21
22
/* PTN5150 registers */
23
#define PTN5150_REG_DEVICE_ID 0x01
24
#define PTN5150_REG_CONTROL 0x02
25
#define PTN5150_REG_INT_STATUS 0x03
26
#define PTN5150_REG_CC_STATUS 0x04
27
#define PTN5150_REG_CON_DET 0x09
28
#define PTN5150_REG_VCONN_STATUS 0x0a
29
#define PTN5150_REG_RESET 0x0b
30
#define PTN5150_REG_INT_MASK 0x18
31
#define PTN5150_REG_INT_REG_STATUS 0x19
32
#define PTN5150_REG_END PTN5150_REG_INT_REG_STATUS
33
34
#define PTN5150_DFP_ATTACHED 0x1
35
#define PTN5150_UFP_ATTACHED 0x2
36
37
/* Define PTN5150 MASK/SHIFT constant */
38
#define PTN5150_REG_DEVICE_ID_VERSION GENMASK(7, 3)
39
#define PTN5150_REG_DEVICE_ID_VENDOR GENMASK(2, 0)
40
41
#define PTN5150_REG_CC_PORT_ATTACHMENT GENMASK(4, 2)
42
#define PTN5150_REG_CC_VBUS_DETECTION BIT(7)
43
#define PTN5150_REG_INT_CABLE_ATTACH_MASK BIT(0)
44
#define PTN5150_REG_INT_CABLE_DETACH_MASK BIT(1)
45
46
struct ptn5150_info {
47
struct device *dev;
48
struct extcon_dev *edev;
49
struct i2c_client *i2c;
50
struct regmap *regmap;
51
struct gpio_desc *int_gpiod;
52
struct gpio_desc *vbus_gpiod;
53
int irq;
54
struct work_struct irq_work;
55
struct mutex mutex;
56
struct usb_role_switch *role_sw;
57
};
58
59
/* List of detectable cables */
60
static const unsigned int ptn5150_extcon_cable[] = {
61
EXTCON_USB,
62
EXTCON_USB_HOST,
63
EXTCON_NONE,
64
};
65
66
static const struct regmap_config ptn5150_regmap_config = {
67
.reg_bits = 8,
68
.val_bits = 8,
69
.max_register = PTN5150_REG_END,
70
};
71
72
static void ptn5150_check_state(struct ptn5150_info *info)
73
{
74
unsigned int port_status, reg_data, vbus;
75
enum usb_role usb_role = USB_ROLE_NONE;
76
int ret;
77
78
ret = regmap_read(info->regmap, PTN5150_REG_CC_STATUS, &reg_data);
79
if (ret) {
80
dev_err(info->dev, "failed to read CC STATUS %d\n", ret);
81
return;
82
}
83
84
port_status = FIELD_GET(PTN5150_REG_CC_PORT_ATTACHMENT, reg_data);
85
86
switch (port_status) {
87
case PTN5150_DFP_ATTACHED:
88
extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false);
89
gpiod_set_value_cansleep(info->vbus_gpiod, 0);
90
extcon_set_state_sync(info->edev, EXTCON_USB, true);
91
usb_role = USB_ROLE_DEVICE;
92
break;
93
case PTN5150_UFP_ATTACHED:
94
extcon_set_state_sync(info->edev, EXTCON_USB, false);
95
vbus = FIELD_GET(PTN5150_REG_CC_VBUS_DETECTION, reg_data);
96
if (vbus)
97
gpiod_set_value_cansleep(info->vbus_gpiod, 0);
98
else
99
gpiod_set_value_cansleep(info->vbus_gpiod, 1);
100
101
extcon_set_state_sync(info->edev, EXTCON_USB_HOST, true);
102
usb_role = USB_ROLE_HOST;
103
break;
104
default:
105
break;
106
}
107
108
if (usb_role) {
109
ret = usb_role_switch_set_role(info->role_sw, usb_role);
110
if (ret)
111
dev_err(info->dev, "failed to set %s role: %d\n",
112
usb_role_string(usb_role), ret);
113
}
114
}
115
116
static void ptn5150_irq_work(struct work_struct *work)
117
{
118
struct ptn5150_info *info = container_of(work,
119
struct ptn5150_info, irq_work);
120
int ret = 0;
121
unsigned int int_status;
122
123
if (!info->edev)
124
return;
125
126
mutex_lock(&info->mutex);
127
128
/* Clear interrupt. Read would clear the register */
129
ret = regmap_read(info->regmap, PTN5150_REG_INT_STATUS, &int_status);
130
if (ret) {
131
dev_err(info->dev, "failed to read INT STATUS %d\n", ret);
132
mutex_unlock(&info->mutex);
133
return;
134
}
135
136
if (int_status) {
137
unsigned int cable_attach;
138
139
cable_attach = int_status & PTN5150_REG_INT_CABLE_ATTACH_MASK;
140
if (cable_attach) {
141
ptn5150_check_state(info);
142
} else {
143
extcon_set_state_sync(info->edev,
144
EXTCON_USB_HOST, false);
145
extcon_set_state_sync(info->edev,
146
EXTCON_USB, false);
147
gpiod_set_value_cansleep(info->vbus_gpiod, 0);
148
149
ret = usb_role_switch_set_role(info->role_sw,
150
USB_ROLE_NONE);
151
if (ret)
152
dev_err(info->dev,
153
"failed to set none role: %d\n",
154
ret);
155
}
156
}
157
158
/* Clear interrupt. Read would clear the register */
159
ret = regmap_read(info->regmap, PTN5150_REG_INT_REG_STATUS,
160
&int_status);
161
if (ret) {
162
dev_err(info->dev,
163
"failed to read INT REG STATUS %d\n", ret);
164
mutex_unlock(&info->mutex);
165
return;
166
}
167
168
mutex_unlock(&info->mutex);
169
}
170
171
172
static irqreturn_t ptn5150_irq_handler(int irq, void *data)
173
{
174
struct ptn5150_info *info = data;
175
176
schedule_work(&info->irq_work);
177
178
return IRQ_HANDLED;
179
}
180
181
static int ptn5150_init_dev_type(struct ptn5150_info *info)
182
{
183
unsigned int reg_data, vendor_id, version_id;
184
int ret;
185
186
ret = regmap_read(info->regmap, PTN5150_REG_DEVICE_ID, &reg_data);
187
if (ret) {
188
dev_err(info->dev, "failed to read DEVICE_ID %d\n", ret);
189
return -EINVAL;
190
}
191
192
vendor_id = FIELD_GET(PTN5150_REG_DEVICE_ID_VENDOR, reg_data);
193
version_id = FIELD_GET(PTN5150_REG_DEVICE_ID_VERSION, reg_data);
194
dev_dbg(info->dev, "Device type: version: 0x%x, vendor: 0x%x\n",
195
version_id, vendor_id);
196
197
/* Clear any existing interrupts */
198
ret = regmap_read(info->regmap, PTN5150_REG_INT_STATUS, &reg_data);
199
if (ret) {
200
dev_err(info->dev,
201
"failed to read PTN5150_REG_INT_STATUS %d\n",
202
ret);
203
return -EINVAL;
204
}
205
206
ret = regmap_read(info->regmap, PTN5150_REG_INT_REG_STATUS, &reg_data);
207
if (ret) {
208
dev_err(info->dev,
209
"failed to read PTN5150_REG_INT_REG_STATUS %d\n", ret);
210
return -EINVAL;
211
}
212
213
return 0;
214
}
215
216
static void ptn5150_work_sync_and_put(void *data)
217
{
218
struct ptn5150_info *info = data;
219
220
cancel_work_sync(&info->irq_work);
221
usb_role_switch_put(info->role_sw);
222
}
223
224
static int ptn5150_i2c_probe(struct i2c_client *i2c)
225
{
226
struct device *dev = &i2c->dev;
227
struct device_node *np = i2c->dev.of_node;
228
struct ptn5150_info *info;
229
int ret;
230
231
if (!np)
232
return -EINVAL;
233
234
info = devm_kzalloc(&i2c->dev, sizeof(*info), GFP_KERNEL);
235
if (!info)
236
return -ENOMEM;
237
i2c_set_clientdata(i2c, info);
238
239
info->dev = &i2c->dev;
240
info->i2c = i2c;
241
info->vbus_gpiod = devm_gpiod_get(&i2c->dev, "vbus", GPIOD_OUT_LOW);
242
if (IS_ERR(info->vbus_gpiod)) {
243
ret = PTR_ERR(info->vbus_gpiod);
244
if (ret == -ENOENT) {
245
dev_info(dev, "No VBUS GPIO, ignoring VBUS control\n");
246
info->vbus_gpiod = NULL;
247
} else {
248
return dev_err_probe(dev, ret, "failed to get VBUS GPIO\n");
249
}
250
}
251
252
mutex_init(&info->mutex);
253
254
INIT_WORK(&info->irq_work, ptn5150_irq_work);
255
256
info->regmap = devm_regmap_init_i2c(i2c, &ptn5150_regmap_config);
257
if (IS_ERR(info->regmap)) {
258
return dev_err_probe(info->dev, PTR_ERR(info->regmap),
259
"failed to allocate register map\n");
260
}
261
262
if (i2c->irq > 0) {
263
info->irq = i2c->irq;
264
} else {
265
info->int_gpiod = devm_gpiod_get(&i2c->dev, "int", GPIOD_IN);
266
if (IS_ERR(info->int_gpiod)) {
267
return dev_err_probe(dev, PTR_ERR(info->int_gpiod),
268
"failed to get INT GPIO\n");
269
}
270
271
info->irq = gpiod_to_irq(info->int_gpiod);
272
if (info->irq < 0) {
273
dev_err(dev, "failed to get INTB IRQ\n");
274
return info->irq;
275
}
276
}
277
278
ret = devm_request_threaded_irq(dev, info->irq, NULL,
279
ptn5150_irq_handler,
280
IRQF_TRIGGER_FALLING |
281
IRQF_ONESHOT,
282
i2c->name, info);
283
if (ret < 0) {
284
dev_err(dev, "failed to request handler for INTB IRQ\n");
285
return ret;
286
}
287
288
/* Allocate extcon device */
289
info->edev = devm_extcon_dev_allocate(info->dev, ptn5150_extcon_cable);
290
if (IS_ERR(info->edev)) {
291
dev_err(info->dev, "failed to allocate memory for extcon\n");
292
return -ENOMEM;
293
}
294
295
/* Register extcon device */
296
ret = devm_extcon_dev_register(info->dev, info->edev);
297
if (ret) {
298
dev_err(info->dev, "failed to register extcon device\n");
299
return ret;
300
}
301
302
extcon_set_property_capability(info->edev, EXTCON_USB,
303
EXTCON_PROP_USB_VBUS);
304
extcon_set_property_capability(info->edev, EXTCON_USB_HOST,
305
EXTCON_PROP_USB_VBUS);
306
extcon_set_property_capability(info->edev, EXTCON_USB_HOST,
307
EXTCON_PROP_USB_TYPEC_POLARITY);
308
309
/* Initialize PTN5150 device and print vendor id and version id */
310
ret = ptn5150_init_dev_type(info);
311
if (ret)
312
return -EINVAL;
313
314
info->role_sw = usb_role_switch_get(info->dev);
315
if (IS_ERR(info->role_sw))
316
return dev_err_probe(info->dev, PTR_ERR(info->role_sw),
317
"failed to get role switch\n");
318
319
ret = devm_add_action_or_reset(dev, ptn5150_work_sync_and_put, info);
320
if (ret)
321
return ret;
322
323
/*
324
* Update current extcon state if for example OTG connection was there
325
* before the probe
326
*/
327
mutex_lock(&info->mutex);
328
ptn5150_check_state(info);
329
mutex_unlock(&info->mutex);
330
331
return 0;
332
}
333
334
static const struct of_device_id ptn5150_dt_match[] = {
335
{ .compatible = "nxp,ptn5150" },
336
{ },
337
};
338
MODULE_DEVICE_TABLE(of, ptn5150_dt_match);
339
340
static const struct i2c_device_id ptn5150_i2c_id[] = {
341
{ "ptn5150" },
342
{ }
343
};
344
MODULE_DEVICE_TABLE(i2c, ptn5150_i2c_id);
345
346
static struct i2c_driver ptn5150_i2c_driver = {
347
.driver = {
348
.name = "ptn5150",
349
.of_match_table = ptn5150_dt_match,
350
},
351
.probe = ptn5150_i2c_probe,
352
.id_table = ptn5150_i2c_id,
353
};
354
module_i2c_driver(ptn5150_i2c_driver);
355
356
MODULE_DESCRIPTION("NXP PTN5150 CC logic Extcon driver");
357
MODULE_AUTHOR("Vijai Kumar K <[email protected]>");
358
MODULE_AUTHOR("Krzysztof Kozlowski <[email protected]>");
359
MODULE_LICENSE("GPL v2");
360
361