.. | .. |
---|
9 | 9 | #include <linux/delay.h> |
---|
10 | 10 | #include <linux/errno.h> |
---|
11 | 11 | #include <linux/extcon.h> |
---|
12 | | -#include <linux/gpio.h> |
---|
| 12 | +#include <linux/gpio/consumer.h> |
---|
13 | 13 | #include <linux/i2c.h> |
---|
14 | 14 | #include <linux/interrupt.h> |
---|
15 | 15 | #include <linux/kernel.h> |
---|
16 | 16 | #include <linux/module.h> |
---|
17 | 17 | #include <linux/mutex.h> |
---|
18 | 18 | #include <linux/of_device.h> |
---|
19 | | -#include <linux/of_gpio.h> |
---|
20 | 19 | #include <linux/pinctrl/consumer.h> |
---|
21 | 20 | #include <linux/proc_fs.h> |
---|
22 | 21 | #include <linux/regulator/consumer.h> |
---|
23 | 22 | #include <linux/sched/clock.h> |
---|
24 | 23 | #include <linux/seq_file.h> |
---|
25 | 24 | #include <linux/slab.h> |
---|
| 25 | +#include <linux/spinlock.h> |
---|
26 | 26 | #include <linux/string.h> |
---|
27 | 27 | #include <linux/types.h> |
---|
| 28 | +#include <linux/usb.h> |
---|
28 | 29 | #include <linux/usb/typec.h> |
---|
29 | 30 | #include <linux/usb/tcpm.h> |
---|
30 | 31 | #include <linux/usb/pd.h> |
---|
.. | .. |
---|
74 | 75 | struct i2c_client *i2c_client; |
---|
75 | 76 | struct tcpm_port *tcpm_port; |
---|
76 | 77 | struct tcpc_dev tcpc_dev; |
---|
77 | | - struct tcpc_config tcpc_config; |
---|
78 | 78 | |
---|
79 | 79 | struct regulator *vbus; |
---|
80 | 80 | |
---|
81 | | - int gpio_int_n; |
---|
| 81 | + spinlock_t irq_lock; |
---|
| 82 | + struct kthread_work irq_work; |
---|
| 83 | + struct kthread_worker *irq_worker; |
---|
| 84 | + bool irq_suspended; |
---|
| 85 | + bool irq_while_suspended; |
---|
| 86 | + struct gpio_desc *gpio_int_n; |
---|
82 | 87 | int gpio_int_n_irq; |
---|
83 | 88 | struct extcon_dev *extcon; |
---|
84 | 89 | |
---|
85 | 90 | struct workqueue_struct *wq; |
---|
86 | 91 | struct delayed_work bc_lvl_handler; |
---|
87 | | - |
---|
88 | | - atomic_t pm_suspend; |
---|
89 | | - atomic_t i2c_busy; |
---|
90 | 92 | |
---|
91 | 93 | /* lock for sharing chip states */ |
---|
92 | 94 | struct mutex lock; |
---|
.. | .. |
---|
123 | 125 | */ |
---|
124 | 126 | |
---|
125 | 127 | #ifdef CONFIG_DEBUG_FS |
---|
126 | | - |
---|
127 | 128 | static bool fusb302_log_full(struct fusb302_chip *chip) |
---|
128 | 129 | { |
---|
129 | 130 | return chip->logbuffer_tail == |
---|
130 | 131 | (chip->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; |
---|
131 | 132 | } |
---|
132 | 133 | |
---|
| 134 | +__printf(2, 0) |
---|
133 | 135 | static void _fusb302_log(struct fusb302_chip *chip, const char *fmt, |
---|
134 | 136 | va_list args) |
---|
135 | 137 | { |
---|
.. | .. |
---|
177 | 179 | mutex_unlock(&chip->logbuffer_lock); |
---|
178 | 180 | } |
---|
179 | 181 | |
---|
| 182 | +__printf(2, 3) |
---|
180 | 183 | static void fusb302_log(struct fusb302_chip *chip, const char *fmt, ...) |
---|
181 | 184 | { |
---|
182 | 185 | va_list args; |
---|
.. | .. |
---|
205 | 208 | } |
---|
206 | 209 | DEFINE_SHOW_ATTRIBUTE(fusb302_debug); |
---|
207 | 210 | |
---|
208 | | -static struct dentry *rootdir; |
---|
209 | | - |
---|
210 | 211 | static void fusb302_debugfs_init(struct fusb302_chip *chip) |
---|
211 | 212 | { |
---|
212 | | - mutex_init(&chip->logbuffer_lock); |
---|
213 | | - if (!rootdir) |
---|
214 | | - rootdir = debugfs_create_dir("fusb302", NULL); |
---|
| 213 | + char name[NAME_MAX]; |
---|
215 | 214 | |
---|
216 | | - chip->dentry = debugfs_create_file(dev_name(chip->dev), |
---|
217 | | - S_IFREG | 0444, rootdir, |
---|
| 215 | + mutex_init(&chip->logbuffer_lock); |
---|
| 216 | + snprintf(name, NAME_MAX, "fusb302-%s", dev_name(chip->dev)); |
---|
| 217 | + chip->dentry = debugfs_create_file(name, S_IFREG | 0444, usb_debug_root, |
---|
218 | 218 | chip, &fusb302_debug_fops); |
---|
219 | 219 | } |
---|
220 | 220 | |
---|
221 | 221 | static void fusb302_debugfs_exit(struct fusb302_chip *chip) |
---|
222 | 222 | { |
---|
223 | 223 | debugfs_remove(chip->dentry); |
---|
224 | | - debugfs_remove(rootdir); |
---|
225 | 224 | } |
---|
226 | 225 | |
---|
227 | 226 | #else |
---|
.. | .. |
---|
233 | 232 | |
---|
234 | 233 | #endif |
---|
235 | 234 | |
---|
236 | | -#define FUSB302_RESUME_RETRY 10 |
---|
237 | | -#define FUSB302_RESUME_RETRY_SLEEP 50 |
---|
238 | | - |
---|
239 | | -static bool fusb302_is_suspended(struct fusb302_chip *chip) |
---|
240 | | -{ |
---|
241 | | - int retry_cnt; |
---|
242 | | - |
---|
243 | | - for (retry_cnt = 0; retry_cnt < FUSB302_RESUME_RETRY; retry_cnt++) { |
---|
244 | | - if (atomic_read(&chip->pm_suspend)) { |
---|
245 | | - dev_err(chip->dev, "i2c: pm suspend, retry %d/%d\n", |
---|
246 | | - retry_cnt + 1, FUSB302_RESUME_RETRY); |
---|
247 | | - msleep(FUSB302_RESUME_RETRY_SLEEP); |
---|
248 | | - } else { |
---|
249 | | - return false; |
---|
250 | | - } |
---|
251 | | - } |
---|
252 | | - |
---|
253 | | - return true; |
---|
254 | | -} |
---|
255 | | - |
---|
256 | 235 | static int fusb302_i2c_write(struct fusb302_chip *chip, |
---|
257 | 236 | u8 address, u8 data) |
---|
258 | 237 | { |
---|
259 | 238 | int ret = 0; |
---|
260 | 239 | |
---|
261 | | - atomic_set(&chip->i2c_busy, 1); |
---|
262 | | - |
---|
263 | | - if (fusb302_is_suspended(chip)) { |
---|
264 | | - atomic_set(&chip->i2c_busy, 0); |
---|
265 | | - return -ETIMEDOUT; |
---|
266 | | - } |
---|
267 | | - |
---|
268 | 240 | ret = i2c_smbus_write_byte_data(chip->i2c_client, address, data); |
---|
269 | 241 | if (ret < 0) |
---|
270 | 242 | fusb302_log(chip, "cannot write 0x%02x to 0x%02x, ret=%d", |
---|
271 | 243 | data, address, ret); |
---|
272 | | - atomic_set(&chip->i2c_busy, 0); |
---|
273 | 244 | |
---|
274 | 245 | return ret; |
---|
275 | 246 | } |
---|
.. | .. |
---|
281 | 252 | |
---|
282 | 253 | if (length <= 0) |
---|
283 | 254 | return ret; |
---|
284 | | - atomic_set(&chip->i2c_busy, 1); |
---|
285 | | - |
---|
286 | | - if (fusb302_is_suspended(chip)) { |
---|
287 | | - atomic_set(&chip->i2c_busy, 0); |
---|
288 | | - return -ETIMEDOUT; |
---|
289 | | - } |
---|
290 | 255 | |
---|
291 | 256 | ret = i2c_smbus_write_i2c_block_data(chip->i2c_client, address, |
---|
292 | 257 | length, data); |
---|
293 | 258 | if (ret < 0) |
---|
294 | 259 | fusb302_log(chip, "cannot block write 0x%02x, len=%d, ret=%d", |
---|
295 | 260 | address, length, ret); |
---|
296 | | - atomic_set(&chip->i2c_busy, 0); |
---|
297 | 261 | |
---|
298 | 262 | return ret; |
---|
299 | 263 | } |
---|
.. | .. |
---|
303 | 267 | { |
---|
304 | 268 | int ret = 0; |
---|
305 | 269 | |
---|
306 | | - atomic_set(&chip->i2c_busy, 1); |
---|
307 | | - |
---|
308 | | - if (fusb302_is_suspended(chip)) { |
---|
309 | | - atomic_set(&chip->i2c_busy, 0); |
---|
310 | | - return -ETIMEDOUT; |
---|
311 | | - } |
---|
312 | | - |
---|
313 | 270 | ret = i2c_smbus_read_byte_data(chip->i2c_client, address); |
---|
314 | 271 | *data = (u8)ret; |
---|
315 | 272 | if (ret < 0) |
---|
316 | 273 | fusb302_log(chip, "cannot read %02x, ret=%d", address, ret); |
---|
317 | | - atomic_set(&chip->i2c_busy, 0); |
---|
318 | 274 | |
---|
319 | 275 | return ret; |
---|
320 | 276 | } |
---|
.. | .. |
---|
326 | 282 | |
---|
327 | 283 | if (length <= 0) |
---|
328 | 284 | return ret; |
---|
329 | | - atomic_set(&chip->i2c_busy, 1); |
---|
330 | | - |
---|
331 | | - if (fusb302_is_suspended(chip)) { |
---|
332 | | - atomic_set(&chip->i2c_busy, 0); |
---|
333 | | - return -ETIMEDOUT; |
---|
334 | | - } |
---|
335 | 285 | |
---|
336 | 286 | ret = i2c_smbus_read_i2c_block_data(chip->i2c_client, address, |
---|
337 | 287 | length, data); |
---|
.. | .. |
---|
347 | 297 | } |
---|
348 | 298 | |
---|
349 | 299 | done: |
---|
350 | | - atomic_set(&chip->i2c_busy, 0); |
---|
351 | | - |
---|
352 | 300 | return ret; |
---|
353 | 301 | } |
---|
354 | 302 | |
---|
.. | .. |
---|
396 | 344 | return ret; |
---|
397 | 345 | } |
---|
398 | 346 | |
---|
399 | | -static int fusb302_enable_tx_auto_retries(struct fusb302_chip *chip) |
---|
| 347 | +static int fusb302_enable_tx_auto_retries(struct fusb302_chip *chip, u8 retry_count) |
---|
400 | 348 | { |
---|
401 | 349 | int ret = 0; |
---|
402 | 350 | |
---|
403 | | - ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, |
---|
404 | | - FUSB_REG_CONTROL3_N_RETRIES_3 | |
---|
| 351 | + ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, retry_count | |
---|
405 | 352 | FUSB_REG_CONTROL3_AUTO_RETRY); |
---|
406 | 353 | |
---|
407 | 354 | return ret; |
---|
.. | .. |
---|
442 | 389 | return ret; |
---|
443 | 390 | } |
---|
444 | 391 | |
---|
| 392 | +static int fusb302_rx_fifo_is_empty(struct fusb302_chip *chip) |
---|
| 393 | +{ |
---|
| 394 | + u8 data; |
---|
| 395 | + |
---|
| 396 | + return (fusb302_i2c_read(chip, FUSB_REG_STATUS1, &data) > 0) && |
---|
| 397 | + (data & FUSB_REG_STATUS1_RX_EMPTY); |
---|
| 398 | +} |
---|
| 399 | + |
---|
445 | 400 | static int tcpm_init(struct tcpc_dev *dev) |
---|
446 | 401 | { |
---|
447 | 402 | struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, |
---|
.. | .. |
---|
452 | 407 | ret = fusb302_sw_reset(chip); |
---|
453 | 408 | if (ret < 0) |
---|
454 | 409 | return ret; |
---|
455 | | - ret = fusb302_enable_tx_auto_retries(chip); |
---|
| 410 | + ret = fusb302_enable_tx_auto_retries(chip, FUSB_REG_CONTROL3_N_RETRIES_3); |
---|
456 | 411 | if (ret < 0) |
---|
457 | 412 | return ret; |
---|
458 | 413 | ret = fusb302_init_interrupt(chip); |
---|
.. | .. |
---|
516 | 471 | } while (current_limit == 0 && time_before(jiffies, timeout)); |
---|
517 | 472 | |
---|
518 | 473 | return current_limit; |
---|
519 | | -} |
---|
520 | | - |
---|
521 | | -static int fusb302_set_cc_pull(struct fusb302_chip *chip, |
---|
522 | | - bool pull_up, bool pull_down) |
---|
523 | | -{ |
---|
524 | | - int ret = 0; |
---|
525 | | - u8 data = 0x00; |
---|
526 | | - u8 mask = FUSB_REG_SWITCHES0_CC1_PU_EN | |
---|
527 | | - FUSB_REG_SWITCHES0_CC2_PU_EN | |
---|
528 | | - FUSB_REG_SWITCHES0_CC1_PD_EN | |
---|
529 | | - FUSB_REG_SWITCHES0_CC2_PD_EN; |
---|
530 | | - |
---|
531 | | - if (pull_up) |
---|
532 | | - data |= (chip->cc_polarity == TYPEC_POLARITY_CC1) ? |
---|
533 | | - FUSB_REG_SWITCHES0_CC1_PU_EN : |
---|
534 | | - FUSB_REG_SWITCHES0_CC2_PU_EN; |
---|
535 | | - if (pull_down) |
---|
536 | | - data |= FUSB_REG_SWITCHES0_CC1_PD_EN | |
---|
537 | | - FUSB_REG_SWITCHES0_CC2_PD_EN; |
---|
538 | | - ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, |
---|
539 | | - mask, data); |
---|
540 | | - if (ret < 0) |
---|
541 | | - return ret; |
---|
542 | | - |
---|
543 | | - return ret; |
---|
544 | 474 | } |
---|
545 | 475 | |
---|
546 | 476 | static int fusb302_set_src_current(struct fusb302_chip *chip, |
---|
.. | .. |
---|
676 | 606 | { |
---|
677 | 607 | struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, |
---|
678 | 608 | tcpc_dev); |
---|
| 609 | + u8 switches0_mask = FUSB_REG_SWITCHES0_CC1_PU_EN | |
---|
| 610 | + FUSB_REG_SWITCHES0_CC2_PU_EN | |
---|
| 611 | + FUSB_REG_SWITCHES0_CC1_PD_EN | |
---|
| 612 | + FUSB_REG_SWITCHES0_CC2_PD_EN; |
---|
| 613 | + u8 rd_mda, switches0_data = 0x00; |
---|
679 | 614 | int ret = 0; |
---|
680 | | - bool pull_up, pull_down; |
---|
681 | | - u8 rd_mda; |
---|
682 | | - enum toggling_mode mode; |
---|
683 | 615 | |
---|
684 | 616 | mutex_lock(&chip->lock); |
---|
685 | 617 | switch (cc) { |
---|
686 | 618 | case TYPEC_CC_OPEN: |
---|
687 | | - pull_up = false; |
---|
688 | | - pull_down = false; |
---|
689 | 619 | break; |
---|
690 | 620 | case TYPEC_CC_RD: |
---|
691 | | - pull_up = false; |
---|
692 | | - pull_down = true; |
---|
| 621 | + switches0_data |= FUSB_REG_SWITCHES0_CC1_PD_EN | |
---|
| 622 | + FUSB_REG_SWITCHES0_CC2_PD_EN; |
---|
693 | 623 | break; |
---|
694 | 624 | case TYPEC_CC_RP_DEF: |
---|
695 | 625 | case TYPEC_CC_RP_1_5: |
---|
696 | 626 | case TYPEC_CC_RP_3_0: |
---|
697 | | - pull_up = true; |
---|
698 | | - pull_down = false; |
---|
| 627 | + switches0_data |= (chip->cc_polarity == TYPEC_POLARITY_CC1) ? |
---|
| 628 | + FUSB_REG_SWITCHES0_CC1_PU_EN : |
---|
| 629 | + FUSB_REG_SWITCHES0_CC2_PU_EN; |
---|
699 | 630 | break; |
---|
700 | 631 | default: |
---|
701 | 632 | fusb302_log(chip, "unsupported cc value %s", |
---|
.. | .. |
---|
703 | 634 | ret = -EINVAL; |
---|
704 | 635 | goto done; |
---|
705 | 636 | } |
---|
| 637 | + |
---|
| 638 | + fusb302_log(chip, "cc := %s", typec_cc_status_name[cc]); |
---|
| 639 | + |
---|
706 | 640 | ret = fusb302_set_toggling(chip, TOGGLING_MODE_OFF); |
---|
707 | 641 | if (ret < 0) { |
---|
708 | | - fusb302_log(chip, "cannot stop toggling, ret=%d", ret); |
---|
| 642 | + fusb302_log(chip, "cannot set toggling mode, ret=%d", ret); |
---|
709 | 643 | goto done; |
---|
710 | 644 | } |
---|
711 | | - ret = fusb302_set_cc_pull(chip, pull_up, pull_down); |
---|
| 645 | + |
---|
| 646 | + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, |
---|
| 647 | + switches0_mask, switches0_data); |
---|
712 | 648 | if (ret < 0) { |
---|
713 | | - fusb302_log(chip, |
---|
714 | | - "cannot set cc pulling up %s, down %s, ret = %d", |
---|
715 | | - pull_up ? "True" : "False", |
---|
716 | | - pull_down ? "True" : "False", |
---|
717 | | - ret); |
---|
| 649 | + fusb302_log(chip, "cannot set pull-up/-down, ret = %d", ret); |
---|
718 | 650 | goto done; |
---|
719 | 651 | } |
---|
720 | 652 | /* reset the cc status */ |
---|
721 | 653 | chip->cc1 = TYPEC_CC_OPEN; |
---|
722 | 654 | chip->cc2 = TYPEC_CC_OPEN; |
---|
| 655 | + |
---|
723 | 656 | /* adjust current for SRC */ |
---|
724 | | - if (pull_up) { |
---|
725 | | - ret = fusb302_set_src_current(chip, cc_src_current[cc]); |
---|
726 | | - if (ret < 0) { |
---|
727 | | - fusb302_log(chip, "cannot set src current %s, ret=%d", |
---|
728 | | - typec_cc_status_name[cc], ret); |
---|
729 | | - goto done; |
---|
730 | | - } |
---|
| 657 | + ret = fusb302_set_src_current(chip, cc_src_current[cc]); |
---|
| 658 | + if (ret < 0) { |
---|
| 659 | + fusb302_log(chip, "cannot set src current %s, ret=%d", |
---|
| 660 | + typec_cc_status_name[cc], ret); |
---|
| 661 | + goto done; |
---|
731 | 662 | } |
---|
| 663 | + |
---|
732 | 664 | /* enable/disable interrupts, BC_LVL for SNK and COMP_CHNG for SRC */ |
---|
733 | | - if (pull_up) { |
---|
| 665 | + switch (cc) { |
---|
| 666 | + case TYPEC_CC_RP_DEF: |
---|
| 667 | + case TYPEC_CC_RP_1_5: |
---|
| 668 | + case TYPEC_CC_RP_3_0: |
---|
734 | 669 | rd_mda = rd_mda_value[cc_src_current[cc]]; |
---|
735 | 670 | ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); |
---|
736 | 671 | if (ret < 0) { |
---|
.. | .. |
---|
742 | 677 | ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK, |
---|
743 | 678 | FUSB_REG_MASK_BC_LVL | |
---|
744 | 679 | FUSB_REG_MASK_COMP_CHNG, |
---|
745 | | - FUSB_REG_MASK_COMP_CHNG); |
---|
| 680 | + FUSB_REG_MASK_BC_LVL); |
---|
746 | 681 | if (ret < 0) { |
---|
747 | 682 | fusb302_log(chip, "cannot set SRC interrupt, ret=%d", |
---|
748 | 683 | ret); |
---|
749 | 684 | goto done; |
---|
750 | 685 | } |
---|
751 | | - chip->intr_bc_lvl = false; |
---|
752 | 686 | chip->intr_comp_chng = true; |
---|
753 | | - } |
---|
754 | | - if (pull_down) { |
---|
| 687 | + chip->intr_bc_lvl = false; |
---|
| 688 | + break; |
---|
| 689 | + case TYPEC_CC_RD: |
---|
755 | 690 | ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK, |
---|
756 | 691 | FUSB_REG_MASK_BC_LVL | |
---|
757 | 692 | FUSB_REG_MASK_COMP_CHNG, |
---|
758 | | - FUSB_REG_MASK_BC_LVL); |
---|
| 693 | + FUSB_REG_MASK_COMP_CHNG); |
---|
759 | 694 | if (ret < 0) { |
---|
760 | 695 | fusb302_log(chip, "cannot set SRC interrupt, ret=%d", |
---|
761 | 696 | ret); |
---|
.. | .. |
---|
763 | 698 | } |
---|
764 | 699 | chip->intr_bc_lvl = true; |
---|
765 | 700 | chip->intr_comp_chng = false; |
---|
766 | | - } |
---|
767 | | - fusb302_log(chip, "cc := %s", typec_cc_status_name[cc]); |
---|
768 | | - |
---|
769 | | - /* Enable detection for fixed SNK or SRC only roles */ |
---|
770 | | - switch (cc) { |
---|
771 | | - case TYPEC_CC_RD: |
---|
772 | | - mode = TOGGLING_MODE_SNK; |
---|
773 | | - break; |
---|
774 | | - case TYPEC_CC_RP_DEF: |
---|
775 | | - case TYPEC_CC_RP_1_5: |
---|
776 | | - case TYPEC_CC_RP_3_0: |
---|
777 | | - mode = TOGGLING_MODE_SRC; |
---|
778 | 701 | break; |
---|
779 | 702 | default: |
---|
780 | | - mode = TOGGLING_MODE_OFF; |
---|
781 | 703 | break; |
---|
782 | | - } |
---|
783 | | - |
---|
784 | | - if (mode != TOGGLING_MODE_OFF) { |
---|
785 | | - ret = fusb302_set_toggling(chip, mode); |
---|
786 | | - if (ret < 0) |
---|
787 | | - fusb302_log(chip, |
---|
788 | | - "cannot set fixed role toggling mode, ret=%d", |
---|
789 | | - ret); |
---|
790 | 704 | } |
---|
791 | 705 | done: |
---|
792 | 706 | mutex_unlock(&chip->lock); |
---|
.. | .. |
---|
1011 | 925 | { |
---|
1012 | 926 | struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, |
---|
1013 | 927 | tcpc_dev); |
---|
| 928 | + enum toggling_mode mode = TOGGLING_MODE_OFF; |
---|
1014 | 929 | int ret = 0; |
---|
1015 | 930 | |
---|
1016 | | - if (port_type != TYPEC_PORT_DRP) |
---|
1017 | | - return -EOPNOTSUPP; |
---|
| 931 | + switch (port_type) { |
---|
| 932 | + case TYPEC_PORT_SRC: |
---|
| 933 | + mode = TOGGLING_MODE_SRC; |
---|
| 934 | + break; |
---|
| 935 | + case TYPEC_PORT_SNK: |
---|
| 936 | + mode = TOGGLING_MODE_SNK; |
---|
| 937 | + break; |
---|
| 938 | + case TYPEC_PORT_DRP: |
---|
| 939 | + mode = TOGGLING_MODE_DRP; |
---|
| 940 | + break; |
---|
| 941 | + } |
---|
1018 | 942 | |
---|
1019 | 943 | mutex_lock(&chip->lock); |
---|
1020 | 944 | ret = fusb302_set_src_current(chip, cc_src_current[cc]); |
---|
.. | .. |
---|
1023 | 947 | typec_cc_status_name[cc], ret); |
---|
1024 | 948 | goto done; |
---|
1025 | 949 | } |
---|
1026 | | - ret = fusb302_set_toggling(chip, TOGGLING_MODE_DRP); |
---|
| 950 | + ret = fusb302_set_toggling(chip, mode); |
---|
1027 | 951 | if (ret < 0) { |
---|
1028 | 952 | fusb302_log(chip, |
---|
1029 | 953 | "unable to start drp toggling, ret=%d", ret); |
---|
.. | .. |
---|
1103 | 1027 | }; |
---|
1104 | 1028 | |
---|
1105 | 1029 | static int tcpm_pd_transmit(struct tcpc_dev *dev, enum tcpm_transmit_type type, |
---|
1106 | | - const struct pd_message *msg) |
---|
| 1030 | + const struct pd_message *msg, unsigned int negotiated_rev) |
---|
1107 | 1031 | { |
---|
1108 | 1032 | struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, |
---|
1109 | 1033 | tcpc_dev); |
---|
.. | .. |
---|
1112 | 1036 | mutex_lock(&chip->lock); |
---|
1113 | 1037 | switch (type) { |
---|
1114 | 1038 | case TCPC_TX_SOP: |
---|
| 1039 | + /* nRetryCount 3 in P2.0 spec, whereas 2 in PD3.0 spec */ |
---|
| 1040 | + ret = fusb302_enable_tx_auto_retries(chip, negotiated_rev > PD_REV20 ? |
---|
| 1041 | + FUSB_REG_CONTROL3_N_RETRIES_2 : |
---|
| 1042 | + FUSB_REG_CONTROL3_N_RETRIES_3); |
---|
| 1043 | + if (ret < 0) |
---|
| 1044 | + fusb302_log(chip, "Cannot update retry count ret=%d", ret); |
---|
| 1045 | + |
---|
1115 | 1046 | ret = fusb302_pd_send_message(chip, msg); |
---|
1116 | 1047 | if (ret < 0) |
---|
1117 | 1048 | fusb302_log(chip, |
---|
.. | .. |
---|
1191 | 1122 | done: |
---|
1192 | 1123 | mutex_unlock(&chip->lock); |
---|
1193 | 1124 | } |
---|
1194 | | - |
---|
1195 | | -#define PDO_FIXED_FLAGS \ |
---|
1196 | | - (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM) |
---|
1197 | | - |
---|
1198 | | -static const u32 src_pdo[] = { |
---|
1199 | | - PDO_FIXED(5000, 400, PDO_FIXED_FLAGS), |
---|
1200 | | -}; |
---|
1201 | | - |
---|
1202 | | -static const struct tcpc_config fusb302_tcpc_config = { |
---|
1203 | | - .src_pdo = src_pdo, |
---|
1204 | | - .nr_src_pdo = ARRAY_SIZE(src_pdo), |
---|
1205 | | - .operating_snk_mw = 2500, |
---|
1206 | | - .type = TYPEC_PORT_DRP, |
---|
1207 | | - .data = TYPEC_PORT_DRD, |
---|
1208 | | - .default_role = TYPEC_SINK, |
---|
1209 | | - .alt_modes = NULL, |
---|
1210 | | -}; |
---|
1211 | 1125 | |
---|
1212 | 1126 | static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev) |
---|
1213 | 1127 | { |
---|
.. | .. |
---|
1436 | 1350 | } else if (cc2 == TYPEC_CC_RD && |
---|
1437 | 1351 | (cc1 == TYPEC_CC_OPEN || cc1 == TYPEC_CC_RA)) { |
---|
1438 | 1352 | cc_polarity = TYPEC_POLARITY_CC2; |
---|
| 1353 | + } else if (cc1 == TYPEC_CC_RA && cc2 == TYPEC_CC_RA) { |
---|
| 1354 | + cc_polarity = TYPEC_POLARITY_CC2; |
---|
1439 | 1355 | } else { |
---|
1440 | 1356 | fusb302_log(chip, "unexpected CC status cc1=%s, cc2=%s, restarting toggling", |
---|
1441 | 1357 | typec_cc_status_name[cc1], |
---|
.. | .. |
---|
1569 | 1485 | static irqreturn_t fusb302_irq_intn(int irq, void *dev_id) |
---|
1570 | 1486 | { |
---|
1571 | 1487 | struct fusb302_chip *chip = dev_id; |
---|
| 1488 | + unsigned long flags; |
---|
| 1489 | + |
---|
| 1490 | + /* Disable our level triggered IRQ until our irq_work has cleared it */ |
---|
| 1491 | + disable_irq_nosync(chip->gpio_int_n_irq); |
---|
| 1492 | + |
---|
| 1493 | + spin_lock_irqsave(&chip->irq_lock, flags); |
---|
| 1494 | + if (chip->irq_suspended) |
---|
| 1495 | + chip->irq_while_suspended = true; |
---|
| 1496 | + else |
---|
| 1497 | + kthread_queue_work(chip->irq_worker, &chip->irq_work); |
---|
| 1498 | + spin_unlock_irqrestore(&chip->irq_lock, flags); |
---|
| 1499 | + |
---|
| 1500 | + return IRQ_HANDLED; |
---|
| 1501 | +} |
---|
| 1502 | + |
---|
| 1503 | +static void fusb302_irq_work(struct kthread_work *work) |
---|
| 1504 | +{ |
---|
| 1505 | + struct fusb302_chip *chip = container_of(work, struct fusb302_chip, irq_work); |
---|
1572 | 1506 | int ret = 0; |
---|
1573 | 1507 | u8 interrupt; |
---|
1574 | 1508 | u8 interrupta; |
---|
.. | .. |
---|
1668 | 1602 | |
---|
1669 | 1603 | if (interrupta & FUSB_REG_INTERRUPTA_TX_SUCCESS) { |
---|
1670 | 1604 | fusb302_log(chip, "IRQ: PD tx success"); |
---|
1671 | | - ret = fusb302_pd_read_message(chip, &pd_msg); |
---|
1672 | | - if (ret < 0) { |
---|
1673 | | - fusb302_log(chip, |
---|
1674 | | - "cannot read in PD message, ret=%d", ret); |
---|
1675 | | - goto done; |
---|
1676 | | - } |
---|
| 1605 | + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS); |
---|
1677 | 1606 | } |
---|
1678 | 1607 | |
---|
1679 | 1608 | if (interrupta & FUSB_REG_INTERRUPTA_HARDRESET) { |
---|
.. | .. |
---|
1688 | 1617 | |
---|
1689 | 1618 | if (interruptb & FUSB_REG_INTERRUPTB_GCRCSENT) { |
---|
1690 | 1619 | fusb302_log(chip, "IRQ: PD sent good CRC"); |
---|
1691 | | - ret = fusb302_pd_read_message(chip, &pd_msg); |
---|
1692 | | - if (ret < 0) { |
---|
1693 | | - fusb302_log(chip, |
---|
1694 | | - "cannot read in PD message, ret=%d", ret); |
---|
1695 | | - goto done; |
---|
| 1620 | + |
---|
| 1621 | + while (!fusb302_rx_fifo_is_empty(chip)) { |
---|
| 1622 | + memset(&pd_msg, 0, sizeof(struct pd_message)); |
---|
| 1623 | + ret = fusb302_pd_read_message(chip, &pd_msg); |
---|
| 1624 | + if (ret < 0) { |
---|
| 1625 | + fusb302_log(chip, |
---|
| 1626 | + "cannot read in PD message, ret=%d", ret); |
---|
| 1627 | + goto done; |
---|
| 1628 | + } |
---|
1696 | 1629 | } |
---|
1697 | 1630 | } |
---|
1698 | 1631 | done: |
---|
1699 | 1632 | mutex_unlock(&chip->lock); |
---|
1700 | | - |
---|
1701 | | - return IRQ_HANDLED; |
---|
| 1633 | + enable_irq(chip->gpio_int_n_irq); |
---|
1702 | 1634 | } |
---|
1703 | 1635 | |
---|
1704 | 1636 | static int init_gpio(struct fusb302_chip *chip) |
---|
1705 | 1637 | { |
---|
1706 | | - struct device_node *node; |
---|
| 1638 | + struct device *dev = chip->dev; |
---|
1707 | 1639 | int ret = 0; |
---|
1708 | 1640 | |
---|
1709 | | - node = chip->dev->of_node; |
---|
1710 | | - chip->gpio_int_n = of_get_named_gpio(node, "fcs,int_n", 0); |
---|
1711 | | - if (!gpio_is_valid(chip->gpio_int_n)) { |
---|
1712 | | - ret = chip->gpio_int_n; |
---|
1713 | | - dev_err(chip->dev, "cannot get named GPIO Int_N, ret=%d", ret); |
---|
1714 | | - return ret; |
---|
| 1641 | + chip->gpio_int_n = devm_gpiod_get(dev, "fcs,int_n", GPIOD_IN); |
---|
| 1642 | + if (IS_ERR(chip->gpio_int_n)) { |
---|
| 1643 | + dev_err(dev, "failed to request gpio_int_n\n"); |
---|
| 1644 | + return PTR_ERR(chip->gpio_int_n); |
---|
1715 | 1645 | } |
---|
1716 | | - ret = devm_gpio_request(chip->dev, chip->gpio_int_n, "fcs,int_n"); |
---|
| 1646 | + ret = gpiod_to_irq(chip->gpio_int_n); |
---|
1717 | 1647 | if (ret < 0) { |
---|
1718 | | - dev_err(chip->dev, "cannot request GPIO Int_N, ret=%d", ret); |
---|
1719 | | - return ret; |
---|
1720 | | - } |
---|
1721 | | - ret = gpio_direction_input(chip->gpio_int_n); |
---|
1722 | | - if (ret < 0) { |
---|
1723 | | - dev_err(chip->dev, |
---|
1724 | | - "cannot set GPIO Int_N to input, ret=%d", ret); |
---|
1725 | | - return ret; |
---|
1726 | | - } |
---|
1727 | | - ret = gpio_to_irq(chip->gpio_int_n); |
---|
1728 | | - if (ret < 0) { |
---|
1729 | | - dev_err(chip->dev, |
---|
| 1648 | + dev_err(dev, |
---|
1730 | 1649 | "cannot request IRQ for GPIO Int_N, ret=%d", ret); |
---|
1731 | 1650 | return ret; |
---|
1732 | 1651 | } |
---|
.. | .. |
---|
1734 | 1653 | return 0; |
---|
1735 | 1654 | } |
---|
1736 | 1655 | |
---|
1737 | | -static int fusb302_composite_snk_pdo_array(struct fusb302_chip *chip) |
---|
| 1656 | +#define PDO_FIXED_FLAGS \ |
---|
| 1657 | + (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM) |
---|
| 1658 | + |
---|
| 1659 | +static const u32 src_pdo[] = { |
---|
| 1660 | + PDO_FIXED(5000, 400, PDO_FIXED_FLAGS) |
---|
| 1661 | +}; |
---|
| 1662 | + |
---|
| 1663 | +static const u32 snk_pdo[] = { |
---|
| 1664 | + PDO_FIXED(5000, 400, PDO_FIXED_FLAGS) |
---|
| 1665 | +}; |
---|
| 1666 | + |
---|
| 1667 | +static const struct property_entry port_props[] = { |
---|
| 1668 | + PROPERTY_ENTRY_STRING("data-role", "dual"), |
---|
| 1669 | + PROPERTY_ENTRY_STRING("power-role", "dual"), |
---|
| 1670 | + PROPERTY_ENTRY_STRING("try-power-role", "sink"), |
---|
| 1671 | + PROPERTY_ENTRY_U32_ARRAY("source-pdos", src_pdo), |
---|
| 1672 | + PROPERTY_ENTRY_U32_ARRAY("sink-pdos", snk_pdo), |
---|
| 1673 | + PROPERTY_ENTRY_U32("op-sink-microwatt", 2500000), |
---|
| 1674 | + { } |
---|
| 1675 | +}; |
---|
| 1676 | + |
---|
| 1677 | +static struct fwnode_handle *fusb302_fwnode_get(struct device *dev) |
---|
1738 | 1678 | { |
---|
1739 | | - struct device *dev = chip->dev; |
---|
1740 | | - u32 max_uv, max_ua; |
---|
| 1679 | + struct fwnode_handle *fwnode; |
---|
1741 | 1680 | |
---|
1742 | | - chip->snk_pdo[0] = PDO_FIXED(5000, 400, PDO_FIXED_FLAGS); |
---|
| 1681 | + fwnode = device_get_named_child_node(dev, "connector"); |
---|
| 1682 | + if (!fwnode) |
---|
| 1683 | + fwnode = fwnode_create_software_node(port_props, NULL); |
---|
1743 | 1684 | |
---|
1744 | | - /* |
---|
1745 | | - * As max_snk_ma/mv/mw is not needed for tcpc_config, |
---|
1746 | | - * those settings should be passed in via sink PDO, so |
---|
1747 | | - * "fcs, max-sink-*" properties will be deprecated, to |
---|
1748 | | - * perserve compatibility with existing users of them, |
---|
1749 | | - * we read those properties to convert them to be a var |
---|
1750 | | - * PDO. |
---|
1751 | | - */ |
---|
1752 | | - if (device_property_read_u32(dev, "fcs,max-sink-microvolt", &max_uv) || |
---|
1753 | | - device_property_read_u32(dev, "fcs,max-sink-microamp", &max_ua)) |
---|
1754 | | - return 1; |
---|
1755 | | - |
---|
1756 | | - chip->snk_pdo[1] = PDO_VAR(5000, max_uv / 1000, max_ua / 1000); |
---|
1757 | | - return 2; |
---|
| 1685 | + return fwnode; |
---|
1758 | 1686 | } |
---|
1759 | 1687 | |
---|
1760 | 1688 | static int fusb302_probe(struct i2c_client *client, |
---|
1761 | 1689 | const struct i2c_device_id *id) |
---|
1762 | 1690 | { |
---|
1763 | 1691 | struct fusb302_chip *chip; |
---|
1764 | | - struct i2c_adapter *adapter; |
---|
| 1692 | + struct i2c_adapter *adapter = client->adapter; |
---|
1765 | 1693 | struct device *dev = &client->dev; |
---|
1766 | 1694 | const char *name; |
---|
1767 | 1695 | int ret = 0; |
---|
1768 | | - u32 v; |
---|
1769 | 1696 | |
---|
1770 | | - adapter = to_i2c_adapter(client->dev.parent); |
---|
1771 | 1697 | if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) { |
---|
1772 | 1698 | dev_err(&client->dev, |
---|
1773 | 1699 | "I2C/SMBus block functionality not supported!\n"); |
---|
.. | .. |
---|
1779 | 1705 | |
---|
1780 | 1706 | chip->i2c_client = client; |
---|
1781 | 1707 | chip->dev = &client->dev; |
---|
1782 | | - chip->tcpc_config = fusb302_tcpc_config; |
---|
1783 | | - chip->tcpc_dev.config = &chip->tcpc_config; |
---|
1784 | 1708 | mutex_init(&chip->lock); |
---|
1785 | | - |
---|
1786 | | - chip->tcpc_dev.fwnode = |
---|
1787 | | - device_get_named_child_node(dev, "connector"); |
---|
1788 | | - |
---|
1789 | | - if (!device_property_read_u32(dev, "fcs,operating-sink-microwatt", &v)) |
---|
1790 | | - chip->tcpc_config.operating_snk_mw = v / 1000; |
---|
1791 | | - |
---|
1792 | | - /* Composite sink PDO */ |
---|
1793 | | - chip->tcpc_config.nr_snk_pdo = fusb302_composite_snk_pdo_array(chip); |
---|
1794 | | - chip->tcpc_config.snk_pdo = chip->snk_pdo; |
---|
1795 | 1709 | |
---|
1796 | 1710 | /* |
---|
1797 | 1711 | * Devicetree platforms should get extcon via phandle (not yet |
---|
.. | .. |
---|
1800 | 1714 | * to be set by the platform code which also registers the i2c client |
---|
1801 | 1715 | * for the fusb302. |
---|
1802 | 1716 | */ |
---|
1803 | | - if (device_property_read_string(dev, "fcs,extcon-name", &name) == 0) { |
---|
| 1717 | + if (device_property_read_string(dev, "linux,extcon-name", &name) == 0) { |
---|
1804 | 1718 | chip->extcon = extcon_get_extcon_dev(name); |
---|
1805 | 1719 | if (!chip->extcon) |
---|
1806 | 1720 | return -EPROBE_DEFER; |
---|
.. | .. |
---|
1814 | 1728 | if (!chip->wq) |
---|
1815 | 1729 | return -ENOMEM; |
---|
1816 | 1730 | |
---|
| 1731 | + chip->irq_worker = kthread_create_worker(0, dev_name(dev)); |
---|
| 1732 | + if (IS_ERR(chip->irq_worker)) |
---|
| 1733 | + return PTR_ERR(chip->irq_worker); |
---|
| 1734 | + sched_set_fifo(chip->irq_worker->task); |
---|
| 1735 | + |
---|
| 1736 | + spin_lock_init(&chip->irq_lock); |
---|
| 1737 | + kthread_init_work(&chip->irq_work, fusb302_irq_work); |
---|
1817 | 1738 | INIT_DELAYED_WORK(&chip->bc_lvl_handler, fusb302_bc_lvl_handler_work); |
---|
1818 | 1739 | init_tcpc_dev(&chip->tcpc_dev); |
---|
| 1740 | + fusb302_debugfs_init(chip); |
---|
1819 | 1741 | |
---|
1820 | 1742 | if (client->irq) { |
---|
1821 | 1743 | chip->gpio_int_n_irq = client->irq; |
---|
.. | .. |
---|
1825 | 1747 | goto destroy_workqueue; |
---|
1826 | 1748 | } |
---|
1827 | 1749 | |
---|
| 1750 | + chip->tcpc_dev.fwnode = fusb302_fwnode_get(dev); |
---|
| 1751 | + if (IS_ERR(chip->tcpc_dev.fwnode)) { |
---|
| 1752 | + ret = PTR_ERR(chip->tcpc_dev.fwnode); |
---|
| 1753 | + goto destroy_workqueue; |
---|
| 1754 | + } |
---|
| 1755 | + |
---|
1828 | 1756 | chip->tcpm_port = tcpm_register_port(&client->dev, &chip->tcpc_dev); |
---|
1829 | 1757 | if (IS_ERR(chip->tcpm_port)) { |
---|
| 1758 | + fwnode_handle_put(chip->tcpc_dev.fwnode); |
---|
1830 | 1759 | ret = PTR_ERR(chip->tcpm_port); |
---|
1831 | 1760 | if (ret != -EPROBE_DEFER) |
---|
1832 | 1761 | dev_err(dev, "cannot register tcpm port, ret=%d", ret); |
---|
1833 | 1762 | goto destroy_workqueue; |
---|
1834 | 1763 | } |
---|
1835 | 1764 | |
---|
1836 | | - ret = devm_request_threaded_irq(chip->dev, chip->gpio_int_n_irq, |
---|
1837 | | - NULL, fusb302_irq_intn, |
---|
1838 | | - IRQF_ONESHOT | IRQF_TRIGGER_LOW, |
---|
1839 | | - "fsc_interrupt_int_n", chip); |
---|
| 1765 | + ret = request_irq(chip->gpio_int_n_irq, fusb302_irq_intn, |
---|
| 1766 | + IRQF_TRIGGER_LOW, |
---|
| 1767 | + "fsc_interrupt_int_n", chip); |
---|
1840 | 1768 | if (ret < 0) { |
---|
1841 | 1769 | dev_err(dev, "cannot request IRQ for GPIO Int_N, ret=%d", ret); |
---|
1842 | 1770 | goto tcpm_unregister_port; |
---|
1843 | 1771 | } |
---|
1844 | 1772 | enable_irq_wake(chip->gpio_int_n_irq); |
---|
1845 | | - fusb302_debugfs_init(chip); |
---|
1846 | 1773 | i2c_set_clientdata(client, chip); |
---|
1847 | 1774 | |
---|
1848 | 1775 | return ret; |
---|
1849 | 1776 | |
---|
1850 | 1777 | tcpm_unregister_port: |
---|
1851 | 1778 | tcpm_unregister_port(chip->tcpm_port); |
---|
| 1779 | + fwnode_handle_put(chip->tcpc_dev.fwnode); |
---|
1852 | 1780 | destroy_workqueue: |
---|
| 1781 | + fusb302_debugfs_exit(chip); |
---|
1853 | 1782 | destroy_workqueue(chip->wq); |
---|
1854 | 1783 | |
---|
1855 | 1784 | return ret; |
---|
.. | .. |
---|
1859 | 1788 | { |
---|
1860 | 1789 | struct fusb302_chip *chip = i2c_get_clientdata(client); |
---|
1861 | 1790 | |
---|
| 1791 | + disable_irq_wake(chip->gpio_int_n_irq); |
---|
| 1792 | + free_irq(chip->gpio_int_n_irq, chip); |
---|
| 1793 | + kthread_destroy_worker(chip->irq_worker); |
---|
| 1794 | + cancel_delayed_work_sync(&chip->bc_lvl_handler); |
---|
1862 | 1795 | tcpm_unregister_port(chip->tcpm_port); |
---|
| 1796 | + fwnode_handle_put(chip->tcpc_dev.fwnode); |
---|
1863 | 1797 | destroy_workqueue(chip->wq); |
---|
1864 | 1798 | fusb302_debugfs_exit(chip); |
---|
1865 | 1799 | |
---|
.. | .. |
---|
1869 | 1803 | static int fusb302_pm_suspend(struct device *dev) |
---|
1870 | 1804 | { |
---|
1871 | 1805 | struct fusb302_chip *chip = dev->driver_data; |
---|
| 1806 | + unsigned long flags; |
---|
1872 | 1807 | |
---|
1873 | | - if (atomic_read(&chip->i2c_busy)) |
---|
1874 | | - return -EBUSY; |
---|
1875 | | - atomic_set(&chip->pm_suspend, 1); |
---|
| 1808 | + spin_lock_irqsave(&chip->irq_lock, flags); |
---|
| 1809 | + chip->irq_suspended = true; |
---|
| 1810 | + spin_unlock_irqrestore(&chip->irq_lock, flags); |
---|
1876 | 1811 | |
---|
| 1812 | + /* Make sure any pending irq_work is finished before the bus suspends */ |
---|
| 1813 | + kthread_flush_worker(chip->irq_worker); |
---|
1877 | 1814 | return 0; |
---|
1878 | 1815 | } |
---|
1879 | 1816 | |
---|
1880 | 1817 | static int fusb302_pm_resume(struct device *dev) |
---|
1881 | 1818 | { |
---|
1882 | 1819 | struct fusb302_chip *chip = dev->driver_data; |
---|
| 1820 | + unsigned long flags; |
---|
| 1821 | + u8 pwr; |
---|
| 1822 | + int ret = 0; |
---|
1883 | 1823 | |
---|
1884 | | - atomic_set(&chip->pm_suspend, 0); |
---|
| 1824 | + /* |
---|
| 1825 | + * When the power of fusb302 is lost or i2c read failed in PM S/R |
---|
| 1826 | + * process, we must reset the tcpm port first to ensure the devices |
---|
| 1827 | + * can attach again. |
---|
| 1828 | + */ |
---|
| 1829 | + ret = fusb302_i2c_read(chip, FUSB_REG_POWER, &pwr); |
---|
| 1830 | + if (pwr != FUSB_REG_POWER_PWR_ALL || ret < 0) |
---|
| 1831 | + tcpm_tcpc_reset(chip->tcpm_port); |
---|
| 1832 | + |
---|
| 1833 | + spin_lock_irqsave(&chip->irq_lock, flags); |
---|
| 1834 | + if (chip->irq_while_suspended) { |
---|
| 1835 | + kthread_queue_work(chip->irq_worker, &chip->irq_work); |
---|
| 1836 | + chip->irq_while_suspended = false; |
---|
| 1837 | + } |
---|
| 1838 | + chip->irq_suspended = false; |
---|
| 1839 | + spin_unlock_irqrestore(&chip->irq_lock, flags); |
---|
1885 | 1840 | |
---|
1886 | 1841 | return 0; |
---|
1887 | 1842 | } |
---|