diff options
author | Shu Zhong <shuz@nvidia.com> | 2018-08-20 19:48:02 -0400 |
---|---|---|
committer | mobile promotions <svcmobile_promotions@nvidia.com> | 2018-11-10 21:16:46 -0500 |
commit | 446d6934c28e82a3f64a0d4de6f7fd6fd93dab62 (patch) | |
tree | 779aaa7612db4aa2f86988bc81729e75a6e8a41e /drivers/video/tegra/dc/dp.c | |
parent | fe17829a12d57dd6a5f53a83f292703d0761581e (diff) |
dp: add Type-C extcon support
In Type-C multi-function pin assignments, the four
USB3.x SS data lanes are divided up for both Alt
Mode and USB signaling. For DP, this means that the
number of available TX lanes is also dependent on the
current pin configuration when driving DP Alt Mode
over Type-C.
The number of connected DP lanes can be derived through
CR/EQ fallback, but this is too late given our current
SW architecture. Mode filtering occurs when the display
driver populates the modedb in response to an HPD_PLUG
event, but LT is only triggered on the subsequent modeset.
In order to resolve this dependency, this change adds
support to the DP driver so that it can query the number
of connected lanes from the ucsi_ccg extcon provider,
before filtering.
Bug 2024637
TDS-3502
Change-Id: I9d0966bc3de5f9323f7ebe18d1fecbe972f6a063
Signed-off-by: Shu Zhong <shuz@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/1815326
(cherry picked from commit a6cac5548d273874b0fe99d62931df26b30ab7aa)
Reviewed-on: https://git-master.nvidia.com/r/1928660
GVS: Gerrit_Virtual_Submit
Reviewed-by: Mitch Luban <mluban@nvidia.com>
Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
Diffstat (limited to 'drivers/video/tegra/dc/dp.c')
-rw-r--r-- | drivers/video/tegra/dc/dp.c | 218 |
1 files changed, 215 insertions, 3 deletions
diff --git a/drivers/video/tegra/dc/dp.c b/drivers/video/tegra/dc/dp.c index 3bb9631ee..ab57fb318 100644 --- a/drivers/video/tegra/dc/dp.c +++ b/drivers/video/tegra/dc/dp.c | |||
@@ -1245,8 +1245,9 @@ int tegra_dc_dp_get_max_lane_count(struct tegra_dc_dp_data *dp, u8 *dpcd_data) | |||
1245 | /* | 1245 | /* |
1246 | * The max lane count supported is a product of everything below: | 1246 | * The max lane count supported is a product of everything below: |
1247 | * 1) The max lane count supported by the DPRX. | 1247 | * 1) The max lane count supported by the DPRX. |
1248 | * 2) The max lane count supported by the DPTX. | 1248 | * 2) The # of connected lanes reported by the extcon provider (Type-C). |
1249 | * 3) If test configuration parameters are set on the DPTX side, these | 1249 | * 3) The max lane count supported by the DPTX. |
1250 | * 4) If test configuration parameters are set on the DPTX side, these | ||
1250 | * parameters need to be accounted for as well. | 1251 | * parameters need to be accounted for as well. |
1251 | */ | 1252 | */ |
1252 | 1253 | ||
@@ -1263,10 +1264,13 @@ int tegra_dc_dp_get_max_lane_count(struct tegra_dc_dp_data *dp, u8 *dpcd_data) | |||
1263 | max_lane_count = max_lane_count & NV_DPCD_MAX_LANE_COUNT_MASK; | 1264 | max_lane_count = max_lane_count & NV_DPCD_MAX_LANE_COUNT_MASK; |
1264 | 1265 | ||
1265 | /* Constraint #2 */ | 1266 | /* Constraint #2 */ |
1267 | max_lane_count = min(max_lane_count, dp->typec_lane_count); | ||
1268 | |||
1269 | /* Constraint #3 */ | ||
1266 | if (dp->pdata && dp->pdata->lanes > 0) | 1270 | if (dp->pdata && dp->pdata->lanes > 0) |
1267 | max_lane_count = min(max_lane_count, (u8)dp->pdata->lanes); | 1271 | max_lane_count = min(max_lane_count, (u8)dp->pdata->lanes); |
1268 | 1272 | ||
1269 | /* Constraint #3 */ | 1273 | /* Constraint #4 */ |
1270 | if (dp->test_max_lanes > 0) | 1274 | if (dp->test_max_lanes > 0) |
1271 | max_lane_count = min(max_lane_count, (u8)dp->test_max_lanes); | 1275 | max_lane_count = min(max_lane_count, (u8)dp->test_max_lanes); |
1272 | 1276 | ||
@@ -1691,6 +1695,198 @@ done: | |||
1691 | 1695 | ||
1692 | } | 1696 | } |
1693 | 1697 | ||
1698 | static inline struct tegra_dc_extcon_cable | ||
1699 | *tegra_dp_get_typec_ecable(struct tegra_dc *dc) | ||
1700 | { | ||
1701 | if (!dc || !dc->out || !dc->out->dp_out) | ||
1702 | return NULL; | ||
1703 | |||
1704 | return &dc->out->dp_out->typec_ecable; | ||
1705 | } | ||
1706 | |||
1707 | static void tegra_dp_wait_for_typec_connect(struct tegra_dc_dp_data *dp) | ||
1708 | { | ||
1709 | #if KERNEL_VERSION(4, 9, 0) <= LINUX_VERSION_CODE | ||
1710 | struct tegra_dc_extcon_cable *typec_ecable; | ||
1711 | struct tegra_dc *dc; | ||
1712 | union extcon_property_value lane_count = {0}; | ||
1713 | int ret; | ||
1714 | bool ecable_connected; | ||
1715 | |||
1716 | if (!dp || !dp->dc) { | ||
1717 | pr_err("%s: all arguments must be non-NULL!\n", __func__); | ||
1718 | return; | ||
1719 | } | ||
1720 | dc = dp->dc; | ||
1721 | |||
1722 | if (dc->out->type == TEGRA_DC_OUT_FAKE_DP) | ||
1723 | return; | ||
1724 | |||
1725 | typec_ecable = tegra_dp_get_typec_ecable(dc); | ||
1726 | if (!typec_ecable || !typec_ecable->edev) | ||
1727 | return; | ||
1728 | |||
1729 | mutex_lock(&typec_ecable->lock); | ||
1730 | |||
1731 | ecable_connected = typec_ecable->connected; | ||
1732 | if (!ecable_connected) | ||
1733 | reinit_completion(&typec_ecable->comp); | ||
1734 | |||
1735 | mutex_unlock(&typec_ecable->lock); | ||
1736 | |||
1737 | if (!ecable_connected) { | ||
1738 | /* | ||
1739 | * See the comment above these fields in dp.h for why this is | ||
1740 | * required. | ||
1741 | */ | ||
1742 | if (unlikely(!dp->typec_notified_once && | ||
1743 | dp->typec_timed_out_once)) | ||
1744 | return; | ||
1745 | |||
1746 | if (!wait_for_completion_timeout(&typec_ecable->comp, | ||
1747 | msecs_to_jiffies(1000))) { | ||
1748 | dev_info(&dc->ndev->dev, | ||
1749 | "dp: typec extcon wait timeout\n"); | ||
1750 | dp->typec_timed_out_once = true; | ||
1751 | |||
1752 | goto typec_lane_count_err; | ||
1753 | } | ||
1754 | } | ||
1755 | |||
1756 | ret = extcon_get_property(typec_ecable->edev, EXTCON_DISP_DP, | ||
1757 | EXTCON_PROP_DISP_DP_LANE, &lane_count); | ||
1758 | if (ret) { | ||
1759 | dev_err(&dc->ndev->dev, | ||
1760 | "dp: extcon get lane prop error - ret=%d\n", ret); | ||
1761 | |||
1762 | goto typec_lane_count_err; | ||
1763 | } | ||
1764 | |||
1765 | if (lane_count.intval > 0) | ||
1766 | dp->typec_lane_count = lane_count.intval; | ||
1767 | |||
1768 | return; | ||
1769 | typec_lane_count_err: | ||
1770 | dp->typec_lane_count = 4; | ||
1771 | #endif | ||
1772 | } | ||
1773 | |||
1774 | static int tegra_dp_typec_ecable_notifier(struct notifier_block *nb, | ||
1775 | unsigned long event, void *data) | ||
1776 | { | ||
1777 | struct tegra_dc_extcon_cable *typec_ecable = | ||
1778 | container_of(nb, struct tegra_dc_extcon_cable, nb); | ||
1779 | struct tegra_dc_dp_data *dp = | ||
1780 | (struct tegra_dc_dp_data *)typec_ecable->drv_data; | ||
1781 | |||
1782 | /* See the comment above this field in dp.h for why this is required. */ | ||
1783 | dp->typec_notified_once = true; | ||
1784 | |||
1785 | mutex_lock(&typec_ecable->lock); | ||
1786 | |||
1787 | typec_ecable->connected = !!event; | ||
1788 | if (event) /* connected */ | ||
1789 | complete(&typec_ecable->comp); | ||
1790 | |||
1791 | mutex_unlock(&typec_ecable->lock); | ||
1792 | |||
1793 | return NOTIFY_DONE; | ||
1794 | } | ||
1795 | |||
1796 | static int tegra_dp_register_typec_ecable(struct tegra_dc_dp_data *dp) | ||
1797 | { | ||
1798 | struct tegra_dc_extcon_cable *typec_ecable; | ||
1799 | int ret; | ||
1800 | |||
1801 | #if KERNEL_VERSION(4, 9, 0) <= LINUX_VERSION_CODE | ||
1802 | union extcon_property_value lane_count = {0}; | ||
1803 | int init_cable_state; | ||
1804 | #endif | ||
1805 | |||
1806 | if (!dp || !dp->dc) { | ||
1807 | pr_err("%s: all arguments must be non-NULL!\n", __func__); | ||
1808 | return -EINVAL; | ||
1809 | } | ||
1810 | |||
1811 | /* Assume that all TX lanes are dedicated for DP by default. */ | ||
1812 | dp->typec_lane_count = 4; | ||
1813 | |||
1814 | dp->typec_notified_once = false; | ||
1815 | dp->typec_timed_out_once = false; | ||
1816 | |||
1817 | typec_ecable = tegra_dp_get_typec_ecable(dp->dc); | ||
1818 | if (!typec_ecable || !typec_ecable->edev) | ||
1819 | return 0; | ||
1820 | |||
1821 | mutex_init(&typec_ecable->lock); | ||
1822 | init_completion(&typec_ecable->comp); | ||
1823 | |||
1824 | typec_ecable->drv_data = dp; | ||
1825 | typec_ecable->connected = false; | ||
1826 | |||
1827 | typec_ecable->nb.notifier_call = tegra_dp_typec_ecable_notifier; | ||
1828 | ret = extcon_register_notifier(typec_ecable->edev, EXTCON_DISP_DP, | ||
1829 | &typec_ecable->nb); | ||
1830 | if (ret < 0) { | ||
1831 | dev_err(&dp->dc->ndev->dev, | ||
1832 | "dp: typec extcon notifier registration failed\n"); | ||
1833 | return ret; | ||
1834 | } | ||
1835 | |||
1836 | #if KERNEL_VERSION(4, 9, 0) <= LINUX_VERSION_CODE | ||
1837 | /* | ||
1838 | * Query the initial Type-C cable state here in case ucsi_ccg updated it | ||
1839 | * before we were able to register the extcon notifier. | ||
1840 | */ | ||
1841 | |||
1842 | mutex_lock(&typec_ecable->lock); | ||
1843 | |||
1844 | init_cable_state = extcon_get_state(typec_ecable->edev, EXTCON_DISP_DP); | ||
1845 | if (init_cable_state < 0) { | ||
1846 | dev_err(&dp->dc->ndev->dev, | ||
1847 | "dp: failed to get initial cable state\n"); | ||
1848 | } else if (init_cable_state) { /* connected */ | ||
1849 | ret = extcon_get_property(typec_ecable->edev, EXTCON_DISP_DP, | ||
1850 | EXTCON_PROP_DISP_DP_LANE, &lane_count); | ||
1851 | if (ret) { | ||
1852 | dev_err(&dp->dc->ndev->dev, | ||
1853 | "dp: failed to get initial lane count\n"); | ||
1854 | } else if (lane_count.intval > 0) { | ||
1855 | typec_ecable->connected = true; | ||
1856 | dp->typec_lane_count = lane_count.intval; | ||
1857 | } | ||
1858 | } | ||
1859 | |||
1860 | mutex_unlock(&typec_ecable->lock); | ||
1861 | #endif | ||
1862 | |||
1863 | return 0; | ||
1864 | } | ||
1865 | |||
1866 | static void tegra_dp_unregister_typec_ecable(struct tegra_dc *dc) | ||
1867 | { | ||
1868 | struct tegra_dc_extcon_cable *typec_ecable; | ||
1869 | int ret; | ||
1870 | |||
1871 | if (!dc) { | ||
1872 | pr_err("%s: all arguments must be non-NULL!\n", __func__); | ||
1873 | return; | ||
1874 | } | ||
1875 | |||
1876 | typec_ecable = tegra_dp_get_typec_ecable(dc); | ||
1877 | if (!typec_ecable || !typec_ecable->edev) | ||
1878 | return; | ||
1879 | |||
1880 | ret = extcon_unregister_notifier(typec_ecable->edev, EXTCON_DISP_DP, | ||
1881 | &typec_ecable->nb); | ||
1882 | if (ret < 0) | ||
1883 | dev_err(&dc->ndev->dev, | ||
1884 | "dp: typec extcon notifier unregistration failed\n"); | ||
1885 | |||
1886 | typec_ecable->drv_data = NULL; | ||
1887 | typec_ecable->connected = false; | ||
1888 | } | ||
1889 | |||
1694 | static irqreturn_t tegra_dp_irq(int irq, void *ptr) | 1890 | static irqreturn_t tegra_dp_irq(int irq, void *ptr) |
1695 | { | 1891 | { |
1696 | struct tegra_dc_dp_data *dp = ptr; | 1892 | struct tegra_dc_dp_data *dp = ptr; |
@@ -1903,6 +2099,12 @@ static int tegra_dc_dp_init(struct tegra_dc *dc) | |||
1903 | dp->parent_clk = parent_clk; | 2099 | dp->parent_clk = parent_clk; |
1904 | dp->mode = &dc->mode; | 2100 | dp->mode = &dc->mode; |
1905 | 2101 | ||
2102 | err = tegra_dp_register_typec_ecable(dp); | ||
2103 | if (err) { | ||
2104 | dev_err(&dc->ndev->dev, "dp: typec ecable register failed\n"); | ||
2105 | goto err_audio_switch; | ||
2106 | } | ||
2107 | |||
1906 | if (dp_instance) { | 2108 | if (dp_instance) { |
1907 | snprintf(dp->hpd_switch_name, CHAR_BUF_SIZE_MAX, | 2109 | snprintf(dp->hpd_switch_name, CHAR_BUF_SIZE_MAX, |
1908 | "dp%d", dp_instance); | 2110 | "dp%d", dp_instance); |
@@ -2215,6 +2417,14 @@ static void tegra_dp_hpd_op_edid_ready(void *drv_data) | |||
2215 | tegra_dp_auto_set_edid_checksum(dp); | 2417 | tegra_dp_auto_set_edid_checksum(dp); |
2216 | } | 2418 | } |
2217 | 2419 | ||
2420 | /* | ||
2421 | * For Type-C, wait until ucsi_ccg notifies us that an extcon CONNECT | ||
2422 | * event has occurred. This ensures that we're in DP configuration, and | ||
2423 | * that the EXTCON_PROP_DISP_DP_LANE property has been populated by | ||
2424 | * ucsi_ccg before we proceed with our subsequent mode filtering. | ||
2425 | */ | ||
2426 | tegra_dp_wait_for_typec_connect(dp); | ||
2427 | |||
2218 | /* Early enables DC with first mode from the monitor specs */ | 2428 | /* Early enables DC with first mode from the monitor specs */ |
2219 | if (dp->early_enable) { | 2429 | if (dp->early_enable) { |
2220 | struct tegra_hpd_data *data = &dp->hpd_data; | 2430 | struct tegra_hpd_data *data = &dp->hpd_data; |
@@ -2797,6 +3007,8 @@ static void tegra_dc_dp_destroy(struct tegra_dc *dc) | |||
2797 | 3007 | ||
2798 | dp->prod_list = NULL; | 3008 | dp->prod_list = NULL; |
2799 | 3009 | ||
3010 | tegra_dp_unregister_typec_ecable(dc); | ||
3011 | |||
2800 | tegra_dc_out_destroy(dc); | 3012 | tegra_dc_out_destroy(dc); |
2801 | 3013 | ||
2802 | tegra_dc_dp_debugfs_remove(dp); | 3014 | tegra_dc_dp_debugfs_remove(dp); |