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 <vijaikumar.kanagarajan@gmail.com> |
8 | // Copyright (c) 2020 Krzysztof Kozlowski <krzk@kernel.org> |
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(map: info->regmap, PTN5150_REG_CC_STATUS, val: ®_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(edev: info->edev, EXTCON_USB_HOST, state: false); |
89 | gpiod_set_value_cansleep(desc: info->vbus_gpiod, value: 0); |
90 | extcon_set_state_sync(edev: info->edev, EXTCON_USB, state: true); |
91 | usb_role = USB_ROLE_DEVICE; |
92 | break; |
93 | case PTN5150_UFP_ATTACHED: |
94 | extcon_set_state_sync(edev: info->edev, EXTCON_USB, state: false); |
95 | vbus = FIELD_GET(PTN5150_REG_CC_VBUS_DETECTION, reg_data); |
96 | if (vbus) |
97 | gpiod_set_value_cansleep(desc: info->vbus_gpiod, value: 0); |
98 | else |
99 | gpiod_set_value_cansleep(desc: info->vbus_gpiod, value: 1); |
100 | |
101 | extcon_set_state_sync(edev: info->edev, EXTCON_USB_HOST, state: 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(sw: info->role_sw, role: 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(map: info->regmap, PTN5150_REG_INT_STATUS, val: &int_status); |
130 | if (ret) { |
131 | dev_err(info->dev, "failed to read INT STATUS %d\n" , ret); |
132 | mutex_unlock(lock: &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(edev: info->edev, |
144 | EXTCON_USB_HOST, state: false); |
145 | extcon_set_state_sync(edev: info->edev, |
146 | EXTCON_USB, state: false); |
147 | gpiod_set_value_cansleep(desc: info->vbus_gpiod, value: 0); |
148 | |
149 | ret = usb_role_switch_set_role(sw: info->role_sw, |
150 | role: 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(map: info->regmap, PTN5150_REG_INT_REG_STATUS, |
160 | val: &int_status); |
161 | if (ret) { |
162 | dev_err(info->dev, |
163 | "failed to read INT REG STATUS %d\n" , ret); |
164 | mutex_unlock(lock: &info->mutex); |
165 | return; |
166 | } |
167 | |
168 | mutex_unlock(lock: &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(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(map: info->regmap, PTN5150_REG_DEVICE_ID, val: ®_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(map: info->regmap, PTN5150_REG_INT_STATUS, val: ®_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(map: info->regmap, PTN5150_REG_INT_REG_STATUS, val: ®_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(work: &info->irq_work); |
221 | usb_role_switch_put(sw: 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(dev: &i2c->dev, size: sizeof(*info), GFP_KERNEL); |
235 | if (!info) |
236 | return -ENOMEM; |
237 | i2c_set_clientdata(client: i2c, data: info); |
238 | |
239 | info->dev = &i2c->dev; |
240 | info->i2c = i2c; |
241 | info->vbus_gpiod = devm_gpiod_get(dev: &i2c->dev, con_id: "vbus" , flags: GPIOD_OUT_LOW); |
242 | if (IS_ERR(ptr: info->vbus_gpiod)) { |
243 | ret = PTR_ERR(ptr: 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, err: ret, fmt: "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(ptr: info->regmap)) { |
258 | return dev_err_probe(dev: info->dev, err: PTR_ERR(ptr: info->regmap), |
259 | fmt: "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(dev: &i2c->dev, con_id: "int" , flags: GPIOD_IN); |
266 | if (IS_ERR(ptr: info->int_gpiod)) { |
267 | return dev_err_probe(dev, err: PTR_ERR(ptr: info->int_gpiod), |
268 | fmt: "failed to get INT GPIO\n" ); |
269 | } |
270 | |
271 | info->irq = gpiod_to_irq(desc: 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, irq: info->irq, NULL, |
279 | thread_fn: ptn5150_irq_handler, |
280 | IRQF_TRIGGER_FALLING | |
281 | IRQF_ONESHOT, |
282 | devname: i2c->name, dev_id: 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(dev: info->dev, cable: ptn5150_extcon_cable); |
290 | if (IS_ERR(ptr: 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(dev: info->dev, edev: 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(edev: info->edev, EXTCON_USB, |
303 | EXTCON_PROP_USB_VBUS); |
304 | extcon_set_property_capability(edev: info->edev, EXTCON_USB_HOST, |
305 | EXTCON_PROP_USB_VBUS); |
306 | extcon_set_property_capability(edev: 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(dev: info->dev); |
315 | if (IS_ERR(ptr: info->role_sw)) |
316 | return dev_err_probe(dev: info->dev, err: PTR_ERR(ptr: info->role_sw), |
317 | fmt: "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(lock: &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" , 0 }, |
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 <vijaikumar.kanagarajan@gmail.com>" ); |
358 | MODULE_AUTHOR("Krzysztof Kozlowski <krzk@kernel.org>" ); |
359 | MODULE_LICENSE("GPL v2" ); |
360 | |