diff options
28 files changed, 1636 insertions, 702 deletions
diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt index a3bd8c050c4e..0fad7ed2ea19 100644 --- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt +++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt @@ -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: diff --git a/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-dpaux-padctl.txt b/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-dpaux-padctl.txt new file mode 100644 index 000000000000..f2abdaee9022 --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-dpaux-padctl.txt @@ -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"; + }; 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) diff --git a/drivers/gpu/host1x/cdma.c b/drivers/gpu/host1x/cdma.c index a18db4d5347c..c5d82a8a2ec9 100644 --- a/drivers/gpu/host1x/cdma.c +++ b/drivers/gpu/host1x/cdma.c @@ -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); diff --git a/drivers/gpu/host1x/channel.c b/drivers/gpu/host1x/channel.c index b4ae3affb987..8f437d924c10 100644 --- a/drivers/gpu/host1x/channel.c +++ b/drivers/gpu/host1x/channel.c @@ -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); diff --git a/drivers/gpu/host1x/debug.c b/drivers/gpu/host1x/debug.c index ee3d12b51c50..d9330fcc62ad 100644 --- a/drivers/gpu/host1x/debug.c +++ b/drivers/gpu/host1x/debug.c @@ -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; } @@ -134,10 +148,10 @@ static int host1x_debug_open_all(struct inode *inode, struct file *file) } static const struct file_operations host1x_debug_all_fops = { - .open = host1x_debug_open_all, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, + .open = host1x_debug_open_all, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, }; static int host1x_debug_open(struct inode *inode, struct file *file) @@ -146,10 +160,10 @@ static int host1x_debug_open(struct inode *inode, struct file *file) } static const struct file_operations host1x_debug_fops = { - .open = host1x_debug_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, + .open = host1x_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, }; static void host1x_debugfs_init(struct host1x *host1x) @@ -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); } diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c index ff348690df94..a62317af76ad 100644 --- a/drivers/gpu/host1x/dev.c +++ b/drivers/gpu/host1x/dev.c @@ -63,13 +63,13 @@ u32 host1x_ch_readl(struct host1x_channel *ch, u32 r) } static const struct host1x_info host1x01_info = { - .nb_channels = 8, - .nb_pts = 32, - .nb_mlocks = 16, - .nb_bases = 8, - .init = host1x01_init, - .sync_offset = 0x3000, - .dma_mask = DMA_BIT_MASK(32), + .nb_channels = 8, + .nb_pts = 32, + .nb_mlocks = 16, + .nb_bases = 8, + .init = host1x01_init, + .sync_offset = 0x3000, + .dma_mask = DMA_BIT_MASK(32), }; static const struct host1x_info host1x02_info = { @@ -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, }, diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h index dace124994bb..5220510f39da 100644 --- a/drivers/gpu/host1x/dev.h +++ b/drivers/gpu/host1x/dev.h @@ -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,21 +82,21 @@ 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; - u64 dma_mask; /* mask of addressable memory */ + 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 */ }; struct host1x { @@ -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, diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c index 305ea8f3382d..659c1bbfeeba 100644 --- a/drivers/gpu/host1x/hw/cdma_hw.c +++ b/drivers/gpu/host1x/hw/cdma_hw.c @@ -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,9 +277,9 @@ static void cdma_timeout_handler(struct work_struct *work) return; } - dev_warn(host1x->dev, "%s: timeout: %d (%s), HW thresh %d, done %d\n", - __func__, cdma->timeout.syncpt->id, cdma->timeout.syncpt->name, - syncpt_val, cdma->timeout.syncpt_val); + 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); /* stop HW, resetting channel/module */ host1x_hw_cdma_freeze(host1x, cdma); @@ -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; } diff --git a/drivers/gpu/host1x/hw/channel_hw.c b/drivers/gpu/host1x/hw/channel_hw.c index 946c332c3906..5e8df78b7acd 100644 --- a/drivers/gpu/host1x/hw/channel_hw.c +++ b/drivers/gpu/host1x/hw/channel_hw.c @@ -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; diff --git a/drivers/gpu/host1x/hw/debug_hw.c b/drivers/gpu/host1x/hw/debug_hw.c index cc3f1825c735..7a4a3286e4a7 100644 --- a/drivers/gpu/host1x/hw/debug_hw.c +++ b/drivers/gpu/host1x/hw/debug_hw.c @@ -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) { @@ -200,14 +199,13 @@ static void host1x_debug_show_channel_cdma(struct host1x *host, if (HOST1X_SYNC_CBSTAT_CBCLASS_V(cbstat) == HOST1X_CLASS_HOST1X && HOST1X_SYNC_CBSTAT_CBOFFSET_V(cbstat) == - HOST1X_UCLASS_WAIT_SYNCPT) + HOST1X_UCLASS_WAIT_SYNCPT) host1x_debug_output(o, "waiting on syncpt %d val %d\n", cbread >> 24, cbread & 0xffffff); else if (HOST1X_SYNC_CBSTAT_CBCLASS_V(cbstat) == - HOST1X_CLASS_HOST1X && - HOST1X_SYNC_CBSTAT_CBOFFSET_V(cbstat) == - HOST1X_UCLASS_WAIT_SYNCPT_BASE) { - + 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"); } diff --git a/drivers/gpu/host1x/hw/intr_hw.c b/drivers/gpu/host1x/hw/intr_hw.c index e1e31e9e67cd..dacb8009a605 100644 --- a/drivers/gpu/host1x/hw/intr_hw.c +++ b/drivers/gpu/host1x/hw/intr_hw.c @@ -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, - void (*syncpt_thresh_work)(struct work_struct *)) +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; } diff --git a/drivers/gpu/host1x/hw/syncpt_hw.c b/drivers/gpu/host1x/hw/syncpt_hw.c index 56e85395ac24..c93f74fcce72 100644 --- a/drivers/gpu/host1x/hw/syncpt_hw.c +++ b/drivers/gpu/host1x/hw/syncpt_hw.c @@ -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; } diff --git a/drivers/gpu/host1x/intr.c b/drivers/gpu/host1x/intr.c index 2491bf82e30c..8b4fad0ab35d 100644 --- a/drivers/gpu/host1x/intr.c +++ b/drivers/gpu/host1x/intr.c @@ -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; } diff --git a/drivers/gpu/host1x/intr.h b/drivers/gpu/host1x/intr.h index 2b8adf016a05..1370c2bb75b8 100644 --- a/drivers/gpu/host1x/intr.h +++ b/drivers/gpu/host1x/intr.h @@ -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); diff --git a/drivers/gpu/host1x/job.c b/drivers/gpu/host1x/job.c index b4515d544039..a91b7c4a6110 100644 --- a/drivers/gpu/host1x/job.c +++ b/drivers/gpu/host1x/job.c @@ -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,14 +573,16 @@ 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) dma_free_wc(job->channel->dev, job->gather_copy_size, - job->gather_copy_mapped, job->gather_copy); + job->gather_copy_mapped, job->gather_copy); } EXPORT_SYMBOL(host1x_job_unpin); diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c index 6b7fdc1e2ed0..95589328ad52 100644 --- a/drivers/gpu/host1x/syncpt.c +++ b/drivers/gpu/host1x/syncpt.c @@ -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); } @@ -186,7 +189,7 @@ static bool syncpt_load_min_is_expired(struct host1x_syncpt *sp, u32 thresh) * Main entrypoint for syncpoint value waits. */ int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, long timeout, - u32 *value) + u32 *value) { DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq); void *ref; @@ -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); diff --git a/drivers/gpu/host1x/syncpt.h b/drivers/gpu/host1x/syncpt.h index 9056465ecd3f..f719205105ac 100644 --- a/drivers/gpu/host1x/syncpt.h +++ b/drivers/gpu/host1x/syncpt.h @@ -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 diff --git a/drivers/pinctrl/pinconf-generic.c b/drivers/pinctrl/pinconf-generic.c index d5bf9fae2ddd..a87439ee4cdd 100644 --- a/drivers/pinctrl/pinconf-generic.c +++ b/drivers/pinctrl/pinconf-generic.c @@ -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 diff --git a/include/linux/pinctrl/pinconf-generic.h b/include/linux/pinctrl/pinconf-generic.h index d921afd5f109..12343caa114e 100644 --- a/include/linux/pinctrl/pinconf-generic.h +++ b/include/linux/pinctrl/pinconf-generic.h @@ -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, |