diff options
author | Daniel Vetter <daniel.vetter@ffwll.ch> | 2016-07-19 09:27:29 +0200 |
---|---|---|
committer | Daniel Vetter <daniel.vetter@ffwll.ch> | 2016-07-19 09:27:29 +0200 |
commit | 2383050f6a3a2e00636eabfcf66445af653ddd80 (patch) | |
tree | 45e5eb04a8ac280ce36f0d1e4305c8d816ee2e70 /drivers/gpu/drm/tegra | |
parent | a1bf09e69f8e938fef85f18c117ac390a48f7763 (diff) | |
parent | 2d635fded22b71381b01e194a59544f2e8d71ef0 (diff) | |
download | linux-2383050f6a3a2e00636eabfcf66445af653ddd80.tar.bz2 |
Merge remote-tracking branch 'airlied/drm-next' into topic/drm-misc
Backmerge drm-next to be able to apply Chris' connector_unregister_all
cleanup (need latest i915 and sun4i state for that).
Also there's a trivial conflict in ttm_bo.c that git rerere fails to
remember.
Signed-off-by: Daniel Vetter <daniel.vetter@intel.com>
Diffstat (limited to 'drivers/gpu/drm/tegra')
-rw-r--r-- | drivers/gpu/drm/tegra/dc.c | 176 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dpaux.c | 245 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/drm.c | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dsi.c | 247 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/hdmi.c | 507 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/hdmi.h | 21 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/output.c | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/sor.c | 716 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/sor.h | 3 |
9 files changed, 1351 insertions, 567 deletions
diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 39940f5b7c91..8495bd01b544 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -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, diff --git a/drivers/gpu/drm/tegra/dpaux.c b/drivers/gpu/drm/tegra/dpaux.c index b24a0f14821a..059f409556d5 100644 --- a/drivers/gpu/drm/tegra/dpaux.c +++ b/drivers/gpu/drm/tegra/dpaux.c @@ -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,11 +444,14 @@ static int tegra_dpaux_probe(struct platform_device *pdev) return -ENXIO; } - 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", - PTR_ERR(dpaux->rst)); - return PTR_ERR(dpaux->rst); + 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", + PTR_ERR(dpaux->rst)); + return PTR_ERR(dpaux->rst); + } } dpaux->clk = devm_clk_get(&pdev->dev, NULL); @@ -315,34 +468,37 @@ static int tegra_dpaux_probe(struct platform_device *pdev) return err; } - reset_control_deassert(dpaux->rst); + 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); - - 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); + err = tegra_dpaux_pad_config(dpaux, DPAUX_HYBRID_PADCTL_MODE_I2C); + if (err < 0) + return err; +#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); - reset_control_assert(dpaux->rst); + + 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; } diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index a177a42a9849..755264d9db22 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -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); diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 099cccb2fbcb..3d228ad90e0f 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -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; - dsi->rst = devm_reset_control_get(&pdev->dev, "dsi"); - if (IS_ERR(dsi->rst)) - return PTR_ERR(dsi->rst); + 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, diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 2fdb8796443e..cda0491ed6bf 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -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; + /* + * 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 |= source; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_AUDIO_CNTRL0); + } - value = AUDIO_CNTRL0_ERROR_TOLERANCE(6) | - AUDIO_CNTRL0_FRAMES_PER_BLOCK(0xc0); - tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_CNTRL0); + /* + * 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_freq, pclk); + 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,43 +616,53 @@ 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; + if (hdmi->config->has_hda) + tegra_hdmi_write_aval(hdmi, config->aval); - case 44100: - offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0441; - break; + tegra_hdmi_setup_audio_fs_tables(hdmi); - case 48000: - offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0480; - break; + return 0; +} - case 88200: - offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0882; - break; +static void tegra_hdmi_disable_audio(struct tegra_hdmi *hdmi) +{ + u32 value; - case 96000: - offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0960; - break; + 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); +} - case 176400: - offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_1764; - break; +static void tegra_hdmi_enable_audio(struct tegra_hdmi *hdmi) +{ + u32 value; - case 192000: - offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_1920; - break; - } + 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); +} - tegra_hdmi_writel(hdmi, config->aval, offset); - } +static void tegra_hdmi_write_eld(struct tegra_hdmi *hdmi) +{ + size_t length = drm_eld_size(hdmi->output.connector.eld), i; + u32 value; - tegra_hdmi_setup_audio_fs_tables(hdmi); + 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); - return 0; + /* + * 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) @@ -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); +} + +static void tegra_hdmi_disable_avi_infoframe(struct tegra_hdmi *hdmi) +{ + u32 value; - tegra_hdmi_writel(hdmi, INFOFRAME_CTRL_ENABLE, - HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL); + 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); - tegra_hdmi_setup_avi_infoframe(hdmi, mode); - tegra_hdmi_setup_audio_infoframe(hdmi); - tegra_hdmi_setup_stereo_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, diff --git a/drivers/gpu/drm/tegra/hdmi.h b/drivers/gpu/drm/tegra/hdmi.h index a882514389cd..2339f134a09a 100644 --- a/drivers/gpu/drm/tegra/hdmi.h +++ b/drivers/gpu/drm/tegra/hdmi.h @@ -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) diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 1480f6aaffe4..595d1ec3e02e 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -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); } diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 34958d71284b..74d0540b8d4c 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -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,10 +699,10 @@ static int tegra_sor_compute_params(struct tegra_sor *sor, return false; } -static int tegra_sor_calc_config(struct tegra_sor *sor, - const struct drm_display_mode *mode, - struct tegra_sor_config *config, - struct drm_dp_link *link) +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) { const u64 f = 100000, link_rate = link->rate * 1000; const u64 pclk = mode->clock * 1000; @@ -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,10 +2353,13 @@ static int tegra_sor_init(struct host1x_client *client) * XXX: Remove this reset once proper hand-over from firmware to * kernel is possible. */ - err = reset_control_assert(sor->rst); - if (err < 0) { - dev_err(sor->dev, "failed to assert SOR reset: %d\n", err); - return err; + if (sor->rst) { + err = reset_control_assert(sor->rst); + if (err < 0) { + dev_err(sor->dev, "failed to assert SOR reset: %d\n", + err); + return err; + } } err = clk_prepare_enable(sor->clk); @@ -2209,10 +2370,13 @@ static int tegra_sor_init(struct host1x_client *client) usleep_range(1000, 3000); - err = reset_control_deassert(sor->rst); - if (err < 0) { - dev_err(sor->dev, "failed to deassert SOR reset: %d\n", err); - return err; + if (sor->rst) { + err = reset_control_deassert(sor->rst); + if (err < 0) { + dev_err(sor->dev, "failed to deassert SOR reset: %d\n", + err); + return err; + } } err = clk_prepare_enable(sor->clk_safe); @@ -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,11 +2610,14 @@ static int tegra_sor_probe(struct platform_device *pdev) goto remove; } - 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); - 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); + goto remove; + } } sor->clk = devm_clk_get(&pdev->dev, NULL); @@ -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, diff --git a/drivers/gpu/drm/tegra/sor.h b/drivers/gpu/drm/tegra/sor.h index 2d31d027e3f6..865c73b48968 100644 --- a/drivers/gpu/drm/tegra/sor.h +++ b/drivers/gpu/drm/tegra/sor.h @@ -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) |