| .. | .. |
|---|
| 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); |
|---|
| .. | .. |
|---|
| 485 | 503 | return err; |
|---|
| 486 | 504 | } |
|---|
| 487 | 505 | |
|---|
| 488 | | - dpaux->vdd = devm_regulator_get(&pdev->dev, "vdd"); |
|---|
| 506 | + dpaux->vdd = devm_regulator_get_optional(&pdev->dev, "vdd"); |
|---|
| 489 | 507 | 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); |
|---|
| 508 | + if (PTR_ERR(dpaux->vdd) != -ENODEV) { |
|---|
| 509 | + if (PTR_ERR(dpaux->vdd) != -EPROBE_DEFER) |
|---|
| 510 | + dev_err(&pdev->dev, |
|---|
| 511 | + "failed to get VDD supply: %ld\n", |
|---|
| 512 | + PTR_ERR(dpaux->vdd)); |
|---|
| 513 | + |
|---|
| 514 | + return PTR_ERR(dpaux->vdd); |
|---|
| 515 | + } |
|---|
| 516 | + |
|---|
| 517 | + dpaux->vdd = NULL; |
|---|
| 493 | 518 | } |
|---|
| 494 | 519 | |
|---|
| 495 | 520 | platform_set_drvdata(pdev, dpaux); |
|---|
| .. | .. |
|---|
| 521 | 546 | * is no possibility to perform the I2C mode configuration in the |
|---|
| 522 | 547 | * HDMI path. |
|---|
| 523 | 548 | */ |
|---|
| 524 | | - err = tegra_dpaux_pad_config(dpaux, DPAUX_HYBRID_PADCTL_MODE_I2C); |
|---|
| 549 | + err = tegra_dpaux_pad_config(dpaux, DPAUX_PADCTL_FUNC_I2C); |
|---|
| 525 | 550 | if (err < 0) |
|---|
| 526 | 551 | return err; |
|---|
| 527 | 552 | |
|---|
| .. | .. |
|---|
| 561 | 586 | /* make sure pads are powered down when not in use */ |
|---|
| 562 | 587 | tegra_dpaux_pad_power_down(dpaux); |
|---|
| 563 | 588 | |
|---|
| 564 | | - pm_runtime_put(&pdev->dev); |
|---|
| 589 | + pm_runtime_put_sync(&pdev->dev); |
|---|
| 565 | 590 | pm_runtime_disable(&pdev->dev); |
|---|
| 566 | 591 | |
|---|
| 567 | 592 | drm_dp_aux_unregister(&dpaux->aux); |
|---|
| .. | .. |
|---|
| 638 | 663 | SET_RUNTIME_PM_OPS(tegra_dpaux_suspend, tegra_dpaux_resume, NULL) |
|---|
| 639 | 664 | }; |
|---|
| 640 | 665 | |
|---|
| 666 | +static const struct tegra_dpaux_soc tegra124_dpaux_soc = { |
|---|
| 667 | + .cmh = 0x02, |
|---|
| 668 | + .drvz = 0x04, |
|---|
| 669 | + .drvi = 0x18, |
|---|
| 670 | +}; |
|---|
| 671 | + |
|---|
| 672 | +static const struct tegra_dpaux_soc tegra210_dpaux_soc = { |
|---|
| 673 | + .cmh = 0x02, |
|---|
| 674 | + .drvz = 0x04, |
|---|
| 675 | + .drvi = 0x30, |
|---|
| 676 | +}; |
|---|
| 677 | + |
|---|
| 678 | +static const struct tegra_dpaux_soc tegra194_dpaux_soc = { |
|---|
| 679 | + .cmh = 0x02, |
|---|
| 680 | + .drvz = 0x04, |
|---|
| 681 | + .drvi = 0x2c, |
|---|
| 682 | +}; |
|---|
| 683 | + |
|---|
| 641 | 684 | 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", }, |
|---|
| 685 | + { .compatible = "nvidia,tegra194-dpaux", .data = &tegra194_dpaux_soc }, |
|---|
| 686 | + { .compatible = "nvidia,tegra186-dpaux", .data = &tegra210_dpaux_soc }, |
|---|
| 687 | + { .compatible = "nvidia,tegra210-dpaux", .data = &tegra210_dpaux_soc }, |
|---|
| 688 | + { .compatible = "nvidia,tegra124-dpaux", .data = &tegra124_dpaux_soc }, |
|---|
| 645 | 689 | { }, |
|---|
| 646 | 690 | }; |
|---|
| 647 | 691 | MODULE_DEVICE_TABLE(of, tegra_dpaux_of_match); |
|---|
| .. | .. |
|---|
| 682 | 726 | output->connector.polled = DRM_CONNECTOR_POLL_HPD; |
|---|
| 683 | 727 | dpaux->output = output; |
|---|
| 684 | 728 | |
|---|
| 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)) { |
|---|
| 729 | + if (output->panel) { |
|---|
| 692 | 730 | enum drm_connector_status status; |
|---|
| 693 | 731 | |
|---|
| 694 | | - status = drm_dp_aux_detect(aux); |
|---|
| 695 | | - if (status == connector_status_connected) { |
|---|
| 696 | | - enable_irq(dpaux->irq); |
|---|
| 697 | | - return 0; |
|---|
| 732 | + if (dpaux->vdd) { |
|---|
| 733 | + err = regulator_enable(dpaux->vdd); |
|---|
| 734 | + if (err < 0) |
|---|
| 735 | + return err; |
|---|
| 698 | 736 | } |
|---|
| 699 | 737 | |
|---|
| 700 | | - usleep_range(1000, 2000); |
|---|
| 738 | + timeout = jiffies + msecs_to_jiffies(250); |
|---|
| 739 | + |
|---|
| 740 | + while (time_before(jiffies, timeout)) { |
|---|
| 741 | + status = drm_dp_aux_detect(aux); |
|---|
| 742 | + |
|---|
| 743 | + if (status == connector_status_connected) |
|---|
| 744 | + break; |
|---|
| 745 | + |
|---|
| 746 | + usleep_range(1000, 2000); |
|---|
| 747 | + } |
|---|
| 748 | + |
|---|
| 749 | + if (status != connector_status_connected) |
|---|
| 750 | + return -ETIMEDOUT; |
|---|
| 701 | 751 | } |
|---|
| 702 | 752 | |
|---|
| 703 | | - return -ETIMEDOUT; |
|---|
| 753 | + enable_irq(dpaux->irq); |
|---|
| 754 | + return 0; |
|---|
| 704 | 755 | } |
|---|
| 705 | 756 | |
|---|
| 706 | 757 | int drm_dp_aux_detach(struct drm_dp_aux *aux) |
|---|
| .. | .. |
|---|
| 711 | 762 | |
|---|
| 712 | 763 | disable_irq(dpaux->irq); |
|---|
| 713 | 764 | |
|---|
| 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)) { |
|---|
| 765 | + if (dpaux->output->panel) { |
|---|
| 721 | 766 | enum drm_connector_status status; |
|---|
| 722 | 767 | |
|---|
| 723 | | - status = drm_dp_aux_detect(aux); |
|---|
| 724 | | - if (status == connector_status_disconnected) { |
|---|
| 725 | | - dpaux->output = NULL; |
|---|
| 726 | | - return 0; |
|---|
| 768 | + if (dpaux->vdd) { |
|---|
| 769 | + err = regulator_disable(dpaux->vdd); |
|---|
| 770 | + if (err < 0) |
|---|
| 771 | + return err; |
|---|
| 727 | 772 | } |
|---|
| 728 | 773 | |
|---|
| 729 | | - usleep_range(1000, 2000); |
|---|
| 774 | + timeout = jiffies + msecs_to_jiffies(250); |
|---|
| 775 | + |
|---|
| 776 | + while (time_before(jiffies, timeout)) { |
|---|
| 777 | + status = drm_dp_aux_detect(aux); |
|---|
| 778 | + |
|---|
| 779 | + if (status == connector_status_disconnected) |
|---|
| 780 | + break; |
|---|
| 781 | + |
|---|
| 782 | + usleep_range(1000, 2000); |
|---|
| 783 | + } |
|---|
| 784 | + |
|---|
| 785 | + if (status != connector_status_disconnected) |
|---|
| 786 | + return -ETIMEDOUT; |
|---|
| 787 | + |
|---|
| 788 | + dpaux->output = NULL; |
|---|
| 730 | 789 | } |
|---|
| 731 | 790 | |
|---|
| 732 | | - return -ETIMEDOUT; |
|---|
| 791 | + return 0; |
|---|
| 733 | 792 | } |
|---|
| 734 | 793 | |
|---|
| 735 | 794 | enum drm_connector_status drm_dp_aux_detect(struct drm_dp_aux *aux) |
|---|
| .. | .. |
|---|
| 757 | 816 | struct tegra_dpaux *dpaux = to_dpaux(aux); |
|---|
| 758 | 817 | |
|---|
| 759 | 818 | 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 | 819 | |
|---|
| 830 | 820 | return 0; |
|---|
| 831 | 821 | } |
|---|