diff options
Diffstat (limited to 'drivers/gpu')
25 files changed, 740 insertions, 139 deletions
diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 4d341db..a6c92be 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -22,6 +22,7 @@ config DRM_I2C_SIL164 config DRM_I2C_NXP_TDA998X tristate "NXP Semiconductors TDA998X HDMI encoder" default m if DRM_TILCDC + select SND_SOC_HDMI_CODEC if SND_SOC help Support for NXP Semiconductors TDA998X HDMI encoders. diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index f4315bc..9798d40 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -20,6 +20,7 @@ #include <linux/module.h> #include <linux/irq.h> #include <sound/asoundef.h> +#include <sound/hdmi-codec.h> #include <drm/drmP.h> #include <drm/drm_atomic_helper.h> @@ -30,6 +31,11 @@ #define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) +struct tda998x_audio_port { + u8 format; /* AFMT_xxx */ + u8 config; /* AP value */ +}; + struct tda998x_priv { struct i2c_client *cec; struct i2c_client *hdmi; @@ -41,7 +47,10 @@ struct tda998x_priv { u8 vip_cntrl_0; u8 vip_cntrl_1; u8 vip_cntrl_2; - struct tda998x_encoder_params params; + struct tda998x_audio_params audio_params; + + struct platform_device *audio_pdev; + struct mutex audio_mutex; wait_queue_head_t wq_edid; volatile int wq_edid_wait; @@ -53,6 +62,8 @@ struct tda998x_priv { struct drm_encoder encoder; struct drm_connector connector; + + struct tda998x_audio_port audio_port[2]; }; #define conn_to_tda998x_priv(x) \ @@ -666,26 +677,16 @@ tda998x_write_if(struct tda998x_priv *priv, u8 bit, u16 addr, reg_set(priv, REG_DIP_IF_FLAGS, bit); } -static void -tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p) +static int tda998x_write_aif(struct tda998x_priv *priv, + struct hdmi_audio_infoframe *cea) { union hdmi_infoframe frame; - hdmi_audio_infoframe_init(&frame.audio); - - frame.audio.channels = p->audio_frame[1] & 0x07; - frame.audio.channel_allocation = p->audio_frame[4]; - frame.audio.level_shift_value = (p->audio_frame[5] & 0x78) >> 3; - frame.audio.downmix_inhibit = (p->audio_frame[5] & 0x80) >> 7; - - /* - * L-PCM and IEC61937 compressed audio shall always set sample - * frequency to "refer to stream". For others, see the HDMI - * specification. - */ - frame.audio.sample_frequency = (p->audio_frame[2] & 0x1c) >> 2; + frame.audio = *cea; tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, &frame); + + return 0; } static void @@ -710,20 +711,21 @@ static void tda998x_audio_mute(struct tda998x_priv *priv, bool on) } } -static void +static int tda998x_configure_audio(struct tda998x_priv *priv, - struct drm_display_mode *mode, struct tda998x_encoder_params *p) + struct tda998x_audio_params *params, + unsigned mode_clock) { u8 buf[6], clksel_aip, clksel_fs, cts_n, adiv; u32 n; /* Enable audio ports */ - reg_write(priv, REG_ENA_AP, p->audio_cfg); - reg_write(priv, REG_ENA_ACLK, p->audio_clk_cfg); + reg_write(priv, REG_ENA_AP, params->config); /* Set audio input source */ - switch (p->audio_format) { + switch (params->format) { case AFMT_SPDIF: + reg_write(priv, REG_ENA_ACLK, 0); reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_SPDIF); clksel_aip = AIP_CLKSEL_AIP_SPDIF; clksel_fs = AIP_CLKSEL_FS_FS64SPDIF; @@ -731,15 +733,29 @@ tda998x_configure_audio(struct tda998x_priv *priv, break; case AFMT_I2S: + reg_write(priv, REG_ENA_ACLK, 1); reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_I2S); clksel_aip = AIP_CLKSEL_AIP_I2S; clksel_fs = AIP_CLKSEL_FS_ACLK; - cts_n = CTS_N_M(3) | CTS_N_K(3); + switch (params->sample_width) { + case 16: + cts_n = CTS_N_M(3) | CTS_N_K(1); + break; + case 18: + case 20: + case 24: + cts_n = CTS_N_M(3) | CTS_N_K(2); + break; + default: + case 32: + cts_n = CTS_N_M(3) | CTS_N_K(3); + break; + } break; default: - BUG(); - return; + dev_err(&priv->hdmi->dev, "Unsupported I2S format\n"); + return -EINVAL; } reg_write(priv, REG_AIP_CLKSEL, clksel_aip); @@ -755,11 +771,11 @@ tda998x_configure_audio(struct tda998x_priv *priv, * assume 100MHz requires larger divider. */ adiv = AUDIO_DIV_SERCLK_8; - if (mode->clock > 100000) + if (mode_clock > 100000) adiv++; /* AUDIO_DIV_SERCLK_16 */ /* S/PDIF asks for a larger divider */ - if (p->audio_format == AFMT_SPDIF) + if (params->format == AFMT_SPDIF) adiv++; /* AUDIO_DIV_SERCLK_16 or _32 */ reg_write(priv, REG_AUDIO_DIV, adiv); @@ -768,7 +784,7 @@ tda998x_configure_audio(struct tda998x_priv *priv, * This is the approximate value of N, which happens to be * the recommended values for non-coherent clocks. */ - n = 128 * p->audio_sample_rate / 1000; + n = 128 * params->sample_rate / 1000; /* Write the CTS and N values */ buf[0] = 0x44; @@ -786,20 +802,21 @@ tda998x_configure_audio(struct tda998x_priv *priv, reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS); reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS); - /* Write the channel status */ - buf[0] = IEC958_AES0_CON_NOT_COPYRIGHT; - buf[1] = 0x00; - buf[2] = IEC958_AES3_CON_FS_NOTID; - buf[3] = IEC958_AES4_CON_ORIGFS_NOTID | - IEC958_AES4_CON_MAX_WORDLEN_24; + /* Write the channel status + * The REG_CH_STAT_B-registers skip IEC958 AES2 byte, because + * there is a separate register for each I2S wire. + */ + buf[0] = params->status[0]; + buf[1] = params->status[1]; + buf[2] = params->status[3]; + buf[3] = params->status[4]; reg_write_range(priv, REG_CH_STAT_B(0), buf, 4); tda998x_audio_mute(priv, true); msleep(20); tda998x_audio_mute(priv, false); - /* Write the audio information packet */ - tda998x_write_aif(priv, p); + return tda998x_write_aif(priv, ¶ms->cea); } /* DRM encoder functions */ @@ -820,7 +837,7 @@ static void tda998x_encoder_set_config(struct tda998x_priv *priv, VIP_CNTRL_2_SWAP_F(p->swap_f) | (p->mirr_f ? VIP_CNTRL_2_MIRR_F : 0); - priv->params = *p; + priv->audio_params = p->audio_params; } static void tda998x_encoder_dpms(struct drm_encoder *encoder, int mode) @@ -1057,9 +1074,13 @@ tda998x_encoder_mode_set(struct drm_encoder *encoder, tda998x_write_avi(priv, adjusted_mode); - if (priv->params.audio_cfg) - tda998x_configure_audio(priv, adjusted_mode, - &priv->params); + if (priv->audio_params.format != AFMT_UNUSED) { + mutex_lock(&priv->audio_mutex); + tda998x_configure_audio(priv, + &priv->audio_params, + adjusted_mode->clock); + mutex_unlock(&priv->audio_mutex); + } } } @@ -1159,6 +1180,8 @@ static int tda998x_connector_get_modes(struct drm_connector *connector) drm_mode_connector_update_edid_property(connector, edid); n = drm_add_edid_modes(connector, edid); priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid); + drm_edid_to_eld(connector, edid); + kfree(edid); return n; @@ -1180,6 +1203,9 @@ static void tda998x_destroy(struct tda998x_priv *priv) cec_write(priv, REG_CEC_RXSHPDINTENA, 0); reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); + if (priv->audio_pdev) + platform_device_unregister(priv->audio_pdev); + if (priv->hdmi->irq) free_irq(priv->hdmi->irq, priv); @@ -1189,8 +1215,189 @@ static void tda998x_destroy(struct tda998x_priv *priv) i2c_unregister_device(priv->cec); } +static int tda998x_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + int i, ret; + struct tda998x_audio_params audio = { + .sample_width = params->sample_width, + .sample_rate = params->sample_rate, + .cea = params->cea, + }; + + if (!priv->encoder.crtc) + return -ENODEV; + + memcpy(audio.status, params->iec.status, + min(sizeof(audio.status), sizeof(params->iec.status))); + + switch (daifmt->fmt) { + case HDMI_I2S: + if (daifmt->bit_clk_inv || daifmt->frame_clk_inv || + daifmt->bit_clk_master || daifmt->frame_clk_master) { + dev_err(dev, "%s: Bad flags %d %d %d %d\n", __func__, + daifmt->bit_clk_inv, daifmt->frame_clk_inv, + daifmt->bit_clk_master, + daifmt->frame_clk_master); + return -EINVAL; + } + for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++) + if (priv->audio_port[i].format == AFMT_I2S) + audio.config = priv->audio_port[i].config; + audio.format = AFMT_I2S; + break; + case HDMI_SPDIF: + for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++) + if (priv->audio_port[i].format == AFMT_SPDIF) + audio.config = priv->audio_port[i].config; + audio.format = AFMT_SPDIF; + break; + default: + dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt); + return -EINVAL; + } + + if (audio.config == 0) { + dev_err(dev, "%s: No audio configutation found\n", __func__); + return -EINVAL; + } + + mutex_lock(&priv->audio_mutex); + ret = tda998x_configure_audio(priv, + &audio, + priv->encoder.crtc->hwmode.clock); + + if (ret == 0) + priv->audio_params = audio; + mutex_unlock(&priv->audio_mutex); + + return ret; +} + +static void tda998x_audio_shutdown(struct device *dev, void *data) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->audio_mutex); + + reg_write(priv, REG_ENA_AP, 0); + + priv->audio_params.format = AFMT_UNUSED; + + mutex_unlock(&priv->audio_mutex); +} + +int tda998x_audio_digital_mute(struct device *dev, void *data, bool enable) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->audio_mutex); + + tda998x_audio_mute(priv, enable); + + mutex_unlock(&priv->audio_mutex); + return 0; +} + +static int tda998x_audio_get_eld(struct device *dev, void *data, + uint8_t *buf, size_t len) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + struct drm_mode_config *config = &priv->encoder.dev->mode_config; + struct drm_connector *connector; + int ret = -ENODEV; + + mutex_lock(&config->mutex); + list_for_each_entry(connector, &config->connector_list, head) { + if (&priv->encoder == connector->encoder) { + memcpy(buf, connector->eld, + min(sizeof(connector->eld), len)); + ret = 0; + } + } + mutex_unlock(&config->mutex); + + return ret; +} + +static const struct hdmi_codec_ops audio_codec_ops = { + .hw_params = tda998x_audio_hw_params, + .audio_shutdown = tda998x_audio_shutdown, + .digital_mute = tda998x_audio_digital_mute, + .get_eld = tda998x_audio_get_eld, +}; + +static int tda998x_audio_codec_init(struct tda998x_priv *priv, + struct device *dev) +{ + struct hdmi_codec_pdata codec_data = { + .ops = &audio_codec_ops, + .max_i2s_channels = 2, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++) { + if (priv->audio_port[i].format == AFMT_I2S && + priv->audio_port[i].config != 0) + codec_data.i2s = 1; + if (priv->audio_port[i].format == AFMT_SPDIF && + priv->audio_port[i].config != 0) + codec_data.spdif = 1; + } + + priv->audio_pdev = platform_device_register_data( + dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO, + &codec_data, sizeof(codec_data)); + + return PTR_ERR_OR_ZERO(priv->audio_pdev); +} + /* I2C driver functions */ +static int tda998x_get_audio_ports(struct tda998x_priv *priv, + struct device_node *np) +{ + const u32 *port_data; + u32 size; + int i; + + port_data = of_get_property(np, "audio-ports", &size); + if (!port_data) + return 0; + + size /= sizeof(u32); + if (size > 2 * ARRAY_SIZE(priv->audio_port) || size % 2 != 0) { + dev_err(&priv->hdmi->dev, + "Bad number of elements in audio-ports dt-property\n"); + return -EINVAL; + } + + size /= 2; + + for (i = 0; i < size; i++) { + u8 afmt = be32_to_cpup(&port_data[2*i]); + u8 ena_ap = be32_to_cpup(&port_data[2*i+1]); + + if (afmt != AFMT_SPDIF && afmt != AFMT_I2S) { + dev_err(&priv->hdmi->dev, + "Bad audio format %u\n", afmt); + return -EINVAL; + } + + priv->audio_port[i].format = afmt; + priv->audio_port[i].config = ena_ap; + } + + if (priv->audio_port[0].format == priv->audio_port[1].format) { + dev_err(&priv->hdmi->dev, + "There can only be on I2S port and one SPDIF port\n"); + return -EINVAL; + } + return 0; +} + static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) { struct device_node *np = client->dev.of_node; @@ -1304,7 +1511,7 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) if (!np) return 0; /* non-DT */ - /* get the optional video properties */ + /* get the device tree parameters */ ret = of_property_read_u32(np, "video-ports", &video); if (ret == 0) { priv->vip_cntrl_0 = video >> 16; @@ -1312,8 +1519,16 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) priv->vip_cntrl_2 = video; } - return 0; + mutex_init(&priv->audio_mutex); /* Protect access from audio thread */ + ret = tda998x_get_audio_ports(priv, np); + if (ret) + goto fail; + + if (priv->audio_port[0].format != AFMT_UNUSED) + tda998x_audio_codec_init(priv, &client->dev); + + return 0; fail: /* if encoder_init fails, the encoder slave is never registered, * so cleanup here: diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 58cd551..d625a82 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -9,5 +9,5 @@ sun4i-tcon-y += sun4i_dotclock.o obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o sun4i-tcon.o obj-$(CONFIG_DRM_SUN4I) += sun4i_backend.o - +obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c index 3ab5604..91a7022 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.c +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -217,6 +217,51 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, } EXPORT_SYMBOL(sun4i_backend_update_layer_buffer); +static int sun4i_backend_init_sat(struct device *dev) { + struct sun4i_backend *backend = dev_get_drvdata(dev); + int ret; + + backend->sat_reset = devm_reset_control_get(dev, "sat"); + if (IS_ERR(backend->sat_reset)) { + dev_err(dev, "Couldn't get the SAT reset line\n"); + return PTR_ERR(backend->sat_reset); + } + + ret = reset_control_deassert(backend->sat_reset); + if (ret) { + dev_err(dev, "Couldn't deassert the SAT reset line\n"); + return ret; + } + + backend->sat_clk = devm_clk_get(dev, "sat"); + if (IS_ERR(backend->sat_clk)) { + dev_err(dev, "Couldn't get our SAT clock\n"); + ret = PTR_ERR(backend->sat_clk); + goto err_assert_reset; + } + + ret = clk_prepare_enable(backend->sat_clk); + if (ret) { + dev_err(dev, "Couldn't enable the SAT clock\n"); + return ret; + } + + return 0; + +err_assert_reset: + reset_control_assert(backend->sat_reset); + return ret; +} + +static int sun4i_backend_free_sat(struct device *dev) { + struct sun4i_backend *backend = dev_get_drvdata(dev); + + clk_disable_unprepare(backend->sat_clk); + reset_control_assert(backend->sat_reset); + + return 0; +} + static struct regmap_config sun4i_backend_regmap_config = { .reg_bits = 32, .val_bits = 32, @@ -291,6 +336,15 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, } clk_prepare_enable(backend->ram_clk); + if (of_device_is_compatible(dev->of_node, + "allwinner,sun8i-a33-display-backend")) { + ret = sun4i_backend_init_sat(dev); + if (ret) { + dev_err(dev, "Couldn't init SAT resources\n"); + goto err_disable_ram_clk; + } + } + /* Reset the registers */ for (i = 0x800; i < 0x1000; i += 4) regmap_write(backend->regs, i, 0); @@ -306,6 +360,8 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, return 0; +err_disable_ram_clk: + clk_disable_unprepare(backend->ram_clk); err_disable_mod_clk: clk_disable_unprepare(backend->mod_clk); err_disable_bus_clk: @@ -320,6 +376,10 @@ static void sun4i_backend_unbind(struct device *dev, struct device *master, { struct sun4i_backend *backend = dev_get_drvdata(dev); + if (of_device_is_compatible(dev->of_node, + "allwinner,sun8i-a33-display-backend")) + sun4i_backend_free_sat(dev); + clk_disable_unprepare(backend->ram_clk); clk_disable_unprepare(backend->mod_clk); clk_disable_unprepare(backend->bus_clk); @@ -345,6 +405,7 @@ static int sun4i_backend_remove(struct platform_device *pdev) static const struct of_device_id sun4i_backend_of_table[] = { { .compatible = "allwinner,sun5i-a13-display-backend" }, + { .compatible = "allwinner,sun8i-a33-display-backend" }, { } }; MODULE_DEVICE_TABLE(of, sun4i_backend_of_table); diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.h b/drivers/gpu/drm/sun4i/sun4i_backend.h index 7070bb3..e007186 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.h +++ b/drivers/gpu/drm/sun4i/sun4i_backend.h @@ -146,6 +146,9 @@ struct sun4i_backend { struct clk *bus_clk; struct clk *mod_clk; struct clk *ram_clk; + + struct clk *sat_clk; + struct reset_control *sat_reset; }; void sun4i_backend_apply_color_correction(struct sun4i_backend *backend); diff --git a/drivers/gpu/drm/sun4i/sun4i_dotclock.c b/drivers/gpu/drm/sun4i/sun4i_dotclock.c index 5b34631..4332da4 100644 --- a/drivers/gpu/drm/sun4i/sun4i_dotclock.c +++ b/drivers/gpu/drm/sun4i/sun4i_dotclock.c @@ -14,6 +14,7 @@ #include <linux/regmap.h> #include "sun4i_tcon.h" +#include "sun4i_dotclock.h" struct sun4i_dclk { struct clk_hw hw; diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index 8913c15..9059e3e 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -200,13 +200,14 @@ static const struct component_master_ops sun4i_drv_master_ops = { static bool sun4i_drv_node_is_frontend(struct device_node *node) { - return of_device_is_compatible(node, - "allwinner,sun5i-a13-display-frontend"); + return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") || + of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend"); } static bool sun4i_drv_node_is_tcon(struct device_node *node) { - return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon"); + return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") || + of_device_is_compatible(node, "allwinner,sun8i-a33-tcon"); } static int compare_of(struct device *dev, void *data) @@ -258,8 +259,8 @@ static int sun4i_drv_add_endpoints(struct device *dev, } /* - * If the node is our TCON, the first port is used for our - * panel, and will not be part of the + * If the node is our TCON, the first port is used for + * panel or bridges, and will not be part of the * component framework. */ if (sun4i_drv_node_is_tcon(node)) { @@ -321,6 +322,7 @@ static int sun4i_drv_remove(struct platform_device *pdev) static const struct of_device_id sun4i_drv_of_table[] = { { .compatible = "allwinner,sun5i-a13-display-engine" }, + { .compatible = "allwinner,sun8i-a33-display-engine" }, { } }; MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c index 70688fe..8b6ce619 100644 --- a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c @@ -15,6 +15,7 @@ #include <drm/drmP.h> #include "sun4i_drv.h" +#include "sun4i_framebuffer.h" static void sun4i_de_output_poll_changed(struct drm_device *drm) { diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c index f5bbac6..c3ff10f 100644 --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c @@ -19,6 +19,7 @@ #include "sun4i_drv.h" #include "sun4i_tcon.h" +#include "sun4i_rgb.h" struct sun4i_rgb { struct drm_connector connector; @@ -151,7 +152,14 @@ static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) DRM_DEBUG_DRIVER("Enabling RGB output\n"); - drm_panel_enable(tcon->panel); + if (!IS_ERR(tcon->panel)) { + drm_panel_prepare(tcon->panel); + drm_panel_enable(tcon->panel); + } + + /* encoder->bridge can be NULL; drm_bridge_enable checks for it */ + drm_bridge_enable(encoder->bridge); + sun4i_tcon_channel_enable(tcon, 0); } @@ -164,7 +172,14 @@ static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) DRM_DEBUG_DRIVER("Disabling RGB output\n"); sun4i_tcon_channel_disable(tcon, 0); - drm_panel_disable(tcon->panel); + + /* encoder->bridge can be NULL; drm_bridge_disable checks for it */ + drm_bridge_disable(encoder->bridge); + + if (!IS_ERR(tcon->panel)) { + drm_panel_disable(tcon->panel); + drm_panel_unprepare(tcon->panel); + } } static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder, @@ -203,17 +218,22 @@ int sun4i_rgb_init(struct drm_device *drm) { struct sun4i_drv *drv = drm->dev_private; struct sun4i_tcon *tcon = drv->tcon; + struct drm_encoder *encoder; struct sun4i_rgb *rgb; int ret; - /* If we don't have a panel, there's no point in going on */ - if (IS_ERR(tcon->panel)) - return -ENODEV; - rgb = devm_kzalloc(drm->dev, sizeof(*rgb), GFP_KERNEL); if (!rgb) return -ENOMEM; rgb->drv = drv; + encoder = &rgb->encoder; + + tcon->panel = sun4i_tcon_find_panel(tcon->dev->of_node); + encoder->bridge = sun4i_tcon_find_bridge(tcon->dev->of_node); + if (IS_ERR(tcon->panel) && IS_ERR(encoder->bridge)) { + dev_info(drm->dev, "No panel or bridge found... RGB output disabled\n"); + return 0; + } drm_encoder_helper_add(&rgb->encoder, &sun4i_rgb_enc_helper_funcs); @@ -230,19 +250,38 @@ int sun4i_rgb_init(struct drm_device *drm) /* The RGB encoder can only work with the TCON channel 0 */ rgb->encoder.possible_crtcs = BIT(0); - drm_connector_helper_add(&rgb->connector, - &sun4i_rgb_con_helper_funcs); - ret = drm_connector_init(drm, &rgb->connector, - &sun4i_rgb_con_funcs, - DRM_MODE_CONNECTOR_Unknown); - if (ret) { - dev_err(drm->dev, "Couldn't initialise the rgb connector\n"); - goto err_cleanup_connector; + if (!IS_ERR(tcon->panel)) { + drm_connector_helper_add(&rgb->connector, + &sun4i_rgb_con_helper_funcs); + ret = drm_connector_init(drm, &rgb->connector, + &sun4i_rgb_con_funcs, + DRM_MODE_CONNECTOR_Unknown); + if (ret) { + dev_err(drm->dev, "Couldn't initialise the rgb connector\n"); + goto err_cleanup_connector; + } + + drm_mode_connector_attach_encoder(&rgb->connector, + &rgb->encoder); + + ret = drm_panel_attach(tcon->panel, &rgb->connector); + if (ret) { + dev_err(drm->dev, "Couldn't attach our panel\n"); + goto err_cleanup_connector; + } } - drm_mode_connector_attach_encoder(&rgb->connector, &rgb->encoder); + if (!IS_ERR(encoder->bridge)) { + encoder->bridge->encoder = &rgb->encoder; - drm_panel_attach(tcon->panel, &rgb->connector); + ret = drm_bridge_attach(drm, encoder->bridge); + if (ret) { + dev_err(drm->dev, "Couldn't attach our bridge\n"); + goto err_cleanup_connector; + } + } else { + encoder->bridge = NULL; + } return 0; diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 652385f..cadacb5 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -59,11 +59,13 @@ void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel) regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, SUN4I_TCON0_CTL_TCON_ENABLE, 0); clk_disable_unprepare(tcon->dclk); - } else if (channel == 1) { - regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, - SUN4I_TCON1_CTL_TCON_ENABLE, 0); - clk_disable_unprepare(tcon->sclk1); + return; } + + WARN_ON(!tcon->has_channel_1); + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_TCON_ENABLE, 0); + clk_disable_unprepare(tcon->sclk1); } EXPORT_SYMBOL(sun4i_tcon_channel_disable); @@ -75,12 +77,14 @@ void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel) SUN4I_TCON0_CTL_TCON_ENABLE, SUN4I_TCON0_CTL_TCON_ENABLE); clk_prepare_enable(tcon->dclk); - } else if (channel == 1) { - regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, - SUN4I_TCON1_CTL_TCON_ENABLE, - SUN4I_TCON1_CTL_TCON_ENABLE); - clk_prepare_enable(tcon->sclk1); + return; } + + WARN_ON(!tcon->has_channel_1); + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_TCON_ENABLE, + SUN4I_TCON1_CTL_TCON_ENABLE); + clk_prepare_enable(tcon->sclk1); } EXPORT_SYMBOL(sun4i_tcon_channel_enable); @@ -198,6 +202,8 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, u8 clk_delay; u32 val; + WARN_ON(!tcon->has_channel_1); + /* Adjust clock delay */ clk_delay = sun4i_tcon_get_clk_delay(mode, 1); regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, @@ -321,10 +327,12 @@ static int sun4i_tcon_init_clocks(struct device *dev, return PTR_ERR(tcon->sclk0); } - tcon->sclk1 = devm_clk_get(dev, "tcon-ch1"); - if (IS_ERR(tcon->sclk1)) { - dev_err(dev, "Couldn't get the TCON channel 1 clock\n"); - return PTR_ERR(tcon->sclk1); + if (tcon->has_channel_1) { + tcon->sclk1 = devm_clk_get(dev, "tcon-ch1"); + if (IS_ERR(tcon->sclk1)) { + dev_err(dev, "Couldn't get the TCON channel 1 clock\n"); + return PTR_ERR(tcon->sclk1); + } } return sun4i_dclk_create(dev, tcon); @@ -374,10 +382,8 @@ static int sun4i_tcon_init_regmap(struct device *dev, res = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs = devm_ioremap_resource(dev, res); - if (IS_ERR(regs)) { - dev_err(dev, "Couldn't map the TCON registers\n"); + if (IS_ERR(regs)) return PTR_ERR(regs); - } tcon->regs = devm_regmap_init_mmio(dev, regs, &sun4i_tcon_regmap_config); @@ -398,7 +404,7 @@ static int sun4i_tcon_init_regmap(struct device *dev, return 0; } -static struct drm_panel *sun4i_tcon_find_panel(struct device_node *node) +struct drm_panel *sun4i_tcon_find_panel(struct device_node *node) { struct device_node *port, *remote, *child; struct device_node *end_node = NULL; @@ -432,6 +438,40 @@ static struct drm_panel *sun4i_tcon_find_panel(struct device_node *node) return of_drm_find_panel(remote) ?: ERR_PTR(-EPROBE_DEFER); } +struct drm_bridge *sun4i_tcon_find_bridge(struct device_node *node) +{ + struct device_node *port, *remote, *child; + struct device_node *end_node = NULL; + + /* Inputs are listed first, then outputs */ + port = of_graph_get_port_by_id(node, 1); + + /* + * Our first output is the RGB interface where the panel will + * be connected. + */ + for_each_child_of_node(port, child) { + u32 reg; + + of_property_read_u32(child, "reg", ®); + if (reg == 0) + end_node = child; + } + + if (!end_node) { + DRM_DEBUG_DRIVER("Missing bridge endpoint\n"); + return ERR_PTR(-ENODEV); + } + + remote = of_graph_get_remote_port_parent(end_node); + if (!remote) { + DRM_DEBUG_DRIVER("Enable to parse remote node\n"); + return ERR_PTR(-EINVAL); + } + + return of_drm_find_bridge(remote) ?: ERR_PTR(-EPROBE_DEFER); +} + static int sun4i_tcon_bind(struct device *dev, struct device *master, void *data) { @@ -446,9 +486,15 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, dev_set_drvdata(dev, tcon); drv->tcon = tcon; tcon->drm = drm; + tcon->dev = dev; - if (of_device_is_compatible(dev->of_node, "allwinner,sun5i-a13-tcon")) + if (of_device_is_compatible(dev->of_node, "allwinner,sun5i-a13-tcon")) { tcon->has_mux = true; + tcon->has_channel_1 = true; + } else { + tcon->has_mux = false; + tcon->has_channel_1 = false; + } tcon->lcd_rst = devm_reset_control_get(dev, "lcd"); if (IS_ERR(tcon->lcd_rst)) { @@ -484,12 +530,6 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, goto err_free_clocks; } - tcon->panel = sun4i_tcon_find_panel(dev->of_node); - if (IS_ERR(tcon->panel)) { - dev_info(dev, "No panel found... RGB output disabled\n"); - return 0; - } - ret = sun4i_rgb_init(drm); if (ret < 0) goto err_free_clocks; @@ -519,19 +559,22 @@ static struct component_ops sun4i_tcon_ops = { static int sun4i_tcon_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; + struct drm_bridge *bridge; struct drm_panel *panel; /* - * The panel is not ready. + * Neither the bridge or the panel is ready. * Defer the probe. */ panel = sun4i_tcon_find_panel(node); + bridge = sun4i_tcon_find_bridge(node); /* * If we don't have a panel endpoint, just go on */ - if (PTR_ERR(panel) == -EPROBE_DEFER) { - DRM_DEBUG_DRIVER("Still waiting for our panel. Deferring...\n"); + if ((PTR_ERR(panel) == -EPROBE_DEFER) && + (PTR_ERR(bridge) == -EPROBE_DEFER)) { + DRM_DEBUG_DRIVER("Still waiting for our panel/bridge. Deferring...\n"); return -EPROBE_DEFER; } @@ -547,6 +590,7 @@ static int sun4i_tcon_remove(struct platform_device *pdev) static const struct of_device_id sun4i_tcon_of_table[] = { { .compatible = "allwinner,sun5i-a13-tcon" }, + { .compatible = "allwinner,sun8i-a33-tcon" }, { } }; MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index 0e0b11d..12bd489 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -143,6 +143,7 @@ #define SUN4I_TCON_MAX_CHANNELS 2 struct sun4i_tcon { + struct device *dev; struct drm_device *drm; struct regmap *regs; @@ -163,8 +164,13 @@ struct sun4i_tcon { bool has_mux; struct drm_panel *panel; + + bool has_channel_1; }; +struct drm_bridge *sun4i_tcon_find_bridge(struct device_node *node); +struct drm_panel *sun4i_tcon_find_panel(struct device_node *node); + /* Global Control */ void sun4i_tcon_disable(struct sun4i_tcon *tcon); void sun4i_tcon_enable(struct sun4i_tcon *tcon); diff --git a/drivers/gpu/drm/sun4i/sun6i_drc.c b/drivers/gpu/drm/sun4i/sun6i_drc.c new file mode 100644 index 0000000..bf6d846 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun6i_drc.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2016 Free Electrons + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +struct sun6i_drc { + struct clk *bus_clk; + struct clk *mod_clk; + struct reset_control *reset; +}; + +static int sun6i_drc_bind(struct device *dev, struct device *master, + void *data) +{ + struct sun6i_drc *drc; + int ret; + + drc = devm_kzalloc(dev, sizeof(*drc), GFP_KERNEL); + if (!drc) + return -ENOMEM; + dev_set_drvdata(dev, drc); + + drc->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(drc->reset)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(drc->reset); + } + + ret = reset_control_deassert(drc->reset); + if (ret) { + dev_err(dev, "Couldn't deassert our reset line\n"); + return ret; + } + + drc->bus_clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(drc->bus_clk)) { + dev_err(dev, "Couldn't get our bus clock\n"); + ret = PTR_ERR(drc->bus_clk); + goto err_assert_reset; + } + clk_prepare_enable(drc->bus_clk); + + drc->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(drc->mod_clk)) { + dev_err(dev, "Couldn't get our mod clock\n"); + ret = PTR_ERR(drc->mod_clk); + goto err_disable_bus_clk; + } + clk_prepare_enable(drc->mod_clk); + + return 0; + +err_disable_bus_clk: + clk_disable_unprepare(drc->bus_clk); +err_assert_reset: + reset_control_assert(drc->reset); + return ret; +} + +static void sun6i_drc_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun6i_drc *drc = dev_get_drvdata(dev); + + clk_disable_unprepare(drc->mod_clk); + clk_disable_unprepare(drc->bus_clk); + reset_control_assert(drc->reset); +} + +static struct component_ops sun6i_drc_ops = { + .bind = sun6i_drc_bind, + .unbind = sun6i_drc_unbind, +}; + +static int sun6i_drc_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun6i_drc_ops); +} + +static int sun6i_drc_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun6i_drc_ops); + + return 0; +} + +static const struct of_device_id sun6i_drc_of_table[] = { + { .compatible = "allwinner,sun8i-a33-drc" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun6i_drc_of_table); + +static struct platform_driver sun6i_drc_platform_driver = { + .probe = sun6i_drc_probe, + .remove = sun6i_drc_remove, + .driver = { + .name = "sun6i-drc", + .of_match_table = sun6i_drc_of_table, + }, +}; +module_platform_driver(sun6i_drc_platform_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A31 Dynamic Range Control (DRC) Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c index 25d6b22..2087689 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c @@ -69,6 +69,7 @@ static void set_scanout(struct drm_crtc *crtc, struct drm_framebuffer *fb) struct drm_gem_cma_object *gem; unsigned int depth, bpp; dma_addr_t start, end; + u64 dma_base_and_ceiling; drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp); gem = drm_fb_cma_get_gem_obj(fb, 0); @@ -79,8 +80,13 @@ static void set_scanout(struct drm_crtc *crtc, struct drm_framebuffer *fb) end = start + (crtc->mode.vdisplay * fb->pitches[0]); - tilcdc_write(dev, LCDC_DMA_FB_BASE_ADDR_0_REG, start); - tilcdc_write(dev, LCDC_DMA_FB_CEILING_ADDR_0_REG, end); + /* Write LCDC_DMA_FB_BASE_ADDR_0_REG and LCDC_DMA_FB_CEILING_ADDR_0_REG + * with a single insruction, if available. This should make it more + * unlikely that LCDC would fetch the DMA addresses in the middle of + * an update. + */ + dma_base_and_ceiling = (u64)(end - 1) << 32 | start; + tilcdc_write64(dev, LCDC_DMA_FB_BASE_ADDR_0_REG, dma_base_and_ceiling); if (tilcdc_crtc->curr_fb) drm_flip_work_queue(&tilcdc_crtc->unref_work, @@ -98,6 +104,8 @@ static void tilcdc_crtc_enable_irqs(struct drm_device *dev) if (priv->rev == 1) { tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_V1_UNDERFLOW_INT_ENA); + tilcdc_set(dev, LCDC_DMA_CTRL_REG, + LCDC_V1_END_OF_FRAME_INT_ENA); } else { tilcdc_write(dev, LCDC_INT_ENABLE_SET_REG, LCDC_V2_UNDERFLOW_INT_ENA | diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c index 4405e4b..f8892e9 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c @@ -33,6 +33,20 @@ static LIST_HEAD(module_list); +static const u32 tilcdc_rev1_formats[] = { DRM_FORMAT_RGB565 }; + +static const u32 tilcdc_straight_formats[] = { DRM_FORMAT_RGB565, + DRM_FORMAT_BGR888, + DRM_FORMAT_XBGR8888 }; + +static const u32 tilcdc_crossed_formats[] = { DRM_FORMAT_BGR565, + DRM_FORMAT_RGB888, + DRM_FORMAT_XRGB8888 }; + +static const u32 tilcdc_legacy_formats[] = { DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_XRGB8888 }; + void tilcdc_module_init(struct tilcdc_module *mod, const char *name, const struct tilcdc_module_ops *funcs) { @@ -226,7 +240,6 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags) struct platform_device *pdev = dev->platformdev; struct device_node *node = pdev->dev.of_node; struct tilcdc_drm_private *priv; - struct tilcdc_module *mod; struct resource *res; u32 bpp = 0; int ret; @@ -318,6 +331,37 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags) pm_runtime_put_sync(dev->dev); + if (priv->rev == 1) { + DBG("Revision 1 LCDC supports only RGB565 format"); + priv->pixelformats = tilcdc_rev1_formats; + priv->num_pixelformats = ARRAY_SIZE(tilcdc_rev1_formats); + bpp = 16; + } else { + const char *str = "\0"; + + of_property_read_string(node, "blue-and-red-wiring", &str); + if (0 == strcmp(str, "crossed")) { + DBG("Configured for crossed blue and red wires"); + priv->pixelformats = tilcdc_crossed_formats; + priv->num_pixelformats = + ARRAY_SIZE(tilcdc_crossed_formats); + bpp = 32; /* Choose bpp with RGB support for fbdef */ + } else if (0 == strcmp(str, "straight")) { + DBG("Configured for straight blue and red wires"); + priv->pixelformats = tilcdc_straight_formats; + priv->num_pixelformats = + ARRAY_SIZE(tilcdc_straight_formats); + bpp = 16; /* Choose bpp with RGB support for fbdef */ + } else { + DBG("Blue and red wiring '%s' unknown, use legacy mode", + str); + priv->pixelformats = tilcdc_legacy_formats; + priv->num_pixelformats = + ARRAY_SIZE(tilcdc_legacy_formats); + bpp = 16; /* This is just a guess */ + } + } + ret = modeset_init(dev); if (ret < 0) { dev_err(dev->dev, "failed to initialize mode setting\n"); @@ -331,7 +375,7 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags) if (ret < 0) goto fail_mode_config_cleanup; - ret = tilcdc_add_external_encoders(dev, &bpp); + ret = tilcdc_add_external_encoders(dev); if (ret < 0) goto fail_component_cleanup; } @@ -354,15 +398,6 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags) goto fail_vblank_cleanup; } - list_for_each_entry(mod, &module_list, list) { - DBG("%s: preferred_bpp: %d", mod->name, mod->preferred_bpp); - bpp = mod->preferred_bpp; - if (bpp > 0) - break; - } - - drm_helper_disable_unused_functions(dev); - drm_mode_config_reset(dev); priv->fbdev = drm_fbdev_cma_init(dev, bpp, diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.h b/drivers/gpu/drm/tilcdc/tilcdc_drv.h index 13001df..a6e5e6d 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.h +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.h @@ -65,6 +65,10 @@ struct tilcdc_drm_private { */ uint32_t max_width; + /* Supported pixel formats */ + const uint32_t *pixelformats; + uint32_t num_pixelformats; + /* The context for pm susped/resume cycle is stored here */ struct drm_atomic_state *saved_state; @@ -112,7 +116,6 @@ struct tilcdc_module { const char *name; struct list_head list; const struct tilcdc_module_ops *funcs; - unsigned int preferred_bpp; }; void tilcdc_module_init(struct tilcdc_module *mod, const char *name, diff --git a/drivers/gpu/drm/tilcdc/tilcdc_external.c b/drivers/gpu/drm/tilcdc/tilcdc_external.c index ad3db4d..68e8950 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_external.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_external.c @@ -52,7 +52,7 @@ static int tilcdc_external_mode_valid(struct drm_connector *connector, return MODE_OK; } -static int tilcdc_add_external_encoder(struct drm_device *dev, int *bpp, +static int tilcdc_add_external_encoder(struct drm_device *dev, struct drm_connector *connector) { struct tilcdc_drm_private *priv = dev->dev_private; @@ -64,7 +64,6 @@ static int tilcdc_add_external_encoder(struct drm_device *dev, int *bpp, /* Only tda998x is supported at the moment. */ tilcdc_crtc_set_simulate_vesa_sync(priv->crtc, true); tilcdc_crtc_set_panel_info(priv->crtc, &panel_info_tda998x); - *bpp = panel_info_tda998x.bpp; connector_funcs = devm_kzalloc(dev->dev, sizeof(*connector_funcs), GFP_KERNEL); @@ -94,7 +93,7 @@ static int tilcdc_add_external_encoder(struct drm_device *dev, int *bpp, return 0; } -int tilcdc_add_external_encoders(struct drm_device *dev, int *bpp) +int tilcdc_add_external_encoders(struct drm_device *dev) { struct tilcdc_drm_private *priv = dev->dev_private; struct drm_connector *connector; @@ -108,7 +107,7 @@ int tilcdc_add_external_encoders(struct drm_device *dev, int *bpp) if (connector == priv->connectors[i]) found = true; if (!found) { - ret = tilcdc_add_external_encoder(dev, bpp, connector); + ret = tilcdc_add_external_encoder(dev, connector); if (ret) return ret; } @@ -154,7 +153,7 @@ int tilcdc_get_external_components(struct device *dev, while ((ep = of_graph_get_next_endpoint(dev->of_node, ep))) { node = of_graph_get_remote_port_parent(ep); - if (!node && !of_device_is_available(node)) { + if (!node || !of_device_is_available(node)) { of_node_put(node); continue; } diff --git a/drivers/gpu/drm/tilcdc/tilcdc_external.h b/drivers/gpu/drm/tilcdc/tilcdc_external.h index 6aabe27..c700e0c 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_external.h +++ b/drivers/gpu/drm/tilcdc/tilcdc_external.h @@ -18,7 +18,7 @@ #ifndef __TILCDC_EXTERNAL_H__ #define __TILCDC_EXTERNAL_H__ -int tilcdc_add_external_encoders(struct drm_device *dev, int *bpp); +int tilcdc_add_external_encoders(struct drm_device *dev); void tilcdc_remove_external_encoders(struct drm_device *dev); int tilcdc_get_external_components(struct device *dev, struct component_match **match); diff --git a/drivers/gpu/drm/tilcdc/tilcdc_panel.c b/drivers/gpu/drm/tilcdc/tilcdc_panel.c index 4ac1d25..7b36509 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_panel.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_panel.c @@ -397,8 +397,6 @@ static int panel_probe(struct platform_device *pdev) goto fail_timings; } - mod->preferred_bpp = panel_mod->info->bpp; - return 0; fail_timings: diff --git a/drivers/gpu/drm/tilcdc/tilcdc_plane.c b/drivers/gpu/drm/tilcdc/tilcdc_plane.c index 41911e3..74c65fa 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_plane.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_plane.c @@ -24,10 +24,6 @@ #include "tilcdc_drv.h" -static const u32 tilcdc_formats[] = { DRM_FORMAT_RGB565, - DRM_FORMAT_RGB888, - DRM_FORMAT_XRGB8888 }; - static struct drm_plane_funcs tilcdc_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, @@ -114,12 +110,13 @@ static const struct drm_plane_helper_funcs plane_helper_funcs = { int tilcdc_plane_init(struct drm_device *dev, struct drm_plane *plane) { + struct tilcdc_drm_private *priv = dev->dev_private; int ret; ret = drm_plane_init(dev, plane, 1, &tilcdc_plane_funcs, - tilcdc_formats, - ARRAY_SIZE(tilcdc_formats), + priv->pixelformats, + priv->num_pixelformats, true); if (ret) { dev_err(dev->dev, "Failed to initialize plane: %d\n", ret); diff --git a/drivers/gpu/drm/tilcdc/tilcdc_regs.h b/drivers/gpu/drm/tilcdc/tilcdc_regs.h index 1bf5e25..f57c0d6 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_regs.h +++ b/drivers/gpu/drm/tilcdc/tilcdc_regs.h @@ -119,6 +119,20 @@ static inline void tilcdc_write(struct drm_device *dev, u32 reg, u32 data) iowrite32(data, priv->mmio + reg); } +static inline void tilcdc_write64(struct drm_device *dev, u32 reg, u64 data) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + volatile void __iomem *addr = priv->mmio + reg; + +#ifdef iowrite64 + iowrite64(data, addr); +#else + __iowmb(); + /* This compiles to strd (=64-bit write) on ARM7 */ + *(volatile u64 __force *)addr = __cpu_to_le64(data); +#endif +} + static inline u32 tilcdc_read(struct drm_device *dev, u32 reg) { struct tilcdc_drm_private *priv = dev->dev_private; diff --git a/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c index 741c7b5..c6a70da 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c @@ -327,8 +327,6 @@ static int tfp410_probe(struct platform_device *pdev) goto fail; } - mod->preferred_bpp = dvi_info.bpp; - i2c_node = of_find_node_by_phandle(i2c_phandle); if (!i2c_node) { dev_err(&pdev->dev, "could not get i2c bus node\n"); diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 8fc2b73..2682f07 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -163,14 +163,6 @@ int vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id, int vblank_lines; int ret = 0; - /* - * XXX Doesn't work well in interlaced mode yet, partially due - * to problems in vc4 kms or drm core interlaced mode handling, - * so disable for now in interlaced mode. - */ - if (mode->flags & DRM_MODE_FLAG_INTERLACE) - return ret; - /* preempt_disable_rt() should go right here in PREEMPT_RT patchset. */ /* Get optional system timestamp before query. */ @@ -191,10 +183,15 @@ int vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id, /* Vertical position of hvs composed scanline. */ *vpos = VC4_GET_FIELD(val, SCALER_DISPSTATX_LINE); + *hpos = 0; - /* No hpos info available. */ - if (hpos) - *hpos = 0; + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + *vpos /= 2; + + /* Use hpos to correct for field offset in interlaced mode. */ + if (VC4_GET_FIELD(val, SCALER_DISPSTATX_FRAME_COUNT) % 2) + *hpos += mode->crtc_htotal / 2; + } /* This is the offset we need for translating hvs -> pv scanout pos. */ fifo_lines = vc4_crtc->cob_size / mode->crtc_hdisplay; @@ -217,8 +214,6 @@ int vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id, * position of the PV. */ *vpos -= fifo_lines + 1; - if (mode->flags & DRM_MODE_FLAG_INTERLACE) - *vpos /= 2; ret |= DRM_SCANOUTPOS_ACCURATE; return ret; @@ -480,6 +475,9 @@ static void vc4_crtc_disable(struct drm_crtc *crtc) int ret; require_hvs_enabled(dev); + /* Disable vblank irq handling before crtc is disabled. */ + drm_crtc_vblank_off(crtc); + CRTC_WRITE(PV_V_CONTROL, CRTC_READ(PV_V_CONTROL) & ~PV_VCONTROL_VIDEN); ret = wait_for(!(CRTC_READ(PV_V_CONTROL) & PV_VCONTROL_VIDEN), 1); @@ -530,6 +528,33 @@ static void vc4_crtc_enable(struct drm_crtc *crtc) /* Turn on the pixel valve, which will emit the vstart signal. */ CRTC_WRITE(PV_V_CONTROL, CRTC_READ(PV_V_CONTROL) | PV_VCONTROL_VIDEN); + + /* Enable vblank irq handling after crtc is started. */ + drm_crtc_vblank_on(crtc); +} + +static bool vc4_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* Do not allow doublescan modes from user space */ + if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN) { + DRM_DEBUG_KMS("[CRTC:%d] Doublescan mode rejected.\n", + crtc->base.id); + return false; + } + + /* + * Interlaced video modes got CRTC_INTERLACE_HALVE_V applied when + * coming from user space. We don't want this, as it screws up + * vblank timestamping, so fix it up. + */ + drm_mode_set_crtcinfo(adjusted_mode, 0); + + DRM_DEBUG_KMS("[CRTC:%d] adjusted_mode :\n", crtc->base.id); + drm_mode_debug_printmodeline(adjusted_mode); + + return true; } static int vc4_crtc_atomic_check(struct drm_crtc *crtc, @@ -819,6 +844,7 @@ static const struct drm_crtc_helper_funcs vc4_crtc_helper_funcs = { .mode_set_nofb = vc4_crtc_mode_set_nofb, .disable = vc4_crtc_disable, .enable = vc4_crtc_enable, + .mode_fixup = vc4_crtc_mode_fixup, .atomic_check = vc4_crtc_atomic_check, .atomic_flush = vc4_crtc_atomic_flush, }; diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index 275fedb..1e1f6b8 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -340,9 +340,20 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) } } +static bool vc4_dpi_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + if (adjusted_mode->flags & DRM_MODE_FLAG_INTERLACE) + return false; + + return true; +} + static const struct drm_encoder_helper_funcs vc4_dpi_encoder_helper_funcs = { .disable = vc4_dpi_encoder_disable, .enable = vc4_dpi_encoder_enable, + .mode_fixup = vc4_dpi_encoder_mode_fixup, }; static const struct of_device_id vc4_dpi_dt_match[] = { diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c index 6155e8a..27c52ec 100644 --- a/drivers/gpu/drm/vc4/vc4_gem.c +++ b/drivers/gpu/drm/vc4/vc4_gem.c @@ -419,10 +419,6 @@ again: vc4_flush_caches(dev); - /* Disable the binner's pre-loaded overflow memory address */ - V3D_WRITE(V3D_BPOA, 0); - V3D_WRITE(V3D_BPOS, 0); - /* Either put the job in the binner if it uses the binner, or * immediately move it to the to-be-rendered queue. */ diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 4452f36..68ad106 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -208,10 +208,35 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector) return ret; } +/* + * drm_helper_probe_single_connector_modes() applies drm_mode_set_crtcinfo to + * all modes with flag CRTC_INTERLACE_HALVE_V. We don't want this, as it + * screws up vblank timestamping for interlaced modes, so fix it up. + */ +static int vc4_hdmi_connector_probe_modes(struct drm_connector *connector, + uint32_t maxX, uint32_t maxY) +{ + struct drm_display_mode *mode; + int count; + + count = drm_helper_probe_single_connector_modes(connector, maxX, maxY); + if (count == 0) + return 0; + + DRM_DEBUG_KMS("[CONNECTOR:%d:%s] probed adapted modes :\n", + connector->base.id, connector->name); + list_for_each_entry(mode, &connector->modes, head) { + drm_mode_set_crtcinfo(mode, 0); + drm_mode_debug_printmodeline(mode); + } + + return count; +} + static const struct drm_connector_funcs vc4_hdmi_connector_funcs = { .dpms = drm_atomic_helper_connector_dpms, .detect = vc4_hdmi_connector_detect, - .fill_modes = drm_helper_probe_single_connector_modes, + .fill_modes = vc4_hdmi_connector_probe_modes, .destroy = vc4_hdmi_connector_destroy, .reset = drm_atomic_helper_connector_reset, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, @@ -246,7 +271,7 @@ static struct drm_connector *vc4_hdmi_connector_init(struct drm_device *dev, connector->polled = (DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT); - connector->interlace_allowed = 0; + connector->interlace_allowed = 1; connector->doublescan_allowed = 0; drm_mode_connector_attach_encoder(connector, encoder); |