diff options
Diffstat (limited to 'drivers/gpu/drm/tegra/sor.c')
-rw-r--r-- | drivers/gpu/drm/tegra/sor.c | 717 |
1 files changed, 485 insertions, 232 deletions
diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 757c6e8..74d0540 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,13 +1372,16 @@ 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; } static const struct drm_connector_helper_funcs tegra_sor_connector_helper_funcs = { .get_modes = tegra_sor_connector_get_modes, .mode_valid = tegra_sor_connector_mode_valid, - .best_encoder = tegra_output_connector_best_encoder, }; static const struct drm_encoder_funcs tegra_sor_encoder_funcs = { @@ -1141,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 @@ -1192,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); @@ -1219,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; @@ -1326,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); @@ -1375,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); @@ -1394,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; @@ -1478,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) @@ -1578,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) { @@ -1590,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; } @@ -1752,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) @@ -1762,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); @@ -1877,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); @@ -2002,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; @@ -2012,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; } @@ -2022,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); @@ -2196,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); @@ -2210,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); @@ -2324,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 = { @@ -2336,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 = { @@ -2346,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[] = { @@ -2435,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); @@ -2449,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); @@ -2470,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; @@ -2481,8 +2682,6 @@ static int tegra_sor_probe(struct platform_device *pdev) goto remove; } - platform_set_drvdata(pdev, sor); - return 0; remove: @@ -2498,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", @@ -2516,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, |