From e12c2f645557708932e15afdf77f5965549cf156 Mon Sep 17 00:00:00 2001 From: Archit Taneja Date: Sat, 9 Jan 2016 14:24:54 +0530 Subject: drm/i2c: adv7511: Convert to drm_bridge We don't want to use the old i2c slave encoder interface anymore. Remove that and make the i2c driver create a drm_bridge entity instead. Converting to bridges helps because the kms drivers don't need to exract encoder slave ops from this driver and use it within their own encoder/connector ops. The driver now creates its own connector when a kms driver attaches itself to the bridge. Therefore, kms drivers don't need to create their own connectors anymore. The old encoder slave ops are now used by the new bridge and connector entities. The of_node member in drm_bridge is accessible only when CONFIG_OF is enabled. The driver anyway only works only when OF is available. Make the driver depend on OF in its Kconfig. Signed-off-by: Archit Taneja diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 22c7ed6..8bb0697 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -3,6 +3,7 @@ menu "I2C encoder or helper chips" config DRM_I2C_ADV7511 tristate "AV7511 encoder" + depends on OF select REGMAP_I2C help Support for the Analog Device ADV7511(W) and ADV7513 HDMI encoders. diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index a02112b..c2642f9 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -14,9 +14,10 @@ #include #include +#include +#include #include #include -#include #include "adv7511.h" @@ -36,7 +37,8 @@ struct adv7511 { bool edid_read; wait_queue_head_t wq; - struct drm_encoder *encoder; + struct drm_bridge bridge; + struct drm_connector connector; bool embedded_sync; enum adv7511_sync_polarity vsync_polarity; @@ -48,11 +50,6 @@ struct adv7511 { struct gpio_desc *gpio_pd; }; -static struct adv7511 *encoder_to_adv7511(struct drm_encoder *encoder) -{ - return to_encoder_slave(encoder)->slave_priv; -} - /* ADI recommended values for proper operation. */ static const struct reg_sequence adv7511_fixed_registers[] = { { 0x98, 0x03 }, @@ -446,8 +443,8 @@ static int adv7511_irq_process(struct adv7511 *adv7511) regmap_write(adv7511->regmap, ADV7511_REG_INT(0), irq0); regmap_write(adv7511->regmap, ADV7511_REG_INT(1), irq1); - if (irq0 & ADV7511_INT0_HPD && adv7511->encoder) - drm_helper_hpd_irq_event(adv7511->encoder->dev); + if (irq0 & ADV7511_INT0_HPD && adv7511->bridge.encoder) + drm_helper_hpd_irq_event(adv7511->connector.dev); if (irq0 & ADV7511_INT0_EDID_READY || irq1 & ADV7511_INT1_DDC_ERROR) { adv7511->edid_read = true; @@ -563,13 +560,12 @@ static int adv7511_get_edid_block(void *data, u8 *buf, unsigned int block, } /* ----------------------------------------------------------------------------- - * Encoder operations + * ADV75xx helpers */ -static int adv7511_get_modes(struct drm_encoder *encoder, +static int adv7511_get_modes(struct adv7511 *adv7511, struct drm_connector *connector) { - struct adv7511 *adv7511 = encoder_to_adv7511(encoder); struct edid *edid; unsigned int count; @@ -606,21 +602,9 @@ static int adv7511_get_modes(struct drm_encoder *encoder, return count; } -static void adv7511_encoder_dpms(struct drm_encoder *encoder, int mode) -{ - struct adv7511 *adv7511 = encoder_to_adv7511(encoder); - - if (mode == DRM_MODE_DPMS_ON) - adv7511_power_on(adv7511); - else - adv7511_power_off(adv7511); -} - static enum drm_connector_status -adv7511_encoder_detect(struct drm_encoder *encoder, - struct drm_connector *connector) +adv7511_detect(struct adv7511 *adv7511, struct drm_connector *connector) { - struct adv7511 *adv7511 = encoder_to_adv7511(encoder); enum drm_connector_status status; unsigned int val; bool hpd; @@ -644,7 +628,7 @@ adv7511_encoder_detect(struct drm_encoder *encoder, if (status == connector_status_connected && hpd && adv7511->powered) { regcache_mark_dirty(adv7511->regmap); adv7511_power_on(adv7511); - adv7511_get_modes(encoder, connector); + adv7511_get_modes(adv7511, connector); if (adv7511->status == connector_status_connected) status = connector_status_disconnected; } else { @@ -658,8 +642,8 @@ adv7511_encoder_detect(struct drm_encoder *encoder, return status; } -static int adv7511_encoder_mode_valid(struct drm_encoder *encoder, - struct drm_display_mode *mode) +static int adv7511_mode_valid(struct adv7511 *adv7511, + struct drm_display_mode *mode) { if (mode->clock > 165000) return MODE_CLOCK_HIGH; @@ -667,11 +651,10 @@ static int adv7511_encoder_mode_valid(struct drm_encoder *encoder, return MODE_OK; } -static void adv7511_encoder_mode_set(struct drm_encoder *encoder, - struct drm_display_mode *mode, - struct drm_display_mode *adj_mode) +static void adv7511_mode_set(struct adv7511 *adv7511, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) { - struct adv7511 *adv7511 = encoder_to_adv7511(encoder); unsigned int low_refresh_rate; unsigned int hsync_polarity = 0; unsigned int vsync_polarity = 0; @@ -762,12 +745,111 @@ static void adv7511_encoder_mode_set(struct drm_encoder *encoder, adv7511->f_tmds = mode->clock; } -static const struct drm_encoder_slave_funcs adv7511_encoder_funcs = { - .dpms = adv7511_encoder_dpms, - .mode_valid = adv7511_encoder_mode_valid, - .mode_set = adv7511_encoder_mode_set, - .detect = adv7511_encoder_detect, - .get_modes = adv7511_get_modes, +/* Connector funcs */ +static struct adv7511 *connector_to_adv7511(struct drm_connector *connector) +{ + return container_of(connector, struct adv7511, connector); +} + +static int adv7511_connector_get_modes(struct drm_connector *connector) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv7511_get_modes(adv, connector); +} + +static enum drm_mode_status +adv7511_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv7511_mode_valid(adv, mode); +} + +static struct drm_connector_helper_funcs adv7511_connector_helper_funcs = { + .get_modes = adv7511_connector_get_modes, + .mode_valid = adv7511_connector_mode_valid, +}; + +static enum drm_connector_status +adv7511_connector_detect(struct drm_connector *connector, bool force) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv7511_detect(adv, connector); +} + +static struct drm_connector_funcs adv7511_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = adv7511_connector_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +/* Bridge funcs */ +static struct adv7511 *bridge_to_adv7511(struct drm_bridge *bridge) +{ + return container_of(bridge, struct adv7511, bridge); +} + +static void adv7511_bridge_enable(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_power_on(adv); +} + +static void adv7511_bridge_disable(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_power_off(adv); +} + +static void adv7511_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_mode_set(adv, mode, adj_mode); +} + +static int adv7511_bridge_attach(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + adv->connector.polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(bridge->dev, &adv->connector, + &adv7511_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + drm_connector_helper_add(&adv->connector, + &adv7511_connector_helper_funcs); + drm_mode_connector_attach_encoder(&adv->connector, bridge->encoder); + + return ret; +} + +static struct drm_bridge_funcs adv7511_bridge_funcs = { + .enable = adv7511_bridge_enable, + .disable = adv7511_bridge_disable, + .mode_set = adv7511_bridge_mode_set, + .attach = adv7511_bridge_attach, }; /* ----------------------------------------------------------------------------- @@ -944,6 +1026,15 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) adv7511_set_link_config(adv7511, &link_config); + adv7511->bridge.funcs = &adv7511_bridge_funcs; + adv7511->bridge.of_node = dev->of_node; + + ret = drm_bridge_add(&adv7511->bridge); + if (ret) { + dev_err(dev, "failed to add adv7511 bridge\n"); + goto err_i2c_unregister_device; + } + return 0; err_i2c_unregister_device: @@ -956,6 +1047,8 @@ static int adv7511_remove(struct i2c_client *i2c) { struct adv7511 *adv7511 = i2c_get_clientdata(i2c); + drm_bridge_remove(&adv7511->bridge); + i2c_unregister_device(adv7511->i2c_edid); kfree(adv7511->edid); @@ -963,20 +1056,6 @@ static int adv7511_remove(struct i2c_client *i2c) return 0; } -static int adv7511_encoder_init(struct i2c_client *i2c, struct drm_device *dev, - struct drm_encoder_slave *encoder) -{ - - struct adv7511 *adv7511 = i2c_get_clientdata(i2c); - - encoder->slave_priv = adv7511; - encoder->slave_funcs = &adv7511_encoder_funcs; - - adv7511->encoder = &encoder->base; - - return 0; -} - static const struct i2c_device_id adv7511_i2c_ids[] = { { "adv7511", 0 }, { "adv7511w", 0 }, @@ -993,31 +1072,17 @@ static const struct of_device_id adv7511_of_ids[] = { }; MODULE_DEVICE_TABLE(of, adv7511_of_ids); -static struct drm_i2c_encoder_driver adv7511_driver = { - .i2c_driver = { - .driver = { - .name = "adv7511", - .of_match_table = adv7511_of_ids, - }, - .id_table = adv7511_i2c_ids, - .probe = adv7511_probe, - .remove = adv7511_remove, +static struct i2c_driver adv7511_driver = { + .driver = { + .name = "adv7511", + .of_match_table = adv7511_of_ids, }, - - .encoder_init = adv7511_encoder_init, + .id_table = adv7511_i2c_ids, + .probe = adv7511_probe, + .remove = adv7511_remove, }; -static int __init adv7511_init(void) -{ - return drm_i2c_encoder_register(THIS_MODULE, &adv7511_driver); -} -module_init(adv7511_init); - -static void __exit adv7511_exit(void) -{ - drm_i2c_encoder_unregister(&adv7511_driver); -} -module_exit(adv7511_exit); +module_i2c_driver(adv7511_driver); MODULE_AUTHOR("Lars-Peter Clausen "); MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver"); -- cgit v0.10.2 From c582778900425d0cf2a5687a84b0125fee797d32 Mon Sep 17 00:00:00 2001 From: Archit Taneja Date: Fri, 17 Jun 2016 13:30:35 +0530 Subject: drm/i2c: adv7511: Move to bridge folder The driver has been converted to use drm_bridge instead of drm_i2c_slave_encoder. We can now move it to the bridge folder. Create a separate folder since we already have a couple of files and expect more when we support audio and ADV7533. Rename the driver to adv7511_drv.c. This will come in handy later when the driver module will need to be built from multiple object files. Signed-off-by: Archit Taneja diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index a1419214..fd7bd7d 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -60,4 +60,6 @@ config DRM_SII902X source "drivers/gpu/drm/bridge/analogix/Kconfig" +source "drivers/gpu/drm/bridge/adv7511/Kconfig" + endmenu diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index bfec9f8..d08bdd1 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_SII902X) += sii902x.o obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ +obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/ diff --git a/drivers/gpu/drm/bridge/adv7511/Kconfig b/drivers/gpu/drm/bridge/adv7511/Kconfig new file mode 100644 index 0000000..222c6cc --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/Kconfig @@ -0,0 +1,7 @@ +config DRM_I2C_ADV7511 + tristate "AV7511 encoder" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + help + Support for the Analog Device ADV7511(W) and ADV7513 HDMI encoders. diff --git a/drivers/gpu/drm/bridge/adv7511/Makefile b/drivers/gpu/drm/bridge/adv7511/Makefile new file mode 100644 index 0000000..692f83a --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/Makefile @@ -0,0 +1,2 @@ +adv7511-y := adv7511_drv.o +obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h new file mode 100644 index 0000000..38515b3 --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -0,0 +1,289 @@ +/* + * Analog Devices ADV7511 HDMI transmitter driver + * + * Copyright 2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef __DRM_I2C_ADV7511_H__ +#define __DRM_I2C_ADV7511_H__ + +#include + +#define ADV7511_REG_CHIP_REVISION 0x00 +#define ADV7511_REG_N0 0x01 +#define ADV7511_REG_N1 0x02 +#define ADV7511_REG_N2 0x03 +#define ADV7511_REG_SPDIF_FREQ 0x04 +#define ADV7511_REG_CTS_AUTOMATIC1 0x05 +#define ADV7511_REG_CTS_AUTOMATIC2 0x06 +#define ADV7511_REG_CTS_MANUAL0 0x07 +#define ADV7511_REG_CTS_MANUAL1 0x08 +#define ADV7511_REG_CTS_MANUAL2 0x09 +#define ADV7511_REG_AUDIO_SOURCE 0x0a +#define ADV7511_REG_AUDIO_CONFIG 0x0b +#define ADV7511_REG_I2S_CONFIG 0x0c +#define ADV7511_REG_I2S_WIDTH 0x0d +#define ADV7511_REG_AUDIO_SUB_SRC0 0x0e +#define ADV7511_REG_AUDIO_SUB_SRC1 0x0f +#define ADV7511_REG_AUDIO_SUB_SRC2 0x10 +#define ADV7511_REG_AUDIO_SUB_SRC3 0x11 +#define ADV7511_REG_AUDIO_CFG1 0x12 +#define ADV7511_REG_AUDIO_CFG2 0x13 +#define ADV7511_REG_AUDIO_CFG3 0x14 +#define ADV7511_REG_I2C_FREQ_ID_CFG 0x15 +#define ADV7511_REG_VIDEO_INPUT_CFG1 0x16 +#define ADV7511_REG_CSC_UPPER(x) (0x18 + (x) * 2) +#define ADV7511_REG_CSC_LOWER(x) (0x19 + (x) * 2) +#define ADV7511_REG_SYNC_DECODER(x) (0x30 + (x)) +#define ADV7511_REG_DE_GENERATOR (0x35 + (x)) +#define ADV7511_REG_PIXEL_REPETITION 0x3b +#define ADV7511_REG_VIC_MANUAL 0x3c +#define ADV7511_REG_VIC_SEND 0x3d +#define ADV7511_REG_VIC_DETECTED 0x3e +#define ADV7511_REG_AUX_VIC_DETECTED 0x3f +#define ADV7511_REG_PACKET_ENABLE0 0x40 +#define ADV7511_REG_POWER 0x41 +#define ADV7511_REG_STATUS 0x42 +#define ADV7511_REG_EDID_I2C_ADDR 0x43 +#define ADV7511_REG_PACKET_ENABLE1 0x44 +#define ADV7511_REG_PACKET_I2C_ADDR 0x45 +#define ADV7511_REG_DSD_ENABLE 0x46 +#define ADV7511_REG_VIDEO_INPUT_CFG2 0x48 +#define ADV7511_REG_INFOFRAME_UPDATE 0x4a +#define ADV7511_REG_GC(x) (0x4b + (x)) /* 0x4b - 0x51 */ +#define ADV7511_REG_AVI_INFOFRAME_VERSION 0x52 +#define ADV7511_REG_AVI_INFOFRAME_LENGTH 0x53 +#define ADV7511_REG_AVI_INFOFRAME_CHECKSUM 0x54 +#define ADV7511_REG_AVI_INFOFRAME(x) (0x55 + (x)) /* 0x55 - 0x6f */ +#define ADV7511_REG_AUDIO_INFOFRAME_VERSION 0x70 +#define ADV7511_REG_AUDIO_INFOFRAME_LENGTH 0x71 +#define ADV7511_REG_AUDIO_INFOFRAME_CHECKSUM 0x72 +#define ADV7511_REG_AUDIO_INFOFRAME(x) (0x73 + (x)) /* 0x73 - 0x7c */ +#define ADV7511_REG_INT_ENABLE(x) (0x94 + (x)) +#define ADV7511_REG_INT(x) (0x96 + (x)) +#define ADV7511_REG_INPUT_CLK_DIV 0x9d +#define ADV7511_REG_PLL_STATUS 0x9e +#define ADV7511_REG_HDMI_POWER 0xa1 +#define ADV7511_REG_HDCP_HDMI_CFG 0xaf +#define ADV7511_REG_AN(x) (0xb0 + (x)) /* 0xb0 - 0xb7 */ +#define ADV7511_REG_HDCP_STATUS 0xb8 +#define ADV7511_REG_BCAPS 0xbe +#define ADV7511_REG_BKSV(x) (0xc0 + (x)) /* 0xc0 - 0xc3 */ +#define ADV7511_REG_EDID_SEGMENT 0xc4 +#define ADV7511_REG_DDC_STATUS 0xc8 +#define ADV7511_REG_EDID_READ_CTRL 0xc9 +#define ADV7511_REG_BSTATUS(x) (0xca + (x)) /* 0xca - 0xcb */ +#define ADV7511_REG_TIMING_GEN_SEQ 0xd0 +#define ADV7511_REG_POWER2 0xd6 +#define ADV7511_REG_HSYNC_PLACEMENT_MSB 0xfa + +#define ADV7511_REG_SYNC_ADJUSTMENT(x) (0xd7 + (x)) /* 0xd7 - 0xdc */ +#define ADV7511_REG_TMDS_CLOCK_INV 0xde +#define ADV7511_REG_ARC_CTRL 0xdf +#define ADV7511_REG_CEC_I2C_ADDR 0xe1 +#define ADV7511_REG_CEC_CTRL 0xe2 +#define ADV7511_REG_CHIP_ID_HIGH 0xf5 +#define ADV7511_REG_CHIP_ID_LOW 0xf6 + +#define ADV7511_CSC_ENABLE BIT(7) +#define ADV7511_CSC_UPDATE_MODE BIT(5) + +#define ADV7511_INT0_HPD BIT(7) +#define ADV7511_INT0_VSYNC BIT(5) +#define ADV7511_INT0_AUDIO_FIFO_FULL BIT(4) +#define ADV7511_INT0_EDID_READY BIT(2) +#define ADV7511_INT0_HDCP_AUTHENTICATED BIT(1) + +#define ADV7511_INT1_DDC_ERROR BIT(7) +#define ADV7511_INT1_BKSV BIT(6) +#define ADV7511_INT1_CEC_TX_READY BIT(5) +#define ADV7511_INT1_CEC_TX_ARBIT_LOST BIT(4) +#define ADV7511_INT1_CEC_TX_RETRY_TIMEOUT BIT(3) +#define ADV7511_INT1_CEC_RX_READY3 BIT(2) +#define ADV7511_INT1_CEC_RX_READY2 BIT(1) +#define ADV7511_INT1_CEC_RX_READY1 BIT(0) + +#define ADV7511_ARC_CTRL_POWER_DOWN BIT(0) + +#define ADV7511_CEC_CTRL_POWER_DOWN BIT(0) + +#define ADV7511_POWER_POWER_DOWN BIT(6) + +#define ADV7511_HDMI_CFG_MODE_MASK 0x2 +#define ADV7511_HDMI_CFG_MODE_DVI 0x0 +#define ADV7511_HDMI_CFG_MODE_HDMI 0x2 + +#define ADV7511_AUDIO_SELECT_I2C 0x0 +#define ADV7511_AUDIO_SELECT_SPDIF 0x1 +#define ADV7511_AUDIO_SELECT_DSD 0x2 +#define ADV7511_AUDIO_SELECT_HBR 0x3 +#define ADV7511_AUDIO_SELECT_DST 0x4 + +#define ADV7511_I2S_SAMPLE_LEN_16 0x2 +#define ADV7511_I2S_SAMPLE_LEN_20 0x3 +#define ADV7511_I2S_SAMPLE_LEN_18 0x4 +#define ADV7511_I2S_SAMPLE_LEN_22 0x5 +#define ADV7511_I2S_SAMPLE_LEN_19 0x8 +#define ADV7511_I2S_SAMPLE_LEN_23 0x9 +#define ADV7511_I2S_SAMPLE_LEN_24 0xb +#define ADV7511_I2S_SAMPLE_LEN_17 0xc +#define ADV7511_I2S_SAMPLE_LEN_21 0xd + +#define ADV7511_SAMPLE_FREQ_44100 0x0 +#define ADV7511_SAMPLE_FREQ_48000 0x2 +#define ADV7511_SAMPLE_FREQ_32000 0x3 +#define ADV7511_SAMPLE_FREQ_88200 0x8 +#define ADV7511_SAMPLE_FREQ_96000 0xa +#define ADV7511_SAMPLE_FREQ_176400 0xc +#define ADV7511_SAMPLE_FREQ_192000 0xe + +#define ADV7511_STATUS_POWER_DOWN_POLARITY BIT(7) +#define ADV7511_STATUS_HPD BIT(6) +#define ADV7511_STATUS_MONITOR_SENSE BIT(5) +#define ADV7511_STATUS_I2S_32BIT_MODE BIT(3) + +#define ADV7511_PACKET_ENABLE_N_CTS BIT(8+6) +#define ADV7511_PACKET_ENABLE_AUDIO_SAMPLE BIT(8+5) +#define ADV7511_PACKET_ENABLE_AVI_INFOFRAME BIT(8+4) +#define ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME BIT(8+3) +#define ADV7511_PACKET_ENABLE_GC BIT(7) +#define ADV7511_PACKET_ENABLE_SPD BIT(6) +#define ADV7511_PACKET_ENABLE_MPEG BIT(5) +#define ADV7511_PACKET_ENABLE_ACP BIT(4) +#define ADV7511_PACKET_ENABLE_ISRC BIT(3) +#define ADV7511_PACKET_ENABLE_GM BIT(2) +#define ADV7511_PACKET_ENABLE_SPARE2 BIT(1) +#define ADV7511_PACKET_ENABLE_SPARE1 BIT(0) + +#define ADV7511_REG_POWER2_HPD_SRC_MASK 0xc0 +#define ADV7511_REG_POWER2_HPD_SRC_BOTH 0x00 +#define ADV7511_REG_POWER2_HPD_SRC_HPD 0x40 +#define ADV7511_REG_POWER2_HPD_SRC_CEC 0x80 +#define ADV7511_REG_POWER2_HPD_SRC_NONE 0xc0 +#define ADV7511_REG_POWER2_TDMS_ENABLE BIT(4) +#define ADV7511_REG_POWER2_GATE_INPUT_CLK BIT(0) + +#define ADV7511_LOW_REFRESH_RATE_NONE 0x0 +#define ADV7511_LOW_REFRESH_RATE_24HZ 0x1 +#define ADV7511_LOW_REFRESH_RATE_25HZ 0x2 +#define ADV7511_LOW_REFRESH_RATE_30HZ 0x3 + +#define ADV7511_AUDIO_CFG3_LEN_MASK 0x0f +#define ADV7511_I2C_FREQ_ID_CFG_RATE_MASK 0xf0 + +#define ADV7511_AUDIO_SOURCE_I2S 0 +#define ADV7511_AUDIO_SOURCE_SPDIF 1 + +#define ADV7511_I2S_FORMAT_I2S 0 +#define ADV7511_I2S_FORMAT_RIGHT_J 1 +#define ADV7511_I2S_FORMAT_LEFT_J 2 + +#define ADV7511_PACKET(p, x) ((p) * 0x20 + (x)) +#define ADV7511_PACKET_SDP(x) ADV7511_PACKET(0, x) +#define ADV7511_PACKET_MPEG(x) ADV7511_PACKET(1, x) +#define ADV7511_PACKET_ACP(x) ADV7511_PACKET(2, x) +#define ADV7511_PACKET_ISRC1(x) ADV7511_PACKET(3, x) +#define ADV7511_PACKET_ISRC2(x) ADV7511_PACKET(4, x) +#define ADV7511_PACKET_GM(x) ADV7511_PACKET(5, x) +#define ADV7511_PACKET_SPARE(x) ADV7511_PACKET(6, x) + +enum adv7511_input_clock { + ADV7511_INPUT_CLOCK_1X, + ADV7511_INPUT_CLOCK_2X, + ADV7511_INPUT_CLOCK_DDR, +}; + +enum adv7511_input_justification { + ADV7511_INPUT_JUSTIFICATION_EVENLY = 0, + ADV7511_INPUT_JUSTIFICATION_RIGHT = 1, + ADV7511_INPUT_JUSTIFICATION_LEFT = 2, +}; + +enum adv7511_input_sync_pulse { + ADV7511_INPUT_SYNC_PULSE_DE = 0, + ADV7511_INPUT_SYNC_PULSE_HSYNC = 1, + ADV7511_INPUT_SYNC_PULSE_VSYNC = 2, + ADV7511_INPUT_SYNC_PULSE_NONE = 3, +}; + +/** + * enum adv7511_sync_polarity - Polarity for the input sync signals + * @ADV7511_SYNC_POLARITY_PASSTHROUGH: Sync polarity matches that of + * the currently configured mode. + * @ADV7511_SYNC_POLARITY_LOW: Sync polarity is low + * @ADV7511_SYNC_POLARITY_HIGH: Sync polarity is high + * + * If the polarity is set to either LOW or HIGH the driver will configure the + * ADV7511 to internally invert the sync signal if required to match the sync + * polarity setting for the currently selected output mode. + * + * If the polarity is set to PASSTHROUGH, the ADV7511 will route the signal + * unchanged. This is used when the upstream graphics core already generates + * the sync signals with the correct polarity. + */ +enum adv7511_sync_polarity { + ADV7511_SYNC_POLARITY_PASSTHROUGH, + ADV7511_SYNC_POLARITY_LOW, + ADV7511_SYNC_POLARITY_HIGH, +}; + +/** + * struct adv7511_link_config - Describes adv7511 hardware configuration + * @input_color_depth: Number of bits per color component (8, 10 or 12) + * @input_colorspace: The input colorspace (RGB, YUV444, YUV422) + * @input_clock: The input video clock style (1x, 2x, DDR) + * @input_style: The input component arrangement variant + * @input_justification: Video input format bit justification + * @clock_delay: Clock delay for the input clock (in ps) + * @embedded_sync: Video input uses BT.656-style embedded sync + * @sync_pulse: Select the sync pulse + * @vsync_polarity: vsync input signal configuration + * @hsync_polarity: hsync input signal configuration + */ +struct adv7511_link_config { + unsigned int input_color_depth; + enum hdmi_colorspace input_colorspace; + enum adv7511_input_clock input_clock; + unsigned int input_style; + enum adv7511_input_justification input_justification; + + int clock_delay; + + bool embedded_sync; + enum adv7511_input_sync_pulse sync_pulse; + enum adv7511_sync_polarity vsync_polarity; + enum adv7511_sync_polarity hsync_polarity; +}; + +/** + * enum adv7511_csc_scaling - Scaling factor for the ADV7511 CSC + * @ADV7511_CSC_SCALING_1: CSC results are not scaled + * @ADV7511_CSC_SCALING_2: CSC results are scaled by a factor of two + * @ADV7511_CSC_SCALING_4: CSC results are scalled by a factor of four + */ +enum adv7511_csc_scaling { + ADV7511_CSC_SCALING_1 = 0, + ADV7511_CSC_SCALING_2 = 1, + ADV7511_CSC_SCALING_4 = 2, +}; + +/** + * struct adv7511_video_config - Describes adv7511 hardware configuration + * @csc_enable: Whether to enable color space conversion + * @csc_scaling_factor: Color space conversion scaling factor + * @csc_coefficents: Color space conversion coefficents + * @hdmi_mode: Whether to use HDMI or DVI output mode + * @avi_infoframe: HDMI infoframe + */ +struct adv7511_video_config { + bool csc_enable; + enum adv7511_csc_scaling csc_scaling_factor; + const uint16_t *csc_coefficents; + + bool hdmi_mode; + struct hdmi_avi_infoframe avi_infoframe; +}; + +#endif /* __DRM_I2C_ADV7511_H__ */ diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c new file mode 100644 index 0000000..c2642f9 --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -0,0 +1,1089 @@ +/* + * Analog Devices ADV7511 HDMI transmitter driver + * + * Copyright 2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "adv7511.h" + +struct adv7511 { + struct i2c_client *i2c_main; + struct i2c_client *i2c_edid; + + struct regmap *regmap; + struct regmap *packet_memory_regmap; + enum drm_connector_status status; + bool powered; + + unsigned int f_tmds; + + unsigned int current_edid_segment; + uint8_t edid_buf[256]; + bool edid_read; + + wait_queue_head_t wq; + struct drm_bridge bridge; + struct drm_connector connector; + + bool embedded_sync; + enum adv7511_sync_polarity vsync_polarity; + enum adv7511_sync_polarity hsync_polarity; + bool rgb; + + struct edid *edid; + + struct gpio_desc *gpio_pd; +}; + +/* ADI recommended values for proper operation. */ +static const struct reg_sequence adv7511_fixed_registers[] = { + { 0x98, 0x03 }, + { 0x9a, 0xe0 }, + { 0x9c, 0x30 }, + { 0x9d, 0x61 }, + { 0xa2, 0xa4 }, + { 0xa3, 0xa4 }, + { 0xe0, 0xd0 }, + { 0xf9, 0x00 }, + { 0x55, 0x02 }, +}; + +/* ----------------------------------------------------------------------------- + * Register access + */ + +static const uint8_t adv7511_register_defaults[] = { + 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00 */ + 0x00, 0x00, 0x01, 0x0e, 0xbc, 0x18, 0x01, 0x13, + 0x25, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10 */ + 0x46, 0x62, 0x04, 0xa8, 0x00, 0x00, 0x1c, 0x84, + 0x1c, 0xbf, 0x04, 0xa8, 0x1e, 0x70, 0x02, 0x1e, /* 20 */ + 0x00, 0x00, 0x04, 0xa8, 0x08, 0x12, 0x1b, 0xac, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 30 */ + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xb0, + 0x00, 0x50, 0x90, 0x7e, 0x79, 0x70, 0x00, 0x00, /* 40 */ + 0x00, 0xa8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x0d, 0x00, 0x00, 0x00, 0x00, /* 50 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 60 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 70 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 80 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, /* 90 */ + 0x0b, 0x02, 0x00, 0x18, 0x5a, 0x60, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x80, 0x08, 0x04, 0x00, 0x00, /* a0 */ + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* b0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* c0 */ + 0x00, 0x03, 0x00, 0x00, 0x02, 0x00, 0x01, 0x04, + 0x30, 0xff, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, /* d0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, + 0x80, 0x75, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, /* e0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x11, 0x00, /* f0 */ + 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static bool adv7511_register_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADV7511_REG_CHIP_REVISION: + case ADV7511_REG_SPDIF_FREQ: + case ADV7511_REG_CTS_AUTOMATIC1: + case ADV7511_REG_CTS_AUTOMATIC2: + case ADV7511_REG_VIC_DETECTED: + case ADV7511_REG_VIC_SEND: + case ADV7511_REG_AUX_VIC_DETECTED: + case ADV7511_REG_STATUS: + case ADV7511_REG_GC(1): + case ADV7511_REG_INT(0): + case ADV7511_REG_INT(1): + case ADV7511_REG_PLL_STATUS: + case ADV7511_REG_AN(0): + case ADV7511_REG_AN(1): + case ADV7511_REG_AN(2): + case ADV7511_REG_AN(3): + case ADV7511_REG_AN(4): + case ADV7511_REG_AN(5): + case ADV7511_REG_AN(6): + case ADV7511_REG_AN(7): + case ADV7511_REG_HDCP_STATUS: + case ADV7511_REG_BCAPS: + case ADV7511_REG_BKSV(0): + case ADV7511_REG_BKSV(1): + case ADV7511_REG_BKSV(2): + case ADV7511_REG_BKSV(3): + case ADV7511_REG_BKSV(4): + case ADV7511_REG_DDC_STATUS: + case ADV7511_REG_EDID_READ_CTRL: + case ADV7511_REG_BSTATUS(0): + case ADV7511_REG_BSTATUS(1): + case ADV7511_REG_CHIP_ID_HIGH: + case ADV7511_REG_CHIP_ID_LOW: + return true; + } + + return false; +} + +static const struct regmap_config adv7511_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_RBTREE, + .reg_defaults_raw = adv7511_register_defaults, + .num_reg_defaults_raw = ARRAY_SIZE(adv7511_register_defaults), + + .volatile_reg = adv7511_register_volatile, +}; + +/* ----------------------------------------------------------------------------- + * Hardware configuration + */ + +static void adv7511_set_colormap(struct adv7511 *adv7511, bool enable, + const uint16_t *coeff, + unsigned int scaling_factor) +{ + unsigned int i; + + regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(1), + ADV7511_CSC_UPDATE_MODE, ADV7511_CSC_UPDATE_MODE); + + if (enable) { + for (i = 0; i < 12; ++i) { + regmap_update_bits(adv7511->regmap, + ADV7511_REG_CSC_UPPER(i), + 0x1f, coeff[i] >> 8); + regmap_write(adv7511->regmap, + ADV7511_REG_CSC_LOWER(i), + coeff[i] & 0xff); + } + } + + if (enable) + regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(0), + 0xe0, 0x80 | (scaling_factor << 5)); + else + regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(0), + 0x80, 0x00); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(1), + ADV7511_CSC_UPDATE_MODE, 0); +} + +static int adv7511_packet_enable(struct adv7511 *adv7511, unsigned int packet) +{ + if (packet & 0xff) + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0, + packet, 0xff); + + if (packet & 0xff00) { + packet >>= 8; + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1, + packet, 0xff); + } + + return 0; +} + +static int adv7511_packet_disable(struct adv7511 *adv7511, unsigned int packet) +{ + if (packet & 0xff) + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0, + packet, 0x00); + + if (packet & 0xff00) { + packet >>= 8; + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1, + packet, 0x00); + } + + return 0; +} + +/* Coefficients for adv7511 color space conversion */ +static const uint16_t adv7511_csc_ycbcr_to_rgb[] = { + 0x0734, 0x04ad, 0x0000, 0x1c1b, + 0x1ddc, 0x04ad, 0x1f24, 0x0135, + 0x0000, 0x04ad, 0x087c, 0x1b77, +}; + +static void adv7511_set_config_csc(struct adv7511 *adv7511, + struct drm_connector *connector, + bool rgb) +{ + struct adv7511_video_config config; + bool output_format_422, output_format_ycbcr; + unsigned int mode; + uint8_t infoframe[17]; + + if (adv7511->edid) + config.hdmi_mode = drm_detect_hdmi_monitor(adv7511->edid); + else + config.hdmi_mode = false; + + hdmi_avi_infoframe_init(&config.avi_infoframe); + + config.avi_infoframe.scan_mode = HDMI_SCAN_MODE_UNDERSCAN; + + if (rgb) { + config.csc_enable = false; + config.avi_infoframe.colorspace = HDMI_COLORSPACE_RGB; + } else { + config.csc_scaling_factor = ADV7511_CSC_SCALING_4; + config.csc_coefficents = adv7511_csc_ycbcr_to_rgb; + + if ((connector->display_info.color_formats & + DRM_COLOR_FORMAT_YCRCB422) && + config.hdmi_mode) { + config.csc_enable = false; + config.avi_infoframe.colorspace = + HDMI_COLORSPACE_YUV422; + } else { + config.csc_enable = true; + config.avi_infoframe.colorspace = HDMI_COLORSPACE_RGB; + } + } + + if (config.hdmi_mode) { + mode = ADV7511_HDMI_CFG_MODE_HDMI; + + switch (config.avi_infoframe.colorspace) { + case HDMI_COLORSPACE_YUV444: + output_format_422 = false; + output_format_ycbcr = true; + break; + case HDMI_COLORSPACE_YUV422: + output_format_422 = true; + output_format_ycbcr = true; + break; + default: + output_format_422 = false; + output_format_ycbcr = false; + break; + } + } else { + mode = ADV7511_HDMI_CFG_MODE_DVI; + output_format_422 = false; + output_format_ycbcr = false; + } + + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME); + + adv7511_set_colormap(adv7511, config.csc_enable, + config.csc_coefficents, + config.csc_scaling_factor); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG1, 0x81, + (output_format_422 << 7) | output_format_ycbcr); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_HDCP_HDMI_CFG, + ADV7511_HDMI_CFG_MODE_MASK, mode); + + hdmi_avi_infoframe_pack(&config.avi_infoframe, infoframe, + sizeof(infoframe)); + + /* The AVI infoframe id is not configurable */ + regmap_bulk_write(adv7511->regmap, ADV7511_REG_AVI_INFOFRAME_VERSION, + infoframe + 1, sizeof(infoframe) - 1); + + adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME); +} + +static void adv7511_set_link_config(struct adv7511 *adv7511, + const struct adv7511_link_config *config) +{ + /* + * The input style values documented in the datasheet don't match the + * hardware register field values :-( + */ + static const unsigned int input_styles[4] = { 0, 2, 1, 3 }; + + unsigned int clock_delay; + unsigned int color_depth; + unsigned int input_id; + + clock_delay = (config->clock_delay + 1200) / 400; + color_depth = config->input_color_depth == 8 ? 3 + : (config->input_color_depth == 10 ? 1 : 2); + + /* TODO Support input ID 6 */ + if (config->input_colorspace != HDMI_COLORSPACE_YUV422) + input_id = config->input_clock == ADV7511_INPUT_CLOCK_DDR + ? 5 : 0; + else if (config->input_clock == ADV7511_INPUT_CLOCK_DDR) + input_id = config->embedded_sync ? 8 : 7; + else if (config->input_clock == ADV7511_INPUT_CLOCK_2X) + input_id = config->embedded_sync ? 4 : 3; + else + input_id = config->embedded_sync ? 2 : 1; + + regmap_update_bits(adv7511->regmap, ADV7511_REG_I2C_FREQ_ID_CFG, 0xf, + input_id); + regmap_update_bits(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG1, 0x7e, + (color_depth << 4) | + (input_styles[config->input_style] << 2)); + regmap_write(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG2, + config->input_justification << 3); + regmap_write(adv7511->regmap, ADV7511_REG_TIMING_GEN_SEQ, + config->sync_pulse << 2); + + regmap_write(adv7511->regmap, 0xba, clock_delay << 5); + + adv7511->embedded_sync = config->embedded_sync; + adv7511->hsync_polarity = config->hsync_polarity; + adv7511->vsync_polarity = config->vsync_polarity; + adv7511->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB; +} + +static void adv7511_power_on(struct adv7511 *adv7511) +{ + adv7511->current_edid_segment = -1; + + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, + ADV7511_POWER_POWER_DOWN, 0); + if (adv7511->i2c_main->irq) { + /* + * Documentation says the INT_ENABLE registers are reset in + * POWER_DOWN mode. My 7511w preserved the bits, however. + * Still, let's be safe and stick to the documentation. + */ + regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0), + ADV7511_INT0_EDID_READY); + regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(1), + ADV7511_INT1_DDC_ERROR); + } + + /* + * Per spec it is allowed to pulse the HPD signal to indicate that the + * EDID information has changed. Some monitors do this when they wakeup + * from standby or are enabled. When the HPD goes low the adv7511 is + * reset and the outputs are disabled which might cause the monitor to + * go to standby again. To avoid this we ignore the HPD pin for the + * first few seconds after enabling the output. + */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7511_REG_POWER2_HPD_SRC_MASK, + ADV7511_REG_POWER2_HPD_SRC_NONE); + + /* + * Most of the registers are reset during power down or when HPD is low. + */ + regcache_sync(adv7511->regmap); + + adv7511->powered = true; +} + +static void adv7511_power_off(struct adv7511 *adv7511) +{ + /* TODO: setup additional power down modes */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, + ADV7511_POWER_POWER_DOWN, + ADV7511_POWER_POWER_DOWN); + regcache_mark_dirty(adv7511->regmap); + + adv7511->powered = false; +} + +/* ----------------------------------------------------------------------------- + * Interrupt and hotplug detection + */ + +static bool adv7511_hpd(struct adv7511 *adv7511) +{ + unsigned int irq0; + int ret; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0); + if (ret < 0) + return false; + + if (irq0 & ADV7511_INT0_HPD) { + regmap_write(adv7511->regmap, ADV7511_REG_INT(0), + ADV7511_INT0_HPD); + return true; + } + + return false; +} + +static int adv7511_irq_process(struct adv7511 *adv7511) +{ + unsigned int irq0, irq1; + int ret; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0); + if (ret < 0) + return ret; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(1), &irq1); + if (ret < 0) + return ret; + + regmap_write(adv7511->regmap, ADV7511_REG_INT(0), irq0); + regmap_write(adv7511->regmap, ADV7511_REG_INT(1), irq1); + + if (irq0 & ADV7511_INT0_HPD && adv7511->bridge.encoder) + drm_helper_hpd_irq_event(adv7511->connector.dev); + + if (irq0 & ADV7511_INT0_EDID_READY || irq1 & ADV7511_INT1_DDC_ERROR) { + adv7511->edid_read = true; + + if (adv7511->i2c_main->irq) + wake_up_all(&adv7511->wq); + } + + return 0; +} + +static irqreturn_t adv7511_irq_handler(int irq, void *devid) +{ + struct adv7511 *adv7511 = devid; + int ret; + + ret = adv7511_irq_process(adv7511); + return ret < 0 ? IRQ_NONE : IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * EDID retrieval + */ + +static int adv7511_wait_for_edid(struct adv7511 *adv7511, int timeout) +{ + int ret; + + if (adv7511->i2c_main->irq) { + ret = wait_event_interruptible_timeout(adv7511->wq, + adv7511->edid_read, msecs_to_jiffies(timeout)); + } else { + for (; timeout > 0; timeout -= 25) { + ret = adv7511_irq_process(adv7511); + if (ret < 0) + break; + + if (adv7511->edid_read) + break; + + msleep(25); + } + } + + return adv7511->edid_read ? 0 : -EIO; +} + +static int adv7511_get_edid_block(void *data, u8 *buf, unsigned int block, + size_t len) +{ + struct adv7511 *adv7511 = data; + struct i2c_msg xfer[2]; + uint8_t offset; + unsigned int i; + int ret; + + if (len > 128) + return -EINVAL; + + if (adv7511->current_edid_segment != block / 2) { + unsigned int status; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_DDC_STATUS, + &status); + if (ret < 0) + return ret; + + if (status != 2) { + adv7511->edid_read = false; + regmap_write(adv7511->regmap, ADV7511_REG_EDID_SEGMENT, + block); + ret = adv7511_wait_for_edid(adv7511, 200); + if (ret < 0) + return ret; + } + + /* Break this apart, hopefully more I2C controllers will + * support 64 byte transfers than 256 byte transfers + */ + + xfer[0].addr = adv7511->i2c_edid->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = &offset; + xfer[1].addr = adv7511->i2c_edid->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 64; + xfer[1].buf = adv7511->edid_buf; + + offset = 0; + + for (i = 0; i < 4; ++i) { + ret = i2c_transfer(adv7511->i2c_edid->adapter, xfer, + ARRAY_SIZE(xfer)); + if (ret < 0) + return ret; + else if (ret != 2) + return -EIO; + + xfer[1].buf += 64; + offset += 64; + } + + adv7511->current_edid_segment = block / 2; + } + + if (block % 2 == 0) + memcpy(buf, adv7511->edid_buf, len); + else + memcpy(buf, adv7511->edid_buf + 128, len); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * ADV75xx helpers + */ + +static int adv7511_get_modes(struct adv7511 *adv7511, + struct drm_connector *connector) +{ + struct edid *edid; + unsigned int count; + + /* Reading the EDID only works if the device is powered */ + if (!adv7511->powered) { + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, + ADV7511_POWER_POWER_DOWN, 0); + if (adv7511->i2c_main->irq) { + regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0), + ADV7511_INT0_EDID_READY); + regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(1), + ADV7511_INT1_DDC_ERROR); + } + adv7511->current_edid_segment = -1; + } + + edid = drm_do_get_edid(connector, adv7511_get_edid_block, adv7511); + + if (!adv7511->powered) + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, + ADV7511_POWER_POWER_DOWN, + ADV7511_POWER_POWER_DOWN); + + kfree(adv7511->edid); + adv7511->edid = edid; + if (!edid) + return 0; + + drm_mode_connector_update_edid_property(connector, edid); + count = drm_add_edid_modes(connector, edid); + + adv7511_set_config_csc(adv7511, connector, adv7511->rgb); + + return count; +} + +static enum drm_connector_status +adv7511_detect(struct adv7511 *adv7511, struct drm_connector *connector) +{ + enum drm_connector_status status; + unsigned int val; + bool hpd; + int ret; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_STATUS, &val); + if (ret < 0) + return connector_status_disconnected; + + if (val & ADV7511_STATUS_HPD) + status = connector_status_connected; + else + status = connector_status_disconnected; + + hpd = adv7511_hpd(adv7511); + + /* The chip resets itself when the cable is disconnected, so in case + * there is a pending HPD interrupt and the cable is connected there was + * at least one transition from disconnected to connected and the chip + * has to be reinitialized. */ + if (status == connector_status_connected && hpd && adv7511->powered) { + regcache_mark_dirty(adv7511->regmap); + adv7511_power_on(adv7511); + adv7511_get_modes(adv7511, connector); + if (adv7511->status == connector_status_connected) + status = connector_status_disconnected; + } else { + /* Renable HPD sensing */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7511_REG_POWER2_HPD_SRC_MASK, + ADV7511_REG_POWER2_HPD_SRC_BOTH); + } + + adv7511->status = status; + return status; +} + +static int adv7511_mode_valid(struct adv7511 *adv7511, + struct drm_display_mode *mode) +{ + if (mode->clock > 165000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static void adv7511_mode_set(struct adv7511 *adv7511, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + unsigned int low_refresh_rate; + unsigned int hsync_polarity = 0; + unsigned int vsync_polarity = 0; + + if (adv7511->embedded_sync) { + unsigned int hsync_offset, hsync_len; + unsigned int vsync_offset, vsync_len; + + hsync_offset = adj_mode->crtc_hsync_start - + adj_mode->crtc_hdisplay; + vsync_offset = adj_mode->crtc_vsync_start - + adj_mode->crtc_vdisplay; + hsync_len = adj_mode->crtc_hsync_end - + adj_mode->crtc_hsync_start; + vsync_len = adj_mode->crtc_vsync_end - + adj_mode->crtc_vsync_start; + + /* The hardware vsync generator has a off-by-one bug */ + vsync_offset += 1; + + regmap_write(adv7511->regmap, ADV7511_REG_HSYNC_PLACEMENT_MSB, + ((hsync_offset >> 10) & 0x7) << 5); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(0), + (hsync_offset >> 2) & 0xff); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(1), + ((hsync_offset & 0x3) << 6) | + ((hsync_len >> 4) & 0x3f)); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(2), + ((hsync_len & 0xf) << 4) | + ((vsync_offset >> 6) & 0xf)); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(3), + ((vsync_offset & 0x3f) << 2) | + ((vsync_len >> 8) & 0x3)); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(4), + vsync_len & 0xff); + + hsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PHSYNC); + vsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PVSYNC); + } else { + enum adv7511_sync_polarity mode_hsync_polarity; + enum adv7511_sync_polarity mode_vsync_polarity; + + /** + * If the input signal is always low or always high we want to + * invert or let it passthrough depending on the polarity of the + * current mode. + **/ + if (adj_mode->flags & DRM_MODE_FLAG_NHSYNC) + mode_hsync_polarity = ADV7511_SYNC_POLARITY_LOW; + else + mode_hsync_polarity = ADV7511_SYNC_POLARITY_HIGH; + + if (adj_mode->flags & DRM_MODE_FLAG_NVSYNC) + mode_vsync_polarity = ADV7511_SYNC_POLARITY_LOW; + else + mode_vsync_polarity = ADV7511_SYNC_POLARITY_HIGH; + + if (adv7511->hsync_polarity != mode_hsync_polarity && + adv7511->hsync_polarity != + ADV7511_SYNC_POLARITY_PASSTHROUGH) + hsync_polarity = 1; + + if (adv7511->vsync_polarity != mode_vsync_polarity && + adv7511->vsync_polarity != + ADV7511_SYNC_POLARITY_PASSTHROUGH) + vsync_polarity = 1; + } + + if (mode->vrefresh <= 24000) + low_refresh_rate = ADV7511_LOW_REFRESH_RATE_24HZ; + else if (mode->vrefresh <= 25000) + low_refresh_rate = ADV7511_LOW_REFRESH_RATE_25HZ; + else if (mode->vrefresh <= 30000) + low_refresh_rate = ADV7511_LOW_REFRESH_RATE_30HZ; + else + low_refresh_rate = ADV7511_LOW_REFRESH_RATE_NONE; + + regmap_update_bits(adv7511->regmap, 0xfb, + 0x6, low_refresh_rate << 1); + regmap_update_bits(adv7511->regmap, 0x17, + 0x60, (vsync_polarity << 6) | (hsync_polarity << 5)); + + /* + * TODO Test first order 4:2:2 to 4:4:4 up conversion method, which is + * supposed to give better results. + */ + + adv7511->f_tmds = mode->clock; +} + +/* Connector funcs */ +static struct adv7511 *connector_to_adv7511(struct drm_connector *connector) +{ + return container_of(connector, struct adv7511, connector); +} + +static int adv7511_connector_get_modes(struct drm_connector *connector) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv7511_get_modes(adv, connector); +} + +static enum drm_mode_status +adv7511_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv7511_mode_valid(adv, mode); +} + +static struct drm_connector_helper_funcs adv7511_connector_helper_funcs = { + .get_modes = adv7511_connector_get_modes, + .mode_valid = adv7511_connector_mode_valid, +}; + +static enum drm_connector_status +adv7511_connector_detect(struct drm_connector *connector, bool force) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv7511_detect(adv, connector); +} + +static struct drm_connector_funcs adv7511_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = adv7511_connector_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +/* Bridge funcs */ +static struct adv7511 *bridge_to_adv7511(struct drm_bridge *bridge) +{ + return container_of(bridge, struct adv7511, bridge); +} + +static void adv7511_bridge_enable(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_power_on(adv); +} + +static void adv7511_bridge_disable(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_power_off(adv); +} + +static void adv7511_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_mode_set(adv, mode, adj_mode); +} + +static int adv7511_bridge_attach(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + adv->connector.polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(bridge->dev, &adv->connector, + &adv7511_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + drm_connector_helper_add(&adv->connector, + &adv7511_connector_helper_funcs); + drm_mode_connector_attach_encoder(&adv->connector, bridge->encoder); + + return ret; +} + +static struct drm_bridge_funcs adv7511_bridge_funcs = { + .enable = adv7511_bridge_enable, + .disable = adv7511_bridge_disable, + .mode_set = adv7511_bridge_mode_set, + .attach = adv7511_bridge_attach, +}; + +/* ----------------------------------------------------------------------------- + * Probe & remove + */ + +static int adv7511_parse_dt(struct device_node *np, + struct adv7511_link_config *config) +{ + const char *str; + int ret; + + memset(config, 0, sizeof(*config)); + + of_property_read_u32(np, "adi,input-depth", &config->input_color_depth); + if (config->input_color_depth != 8 && config->input_color_depth != 10 && + config->input_color_depth != 12) + return -EINVAL; + + ret = of_property_read_string(np, "adi,input-colorspace", &str); + if (ret < 0) + return ret; + + if (!strcmp(str, "rgb")) + config->input_colorspace = HDMI_COLORSPACE_RGB; + else if (!strcmp(str, "yuv422")) + config->input_colorspace = HDMI_COLORSPACE_YUV422; + else if (!strcmp(str, "yuv444")) + config->input_colorspace = HDMI_COLORSPACE_YUV444; + else + return -EINVAL; + + ret = of_property_read_string(np, "adi,input-clock", &str); + if (ret < 0) + return ret; + + if (!strcmp(str, "1x")) + config->input_clock = ADV7511_INPUT_CLOCK_1X; + else if (!strcmp(str, "2x")) + config->input_clock = ADV7511_INPUT_CLOCK_2X; + else if (!strcmp(str, "ddr")) + config->input_clock = ADV7511_INPUT_CLOCK_DDR; + else + return -EINVAL; + + if (config->input_colorspace == HDMI_COLORSPACE_YUV422 || + config->input_clock != ADV7511_INPUT_CLOCK_1X) { + ret = of_property_read_u32(np, "adi,input-style", + &config->input_style); + if (ret) + return ret; + + if (config->input_style < 1 || config->input_style > 3) + return -EINVAL; + + ret = of_property_read_string(np, "adi,input-justification", + &str); + if (ret < 0) + return ret; + + if (!strcmp(str, "left")) + config->input_justification = + ADV7511_INPUT_JUSTIFICATION_LEFT; + else if (!strcmp(str, "evenly")) + config->input_justification = + ADV7511_INPUT_JUSTIFICATION_EVENLY; + else if (!strcmp(str, "right")) + config->input_justification = + ADV7511_INPUT_JUSTIFICATION_RIGHT; + else + return -EINVAL; + + } else { + config->input_style = 1; + config->input_justification = ADV7511_INPUT_JUSTIFICATION_LEFT; + } + + of_property_read_u32(np, "adi,clock-delay", &config->clock_delay); + if (config->clock_delay < -1200 || config->clock_delay > 1600) + return -EINVAL; + + config->embedded_sync = of_property_read_bool(np, "adi,embedded-sync"); + + /* Hardcode the sync pulse configurations for now. */ + config->sync_pulse = ADV7511_INPUT_SYNC_PULSE_NONE; + config->vsync_polarity = ADV7511_SYNC_POLARITY_PASSTHROUGH; + config->hsync_polarity = ADV7511_SYNC_POLARITY_PASSTHROUGH; + + return 0; +} + +static const int edid_i2c_addr = 0x7e; +static const int packet_i2c_addr = 0x70; +static const int cec_i2c_addr = 0x78; + +static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + struct adv7511_link_config link_config; + struct adv7511 *adv7511; + struct device *dev = &i2c->dev; + unsigned int val; + int ret; + + if (!dev->of_node) + return -EINVAL; + + adv7511 = devm_kzalloc(dev, sizeof(*adv7511), GFP_KERNEL); + if (!adv7511) + return -ENOMEM; + + adv7511->powered = false; + adv7511->status = connector_status_disconnected; + + ret = adv7511_parse_dt(dev->of_node, &link_config); + if (ret) + return ret; + + /* + * The power down GPIO is optional. If present, toggle it from active to + * inactive to wake up the encoder. + */ + adv7511->gpio_pd = devm_gpiod_get_optional(dev, "pd", GPIOD_OUT_HIGH); + if (IS_ERR(adv7511->gpio_pd)) + return PTR_ERR(adv7511->gpio_pd); + + if (adv7511->gpio_pd) { + mdelay(5); + gpiod_set_value_cansleep(adv7511->gpio_pd, 0); + } + + adv7511->regmap = devm_regmap_init_i2c(i2c, &adv7511_regmap_config); + if (IS_ERR(adv7511->regmap)) + return PTR_ERR(adv7511->regmap); + + ret = regmap_read(adv7511->regmap, ADV7511_REG_CHIP_REVISION, &val); + if (ret) + return ret; + dev_dbg(dev, "Rev. %d\n", val); + + ret = regmap_register_patch(adv7511->regmap, adv7511_fixed_registers, + ARRAY_SIZE(adv7511_fixed_registers)); + if (ret) + return ret; + + regmap_write(adv7511->regmap, ADV7511_REG_EDID_I2C_ADDR, edid_i2c_addr); + regmap_write(adv7511->regmap, ADV7511_REG_PACKET_I2C_ADDR, + packet_i2c_addr); + regmap_write(adv7511->regmap, ADV7511_REG_CEC_I2C_ADDR, cec_i2c_addr); + adv7511_packet_disable(adv7511, 0xffff); + + adv7511->i2c_main = i2c; + adv7511->i2c_edid = i2c_new_dummy(i2c->adapter, edid_i2c_addr >> 1); + if (!adv7511->i2c_edid) + return -ENOMEM; + + if (i2c->irq) { + init_waitqueue_head(&adv7511->wq); + + ret = devm_request_threaded_irq(dev, i2c->irq, NULL, + adv7511_irq_handler, + IRQF_ONESHOT, dev_name(dev), + adv7511); + if (ret) + goto err_i2c_unregister_device; + } + + /* CEC is unused for now */ + regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL, + ADV7511_CEC_CTRL_POWER_DOWN); + + adv7511_power_off(adv7511); + + i2c_set_clientdata(i2c, adv7511); + + adv7511_set_link_config(adv7511, &link_config); + + adv7511->bridge.funcs = &adv7511_bridge_funcs; + adv7511->bridge.of_node = dev->of_node; + + ret = drm_bridge_add(&adv7511->bridge); + if (ret) { + dev_err(dev, "failed to add adv7511 bridge\n"); + goto err_i2c_unregister_device; + } + + return 0; + +err_i2c_unregister_device: + i2c_unregister_device(adv7511->i2c_edid); + + return ret; +} + +static int adv7511_remove(struct i2c_client *i2c) +{ + struct adv7511 *adv7511 = i2c_get_clientdata(i2c); + + drm_bridge_remove(&adv7511->bridge); + + i2c_unregister_device(adv7511->i2c_edid); + + kfree(adv7511->edid); + + return 0; +} + +static const struct i2c_device_id adv7511_i2c_ids[] = { + { "adv7511", 0 }, + { "adv7511w", 0 }, + { "adv7513", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7511_i2c_ids); + +static const struct of_device_id adv7511_of_ids[] = { + { .compatible = "adi,adv7511", }, + { .compatible = "adi,adv7511w", }, + { .compatible = "adi,adv7513", }, + { } +}; +MODULE_DEVICE_TABLE(of, adv7511_of_ids); + +static struct i2c_driver adv7511_driver = { + .driver = { + .name = "adv7511", + .of_match_table = adv7511_of_ids, + }, + .id_table = adv7511_i2c_ids, + .probe = adv7511_probe, + .remove = adv7511_remove, +}; + +module_i2c_driver(adv7511_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 8bb0697..4d341db 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -1,13 +1,6 @@ menu "I2C encoder or helper chips" depends on DRM && DRM_KMS_HELPER && I2C -config DRM_I2C_ADV7511 - tristate "AV7511 encoder" - depends on OF - select REGMAP_I2C - help - Support for the Analog Device ADV7511(W) and ADV7513 HDMI encoders. - config DRM_I2C_CH7006 tristate "Chrontel ch7006 TV encoder" default m if DRM_NOUVEAU diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile index 2c72eb5..43aa33b 100644 --- a/drivers/gpu/drm/i2c/Makefile +++ b/drivers/gpu/drm/i2c/Makefile @@ -1,7 +1,5 @@ ccflags-y := -Iinclude/drm -obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o - ch7006-y := ch7006_drv.o ch7006_mode.o obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c deleted file mode 100644 index c2642f9..0000000 --- a/drivers/gpu/drm/i2c/adv7511.c +++ /dev/null @@ -1,1089 +0,0 @@ -/* - * Analog Devices ADV7511 HDMI transmitter driver - * - * Copyright 2012 Analog Devices Inc. - * - * Licensed under the GPL-2. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "adv7511.h" - -struct adv7511 { - struct i2c_client *i2c_main; - struct i2c_client *i2c_edid; - - struct regmap *regmap; - struct regmap *packet_memory_regmap; - enum drm_connector_status status; - bool powered; - - unsigned int f_tmds; - - unsigned int current_edid_segment; - uint8_t edid_buf[256]; - bool edid_read; - - wait_queue_head_t wq; - struct drm_bridge bridge; - struct drm_connector connector; - - bool embedded_sync; - enum adv7511_sync_polarity vsync_polarity; - enum adv7511_sync_polarity hsync_polarity; - bool rgb; - - struct edid *edid; - - struct gpio_desc *gpio_pd; -}; - -/* ADI recommended values for proper operation. */ -static const struct reg_sequence adv7511_fixed_registers[] = { - { 0x98, 0x03 }, - { 0x9a, 0xe0 }, - { 0x9c, 0x30 }, - { 0x9d, 0x61 }, - { 0xa2, 0xa4 }, - { 0xa3, 0xa4 }, - { 0xe0, 0xd0 }, - { 0xf9, 0x00 }, - { 0x55, 0x02 }, -}; - -/* ----------------------------------------------------------------------------- - * Register access - */ - -static const uint8_t adv7511_register_defaults[] = { - 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00 */ - 0x00, 0x00, 0x01, 0x0e, 0xbc, 0x18, 0x01, 0x13, - 0x25, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10 */ - 0x46, 0x62, 0x04, 0xa8, 0x00, 0x00, 0x1c, 0x84, - 0x1c, 0xbf, 0x04, 0xa8, 0x1e, 0x70, 0x02, 0x1e, /* 20 */ - 0x00, 0x00, 0x04, 0xa8, 0x08, 0x12, 0x1b, 0xac, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 30 */ - 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xb0, - 0x00, 0x50, 0x90, 0x7e, 0x79, 0x70, 0x00, 0x00, /* 40 */ - 0x00, 0xa8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x0d, 0x00, 0x00, 0x00, 0x00, /* 50 */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 60 */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 70 */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 80 */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, /* 90 */ - 0x0b, 0x02, 0x00, 0x18, 0x5a, 0x60, 0x00, 0x00, - 0x00, 0x00, 0x80, 0x80, 0x08, 0x04, 0x00, 0x00, /* a0 */ - 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x14, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* b0 */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* c0 */ - 0x00, 0x03, 0x00, 0x00, 0x02, 0x00, 0x01, 0x04, - 0x30, 0xff, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, /* d0 */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, - 0x80, 0x75, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, /* e0 */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x11, 0x00, /* f0 */ - 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -static bool adv7511_register_volatile(struct device *dev, unsigned int reg) -{ - switch (reg) { - case ADV7511_REG_CHIP_REVISION: - case ADV7511_REG_SPDIF_FREQ: - case ADV7511_REG_CTS_AUTOMATIC1: - case ADV7511_REG_CTS_AUTOMATIC2: - case ADV7511_REG_VIC_DETECTED: - case ADV7511_REG_VIC_SEND: - case ADV7511_REG_AUX_VIC_DETECTED: - case ADV7511_REG_STATUS: - case ADV7511_REG_GC(1): - case ADV7511_REG_INT(0): - case ADV7511_REG_INT(1): - case ADV7511_REG_PLL_STATUS: - case ADV7511_REG_AN(0): - case ADV7511_REG_AN(1): - case ADV7511_REG_AN(2): - case ADV7511_REG_AN(3): - case ADV7511_REG_AN(4): - case ADV7511_REG_AN(5): - case ADV7511_REG_AN(6): - case ADV7511_REG_AN(7): - case ADV7511_REG_HDCP_STATUS: - case ADV7511_REG_BCAPS: - case ADV7511_REG_BKSV(0): - case ADV7511_REG_BKSV(1): - case ADV7511_REG_BKSV(2): - case ADV7511_REG_BKSV(3): - case ADV7511_REG_BKSV(4): - case ADV7511_REG_DDC_STATUS: - case ADV7511_REG_EDID_READ_CTRL: - case ADV7511_REG_BSTATUS(0): - case ADV7511_REG_BSTATUS(1): - case ADV7511_REG_CHIP_ID_HIGH: - case ADV7511_REG_CHIP_ID_LOW: - return true; - } - - return false; -} - -static const struct regmap_config adv7511_regmap_config = { - .reg_bits = 8, - .val_bits = 8, - - .max_register = 0xff, - .cache_type = REGCACHE_RBTREE, - .reg_defaults_raw = adv7511_register_defaults, - .num_reg_defaults_raw = ARRAY_SIZE(adv7511_register_defaults), - - .volatile_reg = adv7511_register_volatile, -}; - -/* ----------------------------------------------------------------------------- - * Hardware configuration - */ - -static void adv7511_set_colormap(struct adv7511 *adv7511, bool enable, - const uint16_t *coeff, - unsigned int scaling_factor) -{ - unsigned int i; - - regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(1), - ADV7511_CSC_UPDATE_MODE, ADV7511_CSC_UPDATE_MODE); - - if (enable) { - for (i = 0; i < 12; ++i) { - regmap_update_bits(adv7511->regmap, - ADV7511_REG_CSC_UPPER(i), - 0x1f, coeff[i] >> 8); - regmap_write(adv7511->regmap, - ADV7511_REG_CSC_LOWER(i), - coeff[i] & 0xff); - } - } - - if (enable) - regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(0), - 0xe0, 0x80 | (scaling_factor << 5)); - else - regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(0), - 0x80, 0x00); - - regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(1), - ADV7511_CSC_UPDATE_MODE, 0); -} - -static int adv7511_packet_enable(struct adv7511 *adv7511, unsigned int packet) -{ - if (packet & 0xff) - regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0, - packet, 0xff); - - if (packet & 0xff00) { - packet >>= 8; - regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1, - packet, 0xff); - } - - return 0; -} - -static int adv7511_packet_disable(struct adv7511 *adv7511, unsigned int packet) -{ - if (packet & 0xff) - regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0, - packet, 0x00); - - if (packet & 0xff00) { - packet >>= 8; - regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1, - packet, 0x00); - } - - return 0; -} - -/* Coefficients for adv7511 color space conversion */ -static const uint16_t adv7511_csc_ycbcr_to_rgb[] = { - 0x0734, 0x04ad, 0x0000, 0x1c1b, - 0x1ddc, 0x04ad, 0x1f24, 0x0135, - 0x0000, 0x04ad, 0x087c, 0x1b77, -}; - -static void adv7511_set_config_csc(struct adv7511 *adv7511, - struct drm_connector *connector, - bool rgb) -{ - struct adv7511_video_config config; - bool output_format_422, output_format_ycbcr; - unsigned int mode; - uint8_t infoframe[17]; - - if (adv7511->edid) - config.hdmi_mode = drm_detect_hdmi_monitor(adv7511->edid); - else - config.hdmi_mode = false; - - hdmi_avi_infoframe_init(&config.avi_infoframe); - - config.avi_infoframe.scan_mode = HDMI_SCAN_MODE_UNDERSCAN; - - if (rgb) { - config.csc_enable = false; - config.avi_infoframe.colorspace = HDMI_COLORSPACE_RGB; - } else { - config.csc_scaling_factor = ADV7511_CSC_SCALING_4; - config.csc_coefficents = adv7511_csc_ycbcr_to_rgb; - - if ((connector->display_info.color_formats & - DRM_COLOR_FORMAT_YCRCB422) && - config.hdmi_mode) { - config.csc_enable = false; - config.avi_infoframe.colorspace = - HDMI_COLORSPACE_YUV422; - } else { - config.csc_enable = true; - config.avi_infoframe.colorspace = HDMI_COLORSPACE_RGB; - } - } - - if (config.hdmi_mode) { - mode = ADV7511_HDMI_CFG_MODE_HDMI; - - switch (config.avi_infoframe.colorspace) { - case HDMI_COLORSPACE_YUV444: - output_format_422 = false; - output_format_ycbcr = true; - break; - case HDMI_COLORSPACE_YUV422: - output_format_422 = true; - output_format_ycbcr = true; - break; - default: - output_format_422 = false; - output_format_ycbcr = false; - break; - } - } else { - mode = ADV7511_HDMI_CFG_MODE_DVI; - output_format_422 = false; - output_format_ycbcr = false; - } - - adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME); - - adv7511_set_colormap(adv7511, config.csc_enable, - config.csc_coefficents, - config.csc_scaling_factor); - - regmap_update_bits(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG1, 0x81, - (output_format_422 << 7) | output_format_ycbcr); - - regmap_update_bits(adv7511->regmap, ADV7511_REG_HDCP_HDMI_CFG, - ADV7511_HDMI_CFG_MODE_MASK, mode); - - hdmi_avi_infoframe_pack(&config.avi_infoframe, infoframe, - sizeof(infoframe)); - - /* The AVI infoframe id is not configurable */ - regmap_bulk_write(adv7511->regmap, ADV7511_REG_AVI_INFOFRAME_VERSION, - infoframe + 1, sizeof(infoframe) - 1); - - adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME); -} - -static void adv7511_set_link_config(struct adv7511 *adv7511, - const struct adv7511_link_config *config) -{ - /* - * The input style values documented in the datasheet don't match the - * hardware register field values :-( - */ - static const unsigned int input_styles[4] = { 0, 2, 1, 3 }; - - unsigned int clock_delay; - unsigned int color_depth; - unsigned int input_id; - - clock_delay = (config->clock_delay + 1200) / 400; - color_depth = config->input_color_depth == 8 ? 3 - : (config->input_color_depth == 10 ? 1 : 2); - - /* TODO Support input ID 6 */ - if (config->input_colorspace != HDMI_COLORSPACE_YUV422) - input_id = config->input_clock == ADV7511_INPUT_CLOCK_DDR - ? 5 : 0; - else if (config->input_clock == ADV7511_INPUT_CLOCK_DDR) - input_id = config->embedded_sync ? 8 : 7; - else if (config->input_clock == ADV7511_INPUT_CLOCK_2X) - input_id = config->embedded_sync ? 4 : 3; - else - input_id = config->embedded_sync ? 2 : 1; - - regmap_update_bits(adv7511->regmap, ADV7511_REG_I2C_FREQ_ID_CFG, 0xf, - input_id); - regmap_update_bits(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG1, 0x7e, - (color_depth << 4) | - (input_styles[config->input_style] << 2)); - regmap_write(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG2, - config->input_justification << 3); - regmap_write(adv7511->regmap, ADV7511_REG_TIMING_GEN_SEQ, - config->sync_pulse << 2); - - regmap_write(adv7511->regmap, 0xba, clock_delay << 5); - - adv7511->embedded_sync = config->embedded_sync; - adv7511->hsync_polarity = config->hsync_polarity; - adv7511->vsync_polarity = config->vsync_polarity; - adv7511->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB; -} - -static void adv7511_power_on(struct adv7511 *adv7511) -{ - adv7511->current_edid_segment = -1; - - regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, - ADV7511_POWER_POWER_DOWN, 0); - if (adv7511->i2c_main->irq) { - /* - * Documentation says the INT_ENABLE registers are reset in - * POWER_DOWN mode. My 7511w preserved the bits, however. - * Still, let's be safe and stick to the documentation. - */ - regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0), - ADV7511_INT0_EDID_READY); - regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(1), - ADV7511_INT1_DDC_ERROR); - } - - /* - * Per spec it is allowed to pulse the HPD signal to indicate that the - * EDID information has changed. Some monitors do this when they wakeup - * from standby or are enabled. When the HPD goes low the adv7511 is - * reset and the outputs are disabled which might cause the monitor to - * go to standby again. To avoid this we ignore the HPD pin for the - * first few seconds after enabling the output. - */ - regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, - ADV7511_REG_POWER2_HPD_SRC_MASK, - ADV7511_REG_POWER2_HPD_SRC_NONE); - - /* - * Most of the registers are reset during power down or when HPD is low. - */ - regcache_sync(adv7511->regmap); - - adv7511->powered = true; -} - -static void adv7511_power_off(struct adv7511 *adv7511) -{ - /* TODO: setup additional power down modes */ - regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, - ADV7511_POWER_POWER_DOWN, - ADV7511_POWER_POWER_DOWN); - regcache_mark_dirty(adv7511->regmap); - - adv7511->powered = false; -} - -/* ----------------------------------------------------------------------------- - * Interrupt and hotplug detection - */ - -static bool adv7511_hpd(struct adv7511 *adv7511) -{ - unsigned int irq0; - int ret; - - ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0); - if (ret < 0) - return false; - - if (irq0 & ADV7511_INT0_HPD) { - regmap_write(adv7511->regmap, ADV7511_REG_INT(0), - ADV7511_INT0_HPD); - return true; - } - - return false; -} - -static int adv7511_irq_process(struct adv7511 *adv7511) -{ - unsigned int irq0, irq1; - int ret; - - ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0); - if (ret < 0) - return ret; - - ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(1), &irq1); - if (ret < 0) - return ret; - - regmap_write(adv7511->regmap, ADV7511_REG_INT(0), irq0); - regmap_write(adv7511->regmap, ADV7511_REG_INT(1), irq1); - - if (irq0 & ADV7511_INT0_HPD && adv7511->bridge.encoder) - drm_helper_hpd_irq_event(adv7511->connector.dev); - - if (irq0 & ADV7511_INT0_EDID_READY || irq1 & ADV7511_INT1_DDC_ERROR) { - adv7511->edid_read = true; - - if (adv7511->i2c_main->irq) - wake_up_all(&adv7511->wq); - } - - return 0; -} - -static irqreturn_t adv7511_irq_handler(int irq, void *devid) -{ - struct adv7511 *adv7511 = devid; - int ret; - - ret = adv7511_irq_process(adv7511); - return ret < 0 ? IRQ_NONE : IRQ_HANDLED; -} - -/* ----------------------------------------------------------------------------- - * EDID retrieval - */ - -static int adv7511_wait_for_edid(struct adv7511 *adv7511, int timeout) -{ - int ret; - - if (adv7511->i2c_main->irq) { - ret = wait_event_interruptible_timeout(adv7511->wq, - adv7511->edid_read, msecs_to_jiffies(timeout)); - } else { - for (; timeout > 0; timeout -= 25) { - ret = adv7511_irq_process(adv7511); - if (ret < 0) - break; - - if (adv7511->edid_read) - break; - - msleep(25); - } - } - - return adv7511->edid_read ? 0 : -EIO; -} - -static int adv7511_get_edid_block(void *data, u8 *buf, unsigned int block, - size_t len) -{ - struct adv7511 *adv7511 = data; - struct i2c_msg xfer[2]; - uint8_t offset; - unsigned int i; - int ret; - - if (len > 128) - return -EINVAL; - - if (adv7511->current_edid_segment != block / 2) { - unsigned int status; - - ret = regmap_read(adv7511->regmap, ADV7511_REG_DDC_STATUS, - &status); - if (ret < 0) - return ret; - - if (status != 2) { - adv7511->edid_read = false; - regmap_write(adv7511->regmap, ADV7511_REG_EDID_SEGMENT, - block); - ret = adv7511_wait_for_edid(adv7511, 200); - if (ret < 0) - return ret; - } - - /* Break this apart, hopefully more I2C controllers will - * support 64 byte transfers than 256 byte transfers - */ - - xfer[0].addr = adv7511->i2c_edid->addr; - xfer[0].flags = 0; - xfer[0].len = 1; - xfer[0].buf = &offset; - xfer[1].addr = adv7511->i2c_edid->addr; - xfer[1].flags = I2C_M_RD; - xfer[1].len = 64; - xfer[1].buf = adv7511->edid_buf; - - offset = 0; - - for (i = 0; i < 4; ++i) { - ret = i2c_transfer(adv7511->i2c_edid->adapter, xfer, - ARRAY_SIZE(xfer)); - if (ret < 0) - return ret; - else if (ret != 2) - return -EIO; - - xfer[1].buf += 64; - offset += 64; - } - - adv7511->current_edid_segment = block / 2; - } - - if (block % 2 == 0) - memcpy(buf, adv7511->edid_buf, len); - else - memcpy(buf, adv7511->edid_buf + 128, len); - - return 0; -} - -/* ----------------------------------------------------------------------------- - * ADV75xx helpers - */ - -static int adv7511_get_modes(struct adv7511 *adv7511, - struct drm_connector *connector) -{ - struct edid *edid; - unsigned int count; - - /* Reading the EDID only works if the device is powered */ - if (!adv7511->powered) { - regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, - ADV7511_POWER_POWER_DOWN, 0); - if (adv7511->i2c_main->irq) { - regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0), - ADV7511_INT0_EDID_READY); - regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(1), - ADV7511_INT1_DDC_ERROR); - } - adv7511->current_edid_segment = -1; - } - - edid = drm_do_get_edid(connector, adv7511_get_edid_block, adv7511); - - if (!adv7511->powered) - regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, - ADV7511_POWER_POWER_DOWN, - ADV7511_POWER_POWER_DOWN); - - kfree(adv7511->edid); - adv7511->edid = edid; - if (!edid) - return 0; - - drm_mode_connector_update_edid_property(connector, edid); - count = drm_add_edid_modes(connector, edid); - - adv7511_set_config_csc(adv7511, connector, adv7511->rgb); - - return count; -} - -static enum drm_connector_status -adv7511_detect(struct adv7511 *adv7511, struct drm_connector *connector) -{ - enum drm_connector_status status; - unsigned int val; - bool hpd; - int ret; - - ret = regmap_read(adv7511->regmap, ADV7511_REG_STATUS, &val); - if (ret < 0) - return connector_status_disconnected; - - if (val & ADV7511_STATUS_HPD) - status = connector_status_connected; - else - status = connector_status_disconnected; - - hpd = adv7511_hpd(adv7511); - - /* The chip resets itself when the cable is disconnected, so in case - * there is a pending HPD interrupt and the cable is connected there was - * at least one transition from disconnected to connected and the chip - * has to be reinitialized. */ - if (status == connector_status_connected && hpd && adv7511->powered) { - regcache_mark_dirty(adv7511->regmap); - adv7511_power_on(adv7511); - adv7511_get_modes(adv7511, connector); - if (adv7511->status == connector_status_connected) - status = connector_status_disconnected; - } else { - /* Renable HPD sensing */ - regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, - ADV7511_REG_POWER2_HPD_SRC_MASK, - ADV7511_REG_POWER2_HPD_SRC_BOTH); - } - - adv7511->status = status; - return status; -} - -static int adv7511_mode_valid(struct adv7511 *adv7511, - struct drm_display_mode *mode) -{ - if (mode->clock > 165000) - return MODE_CLOCK_HIGH; - - return MODE_OK; -} - -static void adv7511_mode_set(struct adv7511 *adv7511, - struct drm_display_mode *mode, - struct drm_display_mode *adj_mode) -{ - unsigned int low_refresh_rate; - unsigned int hsync_polarity = 0; - unsigned int vsync_polarity = 0; - - if (adv7511->embedded_sync) { - unsigned int hsync_offset, hsync_len; - unsigned int vsync_offset, vsync_len; - - hsync_offset = adj_mode->crtc_hsync_start - - adj_mode->crtc_hdisplay; - vsync_offset = adj_mode->crtc_vsync_start - - adj_mode->crtc_vdisplay; - hsync_len = adj_mode->crtc_hsync_end - - adj_mode->crtc_hsync_start; - vsync_len = adj_mode->crtc_vsync_end - - adj_mode->crtc_vsync_start; - - /* The hardware vsync generator has a off-by-one bug */ - vsync_offset += 1; - - regmap_write(adv7511->regmap, ADV7511_REG_HSYNC_PLACEMENT_MSB, - ((hsync_offset >> 10) & 0x7) << 5); - regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(0), - (hsync_offset >> 2) & 0xff); - regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(1), - ((hsync_offset & 0x3) << 6) | - ((hsync_len >> 4) & 0x3f)); - regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(2), - ((hsync_len & 0xf) << 4) | - ((vsync_offset >> 6) & 0xf)); - regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(3), - ((vsync_offset & 0x3f) << 2) | - ((vsync_len >> 8) & 0x3)); - regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(4), - vsync_len & 0xff); - - hsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PHSYNC); - vsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PVSYNC); - } else { - enum adv7511_sync_polarity mode_hsync_polarity; - enum adv7511_sync_polarity mode_vsync_polarity; - - /** - * If the input signal is always low or always high we want to - * invert or let it passthrough depending on the polarity of the - * current mode. - **/ - if (adj_mode->flags & DRM_MODE_FLAG_NHSYNC) - mode_hsync_polarity = ADV7511_SYNC_POLARITY_LOW; - else - mode_hsync_polarity = ADV7511_SYNC_POLARITY_HIGH; - - if (adj_mode->flags & DRM_MODE_FLAG_NVSYNC) - mode_vsync_polarity = ADV7511_SYNC_POLARITY_LOW; - else - mode_vsync_polarity = ADV7511_SYNC_POLARITY_HIGH; - - if (adv7511->hsync_polarity != mode_hsync_polarity && - adv7511->hsync_polarity != - ADV7511_SYNC_POLARITY_PASSTHROUGH) - hsync_polarity = 1; - - if (adv7511->vsync_polarity != mode_vsync_polarity && - adv7511->vsync_polarity != - ADV7511_SYNC_POLARITY_PASSTHROUGH) - vsync_polarity = 1; - } - - if (mode->vrefresh <= 24000) - low_refresh_rate = ADV7511_LOW_REFRESH_RATE_24HZ; - else if (mode->vrefresh <= 25000) - low_refresh_rate = ADV7511_LOW_REFRESH_RATE_25HZ; - else if (mode->vrefresh <= 30000) - low_refresh_rate = ADV7511_LOW_REFRESH_RATE_30HZ; - else - low_refresh_rate = ADV7511_LOW_REFRESH_RATE_NONE; - - regmap_update_bits(adv7511->regmap, 0xfb, - 0x6, low_refresh_rate << 1); - regmap_update_bits(adv7511->regmap, 0x17, - 0x60, (vsync_polarity << 6) | (hsync_polarity << 5)); - - /* - * TODO Test first order 4:2:2 to 4:4:4 up conversion method, which is - * supposed to give better results. - */ - - adv7511->f_tmds = mode->clock; -} - -/* Connector funcs */ -static struct adv7511 *connector_to_adv7511(struct drm_connector *connector) -{ - return container_of(connector, struct adv7511, connector); -} - -static int adv7511_connector_get_modes(struct drm_connector *connector) -{ - struct adv7511 *adv = connector_to_adv7511(connector); - - return adv7511_get_modes(adv, connector); -} - -static enum drm_mode_status -adv7511_connector_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) -{ - struct adv7511 *adv = connector_to_adv7511(connector); - - return adv7511_mode_valid(adv, mode); -} - -static struct drm_connector_helper_funcs adv7511_connector_helper_funcs = { - .get_modes = adv7511_connector_get_modes, - .mode_valid = adv7511_connector_mode_valid, -}; - -static enum drm_connector_status -adv7511_connector_detect(struct drm_connector *connector, bool force) -{ - struct adv7511 *adv = connector_to_adv7511(connector); - - return adv7511_detect(adv, connector); -} - -static struct drm_connector_funcs adv7511_connector_funcs = { - .dpms = drm_atomic_helper_connector_dpms, - .fill_modes = drm_helper_probe_single_connector_modes, - .detect = adv7511_connector_detect, - .destroy = drm_connector_cleanup, - .reset = drm_atomic_helper_connector_reset, - .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, -}; - -/* Bridge funcs */ -static struct adv7511 *bridge_to_adv7511(struct drm_bridge *bridge) -{ - return container_of(bridge, struct adv7511, bridge); -} - -static void adv7511_bridge_enable(struct drm_bridge *bridge) -{ - struct adv7511 *adv = bridge_to_adv7511(bridge); - - adv7511_power_on(adv); -} - -static void adv7511_bridge_disable(struct drm_bridge *bridge) -{ - struct adv7511 *adv = bridge_to_adv7511(bridge); - - adv7511_power_off(adv); -} - -static void adv7511_bridge_mode_set(struct drm_bridge *bridge, - struct drm_display_mode *mode, - struct drm_display_mode *adj_mode) -{ - struct adv7511 *adv = bridge_to_adv7511(bridge); - - adv7511_mode_set(adv, mode, adj_mode); -} - -static int adv7511_bridge_attach(struct drm_bridge *bridge) -{ - struct adv7511 *adv = bridge_to_adv7511(bridge); - int ret; - - if (!bridge->encoder) { - DRM_ERROR("Parent encoder object not found"); - return -ENODEV; - } - - adv->connector.polled = DRM_CONNECTOR_POLL_HPD; - - ret = drm_connector_init(bridge->dev, &adv->connector, - &adv7511_connector_funcs, - DRM_MODE_CONNECTOR_HDMIA); - if (ret) { - DRM_ERROR("Failed to initialize connector with drm\n"); - return ret; - } - drm_connector_helper_add(&adv->connector, - &adv7511_connector_helper_funcs); - drm_mode_connector_attach_encoder(&adv->connector, bridge->encoder); - - return ret; -} - -static struct drm_bridge_funcs adv7511_bridge_funcs = { - .enable = adv7511_bridge_enable, - .disable = adv7511_bridge_disable, - .mode_set = adv7511_bridge_mode_set, - .attach = adv7511_bridge_attach, -}; - -/* ----------------------------------------------------------------------------- - * Probe & remove - */ - -static int adv7511_parse_dt(struct device_node *np, - struct adv7511_link_config *config) -{ - const char *str; - int ret; - - memset(config, 0, sizeof(*config)); - - of_property_read_u32(np, "adi,input-depth", &config->input_color_depth); - if (config->input_color_depth != 8 && config->input_color_depth != 10 && - config->input_color_depth != 12) - return -EINVAL; - - ret = of_property_read_string(np, "adi,input-colorspace", &str); - if (ret < 0) - return ret; - - if (!strcmp(str, "rgb")) - config->input_colorspace = HDMI_COLORSPACE_RGB; - else if (!strcmp(str, "yuv422")) - config->input_colorspace = HDMI_COLORSPACE_YUV422; - else if (!strcmp(str, "yuv444")) - config->input_colorspace = HDMI_COLORSPACE_YUV444; - else - return -EINVAL; - - ret = of_property_read_string(np, "adi,input-clock", &str); - if (ret < 0) - return ret; - - if (!strcmp(str, "1x")) - config->input_clock = ADV7511_INPUT_CLOCK_1X; - else if (!strcmp(str, "2x")) - config->input_clock = ADV7511_INPUT_CLOCK_2X; - else if (!strcmp(str, "ddr")) - config->input_clock = ADV7511_INPUT_CLOCK_DDR; - else - return -EINVAL; - - if (config->input_colorspace == HDMI_COLORSPACE_YUV422 || - config->input_clock != ADV7511_INPUT_CLOCK_1X) { - ret = of_property_read_u32(np, "adi,input-style", - &config->input_style); - if (ret) - return ret; - - if (config->input_style < 1 || config->input_style > 3) - return -EINVAL; - - ret = of_property_read_string(np, "adi,input-justification", - &str); - if (ret < 0) - return ret; - - if (!strcmp(str, "left")) - config->input_justification = - ADV7511_INPUT_JUSTIFICATION_LEFT; - else if (!strcmp(str, "evenly")) - config->input_justification = - ADV7511_INPUT_JUSTIFICATION_EVENLY; - else if (!strcmp(str, "right")) - config->input_justification = - ADV7511_INPUT_JUSTIFICATION_RIGHT; - else - return -EINVAL; - - } else { - config->input_style = 1; - config->input_justification = ADV7511_INPUT_JUSTIFICATION_LEFT; - } - - of_property_read_u32(np, "adi,clock-delay", &config->clock_delay); - if (config->clock_delay < -1200 || config->clock_delay > 1600) - return -EINVAL; - - config->embedded_sync = of_property_read_bool(np, "adi,embedded-sync"); - - /* Hardcode the sync pulse configurations for now. */ - config->sync_pulse = ADV7511_INPUT_SYNC_PULSE_NONE; - config->vsync_polarity = ADV7511_SYNC_POLARITY_PASSTHROUGH; - config->hsync_polarity = ADV7511_SYNC_POLARITY_PASSTHROUGH; - - return 0; -} - -static const int edid_i2c_addr = 0x7e; -static const int packet_i2c_addr = 0x70; -static const int cec_i2c_addr = 0x78; - -static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) -{ - struct adv7511_link_config link_config; - struct adv7511 *adv7511; - struct device *dev = &i2c->dev; - unsigned int val; - int ret; - - if (!dev->of_node) - return -EINVAL; - - adv7511 = devm_kzalloc(dev, sizeof(*adv7511), GFP_KERNEL); - if (!adv7511) - return -ENOMEM; - - adv7511->powered = false; - adv7511->status = connector_status_disconnected; - - ret = adv7511_parse_dt(dev->of_node, &link_config); - if (ret) - return ret; - - /* - * The power down GPIO is optional. If present, toggle it from active to - * inactive to wake up the encoder. - */ - adv7511->gpio_pd = devm_gpiod_get_optional(dev, "pd", GPIOD_OUT_HIGH); - if (IS_ERR(adv7511->gpio_pd)) - return PTR_ERR(adv7511->gpio_pd); - - if (adv7511->gpio_pd) { - mdelay(5); - gpiod_set_value_cansleep(adv7511->gpio_pd, 0); - } - - adv7511->regmap = devm_regmap_init_i2c(i2c, &adv7511_regmap_config); - if (IS_ERR(adv7511->regmap)) - return PTR_ERR(adv7511->regmap); - - ret = regmap_read(adv7511->regmap, ADV7511_REG_CHIP_REVISION, &val); - if (ret) - return ret; - dev_dbg(dev, "Rev. %d\n", val); - - ret = regmap_register_patch(adv7511->regmap, adv7511_fixed_registers, - ARRAY_SIZE(adv7511_fixed_registers)); - if (ret) - return ret; - - regmap_write(adv7511->regmap, ADV7511_REG_EDID_I2C_ADDR, edid_i2c_addr); - regmap_write(adv7511->regmap, ADV7511_REG_PACKET_I2C_ADDR, - packet_i2c_addr); - regmap_write(adv7511->regmap, ADV7511_REG_CEC_I2C_ADDR, cec_i2c_addr); - adv7511_packet_disable(adv7511, 0xffff); - - adv7511->i2c_main = i2c; - adv7511->i2c_edid = i2c_new_dummy(i2c->adapter, edid_i2c_addr >> 1); - if (!adv7511->i2c_edid) - return -ENOMEM; - - if (i2c->irq) { - init_waitqueue_head(&adv7511->wq); - - ret = devm_request_threaded_irq(dev, i2c->irq, NULL, - adv7511_irq_handler, - IRQF_ONESHOT, dev_name(dev), - adv7511); - if (ret) - goto err_i2c_unregister_device; - } - - /* CEC is unused for now */ - regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL, - ADV7511_CEC_CTRL_POWER_DOWN); - - adv7511_power_off(adv7511); - - i2c_set_clientdata(i2c, adv7511); - - adv7511_set_link_config(adv7511, &link_config); - - adv7511->bridge.funcs = &adv7511_bridge_funcs; - adv7511->bridge.of_node = dev->of_node; - - ret = drm_bridge_add(&adv7511->bridge); - if (ret) { - dev_err(dev, "failed to add adv7511 bridge\n"); - goto err_i2c_unregister_device; - } - - return 0; - -err_i2c_unregister_device: - i2c_unregister_device(adv7511->i2c_edid); - - return ret; -} - -static int adv7511_remove(struct i2c_client *i2c) -{ - struct adv7511 *adv7511 = i2c_get_clientdata(i2c); - - drm_bridge_remove(&adv7511->bridge); - - i2c_unregister_device(adv7511->i2c_edid); - - kfree(adv7511->edid); - - return 0; -} - -static const struct i2c_device_id adv7511_i2c_ids[] = { - { "adv7511", 0 }, - { "adv7511w", 0 }, - { "adv7513", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, adv7511_i2c_ids); - -static const struct of_device_id adv7511_of_ids[] = { - { .compatible = "adi,adv7511", }, - { .compatible = "adi,adv7511w", }, - { .compatible = "adi,adv7513", }, - { } -}; -MODULE_DEVICE_TABLE(of, adv7511_of_ids); - -static struct i2c_driver adv7511_driver = { - .driver = { - .name = "adv7511", - .of_match_table = adv7511_of_ids, - }, - .id_table = adv7511_i2c_ids, - .probe = adv7511_probe, - .remove = adv7511_remove, -}; - -module_i2c_driver(adv7511_driver); - -MODULE_AUTHOR("Lars-Peter Clausen "); -MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/i2c/adv7511.h b/drivers/gpu/drm/i2c/adv7511.h deleted file mode 100644 index 38515b3..0000000 --- a/drivers/gpu/drm/i2c/adv7511.h +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Analog Devices ADV7511 HDMI transmitter driver - * - * Copyright 2012 Analog Devices Inc. - * - * Licensed under the GPL-2. - */ - -#ifndef __DRM_I2C_ADV7511_H__ -#define __DRM_I2C_ADV7511_H__ - -#include - -#define ADV7511_REG_CHIP_REVISION 0x00 -#define ADV7511_REG_N0 0x01 -#define ADV7511_REG_N1 0x02 -#define ADV7511_REG_N2 0x03 -#define ADV7511_REG_SPDIF_FREQ 0x04 -#define ADV7511_REG_CTS_AUTOMATIC1 0x05 -#define ADV7511_REG_CTS_AUTOMATIC2 0x06 -#define ADV7511_REG_CTS_MANUAL0 0x07 -#define ADV7511_REG_CTS_MANUAL1 0x08 -#define ADV7511_REG_CTS_MANUAL2 0x09 -#define ADV7511_REG_AUDIO_SOURCE 0x0a -#define ADV7511_REG_AUDIO_CONFIG 0x0b -#define ADV7511_REG_I2S_CONFIG 0x0c -#define ADV7511_REG_I2S_WIDTH 0x0d -#define ADV7511_REG_AUDIO_SUB_SRC0 0x0e -#define ADV7511_REG_AUDIO_SUB_SRC1 0x0f -#define ADV7511_REG_AUDIO_SUB_SRC2 0x10 -#define ADV7511_REG_AUDIO_SUB_SRC3 0x11 -#define ADV7511_REG_AUDIO_CFG1 0x12 -#define ADV7511_REG_AUDIO_CFG2 0x13 -#define ADV7511_REG_AUDIO_CFG3 0x14 -#define ADV7511_REG_I2C_FREQ_ID_CFG 0x15 -#define ADV7511_REG_VIDEO_INPUT_CFG1 0x16 -#define ADV7511_REG_CSC_UPPER(x) (0x18 + (x) * 2) -#define ADV7511_REG_CSC_LOWER(x) (0x19 + (x) * 2) -#define ADV7511_REG_SYNC_DECODER(x) (0x30 + (x)) -#define ADV7511_REG_DE_GENERATOR (0x35 + (x)) -#define ADV7511_REG_PIXEL_REPETITION 0x3b -#define ADV7511_REG_VIC_MANUAL 0x3c -#define ADV7511_REG_VIC_SEND 0x3d -#define ADV7511_REG_VIC_DETECTED 0x3e -#define ADV7511_REG_AUX_VIC_DETECTED 0x3f -#define ADV7511_REG_PACKET_ENABLE0 0x40 -#define ADV7511_REG_POWER 0x41 -#define ADV7511_REG_STATUS 0x42 -#define ADV7511_REG_EDID_I2C_ADDR 0x43 -#define ADV7511_REG_PACKET_ENABLE1 0x44 -#define ADV7511_REG_PACKET_I2C_ADDR 0x45 -#define ADV7511_REG_DSD_ENABLE 0x46 -#define ADV7511_REG_VIDEO_INPUT_CFG2 0x48 -#define ADV7511_REG_INFOFRAME_UPDATE 0x4a -#define ADV7511_REG_GC(x) (0x4b + (x)) /* 0x4b - 0x51 */ -#define ADV7511_REG_AVI_INFOFRAME_VERSION 0x52 -#define ADV7511_REG_AVI_INFOFRAME_LENGTH 0x53 -#define ADV7511_REG_AVI_INFOFRAME_CHECKSUM 0x54 -#define ADV7511_REG_AVI_INFOFRAME(x) (0x55 + (x)) /* 0x55 - 0x6f */ -#define ADV7511_REG_AUDIO_INFOFRAME_VERSION 0x70 -#define ADV7511_REG_AUDIO_INFOFRAME_LENGTH 0x71 -#define ADV7511_REG_AUDIO_INFOFRAME_CHECKSUM 0x72 -#define ADV7511_REG_AUDIO_INFOFRAME(x) (0x73 + (x)) /* 0x73 - 0x7c */ -#define ADV7511_REG_INT_ENABLE(x) (0x94 + (x)) -#define ADV7511_REG_INT(x) (0x96 + (x)) -#define ADV7511_REG_INPUT_CLK_DIV 0x9d -#define ADV7511_REG_PLL_STATUS 0x9e -#define ADV7511_REG_HDMI_POWER 0xa1 -#define ADV7511_REG_HDCP_HDMI_CFG 0xaf -#define ADV7511_REG_AN(x) (0xb0 + (x)) /* 0xb0 - 0xb7 */ -#define ADV7511_REG_HDCP_STATUS 0xb8 -#define ADV7511_REG_BCAPS 0xbe -#define ADV7511_REG_BKSV(x) (0xc0 + (x)) /* 0xc0 - 0xc3 */ -#define ADV7511_REG_EDID_SEGMENT 0xc4 -#define ADV7511_REG_DDC_STATUS 0xc8 -#define ADV7511_REG_EDID_READ_CTRL 0xc9 -#define ADV7511_REG_BSTATUS(x) (0xca + (x)) /* 0xca - 0xcb */ -#define ADV7511_REG_TIMING_GEN_SEQ 0xd0 -#define ADV7511_REG_POWER2 0xd6 -#define ADV7511_REG_HSYNC_PLACEMENT_MSB 0xfa - -#define ADV7511_REG_SYNC_ADJUSTMENT(x) (0xd7 + (x)) /* 0xd7 - 0xdc */ -#define ADV7511_REG_TMDS_CLOCK_INV 0xde -#define ADV7511_REG_ARC_CTRL 0xdf -#define ADV7511_REG_CEC_I2C_ADDR 0xe1 -#define ADV7511_REG_CEC_CTRL 0xe2 -#define ADV7511_REG_CHIP_ID_HIGH 0xf5 -#define ADV7511_REG_CHIP_ID_LOW 0xf6 - -#define ADV7511_CSC_ENABLE BIT(7) -#define ADV7511_CSC_UPDATE_MODE BIT(5) - -#define ADV7511_INT0_HPD BIT(7) -#define ADV7511_INT0_VSYNC BIT(5) -#define ADV7511_INT0_AUDIO_FIFO_FULL BIT(4) -#define ADV7511_INT0_EDID_READY BIT(2) -#define ADV7511_INT0_HDCP_AUTHENTICATED BIT(1) - -#define ADV7511_INT1_DDC_ERROR BIT(7) -#define ADV7511_INT1_BKSV BIT(6) -#define ADV7511_INT1_CEC_TX_READY BIT(5) -#define ADV7511_INT1_CEC_TX_ARBIT_LOST BIT(4) -#define ADV7511_INT1_CEC_TX_RETRY_TIMEOUT BIT(3) -#define ADV7511_INT1_CEC_RX_READY3 BIT(2) -#define ADV7511_INT1_CEC_RX_READY2 BIT(1) -#define ADV7511_INT1_CEC_RX_READY1 BIT(0) - -#define ADV7511_ARC_CTRL_POWER_DOWN BIT(0) - -#define ADV7511_CEC_CTRL_POWER_DOWN BIT(0) - -#define ADV7511_POWER_POWER_DOWN BIT(6) - -#define ADV7511_HDMI_CFG_MODE_MASK 0x2 -#define ADV7511_HDMI_CFG_MODE_DVI 0x0 -#define ADV7511_HDMI_CFG_MODE_HDMI 0x2 - -#define ADV7511_AUDIO_SELECT_I2C 0x0 -#define ADV7511_AUDIO_SELECT_SPDIF 0x1 -#define ADV7511_AUDIO_SELECT_DSD 0x2 -#define ADV7511_AUDIO_SELECT_HBR 0x3 -#define ADV7511_AUDIO_SELECT_DST 0x4 - -#define ADV7511_I2S_SAMPLE_LEN_16 0x2 -#define ADV7511_I2S_SAMPLE_LEN_20 0x3 -#define ADV7511_I2S_SAMPLE_LEN_18 0x4 -#define ADV7511_I2S_SAMPLE_LEN_22 0x5 -#define ADV7511_I2S_SAMPLE_LEN_19 0x8 -#define ADV7511_I2S_SAMPLE_LEN_23 0x9 -#define ADV7511_I2S_SAMPLE_LEN_24 0xb -#define ADV7511_I2S_SAMPLE_LEN_17 0xc -#define ADV7511_I2S_SAMPLE_LEN_21 0xd - -#define ADV7511_SAMPLE_FREQ_44100 0x0 -#define ADV7511_SAMPLE_FREQ_48000 0x2 -#define ADV7511_SAMPLE_FREQ_32000 0x3 -#define ADV7511_SAMPLE_FREQ_88200 0x8 -#define ADV7511_SAMPLE_FREQ_96000 0xa -#define ADV7511_SAMPLE_FREQ_176400 0xc -#define ADV7511_SAMPLE_FREQ_192000 0xe - -#define ADV7511_STATUS_POWER_DOWN_POLARITY BIT(7) -#define ADV7511_STATUS_HPD BIT(6) -#define ADV7511_STATUS_MONITOR_SENSE BIT(5) -#define ADV7511_STATUS_I2S_32BIT_MODE BIT(3) - -#define ADV7511_PACKET_ENABLE_N_CTS BIT(8+6) -#define ADV7511_PACKET_ENABLE_AUDIO_SAMPLE BIT(8+5) -#define ADV7511_PACKET_ENABLE_AVI_INFOFRAME BIT(8+4) -#define ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME BIT(8+3) -#define ADV7511_PACKET_ENABLE_GC BIT(7) -#define ADV7511_PACKET_ENABLE_SPD BIT(6) -#define ADV7511_PACKET_ENABLE_MPEG BIT(5) -#define ADV7511_PACKET_ENABLE_ACP BIT(4) -#define ADV7511_PACKET_ENABLE_ISRC BIT(3) -#define ADV7511_PACKET_ENABLE_GM BIT(2) -#define ADV7511_PACKET_ENABLE_SPARE2 BIT(1) -#define ADV7511_PACKET_ENABLE_SPARE1 BIT(0) - -#define ADV7511_REG_POWER2_HPD_SRC_MASK 0xc0 -#define ADV7511_REG_POWER2_HPD_SRC_BOTH 0x00 -#define ADV7511_REG_POWER2_HPD_SRC_HPD 0x40 -#define ADV7511_REG_POWER2_HPD_SRC_CEC 0x80 -#define ADV7511_REG_POWER2_HPD_SRC_NONE 0xc0 -#define ADV7511_REG_POWER2_TDMS_ENABLE BIT(4) -#define ADV7511_REG_POWER2_GATE_INPUT_CLK BIT(0) - -#define ADV7511_LOW_REFRESH_RATE_NONE 0x0 -#define ADV7511_LOW_REFRESH_RATE_24HZ 0x1 -#define ADV7511_LOW_REFRESH_RATE_25HZ 0x2 -#define ADV7511_LOW_REFRESH_RATE_30HZ 0x3 - -#define ADV7511_AUDIO_CFG3_LEN_MASK 0x0f -#define ADV7511_I2C_FREQ_ID_CFG_RATE_MASK 0xf0 - -#define ADV7511_AUDIO_SOURCE_I2S 0 -#define ADV7511_AUDIO_SOURCE_SPDIF 1 - -#define ADV7511_I2S_FORMAT_I2S 0 -#define ADV7511_I2S_FORMAT_RIGHT_J 1 -#define ADV7511_I2S_FORMAT_LEFT_J 2 - -#define ADV7511_PACKET(p, x) ((p) * 0x20 + (x)) -#define ADV7511_PACKET_SDP(x) ADV7511_PACKET(0, x) -#define ADV7511_PACKET_MPEG(x) ADV7511_PACKET(1, x) -#define ADV7511_PACKET_ACP(x) ADV7511_PACKET(2, x) -#define ADV7511_PACKET_ISRC1(x) ADV7511_PACKET(3, x) -#define ADV7511_PACKET_ISRC2(x) ADV7511_PACKET(4, x) -#define ADV7511_PACKET_GM(x) ADV7511_PACKET(5, x) -#define ADV7511_PACKET_SPARE(x) ADV7511_PACKET(6, x) - -enum adv7511_input_clock { - ADV7511_INPUT_CLOCK_1X, - ADV7511_INPUT_CLOCK_2X, - ADV7511_INPUT_CLOCK_DDR, -}; - -enum adv7511_input_justification { - ADV7511_INPUT_JUSTIFICATION_EVENLY = 0, - ADV7511_INPUT_JUSTIFICATION_RIGHT = 1, - ADV7511_INPUT_JUSTIFICATION_LEFT = 2, -}; - -enum adv7511_input_sync_pulse { - ADV7511_INPUT_SYNC_PULSE_DE = 0, - ADV7511_INPUT_SYNC_PULSE_HSYNC = 1, - ADV7511_INPUT_SYNC_PULSE_VSYNC = 2, - ADV7511_INPUT_SYNC_PULSE_NONE = 3, -}; - -/** - * enum adv7511_sync_polarity - Polarity for the input sync signals - * @ADV7511_SYNC_POLARITY_PASSTHROUGH: Sync polarity matches that of - * the currently configured mode. - * @ADV7511_SYNC_POLARITY_LOW: Sync polarity is low - * @ADV7511_SYNC_POLARITY_HIGH: Sync polarity is high - * - * If the polarity is set to either LOW or HIGH the driver will configure the - * ADV7511 to internally invert the sync signal if required to match the sync - * polarity setting for the currently selected output mode. - * - * If the polarity is set to PASSTHROUGH, the ADV7511 will route the signal - * unchanged. This is used when the upstream graphics core already generates - * the sync signals with the correct polarity. - */ -enum adv7511_sync_polarity { - ADV7511_SYNC_POLARITY_PASSTHROUGH, - ADV7511_SYNC_POLARITY_LOW, - ADV7511_SYNC_POLARITY_HIGH, -}; - -/** - * struct adv7511_link_config - Describes adv7511 hardware configuration - * @input_color_depth: Number of bits per color component (8, 10 or 12) - * @input_colorspace: The input colorspace (RGB, YUV444, YUV422) - * @input_clock: The input video clock style (1x, 2x, DDR) - * @input_style: The input component arrangement variant - * @input_justification: Video input format bit justification - * @clock_delay: Clock delay for the input clock (in ps) - * @embedded_sync: Video input uses BT.656-style embedded sync - * @sync_pulse: Select the sync pulse - * @vsync_polarity: vsync input signal configuration - * @hsync_polarity: hsync input signal configuration - */ -struct adv7511_link_config { - unsigned int input_color_depth; - enum hdmi_colorspace input_colorspace; - enum adv7511_input_clock input_clock; - unsigned int input_style; - enum adv7511_input_justification input_justification; - - int clock_delay; - - bool embedded_sync; - enum adv7511_input_sync_pulse sync_pulse; - enum adv7511_sync_polarity vsync_polarity; - enum adv7511_sync_polarity hsync_polarity; -}; - -/** - * enum adv7511_csc_scaling - Scaling factor for the ADV7511 CSC - * @ADV7511_CSC_SCALING_1: CSC results are not scaled - * @ADV7511_CSC_SCALING_2: CSC results are scaled by a factor of two - * @ADV7511_CSC_SCALING_4: CSC results are scalled by a factor of four - */ -enum adv7511_csc_scaling { - ADV7511_CSC_SCALING_1 = 0, - ADV7511_CSC_SCALING_2 = 1, - ADV7511_CSC_SCALING_4 = 2, -}; - -/** - * struct adv7511_video_config - Describes adv7511 hardware configuration - * @csc_enable: Whether to enable color space conversion - * @csc_scaling_factor: Color space conversion scaling factor - * @csc_coefficents: Color space conversion coefficents - * @hdmi_mode: Whether to use HDMI or DVI output mode - * @avi_infoframe: HDMI infoframe - */ -struct adv7511_video_config { - bool csc_enable; - enum adv7511_csc_scaling csc_scaling_factor; - const uint16_t *csc_coefficents; - - bool hdmi_mode; - struct hdmi_avi_infoframe avi_infoframe; -}; - -#endif /* __DRM_I2C_ADV7511_H__ */ -- cgit v0.10.2 From f0bfcc22d9822947b0ad3095e8363eab5261864c Mon Sep 17 00:00:00 2001 From: Archit Taneja Date: Wed, 15 Jun 2016 16:20:45 +0530 Subject: drm/bridge: adv7511: Fix mutex deadlock when interrupts are disabled When the adv7511 i2c client doesn't have an interrupt line, we observe a deadlock on caused by trying to lock drm device's mode_config.mutex twice in the same context. Here is the sequence that causes it: ioctl DRM_IOCTL_MODE_GETCONNECTOR from userspace drm_mode_getconnector (acquires mode_config mutex) connector->fill_modes() drm_helper_probe_single_connector_modes connector_funcs->get_modes adv7511_encoder_get_modes adv7511_get_edid_block adv7511_irq_process drm_helper_hpd_irq_event (acquires mode_config mutex again) In adv7511_irq_process, don't call drm_helper_hpd_irq_event when not called from the interrupt handler. It doesn't serve any purpose there anyway. Signed-off-by: Archit Taneja diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index c2642f9..1fff0ab 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -427,7 +427,7 @@ static bool adv7511_hpd(struct adv7511 *adv7511) return false; } -static int adv7511_irq_process(struct adv7511 *adv7511) +static int adv7511_irq_process(struct adv7511 *adv7511, bool process_hpd) { unsigned int irq0, irq1; int ret; @@ -443,7 +443,7 @@ static int adv7511_irq_process(struct adv7511 *adv7511) regmap_write(adv7511->regmap, ADV7511_REG_INT(0), irq0); regmap_write(adv7511->regmap, ADV7511_REG_INT(1), irq1); - if (irq0 & ADV7511_INT0_HPD && adv7511->bridge.encoder) + if (process_hpd && irq0 & ADV7511_INT0_HPD && adv7511->bridge.encoder) drm_helper_hpd_irq_event(adv7511->connector.dev); if (irq0 & ADV7511_INT0_EDID_READY || irq1 & ADV7511_INT1_DDC_ERROR) { @@ -461,7 +461,7 @@ static irqreturn_t adv7511_irq_handler(int irq, void *devid) struct adv7511 *adv7511 = devid; int ret; - ret = adv7511_irq_process(adv7511); + ret = adv7511_irq_process(adv7511, true); return ret < 0 ? IRQ_NONE : IRQ_HANDLED; } @@ -478,7 +478,7 @@ static int adv7511_wait_for_edid(struct adv7511 *adv7511, int timeout) adv7511->edid_read, msecs_to_jiffies(timeout)); } else { for (; timeout > 0; timeout -= 25) { - ret = adv7511_irq_process(adv7511); + ret = adv7511_irq_process(adv7511, false); if (ret < 0) break; -- cgit v0.10.2 From 2437e7cd88e8781cef5fd2c254c85aa62b305d04 Mon Sep 17 00:00:00 2001 From: Archit Taneja Date: Wed, 15 Jun 2016 16:24:03 +0530 Subject: drm/bridge: adv7533: Initial support for ADV7533 ADV7533 is a DSI to HDMI encoder chip. It is a derivative of ADV7511, with additional blocks to translate input DSI data to parallel RGB data. Besides the ADV7511 I2C register map, it has additional registers that require to be configured to activate the DSI Rx block. Create a new config that enables ADV7533 support. Use DT compatible strings to populate the ADV7533 type enum. Add minimal register configurations belonging to the DSI/CEC register map. Keep the ADV7533 code in a separate file. Originally worked on by Lars-Peter Clausen Signed-off-by: Archit Taneja diff --git a/drivers/gpu/drm/bridge/adv7511/Kconfig b/drivers/gpu/drm/bridge/adv7511/Kconfig index 222c6cc..eb84419 100644 --- a/drivers/gpu/drm/bridge/adv7511/Kconfig +++ b/drivers/gpu/drm/bridge/adv7511/Kconfig @@ -5,3 +5,10 @@ config DRM_I2C_ADV7511 select REGMAP_I2C help Support for the Analog Device ADV7511(W) and ADV7513 HDMI encoders. + +config DRM_I2C_ADV7533 + bool "ADV7533 encoder" + depends on DRM_I2C_ADV7511 + default y + help + Support for the Analog Devices ADV7533 DSI to HDMI encoder. diff --git a/drivers/gpu/drm/bridge/adv7511/Makefile b/drivers/gpu/drm/bridge/adv7511/Makefile index 692f83a..9019327 100644 --- a/drivers/gpu/drm/bridge/adv7511/Makefile +++ b/drivers/gpu/drm/bridge/adv7511/Makefile @@ -1,2 +1,3 @@ adv7511-y := adv7511_drv.o +adv7511-$(CONFIG_DRM_I2C_ADV7533) += adv7533.o obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h index 38515b3..5dea769 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -10,6 +10,10 @@ #define __DRM_I2C_ADV7511_H__ #include +#include +#include + +#include #define ADV7511_REG_CHIP_REVISION 0x00 #define ADV7511_REG_N0 0x01 @@ -286,4 +290,71 @@ struct adv7511_video_config { struct hdmi_avi_infoframe avi_infoframe; }; +enum adv7511_type { + ADV7511, + ADV7533, +}; + +struct adv7511 { + struct i2c_client *i2c_main; + struct i2c_client *i2c_edid; + struct i2c_client *i2c_cec; + + struct regmap *regmap; + struct regmap *regmap_cec; + enum drm_connector_status status; + bool powered; + + unsigned int f_tmds; + + unsigned int current_edid_segment; + uint8_t edid_buf[256]; + bool edid_read; + + wait_queue_head_t wq; + struct drm_bridge bridge; + struct drm_connector connector; + + bool embedded_sync; + enum adv7511_sync_polarity vsync_polarity; + enum adv7511_sync_polarity hsync_polarity; + bool rgb; + + struct edid *edid; + + struct gpio_desc *gpio_pd; + + enum adv7511_type type; +}; + +#ifdef CONFIG_DRM_I2C_ADV7533 +void adv7533_dsi_power_on(struct adv7511 *adv); +void adv7533_dsi_power_off(struct adv7511 *adv); +int adv7533_patch_registers(struct adv7511 *adv); +void adv7533_uninit_cec(struct adv7511 *adv); +int adv7533_init_cec(struct adv7511 *adv); +#else +static inline void adv7533_dsi_power_on(struct adv7511 *adv) +{ +} + +static inline void adv7533_dsi_power_off(struct adv7511 *adv) +{ +} + +static inline int adv7533_patch_registers(struct adv7511 *adv) +{ + return -ENODEV; +} + +static inline void adv7533_uninit_cec(struct adv7511 *adv) +{ +} + +static inline int adv7533_init_cec(struct adv7511 *adv) +{ + return -ENODEV; +} +#endif + #endif /* __DRM_I2C_ADV7511_H__ */ diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index 1fff0ab..e33702b 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -8,48 +8,17 @@ #include #include -#include #include -#include +#include #include #include #include #include -#include #include #include "adv7511.h" -struct adv7511 { - struct i2c_client *i2c_main; - struct i2c_client *i2c_edid; - - struct regmap *regmap; - struct regmap *packet_memory_regmap; - enum drm_connector_status status; - bool powered; - - unsigned int f_tmds; - - unsigned int current_edid_segment; - uint8_t edid_buf[256]; - bool edid_read; - - wait_queue_head_t wq; - struct drm_bridge bridge; - struct drm_connector connector; - - bool embedded_sync; - enum adv7511_sync_polarity vsync_polarity; - enum adv7511_sync_polarity hsync_polarity; - bool rgb; - - struct edid *edid; - - struct gpio_desc *gpio_pd; -}; - /* ADI recommended values for proper operation. */ static const struct reg_sequence adv7511_fixed_registers[] = { { 0x98, 0x03 }, @@ -391,6 +360,9 @@ static void adv7511_power_on(struct adv7511 *adv7511) */ regcache_sync(adv7511->regmap); + if (adv7511->type == ADV7533) + adv7533_dsi_power_on(adv7511); + adv7511->powered = true; } @@ -402,6 +374,9 @@ static void adv7511_power_off(struct adv7511 *adv7511) ADV7511_POWER_POWER_DOWN); regcache_mark_dirty(adv7511->regmap); + if (adv7511->type == ADV7533) + adv7533_dsi_power_off(adv7511); + adv7511->powered = false; } @@ -862,8 +837,6 @@ static int adv7511_parse_dt(struct device_node *np, const char *str; int ret; - memset(config, 0, sizeof(*config)); - of_property_read_u32(np, "adi,input-depth", &config->input_color_depth); if (config->input_color_depth != 8 && config->input_color_depth != 10 && config->input_color_depth != 12) @@ -963,9 +936,18 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) adv7511->powered = false; adv7511->status = connector_status_disconnected; - ret = adv7511_parse_dt(dev->of_node, &link_config); - if (ret) - return ret; + if (dev->of_node) + adv7511->type = (enum adv7511_type)of_device_get_match_data(dev); + else + adv7511->type = id->driver_data; + + memset(&link_config, 0, sizeof(link_config)); + + if (adv7511->type == ADV7511) { + ret = adv7511_parse_dt(dev->of_node, &link_config); + if (ret) + return ret; + } /* * The power down GPIO is optional. If present, toggle it from active to @@ -989,8 +971,12 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) return ret; dev_dbg(dev, "Rev. %d\n", val); - ret = regmap_register_patch(adv7511->regmap, adv7511_fixed_registers, - ARRAY_SIZE(adv7511_fixed_registers)); + if (adv7511->type == ADV7511) + ret = regmap_register_patch(adv7511->regmap, + adv7511_fixed_registers, + ARRAY_SIZE(adv7511_fixed_registers)); + else + ret = adv7533_patch_registers(adv7511); if (ret) return ret; @@ -1005,6 +991,12 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) if (!adv7511->i2c_edid) return -ENOMEM; + if (adv7511->type == ADV7533) { + ret = adv7533_init_cec(adv7511); + if (ret) + goto err_i2c_unregister_edid; + } + if (i2c->irq) { init_waitqueue_head(&adv7511->wq); @@ -1013,7 +1005,7 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) IRQF_ONESHOT, dev_name(dev), adv7511); if (ret) - goto err_i2c_unregister_device; + goto err_unregister_cec; } /* CEC is unused for now */ @@ -1024,7 +1016,8 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) i2c_set_clientdata(i2c, adv7511); - adv7511_set_link_config(adv7511, &link_config); + if (adv7511->type == ADV7511) + adv7511_set_link_config(adv7511, &link_config); adv7511->bridge.funcs = &adv7511_bridge_funcs; adv7511->bridge.of_node = dev->of_node; @@ -1032,12 +1025,14 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) ret = drm_bridge_add(&adv7511->bridge); if (ret) { dev_err(dev, "failed to add adv7511 bridge\n"); - goto err_i2c_unregister_device; + goto err_unregister_cec; } return 0; -err_i2c_unregister_device: +err_unregister_cec: + adv7533_uninit_cec(adv7511); +err_i2c_unregister_edid: i2c_unregister_device(adv7511->i2c_edid); return ret; @@ -1049,6 +1044,7 @@ static int adv7511_remove(struct i2c_client *i2c) drm_bridge_remove(&adv7511->bridge); + adv7533_uninit_cec(adv7511); i2c_unregister_device(adv7511->i2c_edid); kfree(adv7511->edid); @@ -1057,17 +1053,23 @@ static int adv7511_remove(struct i2c_client *i2c) } static const struct i2c_device_id adv7511_i2c_ids[] = { - { "adv7511", 0 }, - { "adv7511w", 0 }, - { "adv7513", 0 }, + { "adv7511", ADV7511 }, + { "adv7511w", ADV7511 }, + { "adv7513", ADV7511 }, +#ifdef CONFIG_DRM_I2C_ADV7533 + { "adv7533", ADV7533 }, +#endif { } }; MODULE_DEVICE_TABLE(i2c, adv7511_i2c_ids); static const struct of_device_id adv7511_of_ids[] = { - { .compatible = "adi,adv7511", }, - { .compatible = "adi,adv7511w", }, - { .compatible = "adi,adv7513", }, + { .compatible = "adi,adv7511", .data = (void *)ADV7511 }, + { .compatible = "adi,adv7511w", .data = (void *)ADV7511 }, + { .compatible = "adi,adv7513", .data = (void *)ADV7511 }, +#ifdef CONFIG_DRM_I2C_ADV7533 + { .compatible = "adi,adv7533", .data = (void *)ADV7533 }, +#endif { } }; MODULE_DEVICE_TABLE(of, adv7511_of_ids); diff --git a/drivers/gpu/drm/bridge/adv7511/adv7533.c b/drivers/gpu/drm/bridge/adv7511/adv7533.c new file mode 100644 index 0000000..cb4ca64 --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7533.c @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "adv7511.h" + +static const struct reg_sequence adv7533_fixed_registers[] = { + { 0x16, 0x20 }, + { 0x9a, 0xe0 }, + { 0xba, 0x70 }, + { 0xde, 0x82 }, + { 0xe4, 0x40 }, + { 0xe5, 0x80 }, +}; + +static const struct reg_sequence adv7533_cec_fixed_registers[] = { + { 0x15, 0xd0 }, + { 0x17, 0xd0 }, + { 0x24, 0x20 }, + { 0x57, 0x11 }, +}; + +static const struct regmap_config adv7533_cec_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_RBTREE, +}; + +void adv7533_dsi_power_on(struct adv7511 *adv) +{ + /* set number of dsi lanes (hardcoded to 4 for now) */ + regmap_write(adv->regmap_cec, 0x1c, 4 << 4); + /* disable internal timing generator */ + regmap_write(adv->regmap_cec, 0x27, 0x0b); + /* enable hdmi */ + regmap_write(adv->regmap_cec, 0x03, 0x89); + /* disable test mode */ + regmap_write(adv->regmap_cec, 0x55, 0x00); + + regmap_register_patch(adv->regmap_cec, adv7533_cec_fixed_registers, + ARRAY_SIZE(adv7533_cec_fixed_registers)); +} + +void adv7533_dsi_power_off(struct adv7511 *adv) +{ + /* disable hdmi */ + regmap_write(adv->regmap_cec, 0x03, 0x0b); +} + +int adv7533_patch_registers(struct adv7511 *adv) +{ + return regmap_register_patch(adv->regmap, + adv7533_fixed_registers, + ARRAY_SIZE(adv7533_fixed_registers)); +} + +void adv7533_uninit_cec(struct adv7511 *adv) +{ + i2c_unregister_device(adv->i2c_cec); +} + +static const int cec_i2c_addr = 0x78; + +int adv7533_init_cec(struct adv7511 *adv) +{ + int ret; + + adv->i2c_cec = i2c_new_dummy(adv->i2c_main->adapter, cec_i2c_addr >> 1); + if (!adv->i2c_cec) + return -ENOMEM; + + adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec, + &adv7533_cec_regmap_config); + if (IS_ERR(adv->regmap_cec)) { + ret = PTR_ERR(adv->regmap_cec); + goto err; + } + + ret = regmap_register_patch(adv->regmap_cec, + adv7533_cec_fixed_registers, + ARRAY_SIZE(adv7533_cec_fixed_registers)); + if (ret) + goto err; + + return 0; +err: + adv7533_uninit_cec(adv); + return ret; +} -- cgit v0.10.2 From 1e4d58cd7f888522d16f221d628356befbb08468 Mon Sep 17 00:00:00 2001 From: Archit Taneja Date: Wed, 15 Jun 2016 17:01:27 +0530 Subject: drm/bridge: adv7533: Create a MIPI DSI device In order to pass DSI specific parameters to the DSI host, we need the driver to create a mipi_dsi_device DSI device that attaches to the host. Use of_graph helpers to get the DSI host DT node. Create a MIPI DSI device using this host. Finally, attach this device to the DSI host. Populate DT parameters (number of data lanes for now) that are required for DSI RX to work correctly. Hardcode few other parameters (rgb, embedded_sync) for now. Select DRM_MIPI_DSI config option only when ADV7533 support is enabled. Signed-off-by: Archit Taneja diff --git a/drivers/gpu/drm/bridge/adv7511/Kconfig b/drivers/gpu/drm/bridge/adv7511/Kconfig index eb84419..d2b0499 100644 --- a/drivers/gpu/drm/bridge/adv7511/Kconfig +++ b/drivers/gpu/drm/bridge/adv7511/Kconfig @@ -9,6 +9,7 @@ config DRM_I2C_ADV7511 config DRM_I2C_ADV7533 bool "ADV7533 encoder" depends on DRM_I2C_ADV7511 + select DRM_MIPI_DSI default y help Support for the Analog Devices ADV7533 DSI to HDMI encoder. diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h index 5dea769..3e4d47a 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -14,6 +14,7 @@ #include #include +#include #define ADV7511_REG_CHIP_REVISION 0x00 #define ADV7511_REG_N0 0x01 @@ -324,6 +325,11 @@ struct adv7511 { struct gpio_desc *gpio_pd; + /* ADV7533 DSI RX related params */ + struct device_node *host_node; + struct mipi_dsi_device *dsi; + u8 num_dsi_lanes; + enum adv7511_type type; }; @@ -333,6 +339,9 @@ void adv7533_dsi_power_off(struct adv7511 *adv); int adv7533_patch_registers(struct adv7511 *adv); void adv7533_uninit_cec(struct adv7511 *adv); int adv7533_init_cec(struct adv7511 *adv); +int adv7533_attach_dsi(struct adv7511 *adv); +void adv7533_detach_dsi(struct adv7511 *adv); +int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv); #else static inline void adv7533_dsi_power_on(struct adv7511 *adv) { @@ -355,6 +364,20 @@ static inline int adv7533_init_cec(struct adv7511 *adv) { return -ENODEV; } + +static inline int adv7533_attach_dsi(struct adv7511 *adv) +{ + return -ENODEV; +} + +static inline void adv7533_detach_dsi(struct adv7511 *adv) +{ +} + +static inline int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv) +{ + return -ENODEV; +} #endif #endif /* __DRM_I2C_ADV7511_H__ */ diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index e33702b..6586c52 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -817,6 +817,9 @@ static int adv7511_bridge_attach(struct drm_bridge *bridge) &adv7511_connector_helper_funcs); drm_mode_connector_attach_encoder(&adv->connector, bridge->encoder); + if (adv->type == ADV7533) + ret = adv7533_attach_dsi(adv); + return ret; } @@ -943,11 +946,12 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) memset(&link_config, 0, sizeof(link_config)); - if (adv7511->type == ADV7511) { + if (adv7511->type == ADV7511) ret = adv7511_parse_dt(dev->of_node, &link_config); - if (ret) - return ret; - } + else + ret = adv7533_parse_dt(dev->of_node, adv7511); + if (ret) + return ret; /* * The power down GPIO is optional. If present, toggle it from active to @@ -1042,9 +1046,13 @@ static int adv7511_remove(struct i2c_client *i2c) { struct adv7511 *adv7511 = i2c_get_clientdata(i2c); + if (adv7511->type == ADV7533) { + adv7533_detach_dsi(adv7511); + adv7533_uninit_cec(adv7511); + } + drm_bridge_remove(&adv7511->bridge); - adv7533_uninit_cec(adv7511); i2c_unregister_device(adv7511->i2c_edid); kfree(adv7511->edid); @@ -1074,6 +1082,10 @@ static const struct of_device_id adv7511_of_ids[] = { }; MODULE_DEVICE_TABLE(of, adv7511_of_ids); +static struct mipi_dsi_driver adv7533_dsi_driver = { + .driver.name = "adv7533", +}; + static struct i2c_driver adv7511_driver = { .driver = { .name = "adv7511", @@ -1084,7 +1096,23 @@ static struct i2c_driver adv7511_driver = { .remove = adv7511_remove, }; -module_i2c_driver(adv7511_driver); +static int __init adv7511_init(void) +{ + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) + mipi_dsi_driver_register(&adv7533_dsi_driver); + + return i2c_add_driver(&adv7511_driver); +} +module_init(adv7511_init); + +static void __exit adv7511_exit(void) +{ + i2c_del_driver(&adv7511_driver); + + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) + mipi_dsi_driver_unregister(&adv7533_dsi_driver); +} +module_exit(adv7511_exit); MODULE_AUTHOR("Lars-Peter Clausen "); MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver"); diff --git a/drivers/gpu/drm/bridge/adv7511/adv7533.c b/drivers/gpu/drm/bridge/adv7511/adv7533.c index cb4ca64..ecbcaa0 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7533.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7533.c @@ -11,6 +11,8 @@ * GNU General Public License for more details. */ +#include + #include "adv7511.h" static const struct reg_sequence adv7533_fixed_registers[] = { @@ -39,8 +41,10 @@ static const struct regmap_config adv7533_cec_regmap_config = { void adv7533_dsi_power_on(struct adv7511 *adv) { - /* set number of dsi lanes (hardcoded to 4 for now) */ - regmap_write(adv->regmap_cec, 0x1c, 4 << 4); + struct mipi_dsi_device *dsi = adv->dsi; + + /* set number of dsi lanes */ + regmap_write(adv->regmap_cec, 0x1c, dsi->lanes << 4); /* disable internal timing generator */ regmap_write(adv->regmap_cec, 0x27, 0x0b); /* enable hdmi */ @@ -98,3 +102,86 @@ err: adv7533_uninit_cec(adv); return ret; } + +int adv7533_attach_dsi(struct adv7511 *adv) +{ + struct device *dev = &adv->i2c_main->dev; + struct mipi_dsi_host *host; + struct mipi_dsi_device *dsi; + int ret = 0; + const struct mipi_dsi_device_info info = { .type = "adv7533", + .channel = 0, + .node = NULL, + }; + + host = of_find_mipi_dsi_host_by_node(adv->host_node); + if (!host) { + dev_err(dev, "failed to find dsi host\n"); + return -EPROBE_DEFER; + } + + dsi = mipi_dsi_device_register_full(host, &info); + if (IS_ERR(dsi)) { + dev_err(dev, "failed to create dsi device\n"); + ret = PTR_ERR(dsi); + goto err_dsi_device; + } + + adv->dsi = dsi; + + dsi->lanes = adv->num_dsi_lanes; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "failed to attach dsi to host\n"); + goto err_dsi_attach; + } + + return 0; + +err_dsi_attach: + mipi_dsi_device_unregister(dsi); +err_dsi_device: + return ret; +} + +void adv7533_detach_dsi(struct adv7511 *adv) +{ + mipi_dsi_detach(adv->dsi); + mipi_dsi_device_unregister(adv->dsi); +} + +int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv) +{ + u32 num_lanes; + struct device_node *endpoint; + + of_property_read_u32(np, "adi,dsi-lanes", &num_lanes); + + if (num_lanes < 1 || num_lanes > 4) + return -EINVAL; + + adv->num_dsi_lanes = num_lanes; + + endpoint = of_graph_get_next_endpoint(np, NULL); + if (!endpoint) + return -ENODEV; + + adv->host_node = of_graph_get_remote_port_parent(endpoint); + if (!adv->host_node) { + of_node_put(endpoint); + return -ENODEV; + } + + of_node_put(endpoint); + of_node_put(adv->host_node); + + /* TODO: Check if these need to be parsed by DT or not */ + adv->rgb = true; + adv->embedded_sync = false; + + return 0; +} -- cgit v0.10.2 From 78fa479d703c4d473746e961331aff568dc0c24f Mon Sep 17 00:00:00 2001 From: Archit Taneja Date: Wed, 15 Jun 2016 17:03:12 +0530 Subject: drm/bridge: adv7533: Use internal timing generator ADV7533 provides an internal timing generator for certain modes that it can't use the DSI clock directly. We've observed that HDMI is more stable with the internal timing generator, especially if there are instabilities in the DSI clock source. The data spec also seems to recommend the usage of the timing generator for all modes. However, on some platforms, it's reported that enabling the timing generator causes instabilities with the HDMI output. Create a DT parameter that lets a platform explicitly disable the timing generator. The timing generator is enabled by default. Signed-off-by: Archit Taneja diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h index 3e4d47a..90a8c09 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -306,6 +306,8 @@ struct adv7511 { enum drm_connector_status status; bool powered; + struct drm_display_mode curr_mode; + unsigned int f_tmds; unsigned int current_edid_segment; @@ -329,6 +331,7 @@ struct adv7511 { struct device_node *host_node; struct mipi_dsi_device *dsi; u8 num_dsi_lanes; + bool use_timing_gen; enum adv7511_type type; }; diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index 6586c52..e0c353e 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -712,6 +712,8 @@ static void adv7511_mode_set(struct adv7511 *adv7511, regmap_update_bits(adv7511->regmap, 0x17, 0x60, (vsync_polarity << 6) | (hsync_polarity << 5)); + drm_mode_copy(&adv7511->curr_mode, adj_mode); + /* * TODO Test first order 4:2:2 to 4:4:4 up conversion method, which is * supposed to give better results. diff --git a/drivers/gpu/drm/bridge/adv7511/adv7533.c b/drivers/gpu/drm/bridge/adv7511/adv7533.c index ecbcaa0..d002ac4 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7533.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7533.c @@ -39,14 +39,65 @@ static const struct regmap_config adv7533_cec_regmap_config = { .cache_type = REGCACHE_RBTREE, }; +static void adv7511_dsi_config_timing_gen(struct adv7511 *adv) +{ + struct mipi_dsi_device *dsi = adv->dsi; + struct drm_display_mode *mode = &adv->curr_mode; + unsigned int hsw, hfp, hbp, vsw, vfp, vbp; + u8 clock_div_by_lanes[] = { 6, 4, 3 }; /* 2, 3, 4 lanes */ + + hsw = mode->hsync_end - mode->hsync_start; + hfp = mode->hsync_start - mode->hdisplay; + hbp = mode->htotal - mode->hsync_end; + vsw = mode->vsync_end - mode->vsync_start; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + + /* set pixel clock divider mode */ + regmap_write(adv->regmap_cec, 0x16, + clock_div_by_lanes[dsi->lanes - 2] << 3); + + /* horizontal porch params */ + regmap_write(adv->regmap_cec, 0x28, mode->htotal >> 4); + regmap_write(adv->regmap_cec, 0x29, (mode->htotal << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x2a, hsw >> 4); + regmap_write(adv->regmap_cec, 0x2b, (hsw << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x2c, hfp >> 4); + regmap_write(adv->regmap_cec, 0x2d, (hfp << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x2e, hbp >> 4); + regmap_write(adv->regmap_cec, 0x2f, (hbp << 4) & 0xff); + + /* vertical porch params */ + regmap_write(adv->regmap_cec, 0x30, mode->vtotal >> 4); + regmap_write(adv->regmap_cec, 0x31, (mode->vtotal << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x32, vsw >> 4); + regmap_write(adv->regmap_cec, 0x33, (vsw << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x34, vfp >> 4); + regmap_write(adv->regmap_cec, 0x35, (vfp << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x36, vbp >> 4); + regmap_write(adv->regmap_cec, 0x37, (vbp << 4) & 0xff); +} + void adv7533_dsi_power_on(struct adv7511 *adv) { struct mipi_dsi_device *dsi = adv->dsi; + if (adv->use_timing_gen) + adv7511_dsi_config_timing_gen(adv); + /* set number of dsi lanes */ regmap_write(adv->regmap_cec, 0x1c, dsi->lanes << 4); - /* disable internal timing generator */ - regmap_write(adv->regmap_cec, 0x27, 0x0b); + + if (adv->use_timing_gen) { + /* reset internal timing generator */ + regmap_write(adv->regmap_cec, 0x27, 0xcb); + regmap_write(adv->regmap_cec, 0x27, 0x8b); + regmap_write(adv->regmap_cec, 0x27, 0xcb); + } else { + /* disable internal timing generator */ + regmap_write(adv->regmap_cec, 0x27, 0x0b); + } + /* enable hdmi */ regmap_write(adv->regmap_cec, 0x03, 0x89); /* disable test mode */ @@ -60,6 +111,8 @@ void adv7533_dsi_power_off(struct adv7511 *adv) { /* disable hdmi */ regmap_write(adv->regmap_cec, 0x03, 0x0b); + /* disable internal timing generator */ + regmap_write(adv->regmap_cec, 0x27, 0x0b); } int adv7533_patch_registers(struct adv7511 *adv) @@ -179,6 +232,9 @@ int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv) of_node_put(endpoint); of_node_put(adv->host_node); + adv->use_timing_gen = !of_property_read_bool(np, + "adi,disable-timing-generator"); + /* TODO: Check if these need to be parsed by DT or not */ adv->rgb = true; adv->embedded_sync = false; -- cgit v0.10.2 From 62b2f026cd8e42df53dc8a6ed76594e51ab41735 Mon Sep 17 00:00:00 2001 From: Archit Taneja Date: Fri, 17 Jun 2016 12:15:52 +0530 Subject: drm/bridge: adv7533: Change number of DSI lanes dynamically Lower modes on ADV7533 require lower number of DSI lanes for correct operation. If ADV7533 is being used with 4 DSI lanes, then switch the lanes to 3 when the target mode's pixel clock is less than 80 Mhz. Based on patch by Andy Green Signed-off-by: Archit Taneja diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h index 90a8c09..161c923 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -339,6 +339,7 @@ struct adv7511 { #ifdef CONFIG_DRM_I2C_ADV7533 void adv7533_dsi_power_on(struct adv7511 *adv); void adv7533_dsi_power_off(struct adv7511 *adv); +void adv7533_mode_set(struct adv7511 *adv, struct drm_display_mode *mode); int adv7533_patch_registers(struct adv7511 *adv); void adv7533_uninit_cec(struct adv7511 *adv); int adv7533_init_cec(struct adv7511 *adv); @@ -354,6 +355,11 @@ static inline void adv7533_dsi_power_off(struct adv7511 *adv) { } +static inline void adv7533_mode_set(struct adv7511 *adv, + struct drm_display_mode *mode) +{ +} + static inline int adv7533_patch_registers(struct adv7511 *adv) { return -ENODEV; diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index e0c353e..ec8fb2e 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -712,6 +712,9 @@ static void adv7511_mode_set(struct adv7511 *adv7511, regmap_update_bits(adv7511->regmap, 0x17, 0x60, (vsync_polarity << 6) | (hsync_polarity << 5)); + if (adv7511->type == ADV7533) + adv7533_mode_set(adv7511, adj_mode); + drm_mode_copy(&adv7511->curr_mode, adj_mode); /* diff --git a/drivers/gpu/drm/bridge/adv7511/adv7533.c b/drivers/gpu/drm/bridge/adv7511/adv7533.c index d002ac4..5eebd15 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7533.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7533.c @@ -115,6 +115,28 @@ void adv7533_dsi_power_off(struct adv7511 *adv) regmap_write(adv->regmap_cec, 0x27, 0x0b); } +void adv7533_mode_set(struct adv7511 *adv, struct drm_display_mode *mode) +{ + struct mipi_dsi_device *dsi = adv->dsi; + int lanes, ret; + + if (adv->num_dsi_lanes != 4) + return; + + if (mode->clock > 80000) + lanes = 4; + else + lanes = 3; + + if (lanes != dsi->lanes) { + mipi_dsi_detach(dsi); + dsi->lanes = lanes; + ret = mipi_dsi_attach(dsi); + if (ret) + dev_err(&dsi->dev, "failed to change host lanes\n"); + } +} + int adv7533_patch_registers(struct adv7511 *adv) { return regmap_register_patch(adv->regmap, -- cgit v0.10.2 From 2cc961df3e0b3a675328e2d702a789247c6dbdee Mon Sep 17 00:00:00 2001 From: Archit Taneja Date: Wed, 24 Feb 2016 17:42:58 +0530 Subject: dt-bindings: drm/bridge: Update bindings for ADV7533 Add description of ADV7533. Add the required and optional properties that are specific to it. Cc: devicetree@vger.kernel.org Acked-by: Rob Herring Signed-off-by: Archit Taneja diff --git a/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt b/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt index 96c25ee..6532a59 100644 --- a/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt +++ b/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt @@ -1,13 +1,19 @@ -Analog Device ADV7511(W)/13 HDMI Encoders +Analog Device ADV7511(W)/13/33 HDMI Encoders ----------------------------------------- -The ADV7511, ADV7511W and ADV7513 are HDMI audio and video transmitters +The ADV7511, ADV7511W, ADV7513 and ADV7533 are HDMI audio and video transmitters compatible with HDMI 1.4 and DVI 1.0. They support color space conversion, -S/PDIF, CEC and HDCP. +S/PDIF, CEC and HDCP. ADV7533 supports the DSI interface for input pixels, while +the others support RGB interface. Required properties: -- compatible: Should be one of "adi,adv7511", "adi,adv7511w" or "adi,adv7513" +- compatible: Should be one of: + "adi,adv7511" + "adi,adv7511w" + "adi,adv7513" + "adi,adv7533" + - reg: I2C slave address The ADV7511 supports a large number of input data formats that differ by their @@ -32,6 +38,11 @@ The following input format properties are required except in "rgb 1x" and - adi,input-justification: The input bit justification ("left", "evenly", "right"). +The following properties are required for ADV7533: + +- adi,dsi-lanes: Number of DSI data lanes connected to the DSI host. It should + be one of 1, 2, 3 or 4. + Optional properties: - interrupts: Specifier for the ADV7511 interrupt @@ -42,13 +53,18 @@ Optional properties: - adi,embedded-sync: The input uses synchronization signals embedded in the data stream (similar to BT.656). Defaults to separate H/V synchronization signals. +- adi,disable-timing-generator: Only for ADV7533. Disables the internal timing + generator. The chip will rely on the sync signals in the DSI data lanes, + rather than generate its own timings for HDMI output. Required nodes: The ADV7511 has two video ports. Their connections are modelled using the OF graph bindings specified in Documentation/devicetree/bindings/graph.txt. -- Video port 0 for the RGB or YUV input +- Video port 0 for the RGB, YUV or DSI input. In the case of ADV7533, the + remote endpoint phandle should be a reference to a valid mipi_dsi_host device + node. - Video port 1 for the HDMI output -- cgit v0.10.2 From 22a935622314b2e798dda16452da0c5c521da8ad Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Wed, 13 Jul 2016 09:07:10 +0200 Subject: dt-bindings: tc358767: add DT documentation Add DT binding documentation for the Toshiba TC358767 eDP bridge. Acked-by: Rob Herring Signed-off-by: Philipp Zabel Signed-off-by: Archit Taneja diff --git a/Documentation/devicetree/bindings/display/bridge/toshiba,tc358767.txt b/Documentation/devicetree/bindings/display/bridge/toshiba,tc358767.txt new file mode 100644 index 0000000..e3f6aa6 --- /dev/null +++ b/Documentation/devicetree/bindings/display/bridge/toshiba,tc358767.txt @@ -0,0 +1,53 @@ +Toshiba TC358767 eDP bridge bindings + +Required properties: + - compatible: "toshiba,tc358767" + - reg: i2c address of the bridge, 0x68 or 0x0f, depending on bootstrap pins + - clock-names: should be "ref" + - clocks: OF device-tree clock specification for refclk input. The reference + clock rate must be 13 MHz, 19.2 MHz, 26 MHz, or 38.4 MHz. + +Optional properties: + - shutdown-gpios: OF device-tree gpio specification for SD pin + (active high shutdown input) + - reset-gpios: OF device-tree gpio specification for RSTX pin + (active low system reset) + - ports: the ports node can contain video interface port nodes to connect + to a DPI/DSI source and to an eDP/DP sink according to [1][2]: + - port@0: DSI input port + - port@1: DPI input port + - port@2: eDP/DP output port + +[1]: Documentation/devicetree/bindings/graph.txt +[2]: Documentation/devicetree/bindings/media/video-interfaces.txt + +Example: + edp-bridge@68 { + compatible = "toshiba,tc358767"; + reg = <0x68>; + shutdown-gpios = <&gpio3 23 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio3 24 GPIO_ACTIVE_LOW>; + clock-names = "ref"; + clocks = <&edp_refclk>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + + bridge_in: endpoint { + remote-endpoint = <&dpi_out>; + }; + }; + + port@2 { + reg = <2>; + + bridge_out: endpoint { + remote-endpoint = <&panel_in>; + }; + }; + }; + }; -- cgit v0.10.2 From 7caff0fc4296eba5e2e473e6719726c65f1b7c31 Mon Sep 17 00:00:00 2001 From: Andrey Gusakov Date: Wed, 13 Jul 2016 13:19:59 +0530 Subject: drm/bridge: tc358767: Add DPI to eDP bridge driver Add a drm_bridge driver for the Toshiba TC358767 DPI/DSI to eDP/DP bridge. Currently only DPI input with 24-bit RGB is supported. Signed-off-by: Andrey Gusakov Signed-off-by: Philipp Zabel Signed-off-by: Archit Taneja diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index fd7bd7d..b590e67 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -58,6 +58,15 @@ config DRM_SII902X ---help--- Silicon Image sii902x bridge chip driver. +config DRM_TOSHIBA_TC358767 + tristate "Toshiba TC358767 eDP bridge" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + select DRM_PANEL + ---help--- + Toshiba TC358767 eDP bridge chip driver. + source "drivers/gpu/drm/bridge/analogix/Kconfig" source "drivers/gpu/drm/bridge/adv7511/Kconfig" diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index d08bdd1..efdb07e 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -6,5 +6,6 @@ obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_SII902X) += sii902x.o +obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/ diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c new file mode 100644 index 0000000..a09825d --- /dev/null +++ b/drivers/gpu/drm/bridge/tc358767.c @@ -0,0 +1,1413 @@ +/* + * tc358767 eDP bridge driver + * + * Copyright (C) 2016 CogentEmbedded Inc + * Author: Andrey Gusakov + * + * Copyright (C) 2016 Pengutronix, Philipp Zabel + * + * Initially based on: drivers/gpu/drm/i2c/tda998x_drv.c + * + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* Registers */ + +/* Display Parallel Interface */ +#define DPIPXLFMT 0x0440 +#define VS_POL_ACTIVE_LOW (1 << 10) +#define HS_POL_ACTIVE_LOW (1 << 9) +#define DE_POL_ACTIVE_HIGH (0 << 8) +#define SUB_CFG_TYPE_CONFIG1 (0 << 2) /* LSB aligned */ +#define SUB_CFG_TYPE_CONFIG2 (1 << 2) /* Loosely Packed */ +#define SUB_CFG_TYPE_CONFIG3 (2 << 2) /* LSB aligned 8-bit */ +#define DPI_BPP_RGB888 (0 << 0) +#define DPI_BPP_RGB666 (1 << 0) +#define DPI_BPP_RGB565 (2 << 0) + +/* Video Path */ +#define VPCTRL0 0x0450 +#define OPXLFMT_RGB666 (0 << 8) +#define OPXLFMT_RGB888 (1 << 8) +#define FRMSYNC_DISABLED (0 << 4) /* Video Timing Gen Disabled */ +#define FRMSYNC_ENABLED (1 << 4) /* Video Timing Gen Enabled */ +#define MSF_DISABLED (0 << 0) /* Magic Square FRC disabled */ +#define MSF_ENABLED (1 << 0) /* Magic Square FRC enabled */ +#define HTIM01 0x0454 +#define HTIM02 0x0458 +#define VTIM01 0x045c +#define VTIM02 0x0460 +#define VFUEN0 0x0464 +#define VFUEN BIT(0) /* Video Frame Timing Upload */ + +/* System */ +#define TC_IDREG 0x0500 +#define SYSCTRL 0x0510 +#define DP0_AUDSRC_NO_INPUT (0 << 3) +#define DP0_AUDSRC_I2S_RX (1 << 3) +#define DP0_VIDSRC_NO_INPUT (0 << 0) +#define DP0_VIDSRC_DSI_RX (1 << 0) +#define DP0_VIDSRC_DPI_RX (2 << 0) +#define DP0_VIDSRC_COLOR_BAR (3 << 0) + +/* Control */ +#define DP0CTL 0x0600 +#define VID_MN_GEN BIT(6) /* Auto-generate M/N values */ +#define EF_EN BIT(5) /* Enable Enhanced Framing */ +#define VID_EN BIT(1) /* Video transmission enable */ +#define DP_EN BIT(0) /* Enable DPTX function */ + +/* Clocks */ +#define DP0_VIDMNGEN0 0x0610 +#define DP0_VIDMNGEN1 0x0614 +#define DP0_VMNGENSTATUS 0x0618 + +/* Main Channel */ +#define DP0_SECSAMPLE 0x0640 +#define DP0_VIDSYNCDELAY 0x0644 +#define DP0_TOTALVAL 0x0648 +#define DP0_STARTVAL 0x064c +#define DP0_ACTIVEVAL 0x0650 +#define DP0_SYNCVAL 0x0654 +#define DP0_MISC 0x0658 +#define TU_SIZE_RECOMMENDED (0x3f << 16) /* LSCLK cycles per TU */ +#define BPC_6 (0 << 5) +#define BPC_8 (1 << 5) + +/* AUX channel */ +#define DP0_AUXCFG0 0x0660 +#define DP0_AUXCFG1 0x0664 +#define AUX_RX_FILTER_EN BIT(16) + +#define DP0_AUXADDR 0x0668 +#define DP0_AUXWDATA(i) (0x066c + (i) * 4) +#define DP0_AUXRDATA(i) (0x067c + (i) * 4) +#define DP0_AUXSTATUS 0x068c +#define AUX_STATUS_MASK 0xf0 +#define AUX_STATUS_SHIFT 4 +#define AUX_TIMEOUT BIT(1) +#define AUX_BUSY BIT(0) +#define DP0_AUXI2CADR 0x0698 + +/* Link Training */ +#define DP0_SRCCTRL 0x06a0 +#define DP0_SRCCTRL_SCRMBLDIS BIT(13) +#define DP0_SRCCTRL_EN810B BIT(12) +#define DP0_SRCCTRL_NOTP (0 << 8) +#define DP0_SRCCTRL_TP1 (1 << 8) +#define DP0_SRCCTRL_TP2 (2 << 8) +#define DP0_SRCCTRL_LANESKEW BIT(7) +#define DP0_SRCCTRL_SSCG BIT(3) +#define DP0_SRCCTRL_LANES_1 (0 << 2) +#define DP0_SRCCTRL_LANES_2 (1 << 2) +#define DP0_SRCCTRL_BW27 (1 << 1) +#define DP0_SRCCTRL_BW162 (0 << 1) +#define DP0_SRCCTRL_AUTOCORRECT BIT(0) +#define DP0_LTSTAT 0x06d0 +#define LT_LOOPDONE BIT(13) +#define LT_STATUS_MASK (0x1f << 8) +#define LT_CHANNEL1_EQ_BITS (DP_CHANNEL_EQ_BITS << 4) +#define LT_INTERLANE_ALIGN_DONE BIT(3) +#define LT_CHANNEL0_EQ_BITS (DP_CHANNEL_EQ_BITS) +#define DP0_SNKLTCHGREQ 0x06d4 +#define DP0_LTLOOPCTRL 0x06d8 +#define DP0_SNKLTCTRL 0x06e4 + +/* PHY */ +#define DP_PHY_CTRL 0x0800 +#define DP_PHY_RST BIT(28) /* DP PHY Global Soft Reset */ +#define BGREN BIT(25) /* AUX PHY BGR Enable */ +#define PWR_SW_EN BIT(24) /* PHY Power Switch Enable */ +#define PHY_M1_RST BIT(12) /* Reset PHY1 Main Channel */ +#define PHY_RDY BIT(16) /* PHY Main Channels Ready */ +#define PHY_M0_RST BIT(8) /* Reset PHY0 Main Channel */ +#define PHY_A0_EN BIT(1) /* PHY Aux Channel0 Enable */ +#define PHY_M0_EN BIT(0) /* PHY Main Channel0 Enable */ + +/* PLL */ +#define DP0_PLLCTRL 0x0900 +#define DP1_PLLCTRL 0x0904 /* not defined in DS */ +#define PXL_PLLCTRL 0x0908 +#define PLLUPDATE BIT(2) +#define PLLBYP BIT(1) +#define PLLEN BIT(0) +#define PXL_PLLPARAM 0x0914 +#define IN_SEL_REFCLK (0 << 14) +#define SYS_PLLPARAM 0x0918 +#define REF_FREQ_38M4 (0 << 8) /* 38.4 MHz */ +#define REF_FREQ_19M2 (1 << 8) /* 19.2 MHz */ +#define REF_FREQ_26M (2 << 8) /* 26 MHz */ +#define REF_FREQ_13M (3 << 8) /* 13 MHz */ +#define SYSCLK_SEL_LSCLK (0 << 4) +#define LSCLK_DIV_1 (0 << 0) +#define LSCLK_DIV_2 (1 << 0) + +/* Test & Debug */ +#define TSTCTL 0x0a00 +#define PLL_DBG 0x0a04 + +static bool tc_test_pattern; +module_param_named(test, tc_test_pattern, bool, 0644); + +struct tc_edp_link { + struct drm_dp_link base; + u8 assr; + int scrambler_dis; + int spread; + int coding8b10b; + u8 swing; + u8 preemp; +}; + +struct tc_data { + struct device *dev; + struct regmap *regmap; + struct drm_dp_aux aux; + + struct drm_bridge bridge; + struct drm_connector connector; + struct drm_panel *panel; + + /* link settings */ + struct tc_edp_link link; + + /* display edid */ + struct edid *edid; + /* current mode */ + struct drm_display_mode *mode; + + u32 rev; + u8 assr; + + struct gpio_desc *sd_gpio; + struct gpio_desc *reset_gpio; + struct clk *refclk; +}; + +static inline struct tc_data *aux_to_tc(struct drm_dp_aux *a) +{ + return container_of(a, struct tc_data, aux); +} + +static inline struct tc_data *bridge_to_tc(struct drm_bridge *b) +{ + return container_of(b, struct tc_data, bridge); +} + +static inline struct tc_data *connector_to_tc(struct drm_connector *c) +{ + return container_of(c, struct tc_data, connector); +} + +/* Simple macros to avoid repeated error checks */ +#define tc_write(reg, var) \ + do { \ + ret = regmap_write(tc->regmap, reg, var); \ + if (ret) \ + goto err; \ + } while (0) +#define tc_read(reg, var) \ + do { \ + ret = regmap_read(tc->regmap, reg, var); \ + if (ret) \ + goto err; \ + } while (0) + +static inline int tc_poll_timeout(struct regmap *map, unsigned int addr, + unsigned int cond_mask, + unsigned int cond_value, + unsigned long sleep_us, u64 timeout_us) +{ + ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); + unsigned int val; + int ret; + + for (;;) { + ret = regmap_read(map, addr, &val); + if (ret) + break; + if ((val & cond_mask) == cond_value) + break; + if (timeout_us && ktime_compare(ktime_get(), timeout) > 0) { + ret = regmap_read(map, addr, &val); + break; + } + if (sleep_us) + usleep_range((sleep_us >> 2) + 1, sleep_us); + } + return ret ?: (((val & cond_mask) == cond_value) ? 0 : -ETIMEDOUT); +} + +static int tc_aux_wait_busy(struct tc_data *tc, unsigned int timeout_ms) +{ + return tc_poll_timeout(tc->regmap, DP0_AUXSTATUS, AUX_BUSY, 0, + 1000, 1000 * timeout_ms); +} + +static int tc_aux_get_status(struct tc_data *tc, u8 *reply) +{ + int ret; + u32 value; + + ret = regmap_read(tc->regmap, DP0_AUXSTATUS, &value); + if (ret < 0) + return ret; + if (value & AUX_BUSY) { + if (value & AUX_TIMEOUT) { + dev_err(tc->dev, "i2c access timeout!\n"); + return -ETIMEDOUT; + } + return -EBUSY; + } + + *reply = (value & AUX_STATUS_MASK) >> AUX_STATUS_SHIFT; + return 0; +} + +static ssize_t tc_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct tc_data *tc = aux_to_tc(aux); + size_t size = min_t(size_t, 8, msg->size); + u8 request = msg->request & ~DP_AUX_I2C_MOT; + u8 *buf = msg->buffer; + u32 tmp = 0; + int i = 0; + int ret; + + if (size == 0) + return 0; + + ret = tc_aux_wait_busy(tc, 100); + if (ret) + goto err; + + if (request == DP_AUX_I2C_WRITE || request == DP_AUX_NATIVE_WRITE) { + /* Store data */ + while (i < size) { + if (request == DP_AUX_NATIVE_WRITE) + tmp = tmp | (buf[i] << (8 * (i & 0x3))); + else + tmp = (tmp << 8) | buf[i]; + i++; + if (((i % 4) == 0) || (i == size)) { + tc_write(DP0_AUXWDATA(i >> 2), tmp); + tmp = 0; + } + } + } else if (request != DP_AUX_I2C_READ && + request != DP_AUX_NATIVE_READ) { + return -EINVAL; + } + + /* Store address */ + tc_write(DP0_AUXADDR, msg->address); + /* Start transfer */ + tc_write(DP0_AUXCFG0, ((size - 1) << 8) | request); + + ret = tc_aux_wait_busy(tc, 100); + if (ret) + goto err; + + ret = tc_aux_get_status(tc, &msg->reply); + if (ret) + goto err; + + if (request == DP_AUX_I2C_READ || request == DP_AUX_NATIVE_READ) { + /* Read data */ + while (i < size) { + if ((i % 4) == 0) + tc_read(DP0_AUXRDATA(i >> 2), &tmp); + buf[i] = tmp & 0xff; + tmp = tmp >> 8; + i++; + } + } + + return size; +err: + return ret; +} + +static const char * const training_pattern1_errors[] = { + "No errors", + "Aux write error", + "Aux read error", + "Max voltage reached error", + "Loop counter expired error", + "res", "res", "res" +}; + +static const char * const training_pattern2_errors[] = { + "No errors", + "Aux write error", + "Aux read error", + "Clock recovery failed error", + "Loop counter expired error", + "res", "res", "res" +}; + +static u32 tc_srcctrl(struct tc_data *tc) +{ + /* + * No training pattern, skew lane 1 data by two LSCLK cycles with + * respect to lane 0 data, AutoCorrect Mode = 0 + */ + u32 reg = DP0_SRCCTRL_NOTP | DP0_SRCCTRL_LANESKEW; + + if (tc->link.scrambler_dis) + reg |= DP0_SRCCTRL_SCRMBLDIS; /* Scrambler Disabled */ + if (tc->link.coding8b10b) + /* Enable 8/10B Encoder (TxData[19:16] not used) */ + reg |= DP0_SRCCTRL_EN810B; + if (tc->link.spread) + reg |= DP0_SRCCTRL_SSCG; /* Spread Spectrum Enable */ + if (tc->link.base.num_lanes == 2) + reg |= DP0_SRCCTRL_LANES_2; /* Two Main Channel Lanes */ + if (tc->link.base.rate != 162000) + reg |= DP0_SRCCTRL_BW27; /* 2.7 Gbps link */ + return reg; +} + +static void tc_wait_pll_lock(struct tc_data *tc) +{ + /* Wait for PLL to lock: up to 2.09 ms, depending on refclk */ + usleep_range(3000, 6000); +} + +static int tc_pxl_pll_en(struct tc_data *tc, u32 refclk, u32 pixelclock) +{ + int ret; + int i_pre, best_pre = 1; + int i_post, best_post = 1; + int div, best_div = 1; + int mul, best_mul = 1; + int delta, best_delta; + int ext_div[] = {1, 2, 3, 5, 7}; + int best_pixelclock = 0; + int vco_hi = 0; + + dev_dbg(tc->dev, "PLL: requested %d pixelclock, ref %d\n", pixelclock, + refclk); + best_delta = pixelclock; + /* Loop over all possible ext_divs, skipping invalid configurations */ + for (i_pre = 0; i_pre < ARRAY_SIZE(ext_div); i_pre++) { + /* + * refclk / ext_pre_div should be in the 1 to 200 MHz range. + * We don't allow any refclk > 200 MHz, only check lower bounds. + */ + if (refclk / ext_div[i_pre] < 1000000) + continue; + for (i_post = 0; i_post < ARRAY_SIZE(ext_div); i_post++) { + for (div = 1; div <= 16; div++) { + u32 clk; + u64 tmp; + + tmp = pixelclock * ext_div[i_pre] * + ext_div[i_post] * div; + do_div(tmp, refclk); + mul = tmp; + + /* Check limits */ + if ((mul < 1) || (mul > 128)) + continue; + + clk = (refclk / ext_div[i_pre] / div) * mul; + /* + * refclk * mul / (ext_pre_div * pre_div) + * should be in the 150 to 650 MHz range + */ + if ((clk > 650000000) || (clk < 150000000)) + continue; + + clk = clk / ext_div[i_post]; + delta = clk - pixelclock; + + if (abs(delta) < abs(best_delta)) { + best_pre = i_pre; + best_post = i_post; + best_div = div; + best_mul = mul; + best_delta = delta; + best_pixelclock = clk; + } + } + } + } + if (best_pixelclock == 0) { + dev_err(tc->dev, "Failed to calc clock for %d pixelclock\n", + pixelclock); + return -EINVAL; + } + + dev_dbg(tc->dev, "PLL: got %d, delta %d\n", best_pixelclock, + best_delta); + dev_dbg(tc->dev, "PLL: %d / %d / %d * %d / %d\n", refclk, + ext_div[best_pre], best_div, best_mul, ext_div[best_post]); + + /* if VCO >= 300 MHz */ + if (refclk / ext_div[best_pre] / best_div * best_mul >= 300000000) + vco_hi = 1; + /* see DS */ + if (best_div == 16) + best_div = 0; + if (best_mul == 128) + best_mul = 0; + + /* Power up PLL and switch to bypass */ + tc_write(PXL_PLLCTRL, PLLBYP | PLLEN); + + tc_write(PXL_PLLPARAM, + (vco_hi << 24) | /* For PLL VCO >= 300 MHz = 1 */ + (ext_div[best_pre] << 20) | /* External Pre-divider */ + (ext_div[best_post] << 16) | /* External Post-divider */ + IN_SEL_REFCLK | /* Use RefClk as PLL input */ + (best_div << 8) | /* Divider for PLL RefClk */ + (best_mul << 0)); /* Multiplier for PLL */ + + /* Force PLL parameter update and disable bypass */ + tc_write(PXL_PLLCTRL, PLLUPDATE | PLLEN); + + tc_wait_pll_lock(tc); + + return 0; +err: + return ret; +} + +static int tc_pxl_pll_dis(struct tc_data *tc) +{ + /* Enable PLL bypass, power down PLL */ + return regmap_write(tc->regmap, PXL_PLLCTRL, PLLBYP); +} + +static int tc_stream_clock_calc(struct tc_data *tc) +{ + int ret; + /* + * If the Stream clock and Link Symbol clock are + * asynchronous with each other, the value of M changes over + * time. This way of generating link clock and stream + * clock is called Asynchronous Clock mode. The value M + * must change while the value N stays constant. The + * value of N in this Asynchronous Clock mode must be set + * to 2^15 or 32,768. + * + * LSCLK = 1/10 of high speed link clock + * + * f_STRMCLK = M/N * f_LSCLK + * M/N = f_STRMCLK / f_LSCLK + * + */ + tc_write(DP0_VIDMNGEN1, 32768); + + return 0; +err: + return ret; +} + +static int tc_aux_link_setup(struct tc_data *tc) +{ + unsigned long rate; + u32 value; + int ret; + + rate = clk_get_rate(tc->refclk); + switch (rate) { + case 38400000: + value = REF_FREQ_38M4; + break; + case 26000000: + value = REF_FREQ_26M; + break; + case 19200000: + value = REF_FREQ_19M2; + break; + case 13000000: + value = REF_FREQ_13M; + break; + default: + dev_err(tc->dev, "Invalid refclk rate: %lu Hz\n", rate); + return -EINVAL; + } + + /* Setup DP-PHY / PLL */ + value |= SYSCLK_SEL_LSCLK | LSCLK_DIV_2; + tc_write(SYS_PLLPARAM, value); + + tc_write(DP_PHY_CTRL, BGREN | PWR_SW_EN | BIT(2) | PHY_A0_EN); + + /* + * Initially PLLs are in bypass. Force PLL parameter update, + * disable PLL bypass, enable PLL + */ + tc_write(DP0_PLLCTRL, PLLUPDATE | PLLEN); + tc_wait_pll_lock(tc); + + tc_write(DP1_PLLCTRL, PLLUPDATE | PLLEN); + tc_wait_pll_lock(tc); + + ret = tc_poll_timeout(tc->regmap, DP_PHY_CTRL, PHY_RDY, PHY_RDY, 1, + 1000); + if (ret == -ETIMEDOUT) { + dev_err(tc->dev, "Timeout waiting for PHY to become ready"); + return ret; + } else if (ret) + goto err; + + /* Setup AUX link */ + tc_write(DP0_AUXCFG1, AUX_RX_FILTER_EN | + (0x06 << 8) | /* Aux Bit Period Calculator Threshold */ + (0x3f << 0)); /* Aux Response Timeout Timer */ + + return 0; +err: + dev_err(tc->dev, "tc_aux_link_setup failed: %d\n", ret); + return ret; +} + +static int tc_get_display_props(struct tc_data *tc) +{ + int ret; + /* temp buffer */ + u8 tmp[8]; + + /* Read DP Rx Link Capability */ + ret = drm_dp_link_probe(&tc->aux, &tc->link.base); + if (ret < 0) + goto err_dpcd_read; + if ((tc->link.base.rate != 162000) && (tc->link.base.rate != 270000)) + goto err_dpcd_inval; + + ret = drm_dp_dpcd_readb(&tc->aux, DP_MAX_DOWNSPREAD, tmp); + if (ret < 0) + goto err_dpcd_read; + tc->link.spread = tmp[0] & BIT(0); /* 0.5% down spread */ + + ret = drm_dp_dpcd_readb(&tc->aux, DP_MAIN_LINK_CHANNEL_CODING, tmp); + if (ret < 0) + goto err_dpcd_read; + tc->link.coding8b10b = tmp[0] & BIT(0); + tc->link.scrambler_dis = 0; + /* read assr */ + ret = drm_dp_dpcd_readb(&tc->aux, DP_EDP_CONFIGURATION_SET, tmp); + if (ret < 0) + goto err_dpcd_read; + tc->link.assr = tmp[0] & DP_ALTERNATE_SCRAMBLER_RESET_ENABLE; + + dev_dbg(tc->dev, "DPCD rev: %d.%d, rate: %s, lanes: %d, framing: %s\n", + tc->link.base.revision >> 4, tc->link.base.revision & 0x0f, + (tc->link.base.rate == 162000) ? "1.62Gbps" : "2.7Gbps", + tc->link.base.num_lanes, + (tc->link.base.capabilities & DP_LINK_CAP_ENHANCED_FRAMING) ? + "enhanced" : "non-enhanced"); + dev_dbg(tc->dev, "ANSI 8B/10B: %d\n", tc->link.coding8b10b); + dev_dbg(tc->dev, "Display ASSR: %d, TC358767 ASSR: %d\n", + tc->link.assr, tc->assr); + + return 0; + +err_dpcd_read: + dev_err(tc->dev, "failed to read DPCD: %d\n", ret); + return ret; +err_dpcd_inval: + dev_err(tc->dev, "invalid DPCD\n"); + return -EINVAL; +} + +static int tc_set_video_mode(struct tc_data *tc, struct drm_display_mode *mode) +{ + int ret; + int vid_sync_dly; + int max_tu_symbol; + + int left_margin = mode->htotal - mode->hsync_end; + int right_margin = mode->hsync_start - mode->hdisplay; + int hsync_len = mode->hsync_end - mode->hsync_start; + int upper_margin = mode->vtotal - mode->vsync_end; + int lower_margin = mode->vsync_start - mode->vdisplay; + int vsync_len = mode->vsync_end - mode->vsync_start; + + dev_dbg(tc->dev, "set mode %dx%d\n", + mode->hdisplay, mode->vdisplay); + dev_dbg(tc->dev, "H margin %d,%d sync %d\n", + left_margin, right_margin, hsync_len); + dev_dbg(tc->dev, "V margin %d,%d sync %d\n", + upper_margin, lower_margin, vsync_len); + dev_dbg(tc->dev, "total: %dx%d\n", mode->htotal, mode->vtotal); + + + /* LCD Ctl Frame Size */ + tc_write(VPCTRL0, (0x40 << 20) /* VSDELAY */ | + OPXLFMT_RGB888 | FRMSYNC_DISABLED | MSF_DISABLED); + tc_write(HTIM01, (left_margin << 16) | /* H back porch */ + (hsync_len << 0)); /* Hsync */ + tc_write(HTIM02, (right_margin << 16) | /* H front porch */ + (mode->hdisplay << 0)); /* width */ + tc_write(VTIM01, (upper_margin << 16) | /* V back porch */ + (vsync_len << 0)); /* Vsync */ + tc_write(VTIM02, (lower_margin << 16) | /* V front porch */ + (mode->vdisplay << 0)); /* height */ + tc_write(VFUEN0, VFUEN); /* update settings */ + + /* Test pattern settings */ + tc_write(TSTCTL, + (120 << 24) | /* Red Color component value */ + (20 << 16) | /* Green Color component value */ + (99 << 8) | /* Blue Color component value */ + (1 << 4) | /* Enable I2C Filter */ + (2 << 0) | /* Color bar Mode */ + 0); + + /* DP Main Stream Attributes */ + vid_sync_dly = hsync_len + left_margin + mode->hdisplay; + tc_write(DP0_VIDSYNCDELAY, + (0x003e << 16) | /* thresh_dly */ + (vid_sync_dly << 0)); + + tc_write(DP0_TOTALVAL, (mode->vtotal << 16) | (mode->htotal)); + + tc_write(DP0_STARTVAL, + ((upper_margin + vsync_len) << 16) | + ((left_margin + hsync_len) << 0)); + + tc_write(DP0_ACTIVEVAL, (mode->vdisplay << 16) | (mode->hdisplay)); + + tc_write(DP0_SYNCVAL, (vsync_len << 16) | (hsync_len << 0)); + + tc_write(DPIPXLFMT, VS_POL_ACTIVE_LOW | HS_POL_ACTIVE_LOW | + DE_POL_ACTIVE_HIGH | SUB_CFG_TYPE_CONFIG1 | DPI_BPP_RGB888); + + /* + * Recommended maximum number of symbols transferred in a transfer unit: + * DIV_ROUND_UP((input active video bandwidth in bytes) * tu_size, + * (output active video bandwidth in bytes)) + * Must be less than tu_size. + */ + max_tu_symbol = TU_SIZE_RECOMMENDED - 1; + tc_write(DP0_MISC, (max_tu_symbol << 23) | TU_SIZE_RECOMMENDED | BPC_8); + + return 0; +err: + return ret; +} + +static int tc_link_training(struct tc_data *tc, int pattern) +{ + const char * const *errors; + u32 srcctrl = tc_srcctrl(tc) | DP0_SRCCTRL_SCRMBLDIS | + DP0_SRCCTRL_AUTOCORRECT; + int timeout; + int retry; + u32 value; + int ret; + + if (pattern == DP_TRAINING_PATTERN_1) { + srcctrl |= DP0_SRCCTRL_TP1; + errors = training_pattern1_errors; + } else { + srcctrl |= DP0_SRCCTRL_TP2; + errors = training_pattern2_errors; + } + + /* Set DPCD 0x102 for Training Part 1 or 2 */ + tc_write(DP0_SNKLTCTRL, DP_LINK_SCRAMBLING_DISABLE | pattern); + + tc_write(DP0_LTLOOPCTRL, + (0x0f << 28) | /* Defer Iteration Count */ + (0x0f << 24) | /* Loop Iteration Count */ + (0x0d << 0)); /* Loop Timer Delay */ + + retry = 5; + do { + /* Set DP0 Training Pattern */ + tc_write(DP0_SRCCTRL, srcctrl); + + /* Enable DP0 to start Link Training */ + tc_write(DP0CTL, DP_EN); + + /* wait */ + timeout = 1000; + do { + tc_read(DP0_LTSTAT, &value); + udelay(1); + } while ((!(value & LT_LOOPDONE)) && (--timeout)); + if (timeout == 0) { + dev_err(tc->dev, "Link training timeout!\n"); + } else { + int pattern = (value >> 11) & 0x3; + int error = (value >> 8) & 0x7; + + dev_dbg(tc->dev, + "Link training phase %d done after %d uS: %s\n", + pattern, 1000 - timeout, errors[error]); + if (pattern == DP_TRAINING_PATTERN_1 && error == 0) + break; + if (pattern == DP_TRAINING_PATTERN_2) { + value &= LT_CHANNEL1_EQ_BITS | + LT_INTERLANE_ALIGN_DONE | + LT_CHANNEL0_EQ_BITS; + /* in case of two lanes */ + if ((tc->link.base.num_lanes == 2) && + (value == (LT_CHANNEL1_EQ_BITS | + LT_INTERLANE_ALIGN_DONE | + LT_CHANNEL0_EQ_BITS))) + break; + /* in case of one line */ + if ((tc->link.base.num_lanes == 1) && + (value == (LT_INTERLANE_ALIGN_DONE | + LT_CHANNEL0_EQ_BITS))) + break; + } + } + /* restart */ + tc_write(DP0CTL, 0); + usleep_range(10, 20); + } while (--retry); + if (retry == 0) { + dev_err(tc->dev, "Failed to finish training phase %d\n", + pattern); + } + + return 0; +err: + return ret; +} + +static int tc_main_link_setup(struct tc_data *tc) +{ + struct drm_dp_aux *aux = &tc->aux; + struct device *dev = tc->dev; + unsigned int rate; + u32 dp_phy_ctrl; + int timeout; + bool aligned; + bool ready; + u32 value; + int ret; + u8 tmp[8]; + + /* display mode should be set at this point */ + if (!tc->mode) + return -EINVAL; + + /* from excel file - DP0_SrcCtrl */ + tc_write(DP0_SRCCTRL, DP0_SRCCTRL_SCRMBLDIS | DP0_SRCCTRL_EN810B | + DP0_SRCCTRL_LANESKEW | DP0_SRCCTRL_LANES_2 | + DP0_SRCCTRL_BW27 | DP0_SRCCTRL_AUTOCORRECT); + /* from excel file - DP1_SrcCtrl */ + tc_write(0x07a0, 0x00003083); + + rate = clk_get_rate(tc->refclk); + switch (rate) { + case 38400000: + value = REF_FREQ_38M4; + break; + case 26000000: + value = REF_FREQ_26M; + break; + case 19200000: + value = REF_FREQ_19M2; + break; + case 13000000: + value = REF_FREQ_13M; + break; + default: + return -EINVAL; + } + value |= SYSCLK_SEL_LSCLK | LSCLK_DIV_2; + tc_write(SYS_PLLPARAM, value); + /* Setup Main Link */ + dp_phy_ctrl = BGREN | PWR_SW_EN | BIT(2) | PHY_A0_EN | PHY_M0_EN; + tc_write(DP_PHY_CTRL, dp_phy_ctrl); + msleep(100); + + /* PLL setup */ + tc_write(DP0_PLLCTRL, PLLUPDATE | PLLEN); + tc_wait_pll_lock(tc); + + tc_write(DP1_PLLCTRL, PLLUPDATE | PLLEN); + tc_wait_pll_lock(tc); + + /* PXL PLL setup */ + if (tc_test_pattern) { + ret = tc_pxl_pll_en(tc, clk_get_rate(tc->refclk), + 1000 * tc->mode->clock); + if (ret) + goto err; + } + + /* Reset/Enable Main Links */ + dp_phy_ctrl |= DP_PHY_RST | PHY_M1_RST | PHY_M0_RST; + tc_write(DP_PHY_CTRL, dp_phy_ctrl); + usleep_range(100, 200); + dp_phy_ctrl &= ~(DP_PHY_RST | PHY_M1_RST | PHY_M0_RST); + tc_write(DP_PHY_CTRL, dp_phy_ctrl); + + timeout = 1000; + do { + tc_read(DP_PHY_CTRL, &value); + udelay(1); + } while ((!(value & PHY_RDY)) && (--timeout)); + + if (timeout == 0) { + dev_err(dev, "timeout waiting for phy become ready"); + return -ETIMEDOUT; + } + + /* Set misc: 8 bits per color */ + ret = regmap_update_bits(tc->regmap, DP0_MISC, BPC_8, BPC_8); + if (ret) + goto err; + + /* + * ASSR mode + * on TC358767 side ASSR configured through strap pin + * seems there is no way to change this setting from SW + * + * check is tc configured for same mode + */ + if (tc->assr != tc->link.assr) { + dev_dbg(dev, "Trying to set display to ASSR: %d\n", + tc->assr); + /* try to set ASSR on display side */ + tmp[0] = tc->assr; + ret = drm_dp_dpcd_writeb(aux, DP_EDP_CONFIGURATION_SET, tmp[0]); + if (ret < 0) + goto err_dpcd_read; + /* read back */ + ret = drm_dp_dpcd_readb(aux, DP_EDP_CONFIGURATION_SET, tmp); + if (ret < 0) + goto err_dpcd_read; + + if (tmp[0] != tc->assr) { + dev_warn(dev, "Failed to switch display ASSR to %d, falling back to unscrambled mode\n", + tc->assr); + /* trying with disabled scrambler */ + tc->link.scrambler_dis = 1; + } + } + + /* Setup Link & DPRx Config for Training */ + ret = drm_dp_link_configure(aux, &tc->link.base); + if (ret < 0) + goto err_dpcd_write; + + /* DOWNSPREAD_CTRL */ + tmp[0] = tc->link.spread ? DP_SPREAD_AMP_0_5 : 0x00; + /* MAIN_LINK_CHANNEL_CODING_SET */ + tmp[1] = tc->link.coding8b10b ? DP_SET_ANSI_8B10B : 0x00; + ret = drm_dp_dpcd_write(aux, DP_DOWNSPREAD_CTRL, tmp, 2); + if (ret < 0) + goto err_dpcd_write; + + ret = tc_link_training(tc, DP_TRAINING_PATTERN_1); + if (ret) + goto err; + + ret = tc_link_training(tc, DP_TRAINING_PATTERN_2); + if (ret) + goto err; + + /* Clear DPCD 0x102 */ + /* Note: Can Not use DP0_SNKLTCTRL (0x06E4) short cut */ + tmp[0] = tc->link.scrambler_dis ? DP_LINK_SCRAMBLING_DISABLE : 0x00; + ret = drm_dp_dpcd_writeb(aux, DP_TRAINING_PATTERN_SET, tmp[0]); + if (ret < 0) + goto err_dpcd_write; + + /* Clear Training Pattern, set AutoCorrect Mode = 1 */ + tc_write(DP0_SRCCTRL, tc_srcctrl(tc) | DP0_SRCCTRL_AUTOCORRECT); + + /* Wait */ + timeout = 100; + do { + udelay(1); + /* Read DPCD 0x202-0x207 */ + ret = drm_dp_dpcd_read_link_status(aux, tmp + 2); + if (ret < 0) + goto err_dpcd_read; + ready = (tmp[2] == ((DP_CHANNEL_EQ_BITS << 4) | /* Lane1 */ + DP_CHANNEL_EQ_BITS)); /* Lane0 */ + aligned = tmp[4] & DP_INTERLANE_ALIGN_DONE; + } while ((--timeout) && !(ready && aligned)); + + if (timeout == 0) { + /* Read DPCD 0x200-0x201 */ + ret = drm_dp_dpcd_read(aux, DP_SINK_COUNT, tmp, 2); + if (ret < 0) + goto err_dpcd_read; + dev_info(dev, "0x0200 SINK_COUNT: 0x%02x\n", tmp[0]); + dev_info(dev, "0x0201 DEVICE_SERVICE_IRQ_VECTOR: 0x%02x\n", + tmp[1]); + dev_info(dev, "0x0202 LANE0_1_STATUS: 0x%02x\n", tmp[2]); + dev_info(dev, "0x0204 LANE_ALIGN_STATUS_UPDATED: 0x%02x\n", + tmp[4]); + dev_info(dev, "0x0205 SINK_STATUS: 0x%02x\n", tmp[5]); + dev_info(dev, "0x0206 ADJUST_REQUEST_LANE0_1: 0x%02x\n", + tmp[6]); + + if (!ready) + dev_err(dev, "Lane0/1 not ready\n"); + if (!aligned) + dev_err(dev, "Lane0/1 not aligned\n"); + return -EAGAIN; + } + + ret = tc_set_video_mode(tc, tc->mode); + if (ret) + goto err; + + /* Set M/N */ + ret = tc_stream_clock_calc(tc); + if (ret) + goto err; + + return 0; +err_dpcd_read: + dev_err(tc->dev, "Failed to read DPCD: %d\n", ret); + return ret; +err_dpcd_write: + dev_err(tc->dev, "Failed to write DPCD: %d\n", ret); +err: + return ret; +} + +static int tc_main_link_stream(struct tc_data *tc, int state) +{ + int ret; + u32 value; + + dev_dbg(tc->dev, "stream: %d\n", state); + + if (state) { + value = VID_MN_GEN | DP_EN; + if (tc->link.base.capabilities & DP_LINK_CAP_ENHANCED_FRAMING) + value |= EF_EN; + tc_write(DP0CTL, value); + /* + * VID_EN assertion should be delayed by at least N * LSCLK + * cycles from the time VID_MN_GEN is enabled in order to + * generate stable values for VID_M. LSCLK is 270 MHz or + * 162 MHz, VID_N is set to 32768 in tc_stream_clock_calc(), + * so a delay of at least 203 us should suffice. + */ + usleep_range(500, 1000); + value |= VID_EN; + tc_write(DP0CTL, value); + /* Set input interface */ + value = DP0_AUDSRC_NO_INPUT; + if (tc_test_pattern) + value |= DP0_VIDSRC_COLOR_BAR; + else + value |= DP0_VIDSRC_DPI_RX; + tc_write(SYSCTRL, value); + } else { + tc_write(DP0CTL, 0); + } + + return 0; +err: + return ret; +} + +static enum drm_connector_status +tc_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static void tc_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct tc_data *tc = bridge_to_tc(bridge); + + drm_panel_prepare(tc->panel); +} + +static void tc_bridge_enable(struct drm_bridge *bridge) +{ + struct tc_data *tc = bridge_to_tc(bridge); + int ret; + + ret = tc_main_link_setup(tc); + if (ret < 0) { + dev_err(tc->dev, "main link setup error: %d\n", ret); + return; + } + + ret = tc_main_link_stream(tc, 1); + if (ret < 0) { + dev_err(tc->dev, "main link stream start error: %d\n", ret); + return; + } + + drm_panel_enable(tc->panel); +} + +static void tc_bridge_disable(struct drm_bridge *bridge) +{ + struct tc_data *tc = bridge_to_tc(bridge); + int ret; + + drm_panel_disable(tc->panel); + + ret = tc_main_link_stream(tc, 0); + if (ret < 0) + dev_err(tc->dev, "main link stream stop error: %d\n", ret); +} + +static void tc_bridge_post_disable(struct drm_bridge *bridge) +{ + struct tc_data *tc = bridge_to_tc(bridge); + + drm_panel_unprepare(tc->panel); +} + +static bool tc_bridge_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adj) +{ + /* Fixup sync polarities, both hsync and vsync are active low */ + adj->flags = mode->flags; + adj->flags |= (DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC); + adj->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC); + + return true; +} + +static int tc_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + /* Accept any mode */ + return MODE_OK; +} + +static void tc_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adj) +{ + struct tc_data *tc = bridge_to_tc(bridge); + + tc->mode = mode; +} + +static int tc_connector_get_modes(struct drm_connector *connector) +{ + struct tc_data *tc = connector_to_tc(connector); + struct edid *edid; + unsigned int count; + + if (tc->panel && tc->panel->funcs && tc->panel->funcs->get_modes) { + count = tc->panel->funcs->get_modes(tc->panel); + if (count > 0) + return count; + } + + edid = drm_get_edid(connector, &tc->aux.ddc); + + kfree(tc->edid); + tc->edid = edid; + if (!edid) + return 0; + + drm_mode_connector_update_edid_property(connector, edid); + count = drm_add_edid_modes(connector, edid); + + return count; +} + +static void tc_connector_set_polling(struct tc_data *tc, + struct drm_connector *connector) +{ + /* TODO: add support for HPD */ + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; +} + +static struct drm_encoder * +tc_connector_best_encoder(struct drm_connector *connector) +{ + struct tc_data *tc = connector_to_tc(connector); + + return tc->bridge.encoder; +} + +static const struct drm_connector_helper_funcs tc_connector_helper_funcs = { + .get_modes = tc_connector_get_modes, + .mode_valid = tc_connector_mode_valid, + .best_encoder = tc_connector_best_encoder, +}; + +static void tc_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs tc_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = tc_connector_detect, + .destroy = tc_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int tc_bridge_attach(struct drm_bridge *bridge) +{ + u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + struct tc_data *tc = bridge_to_tc(bridge); + struct drm_device *drm = bridge->dev; + int ret; + + /* Create eDP connector */ + drm_connector_helper_add(&tc->connector, &tc_connector_helper_funcs); + ret = drm_connector_init(drm, &tc->connector, &tc_connector_funcs, + DRM_MODE_CONNECTOR_eDP); + if (ret) + return ret; + + if (tc->panel) + drm_panel_attach(tc->panel, &tc->connector); + + drm_display_info_set_bus_formats(&tc->connector.display_info, + &bus_format, 1); + drm_mode_connector_attach_encoder(&tc->connector, tc->bridge.encoder); + + return 0; +} + +static const struct drm_bridge_funcs tc_bridge_funcs = { + .attach = tc_bridge_attach, + .mode_set = tc_bridge_mode_set, + .pre_enable = tc_bridge_pre_enable, + .enable = tc_bridge_enable, + .disable = tc_bridge_disable, + .post_disable = tc_bridge_post_disable, + .mode_fixup = tc_bridge_mode_fixup, +}; + +static bool tc_readable_reg(struct device *dev, unsigned int reg) +{ + return reg != SYSCTRL; +} + +static const struct regmap_range tc_volatile_ranges[] = { + regmap_reg_range(DP0_AUXWDATA(0), DP0_AUXSTATUS), + regmap_reg_range(DP0_LTSTAT, DP0_SNKLTCHGREQ), + regmap_reg_range(DP_PHY_CTRL, DP_PHY_CTRL), + regmap_reg_range(DP0_PLLCTRL, PXL_PLLCTRL), + regmap_reg_range(VFUEN0, VFUEN0), +}; + +static const struct regmap_access_table tc_volatile_table = { + .yes_ranges = tc_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(tc_volatile_ranges), +}; + +static bool tc_writeable_reg(struct device *dev, unsigned int reg) +{ + return (reg != TC_IDREG) && + (reg != DP0_LTSTAT) && + (reg != DP0_SNKLTCHGREQ); +} + +static const struct regmap_config tc_regmap_config = { + .name = "tc358767", + .reg_bits = 16, + .val_bits = 32, + .reg_stride = 4, + .max_register = PLL_DBG, + .cache_type = REGCACHE_RBTREE, + .readable_reg = tc_readable_reg, + .volatile_table = &tc_volatile_table, + .writeable_reg = tc_writeable_reg, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device_node *ep; + struct tc_data *tc; + int ret; + + tc = devm_kzalloc(dev, sizeof(*tc), GFP_KERNEL); + if (!tc) + return -ENOMEM; + + tc->dev = dev; + + /* port@2 is the output port */ + ep = of_graph_get_endpoint_by_regs(dev->of_node, 2, -1); + if (ep) { + struct device_node *remote; + + remote = of_graph_get_remote_port_parent(ep); + if (!remote) { + dev_warn(dev, "endpoint %s not connected\n", + ep->full_name); + of_node_put(ep); + return -ENODEV; + } + of_node_put(ep); + tc->panel = of_drm_find_panel(remote); + if (tc->panel) { + dev_dbg(dev, "found panel %s\n", remote->full_name); + } else { + dev_dbg(dev, "waiting for panel %s\n", + remote->full_name); + of_node_put(remote); + return -EPROBE_DEFER; + } + of_node_put(remote); + } + + /* Shut down GPIO is optional */ + tc->sd_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH); + if (IS_ERR(tc->sd_gpio)) + return PTR_ERR(tc->sd_gpio); + + if (tc->sd_gpio) { + gpiod_set_value_cansleep(tc->sd_gpio, 0); + usleep_range(5000, 10000); + } + + /* Reset GPIO is optional */ + tc->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(tc->reset_gpio)) + return PTR_ERR(tc->reset_gpio); + + if (tc->reset_gpio) { + gpiod_set_value_cansleep(tc->reset_gpio, 1); + usleep_range(5000, 10000); + } + + tc->refclk = devm_clk_get(dev, "ref"); + if (IS_ERR(tc->refclk)) { + ret = PTR_ERR(tc->refclk); + dev_err(dev, "Failed to get refclk: %d\n", ret); + return ret; + } + + tc->regmap = devm_regmap_init_i2c(client, &tc_regmap_config); + if (IS_ERR(tc->regmap)) { + ret = PTR_ERR(tc->regmap); + dev_err(dev, "Failed to initialize regmap: %d\n", ret); + return ret; + } + + ret = regmap_read(tc->regmap, TC_IDREG, &tc->rev); + if (ret) { + dev_err(tc->dev, "can not read device ID: %d\n", ret); + return ret; + } + + if ((tc->rev != 0x6601) && (tc->rev != 0x6603)) { + dev_err(tc->dev, "invalid device ID: 0x%08x\n", tc->rev); + return -EINVAL; + } + + tc->assr = (tc->rev == 0x6601); /* Enable ASSR for eDP panels */ + + ret = tc_aux_link_setup(tc); + if (ret) + return ret; + + /* Register DP AUX channel */ + tc->aux.name = "TC358767 AUX i2c adapter"; + tc->aux.dev = tc->dev; + tc->aux.transfer = tc_aux_transfer; + ret = drm_dp_aux_register(&tc->aux); + if (ret) + return ret; + + ret = tc_get_display_props(tc); + if (ret) + goto err_unregister_aux; + + tc_connector_set_polling(tc, &tc->connector); + + tc->bridge.funcs = &tc_bridge_funcs; + tc->bridge.of_node = dev->of_node; + ret = drm_bridge_add(&tc->bridge); + if (ret) { + dev_err(dev, "Failed to add drm_bridge: %d\n", ret); + goto err_unregister_aux; + } + + i2c_set_clientdata(client, tc); + + return 0; +err_unregister_aux: + drm_dp_aux_unregister(&tc->aux); + return ret; +} + +static int tc_remove(struct i2c_client *client) +{ + struct tc_data *tc = i2c_get_clientdata(client); + + drm_bridge_remove(&tc->bridge); + drm_dp_aux_unregister(&tc->aux); + + tc_pxl_pll_dis(tc); + + return 0; +} + +static const struct i2c_device_id tc358767_i2c_ids[] = { + { "tc358767", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tc358767_i2c_ids); + +static const struct of_device_id tc358767_of_ids[] = { + { .compatible = "toshiba,tc358767", }, + { } +}; +MODULE_DEVICE_TABLE(of, tc358767_of_ids); + +static struct i2c_driver tc358767_driver = { + .driver = { + .name = "tc358767", + .of_match_table = tc358767_of_ids, + }, + .id_table = tc358767_i2c_ids, + .probe = tc_probe, + .remove = tc_remove, +}; +module_i2c_driver(tc358767_driver); + +MODULE_AUTHOR("Andrey Gusakov "); +MODULE_DESCRIPTION("tc358767 eDP encoder driver"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From 1d926114d8f4e9f138617b95c6cd377667c36369 Mon Sep 17 00:00:00 2001 From: Archit Taneja Date: Mon, 18 Jul 2016 08:44:04 +0530 Subject: drm: rcar-du: Remove i2c slave encoder interface for hdmi encoder The hdmi output in rcar-du uses the i2c slave encoder interface to link to the adv7511 encoder chip. The kms driver creates encoder and connector entities that internally uses the drm_encoder_slave_funcs ops provided by the slave encoder driver. Change the driver such that it expects a bridge entity instead of a slave encoder. The hdmi connector code isn't needed anymore as we expect the adv7511 bridge driver to create/manage the connector. Note that the kms driver still expects a connector node for hdmi to be present in DT. This node has no connection to the connector created by the bridge driver. Tested-by: Laurent Pinchart Signed-off-by: Archit Taneja diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile index 827711e..d3b4465 100644 --- a/drivers/gpu/drm/rcar-du/Makefile +++ b/drivers/gpu/drm/rcar-du/Makefile @@ -7,8 +7,8 @@ rcar-du-drm-y := rcar_du_crtc.o \ rcar_du_plane.o \ rcar_du_vgacon.o -rcar-du-drm-$(CONFIG_DRM_RCAR_HDMI) += rcar_du_hdmicon.o \ - rcar_du_hdmienc.o +rcar-du-drm-$(CONFIG_DRM_RCAR_HDMI) += rcar_du_hdmienc.o + rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c index 55149e9..ab8645c 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c @@ -19,7 +19,6 @@ #include "rcar_du_drv.h" #include "rcar_du_encoder.h" -#include "rcar_du_hdmicon.h" #include "rcar_du_hdmienc.h" #include "rcar_du_kms.h" #include "rcar_du_lvdscon.h" @@ -174,7 +173,7 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, break; case DRM_MODE_ENCODER_TMDS: - ret = rcar_du_hdmi_connector_init(rcdu, renc); + /* connector managed by the bridge driver */ break; default: diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h index a8669c3..7fc10a9 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h @@ -15,7 +15,6 @@ #define __RCAR_DU_ENCODER_H__ #include -#include struct rcar_du_device; struct rcar_du_hdmienc; @@ -30,16 +29,16 @@ enum rcar_du_encoder_type { }; struct rcar_du_encoder { - struct drm_encoder_slave slave; + struct drm_encoder base; enum rcar_du_output output; struct rcar_du_hdmienc *hdmi; struct rcar_du_lvdsenc *lvds; }; #define to_rcar_encoder(e) \ - container_of(e, struct rcar_du_encoder, slave.base) + container_of(e, struct rcar_du_encoder, base) -#define rcar_encoder_to_drm_encoder(e) (&(e)->slave.base) +#define rcar_encoder_to_drm_encoder(e) (&(e)->base) struct rcar_du_connector { struct drm_connector connector; diff --git a/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.c b/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.c deleted file mode 100644 index 612b4d5..0000000 --- a/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * R-Car Display Unit HDMI Connector - * - * Copyright (C) 2014 Renesas Electronics Corporation - * - * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 -#include -#include -#include -#include - -#include "rcar_du_drv.h" -#include "rcar_du_encoder.h" -#include "rcar_du_hdmicon.h" -#include "rcar_du_kms.h" - -#define to_slave_funcs(e) (to_rcar_encoder(e)->slave.slave_funcs) - -static int rcar_du_hdmi_connector_get_modes(struct drm_connector *connector) -{ - struct rcar_du_connector *con = to_rcar_connector(connector); - struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(con->encoder); - const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); - - if (sfuncs->get_modes == NULL) - return 0; - - return sfuncs->get_modes(encoder, connector); -} - -static int rcar_du_hdmi_connector_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) -{ - struct rcar_du_connector *con = to_rcar_connector(connector); - struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(con->encoder); - const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); - - if (sfuncs->mode_valid == NULL) - return MODE_OK; - - return sfuncs->mode_valid(encoder, mode); -} - -static const struct drm_connector_helper_funcs connector_helper_funcs = { - .get_modes = rcar_du_hdmi_connector_get_modes, - .mode_valid = rcar_du_hdmi_connector_mode_valid, -}; - -static enum drm_connector_status -rcar_du_hdmi_connector_detect(struct drm_connector *connector, bool force) -{ - struct rcar_du_connector *con = to_rcar_connector(connector); - struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(con->encoder); - const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); - - if (sfuncs->detect == NULL) - return connector_status_unknown; - - return sfuncs->detect(encoder, connector); -} - -static const struct drm_connector_funcs connector_funcs = { - .dpms = drm_atomic_helper_connector_dpms, - .reset = drm_atomic_helper_connector_reset, - .detect = rcar_du_hdmi_connector_detect, - .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = drm_connector_cleanup, - .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, -}; - -int rcar_du_hdmi_connector_init(struct rcar_du_device *rcdu, - struct rcar_du_encoder *renc) -{ - struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc); - struct rcar_du_connector *rcon; - struct drm_connector *connector; - int ret; - - rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL); - if (rcon == NULL) - return -ENOMEM; - - connector = &rcon->connector; - connector->display_info.width_mm = 0; - connector->display_info.height_mm = 0; - connector->interlace_allowed = true; - connector->polled = DRM_CONNECTOR_POLL_HPD; - - ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs, - DRM_MODE_CONNECTOR_HDMIA); - if (ret < 0) - return ret; - - drm_connector_helper_add(connector, &connector_helper_funcs); - - connector->dpms = DRM_MODE_DPMS_OFF; - drm_object_property_set_value(&connector->base, - rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF); - - ret = drm_mode_connector_attach_encoder(connector, encoder); - if (ret < 0) - return ret; - - rcon->encoder = renc; - - return 0; -} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.h b/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.h deleted file mode 100644 index 87daa94..0000000 --- a/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * R-Car Display Unit HDMI Connector - * - * Copyright (C) 2014 Renesas Electronics Corporation - * - * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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. - */ - -#ifndef __RCAR_DU_HDMICON_H__ -#define __RCAR_DU_HDMICON_H__ - -struct rcar_du_device; -struct rcar_du_encoder; - -#if IS_ENABLED(CONFIG_DRM_RCAR_HDMI) -int rcar_du_hdmi_connector_init(struct rcar_du_device *rcdu, - struct rcar_du_encoder *renc); -#else -static inline int rcar_du_hdmi_connector_init(struct rcar_du_device *rcdu, - struct rcar_du_encoder *renc) -{ - return -ENOSYS; -} -#endif - -#endif /* __RCAR_DU_HDMICON_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c b/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c index 461662d..4de3ff0 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c @@ -16,7 +16,6 @@ #include #include #include -#include #include "rcar_du_drv.h" #include "rcar_du_encoder.h" @@ -25,20 +24,14 @@ struct rcar_du_hdmienc { struct rcar_du_encoder *renc; - struct device *dev; bool enabled; }; #define to_rcar_hdmienc(e) (to_rcar_encoder(e)->hdmi) -#define to_slave_funcs(e) (to_rcar_encoder(e)->slave.slave_funcs) static void rcar_du_hdmienc_disable(struct drm_encoder *encoder) { struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder); - const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); - - if (sfuncs->dpms) - sfuncs->dpms(encoder, DRM_MODE_DPMS_OFF); if (hdmienc->renc->lvds) rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc, @@ -50,15 +43,11 @@ static void rcar_du_hdmienc_disable(struct drm_encoder *encoder) static void rcar_du_hdmienc_enable(struct drm_encoder *encoder) { struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder); - const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); if (hdmienc->renc->lvds) rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc, true); - if (sfuncs->dpms) - sfuncs->dpms(encoder, DRM_MODE_DPMS_ON); - hdmienc->enabled = true; } @@ -67,29 +56,21 @@ static int rcar_du_hdmienc_atomic_check(struct drm_encoder *encoder, struct drm_connector_state *conn_state) { struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder); - const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; - const struct drm_display_mode *mode = &crtc_state->mode; if (hdmienc->renc->lvds) rcar_du_lvdsenc_atomic_check(hdmienc->renc->lvds, adjusted_mode); - if (sfuncs->mode_fixup == NULL) - return 0; - - return sfuncs->mode_fixup(encoder, mode, adjusted_mode) ? 0 : -EINVAL; + return 0; } + static void rcar_du_hdmienc_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder); - const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); - - if (sfuncs->mode_set) - sfuncs->mode_set(encoder, mode, adjusted_mode); rcar_du_crtc_route_output(encoder->crtc, hdmienc->renc->output); } @@ -109,7 +90,6 @@ static void rcar_du_hdmienc_cleanup(struct drm_encoder *encoder) rcar_du_hdmienc_disable(encoder); drm_encoder_cleanup(encoder); - put_device(hdmienc->dev); } static const struct drm_encoder_funcs encoder_funcs = { @@ -120,8 +100,7 @@ int rcar_du_hdmienc_init(struct rcar_du_device *rcdu, struct rcar_du_encoder *renc, struct device_node *np) { struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc); - struct drm_i2c_encoder_driver *driver; - struct i2c_client *i2c_slave; + struct drm_bridge *bridge; struct rcar_du_hdmienc *hdmienc; int ret; @@ -129,44 +108,29 @@ int rcar_du_hdmienc_init(struct rcar_du_device *rcdu, if (hdmienc == NULL) return -ENOMEM; - /* Locate the slave I2C device and driver. */ - i2c_slave = of_find_i2c_device_by_node(np); - if (!i2c_slave || !i2c_get_clientdata(i2c_slave)) { - dev_dbg(rcdu->dev, - "can't get I2C slave for %s, deferring probe\n", - of_node_full_name(np)); + /* Locate drm bridge from the hdmi encoder DT node */ + bridge = of_drm_find_bridge(np); + if (!bridge) return -EPROBE_DEFER; - } - - hdmienc->dev = &i2c_slave->dev; - - if (hdmienc->dev->driver == NULL) { - dev_dbg(rcdu->dev, - "I2C slave %s not probed yet, deferring probe\n", - dev_name(hdmienc->dev)); - ret = -EPROBE_DEFER; - goto error; - } - - /* Initialize the slave encoder. */ - driver = to_drm_i2c_encoder_driver(to_i2c_driver(hdmienc->dev->driver)); - ret = driver->encoder_init(i2c_slave, rcdu->ddev, &renc->slave); - if (ret < 0) - goto error; ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs, DRM_MODE_ENCODER_TMDS, NULL); if (ret < 0) - goto error; + return ret; drm_encoder_helper_add(encoder, &encoder_helper_funcs); renc->hdmi = hdmienc; hdmienc->renc = renc; - return 0; + /* Link drm_bridge to encoder */ + bridge->encoder = encoder; + + ret = drm_bridge_attach(rcdu->ddev, bridge); + if (ret) { + drm_encoder_cleanup(encoder); + return ret; + } -error: - put_device(hdmienc->dev); - return ret; + return 0; } -- cgit v0.10.2