mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-24 13:13:57 +08:00
drm/tegra: Changes for v4.8-rc1
This set of changes contains a bunch of cleanups to the host1x driver as well as the addition of a pin controller for DPAUX, which is required by boards to configure the DPAUX pads in AUX mode (for DisplayPort) or I2C mode (for HDMI and DDC). Included is also a bit of rework of the SOR driver in preparation to add DisplayPort support as well as some refactoring and cleanup. Finally, all output drivers are converted to runtime PM, which greatly simplifies the handling of clocks and resets. -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABCAAGBQJXh5n1AAoJEN0jrNd/PrOh+KcP/0SLO9UKvGBsfD0EFvkxx/NV VZvqCWP0ARCGDAZn890O4sqwgHdRDF+hp0HcUt8wDt4o3VTYJIdifr/5tunOZ6+w 1IatWrjJjpyvZs9bveEvtJ8Hbkq5h0esA1StcqheRUiE0o+uQppdWO1NLO+HxssQ f4qV4GQ7trJQdjr9OGfGJigsvOjwGVbPL7CRrKKLmbfTiqy9zhi5bOyUfojtMEL6 BvH9RMqC0ke70sGtcPgigbL7wgnbkH0uR3sV5TwU5dErrvh0tnhYU1a1ednPotJW z56+W8hWfC3XLIiU9mju1l+3sQA0BuS9ZQ+6VhdRTKxv85CUI5dUKT0/h5BtRrK9 0XgPwrn4YpAr17oFEGdqFPhefkDkbb/1WrWmgUEi8/YaimdCXNkf8rILZvWUVQdt q9GbbmEiGNR2FvBEN7s/nT96E5XEzfLTjOzTOP0c5RF9cOK+Ul9t0t44eBn4Wqbk x6rTv37eieK5RVYd24xKKnzf5uSMpU+RTea4Vpr0xdH6glGcrTJ5ogub0iIsY0Jn A9M1pKadQvu//95ZzGWEyrqAr66cDG8vGjZn9z24PRaNZEbzvY3ohj2/qOSQoRFY HPmswshI0DnyFPWmdhhlPy3di0zNDP6kfPo6NhotoAl2Uwd+RrY/VyWjDG0dwV0R nSkcnGES5hnf3gguXEmO =7xlG -----END PGP SIGNATURE----- Merge tag 'drm/tegra/for-4.8-rc1' of git://anongit.freedesktop.org/tegra/linux into drm-next drm/tegra: Changes for v4.8-rc1 This set of changes contains a bunch of cleanups to the host1x driver as well as the addition of a pin controller for DPAUX, which is required by boards to configure the DPAUX pads in AUX mode (for DisplayPort) or I2C mode (for HDMI and DDC). Included is also a bit of rework of the SOR driver in preparation to add DisplayPort support as well as some refactoring and cleanup. Finally, all output drivers are converted to runtime PM, which greatly simplifies the handling of clocks and resets. * tag 'drm/tegra/for-4.8-rc1' of git://anongit.freedesktop.org/tegra/linux: (35 commits) drm/tegra: sor: Reject HDMI 2.0 modes drm/tegra: sor: Prepare for generic PM domain support drm/tegra: dsi: Prepare for generic PM domain support drm/tegra: sor: Make XBAR configurable per SoC drm/tegra: sor: Use sor1_src clock to set parent for HDMI dt-bindings: display: tegra: Add source clock for SOR drm/tegra: sor: Implement sor1_brick clock drm/tegra: sor: Implement runtime PM drm/tegra: hdmi: Implement runtime PM drm/tegra: dsi: Implement runtime PM drm/tegra: dc: Implement runtime PM drm/tegra: hdmi: Enable audio over HDMI drm/tegra: sor: Do not support deep color modes drm/tegra: sor: Extract tegra_sor_mode_set() drm/tegra: sor: Split out tegra_sor_apply_config() drm/tegra: sor: Rename tegra_sor_calc_config() drm/tegra: sor: Factor out tegra_sor_set_parent_clock() drm/tegra: dpaux: Add pinctrl support dt-bindings: Add bindings for Tegra DPAUX pinctrl driver drm/tegra: Prepare DPAUX for supporting generic PM domains ...
This commit is contained in:
commit
877fa9a42d
@ -208,6 +208,7 @@ of the following host1x client modules:
|
||||
See ../clocks/clock-bindings.txt for details.
|
||||
- clock-names: Must include the following entries:
|
||||
- sor: clock input for the SOR hardware
|
||||
- source: source clock for the SOR clock
|
||||
- parent: input for the pixel clock
|
||||
- dp: reference clock for the SOR clock
|
||||
- safe: safe reference for the SOR clock during power up
|
||||
@ -226,9 +227,9 @@ of the following host1x client modules:
|
||||
- nvidia,dpaux: phandle to a DispayPort AUX interface
|
||||
|
||||
- dpaux: DisplayPort AUX interface
|
||||
- compatible: For Tegra124, must contain "nvidia,tegra124-dpaux". Otherwise,
|
||||
must contain '"nvidia,<chip>-dpaux", "nvidia,tegra124-dpaux"', where
|
||||
<chip> is tegra132.
|
||||
- compatible : Should contain one of the following:
|
||||
- "nvidia,tegra124-dpaux": for Tegra124 and Tegra132
|
||||
- "nvidia,tegra210-dpaux": for Tegra210
|
||||
- reg: Physical base address and length of the controller's registers.
|
||||
- interrupts: The interrupt outputs from the controller.
|
||||
- clocks: Must contain an entry for each entry in clock-names.
|
||||
@ -241,6 +242,12 @@ of the following host1x client modules:
|
||||
- reset-names: Must include the following entries:
|
||||
- dpaux
|
||||
- vdd-supply: phandle of a supply that powers the DisplayPort link
|
||||
- i2c-bus: Subnode where I2C slave devices are listed. This subnode
|
||||
must be always present. If there are no I2C slave devices, an empty
|
||||
node should be added. See ../../i2c/i2c.txt for more information.
|
||||
|
||||
See ../pinctrl/nvidia,tegra124-dpaux-padctl.txt for information
|
||||
regarding the DPAUX pad controller bindings.
|
||||
|
||||
Example:
|
||||
|
||||
|
@ -0,0 +1,60 @@
|
||||
Device tree binding for NVIDIA Tegra DPAUX pad controller
|
||||
========================================================
|
||||
|
||||
The Tegra Display Port Auxiliary (DPAUX) pad controller manages two pins
|
||||
which can be assigned to either the DPAUX channel or to an I2C
|
||||
controller.
|
||||
|
||||
This document defines the device-specific binding for the DPAUX pad
|
||||
controller. Refer to pinctrl-bindings.txt in this directory for generic
|
||||
information about pin controller device tree bindings. Please refer to
|
||||
the binding document ../display/tegra/nvidia,tegra20-host1x.txt for more
|
||||
details on the DPAUX binding.
|
||||
|
||||
Pin muxing:
|
||||
-----------
|
||||
|
||||
Child nodes contain the pinmux configurations following the conventions
|
||||
from the pinctrl-bindings.txt document.
|
||||
|
||||
Since only three configurations are possible, only three child nodes are
|
||||
needed to describe the pin mux'ing options for the DPAUX pads.
|
||||
Furthermore, given that the pad functions are only applicable to a
|
||||
single set of pads, the child nodes only need to describe the pad group
|
||||
the functions are being applied to rather than the individual pads.
|
||||
|
||||
Required properties:
|
||||
- groups: Must be "dpaux-io"
|
||||
- function: Must be either "aux", "i2c" or "off".
|
||||
|
||||
Example:
|
||||
--------
|
||||
|
||||
dpaux@545c0000 {
|
||||
...
|
||||
|
||||
state_dpaux_aux: pinmux-aux {
|
||||
groups = "dpaux-io";
|
||||
function = "aux";
|
||||
};
|
||||
|
||||
state_dpaux_i2c: pinmux-i2c {
|
||||
groups = "dpaux-io";
|
||||
function = "i2c";
|
||||
};
|
||||
|
||||
state_dpaux_off: pinmux-off {
|
||||
groups = "dpaux-io";
|
||||
function = "off";
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
|
||||
i2c@7000d100 {
|
||||
...
|
||||
pinctrl-0 = <&state_dpaux_i2c>;
|
||||
pinctrl-1 = <&state_dpaux_off>;
|
||||
pinctrl-names = "default", "idle";
|
||||
status = "disabled";
|
||||
};
|
@ -10,6 +10,7 @@
|
||||
#include <linux/clk.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include <soc/tegra/pmc.h>
|
||||
@ -1216,6 +1217,8 @@ static void tegra_crtc_disable(struct drm_crtc *crtc)
|
||||
|
||||
tegra_dc_stats_reset(&dc->stats);
|
||||
drm_crtc_vblank_off(crtc);
|
||||
|
||||
pm_runtime_put_sync(dc->dev);
|
||||
}
|
||||
|
||||
static void tegra_crtc_enable(struct drm_crtc *crtc)
|
||||
@ -1225,6 +1228,48 @@ static void tegra_crtc_enable(struct drm_crtc *crtc)
|
||||
struct tegra_dc *dc = to_tegra_dc(crtc);
|
||||
u32 value;
|
||||
|
||||
pm_runtime_get_sync(dc->dev);
|
||||
|
||||
/* initialize display controller */
|
||||
if (dc->syncpt) {
|
||||
u32 syncpt = host1x_syncpt_id(dc->syncpt);
|
||||
|
||||
value = SYNCPT_CNTRL_NO_STALL;
|
||||
tegra_dc_writel(dc, value, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL);
|
||||
|
||||
value = SYNCPT_VSYNC_ENABLE | syncpt;
|
||||
tegra_dc_writel(dc, value, DC_CMD_CONT_SYNCPT_VSYNC);
|
||||
}
|
||||
|
||||
value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_TYPE);
|
||||
|
||||
value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY);
|
||||
|
||||
/* initialize timer */
|
||||
value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) |
|
||||
WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20);
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY);
|
||||
|
||||
value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(1) |
|
||||
WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1);
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER);
|
||||
|
||||
value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE);
|
||||
|
||||
value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_MASK);
|
||||
|
||||
if (dc->soc->supports_border_color)
|
||||
tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR);
|
||||
|
||||
/* apply PLL and pixel clock changes */
|
||||
tegra_dc_commit_state(dc, state);
|
||||
|
||||
/* program display mode */
|
||||
@ -1685,7 +1730,6 @@ static int tegra_dc_init(struct host1x_client *client)
|
||||
struct tegra_drm *tegra = drm->dev_private;
|
||||
struct drm_plane *primary = NULL;
|
||||
struct drm_plane *cursor = NULL;
|
||||
u32 value;
|
||||
int err;
|
||||
|
||||
dc->syncpt = host1x_syncpt_request(dc->dev, flags);
|
||||
@ -1755,47 +1799,6 @@ static int tegra_dc_init(struct host1x_client *client)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* initialize display controller */
|
||||
if (dc->syncpt) {
|
||||
u32 syncpt = host1x_syncpt_id(dc->syncpt);
|
||||
|
||||
value = SYNCPT_CNTRL_NO_STALL;
|
||||
tegra_dc_writel(dc, value, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL);
|
||||
|
||||
value = SYNCPT_VSYNC_ENABLE | syncpt;
|
||||
tegra_dc_writel(dc, value, DC_CMD_CONT_SYNCPT_VSYNC);
|
||||
}
|
||||
|
||||
value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_TYPE);
|
||||
|
||||
value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY);
|
||||
|
||||
/* initialize timer */
|
||||
value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) |
|
||||
WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20);
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY);
|
||||
|
||||
value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(1) |
|
||||
WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1);
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER);
|
||||
|
||||
value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE);
|
||||
|
||||
value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_MASK);
|
||||
|
||||
if (dc->soc->supports_border_color)
|
||||
tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR);
|
||||
|
||||
tegra_dc_stats_reset(&dc->stats);
|
||||
|
||||
return 0;
|
||||
|
||||
cleanup:
|
||||
@ -1987,33 +1990,15 @@ static int tegra_dc_probe(struct platform_device *pdev)
|
||||
return PTR_ERR(dc->rst);
|
||||
}
|
||||
|
||||
reset_control_assert(dc->rst);
|
||||
|
||||
if (dc->soc->has_powergate) {
|
||||
if (dc->pipe == 0)
|
||||
dc->powergate = TEGRA_POWERGATE_DIS;
|
||||
else
|
||||
dc->powergate = TEGRA_POWERGATE_DISB;
|
||||
|
||||
err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk,
|
||||
dc->rst);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to power partition: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
err = clk_prepare_enable(dc->clk);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to enable clock: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = reset_control_deassert(dc->rst);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to deassert reset: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
tegra_powergate_power_off(dc->powergate);
|
||||
}
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
@ -2027,16 +2012,19 @@ static int tegra_dc_probe(struct platform_device *pdev)
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&dc->client.list);
|
||||
dc->client.ops = &dc_client_ops;
|
||||
dc->client.dev = &pdev->dev;
|
||||
|
||||
err = tegra_dc_rgb_probe(dc);
|
||||
if (err < 0 && err != -ENODEV) {
|
||||
dev_err(&pdev->dev, "failed to probe RGB output: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dc);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
INIT_LIST_HEAD(&dc->client.list);
|
||||
dc->client.ops = &dc_client_ops;
|
||||
dc->client.dev = &pdev->dev;
|
||||
|
||||
err = host1x_client_register(&dc->client);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to register host1x client: %d\n",
|
||||
@ -2044,8 +2032,6 @@ static int tegra_dc_probe(struct platform_device *pdev)
|
||||
return err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -2067,7 +2053,22 @@ static int tegra_dc_remove(struct platform_device *pdev)
|
||||
return err;
|
||||
}
|
||||
|
||||
reset_control_assert(dc->rst);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int tegra_dc_suspend(struct device *dev)
|
||||
{
|
||||
struct tegra_dc *dc = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
err = reset_control_assert(dc->rst);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to assert reset: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (dc->soc->has_powergate)
|
||||
tegra_powergate_power_off(dc->powergate);
|
||||
@ -2077,10 +2078,45 @@ static int tegra_dc_remove(struct platform_device *pdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_dc_resume(struct device *dev)
|
||||
{
|
||||
struct tegra_dc *dc = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
if (dc->soc->has_powergate) {
|
||||
err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk,
|
||||
dc->rst);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to power partition: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
err = clk_prepare_enable(dc->clk);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to enable clock: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = reset_control_deassert(dc->rst);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to deassert reset: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops tegra_dc_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(tegra_dc_suspend, tegra_dc_resume, NULL)
|
||||
};
|
||||
|
||||
struct platform_driver tegra_dc_driver = {
|
||||
.driver = {
|
||||
.name = "tegra-dc",
|
||||
.of_match_table = tegra_dc_of_match,
|
||||
.pm = &tegra_dc_pm_ops,
|
||||
},
|
||||
.probe = tegra_dc_probe,
|
||||
.remove = tegra_dc_remove,
|
||||
|
@ -12,6 +12,9 @@
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/pinctrl/pinconf-generic.h>
|
||||
#include <linux/pinctrl/pinctrl.h>
|
||||
#include <linux/pinctrl/pinmux.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
@ -44,6 +47,11 @@ struct tegra_dpaux {
|
||||
struct completion complete;
|
||||
struct work_struct work;
|
||||
struct list_head list;
|
||||
|
||||
#ifdef CONFIG_GENERIC_PINCONF
|
||||
struct pinctrl_dev *pinctrl;
|
||||
struct pinctrl_desc desc;
|
||||
#endif
|
||||
};
|
||||
|
||||
static inline struct tegra_dpaux *to_dpaux(struct drm_dp_aux *aux)
|
||||
@ -267,6 +275,148 @@ static irqreturn_t tegra_dpaux_irq(int irq, void *data)
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum tegra_dpaux_functions {
|
||||
DPAUX_PADCTL_FUNC_AUX,
|
||||
DPAUX_PADCTL_FUNC_I2C,
|
||||
DPAUX_PADCTL_FUNC_OFF,
|
||||
};
|
||||
|
||||
static void tegra_dpaux_pad_power_down(struct tegra_dpaux *dpaux)
|
||||
{
|
||||
u32 value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE);
|
||||
|
||||
value |= DPAUX_HYBRID_SPARE_PAD_POWER_DOWN;
|
||||
|
||||
tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE);
|
||||
}
|
||||
|
||||
static void tegra_dpaux_pad_power_up(struct tegra_dpaux *dpaux)
|
||||
{
|
||||
u32 value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE);
|
||||
|
||||
value &= ~DPAUX_HYBRID_SPARE_PAD_POWER_DOWN;
|
||||
|
||||
tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE);
|
||||
}
|
||||
|
||||
static int tegra_dpaux_pad_config(struct tegra_dpaux *dpaux, unsigned function)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
switch (function) {
|
||||
case DPAUX_PADCTL_FUNC_AUX:
|
||||
value = DPAUX_HYBRID_PADCTL_AUX_CMH(2) |
|
||||
DPAUX_HYBRID_PADCTL_AUX_DRVZ(4) |
|
||||
DPAUX_HYBRID_PADCTL_AUX_DRVI(0x18) |
|
||||
DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV |
|
||||
DPAUX_HYBRID_PADCTL_MODE_AUX;
|
||||
break;
|
||||
|
||||
case DPAUX_PADCTL_FUNC_I2C:
|
||||
value = DPAUX_HYBRID_PADCTL_I2C_SDA_INPUT_RCV |
|
||||
DPAUX_HYBRID_PADCTL_I2C_SCL_INPUT_RCV |
|
||||
DPAUX_HYBRID_PADCTL_MODE_I2C;
|
||||
break;
|
||||
|
||||
case DPAUX_PADCTL_FUNC_OFF:
|
||||
tegra_dpaux_pad_power_down(dpaux);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_PADCTL);
|
||||
tegra_dpaux_pad_power_up(dpaux);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_GENERIC_PINCONF
|
||||
static const struct pinctrl_pin_desc tegra_dpaux_pins[] = {
|
||||
PINCTRL_PIN(0, "DP_AUX_CHx_P"),
|
||||
PINCTRL_PIN(1, "DP_AUX_CHx_N"),
|
||||
};
|
||||
|
||||
static const unsigned tegra_dpaux_pin_numbers[] = { 0, 1 };
|
||||
|
||||
static const char * const tegra_dpaux_groups[] = {
|
||||
"dpaux-io",
|
||||
};
|
||||
|
||||
static const char * const tegra_dpaux_functions[] = {
|
||||
"aux",
|
||||
"i2c",
|
||||
"off",
|
||||
};
|
||||
|
||||
static int tegra_dpaux_get_groups_count(struct pinctrl_dev *pinctrl)
|
||||
{
|
||||
return ARRAY_SIZE(tegra_dpaux_groups);
|
||||
}
|
||||
|
||||
static const char *tegra_dpaux_get_group_name(struct pinctrl_dev *pinctrl,
|
||||
unsigned int group)
|
||||
{
|
||||
return tegra_dpaux_groups[group];
|
||||
}
|
||||
|
||||
static int tegra_dpaux_get_group_pins(struct pinctrl_dev *pinctrl,
|
||||
unsigned group, const unsigned **pins,
|
||||
unsigned *num_pins)
|
||||
{
|
||||
*pins = tegra_dpaux_pin_numbers;
|
||||
*num_pins = ARRAY_SIZE(tegra_dpaux_pin_numbers);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pinctrl_ops tegra_dpaux_pinctrl_ops = {
|
||||
.get_groups_count = tegra_dpaux_get_groups_count,
|
||||
.get_group_name = tegra_dpaux_get_group_name,
|
||||
.get_group_pins = tegra_dpaux_get_group_pins,
|
||||
.dt_node_to_map = pinconf_generic_dt_node_to_map_group,
|
||||
.dt_free_map = pinconf_generic_dt_free_map,
|
||||
};
|
||||
|
||||
static int tegra_dpaux_get_functions_count(struct pinctrl_dev *pinctrl)
|
||||
{
|
||||
return ARRAY_SIZE(tegra_dpaux_functions);
|
||||
}
|
||||
|
||||
static const char *tegra_dpaux_get_function_name(struct pinctrl_dev *pinctrl,
|
||||
unsigned int function)
|
||||
{
|
||||
return tegra_dpaux_functions[function];
|
||||
}
|
||||
|
||||
static int tegra_dpaux_get_function_groups(struct pinctrl_dev *pinctrl,
|
||||
unsigned int function,
|
||||
const char * const **groups,
|
||||
unsigned * const num_groups)
|
||||
{
|
||||
*num_groups = ARRAY_SIZE(tegra_dpaux_groups);
|
||||
*groups = tegra_dpaux_groups;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_dpaux_set_mux(struct pinctrl_dev *pinctrl,
|
||||
unsigned int function, unsigned int group)
|
||||
{
|
||||
struct tegra_dpaux *dpaux = pinctrl_dev_get_drvdata(pinctrl);
|
||||
|
||||
return tegra_dpaux_pad_config(dpaux, function);
|
||||
}
|
||||
|
||||
static const struct pinmux_ops tegra_dpaux_pinmux_ops = {
|
||||
.get_functions_count = tegra_dpaux_get_functions_count,
|
||||
.get_function_name = tegra_dpaux_get_function_name,
|
||||
.get_function_groups = tegra_dpaux_get_function_groups,
|
||||
.set_mux = tegra_dpaux_set_mux,
|
||||
};
|
||||
#endif
|
||||
|
||||
static int tegra_dpaux_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra_dpaux *dpaux;
|
||||
@ -294,12 +444,15 @@ static int tegra_dpaux_probe(struct platform_device *pdev)
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
if (!pdev->dev.pm_domain) {
|
||||
dpaux->rst = devm_reset_control_get(&pdev->dev, "dpaux");
|
||||
if (IS_ERR(dpaux->rst)) {
|
||||
dev_err(&pdev->dev, "failed to get reset control: %ld\n",
|
||||
dev_err(&pdev->dev,
|
||||
"failed to get reset control: %ld\n",
|
||||
PTR_ERR(dpaux->rst));
|
||||
return PTR_ERR(dpaux->rst);
|
||||
}
|
||||
}
|
||||
|
||||
dpaux->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(dpaux->clk)) {
|
||||
@ -315,34 +468,37 @@ static int tegra_dpaux_probe(struct platform_device *pdev)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (dpaux->rst)
|
||||
reset_control_deassert(dpaux->rst);
|
||||
|
||||
dpaux->clk_parent = devm_clk_get(&pdev->dev, "parent");
|
||||
if (IS_ERR(dpaux->clk_parent)) {
|
||||
dev_err(&pdev->dev, "failed to get parent clock: %ld\n",
|
||||
PTR_ERR(dpaux->clk_parent));
|
||||
return PTR_ERR(dpaux->clk_parent);
|
||||
err = PTR_ERR(dpaux->clk_parent);
|
||||
goto assert_reset;
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(dpaux->clk_parent);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to enable parent clock: %d\n",
|
||||
err);
|
||||
return err;
|
||||
goto assert_reset;
|
||||
}
|
||||
|
||||
err = clk_set_rate(dpaux->clk_parent, 270000000);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to set clock to 270 MHz: %d\n",
|
||||
err);
|
||||
return err;
|
||||
goto disable_parent_clk;
|
||||
}
|
||||
|
||||
dpaux->vdd = devm_regulator_get(&pdev->dev, "vdd");
|
||||
if (IS_ERR(dpaux->vdd)) {
|
||||
dev_err(&pdev->dev, "failed to get VDD supply: %ld\n",
|
||||
PTR_ERR(dpaux->vdd));
|
||||
return PTR_ERR(dpaux->vdd);
|
||||
err = PTR_ERR(dpaux->vdd);
|
||||
goto disable_parent_clk;
|
||||
}
|
||||
|
||||
err = devm_request_irq(dpaux->dev, dpaux->irq, tegra_dpaux_irq, 0,
|
||||
@ -350,7 +506,7 @@ static int tegra_dpaux_probe(struct platform_device *pdev)
|
||||
if (err < 0) {
|
||||
dev_err(dpaux->dev, "failed to request IRQ#%u: %d\n",
|
||||
dpaux->irq, err);
|
||||
return err;
|
||||
goto disable_parent_clk;
|
||||
}
|
||||
|
||||
disable_irq(dpaux->irq);
|
||||
@ -360,7 +516,7 @@ static int tegra_dpaux_probe(struct platform_device *pdev)
|
||||
|
||||
err = drm_dp_aux_register(&dpaux->aux);
|
||||
if (err < 0)
|
||||
return err;
|
||||
goto disable_parent_clk;
|
||||
|
||||
/*
|
||||
* Assume that by default the DPAUX/I2C pads will be used for HDMI,
|
||||
@ -370,16 +526,24 @@ static int tegra_dpaux_probe(struct platform_device *pdev)
|
||||
* is no possibility to perform the I2C mode configuration in the
|
||||
* HDMI path.
|
||||
*/
|
||||
value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE);
|
||||
value &= ~DPAUX_HYBRID_SPARE_PAD_POWER_DOWN;
|
||||
tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE);
|
||||
err = tegra_dpaux_pad_config(dpaux, DPAUX_HYBRID_PADCTL_MODE_I2C);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_PADCTL);
|
||||
value = DPAUX_HYBRID_PADCTL_I2C_SDA_INPUT_RCV |
|
||||
DPAUX_HYBRID_PADCTL_I2C_SCL_INPUT_RCV |
|
||||
DPAUX_HYBRID_PADCTL_MODE_I2C;
|
||||
tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_PADCTL);
|
||||
#ifdef CONFIG_GENERIC_PINCONF
|
||||
dpaux->desc.name = dev_name(&pdev->dev);
|
||||
dpaux->desc.pins = tegra_dpaux_pins;
|
||||
dpaux->desc.npins = ARRAY_SIZE(tegra_dpaux_pins);
|
||||
dpaux->desc.pctlops = &tegra_dpaux_pinctrl_ops;
|
||||
dpaux->desc.pmxops = &tegra_dpaux_pinmux_ops;
|
||||
dpaux->desc.owner = THIS_MODULE;
|
||||
|
||||
dpaux->pinctrl = devm_pinctrl_register(&pdev->dev, &dpaux->desc, dpaux);
|
||||
if (!dpaux->pinctrl) {
|
||||
dev_err(&pdev->dev, "failed to register pincontrol\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
/* enable and clear all interrupts */
|
||||
value = DPAUX_INTR_AUX_DONE | DPAUX_INTR_IRQ_EVENT |
|
||||
DPAUX_INTR_UNPLUG_EVENT | DPAUX_INTR_PLUG_EVENT;
|
||||
@ -393,17 +557,24 @@ static int tegra_dpaux_probe(struct platform_device *pdev)
|
||||
platform_set_drvdata(pdev, dpaux);
|
||||
|
||||
return 0;
|
||||
|
||||
disable_parent_clk:
|
||||
clk_disable_unprepare(dpaux->clk_parent);
|
||||
assert_reset:
|
||||
if (dpaux->rst)
|
||||
reset_control_assert(dpaux->rst);
|
||||
|
||||
clk_disable_unprepare(dpaux->clk);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra_dpaux_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra_dpaux *dpaux = platform_get_drvdata(pdev);
|
||||
u32 value;
|
||||
|
||||
/* make sure pads are powered down when not in use */
|
||||
value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE);
|
||||
value |= DPAUX_HYBRID_SPARE_PAD_POWER_DOWN;
|
||||
tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE);
|
||||
tegra_dpaux_pad_power_down(dpaux);
|
||||
|
||||
drm_dp_aux_unregister(&dpaux->aux);
|
||||
|
||||
@ -414,7 +585,10 @@ static int tegra_dpaux_remove(struct platform_device *pdev)
|
||||
cancel_work_sync(&dpaux->work);
|
||||
|
||||
clk_disable_unprepare(dpaux->clk_parent);
|
||||
|
||||
if (dpaux->rst)
|
||||
reset_control_assert(dpaux->rst);
|
||||
|
||||
clk_disable_unprepare(dpaux->clk);
|
||||
|
||||
return 0;
|
||||
@ -528,30 +702,15 @@ enum drm_connector_status drm_dp_aux_detect(struct drm_dp_aux *aux)
|
||||
int drm_dp_aux_enable(struct drm_dp_aux *aux)
|
||||
{
|
||||
struct tegra_dpaux *dpaux = to_dpaux(aux);
|
||||
u32 value;
|
||||
|
||||
value = DPAUX_HYBRID_PADCTL_AUX_CMH(2) |
|
||||
DPAUX_HYBRID_PADCTL_AUX_DRVZ(4) |
|
||||
DPAUX_HYBRID_PADCTL_AUX_DRVI(0x18) |
|
||||
DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV |
|
||||
DPAUX_HYBRID_PADCTL_MODE_AUX;
|
||||
tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_PADCTL);
|
||||
|
||||
value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE);
|
||||
value &= ~DPAUX_HYBRID_SPARE_PAD_POWER_DOWN;
|
||||
tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE);
|
||||
|
||||
return 0;
|
||||
return tegra_dpaux_pad_config(dpaux, DPAUX_PADCTL_FUNC_AUX);
|
||||
}
|
||||
|
||||
int drm_dp_aux_disable(struct drm_dp_aux *aux)
|
||||
{
|
||||
struct tegra_dpaux *dpaux = to_dpaux(aux);
|
||||
u32 value;
|
||||
|
||||
value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE);
|
||||
value |= DPAUX_HYBRID_SPARE_PAD_POWER_DOWN;
|
||||
tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE);
|
||||
tegra_dpaux_pad_power_down(dpaux);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -56,8 +56,8 @@ static void tegra_atomic_complete(struct tegra_drm *tegra,
|
||||
*/
|
||||
|
||||
drm_atomic_helper_commit_modeset_disables(drm, state);
|
||||
drm_atomic_helper_commit_planes(drm, state, false);
|
||||
drm_atomic_helper_commit_modeset_enables(drm, state);
|
||||
drm_atomic_helper_commit_planes(drm, state, true);
|
||||
|
||||
drm_atomic_helper_wait_for_vblanks(drm, state);
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include <linux/regulator/consumer.h>
|
||||
@ -677,6 +678,45 @@ static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi)
|
||||
tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL);
|
||||
}
|
||||
|
||||
static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0);
|
||||
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
/*
|
||||
* XXX Is this still needed? The module reset is deasserted right
|
||||
* before this function is called.
|
||||
*/
|
||||
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0);
|
||||
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1);
|
||||
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2);
|
||||
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3);
|
||||
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4);
|
||||
|
||||
/* start calibration */
|
||||
tegra_dsi_pad_enable(dsi);
|
||||
|
||||
value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) |
|
||||
DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) |
|
||||
DSI_PAD_OUT_CLK(0x0);
|
||||
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2);
|
||||
|
||||
value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) |
|
||||
DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3);
|
||||
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_3);
|
||||
|
||||
return tegra_mipi_calibrate(dsi->mipi);
|
||||
}
|
||||
|
||||
static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk,
|
||||
unsigned int vrefresh)
|
||||
{
|
||||
@ -836,7 +876,7 @@ static void tegra_dsi_encoder_disable(struct drm_encoder *encoder)
|
||||
|
||||
tegra_dsi_disable(dsi);
|
||||
|
||||
return;
|
||||
pm_runtime_put(dsi->dev);
|
||||
}
|
||||
|
||||
static void tegra_dsi_encoder_enable(struct drm_encoder *encoder)
|
||||
@ -847,6 +887,13 @@ static void tegra_dsi_encoder_enable(struct drm_encoder *encoder)
|
||||
struct tegra_dsi *dsi = to_dsi(output);
|
||||
struct tegra_dsi_state *state;
|
||||
u32 value;
|
||||
int err;
|
||||
|
||||
pm_runtime_get_sync(dsi->dev);
|
||||
|
||||
err = tegra_dsi_pad_calibrate(dsi);
|
||||
if (err < 0)
|
||||
dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
|
||||
|
||||
state = tegra_dsi_get_state(dsi);
|
||||
|
||||
@ -875,8 +922,6 @@ static void tegra_dsi_encoder_enable(struct drm_encoder *encoder)
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_enable(output->panel);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int
|
||||
@ -966,55 +1011,12 @@ static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = {
|
||||
.atomic_check = tegra_dsi_encoder_atomic_check,
|
||||
};
|
||||
|
||||
static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0);
|
||||
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0);
|
||||
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1);
|
||||
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2);
|
||||
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3);
|
||||
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4);
|
||||
|
||||
/* start calibration */
|
||||
tegra_dsi_pad_enable(dsi);
|
||||
|
||||
value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) |
|
||||
DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) |
|
||||
DSI_PAD_OUT_CLK(0x0);
|
||||
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2);
|
||||
|
||||
value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) |
|
||||
DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3);
|
||||
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_3);
|
||||
|
||||
return tegra_mipi_calibrate(dsi->mipi);
|
||||
}
|
||||
|
||||
static int tegra_dsi_init(struct host1x_client *client)
|
||||
{
|
||||
struct drm_device *drm = dev_get_drvdata(client->parent);
|
||||
struct tegra_dsi *dsi = host1x_client_to_dsi(client);
|
||||
int err;
|
||||
|
||||
reset_control_deassert(dsi->rst);
|
||||
|
||||
err = tegra_dsi_pad_calibrate(dsi);
|
||||
if (err < 0) {
|
||||
dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
|
||||
goto reset;
|
||||
}
|
||||
|
||||
/* Gangsters must not register their own outputs. */
|
||||
if (!dsi->master) {
|
||||
dsi->output.dev = client->dev;
|
||||
@ -1037,12 +1039,9 @@ static int tegra_dsi_init(struct host1x_client *client)
|
||||
drm_connector_register(&dsi->output.connector);
|
||||
|
||||
err = tegra_output_init(drm, &dsi->output);
|
||||
if (err < 0) {
|
||||
dev_err(client->dev,
|
||||
"failed to initialize output: %d\n",
|
||||
if (err < 0)
|
||||
dev_err(dsi->dev, "failed to initialize output: %d\n",
|
||||
err);
|
||||
goto reset;
|
||||
}
|
||||
|
||||
dsi->output.encoder.possible_crtcs = 0x3;
|
||||
}
|
||||
@ -1054,10 +1053,6 @@ static int tegra_dsi_init(struct host1x_client *client)
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
reset:
|
||||
reset_control_assert(dsi->rst);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra_dsi_exit(struct host1x_client *client)
|
||||
@ -1069,7 +1064,7 @@ static int tegra_dsi_exit(struct host1x_client *client)
|
||||
if (IS_ENABLED(CONFIG_DEBUG_FS))
|
||||
tegra_dsi_debugfs_exit(dsi);
|
||||
|
||||
reset_control_assert(dsi->rst);
|
||||
regulator_disable(dsi->vdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1493,74 +1488,50 @@ static int tegra_dsi_probe(struct platform_device *pdev)
|
||||
dsi->format = MIPI_DSI_FMT_RGB888;
|
||||
dsi->lanes = 4;
|
||||
|
||||
if (!pdev->dev.pm_domain) {
|
||||
dsi->rst = devm_reset_control_get(&pdev->dev, "dsi");
|
||||
if (IS_ERR(dsi->rst))
|
||||
return PTR_ERR(dsi->rst);
|
||||
}
|
||||
|
||||
dsi->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(dsi->clk)) {
|
||||
dev_err(&pdev->dev, "cannot get DSI clock\n");
|
||||
err = PTR_ERR(dsi->clk);
|
||||
goto reset;
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(dsi->clk);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "cannot enable DSI clock\n");
|
||||
goto reset;
|
||||
return PTR_ERR(dsi->clk);
|
||||
}
|
||||
|
||||
dsi->clk_lp = devm_clk_get(&pdev->dev, "lp");
|
||||
if (IS_ERR(dsi->clk_lp)) {
|
||||
dev_err(&pdev->dev, "cannot get low-power clock\n");
|
||||
err = PTR_ERR(dsi->clk_lp);
|
||||
goto disable_clk;
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(dsi->clk_lp);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "cannot enable low-power clock\n");
|
||||
goto disable_clk;
|
||||
return PTR_ERR(dsi->clk_lp);
|
||||
}
|
||||
|
||||
dsi->clk_parent = devm_clk_get(&pdev->dev, "parent");
|
||||
if (IS_ERR(dsi->clk_parent)) {
|
||||
dev_err(&pdev->dev, "cannot get parent clock\n");
|
||||
err = PTR_ERR(dsi->clk_parent);
|
||||
goto disable_clk_lp;
|
||||
return PTR_ERR(dsi->clk_parent);
|
||||
}
|
||||
|
||||
dsi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi");
|
||||
if (IS_ERR(dsi->vdd)) {
|
||||
dev_err(&pdev->dev, "cannot get VDD supply\n");
|
||||
err = PTR_ERR(dsi->vdd);
|
||||
goto disable_clk_lp;
|
||||
}
|
||||
|
||||
err = regulator_enable(dsi->vdd);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "cannot enable VDD supply\n");
|
||||
goto disable_clk_lp;
|
||||
return PTR_ERR(dsi->vdd);
|
||||
}
|
||||
|
||||
err = tegra_dsi_setup_clocks(dsi);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "cannot setup clocks\n");
|
||||
goto disable_vdd;
|
||||
return err;
|
||||
}
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
dsi->regs = devm_ioremap_resource(&pdev->dev, regs);
|
||||
if (IS_ERR(dsi->regs)) {
|
||||
err = PTR_ERR(dsi->regs);
|
||||
goto disable_vdd;
|
||||
}
|
||||
if (IS_ERR(dsi->regs))
|
||||
return PTR_ERR(dsi->regs);
|
||||
|
||||
dsi->mipi = tegra_mipi_request(&pdev->dev);
|
||||
if (IS_ERR(dsi->mipi)) {
|
||||
err = PTR_ERR(dsi->mipi);
|
||||
goto disable_vdd;
|
||||
}
|
||||
if (IS_ERR(dsi->mipi))
|
||||
return PTR_ERR(dsi->mipi);
|
||||
|
||||
dsi->host.ops = &tegra_dsi_host_ops;
|
||||
dsi->host.dev = &pdev->dev;
|
||||
@ -1571,6 +1542,9 @@ static int tegra_dsi_probe(struct platform_device *pdev)
|
||||
goto mipi_free;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dsi);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
INIT_LIST_HEAD(&dsi->client.list);
|
||||
dsi->client.ops = &dsi_client_ops;
|
||||
dsi->client.dev = &pdev->dev;
|
||||
@ -1582,22 +1556,12 @@ static int tegra_dsi_probe(struct platform_device *pdev)
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dsi);
|
||||
|
||||
return 0;
|
||||
|
||||
unregister:
|
||||
mipi_dsi_host_unregister(&dsi->host);
|
||||
mipi_free:
|
||||
tegra_mipi_free(dsi->mipi);
|
||||
disable_vdd:
|
||||
regulator_disable(dsi->vdd);
|
||||
disable_clk_lp:
|
||||
clk_disable_unprepare(dsi->clk_lp);
|
||||
disable_clk:
|
||||
clk_disable_unprepare(dsi->clk);
|
||||
reset:
|
||||
reset_control_assert(dsi->rst);
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -1606,6 +1570,8 @@ static int tegra_dsi_remove(struct platform_device *pdev)
|
||||
struct tegra_dsi *dsi = platform_get_drvdata(pdev);
|
||||
int err;
|
||||
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
err = host1x_client_unregister(&dsi->client);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to unregister host1x client: %d\n",
|
||||
@ -1618,14 +1584,82 @@ static int tegra_dsi_remove(struct platform_device *pdev)
|
||||
mipi_dsi_host_unregister(&dsi->host);
|
||||
tegra_mipi_free(dsi->mipi);
|
||||
|
||||
regulator_disable(dsi->vdd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int tegra_dsi_suspend(struct device *dev)
|
||||
{
|
||||
struct tegra_dsi *dsi = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
if (dsi->rst) {
|
||||
err = reset_control_assert(dsi->rst);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to assert reset: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
clk_disable_unprepare(dsi->clk_lp);
|
||||
clk_disable_unprepare(dsi->clk);
|
||||
reset_control_assert(dsi->rst);
|
||||
|
||||
regulator_disable(dsi->vdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_dsi_resume(struct device *dev)
|
||||
{
|
||||
struct tegra_dsi *dsi = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
err = regulator_enable(dsi->vdd);
|
||||
if (err < 0) {
|
||||
dev_err(dsi->dev, "failed to enable VDD supply: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(dsi->clk);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "cannot enable DSI clock: %d\n", err);
|
||||
goto disable_vdd;
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(dsi->clk_lp);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "cannot enable low-power clock: %d\n", err);
|
||||
goto disable_clk;
|
||||
}
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
if (dsi->rst) {
|
||||
err = reset_control_deassert(dsi->rst);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "cannot assert reset: %d\n", err);
|
||||
goto disable_clk_lp;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
disable_clk_lp:
|
||||
clk_disable_unprepare(dsi->clk_lp);
|
||||
disable_clk:
|
||||
clk_disable_unprepare(dsi->clk);
|
||||
disable_vdd:
|
||||
regulator_disable(dsi->vdd);
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops tegra_dsi_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(tegra_dsi_suspend, tegra_dsi_resume, NULL)
|
||||
};
|
||||
|
||||
static const struct of_device_id tegra_dsi_of_match[] = {
|
||||
{ .compatible = "nvidia,tegra210-dsi", },
|
||||
{ .compatible = "nvidia,tegra132-dsi", },
|
||||
@ -1639,6 +1673,7 @@ struct platform_driver tegra_dsi_driver = {
|
||||
.driver = {
|
||||
.name = "tegra-dsi",
|
||||
.of_match_table = tegra_dsi_of_match,
|
||||
.pm = &tegra_dsi_pm_ops,
|
||||
},
|
||||
.probe = tegra_dsi_probe,
|
||||
.remove = tegra_dsi_remove,
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/hdmi.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
@ -18,10 +19,14 @@
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
|
||||
#include <sound/hda_verbs.h>
|
||||
|
||||
#include "hdmi.h"
|
||||
#include "drm.h"
|
||||
#include "dc.h"
|
||||
|
||||
#define HDMI_ELD_BUFFER_SIZE 96
|
||||
|
||||
struct tmds_config {
|
||||
unsigned int pclk;
|
||||
u32 pll0;
|
||||
@ -39,6 +44,8 @@ struct tegra_hdmi_config {
|
||||
u32 fuse_override_value;
|
||||
|
||||
bool has_sor_io_peak_current;
|
||||
bool has_hda;
|
||||
bool has_hbr;
|
||||
};
|
||||
|
||||
struct tegra_hdmi {
|
||||
@ -60,7 +67,10 @@ struct tegra_hdmi {
|
||||
const struct tegra_hdmi_config *config;
|
||||
|
||||
unsigned int audio_source;
|
||||
unsigned int audio_freq;
|
||||
unsigned int audio_sample_rate;
|
||||
unsigned int audio_channels;
|
||||
|
||||
unsigned int pixel_clock;
|
||||
bool stereo;
|
||||
bool dvi;
|
||||
|
||||
@ -402,11 +412,11 @@ static const struct tmds_config tegra124_tmds_config[] = {
|
||||
};
|
||||
|
||||
static const struct tegra_hdmi_audio_config *
|
||||
tegra_hdmi_get_audio_config(unsigned int audio_freq, unsigned int pclk)
|
||||
tegra_hdmi_get_audio_config(unsigned int sample_rate, unsigned int pclk)
|
||||
{
|
||||
const struct tegra_hdmi_audio_config *table;
|
||||
|
||||
switch (audio_freq) {
|
||||
switch (sample_rate) {
|
||||
case 32000:
|
||||
table = tegra_hdmi_audio_32k;
|
||||
break;
|
||||
@ -476,44 +486,114 @@ static void tegra_hdmi_setup_audio_fs_tables(struct tegra_hdmi *hdmi)
|
||||
}
|
||||
}
|
||||
|
||||
static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk)
|
||||
static void tegra_hdmi_write_aval(struct tegra_hdmi *hdmi, u32 value)
|
||||
{
|
||||
static const struct {
|
||||
unsigned int sample_rate;
|
||||
unsigned int offset;
|
||||
} regs[] = {
|
||||
{ 32000, HDMI_NV_PDISP_SOR_AUDIO_AVAL_0320 },
|
||||
{ 44100, HDMI_NV_PDISP_SOR_AUDIO_AVAL_0441 },
|
||||
{ 48000, HDMI_NV_PDISP_SOR_AUDIO_AVAL_0480 },
|
||||
{ 88200, HDMI_NV_PDISP_SOR_AUDIO_AVAL_0882 },
|
||||
{ 96000, HDMI_NV_PDISP_SOR_AUDIO_AVAL_0960 },
|
||||
{ 176400, HDMI_NV_PDISP_SOR_AUDIO_AVAL_1764 },
|
||||
{ 192000, HDMI_NV_PDISP_SOR_AUDIO_AVAL_1920 },
|
||||
};
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(regs); i++) {
|
||||
if (regs[i].sample_rate == hdmi->audio_sample_rate) {
|
||||
tegra_hdmi_writel(hdmi, value, regs[i].offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi)
|
||||
{
|
||||
struct device_node *node = hdmi->dev->of_node;
|
||||
const struct tegra_hdmi_audio_config *config;
|
||||
unsigned int offset = 0;
|
||||
u32 value;
|
||||
u32 source, value;
|
||||
|
||||
switch (hdmi->audio_source) {
|
||||
case HDA:
|
||||
value = AUDIO_CNTRL0_SOURCE_SELECT_HDAL;
|
||||
if (hdmi->config->has_hda)
|
||||
source = SOR_AUDIO_CNTRL0_SOURCE_SELECT_HDAL;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
break;
|
||||
|
||||
case SPDIF:
|
||||
value = AUDIO_CNTRL0_SOURCE_SELECT_SPDIF;
|
||||
if (hdmi->config->has_hda)
|
||||
source = SOR_AUDIO_CNTRL0_SOURCE_SELECT_SPDIF;
|
||||
else
|
||||
source = AUDIO_CNTRL0_SOURCE_SELECT_SPDIF;
|
||||
break;
|
||||
|
||||
default:
|
||||
value = AUDIO_CNTRL0_SOURCE_SELECT_AUTO;
|
||||
if (hdmi->config->has_hda)
|
||||
source = SOR_AUDIO_CNTRL0_SOURCE_SELECT_AUTO;
|
||||
else
|
||||
source = AUDIO_CNTRL0_SOURCE_SELECT_AUTO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (of_device_is_compatible(node, "nvidia,tegra30-hdmi")) {
|
||||
value |= AUDIO_CNTRL0_ERROR_TOLERANCE(6) |
|
||||
AUDIO_CNTRL0_FRAMES_PER_BLOCK(0xc0);
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_CNTRL0);
|
||||
} else {
|
||||
value |= AUDIO_CNTRL0_INJECT_NULLSMPL;
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_AUDIO_CNTRL0);
|
||||
/*
|
||||
* Tegra30 and later use a slightly modified version of the register
|
||||
* layout to accomodate for changes related to supporting HDA as the
|
||||
* audio input source for HDMI. The source select field has moved to
|
||||
* the SOR_AUDIO_CNTRL0 register, but the error tolerance and frames
|
||||
* per block fields remain in the AUDIO_CNTRL0 register.
|
||||
*/
|
||||
if (hdmi->config->has_hda) {
|
||||
/*
|
||||
* Inject null samples into the audio FIFO for every frame in
|
||||
* which the codec did not receive any samples. This applies
|
||||
* to stereo LPCM only.
|
||||
*
|
||||
* XXX: This seems to be a remnant of MCP days when this was
|
||||
* used to work around issues with monitors not being able to
|
||||
* play back system startup sounds early. It is possibly not
|
||||
* needed on Linux at all.
|
||||
*/
|
||||
if (hdmi->audio_channels == 2)
|
||||
value = SOR_AUDIO_CNTRL0_INJECT_NULLSMPL;
|
||||
else
|
||||
value = 0;
|
||||
|
||||
value = AUDIO_CNTRL0_ERROR_TOLERANCE(6) |
|
||||
AUDIO_CNTRL0_FRAMES_PER_BLOCK(0xc0);
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_CNTRL0);
|
||||
value |= source;
|
||||
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_AUDIO_CNTRL0);
|
||||
}
|
||||
|
||||
config = tegra_hdmi_get_audio_config(hdmi->audio_freq, pclk);
|
||||
/*
|
||||
* On Tegra20, HDA is not a supported audio source and the source
|
||||
* select field is part of the AUDIO_CNTRL0 register.
|
||||
*/
|
||||
value = AUDIO_CNTRL0_FRAMES_PER_BLOCK(0xc0) |
|
||||
AUDIO_CNTRL0_ERROR_TOLERANCE(6);
|
||||
|
||||
if (!hdmi->config->has_hda)
|
||||
value |= source;
|
||||
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_CNTRL0);
|
||||
|
||||
/*
|
||||
* Advertise support for High Bit-Rate on Tegra114 and later.
|
||||
*/
|
||||
if (hdmi->config->has_hbr) {
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_AUDIO_SPARE0);
|
||||
value |= SOR_AUDIO_SPARE0_HBR_ENABLE;
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_AUDIO_SPARE0);
|
||||
}
|
||||
|
||||
config = tegra_hdmi_get_audio_config(hdmi->audio_sample_rate,
|
||||
hdmi->pixel_clock);
|
||||
if (!config) {
|
||||
dev_err(hdmi->dev, "cannot set audio to %u at %u pclk\n",
|
||||
hdmi->audio_freq, pclk);
|
||||
dev_err(hdmi->dev,
|
||||
"cannot set audio to %u Hz at %u Hz pixel clock\n",
|
||||
hdmi->audio_sample_rate, hdmi->pixel_clock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -526,8 +606,8 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk)
|
||||
tegra_hdmi_writel(hdmi, ACR_SUBPACK_N(config->n) | ACR_ENABLE,
|
||||
HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH);
|
||||
|
||||
value = ACR_SUBPACK_CTS(config->cts);
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW);
|
||||
tegra_hdmi_writel(hdmi, ACR_SUBPACK_CTS(config->cts),
|
||||
HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW);
|
||||
|
||||
value = SPARE_HW_CTS | SPARE_FORCE_SW_CTS | SPARE_CTS_RESET_VAL(1);
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_SPARE);
|
||||
@ -536,45 +616,55 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk)
|
||||
value &= ~AUDIO_N_RESETF;
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_N);
|
||||
|
||||
if (of_device_is_compatible(node, "nvidia,tegra30-hdmi")) {
|
||||
switch (hdmi->audio_freq) {
|
||||
case 32000:
|
||||
offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0320;
|
||||
break;
|
||||
|
||||
case 44100:
|
||||
offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0441;
|
||||
break;
|
||||
|
||||
case 48000:
|
||||
offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0480;
|
||||
break;
|
||||
|
||||
case 88200:
|
||||
offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0882;
|
||||
break;
|
||||
|
||||
case 96000:
|
||||
offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0960;
|
||||
break;
|
||||
|
||||
case 176400:
|
||||
offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_1764;
|
||||
break;
|
||||
|
||||
case 192000:
|
||||
offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_1920;
|
||||
break;
|
||||
}
|
||||
|
||||
tegra_hdmi_writel(hdmi, config->aval, offset);
|
||||
}
|
||||
if (hdmi->config->has_hda)
|
||||
tegra_hdmi_write_aval(hdmi, config->aval);
|
||||
|
||||
tegra_hdmi_setup_audio_fs_tables(hdmi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tegra_hdmi_disable_audio(struct tegra_hdmi *hdmi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
|
||||
value &= ~GENERIC_CTRL_AUDIO;
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
|
||||
}
|
||||
|
||||
static void tegra_hdmi_enable_audio(struct tegra_hdmi *hdmi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
|
||||
value |= GENERIC_CTRL_AUDIO;
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
|
||||
}
|
||||
|
||||
static void tegra_hdmi_write_eld(struct tegra_hdmi *hdmi)
|
||||
{
|
||||
size_t length = drm_eld_size(hdmi->output.connector.eld), i;
|
||||
u32 value;
|
||||
|
||||
for (i = 0; i < length; i++)
|
||||
tegra_hdmi_writel(hdmi, i << 8 | hdmi->output.connector.eld[i],
|
||||
HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR);
|
||||
|
||||
/*
|
||||
* The HDA codec will always report an ELD buffer size of 96 bytes and
|
||||
* the HDA codec driver will check that each byte read from the buffer
|
||||
* is valid. Therefore every byte must be written, even if no 96 bytes
|
||||
* were parsed from EDID.
|
||||
*/
|
||||
for (i = length; i < HDMI_ELD_BUFFER_SIZE; i++)
|
||||
tegra_hdmi_writel(hdmi, i << 8 | 0,
|
||||
HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR);
|
||||
|
||||
value = SOR_AUDIO_HDA_PRESENSE_VALID | SOR_AUDIO_HDA_PRESENSE_PRESENT;
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE);
|
||||
}
|
||||
|
||||
static inline u32 tegra_hdmi_subpack(const u8 *ptr, size_t size)
|
||||
{
|
||||
u32 value = 0;
|
||||
@ -644,12 +734,6 @@ static void tegra_hdmi_setup_avi_infoframe(struct tegra_hdmi *hdmi,
|
||||
u8 buffer[17];
|
||||
ssize_t err;
|
||||
|
||||
if (hdmi->dvi) {
|
||||
tegra_hdmi_writel(hdmi, 0,
|
||||
HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL);
|
||||
return;
|
||||
}
|
||||
|
||||
err = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
|
||||
if (err < 0) {
|
||||
dev_err(hdmi->dev, "failed to setup AVI infoframe: %zd\n", err);
|
||||
@ -663,9 +747,24 @@ static void tegra_hdmi_setup_avi_infoframe(struct tegra_hdmi *hdmi,
|
||||
}
|
||||
|
||||
tegra_hdmi_write_infopack(hdmi, buffer, err);
|
||||
}
|
||||
|
||||
tegra_hdmi_writel(hdmi, INFOFRAME_CTRL_ENABLE,
|
||||
HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL);
|
||||
static void tegra_hdmi_disable_avi_infoframe(struct tegra_hdmi *hdmi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL);
|
||||
value &= ~INFOFRAME_CTRL_ENABLE;
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL);
|
||||
}
|
||||
|
||||
static void tegra_hdmi_enable_avi_infoframe(struct tegra_hdmi *hdmi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL);
|
||||
value |= INFOFRAME_CTRL_ENABLE;
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL);
|
||||
}
|
||||
|
||||
static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi)
|
||||
@ -674,12 +773,6 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi)
|
||||
u8 buffer[14];
|
||||
ssize_t err;
|
||||
|
||||
if (hdmi->dvi) {
|
||||
tegra_hdmi_writel(hdmi, 0,
|
||||
HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL);
|
||||
return;
|
||||
}
|
||||
|
||||
err = hdmi_audio_infoframe_init(&frame);
|
||||
if (err < 0) {
|
||||
dev_err(hdmi->dev, "failed to setup audio infoframe: %zd\n",
|
||||
@ -687,7 +780,7 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi)
|
||||
return;
|
||||
}
|
||||
|
||||
frame.channels = 2;
|
||||
frame.channels = hdmi->audio_channels;
|
||||
|
||||
err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer));
|
||||
if (err < 0) {
|
||||
@ -703,9 +796,24 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi)
|
||||
* bytes can be programmed.
|
||||
*/
|
||||
tegra_hdmi_write_infopack(hdmi, buffer, min_t(size_t, 10, err));
|
||||
}
|
||||
|
||||
tegra_hdmi_writel(hdmi, INFOFRAME_CTRL_ENABLE,
|
||||
HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL);
|
||||
static void tegra_hdmi_disable_audio_infoframe(struct tegra_hdmi *hdmi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL);
|
||||
value &= ~INFOFRAME_CTRL_ENABLE;
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL);
|
||||
}
|
||||
|
||||
static void tegra_hdmi_enable_audio_infoframe(struct tegra_hdmi *hdmi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL);
|
||||
value |= INFOFRAME_CTRL_ENABLE;
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL);
|
||||
}
|
||||
|
||||
static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi)
|
||||
@ -713,14 +821,6 @@ static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi)
|
||||
struct hdmi_vendor_infoframe frame;
|
||||
u8 buffer[10];
|
||||
ssize_t err;
|
||||
u32 value;
|
||||
|
||||
if (!hdmi->stereo) {
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
|
||||
value &= ~GENERIC_CTRL_ENABLE;
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
|
||||
return;
|
||||
}
|
||||
|
||||
hdmi_vendor_infoframe_init(&frame);
|
||||
frame.s3d_struct = HDMI_3D_STRUCTURE_FRAME_PACKING;
|
||||
@ -733,6 +833,20 @@ static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi)
|
||||
}
|
||||
|
||||
tegra_hdmi_write_infopack(hdmi, buffer, err);
|
||||
}
|
||||
|
||||
static void tegra_hdmi_disable_stereo_infoframe(struct tegra_hdmi *hdmi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
|
||||
value &= ~GENERIC_CTRL_ENABLE;
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
|
||||
}
|
||||
|
||||
static void tegra_hdmi_enable_stereo_infoframe(struct tegra_hdmi *hdmi)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
|
||||
value |= GENERIC_CTRL_ENABLE;
|
||||
@ -772,10 +886,25 @@ static bool tegra_output_is_hdmi(struct tegra_output *output)
|
||||
return drm_detect_hdmi_monitor(edid);
|
||||
}
|
||||
|
||||
static enum drm_connector_status
|
||||
tegra_hdmi_connector_detect(struct drm_connector *connector, bool force)
|
||||
{
|
||||
struct tegra_output *output = connector_to_output(connector);
|
||||
struct tegra_hdmi *hdmi = to_hdmi(output);
|
||||
enum drm_connector_status status;
|
||||
|
||||
status = tegra_output_connector_detect(connector, force);
|
||||
if (status == connector_status_connected)
|
||||
return status;
|
||||
|
||||
tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE);
|
||||
return status;
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs tegra_hdmi_connector_funcs = {
|
||||
.dpms = drm_atomic_helper_connector_dpms,
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.detect = tegra_output_connector_detect,
|
||||
.detect = tegra_hdmi_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = tegra_output_connector_destroy,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
@ -814,7 +943,9 @@ static const struct drm_encoder_funcs tegra_hdmi_encoder_funcs = {
|
||||
|
||||
static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
||||
struct tegra_hdmi *hdmi = to_hdmi(output);
|
||||
u32 value;
|
||||
|
||||
/*
|
||||
@ -828,6 +959,20 @@ static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder)
|
||||
|
||||
tegra_dc_commit(dc);
|
||||
}
|
||||
|
||||
if (!hdmi->dvi) {
|
||||
if (hdmi->stereo)
|
||||
tegra_hdmi_disable_stereo_infoframe(hdmi);
|
||||
|
||||
tegra_hdmi_disable_audio_infoframe(hdmi);
|
||||
tegra_hdmi_disable_avi_infoframe(hdmi);
|
||||
tegra_hdmi_disable_audio(hdmi);
|
||||
}
|
||||
|
||||
tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_ENABLE);
|
||||
tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_MASK);
|
||||
|
||||
pm_runtime_put(hdmi->dev);
|
||||
}
|
||||
|
||||
static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder)
|
||||
@ -836,21 +981,28 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder)
|
||||
unsigned int h_sync_width, h_front_porch, h_back_porch, i, rekey;
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
||||
struct device_node *node = output->dev->of_node;
|
||||
struct tegra_hdmi *hdmi = to_hdmi(output);
|
||||
unsigned int pulse_start, div82, pclk;
|
||||
unsigned int pulse_start, div82;
|
||||
int retries = 1000;
|
||||
u32 value;
|
||||
int err;
|
||||
|
||||
hdmi->dvi = !tegra_output_is_hdmi(output);
|
||||
pm_runtime_get_sync(hdmi->dev);
|
||||
|
||||
pclk = mode->clock * 1000;
|
||||
/*
|
||||
* Enable and unmask the HDA codec SCRATCH0 register interrupt. This
|
||||
* is used for interoperability between the HDA codec driver and the
|
||||
* HDMI driver.
|
||||
*/
|
||||
tegra_hdmi_writel(hdmi, INT_CODEC_SCRATCH0, HDMI_NV_PDISP_INT_ENABLE);
|
||||
tegra_hdmi_writel(hdmi, INT_CODEC_SCRATCH0, HDMI_NV_PDISP_INT_MASK);
|
||||
|
||||
hdmi->pixel_clock = mode->clock * 1000;
|
||||
h_sync_width = mode->hsync_end - mode->hsync_start;
|
||||
h_back_porch = mode->htotal - mode->hsync_end;
|
||||
h_front_porch = mode->hsync_start - mode->hdisplay;
|
||||
|
||||
err = clk_set_rate(hdmi->clk, pclk);
|
||||
err = clk_set_rate(hdmi->clk, hdmi->pixel_clock);
|
||||
if (err < 0) {
|
||||
dev_err(hdmi->dev, "failed to set HDMI clock frequency: %d\n",
|
||||
err);
|
||||
@ -909,17 +1061,15 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder)
|
||||
value = SOR_REFCLK_DIV_INT(div82 >> 2) | SOR_REFCLK_DIV_FRAC(div82);
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_REFCLK);
|
||||
|
||||
hdmi->dvi = !tegra_output_is_hdmi(output);
|
||||
if (!hdmi->dvi) {
|
||||
err = tegra_hdmi_setup_audio(hdmi, pclk);
|
||||
err = tegra_hdmi_setup_audio(hdmi);
|
||||
if (err < 0)
|
||||
hdmi->dvi = true;
|
||||
}
|
||||
|
||||
if (of_device_is_compatible(node, "nvidia,tegra20-hdmi")) {
|
||||
/*
|
||||
* TODO: add ELD support
|
||||
*/
|
||||
}
|
||||
if (hdmi->config->has_hda)
|
||||
tegra_hdmi_write_eld(hdmi);
|
||||
|
||||
rekey = HDMI_REKEY_DEFAULT;
|
||||
value = HDMI_CTRL_REKEY(rekey);
|
||||
@ -931,20 +1081,17 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder)
|
||||
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_CTRL);
|
||||
|
||||
if (hdmi->dvi)
|
||||
tegra_hdmi_writel(hdmi, 0x0,
|
||||
HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
|
||||
else
|
||||
tegra_hdmi_writel(hdmi, GENERIC_CTRL_AUDIO,
|
||||
HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
|
||||
|
||||
if (!hdmi->dvi) {
|
||||
tegra_hdmi_setup_avi_infoframe(hdmi, mode);
|
||||
tegra_hdmi_setup_audio_infoframe(hdmi);
|
||||
|
||||
if (hdmi->stereo)
|
||||
tegra_hdmi_setup_stereo_infoframe(hdmi);
|
||||
}
|
||||
|
||||
/* TMDS CONFIG */
|
||||
for (i = 0; i < hdmi->config->num_tmds; i++) {
|
||||
if (pclk <= hdmi->config->tmds[i].pclk) {
|
||||
if (hdmi->pixel_clock <= hdmi->config->tmds[i].pclk) {
|
||||
tegra_hdmi_setup_tmds(hdmi, &hdmi->config->tmds[i]);
|
||||
break;
|
||||
}
|
||||
@ -1031,6 +1178,15 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder)
|
||||
|
||||
tegra_dc_commit(dc);
|
||||
|
||||
if (!hdmi->dvi) {
|
||||
tegra_hdmi_enable_avi_infoframe(hdmi);
|
||||
tegra_hdmi_enable_audio_infoframe(hdmi);
|
||||
tegra_hdmi_enable_audio(hdmi);
|
||||
|
||||
if (hdmi->stereo)
|
||||
tegra_hdmi_enable_stereo_infoframe(hdmi);
|
||||
}
|
||||
|
||||
/* TODO: add HDCP support */
|
||||
}
|
||||
|
||||
@ -1235,8 +1391,14 @@ static int tegra_hdmi_show_regs(struct seq_file *s, void *data)
|
||||
DUMP_REG(HDMI_NV_PDISP_KEY_HDCP_KEY_TRIG);
|
||||
DUMP_REG(HDMI_NV_PDISP_KEY_SKEY_INDEX);
|
||||
DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_CNTRL0);
|
||||
DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_SPARE0);
|
||||
DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0);
|
||||
DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH1);
|
||||
DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR);
|
||||
DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE);
|
||||
DUMP_REG(HDMI_NV_PDISP_INT_STATUS);
|
||||
DUMP_REG(HDMI_NV_PDISP_INT_MASK);
|
||||
DUMP_REG(HDMI_NV_PDISP_INT_ENABLE);
|
||||
DUMP_REG(HDMI_NV_PDISP_SOR_IO_PEAK_CURRENT);
|
||||
|
||||
#undef DUMP_REG
|
||||
@ -1360,14 +1522,6 @@ static int tegra_hdmi_init(struct host1x_client *client)
|
||||
return err;
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(hdmi->clk);
|
||||
if (err < 0) {
|
||||
dev_err(hdmi->dev, "failed to enable clock: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
reset_control_deassert(hdmi->rst);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1377,9 +1531,6 @@ static int tegra_hdmi_exit(struct host1x_client *client)
|
||||
|
||||
tegra_output_exit(&hdmi->output);
|
||||
|
||||
reset_control_assert(hdmi->rst);
|
||||
clk_disable_unprepare(hdmi->clk);
|
||||
|
||||
regulator_disable(hdmi->vdd);
|
||||
regulator_disable(hdmi->pll);
|
||||
regulator_disable(hdmi->hdmi);
|
||||
@ -1401,6 +1552,8 @@ static const struct tegra_hdmi_config tegra20_hdmi_config = {
|
||||
.fuse_override_offset = HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT,
|
||||
.fuse_override_value = 1 << 31,
|
||||
.has_sor_io_peak_current = false,
|
||||
.has_hda = false,
|
||||
.has_hbr = false,
|
||||
};
|
||||
|
||||
static const struct tegra_hdmi_config tegra30_hdmi_config = {
|
||||
@ -1409,6 +1562,8 @@ static const struct tegra_hdmi_config tegra30_hdmi_config = {
|
||||
.fuse_override_offset = HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT,
|
||||
.fuse_override_value = 1 << 31,
|
||||
.has_sor_io_peak_current = false,
|
||||
.has_hda = true,
|
||||
.has_hbr = false,
|
||||
};
|
||||
|
||||
static const struct tegra_hdmi_config tegra114_hdmi_config = {
|
||||
@ -1417,6 +1572,8 @@ static const struct tegra_hdmi_config tegra114_hdmi_config = {
|
||||
.fuse_override_offset = HDMI_NV_PDISP_SOR_PAD_CTLS0,
|
||||
.fuse_override_value = 1 << 31,
|
||||
.has_sor_io_peak_current = true,
|
||||
.has_hda = true,
|
||||
.has_hbr = true,
|
||||
};
|
||||
|
||||
static const struct tegra_hdmi_config tegra124_hdmi_config = {
|
||||
@ -1425,6 +1582,8 @@ static const struct tegra_hdmi_config tegra124_hdmi_config = {
|
||||
.fuse_override_offset = HDMI_NV_PDISP_SOR_PAD_CTLS0,
|
||||
.fuse_override_value = 1 << 31,
|
||||
.has_sor_io_peak_current = true,
|
||||
.has_hda = true,
|
||||
.has_hbr = true,
|
||||
};
|
||||
|
||||
static const struct of_device_id tegra_hdmi_of_match[] = {
|
||||
@ -1436,6 +1595,67 @@ static const struct of_device_id tegra_hdmi_of_match[] = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tegra_hdmi_of_match);
|
||||
|
||||
static void hda_format_parse(unsigned int format, unsigned int *rate,
|
||||
unsigned int *channels)
|
||||
{
|
||||
unsigned int mul, div;
|
||||
|
||||
if (format & AC_FMT_BASE_44K)
|
||||
*rate = 44100;
|
||||
else
|
||||
*rate = 48000;
|
||||
|
||||
mul = (format & AC_FMT_MULT_MASK) >> AC_FMT_MULT_SHIFT;
|
||||
div = (format & AC_FMT_DIV_MASK) >> AC_FMT_DIV_SHIFT;
|
||||
|
||||
*rate = *rate * (mul + 1) / (div + 1);
|
||||
|
||||
*channels = (format & AC_FMT_CHAN_MASK) >> AC_FMT_CHAN_SHIFT;
|
||||
}
|
||||
|
||||
static irqreturn_t tegra_hdmi_irq(int irq, void *data)
|
||||
{
|
||||
struct tegra_hdmi *hdmi = data;
|
||||
u32 value;
|
||||
int err;
|
||||
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_INT_STATUS);
|
||||
tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_INT_STATUS);
|
||||
|
||||
if (value & INT_CODEC_SCRATCH0) {
|
||||
unsigned int format;
|
||||
u32 value;
|
||||
|
||||
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0);
|
||||
|
||||
if (value & SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID) {
|
||||
unsigned int sample_rate, channels;
|
||||
|
||||
format = value & SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK;
|
||||
|
||||
hda_format_parse(format, &sample_rate, &channels);
|
||||
|
||||
hdmi->audio_sample_rate = sample_rate;
|
||||
hdmi->audio_channels = channels;
|
||||
|
||||
err = tegra_hdmi_setup_audio(hdmi);
|
||||
if (err < 0) {
|
||||
tegra_hdmi_disable_audio_infoframe(hdmi);
|
||||
tegra_hdmi_disable_audio(hdmi);
|
||||
} else {
|
||||
tegra_hdmi_setup_audio_infoframe(hdmi);
|
||||
tegra_hdmi_enable_audio_infoframe(hdmi);
|
||||
tegra_hdmi_enable_audio(hdmi);
|
||||
}
|
||||
} else {
|
||||
tegra_hdmi_disable_audio_infoframe(hdmi);
|
||||
tegra_hdmi_disable_audio(hdmi);
|
||||
}
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int tegra_hdmi_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct of_device_id *match;
|
||||
@ -1453,8 +1673,10 @@ static int tegra_hdmi_probe(struct platform_device *pdev)
|
||||
|
||||
hdmi->config = match->data;
|
||||
hdmi->dev = &pdev->dev;
|
||||
|
||||
hdmi->audio_source = AUTO;
|
||||
hdmi->audio_freq = 44100;
|
||||
hdmi->audio_sample_rate = 48000;
|
||||
hdmi->audio_channels = 2;
|
||||
hdmi->stereo = false;
|
||||
hdmi->dvi = false;
|
||||
|
||||
@ -1515,6 +1737,17 @@ static int tegra_hdmi_probe(struct platform_device *pdev)
|
||||
|
||||
hdmi->irq = err;
|
||||
|
||||
err = devm_request_irq(hdmi->dev, hdmi->irq, tegra_hdmi_irq, 0,
|
||||
dev_name(hdmi->dev), hdmi);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to request IRQ#%u: %d\n",
|
||||
hdmi->irq, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, hdmi);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
INIT_LIST_HEAD(&hdmi->client.list);
|
||||
hdmi->client.ops = &hdmi_client_ops;
|
||||
hdmi->client.dev = &pdev->dev;
|
||||
@ -1526,8 +1759,6 @@ static int tegra_hdmi_probe(struct platform_device *pdev)
|
||||
return err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, hdmi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1536,6 +1767,8 @@ static int tegra_hdmi_remove(struct platform_device *pdev)
|
||||
struct tegra_hdmi *hdmi = platform_get_drvdata(pdev);
|
||||
int err;
|
||||
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
err = host1x_client_unregister(&hdmi->client);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to unregister host1x client: %d\n",
|
||||
@ -1545,17 +1778,61 @@ static int tegra_hdmi_remove(struct platform_device *pdev)
|
||||
|
||||
tegra_output_remove(&hdmi->output);
|
||||
|
||||
clk_disable_unprepare(hdmi->clk_parent);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int tegra_hdmi_suspend(struct device *dev)
|
||||
{
|
||||
struct tegra_hdmi *hdmi = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
err = reset_control_assert(hdmi->rst);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to assert reset: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
clk_disable_unprepare(hdmi->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_hdmi_resume(struct device *dev)
|
||||
{
|
||||
struct tegra_hdmi *hdmi = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
err = clk_prepare_enable(hdmi->clk);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to enable clock: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
err = reset_control_deassert(hdmi->rst);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to deassert reset: %d\n", err);
|
||||
clk_disable_unprepare(hdmi->clk);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops tegra_hdmi_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(tegra_hdmi_suspend, tegra_hdmi_resume, NULL)
|
||||
};
|
||||
|
||||
struct platform_driver tegra_hdmi_driver = {
|
||||
.driver = {
|
||||
.name = "tegra-hdmi",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = tegra_hdmi_of_match,
|
||||
.pm = &tegra_hdmi_pm_ops,
|
||||
},
|
||||
.probe = tegra_hdmi_probe,
|
||||
.remove = tegra_hdmi_remove,
|
||||
|
@ -468,9 +468,20 @@
|
||||
#define HDMI_NV_PDISP_KEY_SKEY_INDEX 0xa3
|
||||
|
||||
#define HDMI_NV_PDISP_SOR_AUDIO_CNTRL0 0xac
|
||||
#define AUDIO_CNTRL0_INJECT_NULLSMPL (1 << 29)
|
||||
#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_AUTO (0 << 20)
|
||||
#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_SPDIF (1 << 20)
|
||||
#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_HDAL (2 << 20)
|
||||
#define SOR_AUDIO_CNTRL0_INJECT_NULLSMPL (1 << 29)
|
||||
#define HDMI_NV_PDISP_SOR_AUDIO_SPARE0 0xae
|
||||
#define SOR_AUDIO_SPARE0_HBR_ENABLE (1 << 27)
|
||||
#define HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0 0xba
|
||||
#define SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID (1 << 30)
|
||||
#define SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK 0xffff
|
||||
#define HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH1 0xbb
|
||||
#define HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR 0xbc
|
||||
#define HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE 0xbd
|
||||
#define SOR_AUDIO_HDA_PRESENSE_VALID (1 << 1)
|
||||
#define SOR_AUDIO_HDA_PRESENSE_PRESENT (1 << 0)
|
||||
|
||||
#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0320 0xbf
|
||||
#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0441 0xc0
|
||||
@ -481,6 +492,14 @@
|
||||
#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_1920 0xc5
|
||||
#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_DEFAULT 0xc5
|
||||
|
||||
#define HDMI_NV_PDISP_INT_STATUS 0xcc
|
||||
#define INT_SCRATCH (1 << 3)
|
||||
#define INT_CP_REQUEST (1 << 2)
|
||||
#define INT_CODEC_SCRATCH1 (1 << 1)
|
||||
#define INT_CODEC_SCRATCH0 (1 << 0)
|
||||
#define HDMI_NV_PDISP_INT_MASK 0xcd
|
||||
#define HDMI_NV_PDISP_INT_ENABLE 0xce
|
||||
|
||||
#define HDMI_NV_PDISP_SOR_IO_PEAK_CURRENT 0xd1
|
||||
#define PEAK_CURRENT_LANE0(x) (((x) & 0x7f) << 0)
|
||||
#define PEAK_CURRENT_LANE1(x) (((x) & 0x7f) << 8)
|
||||
|
@ -36,6 +36,7 @@ int tegra_output_connector_get_modes(struct drm_connector *connector)
|
||||
|
||||
if (edid) {
|
||||
err = drm_add_edid_modes(connector, edid);
|
||||
drm_edid_to_eld(connector, edid);
|
||||
kfree(edid);
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,13 @@
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
@ -149,6 +151,8 @@ struct tegra_sor_soc {
|
||||
|
||||
const struct tegra_sor_hdmi_settings *settings;
|
||||
unsigned int num_settings;
|
||||
|
||||
const u8 *xbar_cfg;
|
||||
};
|
||||
|
||||
struct tegra_sor;
|
||||
@ -169,7 +173,9 @@ struct tegra_sor {
|
||||
|
||||
struct reset_control *rst;
|
||||
struct clk *clk_parent;
|
||||
struct clk *clk_brick;
|
||||
struct clk *clk_safe;
|
||||
struct clk *clk_src;
|
||||
struct clk *clk_dp;
|
||||
struct clk *clk;
|
||||
|
||||
@ -190,6 +196,18 @@ struct tegra_sor {
|
||||
struct regulator *hdmi_supply;
|
||||
};
|
||||
|
||||
struct tegra_sor_state {
|
||||
struct drm_connector_state base;
|
||||
|
||||
unsigned int bpc;
|
||||
};
|
||||
|
||||
static inline struct tegra_sor_state *
|
||||
to_sor_state(struct drm_connector_state *state)
|
||||
{
|
||||
return container_of(state, struct tegra_sor_state, base);
|
||||
}
|
||||
|
||||
struct tegra_sor_config {
|
||||
u32 bits_per_pixel;
|
||||
|
||||
@ -225,6 +243,118 @@ static inline void tegra_sor_writel(struct tegra_sor *sor, u32 value,
|
||||
writel(value, sor->regs + (offset << 2));
|
||||
}
|
||||
|
||||
static int tegra_sor_set_parent_clock(struct tegra_sor *sor, struct clk *parent)
|
||||
{
|
||||
int err;
|
||||
|
||||
clk_disable_unprepare(sor->clk);
|
||||
|
||||
err = clk_set_parent(sor->clk, parent);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = clk_prepare_enable(sor->clk);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct tegra_clk_sor_brick {
|
||||
struct clk_hw hw;
|
||||
struct tegra_sor *sor;
|
||||
};
|
||||
|
||||
static inline struct tegra_clk_sor_brick *to_brick(struct clk_hw *hw)
|
||||
{
|
||||
return container_of(hw, struct tegra_clk_sor_brick, hw);
|
||||
}
|
||||
|
||||
static const char * const tegra_clk_sor_brick_parents[] = {
|
||||
"pll_d2_out0", "pll_dp"
|
||||
};
|
||||
|
||||
static int tegra_clk_sor_brick_set_parent(struct clk_hw *hw, u8 index)
|
||||
{
|
||||
struct tegra_clk_sor_brick *brick = to_brick(hw);
|
||||
struct tegra_sor *sor = brick->sor;
|
||||
u32 value;
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_CLK_CNTRL);
|
||||
value &= ~SOR_CLK_CNTRL_DP_CLK_SEL_MASK;
|
||||
|
||||
switch (index) {
|
||||
case 0:
|
||||
value |= SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_PCLK;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
value |= SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_DPCLK;
|
||||
break;
|
||||
}
|
||||
|
||||
tegra_sor_writel(sor, value, SOR_CLK_CNTRL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u8 tegra_clk_sor_brick_get_parent(struct clk_hw *hw)
|
||||
{
|
||||
struct tegra_clk_sor_brick *brick = to_brick(hw);
|
||||
struct tegra_sor *sor = brick->sor;
|
||||
u8 parent = U8_MAX;
|
||||
u32 value;
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_CLK_CNTRL);
|
||||
|
||||
switch (value & SOR_CLK_CNTRL_DP_CLK_SEL_MASK) {
|
||||
case SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_PCLK:
|
||||
case SOR_CLK_CNTRL_DP_CLK_SEL_DIFF_PCLK:
|
||||
parent = 0;
|
||||
break;
|
||||
|
||||
case SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_DPCLK:
|
||||
case SOR_CLK_CNTRL_DP_CLK_SEL_DIFF_DPCLK:
|
||||
parent = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
static const struct clk_ops tegra_clk_sor_brick_ops = {
|
||||
.set_parent = tegra_clk_sor_brick_set_parent,
|
||||
.get_parent = tegra_clk_sor_brick_get_parent,
|
||||
};
|
||||
|
||||
static struct clk *tegra_clk_sor_brick_register(struct tegra_sor *sor,
|
||||
const char *name)
|
||||
{
|
||||
struct tegra_clk_sor_brick *brick;
|
||||
struct clk_init_data init;
|
||||
struct clk *clk;
|
||||
|
||||
brick = devm_kzalloc(sor->dev, sizeof(*brick), GFP_KERNEL);
|
||||
if (!brick)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
brick->sor = sor;
|
||||
|
||||
init.name = name;
|
||||
init.flags = 0;
|
||||
init.parent_names = tegra_clk_sor_brick_parents;
|
||||
init.num_parents = ARRAY_SIZE(tegra_clk_sor_brick_parents);
|
||||
init.ops = &tegra_clk_sor_brick_ops;
|
||||
|
||||
brick->hw.init = &init;
|
||||
|
||||
clk = devm_clk_register(sor->dev, &brick->hw);
|
||||
if (IS_ERR(clk))
|
||||
kfree(brick);
|
||||
|
||||
return clk;
|
||||
}
|
||||
|
||||
static int tegra_sor_dp_train_fast(struct tegra_sor *sor,
|
||||
struct drm_dp_link *link)
|
||||
{
|
||||
@ -569,7 +699,7 @@ static int tegra_sor_compute_params(struct tegra_sor *sor,
|
||||
return false;
|
||||
}
|
||||
|
||||
static int tegra_sor_calc_config(struct tegra_sor *sor,
|
||||
static int tegra_sor_compute_config(struct tegra_sor *sor,
|
||||
const struct drm_display_mode *mode,
|
||||
struct tegra_sor_config *config,
|
||||
struct drm_dp_link *link)
|
||||
@ -661,6 +791,135 @@ static int tegra_sor_calc_config(struct tegra_sor *sor,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tegra_sor_apply_config(struct tegra_sor *sor,
|
||||
const struct tegra_sor_config *config)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_DP_LINKCTL0);
|
||||
value &= ~SOR_DP_LINKCTL_TU_SIZE_MASK;
|
||||
value |= SOR_DP_LINKCTL_TU_SIZE(config->tu_size);
|
||||
tegra_sor_writel(sor, value, SOR_DP_LINKCTL0);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_DP_CONFIG0);
|
||||
value &= ~SOR_DP_CONFIG_WATERMARK_MASK;
|
||||
value |= SOR_DP_CONFIG_WATERMARK(config->watermark);
|
||||
|
||||
value &= ~SOR_DP_CONFIG_ACTIVE_SYM_COUNT_MASK;
|
||||
value |= SOR_DP_CONFIG_ACTIVE_SYM_COUNT(config->active_count);
|
||||
|
||||
value &= ~SOR_DP_CONFIG_ACTIVE_SYM_FRAC_MASK;
|
||||
value |= SOR_DP_CONFIG_ACTIVE_SYM_FRAC(config->active_frac);
|
||||
|
||||
if (config->active_polarity)
|
||||
value |= SOR_DP_CONFIG_ACTIVE_SYM_POLARITY;
|
||||
else
|
||||
value &= ~SOR_DP_CONFIG_ACTIVE_SYM_POLARITY;
|
||||
|
||||
value |= SOR_DP_CONFIG_ACTIVE_SYM_ENABLE;
|
||||
value |= SOR_DP_CONFIG_DISPARITY_NEGATIVE;
|
||||
tegra_sor_writel(sor, value, SOR_DP_CONFIG0);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_DP_AUDIO_HBLANK_SYMBOLS);
|
||||
value &= ~SOR_DP_AUDIO_HBLANK_SYMBOLS_MASK;
|
||||
value |= config->hblank_symbols & 0xffff;
|
||||
tegra_sor_writel(sor, value, SOR_DP_AUDIO_HBLANK_SYMBOLS);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_DP_AUDIO_VBLANK_SYMBOLS);
|
||||
value &= ~SOR_DP_AUDIO_VBLANK_SYMBOLS_MASK;
|
||||
value |= config->vblank_symbols & 0xffff;
|
||||
tegra_sor_writel(sor, value, SOR_DP_AUDIO_VBLANK_SYMBOLS);
|
||||
}
|
||||
|
||||
static void tegra_sor_mode_set(struct tegra_sor *sor,
|
||||
const struct drm_display_mode *mode,
|
||||
struct tegra_sor_state *state)
|
||||
{
|
||||
struct tegra_dc *dc = to_tegra_dc(sor->output.encoder.crtc);
|
||||
unsigned int vbe, vse, hbe, hse, vbs, hbs;
|
||||
u32 value;
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_STATE1);
|
||||
value &= ~SOR_STATE_ASY_PIXELDEPTH_MASK;
|
||||
value &= ~SOR_STATE_ASY_CRC_MODE_MASK;
|
||||
value &= ~SOR_STATE_ASY_OWNER_MASK;
|
||||
|
||||
value |= SOR_STATE_ASY_CRC_MODE_COMPLETE |
|
||||
SOR_STATE_ASY_OWNER(dc->pipe + 1);
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_PHSYNC)
|
||||
value &= ~SOR_STATE_ASY_HSYNCPOL;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
|
||||
value |= SOR_STATE_ASY_HSYNCPOL;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_PVSYNC)
|
||||
value &= ~SOR_STATE_ASY_VSYNCPOL;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
||||
value |= SOR_STATE_ASY_VSYNCPOL;
|
||||
|
||||
switch (state->bpc) {
|
||||
case 16:
|
||||
value |= SOR_STATE_ASY_PIXELDEPTH_BPP_48_444;
|
||||
break;
|
||||
|
||||
case 12:
|
||||
value |= SOR_STATE_ASY_PIXELDEPTH_BPP_36_444;
|
||||
break;
|
||||
|
||||
case 10:
|
||||
value |= SOR_STATE_ASY_PIXELDEPTH_BPP_30_444;
|
||||
break;
|
||||
|
||||
case 8:
|
||||
value |= SOR_STATE_ASY_PIXELDEPTH_BPP_24_444;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
value |= SOR_STATE_ASY_PIXELDEPTH_BPP_18_444;
|
||||
break;
|
||||
|
||||
default:
|
||||
value |= SOR_STATE_ASY_PIXELDEPTH_BPP_24_444;
|
||||
break;
|
||||
}
|
||||
|
||||
tegra_sor_writel(sor, value, SOR_STATE1);
|
||||
|
||||
/*
|
||||
* TODO: The video timing programming below doesn't seem to match the
|
||||
* register definitions.
|
||||
*/
|
||||
|
||||
value = ((mode->vtotal & 0x7fff) << 16) | (mode->htotal & 0x7fff);
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE1(dc->pipe));
|
||||
|
||||
/* sync end = sync width - 1 */
|
||||
vse = mode->vsync_end - mode->vsync_start - 1;
|
||||
hse = mode->hsync_end - mode->hsync_start - 1;
|
||||
|
||||
value = ((vse & 0x7fff) << 16) | (hse & 0x7fff);
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE2(dc->pipe));
|
||||
|
||||
/* blank end = sync end + back porch */
|
||||
vbe = vse + (mode->vtotal - mode->vsync_end);
|
||||
hbe = hse + (mode->htotal - mode->hsync_end);
|
||||
|
||||
value = ((vbe & 0x7fff) << 16) | (hbe & 0x7fff);
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE3(dc->pipe));
|
||||
|
||||
/* blank start = blank end + active */
|
||||
vbs = vbe + mode->vdisplay;
|
||||
hbs = hbe + mode->hdisplay;
|
||||
|
||||
value = ((vbs & 0x7fff) << 16) | (hbs & 0x7fff);
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE4(dc->pipe));
|
||||
|
||||
/* XXX interlacing support */
|
||||
tegra_sor_writel(sor, 0x001, SOR_HEAD_STATE5(dc->pipe));
|
||||
}
|
||||
|
||||
static int tegra_sor_detach(struct tegra_sor *sor)
|
||||
{
|
||||
unsigned long value, timeout;
|
||||
@ -733,7 +992,8 @@ static int tegra_sor_power_down(struct tegra_sor *sor)
|
||||
if ((value & SOR_PWR_TRIGGER) != 0)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
err = clk_set_parent(sor->clk, sor->clk_safe);
|
||||
/* switch to safe parent clock */
|
||||
err = tegra_sor_set_parent_clock(sor, sor->clk_safe);
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to set safe parent clock: %d\n", err);
|
||||
|
||||
@ -1038,6 +1298,22 @@ static void tegra_sor_debugfs_exit(struct tegra_sor *sor)
|
||||
sor->debugfs = NULL;
|
||||
}
|
||||
|
||||
static void tegra_sor_connector_reset(struct drm_connector *connector)
|
||||
{
|
||||
struct tegra_sor_state *state;
|
||||
|
||||
state = kzalloc(sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return;
|
||||
|
||||
if (connector->state) {
|
||||
__drm_atomic_helper_connector_destroy_state(connector->state);
|
||||
kfree(connector->state);
|
||||
}
|
||||
|
||||
__drm_atomic_helper_connector_reset(connector, &state->base);
|
||||
}
|
||||
|
||||
static enum drm_connector_status
|
||||
tegra_sor_connector_detect(struct drm_connector *connector, bool force)
|
||||
{
|
||||
@ -1050,13 +1326,28 @@ tegra_sor_connector_detect(struct drm_connector *connector, bool force)
|
||||
return tegra_output_connector_detect(connector, force);
|
||||
}
|
||||
|
||||
static struct drm_connector_state *
|
||||
tegra_sor_connector_duplicate_state(struct drm_connector *connector)
|
||||
{
|
||||
struct tegra_sor_state *state = to_sor_state(connector->state);
|
||||
struct tegra_sor_state *copy;
|
||||
|
||||
copy = kmemdup(state, sizeof(*state), GFP_KERNEL);
|
||||
if (!copy)
|
||||
return NULL;
|
||||
|
||||
__drm_atomic_helper_connector_duplicate_state(connector, ©->base);
|
||||
|
||||
return ©->base;
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs tegra_sor_connector_funcs = {
|
||||
.dpms = drm_atomic_helper_connector_dpms,
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.reset = tegra_sor_connector_reset,
|
||||
.detect = tegra_sor_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = tegra_output_connector_destroy,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_duplicate_state = tegra_sor_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
@ -1081,6 +1372,10 @@ static enum drm_mode_status
|
||||
tegra_sor_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
/* HDMI 2.0 modes are not yet supported */
|
||||
if (mode->clock > 340000)
|
||||
return MODE_NOCLOCK;
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
@ -1140,8 +1435,7 @@ static void tegra_sor_edp_disable(struct drm_encoder *encoder)
|
||||
if (output->panel)
|
||||
drm_panel_unprepare(output->panel);
|
||||
|
||||
reset_control_assert(sor->rst);
|
||||
clk_disable_unprepare(sor->clk);
|
||||
pm_runtime_put(sor->dev);
|
||||
}
|
||||
|
||||
#if 0
|
||||
@ -1191,19 +1485,18 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder)
|
||||
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
||||
unsigned int vbe, vse, hbe, hse, vbs, hbs, i;
|
||||
struct tegra_sor *sor = to_sor(output);
|
||||
struct tegra_sor_config config;
|
||||
struct tegra_sor_state *state;
|
||||
struct drm_dp_link link;
|
||||
u8 rate, lanes;
|
||||
unsigned int i;
|
||||
int err = 0;
|
||||
u32 value;
|
||||
|
||||
err = clk_prepare_enable(sor->clk);
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to enable clock: %d\n", err);
|
||||
state = to_sor_state(output->connector.state);
|
||||
|
||||
reset_control_deassert(sor->rst);
|
||||
pm_runtime_get_sync(sor->dev);
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_prepare(output->panel);
|
||||
@ -1218,17 +1511,17 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder)
|
||||
return;
|
||||
}
|
||||
|
||||
err = clk_set_parent(sor->clk, sor->clk_safe);
|
||||
/* switch to safe parent clock */
|
||||
err = tegra_sor_set_parent_clock(sor, sor->clk_safe);
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to set safe parent clock: %d\n", err);
|
||||
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.bits_per_pixel = output->connector.display_info.bpc * 3;
|
||||
config.bits_per_pixel = state->bpc * 3;
|
||||
|
||||
err = tegra_sor_calc_config(sor, mode, &config, &link);
|
||||
err = tegra_sor_compute_config(sor, mode, &config, &link);
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to compute link configuration: %d\n",
|
||||
err);
|
||||
dev_err(sor->dev, "failed to compute configuration: %d\n", err);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_CLK_CNTRL);
|
||||
value &= ~SOR_CLK_CNTRL_DP_CLK_SEL_MASK;
|
||||
@ -1325,10 +1618,18 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder)
|
||||
value &= ~SOR_PLL2_PORT_POWERDOWN;
|
||||
tegra_sor_writel(sor, value, SOR_PLL2);
|
||||
|
||||
/* switch to DP clock */
|
||||
err = clk_set_parent(sor->clk, sor->clk_dp);
|
||||
/* XXX not in TRM */
|
||||
for (value = 0, i = 0; i < 5; i++)
|
||||
value |= SOR_XBAR_CTRL_LINK0_XSEL(i, sor->soc->xbar_cfg[i]) |
|
||||
SOR_XBAR_CTRL_LINK1_XSEL(i, i);
|
||||
|
||||
tegra_sor_writel(sor, 0x00000000, SOR_XBAR_POL);
|
||||
tegra_sor_writel(sor, value, SOR_XBAR_CTRL);
|
||||
|
||||
/* switch to DP parent clock */
|
||||
err = tegra_sor_set_parent_clock(sor, sor->clk_dp);
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to set DP parent clock: %d\n", err);
|
||||
dev_err(sor->dev, "failed to set parent clock: %d\n", err);
|
||||
|
||||
/* power DP lanes */
|
||||
value = tegra_sor_readl(sor, SOR_DP_PADCTL0);
|
||||
@ -1374,13 +1675,11 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder)
|
||||
value |= drm_dp_link_rate_to_bw_code(link.rate) << 2;
|
||||
tegra_sor_writel(sor, value, SOR_CLK_CNTRL);
|
||||
|
||||
/* set linkctl */
|
||||
tegra_sor_apply_config(sor, &config);
|
||||
|
||||
/* enable link */
|
||||
value = tegra_sor_readl(sor, SOR_DP_LINKCTL0);
|
||||
value |= SOR_DP_LINKCTL_ENABLE;
|
||||
|
||||
value &= ~SOR_DP_LINKCTL_TU_SIZE_MASK;
|
||||
value |= SOR_DP_LINKCTL_TU_SIZE(config.tu_size);
|
||||
|
||||
value |= SOR_DP_LINKCTL_ENHANCED_FRAME;
|
||||
tegra_sor_writel(sor, value, SOR_DP_LINKCTL0);
|
||||
|
||||
@ -1393,35 +1692,6 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder)
|
||||
|
||||
tegra_sor_writel(sor, value, SOR_DP_TPG);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_DP_CONFIG0);
|
||||
value &= ~SOR_DP_CONFIG_WATERMARK_MASK;
|
||||
value |= SOR_DP_CONFIG_WATERMARK(config.watermark);
|
||||
|
||||
value &= ~SOR_DP_CONFIG_ACTIVE_SYM_COUNT_MASK;
|
||||
value |= SOR_DP_CONFIG_ACTIVE_SYM_COUNT(config.active_count);
|
||||
|
||||
value &= ~SOR_DP_CONFIG_ACTIVE_SYM_FRAC_MASK;
|
||||
value |= SOR_DP_CONFIG_ACTIVE_SYM_FRAC(config.active_frac);
|
||||
|
||||
if (config.active_polarity)
|
||||
value |= SOR_DP_CONFIG_ACTIVE_SYM_POLARITY;
|
||||
else
|
||||
value &= ~SOR_DP_CONFIG_ACTIVE_SYM_POLARITY;
|
||||
|
||||
value |= SOR_DP_CONFIG_ACTIVE_SYM_ENABLE;
|
||||
value |= SOR_DP_CONFIG_DISPARITY_NEGATIVE;
|
||||
tegra_sor_writel(sor, value, SOR_DP_CONFIG0);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_DP_AUDIO_HBLANK_SYMBOLS);
|
||||
value &= ~SOR_DP_AUDIO_HBLANK_SYMBOLS_MASK;
|
||||
value |= config.hblank_symbols & 0xffff;
|
||||
tegra_sor_writel(sor, value, SOR_DP_AUDIO_HBLANK_SYMBOLS);
|
||||
|
||||
value = tegra_sor_readl(sor, SOR_DP_AUDIO_VBLANK_SYMBOLS);
|
||||
value &= ~SOR_DP_AUDIO_VBLANK_SYMBOLS_MASK;
|
||||
value |= config.vblank_symbols & 0xffff;
|
||||
tegra_sor_writel(sor, value, SOR_DP_AUDIO_VBLANK_SYMBOLS);
|
||||
|
||||
/* enable pad calibration logic */
|
||||
value = tegra_sor_readl(sor, SOR_DP_PADCTL0);
|
||||
value |= SOR_DP_PADCTL_PAD_CAL_PD;
|
||||
@ -1477,75 +1747,19 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder)
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to power up SOR: %d\n", err);
|
||||
|
||||
/*
|
||||
* configure panel (24bpp, vsync-, hsync-, DP-A protocol, complete
|
||||
* raster, associate with display controller)
|
||||
*/
|
||||
value = SOR_STATE_ASY_PROTOCOL_DP_A |
|
||||
SOR_STATE_ASY_CRC_MODE_COMPLETE |
|
||||
SOR_STATE_ASY_OWNER(dc->pipe + 1);
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_PHSYNC)
|
||||
value &= ~SOR_STATE_ASY_HSYNCPOL;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
|
||||
value |= SOR_STATE_ASY_HSYNCPOL;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_PVSYNC)
|
||||
value &= ~SOR_STATE_ASY_VSYNCPOL;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
||||
value |= SOR_STATE_ASY_VSYNCPOL;
|
||||
|
||||
switch (config.bits_per_pixel) {
|
||||
case 24:
|
||||
value |= SOR_STATE_ASY_PIXELDEPTH_BPP_24_444;
|
||||
break;
|
||||
|
||||
case 18:
|
||||
value |= SOR_STATE_ASY_PIXELDEPTH_BPP_18_444;
|
||||
break;
|
||||
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
|
||||
tegra_sor_writel(sor, value, SOR_STATE1);
|
||||
|
||||
/*
|
||||
* TODO: The video timing programming below doesn't seem to match the
|
||||
* register definitions.
|
||||
*/
|
||||
|
||||
value = ((mode->vtotal & 0x7fff) << 16) | (mode->htotal & 0x7fff);
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE1(dc->pipe));
|
||||
|
||||
vse = mode->vsync_end - mode->vsync_start - 1;
|
||||
hse = mode->hsync_end - mode->hsync_start - 1;
|
||||
|
||||
value = ((vse & 0x7fff) << 16) | (hse & 0x7fff);
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE2(dc->pipe));
|
||||
|
||||
vbe = vse + (mode->vsync_start - mode->vdisplay);
|
||||
hbe = hse + (mode->hsync_start - mode->hdisplay);
|
||||
|
||||
value = ((vbe & 0x7fff) << 16) | (hbe & 0x7fff);
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE3(dc->pipe));
|
||||
|
||||
vbs = vbe + mode->vdisplay;
|
||||
hbs = hbe + mode->hdisplay;
|
||||
|
||||
value = ((vbs & 0x7fff) << 16) | (hbs & 0x7fff);
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE4(dc->pipe));
|
||||
|
||||
tegra_sor_writel(sor, 0x1, SOR_HEAD_STATE5(dc->pipe));
|
||||
|
||||
/* CSTM (LVDS, link A/B, upper) */
|
||||
value = SOR_CSTM_LVDS | SOR_CSTM_LINK_ACT_A | SOR_CSTM_LINK_ACT_B |
|
||||
SOR_CSTM_UPPER;
|
||||
tegra_sor_writel(sor, value, SOR_CSTM);
|
||||
|
||||
/* use DP-A protocol */
|
||||
value = tegra_sor_readl(sor, SOR_STATE1);
|
||||
value &= ~SOR_STATE_ASY_PROTOCOL_MASK;
|
||||
value |= SOR_STATE_ASY_PROTOCOL_DP_A;
|
||||
tegra_sor_writel(sor, value, SOR_STATE1);
|
||||
|
||||
tegra_sor_mode_set(sor, mode, state);
|
||||
|
||||
/* PWM setup */
|
||||
err = tegra_sor_setup_pwm(sor, 250);
|
||||
if (err < 0)
|
||||
@ -1577,11 +1791,15 @@ tegra_sor_encoder_atomic_check(struct drm_encoder *encoder,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_sor_state *state = to_sor_state(conn_state);
|
||||
struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
|
||||
unsigned long pclk = crtc_state->mode.clock * 1000;
|
||||
struct tegra_sor *sor = to_sor(output);
|
||||
struct drm_display_info *info;
|
||||
int err;
|
||||
|
||||
info = &output->connector.display_info;
|
||||
|
||||
err = tegra_dc_state_setup_clock(dc, crtc_state, sor->clk_parent,
|
||||
pclk, 0);
|
||||
if (err < 0) {
|
||||
@ -1589,6 +1807,18 @@ tegra_sor_encoder_atomic_check(struct drm_encoder *encoder,
|
||||
return err;
|
||||
}
|
||||
|
||||
switch (info->bpc) {
|
||||
case 8:
|
||||
case 6:
|
||||
state->bpc = info->bpc;
|
||||
break;
|
||||
|
||||
default:
|
||||
DRM_DEBUG_KMS("%u bits-per-color not supported\n", info->bpc);
|
||||
state->bpc = 8;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1751,9 +1981,7 @@ static void tegra_sor_hdmi_disable(struct drm_encoder *encoder)
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to power off HDMI rail: %d\n", err);
|
||||
|
||||
reset_control_assert(sor->rst);
|
||||
usleep_range(1000, 2000);
|
||||
clk_disable_unprepare(sor->clk);
|
||||
pm_runtime_put(sor->dev);
|
||||
}
|
||||
|
||||
static void tegra_sor_hdmi_enable(struct drm_encoder *encoder)
|
||||
@ -1761,26 +1989,21 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder)
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
unsigned int h_ref_to_sync = 1, pulse_start, max_ac;
|
||||
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
||||
unsigned int vbe, vse, hbe, hse, vbs, hbs, div;
|
||||
struct tegra_sor_hdmi_settings *settings;
|
||||
struct tegra_sor *sor = to_sor(output);
|
||||
struct tegra_sor_state *state;
|
||||
struct drm_display_mode *mode;
|
||||
struct drm_display_info *info;
|
||||
unsigned int div, i;
|
||||
u32 value;
|
||||
int err;
|
||||
|
||||
state = to_sor_state(output->connector.state);
|
||||
mode = &encoder->crtc->state->adjusted_mode;
|
||||
info = &output->connector.display_info;
|
||||
|
||||
err = clk_prepare_enable(sor->clk);
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to enable clock: %d\n", err);
|
||||
pm_runtime_get_sync(sor->dev);
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
reset_control_deassert(sor->rst);
|
||||
|
||||
err = clk_set_parent(sor->clk, sor->clk_safe);
|
||||
/* switch to safe parent clock */
|
||||
err = tegra_sor_set_parent_clock(sor, sor->clk_safe);
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to set safe parent clock: %d\n", err);
|
||||
|
||||
@ -1876,22 +2099,20 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder)
|
||||
value = SOR_REFCLK_DIV_INT(div) | SOR_REFCLK_DIV_FRAC(div);
|
||||
tegra_sor_writel(sor, value, SOR_REFCLK);
|
||||
|
||||
/* XXX don't hardcode */
|
||||
value = SOR_XBAR_CTRL_LINK1_XSEL(4, 4) |
|
||||
SOR_XBAR_CTRL_LINK1_XSEL(3, 3) |
|
||||
SOR_XBAR_CTRL_LINK1_XSEL(2, 2) |
|
||||
SOR_XBAR_CTRL_LINK1_XSEL(1, 1) |
|
||||
SOR_XBAR_CTRL_LINK1_XSEL(0, 0) |
|
||||
SOR_XBAR_CTRL_LINK0_XSEL(4, 4) |
|
||||
SOR_XBAR_CTRL_LINK0_XSEL(3, 3) |
|
||||
SOR_XBAR_CTRL_LINK0_XSEL(2, 0) |
|
||||
SOR_XBAR_CTRL_LINK0_XSEL(1, 1) |
|
||||
SOR_XBAR_CTRL_LINK0_XSEL(0, 2);
|
||||
tegra_sor_writel(sor, value, SOR_XBAR_CTRL);
|
||||
/* XXX not in TRM */
|
||||
for (value = 0, i = 0; i < 5; i++)
|
||||
value |= SOR_XBAR_CTRL_LINK0_XSEL(i, sor->soc->xbar_cfg[i]) |
|
||||
SOR_XBAR_CTRL_LINK1_XSEL(i, i);
|
||||
|
||||
tegra_sor_writel(sor, 0x00000000, SOR_XBAR_POL);
|
||||
tegra_sor_writel(sor, value, SOR_XBAR_CTRL);
|
||||
|
||||
err = clk_set_parent(sor->clk, sor->clk_parent);
|
||||
/* switch to parent clock */
|
||||
err = clk_set_parent(sor->clk_src, sor->clk_parent);
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to set source clock: %d\n", err);
|
||||
|
||||
err = tegra_sor_set_parent_clock(sor, sor->clk_src);
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to set parent clock: %d\n", err);
|
||||
|
||||
@ -2001,7 +2222,7 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder)
|
||||
value &= ~DITHER_CONTROL_MASK;
|
||||
value &= ~BASE_COLOR_SIZE_MASK;
|
||||
|
||||
switch (info->bpc) {
|
||||
switch (state->bpc) {
|
||||
case 6:
|
||||
value |= BASE_COLOR_SIZE_666;
|
||||
break;
|
||||
@ -2011,7 +2232,8 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder)
|
||||
break;
|
||||
|
||||
default:
|
||||
WARN(1, "%u bits-per-color not supported\n", info->bpc);
|
||||
WARN(1, "%u bits-per-color not supported\n", state->bpc);
|
||||
value |= BASE_COLOR_SIZE_888;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2021,83 +2243,19 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder)
|
||||
if (err < 0)
|
||||
dev_err(sor->dev, "failed to power up SOR: %d\n", err);
|
||||
|
||||
/* configure mode */
|
||||
value = tegra_sor_readl(sor, SOR_STATE1);
|
||||
value &= ~SOR_STATE_ASY_PIXELDEPTH_MASK;
|
||||
value &= ~SOR_STATE_ASY_CRC_MODE_MASK;
|
||||
value &= ~SOR_STATE_ASY_OWNER_MASK;
|
||||
|
||||
value |= SOR_STATE_ASY_CRC_MODE_COMPLETE |
|
||||
SOR_STATE_ASY_OWNER(dc->pipe + 1);
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_PHSYNC)
|
||||
value &= ~SOR_STATE_ASY_HSYNCPOL;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
|
||||
value |= SOR_STATE_ASY_HSYNCPOL;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_PVSYNC)
|
||||
value &= ~SOR_STATE_ASY_VSYNCPOL;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
||||
value |= SOR_STATE_ASY_VSYNCPOL;
|
||||
|
||||
switch (info->bpc) {
|
||||
case 8:
|
||||
value |= SOR_STATE_ASY_PIXELDEPTH_BPP_24_444;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
value |= SOR_STATE_ASY_PIXELDEPTH_BPP_18_444;
|
||||
break;
|
||||
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
|
||||
tegra_sor_writel(sor, value, SOR_STATE1);
|
||||
|
||||
/* configure dynamic range of output */
|
||||
value = tegra_sor_readl(sor, SOR_HEAD_STATE0(dc->pipe));
|
||||
value &= ~SOR_HEAD_STATE_RANGECOMPRESS_MASK;
|
||||
value &= ~SOR_HEAD_STATE_DYNRANGE_MASK;
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE0(dc->pipe));
|
||||
|
||||
/* configure colorspace */
|
||||
value = tegra_sor_readl(sor, SOR_HEAD_STATE0(dc->pipe));
|
||||
value &= ~SOR_HEAD_STATE_COLORSPACE_MASK;
|
||||
value |= SOR_HEAD_STATE_COLORSPACE_RGB;
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE0(dc->pipe));
|
||||
|
||||
/*
|
||||
* TODO: The video timing programming below doesn't seem to match the
|
||||
* register definitions.
|
||||
*/
|
||||
|
||||
value = ((mode->vtotal & 0x7fff) << 16) | (mode->htotal & 0x7fff);
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE1(dc->pipe));
|
||||
|
||||
/* sync end = sync width - 1 */
|
||||
vse = mode->vsync_end - mode->vsync_start - 1;
|
||||
hse = mode->hsync_end - mode->hsync_start - 1;
|
||||
|
||||
value = ((vse & 0x7fff) << 16) | (hse & 0x7fff);
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE2(dc->pipe));
|
||||
|
||||
/* blank end = sync end + back porch */
|
||||
vbe = vse + (mode->vtotal - mode->vsync_end);
|
||||
hbe = hse + (mode->htotal - mode->hsync_end);
|
||||
|
||||
value = ((vbe & 0x7fff) << 16) | (hbe & 0x7fff);
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE3(dc->pipe));
|
||||
|
||||
/* blank start = blank end + active */
|
||||
vbs = vbe + mode->vdisplay;
|
||||
hbs = hbe + mode->hdisplay;
|
||||
|
||||
value = ((vbs & 0x7fff) << 16) | (hbs & 0x7fff);
|
||||
tegra_sor_writel(sor, value, SOR_HEAD_STATE4(dc->pipe));
|
||||
|
||||
tegra_sor_writel(sor, 0x1, SOR_HEAD_STATE5(dc->pipe));
|
||||
tegra_sor_mode_set(sor, mode, state);
|
||||
|
||||
tegra_sor_update(sor);
|
||||
|
||||
@ -2195,11 +2353,14 @@ static int tegra_sor_init(struct host1x_client *client)
|
||||
* XXX: Remove this reset once proper hand-over from firmware to
|
||||
* kernel is possible.
|
||||
*/
|
||||
if (sor->rst) {
|
||||
err = reset_control_assert(sor->rst);
|
||||
if (err < 0) {
|
||||
dev_err(sor->dev, "failed to assert SOR reset: %d\n", err);
|
||||
dev_err(sor->dev, "failed to assert SOR reset: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(sor->clk);
|
||||
if (err < 0) {
|
||||
@ -2209,11 +2370,14 @@ static int tegra_sor_init(struct host1x_client *client)
|
||||
|
||||
usleep_range(1000, 3000);
|
||||
|
||||
if (sor->rst) {
|
||||
err = reset_control_deassert(sor->rst);
|
||||
if (err < 0) {
|
||||
dev_err(sor->dev, "failed to deassert SOR reset: %d\n", err);
|
||||
dev_err(sor->dev, "failed to deassert SOR reset: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(sor->clk_safe);
|
||||
if (err < 0)
|
||||
@ -2323,11 +2487,16 @@ static const struct tegra_sor_ops tegra_sor_hdmi_ops = {
|
||||
.remove = tegra_sor_hdmi_remove,
|
||||
};
|
||||
|
||||
static const u8 tegra124_sor_xbar_cfg[5] = {
|
||||
0, 1, 2, 3, 4
|
||||
};
|
||||
|
||||
static const struct tegra_sor_soc tegra124_sor = {
|
||||
.supports_edp = true,
|
||||
.supports_lvds = true,
|
||||
.supports_hdmi = false,
|
||||
.supports_dp = false,
|
||||
.xbar_cfg = tegra124_sor_xbar_cfg,
|
||||
};
|
||||
|
||||
static const struct tegra_sor_soc tegra210_sor = {
|
||||
@ -2335,6 +2504,11 @@ static const struct tegra_sor_soc tegra210_sor = {
|
||||
.supports_lvds = false,
|
||||
.supports_hdmi = false,
|
||||
.supports_dp = false,
|
||||
.xbar_cfg = tegra124_sor_xbar_cfg,
|
||||
};
|
||||
|
||||
static const u8 tegra210_sor_xbar_cfg[5] = {
|
||||
2, 1, 0, 3, 4
|
||||
};
|
||||
|
||||
static const struct tegra_sor_soc tegra210_sor1 = {
|
||||
@ -2345,6 +2519,8 @@ static const struct tegra_sor_soc tegra210_sor1 = {
|
||||
|
||||
.num_settings = ARRAY_SIZE(tegra210_sor_hdmi_defaults),
|
||||
.settings = tegra210_sor_hdmi_defaults,
|
||||
|
||||
.xbar_cfg = tegra210_sor_xbar_cfg,
|
||||
};
|
||||
|
||||
static const struct of_device_id tegra_sor_of_match[] = {
|
||||
@ -2434,12 +2610,15 @@ static int tegra_sor_probe(struct platform_device *pdev)
|
||||
goto remove;
|
||||
}
|
||||
|
||||
if (!pdev->dev.pm_domain) {
|
||||
sor->rst = devm_reset_control_get(&pdev->dev, "sor");
|
||||
if (IS_ERR(sor->rst)) {
|
||||
err = PTR_ERR(sor->rst);
|
||||
dev_err(&pdev->dev, "failed to get reset control: %d\n", err);
|
||||
dev_err(&pdev->dev, "failed to get reset control: %d\n",
|
||||
err);
|
||||
goto remove;
|
||||
}
|
||||
}
|
||||
|
||||
sor->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(sor->clk)) {
|
||||
@ -2448,6 +2627,16 @@ static int tegra_sor_probe(struct platform_device *pdev)
|
||||
goto remove;
|
||||
}
|
||||
|
||||
if (sor->soc->supports_hdmi || sor->soc->supports_dp) {
|
||||
sor->clk_src = devm_clk_get(&pdev->dev, "source");
|
||||
if (IS_ERR(sor->clk_src)) {
|
||||
err = PTR_ERR(sor->clk_src);
|
||||
dev_err(sor->dev, "failed to get source clock: %d\n",
|
||||
err);
|
||||
goto remove;
|
||||
}
|
||||
}
|
||||
|
||||
sor->clk_parent = devm_clk_get(&pdev->dev, "parent");
|
||||
if (IS_ERR(sor->clk_parent)) {
|
||||
err = PTR_ERR(sor->clk_parent);
|
||||
@ -2469,6 +2658,19 @@ static int tegra_sor_probe(struct platform_device *pdev)
|
||||
goto remove;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, sor);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
sor->clk_brick = tegra_clk_sor_brick_register(sor, "sor1_brick");
|
||||
pm_runtime_put(&pdev->dev);
|
||||
|
||||
if (IS_ERR(sor->clk_brick)) {
|
||||
err = PTR_ERR(sor->clk_brick);
|
||||
dev_err(&pdev->dev, "failed to register SOR clock: %d\n", err);
|
||||
goto remove;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&sor->client.list);
|
||||
sor->client.ops = &sor_client_ops;
|
||||
sor->client.dev = &pdev->dev;
|
||||
@ -2480,8 +2682,6 @@ static int tegra_sor_probe(struct platform_device *pdev)
|
||||
goto remove;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, sor);
|
||||
|
||||
return 0;
|
||||
|
||||
remove:
|
||||
@ -2497,6 +2697,8 @@ static int tegra_sor_remove(struct platform_device *pdev)
|
||||
struct tegra_sor *sor = platform_get_drvdata(pdev);
|
||||
int err;
|
||||
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
err = host1x_client_unregister(&sor->client);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to unregister host1x client: %d\n",
|
||||
@ -2515,10 +2717,62 @@ static int tegra_sor_remove(struct platform_device *pdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int tegra_sor_suspend(struct device *dev)
|
||||
{
|
||||
struct tegra_sor *sor = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
if (sor->rst) {
|
||||
err = reset_control_assert(sor->rst);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to assert reset: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
clk_disable_unprepare(sor->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_sor_resume(struct device *dev)
|
||||
{
|
||||
struct tegra_sor *sor = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
err = clk_prepare_enable(sor->clk);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to enable clock: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
if (sor->rst) {
|
||||
err = reset_control_deassert(sor->rst);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to deassert reset: %d\n", err);
|
||||
clk_disable_unprepare(sor->clk);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops tegra_sor_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(tegra_sor_suspend, tegra_sor_resume, NULL)
|
||||
};
|
||||
|
||||
struct platform_driver tegra_sor_driver = {
|
||||
.driver = {
|
||||
.name = "tegra-sor",
|
||||
.of_match_table = tegra_sor_of_match,
|
||||
.pm = &tegra_sor_pm_ops,
|
||||
},
|
||||
.probe = tegra_sor_probe,
|
||||
.remove = tegra_sor_remove,
|
||||
|
@ -27,6 +27,9 @@
|
||||
#define SOR_STATE_ASY_PIXELDEPTH_MASK (0xf << 17)
|
||||
#define SOR_STATE_ASY_PIXELDEPTH_BPP_18_444 (0x2 << 17)
|
||||
#define SOR_STATE_ASY_PIXELDEPTH_BPP_24_444 (0x5 << 17)
|
||||
#define SOR_STATE_ASY_PIXELDEPTH_BPP_30_444 (0x6 << 17)
|
||||
#define SOR_STATE_ASY_PIXELDEPTH_BPP_36_444 (0x8 << 17)
|
||||
#define SOR_STATE_ASY_PIXELDEPTH_BPP_48_444 (0x9 << 17)
|
||||
#define SOR_STATE_ASY_VSYNCPOL (1 << 13)
|
||||
#define SOR_STATE_ASY_HSYNCPOL (1 << 12)
|
||||
#define SOR_STATE_ASY_PROTOCOL_MASK (0xf << 8)
|
||||
|
@ -96,12 +96,12 @@ fail:
|
||||
*/
|
||||
static void host1x_pushbuffer_push(struct push_buffer *pb, u32 op1, u32 op2)
|
||||
{
|
||||
u32 pos = pb->pos;
|
||||
u32 *p = (u32 *)((void *)pb->mapped + pos);
|
||||
WARN_ON(pos == pb->fence);
|
||||
u32 *p = (u32 *)((void *)pb->mapped + pb->pos);
|
||||
|
||||
WARN_ON(pb->pos == pb->fence);
|
||||
*(p++) = op1;
|
||||
*(p++) = op2;
|
||||
pb->pos = (pos + 8) & (pb->size_bytes - 1);
|
||||
pb->pos = (pb->pos + 8) & (pb->size_bytes - 1);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -134,14 +134,19 @@ unsigned int host1x_cdma_wait_locked(struct host1x_cdma *cdma,
|
||||
enum cdma_event event)
|
||||
{
|
||||
for (;;) {
|
||||
struct push_buffer *pb = &cdma->push_buffer;
|
||||
unsigned int space;
|
||||
|
||||
if (event == CDMA_EVENT_SYNC_QUEUE_EMPTY)
|
||||
switch (event) {
|
||||
case CDMA_EVENT_SYNC_QUEUE_EMPTY:
|
||||
space = list_empty(&cdma->sync_queue) ? 1 : 0;
|
||||
else if (event == CDMA_EVENT_PUSH_BUFFER_SPACE) {
|
||||
struct push_buffer *pb = &cdma->push_buffer;
|
||||
break;
|
||||
|
||||
case CDMA_EVENT_PUSH_BUFFER_SPACE:
|
||||
space = host1x_pushbuffer_space(pb);
|
||||
} else {
|
||||
break;
|
||||
|
||||
default:
|
||||
WARN_ON(1);
|
||||
return -EINVAL;
|
||||
}
|
||||
@ -159,12 +164,14 @@ unsigned int host1x_cdma_wait_locked(struct host1x_cdma *cdma,
|
||||
mutex_lock(&cdma->lock);
|
||||
continue;
|
||||
}
|
||||
|
||||
cdma->event = event;
|
||||
|
||||
mutex_unlock(&cdma->lock);
|
||||
down(&cdma->sem);
|
||||
mutex_lock(&cdma->lock);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -234,6 +241,7 @@ static void update_cdma_locked(struct host1x_cdma *cdma)
|
||||
/* Start timer on next pending syncpt */
|
||||
if (job->timeout)
|
||||
cdma_start_timer_locked(cdma, job);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@ -247,7 +255,9 @@ static void update_cdma_locked(struct host1x_cdma *cdma)
|
||||
/* Pop push buffer slots */
|
||||
if (job->num_slots) {
|
||||
struct push_buffer *pb = &cdma->push_buffer;
|
||||
|
||||
host1x_pushbuffer_pop(pb, job->num_slots);
|
||||
|
||||
if (cdma->event == CDMA_EVENT_PUSH_BUFFER_SPACE)
|
||||
signal = true;
|
||||
}
|
||||
@ -269,11 +279,9 @@ static void update_cdma_locked(struct host1x_cdma *cdma)
|
||||
void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma,
|
||||
struct device *dev)
|
||||
{
|
||||
u32 restart_addr;
|
||||
u32 syncpt_incrs;
|
||||
struct host1x_job *job = NULL;
|
||||
u32 syncpt_val;
|
||||
struct host1x *host1x = cdma_to_host1x(cdma);
|
||||
u32 restart_addr, syncpt_incrs, syncpt_val;
|
||||
struct host1x_job *job = NULL;
|
||||
|
||||
syncpt_val = host1x_syncpt_load(cdma->timeout.syncpt);
|
||||
|
||||
@ -342,9 +350,11 @@ void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma,
|
||||
syncpt_val += syncpt_incrs;
|
||||
}
|
||||
|
||||
/* The following sumbits from the same client may be dependent on the
|
||||
/*
|
||||
* The following sumbits from the same client may be dependent on the
|
||||
* failed submit and therefore they may fail. Force a small timeout
|
||||
* to make the queue cleanup faster */
|
||||
* to make the queue cleanup faster.
|
||||
*/
|
||||
|
||||
list_for_each_entry_from(job, &cdma->sync_queue, list)
|
||||
if (job->client == cdma->timeout.client)
|
||||
@ -375,6 +385,7 @@ int host1x_cdma_init(struct host1x_cdma *cdma)
|
||||
err = host1x_pushbuffer_init(&cdma->push_buffer);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -410,6 +421,7 @@ int host1x_cdma_begin(struct host1x_cdma *cdma, struct host1x_job *job)
|
||||
/* init state on first submit with timeout value */
|
||||
if (!cdma->timeout.initialized) {
|
||||
int err;
|
||||
|
||||
err = host1x_hw_cdma_timeout_init(host1x, cdma,
|
||||
job->syncpt_id);
|
||||
if (err) {
|
||||
@ -418,6 +430,7 @@ int host1x_cdma_begin(struct host1x_cdma *cdma, struct host1x_job *job)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!cdma->running)
|
||||
host1x_hw_cdma_start(host1x, cdma);
|
||||
|
||||
@ -448,6 +461,7 @@ void host1x_cdma_push(struct host1x_cdma *cdma, u32 op1, u32 op2)
|
||||
slots_free = host1x_cdma_wait_locked(cdma,
|
||||
CDMA_EVENT_PUSH_BUFFER_SPACE);
|
||||
}
|
||||
|
||||
cdma->slots_free = slots_free - 1;
|
||||
cdma->slots_used++;
|
||||
host1x_pushbuffer_push(pb, op1, op2);
|
||||
|
@ -83,9 +83,10 @@ EXPORT_SYMBOL(host1x_channel_put);
|
||||
struct host1x_channel *host1x_channel_request(struct device *dev)
|
||||
{
|
||||
struct host1x *host = dev_get_drvdata(dev->parent);
|
||||
int max_channels = host->info->nb_channels;
|
||||
unsigned int max_channels = host->info->nb_channels;
|
||||
struct host1x_channel *channel = NULL;
|
||||
int index, err;
|
||||
unsigned long index;
|
||||
int err;
|
||||
|
||||
mutex_lock(&host->chlist_mutex);
|
||||
|
||||
|
@ -39,6 +39,7 @@ void host1x_debug_output(struct output *o, const char *fmt, ...)
|
||||
va_start(args, fmt);
|
||||
len = vsnprintf(o->buf, sizeof(o->buf), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
o->fn(o->ctx, o->buf, len);
|
||||
}
|
||||
|
||||
@ -48,13 +49,17 @@ static int show_channels(struct host1x_channel *ch, void *data, bool show_fifo)
|
||||
struct output *o = data;
|
||||
|
||||
mutex_lock(&ch->reflock);
|
||||
|
||||
if (ch->refcount) {
|
||||
mutex_lock(&ch->cdma.lock);
|
||||
|
||||
if (show_fifo)
|
||||
host1x_hw_show_channel_fifo(m, ch, o);
|
||||
|
||||
host1x_hw_show_channel_cdma(m, ch, o);
|
||||
mutex_unlock(&ch->cdma.lock);
|
||||
}
|
||||
|
||||
mutex_unlock(&ch->reflock);
|
||||
|
||||
return 0;
|
||||
@ -62,22 +67,27 @@ static int show_channels(struct host1x_channel *ch, void *data, bool show_fifo)
|
||||
|
||||
static void show_syncpts(struct host1x *m, struct output *o)
|
||||
{
|
||||
int i;
|
||||
unsigned int i;
|
||||
|
||||
host1x_debug_output(o, "---- syncpts ----\n");
|
||||
|
||||
for (i = 0; i < host1x_syncpt_nb_pts(m); i++) {
|
||||
u32 max = host1x_syncpt_read_max(m->syncpt + i);
|
||||
u32 min = host1x_syncpt_load(m->syncpt + i);
|
||||
|
||||
if (!min && !max)
|
||||
continue;
|
||||
host1x_debug_output(o, "id %d (%s) min %d max %d\n",
|
||||
|
||||
host1x_debug_output(o, "id %u (%s) min %d max %d\n",
|
||||
i, m->syncpt[i].name, min, max);
|
||||
}
|
||||
|
||||
for (i = 0; i < host1x_syncpt_nb_bases(m); i++) {
|
||||
u32 base_val;
|
||||
|
||||
base_val = host1x_syncpt_load_wait_base(m->syncpt + i);
|
||||
if (base_val)
|
||||
host1x_debug_output(o, "waitbase id %d val %d\n", i,
|
||||
host1x_debug_output(o, "waitbase id %u val %d\n", i,
|
||||
base_val);
|
||||
}
|
||||
|
||||
@ -114,7 +124,9 @@ static int host1x_debug_show_all(struct seq_file *s, void *unused)
|
||||
.fn = write_to_seqfile,
|
||||
.ctx = s
|
||||
};
|
||||
|
||||
show_all(s->private, &o);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -124,7 +136,9 @@ static int host1x_debug_show(struct seq_file *s, void *unused)
|
||||
.fn = write_to_seqfile,
|
||||
.ctx = s
|
||||
};
|
||||
|
||||
show_all_no_fifo(s->private, &o);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -201,6 +215,7 @@ void host1x_debug_dump(struct host1x *host1x)
|
||||
struct output o = {
|
||||
.fn = write_to_printk
|
||||
};
|
||||
|
||||
show_all(host1x, &o);
|
||||
}
|
||||
|
||||
@ -209,5 +224,6 @@ void host1x_debug_dump_syncpts(struct host1x *host1x)
|
||||
struct output o = {
|
||||
.fn = write_to_printk
|
||||
};
|
||||
|
||||
show_syncpts(host1x, &o);
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ static const struct host1x_info host1x05_info = {
|
||||
.dma_mask = DMA_BIT_MASK(34),
|
||||
};
|
||||
|
||||
static struct of_device_id host1x_of_match[] = {
|
||||
static const struct of_device_id host1x_of_match[] = {
|
||||
{ .compatible = "nvidia,tegra210-host1x", .data = &host1x05_info, },
|
||||
{ .compatible = "nvidia,tegra124-host1x", .data = &host1x04_info, },
|
||||
{ .compatible = "nvidia,tegra114-host1x", .data = &host1x02_info, },
|
||||
|
@ -45,7 +45,7 @@ struct host1x_cdma_ops {
|
||||
void (*start)(struct host1x_cdma *cdma);
|
||||
void (*stop)(struct host1x_cdma *cdma);
|
||||
void (*flush)(struct host1x_cdma *cdma);
|
||||
int (*timeout_init)(struct host1x_cdma *cdma, u32 syncpt_id);
|
||||
int (*timeout_init)(struct host1x_cdma *cdma, unsigned int syncpt);
|
||||
void (*timeout_destroy)(struct host1x_cdma *cdma);
|
||||
void (*freeze)(struct host1x_cdma *cdma);
|
||||
void (*resume)(struct host1x_cdma *cdma, u32 getptr);
|
||||
@ -82,20 +82,20 @@ struct host1x_intr_ops {
|
||||
int (*init_host_sync)(struct host1x *host, u32 cpm,
|
||||
void (*syncpt_thresh_work)(struct work_struct *work));
|
||||
void (*set_syncpt_threshold)(
|
||||
struct host1x *host, u32 id, u32 thresh);
|
||||
void (*enable_syncpt_intr)(struct host1x *host, u32 id);
|
||||
void (*disable_syncpt_intr)(struct host1x *host, u32 id);
|
||||
struct host1x *host, unsigned int id, u32 thresh);
|
||||
void (*enable_syncpt_intr)(struct host1x *host, unsigned int id);
|
||||
void (*disable_syncpt_intr)(struct host1x *host, unsigned int id);
|
||||
void (*disable_all_syncpt_intrs)(struct host1x *host);
|
||||
int (*free_syncpt_irq)(struct host1x *host);
|
||||
};
|
||||
|
||||
struct host1x_info {
|
||||
int nb_channels; /* host1x: num channels supported */
|
||||
int nb_pts; /* host1x: num syncpoints supported */
|
||||
int nb_bases; /* host1x: num syncpoints supported */
|
||||
int nb_mlocks; /* host1x: number of mlocks */
|
||||
int (*init)(struct host1x *); /* initialize per SoC ops */
|
||||
int sync_offset;
|
||||
unsigned int nb_channels; /* host1x: number of channels supported */
|
||||
unsigned int nb_pts; /* host1x: number of syncpoints supported */
|
||||
unsigned int nb_bases; /* host1x: number of syncpoint bases supported */
|
||||
unsigned int nb_mlocks; /* host1x: number of mlocks supported */
|
||||
int (*init)(struct host1x *host1x); /* initialize per SoC ops */
|
||||
unsigned int sync_offset; /* offset of syncpoint registers */
|
||||
u64 dma_mask; /* mask of addressable memory */
|
||||
};
|
||||
|
||||
@ -109,7 +109,6 @@ struct host1x {
|
||||
struct clk *clk;
|
||||
|
||||
struct mutex intr_mutex;
|
||||
struct workqueue_struct *intr_wq;
|
||||
int intr_syncpt_irq;
|
||||
|
||||
const struct host1x_syncpt_ops *syncpt_op;
|
||||
@ -183,19 +182,20 @@ static inline int host1x_hw_intr_init_host_sync(struct host1x *host, u32 cpm,
|
||||
}
|
||||
|
||||
static inline void host1x_hw_intr_set_syncpt_threshold(struct host1x *host,
|
||||
u32 id, u32 thresh)
|
||||
unsigned int id,
|
||||
u32 thresh)
|
||||
{
|
||||
host->intr_op->set_syncpt_threshold(host, id, thresh);
|
||||
}
|
||||
|
||||
static inline void host1x_hw_intr_enable_syncpt_intr(struct host1x *host,
|
||||
u32 id)
|
||||
unsigned int id)
|
||||
{
|
||||
host->intr_op->enable_syncpt_intr(host, id);
|
||||
}
|
||||
|
||||
static inline void host1x_hw_intr_disable_syncpt_intr(struct host1x *host,
|
||||
u32 id)
|
||||
unsigned int id)
|
||||
{
|
||||
host->intr_op->disable_syncpt_intr(host, id);
|
||||
}
|
||||
@ -212,9 +212,9 @@ static inline int host1x_hw_intr_free_syncpt_irq(struct host1x *host)
|
||||
|
||||
static inline int host1x_hw_channel_init(struct host1x *host,
|
||||
struct host1x_channel *channel,
|
||||
int chid)
|
||||
unsigned int id)
|
||||
{
|
||||
return host->channel_op->init(channel, host, chid);
|
||||
return host->channel_op->init(channel, host, id);
|
||||
}
|
||||
|
||||
static inline int host1x_hw_channel_submit(struct host1x *host,
|
||||
@ -243,9 +243,9 @@ static inline void host1x_hw_cdma_flush(struct host1x *host,
|
||||
|
||||
static inline int host1x_hw_cdma_timeout_init(struct host1x *host,
|
||||
struct host1x_cdma *cdma,
|
||||
u32 syncpt_id)
|
||||
unsigned int syncpt)
|
||||
{
|
||||
return host->cdma_op->timeout_init(cdma, syncpt_id);
|
||||
return host->cdma_op->timeout_init(cdma, syncpt);
|
||||
}
|
||||
|
||||
static inline void host1x_hw_cdma_timeout_destroy(struct host1x *host,
|
||||
|
@ -41,7 +41,7 @@ static void cdma_timeout_cpu_incr(struct host1x_cdma *cdma, u32 getptr,
|
||||
{
|
||||
struct host1x *host1x = cdma_to_host1x(cdma);
|
||||
struct push_buffer *pb = &cdma->push_buffer;
|
||||
u32 i;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < syncpt_incrs; i++)
|
||||
host1x_syncpt_incr(cdma->timeout.syncpt);
|
||||
@ -58,6 +58,7 @@ static void cdma_timeout_cpu_incr(struct host1x_cdma *cdma, u32 getptr,
|
||||
&pb->phys, getptr);
|
||||
getptr = (getptr + 8) & (pb->size_bytes - 1);
|
||||
}
|
||||
|
||||
wmb();
|
||||
}
|
||||
|
||||
@ -162,12 +163,14 @@ static void cdma_stop(struct host1x_cdma *cdma)
|
||||
struct host1x_channel *ch = cdma_to_channel(cdma);
|
||||
|
||||
mutex_lock(&cdma->lock);
|
||||
|
||||
if (cdma->running) {
|
||||
host1x_cdma_wait_locked(cdma, CDMA_EVENT_SYNC_QUEUE_EMPTY);
|
||||
host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP,
|
||||
HOST1X_CHANNEL_DMACTRL);
|
||||
cdma->running = false;
|
||||
}
|
||||
|
||||
mutex_unlock(&cdma->lock);
|
||||
}
|
||||
|
||||
@ -213,11 +216,11 @@ static void cdma_resume(struct host1x_cdma *cdma, u32 getptr)
|
||||
u32 cmdproc_stop;
|
||||
|
||||
dev_dbg(host1x->dev,
|
||||
"resuming channel (id %d, DMAGET restart = 0x%x)\n",
|
||||
"resuming channel (id %u, DMAGET restart = 0x%x)\n",
|
||||
ch->id, getptr);
|
||||
|
||||
cmdproc_stop = host1x_sync_readl(host1x, HOST1X_SYNC_CMDPROC_STOP);
|
||||
cmdproc_stop &= ~(BIT(ch->id));
|
||||
cmdproc_stop &= ~BIT(ch->id);
|
||||
host1x_sync_writel(host1x, cmdproc_stop, HOST1X_SYNC_CMDPROC_STOP);
|
||||
|
||||
cdma->torndown = false;
|
||||
@ -231,14 +234,11 @@ static void cdma_resume(struct host1x_cdma *cdma, u32 getptr)
|
||||
*/
|
||||
static void cdma_timeout_handler(struct work_struct *work)
|
||||
{
|
||||
u32 prev_cmdproc, cmdproc_stop, syncpt_val;
|
||||
struct host1x_cdma *cdma;
|
||||
struct host1x *host1x;
|
||||
struct host1x_channel *ch;
|
||||
|
||||
u32 syncpt_val;
|
||||
|
||||
u32 prev_cmdproc, cmdproc_stop;
|
||||
|
||||
cdma = container_of(to_delayed_work(work), struct host1x_cdma,
|
||||
timeout.wq);
|
||||
host1x = cdma_to_host1x(cdma);
|
||||
@ -277,7 +277,7 @@ static void cdma_timeout_handler(struct work_struct *work)
|
||||
return;
|
||||
}
|
||||
|
||||
dev_warn(host1x->dev, "%s: timeout: %d (%s), HW thresh %d, done %d\n",
|
||||
dev_warn(host1x->dev, "%s: timeout: %u (%s), HW thresh %d, done %d\n",
|
||||
__func__, cdma->timeout.syncpt->id, cdma->timeout.syncpt->name,
|
||||
syncpt_val, cdma->timeout.syncpt_val);
|
||||
|
||||
@ -291,7 +291,7 @@ static void cdma_timeout_handler(struct work_struct *work)
|
||||
/*
|
||||
* Init timeout resources
|
||||
*/
|
||||
static int cdma_timeout_init(struct host1x_cdma *cdma, u32 syncpt_id)
|
||||
static int cdma_timeout_init(struct host1x_cdma *cdma, unsigned int syncpt)
|
||||
{
|
||||
INIT_DELAYED_WORK(&cdma->timeout.wq, cdma_timeout_handler);
|
||||
cdma->timeout.initialized = true;
|
||||
@ -306,6 +306,7 @@ static void cdma_timeout_destroy(struct host1x_cdma *cdma)
|
||||
{
|
||||
if (cdma->timeout.initialized)
|
||||
cancel_delayed_work(&cdma->timeout.wq);
|
||||
|
||||
cdma->timeout.initialized = false;
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@ static void trace_write_gather(struct host1x_cdma *cdma, struct host1x_bo *bo,
|
||||
*/
|
||||
for (i = 0; i < words; i += TRACE_MAX_LENGTH) {
|
||||
u32 num_words = min(words - i, TRACE_MAX_LENGTH);
|
||||
|
||||
offset += i * sizeof(u32);
|
||||
|
||||
trace_host1x_cdma_push_gather(dev_name(dev), bo,
|
||||
@ -66,6 +67,7 @@ static void submit_gathers(struct host1x_job *job)
|
||||
struct host1x_job_gather *g = &job->gathers[i];
|
||||
u32 op1 = host1x_opcode_gather(g->words);
|
||||
u32 op2 = g->base + g->offset;
|
||||
|
||||
trace_write_gather(cdma, g->bo, g->offset, op1 & 0xffff);
|
||||
host1x_cdma_push(cdma, op1, op2);
|
||||
}
|
||||
@ -75,7 +77,8 @@ static inline void synchronize_syncpt_base(struct host1x_job *job)
|
||||
{
|
||||
struct host1x *host = dev_get_drvdata(job->channel->dev->parent);
|
||||
struct host1x_syncpt *sp = host->syncpt + job->syncpt_id;
|
||||
u32 id, value;
|
||||
unsigned int id;
|
||||
u32 value;
|
||||
|
||||
value = host1x_syncpt_read_max(sp);
|
||||
id = sp->base->id;
|
||||
|
@ -40,8 +40,7 @@ enum {
|
||||
|
||||
static unsigned int show_channel_command(struct output *o, u32 val)
|
||||
{
|
||||
unsigned mask;
|
||||
unsigned subop;
|
||||
unsigned int mask, subop;
|
||||
|
||||
switch (val >> 28) {
|
||||
case HOST1X_OPCODE_SETCLASS:
|
||||
@ -51,12 +50,11 @@ static unsigned int show_channel_command(struct output *o, u32 val)
|
||||
val >> 6 & 0x3ff,
|
||||
val >> 16 & 0xfff, mask);
|
||||
return hweight8(mask);
|
||||
} else {
|
||||
host1x_debug_output(o, "SETCL(class=%03x)\n",
|
||||
val >> 6 & 0x3ff);
|
||||
return 0;
|
||||
}
|
||||
|
||||
host1x_debug_output(o, "SETCL(class=%03x)\n", val >> 6 & 0x3ff);
|
||||
return 0;
|
||||
|
||||
case HOST1X_OPCODE_INCR:
|
||||
host1x_debug_output(o, "INCR(offset=%03x, [",
|
||||
val >> 16 & 0xfff);
|
||||
@ -143,7 +141,8 @@ static void show_channel_gathers(struct output *o, struct host1x_cdma *cdma)
|
||||
struct host1x_job *job;
|
||||
|
||||
list_for_each_entry(job, &cdma->sync_queue, list) {
|
||||
int i;
|
||||
unsigned int i;
|
||||
|
||||
host1x_debug_output(o, "\n%p: JOB, syncpt_id=%d, syncpt_val=%d, first_get=%08x, timeout=%d num_slots=%d, num_handles=%d\n",
|
||||
job, job->syncpt_id, job->syncpt_end,
|
||||
job->first_get, job->timeout,
|
||||
@ -190,7 +189,7 @@ static void host1x_debug_show_channel_cdma(struct host1x *host,
|
||||
cbread = host1x_sync_readl(host, HOST1X_SYNC_CBREAD(ch->id));
|
||||
cbstat = host1x_sync_readl(host, HOST1X_SYNC_CBSTAT(ch->id));
|
||||
|
||||
host1x_debug_output(o, "%d-%s: ", ch->id, dev_name(ch->dev));
|
||||
host1x_debug_output(o, "%u-%s: ", ch->id, dev_name(ch->dev));
|
||||
|
||||
if (HOST1X_CHANNEL_DMACTRL_DMASTOP_V(dmactrl) ||
|
||||
!ch->cdma.push_buffer.mapped) {
|
||||
@ -207,7 +206,6 @@ static void host1x_debug_show_channel_cdma(struct host1x *host,
|
||||
HOST1X_CLASS_HOST1X &&
|
||||
HOST1X_SYNC_CBSTAT_CBOFFSET_V(cbstat) ==
|
||||
HOST1X_UCLASS_WAIT_SYNCPT_BASE) {
|
||||
|
||||
base = (cbread >> 16) & 0xff;
|
||||
baseval =
|
||||
host1x_sync_readl(host, HOST1X_SYNC_SYNCPT_BASE(base));
|
||||
@ -236,7 +234,7 @@ static void host1x_debug_show_channel_fifo(struct host1x *host,
|
||||
u32 val, rd_ptr, wr_ptr, start, end;
|
||||
unsigned int data_count = 0;
|
||||
|
||||
host1x_debug_output(o, "%d: fifo:\n", ch->id);
|
||||
host1x_debug_output(o, "%u: fifo:\n", ch->id);
|
||||
|
||||
val = host1x_ch_readl(ch, HOST1X_CHANNEL_FIFOSTAT);
|
||||
host1x_debug_output(o, "FIFOSTAT %08x\n", val);
|
||||
@ -290,20 +288,22 @@ static void host1x_debug_show_channel_fifo(struct host1x *host,
|
||||
|
||||
static void host1x_debug_show_mlocks(struct host1x *host, struct output *o)
|
||||
{
|
||||
int i;
|
||||
unsigned int i;
|
||||
|
||||
host1x_debug_output(o, "---- mlocks ----\n");
|
||||
|
||||
for (i = 0; i < host1x_syncpt_nb_mlocks(host); i++) {
|
||||
u32 owner =
|
||||
host1x_sync_readl(host, HOST1X_SYNC_MLOCK_OWNER(i));
|
||||
if (HOST1X_SYNC_MLOCK_OWNER_CH_OWNS_V(owner))
|
||||
host1x_debug_output(o, "%d: locked by channel %d\n",
|
||||
host1x_debug_output(o, "%u: locked by channel %u\n",
|
||||
i, HOST1X_SYNC_MLOCK_OWNER_CHID_V(owner));
|
||||
else if (HOST1X_SYNC_MLOCK_OWNER_CPU_OWNS_V(owner))
|
||||
host1x_debug_output(o, "%d: locked by cpu\n", i);
|
||||
host1x_debug_output(o, "%u: locked by cpu\n", i);
|
||||
else
|
||||
host1x_debug_output(o, "%d: unlocked\n", i);
|
||||
host1x_debug_output(o, "%u: unlocked\n", i);
|
||||
}
|
||||
|
||||
host1x_debug_output(o, "\n");
|
||||
}
|
||||
|
||||
|
@ -38,14 +38,14 @@ static void host1x_intr_syncpt_handle(struct host1x_syncpt *syncpt)
|
||||
host1x_sync_writel(host, BIT_MASK(id),
|
||||
HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(BIT_WORD(id)));
|
||||
|
||||
queue_work(host->intr_wq, &syncpt->intr.work);
|
||||
schedule_work(&syncpt->intr.work);
|
||||
}
|
||||
|
||||
static irqreturn_t syncpt_thresh_isr(int irq, void *dev_id)
|
||||
{
|
||||
struct host1x *host = dev_id;
|
||||
unsigned long reg;
|
||||
int i, id;
|
||||
unsigned int i, id;
|
||||
|
||||
for (i = 0; i < DIV_ROUND_UP(host->info->nb_pts, 32); i++) {
|
||||
reg = host1x_sync_readl(host,
|
||||
@ -62,7 +62,7 @@ static irqreturn_t syncpt_thresh_isr(int irq, void *dev_id)
|
||||
|
||||
static void _host1x_intr_disable_all_syncpt_intrs(struct host1x *host)
|
||||
{
|
||||
u32 i;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < DIV_ROUND_UP(host->info->nb_pts, 32); ++i) {
|
||||
host1x_sync_writel(host, 0xffffffffu,
|
||||
@ -72,10 +72,12 @@ static void _host1x_intr_disable_all_syncpt_intrs(struct host1x *host)
|
||||
}
|
||||
}
|
||||
|
||||
static int _host1x_intr_init_host_sync(struct host1x *host, u32 cpm,
|
||||
static int
|
||||
_host1x_intr_init_host_sync(struct host1x *host, u32 cpm,
|
||||
void (*syncpt_thresh_work)(struct work_struct *))
|
||||
{
|
||||
int i, err;
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
host1x_hw_intr_disable_all_syncpt_intrs(host);
|
||||
|
||||
@ -106,18 +108,21 @@ static int _host1x_intr_init_host_sync(struct host1x *host, u32 cpm,
|
||||
}
|
||||
|
||||
static void _host1x_intr_set_syncpt_threshold(struct host1x *host,
|
||||
u32 id, u32 thresh)
|
||||
unsigned int id,
|
||||
u32 thresh)
|
||||
{
|
||||
host1x_sync_writel(host, thresh, HOST1X_SYNC_SYNCPT_INT_THRESH(id));
|
||||
}
|
||||
|
||||
static void _host1x_intr_enable_syncpt_intr(struct host1x *host, u32 id)
|
||||
static void _host1x_intr_enable_syncpt_intr(struct host1x *host,
|
||||
unsigned int id)
|
||||
{
|
||||
host1x_sync_writel(host, BIT_MASK(id),
|
||||
HOST1X_SYNC_SYNCPT_THRESH_INT_ENABLE_CPU0(BIT_WORD(id)));
|
||||
}
|
||||
|
||||
static void _host1x_intr_disable_syncpt_intr(struct host1x *host, u32 id)
|
||||
static void _host1x_intr_disable_syncpt_intr(struct host1x *host,
|
||||
unsigned int id)
|
||||
{
|
||||
host1x_sync_writel(host, BIT_MASK(id),
|
||||
HOST1X_SYNC_SYNCPT_THRESH_INT_DISABLE(BIT_WORD(id)));
|
||||
@ -127,8 +132,13 @@ static void _host1x_intr_disable_syncpt_intr(struct host1x *host, u32 id)
|
||||
|
||||
static int _host1x_free_syncpt_irq(struct host1x *host)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
devm_free_irq(host->dev, host->intr_syncpt_irq, host);
|
||||
flush_workqueue(host->intr_wq);
|
||||
|
||||
for (i = 0; i < host->info->nb_pts; i++)
|
||||
cancel_work_sync(&host->syncpt[i].intr.work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -26,8 +26,9 @@
|
||||
*/
|
||||
static void syncpt_restore(struct host1x_syncpt *sp)
|
||||
{
|
||||
u32 min = host1x_syncpt_read_min(sp);
|
||||
struct host1x *host = sp->host;
|
||||
int min = host1x_syncpt_read_min(sp);
|
||||
|
||||
host1x_sync_writel(host, min, HOST1X_SYNC_SYNCPT(sp->id));
|
||||
}
|
||||
|
||||
@ -37,6 +38,7 @@ static void syncpt_restore(struct host1x_syncpt *sp)
|
||||
static void syncpt_restore_wait_base(struct host1x_syncpt *sp)
|
||||
{
|
||||
struct host1x *host = sp->host;
|
||||
|
||||
host1x_sync_writel(host, sp->base_val,
|
||||
HOST1X_SYNC_SYNCPT_BASE(sp->id));
|
||||
}
|
||||
@ -47,6 +49,7 @@ static void syncpt_restore_wait_base(struct host1x_syncpt *sp)
|
||||
static void syncpt_read_wait_base(struct host1x_syncpt *sp)
|
||||
{
|
||||
struct host1x *host = sp->host;
|
||||
|
||||
sp->base_val =
|
||||
host1x_sync_readl(host, HOST1X_SYNC_SYNCPT_BASE(sp->id));
|
||||
}
|
||||
@ -85,6 +88,7 @@ static int syncpt_cpu_incr(struct host1x_syncpt *sp)
|
||||
if (!host1x_syncpt_client_managed(sp) &&
|
||||
host1x_syncpt_idle(sp))
|
||||
return -EINVAL;
|
||||
|
||||
host1x_sync_writel(host, BIT_MASK(sp->id),
|
||||
HOST1X_SYNC_SYNCPT_CPU_INCR(reg_offset));
|
||||
wmb();
|
||||
@ -95,10 +99,10 @@ static int syncpt_cpu_incr(struct host1x_syncpt *sp)
|
||||
/* remove a wait pointed to by patch_addr */
|
||||
static int syncpt_patch_wait(struct host1x_syncpt *sp, void *patch_addr)
|
||||
{
|
||||
u32 override = host1x_class_host_wait_syncpt(
|
||||
HOST1X_SYNCPT_RESERVED, 0);
|
||||
u32 override = host1x_class_host_wait_syncpt(HOST1X_SYNCPT_RESERVED, 0);
|
||||
|
||||
*((u32 *)patch_addr) = override;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -122,18 +122,20 @@ static void action_submit_complete(struct host1x_waitlist *waiter)
|
||||
static void action_wakeup(struct host1x_waitlist *waiter)
|
||||
{
|
||||
wait_queue_head_t *wq = waiter->data;
|
||||
|
||||
wake_up(wq);
|
||||
}
|
||||
|
||||
static void action_wakeup_interruptible(struct host1x_waitlist *waiter)
|
||||
{
|
||||
wait_queue_head_t *wq = waiter->data;
|
||||
|
||||
wake_up_interruptible(wq);
|
||||
}
|
||||
|
||||
typedef void (*action_handler)(struct host1x_waitlist *waiter);
|
||||
|
||||
static action_handler action_handlers[HOST1X_INTR_ACTION_COUNT] = {
|
||||
static const action_handler action_handlers[HOST1X_INTR_ACTION_COUNT] = {
|
||||
action_submit_complete,
|
||||
action_wakeup,
|
||||
action_wakeup_interruptible,
|
||||
@ -209,7 +211,7 @@ static void syncpt_thresh_work(struct work_struct *work)
|
||||
host1x_syncpt_load(host->syncpt + id));
|
||||
}
|
||||
|
||||
int host1x_intr_add_action(struct host1x *host, u32 id, u32 thresh,
|
||||
int host1x_intr_add_action(struct host1x *host, unsigned int id, u32 thresh,
|
||||
enum host1x_intr_action action, void *data,
|
||||
struct host1x_waitlist *waiter, void **ref)
|
||||
{
|
||||
@ -254,7 +256,7 @@ int host1x_intr_add_action(struct host1x *host, u32 id, u32 thresh,
|
||||
return 0;
|
||||
}
|
||||
|
||||
void host1x_intr_put_ref(struct host1x *host, u32 id, void *ref)
|
||||
void host1x_intr_put_ref(struct host1x *host, unsigned int id, void *ref)
|
||||
{
|
||||
struct host1x_waitlist *waiter = ref;
|
||||
struct host1x_syncpt *syncpt;
|
||||
@ -277,9 +279,6 @@ int host1x_intr_init(struct host1x *host, unsigned int irq_sync)
|
||||
|
||||
mutex_init(&host->intr_mutex);
|
||||
host->intr_syncpt_irq = irq_sync;
|
||||
host->intr_wq = create_workqueue("host_syncpt");
|
||||
if (!host->intr_wq)
|
||||
return -ENOMEM;
|
||||
|
||||
for (id = 0; id < nb_pts; ++id) {
|
||||
struct host1x_syncpt *syncpt = host->syncpt + id;
|
||||
@ -288,7 +287,7 @@ int host1x_intr_init(struct host1x *host, unsigned int irq_sync)
|
||||
INIT_LIST_HEAD(&syncpt->intr.wait_head);
|
||||
snprintf(syncpt->intr.thresh_irq_name,
|
||||
sizeof(syncpt->intr.thresh_irq_name),
|
||||
"host1x_sp_%02d", id);
|
||||
"host1x_sp_%02u", id);
|
||||
}
|
||||
|
||||
host1x_intr_start(host);
|
||||
@ -299,7 +298,6 @@ int host1x_intr_init(struct host1x *host, unsigned int irq_sync)
|
||||
void host1x_intr_deinit(struct host1x *host)
|
||||
{
|
||||
host1x_intr_stop(host);
|
||||
destroy_workqueue(host->intr_wq);
|
||||
}
|
||||
|
||||
void host1x_intr_start(struct host1x *host)
|
||||
@ -342,7 +340,7 @@ void host1x_intr_stop(struct host1x *host)
|
||||
if (!list_empty(&syncpt[id].intr.wait_head)) {
|
||||
/* output diagnostics */
|
||||
mutex_unlock(&host->intr_mutex);
|
||||
pr_warn("%s cannot stop syncpt intr id=%d\n",
|
||||
pr_warn("%s cannot stop syncpt intr id=%u\n",
|
||||
__func__, id);
|
||||
return;
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ struct host1x_waitlist {
|
||||
*
|
||||
* This is a non-blocking api.
|
||||
*/
|
||||
int host1x_intr_add_action(struct host1x *host, u32 id, u32 thresh,
|
||||
int host1x_intr_add_action(struct host1x *host, unsigned int id, u32 thresh,
|
||||
enum host1x_intr_action action, void *data,
|
||||
struct host1x_waitlist *waiter, void **ref);
|
||||
|
||||
@ -84,7 +84,7 @@ int host1x_intr_add_action(struct host1x *host, u32 id, u32 thresh,
|
||||
* You must call this if you passed non-NULL as ref.
|
||||
* @ref the ref returned from host1x_intr_add_action()
|
||||
*/
|
||||
void host1x_intr_put_ref(struct host1x *host, u32 id, void *ref);
|
||||
void host1x_intr_put_ref(struct host1x *host, unsigned int id, void *ref);
|
||||
|
||||
/* Initialize host1x sync point interrupt */
|
||||
int host1x_intr_init(struct host1x *host, unsigned int irq_sync);
|
||||
|
@ -161,7 +161,7 @@ static int do_waitchks(struct host1x_job *job, struct host1x *host,
|
||||
|
||||
if (host1x_syncpt_is_expired(sp, wait->thresh)) {
|
||||
dev_dbg(host->dev,
|
||||
"drop WAIT id %d (%s) thresh 0x%x, min 0x%x\n",
|
||||
"drop WAIT id %u (%s) thresh 0x%x, min 0x%x\n",
|
||||
wait->syncpt_id, sp->name, wait->thresh,
|
||||
host1x_syncpt_read_min(sp));
|
||||
|
||||
@ -464,6 +464,7 @@ static inline int copy_gathers(struct host1x_job *job, struct device *dev)
|
||||
|
||||
for (i = 0; i < job->num_gathers; i++) {
|
||||
struct host1x_job_gather *g = &job->gathers[i];
|
||||
|
||||
size += g->words * sizeof(u32);
|
||||
}
|
||||
|
||||
@ -514,6 +515,7 @@ int host1x_job_pin(struct host1x_job *job, struct device *dev)
|
||||
bitmap_zero(waitchk_mask, host1x_syncpt_nb_pts(host));
|
||||
for (i = 0; i < job->num_waitchk; i++) {
|
||||
u32 syncpt_id = job->waitchk[i].syncpt_id;
|
||||
|
||||
if (syncpt_id < host1x_syncpt_nb_pts(host))
|
||||
set_bit(syncpt_id, waitchk_mask);
|
||||
}
|
||||
@ -571,9 +573,11 @@ void host1x_job_unpin(struct host1x_job *job)
|
||||
|
||||
for (i = 0; i < job->num_unpins; i++) {
|
||||
struct host1x_job_unpin_data *unpin = &job->unpins[i];
|
||||
|
||||
host1x_bo_unpin(unpin->bo, unpin->sgt);
|
||||
host1x_bo_put(unpin->bo);
|
||||
}
|
||||
|
||||
job->num_unpins = 0;
|
||||
|
||||
if (job->gather_copy_size)
|
||||
|
@ -73,7 +73,7 @@ static struct host1x_syncpt *host1x_syncpt_alloc(struct host1x *host,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
name = kasprintf(GFP_KERNEL, "%02d-%s", sp->id,
|
||||
name = kasprintf(GFP_KERNEL, "%02u-%s", sp->id,
|
||||
dev ? dev_name(dev) : NULL);
|
||||
if (!name)
|
||||
return NULL;
|
||||
@ -110,12 +110,14 @@ EXPORT_SYMBOL(host1x_syncpt_incr_max);
|
||||
void host1x_syncpt_restore(struct host1x *host)
|
||||
{
|
||||
struct host1x_syncpt *sp_base = host->syncpt;
|
||||
u32 i;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < host1x_syncpt_nb_pts(host); i++)
|
||||
host1x_hw_syncpt_restore(host, sp_base + i);
|
||||
|
||||
for (i = 0; i < host1x_syncpt_nb_bases(host); i++)
|
||||
host1x_hw_syncpt_restore_wait_base(host, sp_base + i);
|
||||
|
||||
wmb();
|
||||
}
|
||||
|
||||
@ -126,7 +128,7 @@ void host1x_syncpt_restore(struct host1x *host)
|
||||
void host1x_syncpt_save(struct host1x *host)
|
||||
{
|
||||
struct host1x_syncpt *sp_base = host->syncpt;
|
||||
u32 i;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < host1x_syncpt_nb_pts(host); i++) {
|
||||
if (host1x_syncpt_client_managed(sp_base + i))
|
||||
@ -146,6 +148,7 @@ void host1x_syncpt_save(struct host1x *host)
|
||||
u32 host1x_syncpt_load(struct host1x_syncpt *sp)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = host1x_hw_syncpt_load(sp->host, sp);
|
||||
trace_host1x_syncpt_load_min(sp->id, val);
|
||||
|
||||
@ -157,10 +160,9 @@ u32 host1x_syncpt_load(struct host1x_syncpt *sp)
|
||||
*/
|
||||
u32 host1x_syncpt_load_wait_base(struct host1x_syncpt *sp)
|
||||
{
|
||||
u32 val;
|
||||
host1x_hw_syncpt_load_wait_base(sp->host, sp);
|
||||
val = sp->base_val;
|
||||
return val;
|
||||
|
||||
return sp->base_val;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -179,6 +181,7 @@ EXPORT_SYMBOL(host1x_syncpt_incr);
|
||||
static bool syncpt_load_min_is_expired(struct host1x_syncpt *sp, u32 thresh)
|
||||
{
|
||||
host1x_hw_syncpt_load(sp->host, sp);
|
||||
|
||||
return host1x_syncpt_is_expired(sp, thresh);
|
||||
}
|
||||
|
||||
@ -201,6 +204,7 @@ int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, long timeout,
|
||||
if (host1x_syncpt_is_expired(sp, thresh)) {
|
||||
if (value)
|
||||
*value = host1x_syncpt_load(sp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -209,6 +213,7 @@ int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, long timeout,
|
||||
if (host1x_syncpt_is_expired(sp, thresh)) {
|
||||
if (value)
|
||||
*value = val;
|
||||
|
||||
goto done;
|
||||
}
|
||||
|
||||
@ -239,32 +244,42 @@ int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, long timeout,
|
||||
/* wait for the syncpoint, or timeout, or signal */
|
||||
while (timeout) {
|
||||
long check = min_t(long, SYNCPT_CHECK_PERIOD, timeout);
|
||||
int remain = wait_event_interruptible_timeout(wq,
|
||||
int remain;
|
||||
|
||||
remain = wait_event_interruptible_timeout(wq,
|
||||
syncpt_load_min_is_expired(sp, thresh),
|
||||
check);
|
||||
if (remain > 0 || host1x_syncpt_is_expired(sp, thresh)) {
|
||||
if (value)
|
||||
*value = host1x_syncpt_load(sp);
|
||||
|
||||
err = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (remain < 0) {
|
||||
err = remain;
|
||||
break;
|
||||
}
|
||||
|
||||
timeout -= check;
|
||||
|
||||
if (timeout && check_count <= MAX_STUCK_CHECK_COUNT) {
|
||||
dev_warn(sp->host->dev,
|
||||
"%s: syncpoint id %d (%s) stuck waiting %d, timeout=%ld\n",
|
||||
"%s: syncpoint id %u (%s) stuck waiting %d, timeout=%ld\n",
|
||||
current->comm, sp->id, sp->name,
|
||||
thresh, timeout);
|
||||
|
||||
host1x_debug_dump_syncpts(sp->host);
|
||||
|
||||
if (check_count == MAX_STUCK_CHECK_COUNT)
|
||||
host1x_debug_dump(sp->host);
|
||||
|
||||
check_count++;
|
||||
}
|
||||
}
|
||||
|
||||
host1x_intr_put_ref(sp->host, sp->id, ref);
|
||||
|
||||
done:
|
||||
@ -279,7 +294,9 @@ bool host1x_syncpt_is_expired(struct host1x_syncpt *sp, u32 thresh)
|
||||
{
|
||||
u32 current_val;
|
||||
u32 future_val;
|
||||
|
||||
smp_rmb();
|
||||
|
||||
current_val = (u32)atomic_read(&sp->min_val);
|
||||
future_val = (u32)atomic_read(&sp->max_val);
|
||||
|
||||
@ -341,14 +358,14 @@ int host1x_syncpt_init(struct host1x *host)
|
||||
{
|
||||
struct host1x_syncpt_base *bases;
|
||||
struct host1x_syncpt *syncpt;
|
||||
int i;
|
||||
unsigned int i;
|
||||
|
||||
syncpt = devm_kzalloc(host->dev, sizeof(*syncpt) * host->info->nb_pts,
|
||||
syncpt = devm_kcalloc(host->dev, host->info->nb_pts, sizeof(*syncpt),
|
||||
GFP_KERNEL);
|
||||
if (!syncpt)
|
||||
return -ENOMEM;
|
||||
|
||||
bases = devm_kzalloc(host->dev, sizeof(*bases) * host->info->nb_bases,
|
||||
bases = devm_kcalloc(host->dev, host->info->nb_bases, sizeof(*bases),
|
||||
GFP_KERNEL);
|
||||
if (!bases)
|
||||
return -ENOMEM;
|
||||
@ -378,6 +395,7 @@ struct host1x_syncpt *host1x_syncpt_request(struct device *dev,
|
||||
unsigned long flags)
|
||||
{
|
||||
struct host1x *host = dev_get_drvdata(dev->parent);
|
||||
|
||||
return host1x_syncpt_alloc(host, dev, flags);
|
||||
}
|
||||
EXPORT_SYMBOL(host1x_syncpt_request);
|
||||
@ -398,8 +416,9 @@ EXPORT_SYMBOL(host1x_syncpt_free);
|
||||
|
||||
void host1x_syncpt_deinit(struct host1x *host)
|
||||
{
|
||||
int i;
|
||||
struct host1x_syncpt *sp = host->syncpt;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < host->info->nb_pts; i++, sp++)
|
||||
kfree(sp->name);
|
||||
}
|
||||
@ -407,10 +426,11 @@ void host1x_syncpt_deinit(struct host1x *host)
|
||||
/*
|
||||
* Read max. It indicates how many operations there are in queue, either in
|
||||
* channel or in a software thread.
|
||||
* */
|
||||
*/
|
||||
u32 host1x_syncpt_read_max(struct host1x_syncpt *sp)
|
||||
{
|
||||
smp_rmb();
|
||||
|
||||
return (u32)atomic_read(&sp->max_val);
|
||||
}
|
||||
EXPORT_SYMBOL(host1x_syncpt_read_max);
|
||||
@ -421,6 +441,7 @@ EXPORT_SYMBOL(host1x_syncpt_read_max);
|
||||
u32 host1x_syncpt_read_min(struct host1x_syncpt *sp)
|
||||
{
|
||||
smp_rmb();
|
||||
|
||||
return (u32)atomic_read(&sp->min_val);
|
||||
}
|
||||
EXPORT_SYMBOL(host1x_syncpt_read_min);
|
||||
@ -431,25 +452,26 @@ u32 host1x_syncpt_read(struct host1x_syncpt *sp)
|
||||
}
|
||||
EXPORT_SYMBOL(host1x_syncpt_read);
|
||||
|
||||
int host1x_syncpt_nb_pts(struct host1x *host)
|
||||
unsigned int host1x_syncpt_nb_pts(struct host1x *host)
|
||||
{
|
||||
return host->info->nb_pts;
|
||||
}
|
||||
|
||||
int host1x_syncpt_nb_bases(struct host1x *host)
|
||||
unsigned int host1x_syncpt_nb_bases(struct host1x *host)
|
||||
{
|
||||
return host->info->nb_bases;
|
||||
}
|
||||
|
||||
int host1x_syncpt_nb_mlocks(struct host1x *host)
|
||||
unsigned int host1x_syncpt_nb_mlocks(struct host1x *host)
|
||||
{
|
||||
return host->info->nb_mlocks;
|
||||
}
|
||||
|
||||
struct host1x_syncpt *host1x_syncpt_get(struct host1x *host, u32 id)
|
||||
struct host1x_syncpt *host1x_syncpt_get(struct host1x *host, unsigned int id)
|
||||
{
|
||||
if (host->info->nb_pts < id)
|
||||
return NULL;
|
||||
|
||||
return host->syncpt + id;
|
||||
}
|
||||
EXPORT_SYMBOL(host1x_syncpt_get);
|
||||
|
@ -37,7 +37,7 @@ struct host1x_syncpt_base {
|
||||
};
|
||||
|
||||
struct host1x_syncpt {
|
||||
int id;
|
||||
unsigned int id;
|
||||
atomic_t min_val;
|
||||
atomic_t max_val;
|
||||
u32 base_val;
|
||||
@ -58,13 +58,13 @@ int host1x_syncpt_init(struct host1x *host);
|
||||
void host1x_syncpt_deinit(struct host1x *host);
|
||||
|
||||
/* Return number of sync point supported. */
|
||||
int host1x_syncpt_nb_pts(struct host1x *host);
|
||||
unsigned int host1x_syncpt_nb_pts(struct host1x *host);
|
||||
|
||||
/* Return number of wait bases supported. */
|
||||
int host1x_syncpt_nb_bases(struct host1x *host);
|
||||
unsigned int host1x_syncpt_nb_bases(struct host1x *host);
|
||||
|
||||
/* Return number of mlocks supported. */
|
||||
int host1x_syncpt_nb_mlocks(struct host1x *host);
|
||||
unsigned int host1x_syncpt_nb_mlocks(struct host1x *host);
|
||||
|
||||
/*
|
||||
* Check sync point sanity. If max is larger than min, there have too many
|
||||
|
@ -391,4 +391,12 @@ exit:
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pinconf_generic_dt_node_to_map);
|
||||
|
||||
void pinconf_generic_dt_free_map(struct pinctrl_dev *pctldev,
|
||||
struct pinctrl_map *map,
|
||||
unsigned num_maps)
|
||||
{
|
||||
pinctrl_utils_free_map(pctldev, map, num_maps);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pinconf_generic_dt_free_map);
|
||||
|
||||
#endif
|
||||
|
@ -175,6 +175,8 @@ int pinconf_generic_dt_subnode_to_map(struct pinctrl_dev *pctldev,
|
||||
int pinconf_generic_dt_node_to_map(struct pinctrl_dev *pctldev,
|
||||
struct device_node *np_config, struct pinctrl_map **map,
|
||||
unsigned *num_maps, enum pinctrl_map_type type);
|
||||
void pinconf_generic_dt_free_map(struct pinctrl_dev *pctldev,
|
||||
struct pinctrl_map *map, unsigned num_maps);
|
||||
|
||||
static inline int pinconf_generic_dt_node_to_map_group(
|
||||
struct pinctrl_dev *pctldev, struct device_node *np_config,
|
||||
|
Loading…
Reference in New Issue
Block a user