.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * Copyright (C) 2013 NVIDIA Corporation |
---|
3 | | - * |
---|
4 | | - * This program is free software; you can redistribute it and/or modify |
---|
5 | | - * it under the terms of the GNU General Public License version 2 as |
---|
6 | | - * published by the Free Software Foundation. |
---|
7 | 4 | */ |
---|
8 | 5 | |
---|
9 | 6 | #include <linux/clk.h> |
---|
10 | 7 | #include <linux/delay.h> |
---|
11 | | -#include <linux/gpio.h> |
---|
12 | 8 | #include <linux/interrupt.h> |
---|
13 | 9 | #include <linux/io.h> |
---|
14 | | -#include <linux/of_gpio.h> |
---|
| 10 | +#include <linux/module.h> |
---|
| 11 | +#include <linux/of_device.h> |
---|
15 | 12 | #include <linux/pinctrl/pinconf-generic.h> |
---|
16 | 13 | #include <linux/pinctrl/pinctrl.h> |
---|
17 | 14 | #include <linux/pinctrl/pinmux.h> |
---|
18 | | -#include <linux/pm_runtime.h> |
---|
19 | 15 | #include <linux/platform_device.h> |
---|
20 | | -#include <linux/reset.h> |
---|
| 16 | +#include <linux/pm_runtime.h> |
---|
21 | 17 | #include <linux/regulator/consumer.h> |
---|
| 18 | +#include <linux/reset.h> |
---|
22 | 19 | #include <linux/workqueue.h> |
---|
23 | 20 | |
---|
24 | 21 | #include <drm/drm_dp_helper.h> |
---|
25 | 22 | #include <drm/drm_panel.h> |
---|
26 | 23 | |
---|
| 24 | +#include "dp.h" |
---|
27 | 25 | #include "dpaux.h" |
---|
28 | 26 | #include "drm.h" |
---|
29 | 27 | #include "trace.h" |
---|
.. | .. |
---|
31 | 29 | static DEFINE_MUTEX(dpaux_lock); |
---|
32 | 30 | static LIST_HEAD(dpaux_list); |
---|
33 | 31 | |
---|
| 32 | +struct tegra_dpaux_soc { |
---|
| 33 | + unsigned int cmh; |
---|
| 34 | + unsigned int drvz; |
---|
| 35 | + unsigned int drvi; |
---|
| 36 | +}; |
---|
| 37 | + |
---|
34 | 38 | struct tegra_dpaux { |
---|
35 | 39 | struct drm_dp_aux aux; |
---|
36 | 40 | struct device *dev; |
---|
| 41 | + |
---|
| 42 | + const struct tegra_dpaux_soc *soc; |
---|
37 | 43 | |
---|
38 | 44 | void __iomem *regs; |
---|
39 | 45 | int irq; |
---|
.. | .. |
---|
122 | 128 | struct tegra_dpaux *dpaux = to_dpaux(aux); |
---|
123 | 129 | unsigned long status; |
---|
124 | 130 | ssize_t ret = 0; |
---|
| 131 | + u8 reply = 0; |
---|
125 | 132 | u32 value; |
---|
126 | 133 | |
---|
127 | 134 | /* Tegra has 4x4 byte DP AUX transmit and receive FIFOs. */ |
---|
.. | .. |
---|
216 | 223 | |
---|
217 | 224 | switch ((value & DPAUX_DP_AUXSTAT_REPLY_TYPE_MASK) >> 16) { |
---|
218 | 225 | case 0x00: |
---|
219 | | - msg->reply = DP_AUX_NATIVE_REPLY_ACK; |
---|
| 226 | + reply = DP_AUX_NATIVE_REPLY_ACK; |
---|
220 | 227 | break; |
---|
221 | 228 | |
---|
222 | 229 | case 0x01: |
---|
223 | | - msg->reply = DP_AUX_NATIVE_REPLY_NACK; |
---|
| 230 | + reply = DP_AUX_NATIVE_REPLY_NACK; |
---|
224 | 231 | break; |
---|
225 | 232 | |
---|
226 | 233 | case 0x02: |
---|
227 | | - msg->reply = DP_AUX_NATIVE_REPLY_DEFER; |
---|
| 234 | + reply = DP_AUX_NATIVE_REPLY_DEFER; |
---|
228 | 235 | break; |
---|
229 | 236 | |
---|
230 | 237 | case 0x04: |
---|
231 | | - msg->reply = DP_AUX_I2C_REPLY_NACK; |
---|
| 238 | + reply = DP_AUX_I2C_REPLY_NACK; |
---|
232 | 239 | break; |
---|
233 | 240 | |
---|
234 | 241 | case 0x08: |
---|
235 | | - msg->reply = DP_AUX_I2C_REPLY_DEFER; |
---|
| 242 | + reply = DP_AUX_I2C_REPLY_DEFER; |
---|
236 | 243 | break; |
---|
237 | 244 | } |
---|
238 | 245 | |
---|
.. | .. |
---|
240 | 247 | if (msg->request & DP_AUX_I2C_READ) { |
---|
241 | 248 | size_t count = value & DPAUX_DP_AUXSTAT_REPLY_MASK; |
---|
242 | 249 | |
---|
243 | | - if (WARN_ON(count != msg->size)) |
---|
244 | | - count = min_t(size_t, count, msg->size); |
---|
| 250 | + /* |
---|
| 251 | + * There might be a smarter way to do this, but since |
---|
| 252 | + * the DP helpers will already retry transactions for |
---|
| 253 | + * an -EBUSY return value, simply reuse that instead. |
---|
| 254 | + */ |
---|
| 255 | + if (count != msg->size) { |
---|
| 256 | + ret = -EBUSY; |
---|
| 257 | + goto out; |
---|
| 258 | + } |
---|
245 | 259 | |
---|
246 | 260 | tegra_dpaux_read_fifo(dpaux, msg->buffer, count); |
---|
247 | 261 | ret = count; |
---|
248 | 262 | } |
---|
249 | 263 | } |
---|
250 | 264 | |
---|
| 265 | + msg->reply = reply; |
---|
| 266 | + |
---|
| 267 | +out: |
---|
251 | 268 | return ret; |
---|
252 | 269 | } |
---|
253 | 270 | |
---|
.. | .. |
---|
312 | 329 | |
---|
313 | 330 | switch (function) { |
---|
314 | 331 | case DPAUX_PADCTL_FUNC_AUX: |
---|
315 | | - value = DPAUX_HYBRID_PADCTL_AUX_CMH(2) | |
---|
316 | | - DPAUX_HYBRID_PADCTL_AUX_DRVZ(4) | |
---|
317 | | - DPAUX_HYBRID_PADCTL_AUX_DRVI(0x18) | |
---|
| 332 | + value = DPAUX_HYBRID_PADCTL_AUX_CMH(dpaux->soc->cmh) | |
---|
| 333 | + DPAUX_HYBRID_PADCTL_AUX_DRVZ(dpaux->soc->drvz) | |
---|
| 334 | + DPAUX_HYBRID_PADCTL_AUX_DRVI(dpaux->soc->drvi) | |
---|
318 | 335 | DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV | |
---|
319 | 336 | DPAUX_HYBRID_PADCTL_MODE_AUX; |
---|
320 | 337 | break; |
---|
.. | .. |
---|
322 | 339 | case DPAUX_PADCTL_FUNC_I2C: |
---|
323 | 340 | value = DPAUX_HYBRID_PADCTL_I2C_SDA_INPUT_RCV | |
---|
324 | 341 | DPAUX_HYBRID_PADCTL_I2C_SCL_INPUT_RCV | |
---|
325 | | - DPAUX_HYBRID_PADCTL_AUX_CMH(2) | |
---|
326 | | - DPAUX_HYBRID_PADCTL_AUX_DRVZ(4) | |
---|
327 | | - DPAUX_HYBRID_PADCTL_AUX_DRVI(0x18) | |
---|
| 342 | + DPAUX_HYBRID_PADCTL_AUX_CMH(dpaux->soc->cmh) | |
---|
| 343 | + DPAUX_HYBRID_PADCTL_AUX_DRVZ(dpaux->soc->drvz) | |
---|
| 344 | + DPAUX_HYBRID_PADCTL_AUX_DRVI(dpaux->soc->drvi) | |
---|
328 | 345 | DPAUX_HYBRID_PADCTL_MODE_I2C; |
---|
329 | 346 | break; |
---|
330 | 347 | |
---|
.. | .. |
---|
438 | 455 | if (!dpaux) |
---|
439 | 456 | return -ENOMEM; |
---|
440 | 457 | |
---|
| 458 | + dpaux->soc = of_device_get_match_data(&pdev->dev); |
---|
441 | 459 | INIT_WORK(&dpaux->work, tegra_dpaux_hotplug); |
---|
442 | 460 | init_completion(&dpaux->complete); |
---|
443 | 461 | INIT_LIST_HEAD(&dpaux->list); |
---|
.. | .. |
---|
449 | 467 | return PTR_ERR(dpaux->regs); |
---|
450 | 468 | |
---|
451 | 469 | dpaux->irq = platform_get_irq(pdev, 0); |
---|
452 | | - if (dpaux->irq < 0) { |
---|
453 | | - dev_err(&pdev->dev, "failed to get IRQ\n"); |
---|
454 | | - return -ENXIO; |
---|
455 | | - } |
---|
| 470 | + if (dpaux->irq < 0) |
---|
| 471 | + return dpaux->irq; |
---|
456 | 472 | |
---|
457 | 473 | if (!pdev->dev.pm_domain) { |
---|
458 | 474 | dpaux->rst = devm_reset_control_get(&pdev->dev, "dpaux"); |
---|
.. | .. |
---|
485 | 501 | return err; |
---|
486 | 502 | } |
---|
487 | 503 | |
---|
488 | | - dpaux->vdd = devm_regulator_get(&pdev->dev, "vdd"); |
---|
| 504 | + dpaux->vdd = devm_regulator_get_optional(&pdev->dev, "vdd"); |
---|
489 | 505 | if (IS_ERR(dpaux->vdd)) { |
---|
490 | | - dev_err(&pdev->dev, "failed to get VDD supply: %ld\n", |
---|
491 | | - PTR_ERR(dpaux->vdd)); |
---|
492 | | - return PTR_ERR(dpaux->vdd); |
---|
| 506 | + if (PTR_ERR(dpaux->vdd) != -ENODEV) { |
---|
| 507 | + if (PTR_ERR(dpaux->vdd) != -EPROBE_DEFER) |
---|
| 508 | + dev_err(&pdev->dev, |
---|
| 509 | + "failed to get VDD supply: %ld\n", |
---|
| 510 | + PTR_ERR(dpaux->vdd)); |
---|
| 511 | + |
---|
| 512 | + return PTR_ERR(dpaux->vdd); |
---|
| 513 | + } |
---|
| 514 | + |
---|
| 515 | + dpaux->vdd = NULL; |
---|
493 | 516 | } |
---|
494 | 517 | |
---|
495 | 518 | platform_set_drvdata(pdev, dpaux); |
---|
.. | .. |
---|
521 | 544 | * is no possibility to perform the I2C mode configuration in the |
---|
522 | 545 | * HDMI path. |
---|
523 | 546 | */ |
---|
524 | | - err = tegra_dpaux_pad_config(dpaux, DPAUX_HYBRID_PADCTL_MODE_I2C); |
---|
| 547 | + err = tegra_dpaux_pad_config(dpaux, DPAUX_PADCTL_FUNC_I2C); |
---|
525 | 548 | if (err < 0) |
---|
526 | 549 | return err; |
---|
527 | 550 | |
---|
.. | .. |
---|
561 | 584 | /* make sure pads are powered down when not in use */ |
---|
562 | 585 | tegra_dpaux_pad_power_down(dpaux); |
---|
563 | 586 | |
---|
564 | | - pm_runtime_put(&pdev->dev); |
---|
| 587 | + pm_runtime_put_sync(&pdev->dev); |
---|
565 | 588 | pm_runtime_disable(&pdev->dev); |
---|
566 | 589 | |
---|
567 | 590 | drm_dp_aux_unregister(&dpaux->aux); |
---|
.. | .. |
---|
638 | 661 | SET_RUNTIME_PM_OPS(tegra_dpaux_suspend, tegra_dpaux_resume, NULL) |
---|
639 | 662 | }; |
---|
640 | 663 | |
---|
| 664 | +static const struct tegra_dpaux_soc tegra124_dpaux_soc = { |
---|
| 665 | + .cmh = 0x02, |
---|
| 666 | + .drvz = 0x04, |
---|
| 667 | + .drvi = 0x18, |
---|
| 668 | +}; |
---|
| 669 | + |
---|
| 670 | +static const struct tegra_dpaux_soc tegra210_dpaux_soc = { |
---|
| 671 | + .cmh = 0x02, |
---|
| 672 | + .drvz = 0x04, |
---|
| 673 | + .drvi = 0x30, |
---|
| 674 | +}; |
---|
| 675 | + |
---|
| 676 | +static const struct tegra_dpaux_soc tegra194_dpaux_soc = { |
---|
| 677 | + .cmh = 0x02, |
---|
| 678 | + .drvz = 0x04, |
---|
| 679 | + .drvi = 0x2c, |
---|
| 680 | +}; |
---|
| 681 | + |
---|
641 | 682 | static const struct of_device_id tegra_dpaux_of_match[] = { |
---|
642 | | - { .compatible = "nvidia,tegra186-dpaux", }, |
---|
643 | | - { .compatible = "nvidia,tegra210-dpaux", }, |
---|
644 | | - { .compatible = "nvidia,tegra124-dpaux", }, |
---|
| 683 | + { .compatible = "nvidia,tegra194-dpaux", .data = &tegra194_dpaux_soc }, |
---|
| 684 | + { .compatible = "nvidia,tegra186-dpaux", .data = &tegra210_dpaux_soc }, |
---|
| 685 | + { .compatible = "nvidia,tegra210-dpaux", .data = &tegra210_dpaux_soc }, |
---|
| 686 | + { .compatible = "nvidia,tegra124-dpaux", .data = &tegra124_dpaux_soc }, |
---|
645 | 687 | { }, |
---|
646 | 688 | }; |
---|
647 | 689 | MODULE_DEVICE_TABLE(of, tegra_dpaux_of_match); |
---|
.. | .. |
---|
682 | 724 | output->connector.polled = DRM_CONNECTOR_POLL_HPD; |
---|
683 | 725 | dpaux->output = output; |
---|
684 | 726 | |
---|
685 | | - err = regulator_enable(dpaux->vdd); |
---|
686 | | - if (err < 0) |
---|
687 | | - return err; |
---|
688 | | - |
---|
689 | | - timeout = jiffies + msecs_to_jiffies(250); |
---|
690 | | - |
---|
691 | | - while (time_before(jiffies, timeout)) { |
---|
| 727 | + if (output->panel) { |
---|
692 | 728 | enum drm_connector_status status; |
---|
693 | 729 | |
---|
694 | | - status = drm_dp_aux_detect(aux); |
---|
695 | | - if (status == connector_status_connected) { |
---|
696 | | - enable_irq(dpaux->irq); |
---|
697 | | - return 0; |
---|
| 730 | + if (dpaux->vdd) { |
---|
| 731 | + err = regulator_enable(dpaux->vdd); |
---|
| 732 | + if (err < 0) |
---|
| 733 | + return err; |
---|
698 | 734 | } |
---|
699 | 735 | |
---|
700 | | - usleep_range(1000, 2000); |
---|
| 736 | + timeout = jiffies + msecs_to_jiffies(250); |
---|
| 737 | + |
---|
| 738 | + while (time_before(jiffies, timeout)) { |
---|
| 739 | + status = drm_dp_aux_detect(aux); |
---|
| 740 | + |
---|
| 741 | + if (status == connector_status_connected) |
---|
| 742 | + break; |
---|
| 743 | + |
---|
| 744 | + usleep_range(1000, 2000); |
---|
| 745 | + } |
---|
| 746 | + |
---|
| 747 | + if (status != connector_status_connected) |
---|
| 748 | + return -ETIMEDOUT; |
---|
701 | 749 | } |
---|
702 | 750 | |
---|
703 | | - return -ETIMEDOUT; |
---|
| 751 | + enable_irq(dpaux->irq); |
---|
| 752 | + return 0; |
---|
704 | 753 | } |
---|
705 | 754 | |
---|
706 | 755 | int drm_dp_aux_detach(struct drm_dp_aux *aux) |
---|
.. | .. |
---|
711 | 760 | |
---|
712 | 761 | disable_irq(dpaux->irq); |
---|
713 | 762 | |
---|
714 | | - err = regulator_disable(dpaux->vdd); |
---|
715 | | - if (err < 0) |
---|
716 | | - return err; |
---|
717 | | - |
---|
718 | | - timeout = jiffies + msecs_to_jiffies(250); |
---|
719 | | - |
---|
720 | | - while (time_before(jiffies, timeout)) { |
---|
| 763 | + if (dpaux->output->panel) { |
---|
721 | 764 | enum drm_connector_status status; |
---|
722 | 765 | |
---|
723 | | - status = drm_dp_aux_detect(aux); |
---|
724 | | - if (status == connector_status_disconnected) { |
---|
725 | | - dpaux->output = NULL; |
---|
726 | | - return 0; |
---|
| 766 | + if (dpaux->vdd) { |
---|
| 767 | + err = regulator_disable(dpaux->vdd); |
---|
| 768 | + if (err < 0) |
---|
| 769 | + return err; |
---|
727 | 770 | } |
---|
728 | 771 | |
---|
729 | | - usleep_range(1000, 2000); |
---|
| 772 | + timeout = jiffies + msecs_to_jiffies(250); |
---|
| 773 | + |
---|
| 774 | + while (time_before(jiffies, timeout)) { |
---|
| 775 | + status = drm_dp_aux_detect(aux); |
---|
| 776 | + |
---|
| 777 | + if (status == connector_status_disconnected) |
---|
| 778 | + break; |
---|
| 779 | + |
---|
| 780 | + usleep_range(1000, 2000); |
---|
| 781 | + } |
---|
| 782 | + |
---|
| 783 | + if (status != connector_status_disconnected) |
---|
| 784 | + return -ETIMEDOUT; |
---|
| 785 | + |
---|
| 786 | + dpaux->output = NULL; |
---|
730 | 787 | } |
---|
731 | 788 | |
---|
732 | | - return -ETIMEDOUT; |
---|
| 789 | + return 0; |
---|
733 | 790 | } |
---|
734 | 791 | |
---|
735 | 792 | enum drm_connector_status drm_dp_aux_detect(struct drm_dp_aux *aux) |
---|
.. | .. |
---|
757 | 814 | struct tegra_dpaux *dpaux = to_dpaux(aux); |
---|
758 | 815 | |
---|
759 | 816 | tegra_dpaux_pad_power_down(dpaux); |
---|
760 | | - |
---|
761 | | - return 0; |
---|
762 | | -} |
---|
763 | | - |
---|
764 | | -int drm_dp_aux_prepare(struct drm_dp_aux *aux, u8 encoding) |
---|
765 | | -{ |
---|
766 | | - int err; |
---|
767 | | - |
---|
768 | | - err = drm_dp_dpcd_writeb(aux, DP_MAIN_LINK_CHANNEL_CODING_SET, |
---|
769 | | - encoding); |
---|
770 | | - if (err < 0) |
---|
771 | | - return err; |
---|
772 | | - |
---|
773 | | - return 0; |
---|
774 | | -} |
---|
775 | | - |
---|
776 | | -int drm_dp_aux_train(struct drm_dp_aux *aux, struct drm_dp_link *link, |
---|
777 | | - u8 pattern) |
---|
778 | | -{ |
---|
779 | | - u8 tp = pattern & DP_TRAINING_PATTERN_MASK; |
---|
780 | | - u8 status[DP_LINK_STATUS_SIZE], values[4]; |
---|
781 | | - unsigned int i; |
---|
782 | | - int err; |
---|
783 | | - |
---|
784 | | - err = drm_dp_dpcd_writeb(aux, DP_TRAINING_PATTERN_SET, pattern); |
---|
785 | | - if (err < 0) |
---|
786 | | - return err; |
---|
787 | | - |
---|
788 | | - if (tp == DP_TRAINING_PATTERN_DISABLE) |
---|
789 | | - return 0; |
---|
790 | | - |
---|
791 | | - for (i = 0; i < link->num_lanes; i++) |
---|
792 | | - values[i] = DP_TRAIN_MAX_PRE_EMPHASIS_REACHED | |
---|
793 | | - DP_TRAIN_PRE_EMPH_LEVEL_0 | |
---|
794 | | - DP_TRAIN_MAX_SWING_REACHED | |
---|
795 | | - DP_TRAIN_VOLTAGE_SWING_LEVEL_0; |
---|
796 | | - |
---|
797 | | - err = drm_dp_dpcd_write(aux, DP_TRAINING_LANE0_SET, values, |
---|
798 | | - link->num_lanes); |
---|
799 | | - if (err < 0) |
---|
800 | | - return err; |
---|
801 | | - |
---|
802 | | - usleep_range(500, 1000); |
---|
803 | | - |
---|
804 | | - err = drm_dp_dpcd_read_link_status(aux, status); |
---|
805 | | - if (err < 0) |
---|
806 | | - return err; |
---|
807 | | - |
---|
808 | | - switch (tp) { |
---|
809 | | - case DP_TRAINING_PATTERN_1: |
---|
810 | | - if (!drm_dp_clock_recovery_ok(status, link->num_lanes)) |
---|
811 | | - return -EAGAIN; |
---|
812 | | - |
---|
813 | | - break; |
---|
814 | | - |
---|
815 | | - case DP_TRAINING_PATTERN_2: |
---|
816 | | - if (!drm_dp_channel_eq_ok(status, link->num_lanes)) |
---|
817 | | - return -EAGAIN; |
---|
818 | | - |
---|
819 | | - break; |
---|
820 | | - |
---|
821 | | - default: |
---|
822 | | - dev_err(aux->dev, "unsupported training pattern %u\n", tp); |
---|
823 | | - return -EINVAL; |
---|
824 | | - } |
---|
825 | | - |
---|
826 | | - err = drm_dp_dpcd_writeb(aux, DP_EDP_CONFIGURATION_SET, 0); |
---|
827 | | - if (err < 0) |
---|
828 | | - return err; |
---|
829 | 817 | |
---|
830 | 818 | return 0; |
---|
831 | 819 | } |
---|