ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
This series adds support for ADV7533. Unlike ADV7511 that's modelled as a drm i2c slave encoder, ADV7533 is a drm_bridge object. The bridge ops further create a HDMI type connector device for the chip's output.
In order to pass DSI specific parameters to the host, the driver creates a dummy DSI device, which it registers to a DSI host as specified via DT.
The first patch in the series is a minor fix, the rest add ADV7533 support. They depend on the series I posted sometime back:
"drm/dsi: DSI for devices with different control bus" https://lkml.org/lkml/2015/6/30/42
Archit Taneja (4): drm/i2c: adv7511: Fix mutex deadlock when interrupts are disabled drm/i2c: adv7511: Refactor encoder slave functions drm/i2c: adv7511: Add drm_bridge/connector for ADV7533 drm/i2c: adv7511: Create mipi_dsi_device for ADV7533
Lars-Peter Clausen (1): drm/i2c: adv7511: Initial support for adv7533
drivers/gpu/drm/i2c/adv7511.c | 486 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 437 insertions(+), 49 deletions(-)
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 interrupt context. It doesn't serve any purpose there anyway.
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 2aaa3c8..cf5bb29 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -422,7 +422,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; @@ -438,7 +438,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_HDP && adv7511->encoder) + if (process_hpd && irq0 & ADV7511_INT0_HDP && adv7511->encoder) drm_helper_hpd_irq_event(adv7511->encoder->dev);
if (irq0 & ADV7511_INT0_EDID_READY || irq1 & ADV7511_INT1_DDC_ERROR) { @@ -456,7 +456,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; }
@@ -473,7 +473,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;
From: Lars-Peter Clausen lars@metafoo.de
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 blocks.
Use DT compatible strings to populate the adv7533 type enum. Add minimal register configurations belonging to the DSI/CEC register map.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 155 +++++++++++++++++++++++++++++++++++------- 1 file changed, 131 insertions(+), 24 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index cf5bb29..63a3d20 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -20,12 +20,18 @@
#include "adv7511.h"
+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 *packet_memory_regmap; + struct regmap *regmap_cec; enum drm_connector_status status; bool powered;
@@ -46,6 +52,8 @@ struct adv7511 { struct edid *edid;
struct gpio_desc *gpio_pd; + + enum adv7511_type type; };
static struct adv7511 *encoder_to_adv7511(struct drm_encoder *encoder) @@ -66,6 +74,23 @@ static const struct reg_default adv7511_fixed_registers[] = { { 0x55, 0x02 }, };
+/* ADI recommended values for proper operation. */ +static const struct reg_default adv7533_fixed_registers[] = { + { 0x16, 0x20 }, + { 0x9a, 0xe0 }, + { 0xba, 0x70 }, + { 0xde, 0x82 }, + { 0xe4, 0x40 }, + { 0xe5, 0x80 }, +}; + +static const struct reg_default adv7533_cec_fixed_registers[] = { + { 0x15, 0xd0 }, + { 0x17, 0xd0 }, + { 0x24, 0x20 }, + { 0x57, 0x11 }, +}; + /* ----------------------------------------------------------------------------- * Register access */ @@ -158,6 +183,15 @@ static const struct regmap_config adv7511_regmap_config = { .volatile_reg = adv7511_register_volatile, };
+static const struct regmap_config adv7533_cec_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_RBTREE, +}; + + /* ----------------------------------------------------------------------------- * Hardware configuration */ @@ -358,6 +392,25 @@ static void adv7511_set_link_config(struct adv7511 *adv7511, adv7511->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB; }
+static void adv7511_dsi_receiver_dpms(struct adv7511 *adv7511) +{ + if (adv7511->type != ADV7533) + return; + + if (adv7511->powered) { + /* set number of dsi lanes (hardcoded to 4 for now) */ + regmap_write(adv7511->regmap_cec, 0x1c, 0x4 << 4); + /* disable internal timing generator */ + regmap_write(adv7511->regmap_cec, 0x27, 0x0b); + /* enable hdmi */ + regmap_write(adv7511->regmap_cec, 0x03, 0x89); + /* disable test mode */ + regmap_write(adv7511->regmap_cec, 0x55, 0x00); + } else { + regmap_write(adv7511->regmap_cec, 0x03, 0x0b); + } +} + static void adv7511_power_on(struct adv7511 *adv7511) { adv7511->current_edid_segment = -1; @@ -387,6 +440,8 @@ static void adv7511_power_on(struct adv7511 *adv7511) regcache_sync(adv7511->regmap);
adv7511->powered = true; + + adv7511_dsi_receiver_dpms(adv7511); }
static void adv7511_power_off(struct adv7511 *adv7511) @@ -398,6 +453,8 @@ static void adv7511_power_off(struct adv7511 *adv7511) regcache_mark_dirty(adv7511->regmap);
adv7511->powered = false; + + adv7511_dsi_receiver_dpms(adv7511); }
/* ----------------------------------------------------------------------------- @@ -567,6 +624,9 @@ static int adv7511_get_modes(struct drm_encoder *encoder,
/* Reading the EDID only works if the device is powered */ if (!adv7511->powered) { + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7511_REG_POWER2_HDP_SRC_MASK, + ADV7511_REG_POWER2_HDP_SRC_NONE); regmap_write(adv7511->regmap, ADV7511_REG_INT(0), ADV7511_INT0_EDID_READY); regmap_write(adv7511->regmap, ADV7511_REG_INT(1), @@ -770,8 +830,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) @@ -853,6 +911,15 @@ static const int edid_i2c_addr = 0x7e; static const int packet_i2c_addr = 0x70; static const int cec_i2c_addr = 0x78;
+static const struct of_device_id adv7511_of_ids[] = { + { .compatible = "adi,adv7511", .data = (void *) ADV7511 }, + { .compatible = "adi,adv7511w", .data = (void *) ADV7511 }, + { .compatible = "adi,adv7513", .data = (void *) ADV7511 }, + { .compatible = "adi,adv7533", .data = (void *) ADV7533 }, + { } +}; +MODULE_DEVICE_TABLE(of, adv7511_of_ids); + static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct adv7511_link_config link_config; @@ -871,9 +938,22 @@ 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) { + const struct of_device_id *of_id; + + of_id = of_match_node(adv7511_of_ids, dev->of_node); + adv7511->type = (enum adv7511_type) of_id->data; + } 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 @@ -897,10 +977,19 @@ 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 (ret) - return ret; + if (adv7511->type == ADV7511) { + ret = regmap_register_patch(adv7511->regmap, + adv7511_fixed_registers, + ARRAY_SIZE(adv7511_fixed_registers)); + if (ret) + return ret; + } else { + ret = regmap_register_patch(adv7511->regmap, + adv7533_fixed_registers, + ARRAY_SIZE(adv7533_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, @@ -913,6 +1002,27 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) if (!adv7511->i2c_edid) return -ENOMEM;
+ adv7511->i2c_cec = i2c_new_dummy(i2c->adapter, cec_i2c_addr >> 1); + if (!adv7511->i2c_cec) { + ret = -ENOMEM; + goto err_i2c_unregister_edid; + } + + adv7511->regmap_cec = devm_regmap_init_i2c(adv7511->i2c_cec, + &adv7533_cec_regmap_config); + if (IS_ERR(adv7511->regmap_cec)) { + ret = PTR_ERR(adv7511->regmap_cec); + goto err_i2c_unregister_cec; + } + + if (adv7511->type == ADV7533) { + ret = regmap_register_patch(adv7511->regmap_cec, + adv7533_cec_fixed_registers, + ARRAY_SIZE(adv7533_cec_fixed_registers)); + if (ret) + return ret; + } + if (i2c->irq) { init_waitqueue_head(&adv7511->wq);
@@ -921,7 +1031,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_i2c_unregister_cec; }
/* CEC is unused for now */ @@ -932,11 +1042,14 @@ 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);
return 0;
-err_i2c_unregister_device: +err_i2c_unregister_cec: + i2c_unregister_device(adv7511->i2c_cec); +err_i2c_unregister_edid: i2c_unregister_device(adv7511->i2c_edid);
return ret; @@ -946,6 +1059,7 @@ static int adv7511_remove(struct i2c_client *i2c) { struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
+ i2c_unregister_device(adv7511->i2c_cec); i2c_unregister_device(adv7511->i2c_edid);
kfree(adv7511->edid); @@ -968,21 +1082,14 @@ static int adv7511_encoder_init(struct i2c_client *i2c, struct drm_device *dev, }
static const struct i2c_device_id adv7511_i2c_ids[] = { - { "adv7511", 0 }, - { "adv7511w", 0 }, - { "adv7513", 0 }, + { "adv7511", ADV7511 }, + { "adv7511w", ADV7511 }, + { "adv7513", ADV7511 }, + { "adv7533", ADV7533 }, { } }; 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 drm_i2c_encoder_driver adv7511_driver = { .i2c_driver = { .driver = {
On Sun 26 Jul 23:16 PDT 2015, Archit Taneja wrote:
From: Lars-Peter Clausen lars@metafoo.de
[..]
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c
[..]
+static const struct of_device_id adv7511_of_ids[] = {
- { .compatible = "adi,adv7511", .data = (void *) ADV7511 },
- { .compatible = "adi,adv7511w", .data = (void *) ADV7511 },
- { .compatible = "adi,adv7513", .data = (void *) ADV7511 },
- { .compatible = "adi,adv7533", .data = (void *) ADV7533 },
- { }
+}; +MODULE_DEVICE_TABLE(of, adv7511_of_ids);
Please leave this at the bottom.
static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct adv7511_link_config link_config; @@ -871,9 +938,22 @@ 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) {
const struct of_device_id *of_id;
of_id = of_match_node(adv7511_of_ids, dev->of_node);
If you use of_device_get_match_data() instead you don't need to move the of_device_id table.
adv7511->type = (enum adv7511_type) of_id->data;
- } else {
adv7511->type = id->driver_data;
- }
[..]
static const struct i2c_device_id adv7511_i2c_ids[] = {
- { "adv7511", 0 },
- { "adv7511w", 0 },
- { "adv7513", 0 },
- { "adv7511", ADV7511 },
- { "adv7511w", ADV7511 },
- { "adv7513", ADV7511 },
- { "adv7533", ADV7533 }, { }
}; 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);
Regards, Bjorn
On 07/28/2015 08:57 AM, Bjorn Andersson wrote:
On Sun 26 Jul 23:16 PDT 2015, Archit Taneja wrote:
From: Lars-Peter Clausen lars@metafoo.de
[..]
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c
[..]
+static const struct of_device_id adv7511_of_ids[] = {
- { .compatible = "adi,adv7511", .data = (void *) ADV7511 },
- { .compatible = "adi,adv7511w", .data = (void *) ADV7511 },
- { .compatible = "adi,adv7513", .data = (void *) ADV7511 },
- { .compatible = "adi,adv7533", .data = (void *) ADV7533 },
- { }
+}; +MODULE_DEVICE_TABLE(of, adv7511_of_ids);
Please leave this at the bottom.
- static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct adv7511_link_config link_config;
@@ -871,9 +938,22 @@ 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) {
const struct of_device_id *of_id;
of_id = of_match_node(adv7511_of_ids, dev->of_node);
If you use of_device_get_match_data() instead you don't need to move the of_device_id table.
Thanks, will use this in future revisions.
Archit
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Move the code in encoder slave functions to generate helpers that are agnostic to the drm object type. These helpers will later also be used by bridge and connecter helper functions.
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 80 ++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 23 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 63a3d20..46fb24d 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -612,13 +612,11 @@ 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, - struct drm_connector *connector) +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;
@@ -656,21 +654,10 @@ 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, +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; @@ -694,7 +681,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 { @@ -708,8 +695,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, + const struct drm_display_mode *mode) { if (mode->clock > 165000) return MODE_CLOCK_HIGH; @@ -717,11 +704,10 @@ static int adv7511_encoder_mode_valid(struct drm_encoder *encoder, return MODE_OK; }
-static void adv7511_encoder_mode_set(struct drm_encoder *encoder, +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; @@ -812,12 +798,60 @@ static void adv7511_encoder_mode_set(struct drm_encoder *encoder, adv7511->f_tmds = mode->clock; }
+/* ----------------------------------------------------------------------------- + * Encoder operations + */ + +static int adv7511_encoder_get_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct adv7511 *adv7511 = encoder_to_adv7511(encoder); + + return adv7511_get_modes(adv7511, connector); +} + +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) +{ + struct adv7511 *adv7511 = encoder_to_adv7511(encoder); + + return adv7511_detect(adv7511, connector); +} + +static int adv7511_encoder_mode_valid(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + struct adv7511 *adv7511 = encoder_to_adv7511(encoder); + + return adv7511_mode_valid(adv7511, mode); +} + +static void adv7511_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct adv7511 *adv7511 = encoder_to_adv7511(encoder); + + adv7511_mode_set(adv7511, mode, adj_mode); +} + static 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, + .get_modes = adv7511_encoder_get_modes, };
/* -----------------------------------------------------------------------------
Hi Archit,
(CC'ing Boris Brezillon)
Thank you for the patch.
On Monday 27 July 2015 11:46:57 Archit Taneja wrote:
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Please, no. It's really time to stop piling hacks and fix the problem properly. There's no reason to have separate APIs for I2C slave encoders and DRM bridges. Those two APIs need to be merged, and then you'll find it much easier to implement ADV7533 support.
Boris, I know you were experimenting with that, do you have anything to report ?
Move the code in encoder slave functions to generate helpers that are agnostic to the drm object type. These helpers will later also be used by bridge and connecter helper functions.
Signed-off-by: Archit Taneja architt@codeaurora.org
drivers/gpu/drm/i2c/adv7511.c | 80 +++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 23 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 63a3d20..46fb24d 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -612,13 +612,11 @@ 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,
struct drm_connector *connector)
+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;
@@ -656,21 +654,10 @@ 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, +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;
@@ -694,7 +681,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);
if (adv7511->status == connector_status_connected) status = connector_status_disconnected; } else {adv7511_get_modes(adv7511, connector);
@@ -708,8 +695,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,
const struct drm_display_mode *mode)
{ if (mode->clock > 165000) return MODE_CLOCK_HIGH; @@ -717,11 +704,10 @@ static int adv7511_encoder_mode_valid(struct drm_encoder *encoder, return MODE_OK; }
-static void adv7511_encoder_mode_set(struct drm_encoder *encoder, +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;
@@ -812,12 +798,60 @@ static void adv7511_encoder_mode_set(struct drm_encoder *encoder, adv7511->f_tmds = mode->clock; }
+/*
-- + * Encoder operations
- */
+static int adv7511_encoder_get_modes(struct drm_encoder *encoder,
struct drm_connector *connector)
+{
- struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
- return adv7511_get_modes(adv7511, connector);
+}
+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)
+{
- struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
- return adv7511_detect(adv7511, connector);
+}
+static int adv7511_encoder_mode_valid(struct drm_encoder *encoder,
struct drm_display_mode *mode)
+{
- struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
- return adv7511_mode_valid(adv7511, mode);
+}
+static void adv7511_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
+{
- struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
- adv7511_mode_set(adv7511, mode, adj_mode);
+}
static 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,
- .get_modes = adv7511_encoder_get_modes,
};
/*
--
Hi,
On 07/27/2015 02:29 PM, Laurent Pinchart wrote:
Hi Archit,
(CC'ing Boris Brezillon)
Thank you for the patch.
On Monday 27 July 2015 11:46:57 Archit Taneja wrote:
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Please, no. It's really time to stop piling hacks and fix the problem properly. There's no reason to have separate APIs for I2C slave encoders and DRM bridges. Those two APIs need to be merged, and then you'll find it much easier to implement ADV7533 support.
i2c slave encoders and bridges aren't exactly the same. slave encoders are used when the there is no real 'encoder' in the display chain. bridges are used when there is already an encoder available, and the bridge entity represents another encoder in the chain.
ADV7511 takes in RGB/MIPI DPI data, which is generally the output of a crtc for drm drivers.
ADV7533 takes in MIPI DSI data, which is generally the output of an encoder for drm drivers.
Therefore, having i2c slave encoder for the former and bridge for the latter made sense to me.
I do agree that it would be better if they were somehow merged. It would prevent the fragmentation we currently have among encoder chips.
One possible way would be to convert slave encoder to bridge. With this, an i2c slave encoder would be a 'dummy encoder' and a bridge. i2c slave encoders even now just tie the slave encoder helper funcs to encoder helper funcs. So it's not really any different.
Merging these 2 entities would be nice, but we're still shying away from the larger problem of creating generic encoder chains. The ideal solution would be for bridges and slave encoders to just be 'encoders', and the facility to connect on encoder output to the input of another. I don't know how easy it is to do this, and whether it'll break userspace.
Archit
Boris, I know you were experimenting with that, do you have anything to report ?
Move the code in encoder slave functions to generate helpers that are agnostic to the drm object type. These helpers will later also be used by bridge and connecter helper functions.
Signed-off-by: Archit Taneja architt@codeaurora.org
drivers/gpu/drm/i2c/adv7511.c | 80 +++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 23 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 63a3d20..46fb24d 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -612,13 +612,11 @@ 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,
struct drm_connector *connector)
+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;
@@ -656,21 +654,10 @@ 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, +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;
@@ -694,7 +681,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);
if (adv7511->status == connector_status_connected) status = connector_status_disconnected; } else {adv7511_get_modes(adv7511, connector);
@@ -708,8 +695,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,
{ if (mode->clock > 165000) return MODE_CLOCK_HIGH;const struct drm_display_mode *mode)
@@ -717,11 +704,10 @@ static int adv7511_encoder_mode_valid(struct drm_encoder *encoder, return MODE_OK; }
-static void adv7511_encoder_mode_set(struct drm_encoder *encoder, +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;
@@ -812,12 +798,60 @@ static void adv7511_encoder_mode_set(struct drm_encoder *encoder, adv7511->f_tmds = mode->clock; }
+/*
-- + * Encoder operations
- */
+static int adv7511_encoder_get_modes(struct drm_encoder *encoder,
struct drm_connector *connector)
+{
- struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
- return adv7511_get_modes(adv7511, connector);
+}
+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)
+{
- struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
- return adv7511_detect(adv7511, connector);
+}
+static int adv7511_encoder_mode_valid(struct drm_encoder *encoder,
struct drm_display_mode *mode)
+{
- struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
- return adv7511_mode_valid(adv7511, mode);
+}
+static void adv7511_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
+{
- struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
- adv7511_mode_set(adv7511, mode, adj_mode);
+}
- static 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,
.get_modes = adv7511_encoder_get_modes, };
/*
--
Archit, Laurent,
On Tue, 28 Jul 2015 13:47:37 +0530 Archit Taneja architt@codeaurora.org wrote:
Hi,
On 07/27/2015 02:29 PM, Laurent Pinchart wrote:
Hi Archit,
(CC'ing Boris Brezillon)
Thank you for the patch.
On Monday 27 July 2015 11:46:57 Archit Taneja wrote:
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Please, no. It's really time to stop piling hacks and fix the problem properly. There's no reason to have separate APIs for I2C slave encoders and DRM bridges. Those two APIs need to be merged, and then you'll find it much easier to implement ADV7533 support.
i2c slave encoders and bridges aren't exactly the same. slave encoders are used when the there is no real 'encoder' in the display chain. bridges are used when there is already an encoder available, and the bridge entity represents another encoder in the chain.
ADV7511 takes in RGB/MIPI DPI data, which is generally the output of a crtc for drm drivers.
ADV7533 takes in MIPI DSI data, which is generally the output of an encoder for drm drivers.
Therefore, having i2c slave encoder for the former and bridge for the latter made sense to me.
I do agree that it would be better if they were somehow merged. It would prevent the fragmentation we currently have among encoder chips.
One possible way would be to convert slave encoder to bridge. With this, an i2c slave encoder would be a 'dummy encoder' and a bridge. i2c slave encoders even now just tie the slave encoder helper funcs to encoder helper funcs. So it's not really any different.
Merging these 2 entities would be nice, but we're still shying away from the larger problem of creating generic encoder chains. The ideal solution would be for bridges and slave encoders to just be 'encoders', and the facility to connect on encoder output to the input of another. I don't know how easy it is to do this, and whether it'll break userspace.
Yes, that's pretty much what I was trying to do. I'd also like to ease display pipelines creation by providing helper functions, so that display controller don't have to worry about encoders and connectors creation if these ones are attached to external encoders.
Archit
Boris, I know you were experimenting with that, do you have anything to report ?
Nope, I didn't work on it since last time we talked about it. I pushed my work here if you want to have a look [1].
Best Regards,
Boris
[1]https://github.com/bbrezillon/linux-at91/tree/drm-encoder-chain-WIP
Hi Boris, Laurent,
On 07/28/2015 08:08 PM, Boris Brezillon wrote:
Archit, Laurent,
On Tue, 28 Jul 2015 13:47:37 +0530 Archit Taneja architt@codeaurora.org wrote:
Hi,
On 07/27/2015 02:29 PM, Laurent Pinchart wrote:
Hi Archit,
(CC'ing Boris Brezillon)
Thank you for the patch.
On Monday 27 July 2015 11:46:57 Archit Taneja wrote:
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Please, no. It's really time to stop piling hacks and fix the problem properly. There's no reason to have separate APIs for I2C slave encoders and DRM bridges. Those two APIs need to be merged, and then you'll find it much easier to implement ADV7533 support.
i2c slave encoders and bridges aren't exactly the same. slave encoders are used when the there is no real 'encoder' in the display chain. bridges are used when there is already an encoder available, and the bridge entity represents another encoder in the chain.
ADV7511 takes in RGB/MIPI DPI data, which is generally the output of a crtc for drm drivers.
ADV7533 takes in MIPI DSI data, which is generally the output of an encoder for drm drivers.
Therefore, having i2c slave encoder for the former and bridge for the latter made sense to me.
I do agree that it would be better if they were somehow merged. It would prevent the fragmentation we currently have among encoder chips.
One possible way would be to convert slave encoder to bridge. With this, an i2c slave encoder would be a 'dummy encoder' and a bridge. i2c slave encoders even now just tie the slave encoder helper funcs to encoder helper funcs. So it's not really any different.
Merging these 2 entities would be nice, but we're still shying away from the larger problem of creating generic encoder chains. The ideal solution would be for bridges and slave encoders to just be 'encoders', and the facility to connect on encoder output to the input of another. I don't know how easy it is to do this, and whether it'll break userspace.
Yes, that's pretty much what I was trying to do. I'd also like to ease display pipelines creation by providing helper functions, so that display controller don't have to worry about encoders and connectors creation if these ones are attached to external encoders.
Archit
Boris, I know you were experimenting with that, do you have anything to report ?
Nope, I didn't work on it since last time we talked about it. I pushed my work here if you want to have a look [1].
I went through the branch you shared. From what I understood, the encoder chain comprises of one 'real' encoder object (drm_encoder) in the 'drm_encoder_chain' struct. This drm_encoder encapsulates all the 'encoder elements' forming the chain.
I'm guessing the various dridge/slave encoder drivers would have to be changed to now create a drm_encoder_element object, replacing drm_bridge/drm_i2c_slave_encoder objects.
One problem I see with this approach is that we can't use this when the display controller already exposes a drm_encoder. I.e, it already creates a drm_encoder object. If we want the encoder chain to be connected to the output of this encoder, we'll need to link the 2 drm_encoders together, which isn't possible at the moment.
I guess we have two ways to go about this:
1) Have an approach like this where all the entities in the encoder chain connect to just one encoder. We define the sequence in which they are connected. The drm kms framework acts as if this chain doesn't exist, and assumes that this is what the encoder is outputting.
2) Make each element in the chain be a 'drm_encoder' object in itself. This would be a more intrusive change, since drm_encoders are expected to receive an input from crtc, and output to a connector. Furthermore, it may confuse userspace what encoder to chose.
For 1), we could either work more on your approach, or use drm_bridges. drm_bridges can already be chained to each other, and bridge ops of each bridge in the chain are called successively. It still relies on the device drivers to form the chain, which is something your approach takes care of by itself. But that's something that can be extended for bridges too.
Laurent,
Merging i2c slave encoders and bridges is possible, but there is no guarantee that the new solution would be future proof and work well with encoder chains. We needed more consensus from folks on dri-devel.
For ADV7533, could you please look at the other parts? Especially the one that creates a dummy mipi DSI device, and connecting to a dsi host. I'd previously posted a RFC to enable that:
http://lkml.iu.edu/hypermail/linux/kernel/1506.3/03249.html
Archit
Best Regards,
Boris
[1]https://github.com/bbrezillon/linux-at91/tree/drm-encoder-chain-WIP
Hi Archit,
On Fri, 31 Jul 2015 10:56:20 +0530 Archit Taneja architt@codeaurora.org wrote:
Hi Boris, Laurent,
On 07/28/2015 08:08 PM, Boris Brezillon wrote:
Archit, Laurent,
On Tue, 28 Jul 2015 13:47:37 +0530 Archit Taneja architt@codeaurora.org wrote:
Hi,
On 07/27/2015 02:29 PM, Laurent Pinchart wrote:
Hi Archit,
(CC'ing Boris Brezillon)
Thank you for the patch.
On Monday 27 July 2015 11:46:57 Archit Taneja wrote:
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Please, no. It's really time to stop piling hacks and fix the problem properly. There's no reason to have separate APIs for I2C slave encoders and DRM bridges. Those two APIs need to be merged, and then you'll find it much easier to implement ADV7533 support.
i2c slave encoders and bridges aren't exactly the same. slave encoders are used when the there is no real 'encoder' in the display chain. bridges are used when there is already an encoder available, and the bridge entity represents another encoder in the chain.
ADV7511 takes in RGB/MIPI DPI data, which is generally the output of a crtc for drm drivers.
ADV7533 takes in MIPI DSI data, which is generally the output of an encoder for drm drivers.
Therefore, having i2c slave encoder for the former and bridge for the latter made sense to me.
I do agree that it would be better if they were somehow merged. It would prevent the fragmentation we currently have among encoder chips.
One possible way would be to convert slave encoder to bridge. With this, an i2c slave encoder would be a 'dummy encoder' and a bridge. i2c slave encoders even now just tie the slave encoder helper funcs to encoder helper funcs. So it's not really any different.
Merging these 2 entities would be nice, but we're still shying away from the larger problem of creating generic encoder chains. The ideal solution would be for bridges and slave encoders to just be 'encoders', and the facility to connect on encoder output to the input of another. I don't know how easy it is to do this, and whether it'll break userspace.
Yes, that's pretty much what I was trying to do. I'd also like to ease display pipelines creation by providing helper functions, so that display controller don't have to worry about encoders and connectors creation if these ones are attached to external encoders.
Archit
Boris, I know you were experimenting with that, do you have anything to report ?
Nope, I didn't work on it since last time we talked about it. I pushed my work here if you want to have a look [1].
I went through the branch you shared. From what I understood, the encoder chain comprises of one 'real' encoder object (drm_encoder) in the 'drm_encoder_chain' struct. This drm_encoder encapsulates all the 'encoder elements' forming the chain.
I'm guessing the various dridge/slave encoder drivers would have to be changed to now create a drm_encoder_element object, replacing drm_bridge/drm_i2c_slave_encoder objects.
One problem I see with this approach is that we can't use this when the display controller already exposes a drm_encoder. I.e, it already creates a drm_encoder object. If we want the encoder chain to be connected to the output of this encoder, we'll need to link the 2 drm_encoders together, which isn't possible at the moment.
Actually my goal was to move everybody to the drm_encoder_element model, even the encoder directly provided by the display controller IP. If the internal encoder is actually directly connected to a connector, then the encoder chain will just contain one element, but everything should work fine.
I guess we have two ways to go about this:
- Have an approach like this where all the entities in the encoder
chain connect to just one encoder. We define the sequence in which they are connected. The drm kms framework acts as if this chain doesn't exist, and assumes that this is what the encoder is outputting.
Yep, that's pretty much what I've done. The main reason for doing that is to keep the interface with the userspace unchanged.
- Make each element in the chain be a 'drm_encoder' object in itself.
This would be a more intrusive change, since drm_encoders are expected to receive an input from crtc, and output to a connector. Furthermore, it may confuse userspace what encoder to chose.
That's why Laurent suggested to go for the 1st approach.
For 1), we could either work more on your approach, or use drm_bridges. drm_bridges can already be chained to each other, and bridge ops of each bridge in the chain are called successively. It still relies on the device drivers to form the chain, which is something your approach takes care of by itself. But that's something that can be extended for bridges too.
Can we really chain several bridges ?
Also note that I plan to automate the encoder chain creation, or at least provide helper functions so that in standard cases the display controller does not have to bother creating its encoder chain. This is particularly true for platforms supporting DT, the encoder chain + connector can be declared in a generic way, and the core could provide helper functions to parse and create the encoder chain and the endpoint connector.
Laurent,
Merging i2c slave encoders and bridges is possible, but there is no guarantee that the new solution would be future proof and work well with encoder chains. We needed more consensus from folks on dri-devel.
I'll let Laurent correct me if I'm wrong, but I think the plan was to move all slave encoders and bridges to the encoder element representation.
Best Regards,
Boris
On 07/31/2015 02:42 PM, Boris Brezillon wrote:
Hi Archit,
On Fri, 31 Jul 2015 10:56:20 +0530 Archit Taneja architt@codeaurora.org wrote:
Hi Boris, Laurent,
On 07/28/2015 08:08 PM, Boris Brezillon wrote:
Archit, Laurent,
On Tue, 28 Jul 2015 13:47:37 +0530 Archit Taneja architt@codeaurora.org wrote:
Hi,
On 07/27/2015 02:29 PM, Laurent Pinchart wrote:
Hi Archit,
(CC'ing Boris Brezillon)
Thank you for the patch.
On Monday 27 July 2015 11:46:57 Archit Taneja wrote:
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Please, no. It's really time to stop piling hacks and fix the problem properly. There's no reason to have separate APIs for I2C slave encoders and DRM bridges. Those two APIs need to be merged, and then you'll find it much easier to implement ADV7533 support.
i2c slave encoders and bridges aren't exactly the same. slave encoders are used when the there is no real 'encoder' in the display chain. bridges are used when there is already an encoder available, and the bridge entity represents another encoder in the chain.
ADV7511 takes in RGB/MIPI DPI data, which is generally the output of a crtc for drm drivers.
ADV7533 takes in MIPI DSI data, which is generally the output of an encoder for drm drivers.
Therefore, having i2c slave encoder for the former and bridge for the latter made sense to me.
I do agree that it would be better if they were somehow merged. It would prevent the fragmentation we currently have among encoder chips.
One possible way would be to convert slave encoder to bridge. With this, an i2c slave encoder would be a 'dummy encoder' and a bridge. i2c slave encoders even now just tie the slave encoder helper funcs to encoder helper funcs. So it's not really any different.
Merging these 2 entities would be nice, but we're still shying away from the larger problem of creating generic encoder chains. The ideal solution would be for bridges and slave encoders to just be 'encoders', and the facility to connect on encoder output to the input of another. I don't know how easy it is to do this, and whether it'll break userspace.
Yes, that's pretty much what I was trying to do. I'd also like to ease display pipelines creation by providing helper functions, so that display controller don't have to worry about encoders and connectors creation if these ones are attached to external encoders.
Archit
Boris, I know you were experimenting with that, do you have anything to report ?
Nope, I didn't work on it since last time we talked about it. I pushed my work here if you want to have a look [1].
I went through the branch you shared. From what I understood, the encoder chain comprises of one 'real' encoder object (drm_encoder) in the 'drm_encoder_chain' struct. This drm_encoder encapsulates all the 'encoder elements' forming the chain.
I'm guessing the various dridge/slave encoder drivers would have to be changed to now create a drm_encoder_element object, replacing drm_bridge/drm_i2c_slave_encoder objects.
One problem I see with this approach is that we can't use this when the display controller already exposes a drm_encoder. I.e, it already creates a drm_encoder object. If we want the encoder chain to be connected to the output of this encoder, we'll need to link the 2 drm_encoders together, which isn't possible at the moment.
Actually my goal was to move everybody to the drm_encoder_element model, even the encoder directly provided by the display controller IP. If the internal encoder is actually directly connected to a connector, then the encoder chain will just contain one element, but everything should work fine.
Okay. That approach makes sense.
It might be good to have a look at the current state of drm_bridge. We need to probably make a call between extending bridges or starting with encoder elements from scratch. Extending bridges might be less intrusive. Although, encoder elements is more uniform. In bridges, we'll be stuck with two entities: One encoder (drm_encoder), followed by a chain of bridges.
I guess we have two ways to go about this:
- Have an approach like this where all the entities in the encoder
chain connect to just one encoder. We define the sequence in which they are connected. The drm kms framework acts as if this chain doesn't exist, and assumes that this is what the encoder is outputting.
Yep, that's pretty much what I've done. The main reason for doing that is to keep the interface with the userspace unchanged.
- Make each element in the chain be a 'drm_encoder' object in itself.
This would be a more intrusive change, since drm_encoders are expected to receive an input from crtc, and output to a connector. Furthermore, it may confuse userspace what encoder to chose.
That's why Laurent suggested to go for the 1st approach.
For 1), we could either work more on your approach, or use drm_bridges. drm_bridges can already be chained to each other, and bridge ops of each bridge in the chain are called successively. It still relies on the device drivers to form the chain, which is something your approach takes care of by itself. But that's something that can be extended for bridges too.
Can we really chain several bridges ?
Yes. The support was recently added. We can link one bridge to another via drm_bridge_attach(), by populating the bridge->next field.
The bridge helpers used in atomic_helper.c and crtc_helper.c recursively call all the bridges in the chain.
Also note that I plan to automate the encoder chain creation, or at least provide helper functions so that in standard cases the display controller does not have to bother creating its encoder chain. This is particularly true for platforms supporting DT, the encoder chain + connector can be declared in a generic way, and the core could provide helper functions to parse and create the encoder chain and the endpoint connector.
This would be quite useful.
Laurent,
Merging i2c slave encoders and bridges is possible, but there is no guarantee that the new solution would be future proof and work well with encoder chains. We needed more consensus from folks on dri-devel.
I'll let Laurent correct me if I'm wrong, but I think the plan was to move all slave encoders and bridges to the encoder element representation.
Some troubles I've had when working with encoder chains:
- There can be dependencies between two elements in the chain. For example, an element in the chain might provide interface clocks for the next element in the chain. The laterelement shouldn't even try to touch its registers if the previous element isn't enabled.
- The sequence in which each encoder element needs to be called. Some have to be first to last, others last to first.
- Some external encoder drivers(currently bridge, i2c slaves) create their own connectors within the driver. If such an encoder is placed in the middle of an encoder chain. It could lead to problems.
We might want to consider these (and probably more things) when working on the solution.
Thanks, Archit
On Fri, Jul 31, 2015 at 5:12 AM, Boris Brezillon boris.brezillon@free-electrons.com wrote:
Hi Archit,
On Fri, 31 Jul 2015 10:56:20 +0530 Archit Taneja architt@codeaurora.org wrote:
Hi Boris, Laurent,
On 07/28/2015 08:08 PM, Boris Brezillon wrote:
Archit, Laurent,
On Tue, 28 Jul 2015 13:47:37 +0530 Archit Taneja architt@codeaurora.org wrote:
Hi,
On 07/27/2015 02:29 PM, Laurent Pinchart wrote:
Hi Archit,
(CC'ing Boris Brezillon)
Thank you for the patch.
On Monday 27 July 2015 11:46:57 Archit Taneja wrote:
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Please, no. It's really time to stop piling hacks and fix the problem properly. There's no reason to have separate APIs for I2C slave encoders and DRM bridges. Those two APIs need to be merged, and then you'll find it much easier to implement ADV7533 support.
i2c slave encoders and bridges aren't exactly the same. slave encoders are used when the there is no real 'encoder' in the display chain. bridges are used when there is already an encoder available, and the bridge entity represents another encoder in the chain.
ADV7511 takes in RGB/MIPI DPI data, which is generally the output of a crtc for drm drivers.
ADV7533 takes in MIPI DSI data, which is generally the output of an encoder for drm drivers.
Therefore, having i2c slave encoder for the former and bridge for the latter made sense to me.
I do agree that it would be better if they were somehow merged. It would prevent the fragmentation we currently have among encoder chips.
One possible way would be to convert slave encoder to bridge. With this, an i2c slave encoder would be a 'dummy encoder' and a bridge. i2c slave encoders even now just tie the slave encoder helper funcs to encoder helper funcs. So it's not really any different.
Merging these 2 entities would be nice, but we're still shying away from the larger problem of creating generic encoder chains. The ideal solution would be for bridges and slave encoders to just be 'encoders', and the facility to connect on encoder output to the input of another. I don't know how easy it is to do this, and whether it'll break userspace.
Yes, that's pretty much what I was trying to do. I'd also like to ease display pipelines creation by providing helper functions, so that display controller don't have to worry about encoders and connectors creation if these ones are attached to external encoders.
Archit
Boris, I know you were experimenting with that, do you have anything to report ?
Nope, I didn't work on it since last time we talked about it. I pushed my work here if you want to have a look [1].
I went through the branch you shared. From what I understood, the encoder chain comprises of one 'real' encoder object (drm_encoder) in the 'drm_encoder_chain' struct. This drm_encoder encapsulates all the 'encoder elements' forming the chain.
I'm guessing the various dridge/slave encoder drivers would have to be changed to now create a drm_encoder_element object, replacing drm_bridge/drm_i2c_slave_encoder objects.
One problem I see with this approach is that we can't use this when the display controller already exposes a drm_encoder. I.e, it already creates a drm_encoder object. If we want the encoder chain to be connected to the output of this encoder, we'll need to link the 2 drm_encoders together, which isn't possible at the moment.
Actually my goal was to move everybody to the drm_encoder_element model, even the encoder directly provided by the display controller IP. If the internal encoder is actually directly connected to a connector, then the encoder chain will just contain one element, but everything should work fine.
so.. I'd be careful about changing the role of drm_encoder, as it does play a small role in the interface exposed to userspace.
If you do anything, I think you need to make i2c-encoder-slave stuff look like a drm_bridge + drm_connector, since bridge is not visible to userspace and can already be chained... which I think ends up making it more like how adv7533 looks w/ archit's patches.
That said, the adv7511 type case (raw parallel output) is kind of a better fit for the existing i2c-slave-encoder model, vs adv7533 where you already have a dsi encoder and is a better fit for the drm_bridge model. So maybe there is still a place for both.
At any rate, if we do unify, I think we should go towards the drm_bridge + drm_connector approach and migrate i2c-encoder users over to that. Which would make Archit's approach a reasonable transition step. We just drop the i2c-encoder part of it when none of the adv7511 users still depend on that.
BR, -R
I guess we have two ways to go about this:
- Have an approach like this where all the entities in the encoder
chain connect to just one encoder. We define the sequence in which they are connected. The drm kms framework acts as if this chain doesn't exist, and assumes that this is what the encoder is outputting.
Yep, that's pretty much what I've done. The main reason for doing that is to keep the interface with the userspace unchanged.
- Make each element in the chain be a 'drm_encoder' object in itself.
This would be a more intrusive change, since drm_encoders are expected to receive an input from crtc, and output to a connector. Furthermore, it may confuse userspace what encoder to chose.
That's why Laurent suggested to go for the 1st approach.
For 1), we could either work more on your approach, or use drm_bridges. drm_bridges can already be chained to each other, and bridge ops of each bridge in the chain are called successively. It still relies on the device drivers to form the chain, which is something your approach takes care of by itself. But that's something that can be extended for bridges too.
Can we really chain several bridges ?
Also note that I plan to automate the encoder chain creation, or at least provide helper functions so that in standard cases the display controller does not have to bother creating its encoder chain. This is particularly true for platforms supporting DT, the encoder chain + connector can be declared in a generic way, and the core could provide helper functions to parse and create the encoder chain and the endpoint connector.
Laurent,
Merging i2c slave encoders and bridges is possible, but there is no guarantee that the new solution would be future proof and work well with encoder chains. We needed more consensus from folks on dri-devel.
I'll let Laurent correct me if I'm wrong, but I think the plan was to move all slave encoders and bridges to the encoder element representation.
Best Regards,
Boris
-- Boris Brezillon, Free Electrons Embedded Linux and Kernel engineering http://free-electrons.com _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
Hi Rob,
On Fri, 31 Jul 2015 08:13:59 -0400 Rob Clark robdclark@gmail.com wrote:
I went through the branch you shared. From what I understood, the encoder chain comprises of one 'real' encoder object (drm_encoder) in the 'drm_encoder_chain' struct. This drm_encoder encapsulates all the 'encoder elements' forming the chain.
I'm guessing the various dridge/slave encoder drivers would have to be changed to now create a drm_encoder_element object, replacing drm_bridge/drm_i2c_slave_encoder objects.
One problem I see with this approach is that we can't use this when the display controller already exposes a drm_encoder. I.e, it already creates a drm_encoder object. If we want the encoder chain to be connected to the output of this encoder, we'll need to link the 2 drm_encoders together, which isn't possible at the moment.
Actually my goal was to move everybody to the drm_encoder_element model, even the encoder directly provided by the display controller IP. If the internal encoder is actually directly connected to a connector, then the encoder chain will just contain one element, but everything should work fine.
so.. I'd be careful about changing the role of drm_encoder, as it does play a small role in the interface exposed to userspace.
I don't think I changed the role of the drm_encoder in my approach. Actually I'm only exposing one encoder for the whole encoder chain. The encoder chain is containing one or several encoder elements, which are not exposed to userspace.
If you do anything, I think you need to make i2c-encoder-slave stuff look like a drm_bridge + drm_connector, since bridge is not visible to userspace and can already be chained... which I think ends up making it more like how adv7533 looks w/ archit's patches.
Okay, maybe we can do the same with drm bridges (I wasn't aware that these ones could be chained before Archit mentioned it). I remember that I was missing a few features in the DRM bridge implementation (like the possibility to negotiate the format passed on the link between 2 encoders), but that's probably something we can add.
That said, the adv7511 type case (raw parallel output) is kind of a better fit for the existing i2c-slave-encoder model, vs adv7533 where you already have a dsi encoder and is a better fit for the drm_bridge model. So maybe there is still a place for both.
Excuse my ignorance, but I still don't get why the RGB/MIPI DPI are not represented as encoders. They are dummy encoders which just forwards the output of the display controller in raw RGB format, but still. IMHO, representing them as encoders in the chain would ease the whole thing. Moreover, by doing that we would be able to link this RGB/DPI encoder to a bridge, and we wouldn't have to differentiate the encoder-slave and bridge elements.
At any rate, if we do unify, I think we should go towards the drm_bridge + drm_connector approach and migrate i2c-encoder users over to that. Which would make Archit's approach a reasonable transition step. We just drop the i2c-encoder part of it when none of the adv7511 users still depend on that.
Another problem I've seen with some drm bridge drivers is that they directly create their own connector, which, as Archit stated, is wrong if you decide to chain this bridge with another bridge. The other reason why the bridge should not create the connector by its own is that in some case the encoder supports different types of connectors (a TDMS encoder can be used to output HDMI or DVI), and thus, selecting the connector type should be left to the part responsible for creating the display pipelines.
This being said, I'm definitely not an expert in this area, so don't hesitate to show me where I'm wrong.
Best Regards,
Boris
On Fri, Jul 31, 2015 at 8:58 AM, Boris Brezillon boris.brezillon@free-electrons.com wrote:
Hi Rob,
On Fri, 31 Jul 2015 08:13:59 -0400 Rob Clark robdclark@gmail.com wrote:
I went through the branch you shared. From what I understood, the encoder chain comprises of one 'real' encoder object (drm_encoder) in the 'drm_encoder_chain' struct. This drm_encoder encapsulates all the 'encoder elements' forming the chain.
I'm guessing the various dridge/slave encoder drivers would have to be changed to now create a drm_encoder_element object, replacing drm_bridge/drm_i2c_slave_encoder objects.
One problem I see with this approach is that we can't use this when the display controller already exposes a drm_encoder. I.e, it already creates a drm_encoder object. If we want the encoder chain to be connected to the output of this encoder, we'll need to link the 2 drm_encoders together, which isn't possible at the moment.
Actually my goal was to move everybody to the drm_encoder_element model, even the encoder directly provided by the display controller IP. If the internal encoder is actually directly connected to a connector, then the encoder chain will just contain one element, but everything should work fine.
so.. I'd be careful about changing the role of drm_encoder, as it does play a small role in the interface exposed to userspace.
I don't think I changed the role of the drm_encoder in my approach. Actually I'm only exposing one encoder for the whole encoder chain. The encoder chain is containing one or several encoder elements, which are not exposed to userspace.
If you do anything, I think you need to make i2c-encoder-slave stuff look like a drm_bridge + drm_connector, since bridge is not visible to userspace and can already be chained... which I think ends up making it more like how adv7533 looks w/ archit's patches.
Okay, maybe we can do the same with drm bridges (I wasn't aware that these ones could be chained before Archit mentioned it). I remember that I was missing a few features in the DRM bridge implementation (like the possibility to negotiate the format passed on the link between 2 encoders), but that's probably something we can add.
chaining bridges is very recent addition, fwiw
At any rate, extending bridge where needed seems preferable over adding new types of objects, I think.
That said, the adv7511 type case (raw parallel output) is kind of a better fit for the existing i2c-slave-encoder model, vs adv7533 where you already have a dsi encoder and is a better fit for the drm_bridge model. So maybe there is still a place for both.
Excuse my ignorance, but I still don't get why the RGB/MIPI DPI are not represented as encoders. They are dummy encoders which just forwards the output of the display controller in raw RGB format, but still. IMHO, representing them as encoders in the chain would ease the whole thing.
Yeah, creating a dummy encoder in the driver would be the approach to switch from i2c-encoder to bridge. I didn't mean to imply that this couldn't be done. The only point I was trying to make was that for simple cases don't need this currently. (The case I'm more familiar w/ is tilcdc -> tda998x but I guess other simple display controllers to adv7511 is probably similar)
I guess in the i2c-encoder case the driver ends up creating a sort of shim encoder/connector, so maybe it isn't that much different. It is a bunch of shuffling things around to change from i2c-encoder to bridge, and it ends up effecting a bunch of drivers (including some less simple ones like nouveau), so I'm not sure the best migration path. Exposing both i2c-encoder and bridge interfaces for a period of time seems unavoidable..
Moreover, by doing that we would be able to link this RGB/DPI encoder to a bridge, and we wouldn't have to differentiate the encoder-slave and bridge elements.
At any rate, if we do unify, I think we should go towards the drm_bridge + drm_connector approach and migrate i2c-encoder users over to that. Which would make Archit's approach a reasonable transition step. We just drop the i2c-encoder part of it when none of the adv7511 users still depend on that.
Another problem I've seen with some drm bridge drivers is that they directly create their own connector, which, as Archit stated, is wrong if you decide to chain this bridge with another bridge. The other
I agree with Archit on this. He took this approach w/ msm support for external bridges, and it seems sensible that the last bridge constructs the connector. (Plus presumably that is where hpd, ddc probing, etc, is happing)
reason why the bridge should not create the connector by its own is that in some case the encoder supports different types of connectors (a TDMS encoder can be used to output HDMI or DVI), and thus, selecting the connector type should be left to the part responsible for creating the display pipelines.
did you mean "should" instead of "should not"? Otherwise I don't think I understand..
BR, -R
This being said, I'm definitely not an expert in this area, so don't hesitate to show me where I'm wrong.
Best Regards,
Boris
-- Boris Brezillon, Free Electrons Embedded Linux and Kernel engineering http://free-electrons.com
Hi,
On 07/31/2015 04:48 PM, Rob Clark wrote:
On Fri, Jul 31, 2015 at 8:58 AM, Boris Brezillon boris.brezillon@free-electrons.com wrote:
Hi Rob,
On Fri, 31 Jul 2015 08:13:59 -0400 Rob Clark robdclark@gmail.com wrote:
(...)
Another problem I've seen with some drm bridge drivers is that they directly create their own connector, which, as Archit stated, is wrong if you decide to chain this bridge with another bridge. The other
I agree with Archit on this. He took this approach w/ msm support for external bridges, and it seems sensible that the last bridge constructs the connector. (Plus presumably that is where hpd, ddc probing, etc, is happing)
With this approach many bridges should construct connector conditionally, depending if there is something behind them in pipeline (the same is true for encoders and even crtcs). It works, but for me there is lot of unnecessary code and all those conditional paths make things less friendly for development. Wouldn't be better to move creation of the connector to the main drm driver, it would require probably adding some opses/fields to drm_bridges but the drivers would be simpler, I guess.
Regards Andrzej
reason why the bridge should not create the connector by its own is that in some case the encoder supports different types of connectors (a TDMS encoder can be used to output HDMI or DVI), and thus, selecting the connector type should be left to the part responsible for creating the display pipelines.
did you mean "should" instead of "should not"? Otherwise I don't think I understand..
BR, -R
This being said, I'm definitely not an expert in this area, so don't hesitate to show me where I'm wrong.
Best Regards,
Boris
-- Boris Brezillon, Free Electrons Embedded Linux and Kernel engineering http://free-electrons.com
On Mon, Aug 3, 2015 at 8:03 AM, Andrzej Hajda a.hajda@samsung.com wrote:
Hi,
On 07/31/2015 04:48 PM, Rob Clark wrote:
On Fri, Jul 31, 2015 at 8:58 AM, Boris Brezillon boris.brezillon@free-electrons.com wrote:
Hi Rob,
On Fri, 31 Jul 2015 08:13:59 -0400 Rob Clark robdclark@gmail.com wrote:
(...)
Another problem I've seen with some drm bridge drivers is that they directly create their own connector, which, as Archit stated, is wrong if you decide to chain this bridge with another bridge. The other
I agree with Archit on this. He took this approach w/ msm support for external bridges, and it seems sensible that the last bridge constructs the connector. (Plus presumably that is where hpd, ddc probing, etc, is happing)
With this approach many bridges should construct connector conditionally, depending if there is something behind them in pipeline (the same is true for encoders and even crtcs). It works, but for me there is lot of unnecessary code and all those conditional paths make things less friendly for development. Wouldn't be better to move creation of the connector to the main drm driver, it would require probably adding some opses/fields to drm_bridges but the drivers would be simpler, I guess.
six of one, half dozen of the other.. you'd still need to implement the hpd and ddc probe bits, and that sort of thing *somewhere*. Whether it is directly by implementing drm_connector in the bridge, or indirectly via some extra drm_bridge op's called by a shim connector in the main drm driver, doesn't really seem to change things..
BR, -R
Regards Andrzej
reason why the bridge should not create the connector by its own is that in some case the encoder supports different types of connectors (a TDMS encoder can be used to output HDMI or DVI), and thus, selecting the connector type should be left to the part responsible for creating the display pipelines.
did you mean "should" instead of "should not"? Otherwise I don't think I understand..
BR, -R
This being said, I'm definitely not an expert in this area, so don't hesitate to show me where I'm wrong.
Best Regards,
Boris
-- Boris Brezillon, Free Electrons Embedded Linux and Kernel engineering http://free-electrons.com
On 08/03/2015 04:04 PM, Rob Clark wrote:
On Mon, Aug 3, 2015 at 8:03 AM, Andrzej Hajda a.hajda@samsung.com wrote:
Hi,
On 07/31/2015 04:48 PM, Rob Clark wrote:
On Fri, Jul 31, 2015 at 8:58 AM, Boris Brezillon boris.brezillon@free-electrons.com wrote:
Hi Rob,
On Fri, 31 Jul 2015 08:13:59 -0400 Rob Clark robdclark@gmail.com wrote:
(...)
Another problem I've seen with some drm bridge drivers is that they directly create their own connector, which, as Archit stated, is wrong if you decide to chain this bridge with another bridge. The other
I agree with Archit on this. He took this approach w/ msm support for external bridges, and it seems sensible that the last bridge constructs the connector. (Plus presumably that is where hpd, ddc probing, etc, is happing)
With this approach many bridges should construct connector conditionally, depending if there is something behind them in pipeline (the same is true for encoders and even crtcs). It works, but for me there is lot of unnecessary code and all those conditional paths make things less friendly for development. Wouldn't be better to move creation of the connector to the main drm driver, it would require probably adding some opses/fields to drm_bridges but the drivers would be simpler, I guess.
six of one, half dozen of the other.. you'd still need to implement the hpd and ddc probe bits, and that sort of thing *somewhere*. Whether it is directly by implementing drm_connector in the bridge, or indirectly via some extra drm_bridge op's called by a shim connector in the main drm driver, doesn't really seem to change things..
The difference is that instead of duplicating connector related code in every driver you will call one helper from the main drm driver/core.
Regards Andrzej
BR, -R
Regards Andrzej
reason why the bridge should not create the connector by its own is that in some case the encoder supports different types of connectors (a TDMS encoder can be used to output HDMI or DVI), and thus, selecting the connector type should be left to the part responsible for creating the display pipelines.
did you mean "should" instead of "should not"? Otherwise I don't think I understand..
BR, -R
This being said, I'm definitely not an expert in this area, so don't hesitate to show me where I'm wrong.
Best Regards,
Boris
-- Boris Brezillon, Free Electrons Embedded Linux and Kernel engineering http://free-electrons.com
On Tue, Aug 4, 2015 at 1:16 AM, Andrzej Hajda a.hajda@samsung.com wrote:
On 08/03/2015 04:04 PM, Rob Clark wrote:
On Mon, Aug 3, 2015 at 8:03 AM, Andrzej Hajda a.hajda@samsung.com wrote:
Hi,
On 07/31/2015 04:48 PM, Rob Clark wrote:
On Fri, Jul 31, 2015 at 8:58 AM, Boris Brezillon boris.brezillon@free-electrons.com wrote:
Hi Rob,
On Fri, 31 Jul 2015 08:13:59 -0400 Rob Clark robdclark@gmail.com wrote:
(...)
Another problem I've seen with some drm bridge drivers is that they directly create their own connector, which, as Archit stated, is wrong if you decide to chain this bridge with another bridge. The other
I agree with Archit on this. He took this approach w/ msm support for external bridges, and it seems sensible that the last bridge constructs the connector. (Plus presumably that is where hpd, ddc probing, etc, is happing)
With this approach many bridges should construct connector conditionally, depending if there is something behind them in pipeline (the same is true for encoders and even crtcs). It works, but for me there is lot of unnecessary code and all those conditional paths make things less friendly for development. Wouldn't be better to move creation of the connector to the main drm driver, it would require probably adding some opses/fields to drm_bridges but the drivers would be simpler, I guess.
six of one, half dozen of the other.. you'd still need to implement the hpd and ddc probe bits, and that sort of thing *somewhere*. Whether it is directly by implementing drm_connector in the bridge, or indirectly via some extra drm_bridge op's called by a shim connector in the main drm driver, doesn't really seem to change things..
The difference is that instead of duplicating connector related code in every driver you will call one helper from the main drm driver/core.
Which isn't more than a few lines of code.. I mean, looking at a couple connectors, the bulk of the code is hpd and ddc related. That doesn't magically go away. There isn't that many lines of boilerplate, so meh.
BR, -R
Regards Andrzej
BR, -R
Regards Andrzej
reason why the bridge should not create the connector by its own is that in some case the encoder supports different types of connectors (a TDMS encoder can be used to output HDMI or DVI), and thus, selecting the connector type should be left to the part responsible for creating the display pipelines.
did you mean "should" instead of "should not"? Otherwise I don't think I understand..
BR, -R
This being said, I'm definitely not an expert in this area, so don't hesitate to show me where I'm wrong.
Best Regards,
Boris
-- Boris Brezillon, Free Electrons Embedded Linux and Kernel engineering http://free-electrons.com
On 08/04/2015 05:54 PM, Rob Clark wrote:
On Tue, Aug 4, 2015 at 1:16 AM, Andrzej Hajda a.hajda@samsung.com wrote:
On 08/03/2015 04:04 PM, Rob Clark wrote:
On Mon, Aug 3, 2015 at 8:03 AM, Andrzej Hajda a.hajda@samsung.com wrote:
Hi,
On 07/31/2015 04:48 PM, Rob Clark wrote:
On Fri, Jul 31, 2015 at 8:58 AM, Boris Brezillon boris.brezillon@free-electrons.com wrote:
Hi Rob,
On Fri, 31 Jul 2015 08:13:59 -0400 Rob Clark robdclark@gmail.com wrote:
(...)
Another problem I've seen with some drm bridge drivers is that they directly create their own connector, which, as Archit stated, is wrong if you decide to chain this bridge with another bridge. The other
I agree with Archit on this. He took this approach w/ msm support for external bridges, and it seems sensible that the last bridge constructs the connector. (Plus presumably that is where hpd, ddc probing, etc, is happing)
With this approach many bridges should construct connector conditionally, depending if there is something behind them in pipeline (the same is true for encoders and even crtcs). It works, but for me there is lot of unnecessary code and all those conditional paths make things less friendly for development. Wouldn't be better to move creation of the connector to the main drm driver, it would require probably adding some opses/fields to drm_bridges but the drivers would be simpler, I guess.
six of one, half dozen of the other.. you'd still need to implement the hpd and ddc probe bits, and that sort of thing *somewhere*. Whether it is directly by implementing drm_connector in the bridge, or indirectly via some extra drm_bridge op's called by a shim connector in the main drm driver, doesn't really seem to change things..
The difference is that instead of duplicating connector related code in every driver you will call one helper from the main drm driver/core.
Which isn't more than a few lines of code.. I mean, looking at a couple connectors, the bulk of the code is hpd and ddc related. That doesn't magically go away. There isn't that many lines of boilerplate, so meh.
Could we get to a consensus for this?
Is it okay for bridge and i2c_encoder to co-exist for now?
Thanks, Archit
BR, -R
Regards Andrzej
BR, -R
Regards Andrzej
reason why the bridge should not create the connector by its own is that in some case the encoder supports different types of connectors (a TDMS encoder can be used to output HDMI or DVI), and thus, selecting the connector type should be left to the part responsible for creating the display pipelines.
did you mean "should" instead of "should not"? Otherwise I don't think I understand..
BR, -R
This being said, I'm definitely not an expert in this area, so don't hesitate to show me where I'm wrong.
Best Regards,
Boris
-- Boris Brezillon, Free Electrons Embedded Linux and Kernel engineering http://free-electrons.com
On Mon, Jul 27, 2015 at 4:59 AM, Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
Hi Archit,
(CC'ing Boris Brezillon)
Thank you for the patch.
On Monday 27 July 2015 11:46:57 Archit Taneja wrote:
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Please, no. It's really time to stop piling hacks and fix the problem properly. There's no reason to have separate APIs for I2C slave encoders and DRM bridges. Those two APIs need to be merged, and then you'll find it much easier to implement ADV7533 support.
btw, what is the status here? I'd kind of lost track of the discussion, but I'm getting impatient that it is somehow taking ridiculously long to get adv7533 support merged. (It's good thing the x86/desktop folks don't bikeshed so much.. I'd hate to wait a year for my new laptop to be supported..)
Anyways, if needed, just copy/paste the adv7511/7533 code into a separate bridge-only driver, and we'll use that. Once the i2c-slave-encoder users for adv7511 are converted over, we can delete the original slave encoder driver. That seems like a sane transition plan to a bridge-only world.
BR, -R
Boris, I know you were experimenting with that, do you have anything to report ?
Move the code in encoder slave functions to generate helpers that are agnostic to the drm object type. These helpers will later also be used by bridge and connecter helper functions.
Signed-off-by: Archit Taneja architt@codeaurora.org
drivers/gpu/drm/i2c/adv7511.c | 80 +++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 23 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 63a3d20..46fb24d 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -612,13 +612,11 @@ 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,
struct drm_connector *connector)
+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;
@@ -656,21 +654,10 @@ 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, +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;
@@ -694,7 +681,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 {
@@ -708,8 +695,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,
const struct drm_display_mode *mode)
{ if (mode->clock > 165000) return MODE_CLOCK_HIGH; @@ -717,11 +704,10 @@ static int adv7511_encoder_mode_valid(struct drm_encoder *encoder, return MODE_OK; }
-static void adv7511_encoder_mode_set(struct drm_encoder *encoder, +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;
@@ -812,12 +798,60 @@ static void adv7511_encoder_mode_set(struct drm_encoder *encoder, adv7511->f_tmds = mode->clock; }
+/*
-- + * Encoder operations
- */
+static int adv7511_encoder_get_modes(struct drm_encoder *encoder,
struct drm_connector *connector)
+{
struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
return adv7511_get_modes(adv7511, connector);
+}
+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)
+{
struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
return adv7511_detect(adv7511, connector);
+}
+static int adv7511_encoder_mode_valid(struct drm_encoder *encoder,
struct drm_display_mode *mode)
+{
struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
return adv7511_mode_valid(adv7511, mode);
+}
+static void adv7511_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
+{
struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
adv7511_mode_set(adv7511, mode, adj_mode);
+}
static 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,
.get_modes = adv7511_encoder_get_modes,
};
/*
--
-- Regards,
Laurent Pinchart
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
On Thursday 03 December 2015 10:02:02 Rob Clark wrote:
On Mon, Jul 27, 2015 at 4:59 AM, Laurent Pinchart wrote:
On Monday 27 July 2015 11:46:57 Archit Taneja wrote:
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Please, no. It's really time to stop piling hacks and fix the problem properly. There's no reason to have separate APIs for I2C slave encoders and DRM bridges. Those two APIs need to be merged, and then you'll find it much easier to implement ADV7533 support.
btw, what is the status here? I'd kind of lost track of the discussion, but I'm getting impatient that it is somehow taking ridiculously long to get adv7533 support merged. (It's good thing the x86/desktop folks don't bikeshed so much.. I'd hate to wait a year for my new laptop to be supported..)
I'd hardly call the overall architecture on which drivers rely a bikeshed (and maybe if x86/desktop folks cared more about embedded there would be more willingness to make the framework evolve in an embedded-friendly way).
Anyways, if needed, just copy/paste the adv7511/7533 code into a separate bridge-only driver, and we'll use that. Once the i2c-slave-encoder users for adv7511 are converted over, we can delete the original slave encoder driver. That seems like a sane transition plan to a bridge-only world.
It's a very sane way to make sure nobody will do the work and keep two copies of the same code for a long time, yes.
The path forward is pretty clear, the issue is to find someone who can do the work.
On Thu, Dec 3, 2015 at 10:28 AM, Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
On Thursday 03 December 2015 10:02:02 Rob Clark wrote:
On Mon, Jul 27, 2015 at 4:59 AM, Laurent Pinchart wrote:
On Monday 27 July 2015 11:46:57 Archit Taneja wrote:
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Please, no. It's really time to stop piling hacks and fix the problem properly. There's no reason to have separate APIs for I2C slave encoders and DRM bridges. Those two APIs need to be merged, and then you'll find it much easier to implement ADV7533 support.
btw, what is the status here? I'd kind of lost track of the discussion, but I'm getting impatient that it is somehow taking ridiculously long to get adv7533 support merged. (It's good thing the x86/desktop folks don't bikeshed so much.. I'd hate to wait a year for my new laptop to be supported..)
I'd hardly call the overall architecture on which drivers rely a bikeshed (and maybe if x86/desktop folks cared more about embedded there would be more willingness to make the framework evolve in an embedded-friendly way).
I don't know that we can blame this on the desktop folks like that.. after all i2c-slave was sufficient for embedded devices for some time, and it was an improvement over what came before when it comes to sharing external encoder support (ie. nothing)
But, as was the case with atomic, the framework evolves. And as atomic roll-out has shown, it doesn't require a flag-day if you accept some duplication.
Anyways, if needed, just copy/paste the adv7511/7533 code into a separate bridge-only driver, and we'll use that. Once the i2c-slave-encoder users for adv7511 are converted over, we can delete the original slave encoder driver. That seems like a sane transition plan to a bridge-only world.
It's a very sane way to make sure nobody will do the work and keep two copies of the same code for a long time, yes.
As opposed to a change everything at once approach, and corresponding updates to drivers for hw you don't have. That sounds like a *much* saner plan</sarcasm>
Seriously, the cost of duplicating a bit of code is low. Especially when the code you are duplicating is low churn. Duplicating lets other drivers that use adv75xx via slave-encoder migrate over at their own pace. Similar to how atomic was rolled out. To me that sounds like the much more practical way forward.
The path forward is pretty clear, the issue is to find someone who can do the work.
Maybe the end goal is clear. But apparently the path forward less so.
BR, -R
-- Regards,
Laurent Pinchart
Hi Rob,
On Thursday 03 December 2015 10:55:12 Rob Clark wrote:
On Thu, Dec 3, 2015 at 10:28 AM, Laurent Pinchart wrote:
On Thursday 03 December 2015 10:02:02 Rob Clark wrote:
On Mon, Jul 27, 2015 at 4:59 AM, Laurent Pinchart wrote:
On Monday 27 July 2015 11:46:57 Archit Taneja wrote:
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Please, no. It's really time to stop piling hacks and fix the problem properly. There's no reason to have separate APIs for I2C slave encoders and DRM bridges. Those two APIs need to be merged, and then you'll find it much easier to implement ADV7533 support.
btw, what is the status here? I'd kind of lost track of the discussion, but I'm getting impatient that it is somehow taking ridiculously long to get adv7533 support merged. (It's good thing the x86/desktop folks don't bikeshed so much.. I'd hate to wait a year for my new laptop to be supported..)
I'd hardly call the overall architecture on which drivers rely a bikeshed (and maybe if x86/desktop folks cared more about embedded there would be more willingness to make the framework evolve in an embedded-friendly way).
I don't know that we can blame this on the desktop folks like that..
To be clear I'm not blaming anyone here, just pointing out that the grass isn't all green on the other side ;-)
after all i2c-slave was sufficient for embedded devices for some time, and it was an improvement over what came before when it comes to sharing external encoder support (ie. nothing)
But, as was the case with atomic, the framework evolves. And as atomic roll-out has shown, it doesn't require a flag-day if you accept some duplication.
Anyways, if needed, just copy/paste the adv7511/7533 code into a separate bridge-only driver, and we'll use that. Once the i2c-slave-encoder users for adv7511 are converted over, we can delete the original slave encoder driver. That seems like a sane transition plan to a bridge-only world.
It's a very sane way to make sure nobody will do the work and keep two copies of the same code for a long time, yes.
As opposed to a change everything at once approach, and corresponding updates to drivers for hw you don't have. That sounds like a *much* saner plan</sarcasm>
There's a single driver using the adv7511 I2C slave encoder, that's the rcar- du driver. I will make sure to update and test the driver.
Seriously, the cost of duplicating a bit of code is low. Especially when the code you are duplicating is low churn. Duplicating lets other drivers that use adv75xx via slave-encoder migrate over at their own pace. Similar to how atomic was rolled out. To me that sounds like the much more practical way forward.
The path forward is pretty clear, the issue is to find someone who can do the work.
Maybe the end goal is clear. But apparently the path forward less so.
On 12/3/2015 9:25 PM, Rob Clark wrote:
On Thu, Dec 3, 2015 at 10:28 AM, Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
On Thursday 03 December 2015 10:02:02 Rob Clark wrote:
On Mon, Jul 27, 2015 at 4:59 AM, Laurent Pinchart wrote:
On Monday 27 July 2015 11:46:57 Archit Taneja wrote:
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Please, no. It's really time to stop piling hacks and fix the problem properly. There's no reason to have separate APIs for I2C slave encoders and DRM bridges. Those two APIs need to be merged, and then you'll find it much easier to implement ADV7533 support.
btw, what is the status here? I'd kind of lost track of the discussion, but I'm getting impatient that it is somehow taking ridiculously long to get adv7533 support merged. (It's good thing the x86/desktop folks don't bikeshed so much.. I'd hate to wait a year for my new laptop to be supported..)
I'd hardly call the overall architecture on which drivers rely a bikeshed (and maybe if x86/desktop folks cared more about embedded there would be more willingness to make the framework evolve in an embedded-friendly way).
I don't know that we can blame this on the desktop folks like that.. after all i2c-slave was sufficient for embedded devices for some time, and it was an improvement over what came before when it comes to sharing external encoder support (ie. nothing)
But, as was the case with atomic, the framework evolves. And as atomic roll-out has shown, it doesn't require a flag-day if you accept some duplication.
Anyways, if needed, just copy/paste the adv7511/7533 code into a separate bridge-only driver, and we'll use that. Once the i2c-slave-encoder users for adv7511 are converted over, we can delete the original slave encoder driver. That seems like a sane transition plan to a bridge-only world.
It's a very sane way to make sure nobody will do the work and keep two copies of the same code for a long time, yes.
As opposed to a change everything at once approach, and corresponding updates to drivers for hw you don't have. That sounds like a *much* saner plan</sarcasm>
Seriously, the cost of duplicating a bit of code is low. Especially when the code you are duplicating is low churn. Duplicating lets other drivers that use adv75xx via slave-encoder migrate over at their own pace. Similar to how atomic was rolled out. To me that sounds like the much more practical way forward.
The path forward is pretty clear, the issue is to find someone who can do the work.
I volunteer (as tribute)!
Once we settle on the best way to do it.
Maybe the end goal is clear. But apparently the path forward less so.
Laurent, as we'd discussed in the past, I did an initial study of how we could map bridge ops to the encoder slave ops, and change it for the rcar kms driver (and others) that might use adv7511.
The drm_encoder_slave_funcs ops are a superset of bridge ops. The rcar-du driver uses a subset of these ops (encoder-like ops) to implement the hdmi encoder (rcar_du_hdmienc.c) and the other (connector-like ops)for implementing the hdmi connector (rcar_du_hdmicon.c)
We have two options:
1. If we want to use drm_bridge as it is, the rcar driver shouldn't create a hdmi connector anymore, and rely on the adv7511 driver to make a connector for it. This is the easiest way, but it will break the nice representation of the hardware in DT.
2. We add more drm_bridge ops (the ones that implement the connector ops), and make the hdmi connector created by rcar-du use these bridge ops.
Both these options have issues. The 2) one is the probably the better of the two, but I don't know if adding more ops to the bridge is the right way to go.
Therefore, I don't know how we can solve this straight away. If there is more debate needed on this, then it is probably easier to get adv7533 support in, and then figure out how two merge the two.
Archit
BR, -R
-- Regards,
Laurent Pinchart
Hi Laurent,
On 12/3/2015 9:41 PM, Archit Taneja wrote:
On 12/3/2015 9:25 PM, Rob Clark wrote:
On Thu, Dec 3, 2015 at 10:28 AM, Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
On Thursday 03 December 2015 10:02:02 Rob Clark wrote:
On Mon, Jul 27, 2015 at 4:59 AM, Laurent Pinchart wrote:
On Monday 27 July 2015 11:46:57 Archit Taneja wrote:
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
<snip>
Laurent, as we'd discussed in the past, I did an initial study of how we could map bridge ops to the encoder slave ops, and change it for the rcar kms driver (and others) that might use adv7511.
The drm_encoder_slave_funcs ops are a superset of bridge ops. The rcar-du driver uses a subset of these ops (encoder-like ops) to implement the hdmi encoder (rcar_du_hdmienc.c) and the other (connector-like ops)for implementing the hdmi connector (rcar_du_hdmicon.c)
We have two options:
- If we want to use drm_bridge as it is, the rcar driver shouldn't
create a hdmi connector anymore, and rely on the adv7511 driver to make a connector for it. This is the easiest way, but it will break the nice representation of the hardware in DT.
- We add more drm_bridge ops (the ones that implement the connector
ops), and make the hdmi connector created by rcar-du use these bridge ops.
Both these options have issues. The 2) one is the probably the better of the two, but I don't know if adding more ops to the bridge is the right way to go.
Therefore, I don't know how we can solve this straight away. If there is more debate needed on this, then it is probably easier to get adv7533 support in, and then figure out how two merge the two.
I gave approach (1) a try. I modified the rcar-du driver to use bridge instead of i2c slave encoder and posted a RFC [1]. The hdmi connector DT nodes (like in r8a7790-lager.dts) aren't really used by the rcar-du driver. Now, with the adv7511 driver creating its own connector, they mean even much less.
The adv7511 refactor from i2c slave encoder to bridge is done in [2].
[1] http://marc.info/?l=dri-devel&m=145235835902378&w=1 [2] http://marc.info/?l=dri-devel&m=145235823602353&w=1
Could you please review and test?
Thanks, Archit
BR, -R
-- Regards,
Laurent Pinchart
Create bridge and connector helper functions. These internally refer to the ADV75xx helper functions.
The driver registers a drm_bridge object during probe. The bridge, in turn registers a HDMI connector when a user attaches the bridge.
Therefore, when the device type is ADV7533, we create bridge and connector entities, and when it's ADV7511, we create a slave encoder as before.
Since the i2c driver is still wrapped around by the drm_i2c_slave_encoder struct. We make sure the encoder_init op returns an error when the device type is ADV7533.
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 155 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 46fb24d..10642e1 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -17,6 +17,8 @@ #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> #include <drm/drm_encoder_slave.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h>
#include "adv7511.h"
@@ -44,6 +46,9 @@ struct adv7511 { wait_queue_head_t wq; struct drm_encoder *encoder;
+ struct drm_connector connector; + struct drm_bridge bridge; + bool embedded_sync; enum adv7511_sync_polarity vsync_polarity; enum adv7511_sync_polarity hsync_polarity; @@ -855,6 +860,139 @@ static struct drm_encoder_slave_funcs adv7511_encoder_funcs = { };
/* ----------------------------------------------------------------------------- + * Bridge and connector functions + */ + +static struct adv7511 *connector_to_adv7511(struct drm_connector *connector) +{ + return container_of(connector, struct adv7511, connector); +} + +/* Connector helper functions */ +static int adv7533_connector_get_modes(struct drm_connector *connector) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv7511_get_modes(adv, connector); +} + +static struct drm_encoder * +adv7533_connector_best_encoder(struct drm_connector *connector) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv->bridge.encoder; +} + +static enum drm_mode_status +adv7533_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 adv7533_connector_helper_funcs = { + .get_modes = adv7533_connector_get_modes, + .best_encoder = adv7533_connector_best_encoder, + .mode_valid = adv7533_connector_mode_valid, +}; + +static enum drm_connector_status +adv7533_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 adv7533_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = adv7533_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 adv7533_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_power_on(adv); +} + +static void adv7533_bridge_post_disable(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_power_off(adv); +} + +static void adv7533_bridge_enable(struct drm_bridge *bridge) +{ +} + +static void adv7533_bridge_disable(struct drm_bridge *bridge) +{ +} + +static void adv7533_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 adv7533_bridge_attach(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + int ret; + + adv->encoder = bridge->encoder; + + 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, + &adv7533_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, + &adv7533_connector_helper_funcs); + drm_connector_register(&adv->connector); + drm_mode_connector_attach_encoder(&adv->connector, adv->encoder); + + drm_helper_hpd_irq_event(adv->connector.dev); + + return ret; +} + +static struct drm_bridge_funcs adv7533_bridge_funcs = { + .pre_enable = adv7533_bridge_pre_enable, + .enable = adv7533_bridge_enable, + .disable = adv7533_bridge_disable, + .post_disable = adv7533_bridge_post_disable, + .mode_set = adv7533_bridge_mode_set, + .attach = adv7533_bridge_attach, +}; + +/* ----------------------------------------------------------------------------- * Probe & remove */
@@ -1079,6 +1217,17 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) if (adv7511->type == ADV7511) adv7511_set_link_config(adv7511, &link_config);
+ if (adv7511->type == ADV7533) { + adv7511->bridge.funcs = &adv7533_bridge_funcs; + adv7511->bridge.of_node = dev->of_node; + + ret = drm_bridge_add(&adv7511->bridge); + if (ret) { + dev_err(dev, "failed to add adv7533 bridge\n"); + goto err_i2c_unregister_cec; + } + } + return 0;
err_i2c_unregister_cec: @@ -1098,6 +1247,9 @@ static int adv7511_remove(struct i2c_client *i2c)
kfree(adv7511->edid);
+ if (adv7511->type == ADV7533) + drm_bridge_remove(&adv7511->bridge); + return 0; }
@@ -1107,6 +1259,9 @@ static int adv7511_encoder_init(struct i2c_client *i2c, struct drm_device *dev,
struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
+ if (adv7511->type == ADV7533) + return -ENODEV; + encoder->slave_priv = adv7511; encoder->slave_funcs = &adv7511_encoder_funcs;
In order to pass DSI specific parameters to the DSI host, we need the driver to create a mipi_dsi_device that attaches to the host.
Use of_graph helpers to get the DSI host DT node. Create a dummy dsi device using this host. Finally, attach this device to the host.
Populate few other DT parameters (number of data lanes etc) that are required for DSI RX to work correctly. Hardcode few other parameters (rgb, embedded_sync) for now.
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 106 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 10642e1..ccb57a9 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -12,6 +12,7 @@ #include <linux/module.h> #include <linux/regmap.h> #include <linux/slab.h> +#include <linux/of_graph.h>
#include <drm/drmP.h> #include <drm/drm_crtc_helper.h> @@ -19,6 +20,7 @@ #include <drm/drm_encoder_slave.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> +#include <drm/drm_mipi_dsi.h>
#include "adv7511.h"
@@ -58,6 +60,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; };
@@ -403,8 +410,10 @@ static void adv7511_dsi_receiver_dpms(struct adv7511 *adv7511) return;
if (adv7511->powered) { - /* set number of dsi lanes (hardcoded to 4 for now) */ - regmap_write(adv7511->regmap_cec, 0x1c, 0x4 << 4); + struct mipi_dsi_device *dsi = adv7511->dsi; + + /* set number of dsi lanes */ + regmap_write(adv7511->regmap_cec, 0x1c, dsi->lanes << 4); /* disable internal timing generator */ regmap_write(adv7511->regmap_cec, 0x27, 0x0b); /* enable hdmi */ @@ -954,6 +963,48 @@ static void adv7533_bridge_mode_set(struct drm_bridge *bridge, adv7511_mode_set(adv, mode, adj_mode); }
+static int adv7533_attach_dsi(struct adv7511 *adv7511) +{ + struct device *dev = &adv7511->i2c_main->dev; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *host; + int ret; + + host = of_find_mipi_dsi_host_by_node(adv7511->host_node); + if (!host) { + dev_err(dev, "failed to find dsi host\n"); + return -EPROBE_DEFER; + } + + /* can adv7533 virtual channel be non-zero? */ + dsi = mipi_dsi_new_dummy(host, 0); + if (IS_ERR(dsi)) { + dev_err(dev, "failed to create dummy dsi device\n"); + ret = PTR_ERR(dsi); + goto err_dsi_device; + } + + adv7511->dsi = dsi; + + dsi->lanes = adv7511->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_unregister_device(dsi); +err_dsi_device: + return ret; +} + static int adv7533_bridge_attach(struct drm_bridge *bridge) { struct adv7511 *adv = bridge_to_adv7511(bridge); @@ -980,6 +1031,8 @@ static int adv7533_bridge_attach(struct drm_bridge *bridge)
drm_helper_hpd_irq_event(adv->connector.dev);
+ adv7533_attach_dsi(adv); + return ret; }
@@ -1079,6 +1132,41 @@ static int adv7511_parse_dt(struct device_node *np, return 0; }
+static int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv7511) +{ + 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; + + adv7511->num_dsi_lanes = num_lanes; + + endpoint = of_graph_get_next_endpoint(np, NULL); + if (!endpoint) { + DRM_ERROR("adv dsi input endpoint not found\n"); + return -ENODEV; + } + + adv7511->host_node = of_graph_get_remote_port_parent(endpoint); + if (!adv7511->host_node) { + DRM_ERROR("dsi host node not found\n"); + of_node_put(endpoint); + return -ENODEV; + } + + of_node_put(endpoint); + of_node_put(adv7511->host_node); + + /* TODO: Check if these need to be parsed by DT or not */ + adv7511->rgb = true; + adv7511->embedded_sync = false; + + return 0; +} + static const int edid_i2c_addr = 0x7e; static const int packet_i2c_addr = 0x70; static const int cec_i2c_addr = 0x78; @@ -1121,11 +1209,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 @@ -1247,8 +1336,11 @@ static int adv7511_remove(struct i2c_client *i2c)
kfree(adv7511->edid);
- if (adv7511->type == ADV7533) + if (adv7511->type == ADV7533) { + mipi_dsi_detach(adv7511->dsi); + mipi_dsi_unregister_device(adv7511->dsi); drm_bridge_remove(&adv7511->bridge); + }
return 0; }
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
This series adds support for ADV7533. Unlike ADV7511 that's modelled as a drm i2c slave encoder, ADV7533 is a drm_bridge object. The bridge ops further create a HDMI type connector device for the chip's output.
Changes in v2:
- The last patch in the series has changed completely. Instead of using the dummy mipi dsi device usage, we now have two separate drivers for i2c and dsi components of the chip. This was discussed in depth (without a final conclusion) here:
https://lkml.org/lkml/2015/6/30/42
With this approach, we wouldn't need to create dummy dsi devices, but we'd have two DT nodes for two different parts of the same chip
- Use of_device_get_match_data() to simplify things.
The older series (using dummy mipi dsi devices) is here:
http://lists.freedesktop.org/archives/dri-devel/2015-July/087088.html
Can we please decide on one of these approaches?
Archit Taneja (4): drm/i2c: adv7511: Fix mutex deadlock when interrupts are disabled drm/i2c: adv7511: Refactor encoder slave functions drm/i2c: adv7511: Add drm_bridge/connector for ADV7533 drm/i2c: adv7511: Add dsi driver for adv7533
Lars-Peter Clausen (1): drm/i2c: adv7511: Initial support for adv7533
drivers/gpu/drm/i2c/adv7511.c | 480 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 434 insertions(+), 46 deletions(-)
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 interrupt context. It doesn't serve any purpose there anyway.
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 2aaa3c8..cf5bb29 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -422,7 +422,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; @@ -438,7 +438,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_HDP && adv7511->encoder) + if (process_hpd && irq0 & ADV7511_INT0_HDP && adv7511->encoder) drm_helper_hpd_irq_event(adv7511->encoder->dev);
if (irq0 & ADV7511_INT0_EDID_READY || irq1 & ADV7511_INT1_DDC_ERROR) { @@ -456,7 +456,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; }
@@ -473,7 +473,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;
From: Lars-Peter Clausen lars@metafoo.de
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 blocks.
Use DT compatible strings to populate the adv7533 type enum. Add minimal register configurations belonging to the DSI/CEC register map.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de --- drivers/gpu/drm/i2c/adv7511.c | 141 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 122 insertions(+), 19 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index cf5bb29..cf5961d5 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -20,12 +20,18 @@
#include "adv7511.h"
+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 *packet_memory_regmap; + struct regmap *regmap_cec; enum drm_connector_status status; bool powered;
@@ -46,6 +52,8 @@ struct adv7511 { struct edid *edid;
struct gpio_desc *gpio_pd; + + enum adv7511_type type; };
static struct adv7511 *encoder_to_adv7511(struct drm_encoder *encoder) @@ -66,6 +74,23 @@ static const struct reg_default adv7511_fixed_registers[] = { { 0x55, 0x02 }, };
+/* ADI recommended values for proper operation. */ +static const struct reg_default adv7533_fixed_registers[] = { + { 0x16, 0x20 }, + { 0x9a, 0xe0 }, + { 0xba, 0x70 }, + { 0xde, 0x82 }, + { 0xe4, 0x40 }, + { 0xe5, 0x80 }, +}; + +static const struct reg_default adv7533_cec_fixed_registers[] = { + { 0x15, 0xd0 }, + { 0x17, 0xd0 }, + { 0x24, 0x20 }, + { 0x57, 0x11 }, +}; + /* ----------------------------------------------------------------------------- * Register access */ @@ -158,6 +183,15 @@ static const struct regmap_config adv7511_regmap_config = { .volatile_reg = adv7511_register_volatile, };
+static const struct regmap_config adv7533_cec_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_RBTREE, +}; + + /* ----------------------------------------------------------------------------- * Hardware configuration */ @@ -358,6 +392,25 @@ static void adv7511_set_link_config(struct adv7511 *adv7511, adv7511->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB; }
+static void adv7511_dsi_receiver_dpms(struct adv7511 *adv7511) +{ + if (adv7511->type != ADV7533) + return; + + if (adv7511->powered) { + /* set number of dsi lanes (hardcoded to 4 for now) */ + regmap_write(adv7511->regmap_cec, 0x1c, 0x4 << 4); + /* disable internal timing generator */ + regmap_write(adv7511->regmap_cec, 0x27, 0x0b); + /* enable hdmi */ + regmap_write(adv7511->regmap_cec, 0x03, 0x89); + /* disable test mode */ + regmap_write(adv7511->regmap_cec, 0x55, 0x00); + } else { + regmap_write(adv7511->regmap_cec, 0x03, 0x0b); + } +} + static void adv7511_power_on(struct adv7511 *adv7511) { adv7511->current_edid_segment = -1; @@ -387,6 +440,8 @@ static void adv7511_power_on(struct adv7511 *adv7511) regcache_sync(adv7511->regmap);
adv7511->powered = true; + + adv7511_dsi_receiver_dpms(adv7511); }
static void adv7511_power_off(struct adv7511 *adv7511) @@ -398,6 +453,8 @@ static void adv7511_power_off(struct adv7511 *adv7511) regcache_mark_dirty(adv7511->regmap);
adv7511->powered = false; + + adv7511_dsi_receiver_dpms(adv7511); }
/* ----------------------------------------------------------------------------- @@ -567,6 +624,9 @@ static int adv7511_get_modes(struct drm_encoder *encoder,
/* Reading the EDID only works if the device is powered */ if (!adv7511->powered) { + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7511_REG_POWER2_HDP_SRC_MASK, + ADV7511_REG_POWER2_HDP_SRC_NONE); regmap_write(adv7511->regmap, ADV7511_REG_INT(0), ADV7511_INT0_EDID_READY); regmap_write(adv7511->regmap, ADV7511_REG_INT(1), @@ -770,8 +830,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) @@ -871,9 +929,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 = 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 @@ -897,10 +964,19 @@ 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 (ret) - return ret; + if (adv7511->type == ADV7511) { + ret = regmap_register_patch(adv7511->regmap, + adv7511_fixed_registers, + ARRAY_SIZE(adv7511_fixed_registers)); + if (ret) + return ret; + } else { + ret = regmap_register_patch(adv7511->regmap, + adv7533_fixed_registers, + ARRAY_SIZE(adv7533_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, @@ -913,6 +989,27 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) if (!adv7511->i2c_edid) return -ENOMEM;
+ adv7511->i2c_cec = i2c_new_dummy(i2c->adapter, cec_i2c_addr >> 1); + if (!adv7511->i2c_cec) { + ret = -ENOMEM; + goto err_i2c_unregister_edid; + } + + adv7511->regmap_cec = devm_regmap_init_i2c(adv7511->i2c_cec, + &adv7533_cec_regmap_config); + if (IS_ERR(adv7511->regmap_cec)) { + ret = PTR_ERR(adv7511->regmap_cec); + goto err_i2c_unregister_cec; + } + + if (adv7511->type == ADV7533) { + ret = regmap_register_patch(adv7511->regmap_cec, + adv7533_cec_fixed_registers, + ARRAY_SIZE(adv7533_cec_fixed_registers)); + if (ret) + return ret; + } + if (i2c->irq) { init_waitqueue_head(&adv7511->wq);
@@ -921,7 +1018,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_i2c_unregister_cec; }
/* CEC is unused for now */ @@ -932,11 +1029,14 @@ 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);
return 0;
-err_i2c_unregister_device: +err_i2c_unregister_cec: + i2c_unregister_device(adv7511->i2c_cec); +err_i2c_unregister_edid: i2c_unregister_device(adv7511->i2c_edid);
return ret; @@ -946,6 +1046,7 @@ static int adv7511_remove(struct i2c_client *i2c) { struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
+ i2c_unregister_device(adv7511->i2c_cec); i2c_unregister_device(adv7511->i2c_edid);
kfree(adv7511->edid); @@ -968,17 +1069,19 @@ static int adv7511_encoder_init(struct i2c_client *i2c, struct drm_device *dev, }
static const struct i2c_device_id adv7511_i2c_ids[] = { - { "adv7511", 0 }, - { "adv7511w", 0 }, - { "adv7513", 0 }, + { "adv7511", ADV7511 }, + { "adv7511w", ADV7511 }, + { "adv7513", ADV7511 }, + { "adv7533", ADV7533 }, { } }; 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 }, + { .compatible = "adi,adv7533-i2c", .data = (void *) ADV7533 }, { } }; MODULE_DEVICE_TABLE(of, adv7511_of_ids);
ADV7511 is represented as an i2c drm slave encoder device. ADV7533, on the other hand, is going be a normal i2c client device creating bridge and connector entities.
Move the code in encoder slave functions to generate helpers that are agnostic to the drm object type. These helpers will later also be used by bridge and connecter helper functions.
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 80 ++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 23 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index cf5961d5..e35ad37 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -612,13 +612,11 @@ 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, - struct drm_connector *connector) +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;
@@ -656,21 +654,10 @@ 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, +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; @@ -694,7 +681,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 { @@ -708,8 +695,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, + const struct drm_display_mode *mode) { if (mode->clock > 165000) return MODE_CLOCK_HIGH; @@ -717,11 +704,10 @@ static int adv7511_encoder_mode_valid(struct drm_encoder *encoder, return MODE_OK; }
-static void adv7511_encoder_mode_set(struct drm_encoder *encoder, +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; @@ -812,12 +798,60 @@ static void adv7511_encoder_mode_set(struct drm_encoder *encoder, adv7511->f_tmds = mode->clock; }
+/* ----------------------------------------------------------------------------- + * Encoder operations + */ + +static int adv7511_encoder_get_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct adv7511 *adv7511 = encoder_to_adv7511(encoder); + + return adv7511_get_modes(adv7511, connector); +} + +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) +{ + struct adv7511 *adv7511 = encoder_to_adv7511(encoder); + + return adv7511_detect(adv7511, connector); +} + +static int adv7511_encoder_mode_valid(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + struct adv7511 *adv7511 = encoder_to_adv7511(encoder); + + return adv7511_mode_valid(adv7511, mode); +} + +static void adv7511_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct adv7511 *adv7511 = encoder_to_adv7511(encoder); + + adv7511_mode_set(adv7511, mode, adj_mode); +} + static 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, + .get_modes = adv7511_encoder_get_modes, };
/* -----------------------------------------------------------------------------
Create bridge and connector helper functions. These internally refer to the ADV75xx helper functions.
The driver registers a drm_bridge object during probe. The bridge, in turn registers a HDMI connector when a user attaches the bridge.
Therefore, when the device type is ADV7533, we create bridge and connector entities, and when it's ADV7511, we create a slave encoder as before.
Since the i2c driver is still wrapped around by the drm_i2c_slave_encoder struct. We make sure the encoder_init op returns an error when the device type is ADV7533.
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 155 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index e35ad37..b6c80e3 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -17,6 +17,8 @@ #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> #include <drm/drm_encoder_slave.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h>
#include "adv7511.h"
@@ -44,6 +46,9 @@ struct adv7511 { wait_queue_head_t wq; struct drm_encoder *encoder;
+ struct drm_connector connector; + struct drm_bridge bridge; + bool embedded_sync; enum adv7511_sync_polarity vsync_polarity; enum adv7511_sync_polarity hsync_polarity; @@ -855,6 +860,139 @@ static struct drm_encoder_slave_funcs adv7511_encoder_funcs = { };
/* ----------------------------------------------------------------------------- + * Bridge and connector functions + */ + +static struct adv7511 *connector_to_adv7511(struct drm_connector *connector) +{ + return container_of(connector, struct adv7511, connector); +} + +/* Connector helper functions */ +static int adv7533_connector_get_modes(struct drm_connector *connector) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv7511_get_modes(adv, connector); +} + +static struct drm_encoder * +adv7533_connector_best_encoder(struct drm_connector *connector) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv->bridge.encoder; +} + +static enum drm_mode_status +adv7533_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 adv7533_connector_helper_funcs = { + .get_modes = adv7533_connector_get_modes, + .best_encoder = adv7533_connector_best_encoder, + .mode_valid = adv7533_connector_mode_valid, +}; + +static enum drm_connector_status +adv7533_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 adv7533_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = adv7533_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 adv7533_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_power_on(adv); +} + +static void adv7533_bridge_post_disable(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_power_off(adv); +} + +static void adv7533_bridge_enable(struct drm_bridge *bridge) +{ +} + +static void adv7533_bridge_disable(struct drm_bridge *bridge) +{ +} + +static void adv7533_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 adv7533_bridge_attach(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + int ret; + + adv->encoder = bridge->encoder; + + 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, + &adv7533_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, + &adv7533_connector_helper_funcs); + drm_connector_register(&adv->connector); + drm_mode_connector_attach_encoder(&adv->connector, adv->encoder); + + drm_helper_hpd_irq_event(adv->connector.dev); + + return ret; +} + +static struct drm_bridge_funcs adv7533_bridge_funcs = { + .pre_enable = adv7533_bridge_pre_enable, + .enable = adv7533_bridge_enable, + .disable = adv7533_bridge_disable, + .post_disable = adv7533_bridge_post_disable, + .mode_set = adv7533_bridge_mode_set, + .attach = adv7533_bridge_attach, +}; + +/* ----------------------------------------------------------------------------- * Probe & remove */
@@ -1066,6 +1204,17 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) if (adv7511->type == ADV7511) adv7511_set_link_config(adv7511, &link_config);
+ if (adv7511->type == ADV7533) { + adv7511->bridge.funcs = &adv7533_bridge_funcs; + adv7511->bridge.of_node = dev->of_node; + + ret = drm_bridge_add(&adv7511->bridge); + if (ret) { + dev_err(dev, "failed to add adv7533 bridge\n"); + goto err_i2c_unregister_cec; + } + } + return 0;
err_i2c_unregister_cec: @@ -1085,6 +1234,9 @@ static int adv7511_remove(struct i2c_client *i2c)
kfree(adv7511->edid);
+ if (adv7511->type == ADV7533) + drm_bridge_remove(&adv7511->bridge); + return 0; }
@@ -1094,6 +1246,9 @@ static int adv7511_encoder_init(struct i2c_client *i2c, struct drm_device *dev,
struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
+ if (adv7511->type == ADV7533) + return -ENODEV; + encoder->slave_priv = adv7511; encoder->slave_funcs = &adv7511_encoder_funcs;
Add a separate mipi_dsi_driver for adv7533. In the case of this chip, both the i2c and dsi drivers will operate together.
Both the drivers are expected to use the same per-device driver data struct. The i2c driver takes the responsibility of allocating the struct, and the dsi device gets a pointer to it by getting the corresponding i2c client device's data.
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 98 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index b6c80e3..8325913 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -19,6 +19,7 @@ #include <drm/drm_encoder_slave.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> +#include <drm/drm_mipi_dsi.h>
#include "adv7511.h"
@@ -58,6 +59,9 @@ struct adv7511 {
struct gpio_desc *gpio_pd;
+ struct mipi_dsi_device *dsi; + int num_dsi_lanes; + enum adv7511_type type; };
@@ -403,8 +407,10 @@ static void adv7511_dsi_receiver_dpms(struct adv7511 *adv7511) return;
if (adv7511->powered) { + struct mipi_dsi_device *dsi = adv7511->dsi; + /* set number of dsi lanes (hardcoded to 4 for now) */ - regmap_write(adv7511->regmap_cec, 0x1c, 0x4 << 4); + regmap_write(adv7511->regmap_cec, 0x1c, dsi->lanes << 4); /* disable internal timing generator */ regmap_write(adv7511->regmap_cec, 0x27, 0x0b); /* enable hdmi */ @@ -1289,8 +1295,97 @@ static struct drm_i2c_encoder_driver adv7511_driver = { .encoder_init = adv7511_encoder_init, };
+/* Driver for the DSI component within the chip */ +static int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv7511) +{ + u32 num_lanes; + + of_property_read_u32(np, "adi,dsi-lanes", &num_lanes); + + if (num_lanes < 1 || num_lanes > 4) + return -EINVAL; + + adv7511->num_dsi_lanes = num_lanes; + + /* TODO: Check if these need to be parsed by DT or not */ + adv7511->rgb = true; + adv7511->embedded_sync = false; + + return 0; +} + +static int adv7533_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct adv7511 *adv7511; + struct device_node *np = dsi->dev.of_node; + struct device_node *i2c_node; + struct i2c_client *i2c; + int ret; + + i2c_node = of_parse_phandle(np, "i2c-control", 0); + if (!i2c_node) + return -ENODEV; + + i2c = of_find_i2c_device_by_node(i2c_node); + if (!i2c) + return -EPROBE_DEFER; + + adv7511 = i2c_get_clientdata(i2c); + if (!adv7511) + return -ENODEV; + + /* this sets up link with the i2c driver */ + adv7511->dsi = dsi; + + mipi_dsi_set_drvdata(dsi, adv7511); + + ret = adv7533_parse_dt(np, adv7511); + if (ret) + return ret; + + dsi->lanes = adv7511->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; + + return mipi_dsi_attach(dsi); +} + +static int adv7533_remove(struct mipi_dsi_device *dsi) +{ + mipi_dsi_detach(dsi); + + return 0; +} + +static const struct of_device_id adv7533_of_match[] = { + { .compatible = "adi,adv7533-dsi" }, + { } +}; +MODULE_DEVICE_TABLE(of, adv7533_of_match); + +static struct mipi_dsi_driver adv7533_driver = { + .probe = adv7533_probe, + .remove = adv7533_remove, + .driver = { + .name = "adv7533", + .of_match_table = adv7533_of_match, + }, +}; + static int __init adv7511_init(void) { + int ret; + + /* + * not bailing out here because adv7511 variant won't + * need a DSI driver + */ + ret = mipi_dsi_driver_register(&adv7533_driver); + if (ret) + DRM_DEBUG("mipi driver register failed %d\n", ret); + return drm_i2c_encoder_register(THIS_MODULE, &adv7511_driver); } module_init(adv7511_init); @@ -1298,6 +1393,7 @@ module_init(adv7511_init); static void __exit adv7511_exit(void) { drm_i2c_encoder_unregister(&adv7511_driver); + mipi_dsi_driver_unregister(&adv7533_driver); } module_exit(adv7511_exit);
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
Trying to get this driver merged has had some challenges:
- ADV7533 has an I2C control bus, but acts as a DSI peripheral too. After discussions, it was concluded that we'd want to provide an API to create MIPI DSI devices, rather than expose two different interfaces on DT. The first version [1] tried the former approach the second version [2] showed how the driver would look like if exposed 2 DT nodes. This lateset patchset relies on the MIPI DSI device creation API provided by [3], this has been accepted and should be merged for 4.6.
- The driver was designed as an I2C slave encoder. When ADV7533 patches were posted [1], it was modelled as a bridge, but ADV7511 and others were still left as I2C slave encoders. This wasn't accepted. After discussions, it was decided that ADV7511 too would be converted into a bridge driver, and all the users of ADV7511 should assume it is a bridge. This bridge conversion was done in [4]. There is still some debate over whether the bridge driver be involved in the connector creation, or the KMS driver that has the whole view of the display pipeline. This discussion shouldn't affect this patch set, though.
This patch set enables ADV7533 support with the above two issues now resolved. It also incorporates ADV7533 specific features and fixes that we've discovered since the first version of this patch was posted.
Tested on ADV7533 chips on DB410c. It should work on the Hikey board too. I'd appreaciate if someone could test it on a ADV7511 platform since I don't have one.
[4] https://lists.freedesktop.org/archives/dri-devel/2016-January/098287.html
[3] https://lkml.org/lkml/2016/2/12/67
[2] https://lists.freedesktop.org/archives/dri-devel/2015-September/089884.html
[1]: https://lists.freedesktop.org/archives/dri-devel/2015-July/087088.html
Archit Taneja (7): drm/i2c: adv7511: Convert to drm_bridge drm/i2c: adv7511: Fix mutex deadlock when interrupts are disabled drm/i2c: adv7511: Initial support for ADV7533 drm/i2c: adv7511: Create a MIPI DSI device drm/i2c: adv7511: Use internal timing generator drm/i2c: adv7511: Change number of DSI lanes dynamically dt-bindings: drm/bridge: Update bindings for ADV7533
.../bindings/display/bridge/adi,adv7511.txt | 25 +- drivers/gpu/drm/i2c/adv7511.c | 539 +++++++++++++++++---- 2 files changed, 476 insertions(+), 88 deletions(-)
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.
Signed-off-by: Archit Taneja architt@codeaurora.org --- v3: - Remove calls to connector_register and hpd helper in bridge_attach - Remove noop bridge ops
drivers/gpu/drm/i2c/adv7511.c | 222 ++++++++++++++++++++++++++++-------------- 1 file changed, 148 insertions(+), 74 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index a02112b..fec5627 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -14,9 +14,10 @@ #include <linux/slab.h>
#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> -#include <drm/drm_encoder_slave.h>
#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,120 @@ 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 struct drm_encoder * +adv7511_connector_best_encoder(struct drm_connector *connector) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv->bridge.encoder; +} + +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, + .best_encoder = adv7511_connector_best_encoder, + .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 +1035,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 +1056,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 +1065,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 +1081,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 lars@metafoo.de"); MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver");
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 architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index fec5627..c00d11d 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.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;
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.
Use DT compatible strings to populate the ADV7533 type enum. Add minimal register configurations belonging to the DSI/CEC register map.
Originally worked on by Lars-Peter Clausen lars@metafoo.de
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 138 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 120 insertions(+), 18 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index c00d11d..75be17c 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -10,6 +10,7 @@ #include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/module.h> +#include <linux/of_device.h> #include <linux/regmap.h> #include <linux/slab.h>
@@ -21,12 +22,18 @@
#include "adv7511.h"
+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 *packet_memory_regmap; + struct regmap *regmap_cec; enum drm_connector_status status; bool powered;
@@ -48,6 +55,8 @@ struct adv7511 { struct edid *edid;
struct gpio_desc *gpio_pd; + + enum adv7511_type type; };
/* ADI recommended values for proper operation. */ @@ -63,6 +72,22 @@ static const struct reg_sequence adv7511_fixed_registers[] = { { 0x55, 0x02 }, };
+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 }, +}; + /* ----------------------------------------------------------------------------- * Register access */ @@ -156,6 +181,15 @@ static const struct regmap_config adv7511_regmap_config = { .volatile_reg = adv7511_register_volatile, };
+static const struct regmap_config adv7533_cec_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_RBTREE, +}; + + /* ----------------------------------------------------------------------------- * Hardware configuration */ @@ -356,6 +390,21 @@ static void adv7511_set_link_config(struct adv7511 *adv7511, adv7511->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB; }
+static void adv7533_dsi_power_on(struct adv7511 *adv) +{ + /* set number of dsi lanes (hardcoded to 4 for now) */ + regmap_write(adv->regmap_cec, 0x1c, 0x4 << 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)); +} + static void adv7511_power_on(struct adv7511 *adv7511) { adv7511->current_edid_segment = -1; @@ -391,9 +440,18 @@ static void adv7511_power_on(struct adv7511 *adv7511) */ regcache_sync(adv7511->regmap);
+ if (adv7511->type == ADV7533) + adv7533_dsi_power_on(adv7511); + adv7511->powered = true; }
+static void adv7533_dsi_power_off(struct adv7511 *adv) +{ + /* disable hdmi */ + regmap_write(adv->regmap_cec, 0x03, 0x0b); +} + static void adv7511_power_off(struct adv7511 *adv7511) { /* TODO: setup additional power down modes */ @@ -402,6 +460,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; }
@@ -871,8 +932,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) @@ -972,9 +1031,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 @@ -998,8 +1066,15 @@ 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 = regmap_register_patch(adv7511->regmap, + adv7533_fixed_registers, + ARRAY_SIZE(adv7533_fixed_registers)); + } if (ret) return ret;
@@ -1014,6 +1089,27 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) if (!adv7511->i2c_edid) return -ENOMEM;
+ adv7511->i2c_cec = i2c_new_dummy(i2c->adapter, cec_i2c_addr >> 1); + if (!adv7511->i2c_cec) { + ret = -ENOMEM; + goto err_i2c_unregister_edid; + } + + adv7511->regmap_cec = devm_regmap_init_i2c(adv7511->i2c_cec, + &adv7533_cec_regmap_config); + if (IS_ERR(adv7511->regmap_cec)) { + ret = PTR_ERR(adv7511->regmap_cec); + goto err_i2c_unregister_cec; + } + + if (adv7511->type == ADV7533) { + ret = regmap_register_patch(adv7511->regmap_cec, + adv7533_cec_fixed_registers, + ARRAY_SIZE(adv7533_cec_fixed_registers)); + if (ret) + goto err_i2c_unregister_cec; + } + if (i2c->irq) { init_waitqueue_head(&adv7511->wq);
@@ -1022,7 +1118,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_i2c_unregister_cec; }
/* CEC is unused for now */ @@ -1033,7 +1129,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; @@ -1041,12 +1138,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_i2c_unregister_cec; }
return 0;
-err_i2c_unregister_device: +err_i2c_unregister_cec: + i2c_unregister_device(adv7511->i2c_cec); +err_i2c_unregister_edid: i2c_unregister_device(adv7511->i2c_edid);
return ret; @@ -1058,6 +1157,7 @@ static int adv7511_remove(struct i2c_client *i2c)
drm_bridge_remove(&adv7511->bridge);
+ i2c_unregister_device(adv7511->i2c_cec); i2c_unregister_device(adv7511->i2c_edid);
kfree(adv7511->edid); @@ -1066,17 +1166,19 @@ 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 }, + { "adv7533", ADV7533 }, { } }; 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 }, + { .compatible = "adi,adv7533", .data = (void *)ADV7533 }, { } }; MODULE_DEVICE_TABLE(of, adv7511_of_ids);
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.
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 130 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 75be17c..6c2a89a 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -11,6 +11,7 @@ #include <linux/i2c.h> #include <linux/module.h> #include <linux/of_device.h> +#include <linux/of_graph.h> #include <linux/regmap.h> #include <linux/slab.h>
@@ -19,6 +20,7 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> +#include <drm/drm_mipi_dsi.h>
#include "adv7511.h"
@@ -56,6 +58,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; };
@@ -392,8 +399,10 @@ static void adv7511_set_link_config(struct adv7511 *adv7511,
static void adv7533_dsi_power_on(struct adv7511 *adv) { - /* set number of dsi lanes (hardcoded to 4 for now) */ - regmap_write(adv->regmap_cec, 0x1c, 0x4 << 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 */ @@ -889,6 +898,51 @@ static void adv7511_bridge_mode_set(struct drm_bridge *bridge, adv7511_mode_set(adv, mode, adj_mode); }
+static 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; +} + static int adv7511_bridge_attach(struct drm_bridge *bridge) { struct adv7511 *adv = bridge_to_adv7511(bridge); @@ -912,6 +966,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; }
@@ -1009,6 +1066,41 @@ static int adv7511_parse_dt(struct device_node *np, return 0; }
+static 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) { + DRM_ERROR("ADV7533 DSI input endpoint not found\n"); + return -ENODEV; + } + + adv->host_node = of_graph_get_remote_port_parent(endpoint); + if (!adv->host_node) { + DRM_ERROR("DSI host node not found\n"); + 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; +} + static const int edid_i2c_addr = 0x7e; static const int packet_i2c_addr = 0x70; static const int cec_i2c_addr = 0x78; @@ -1038,11 +1130,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 @@ -1155,6 +1248,11 @@ static int adv7511_remove(struct i2c_client *i2c) { struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
+ if (adv7511->type == ADV7533) { + mipi_dsi_detach(adv7511->dsi); + mipi_dsi_device_unregister(adv7511->dsi); + } + drm_bridge_remove(&adv7511->bridge);
i2c_unregister_device(adv7511->i2c_cec); @@ -1183,6 +1281,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", @@ -1193,7 +1295,21 @@ static struct i2c_driver adv7511_driver = { .remove = adv7511_remove, };
-module_i2c_driver(adv7511_driver); +static int __init adv7511_init(void) +{ + 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); + + mipi_dsi_driver_unregister(&adv7533_dsi_driver); +} +module_exit(adv7511_exit);
MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver");
Hi Archit,
Thank you for the patch.
On Wednesday 09 Mar 2016 16:27:15 Archit Taneja wrote:
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.
Signed-off-by: Archit Taneja architt@codeaurora.org
This adds a hard dependency on CONFIG_DRM_MIPI_DSI, otherwise the kernel won't link. As the ADV7511 doesn't require DSI, could you make it optional with conditional compilation to avoid pulling in dead code ?
drivers/gpu/drm/i2c/adv7511.c | 130 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 75be17c..6c2a89a 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -11,6 +11,7 @@ #include <linux/i2c.h> #include <linux/module.h> #include <linux/of_device.h> +#include <linux/of_graph.h> #include <linux/regmap.h> #include <linux/slab.h>
@@ -19,6 +20,7 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> +#include <drm/drm_mipi_dsi.h>
#include "adv7511.h"
@@ -56,6 +58,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;
};
@@ -392,8 +399,10 @@ static void adv7511_set_link_config(struct adv7511 *adv7511,
static void adv7533_dsi_power_on(struct adv7511 *adv) {
- /* set number of dsi lanes (hardcoded to 4 for now) */
- regmap_write(adv->regmap_cec, 0x1c, 0x4 << 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 */
@@ -889,6 +898,51 @@ static void adv7511_bridge_mode_set(struct drm_bridge *bridge, adv7511_mode_set(adv, mode, adj_mode); }
+static 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;
+}
static int adv7511_bridge_attach(struct drm_bridge *bridge) { struct adv7511 *adv = bridge_to_adv7511(bridge); @@ -912,6 +966,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;
}
@@ -1009,6 +1066,41 @@ static int adv7511_parse_dt(struct device_node *np, return 0; }
+static 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) {
DRM_ERROR("ADV7533 DSI input endpoint not found\n");
return -ENODEV;
- }
- adv->host_node = of_graph_get_remote_port_parent(endpoint);
- if (!adv->host_node) {
DRM_ERROR("DSI host node not found\n");
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;
+}
static const int edid_i2c_addr = 0x7e; static const int packet_i2c_addr = 0x70; static const int cec_i2c_addr = 0x78; @@ -1038,11 +1130,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
@@ -1155,6 +1248,11 @@ static int adv7511_remove(struct i2c_client *i2c) { struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
if (adv7511->type == ADV7533) {
mipi_dsi_detach(adv7511->dsi);
mipi_dsi_device_unregister(adv7511->dsi);
}
drm_bridge_remove(&adv7511->bridge);
i2c_unregister_device(adv7511->i2c_cec);
@@ -1183,6 +1281,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", @@ -1193,7 +1295,21 @@ static struct i2c_driver adv7511_driver = { .remove = adv7511_remove, };
-module_i2c_driver(adv7511_driver); +static int __init adv7511_init(void) +{
- 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);
- mipi_dsi_driver_unregister(&adv7533_dsi_driver);
+} +module_exit(adv7511_exit);
MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver");
Hi Laurent,
On 04/22/2016 03:59 AM, Laurent Pinchart wrote:
Hi Archit,
Thank you for the patch.
On Wednesday 09 Mar 2016 16:27:15 Archit Taneja wrote:
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.
Signed-off-by: Archit Taneja architt@codeaurora.org
This adds a hard dependency on CONFIG_DRM_MIPI_DSI, otherwise the kernel won't link. As the ADV7511 doesn't require DSI, could you make it optional with conditional compilation to avoid pulling in dead code ?
You're right. The driver's Kconfig should at least select DRM_MIPI_DSI in the current state to make sure we don't break build.
Do you suggest we create another config option for ADV7533, which selects DRM_MIPI_DSI and builds the ADV7533 parts? Or did you mean something else?
Thanks for the review.
Archit
drivers/gpu/drm/i2c/adv7511.c | 130 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 75be17c..6c2a89a 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -11,6 +11,7 @@ #include <linux/i2c.h> #include <linux/module.h> #include <linux/of_device.h> +#include <linux/of_graph.h> #include <linux/regmap.h> #include <linux/slab.h>
@@ -19,6 +20,7 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> +#include <drm/drm_mipi_dsi.h>
#include "adv7511.h"
@@ -56,6 +58,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; };
@@ -392,8 +399,10 @@ static void adv7511_set_link_config(struct adv7511 *adv7511,
static void adv7533_dsi_power_on(struct adv7511 *adv) {
- /* set number of dsi lanes (hardcoded to 4 for now) */
- regmap_write(adv->regmap_cec, 0x1c, 0x4 << 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 */
@@ -889,6 +898,51 @@ static void adv7511_bridge_mode_set(struct drm_bridge *bridge, adv7511_mode_set(adv, mode, adj_mode); }
+static 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;
+}
- static int adv7511_bridge_attach(struct drm_bridge *bridge) { struct adv7511 *adv = bridge_to_adv7511(bridge);
@@ -912,6 +966,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; }
@@ -1009,6 +1066,41 @@ static int adv7511_parse_dt(struct device_node *np, return 0; }
+static 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) {
DRM_ERROR("ADV7533 DSI input endpoint not found\n");
return -ENODEV;
- }
- adv->host_node = of_graph_get_remote_port_parent(endpoint);
- if (!adv->host_node) {
DRM_ERROR("DSI host node not found\n");
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;
+}
- static const int edid_i2c_addr = 0x7e; static const int packet_i2c_addr = 0x70; static const int cec_i2c_addr = 0x78;
@@ -1038,11 +1130,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
@@ -1155,6 +1248,11 @@ static int adv7511_remove(struct i2c_client *i2c) { struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
if (adv7511->type == ADV7533) {
mipi_dsi_detach(adv7511->dsi);
mipi_dsi_device_unregister(adv7511->dsi);
}
drm_bridge_remove(&adv7511->bridge);
i2c_unregister_device(adv7511->i2c_cec);
@@ -1183,6 +1281,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",
@@ -1193,7 +1295,21 @@ static struct i2c_driver adv7511_driver = { .remove = adv7511_remove, };
-module_i2c_driver(adv7511_driver); +static int __init adv7511_init(void) +{
- 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);
- mipi_dsi_driver_unregister(&adv7533_dsi_driver);
+} +module_exit(adv7511_exit);
MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver");
Hi Laurent,
On 04/22/2016 10:40 AM, Archit Taneja wrote:
Hi Laurent,
On 04/22/2016 03:59 AM, Laurent Pinchart wrote:
Hi Archit,
Thank you for the patch.
On Wednesday 09 Mar 2016 16:27:15 Archit Taneja wrote:
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.
Signed-off-by: Archit Taneja architt@codeaurora.org
This adds a hard dependency on CONFIG_DRM_MIPI_DSI, otherwise the kernel won't link. As the ADV7511 doesn't require DSI, could you make it optional with conditional compilation to avoid pulling in dead code ?
You're right. The driver's Kconfig should at least select DRM_MIPI_DSI in the current state to make sure we don't break build.
Do you suggest we create another config option for ADV7533, which selects DRM_MIPI_DSI and builds the ADV7533 parts? Or did you mean something else?
Do you have any suggestions for this point? For the next revision, I've just selected DRM_MIPI_DSI in the Kconfig to ensure build doesn't break.
For this driver, to conditionally compile DRM_MIPI_DSI, it essentially means we allow conditional support for ADV7533. I would imagine us #ifdef-ing out the ADV7533 compatible string in the of_match_table. Is this something we want to do?
Thanks, Archit
drivers/gpu/drm/i2c/adv7511.c | 130 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 75be17c..6c2a89a 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -11,6 +11,7 @@ #include <linux/i2c.h> #include <linux/module.h> #include <linux/of_device.h> +#include <linux/of_graph.h> #include <linux/regmap.h> #include <linux/slab.h>
@@ -19,6 +20,7 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> +#include <drm/drm_mipi_dsi.h>
#include "adv7511.h"
@@ -56,6 +58,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;
@@ -392,8 +399,10 @@ static void adv7511_set_link_config(struct adv7511 *adv7511,
static void adv7533_dsi_power_on(struct adv7511 *adv) {
- /* set number of dsi lanes (hardcoded to 4 for now) */
- regmap_write(adv->regmap_cec, 0x1c, 0x4 << 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 */
@@ -889,6 +898,51 @@ static void adv7511_bridge_mode_set(struct drm_bridge *bridge, adv7511_mode_set(adv, mode, adj_mode); }
+static 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;
+}
- static int adv7511_bridge_attach(struct drm_bridge *bridge) { struct adv7511 *adv = bridge_to_adv7511(bridge);
@@ -912,6 +966,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;
@@ -1009,6 +1066,41 @@ static int adv7511_parse_dt(struct device_node *np, return 0; }
+static 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) {
DRM_ERROR("ADV7533 DSI input endpoint not found\n");
return -ENODEV;
- }
- adv->host_node = of_graph_get_remote_port_parent(endpoint);
- if (!adv->host_node) {
DRM_ERROR("DSI host node not found\n");
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;
+}
- static const int edid_i2c_addr = 0x7e; static const int packet_i2c_addr = 0x70; static const int cec_i2c_addr = 0x78;
@@ -1038,11 +1130,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 @@ -1155,6 +1248,11 @@ static int adv7511_remove(struct i2c_client *i2c) { struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
if (adv7511->type == ADV7533) {
mipi_dsi_detach(adv7511->dsi);
mipi_dsi_device_unregister(adv7511->dsi);
}
drm_bridge_remove(&adv7511->bridge); i2c_unregister_device(adv7511->i2c_cec);
@@ -1183,6 +1281,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",
@@ -1193,7 +1295,21 @@ static struct i2c_driver adv7511_driver = { .remove = adv7511_remove, };
-module_i2c_driver(adv7511_driver); +static int __init adv7511_init(void) +{
- 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);
- mipi_dsi_driver_unregister(&adv7533_dsi_driver);
+} +module_exit(adv7511_exit);
MODULE_AUTHOR("Lars-Peter Clausen lars@metafoo.de"); MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver");
Hi Archit,
On Tuesday 03 May 2016 12:27:38 Archit Taneja wrote:
On 04/22/2016 10:40 AM, Archit Taneja wrote:
On 04/22/2016 03:59 AM, Laurent Pinchart wrote:
On Wednesday 09 Mar 2016 16:27:15 Archit Taneja wrote:
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.
Signed-off-by: Archit Taneja architt@codeaurora.org
This adds a hard dependency on CONFIG_DRM_MIPI_DSI, otherwise the kernel won't link. As the ADV7511 doesn't require DSI, could you make it optional with conditional compilation to avoid pulling in dead code ?
You're right. The driver's Kconfig should at least select DRM_MIPI_DSI in the current state to make sure we don't break build.
Do you suggest we create another config option for ADV7533, which selects DRM_MIPI_DSI and builds the ADV7533 parts? Or did you mean something else?
Do you have any suggestions for this point? For the next revision, I've just selected DRM_MIPI_DSI in the Kconfig to ensure build doesn't break.
For this driver, to conditionally compile DRM_MIPI_DSI, it essentially means we allow conditional support for ADV7533. I would imagine us #ifdef-ing out the ADV7533 compatible string in the of_match_table. Is this something we want to do?
If it's not much trouble I think that would be useful to avoid bloating the kernel with unused features, yes. You might want to add stub functions to include/drm/drm_mipi_dsi.h when CONFIG_DRM_MIPI_DSI is not defined to avoid sprinkling the driver with lots of #ifdefs.
drivers/gpu/drm/i2c/adv7511.c | 130 +++++++++++++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 7 deletions(-)
On 05/10/2016 02:08 AM, Laurent Pinchart wrote:
Hi Archit,
On Tuesday 03 May 2016 12:27:38 Archit Taneja wrote:
On 04/22/2016 10:40 AM, Archit Taneja wrote:
On 04/22/2016 03:59 AM, Laurent Pinchart wrote:
On Wednesday 09 Mar 2016 16:27:15 Archit Taneja wrote:
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.
Signed-off-by: Archit Taneja architt@codeaurora.org
This adds a hard dependency on CONFIG_DRM_MIPI_DSI, otherwise the kernel won't link. As the ADV7511 doesn't require DSI, could you make it optional with conditional compilation to avoid pulling in dead code ?
You're right. The driver's Kconfig should at least select DRM_MIPI_DSI in the current state to make sure we don't break build.
Do you suggest we create another config option for ADV7533, which selects DRM_MIPI_DSI and builds the ADV7533 parts? Or did you mean something else?
Do you have any suggestions for this point? For the next revision, I've just selected DRM_MIPI_DSI in the Kconfig to ensure build doesn't break.
For this driver, to conditionally compile DRM_MIPI_DSI, it essentially means we allow conditional support for ADV7533. I would imagine us #ifdef-ing out the ADV7533 compatible string in the of_match_table. Is this something we want to do?
If it's not much trouble I think that would be useful to avoid bloating the kernel with unused features, yes. You might want to add stub functions to include/drm/drm_mipi_dsi.h when CONFIG_DRM_MIPI_DSI is not defined to avoid sprinkling the driver with lots of #ifdefs.
Yeah, I can do this. Now that there isn't a chance for it to get in the 4.7 merge window, there's plenty time.
Archit
drivers/gpu/drm/i2c/adv7511.c | 130 +++++++++++++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 7 deletions(-)
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 architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 65 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 6c2a89a..88918ae 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -39,6 +39,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; @@ -62,6 +64,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; }; @@ -397,14 +400,65 @@ static void adv7511_set_link_config(struct adv7511 *adv7511, adv7511->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB; }
+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); +} + static 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 */ @@ -459,6 +513,8 @@ static 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); }
static void adv7511_power_off(struct adv7511 *adv7511) @@ -807,6 +863,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. @@ -1094,6 +1152,9 @@ static 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;
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 andy.green@linaro.org
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 88918ae..ca9e862 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -863,6 +863,26 @@ 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 && adv7511->num_dsi_lanes == 4) { + struct mipi_dsi_device *dsi = adv7511->dsi; + int lanes, ret; + + if (adj_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) { + DRM_ERROR("Failed to change host lanes\n"); + return; + } + } + } + drm_mode_copy(&adv7511->curr_mode, adj_mode);
/*
Add description of ADV7533. Add the required and optional properties that are specific to it.
Cc: devicetree@vger.kernel.org Cc: Rob Herring robh@kernel.org
Signed-off-by: Archit Taneja architt@codeaurora.org --- .../bindings/display/bridge/adi,adv7511.txt | 25 +++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt b/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt index 96c25ee..420da5a 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,17 @@ 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 refer to a valid mipi_dsi_host device node. - Video port 1 for the HDMI output
On Wed, Mar 09, 2016 at 04:27:18PM +0530, Archit Taneja wrote:
Add description of ADV7533. Add the required and optional properties that are specific to it.
Cc: devicetree@vger.kernel.org Cc: Rob Herring robh@kernel.org
Signed-off-by: Archit Taneja architt@codeaurora.org
.../bindings/display/bridge/adi,adv7511.txt | 25 +++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-)
Acked-by: Rob Herring robh@kernel.org
Hi Archit,
Thank you for the patch.
On Wednesday 09 Mar 2016 16:27:18 Archit Taneja wrote:
Add description of ADV7533. Add the required and optional properties that are specific to it.
Cc: devicetree@vger.kernel.org Cc: Rob Herring robh@kernel.org
Signed-off-by: Archit Taneja architt@codeaurora.org
.../bindings/display/bridge/adi,adv7511.txt | 25 ++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt b/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt index 96c25ee..420da5a 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.
Does the ADV7533 support data lanes remapping ?
Optional properties:
- interrupts: Specifier for the ADV7511 interrupt
@@ -42,13 +53,17 @@ 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.
Isn't that something that should be selectable at runtime ?
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 refer to a valid mipi_dsi_host device
Nitpicking, I would write "reference" instead of "refer to".
node. - Video port 1 for the HDMI output
On 04/22/2016 04:02 AM, Laurent Pinchart wrote:
Hi Archit,
Thank you for the patch.
On Wednesday 09 Mar 2016 16:27:18 Archit Taneja wrote:
Add description of ADV7533. Add the required and optional properties that are specific to it.
Cc: devicetree@vger.kernel.org Cc: Rob Herring robh@kernel.org
Signed-off-by: Archit Taneja architt@codeaurora.org
.../bindings/display/bridge/adi,adv7511.txt | 25 ++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt b/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt index 96c25ee..420da5a 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.
Does the ADV7533 support data lanes remapping ?
It doesn't support remapping of lanes. There is only one register field that allows us to select the number of lanes.
Optional properties:
- interrupts: Specifier for the ADV7511 interrupt
@@ -42,13 +53,17 @@ 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.
Isn't that something that should be selectable at runtime ?
The timing generator can be enabled/disabled at runtime. Although, we don't have a way to tell the driver whether we want to keep it enabled or not.
It's a hardware feature that works well on most platforms, but not on all. In particular, it works well on DB410c, but causes issues with the Hikey 96 board. The DSI host on Hikey has different clock sources that generate the display controller's pixel clock and DSI byte clock, whereas the Qualcomm SoC uses the same source. My guess is that the ADV7533's timing generator doesn't like it when the pixel data and clock are out of phase or something.
Since it is a hardware feature which needs tweaking, I thought it qualified as a DT property.
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 refer to a valid mipi_dsi_host device
Nitpicking, I would write "reference" instead of "refer to".
I'll fix this in the next revision.
Thanks, Archit
Hi Archit,
On Friday 22 Apr 2016 11:10:18 Archit Taneja wrote:
On 04/22/2016 04:02 AM, Laurent Pinchart wrote:
On Wednesday 09 Mar 2016 16:27:18 Archit Taneja wrote:
Add description of ADV7533. Add the required and optional properties that are specific to it.
Cc: devicetree@vger.kernel.org Cc: Rob Herring robh@kernel.org
Signed-off-by: Archit Taneja architt@codeaurora.org
.../bindings/display/bridge/adi,adv7511.txt | 25 ++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt b/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt index 96c25ee..420da5a 100644 --- a/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt +++ b/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt
[snip]
+- 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.
Isn't that something that should be selectable at runtime ?
The timing generator can be enabled/disabled at runtime. Although, we don't have a way to tell the driver whether we want to keep it enabled or not.
It's a hardware feature that works well on most platforms, but not on all. In particular, it works well on DB410c, but causes issues with the Hikey 96 board. The DSI host on Hikey has different clock sources that generate the display controller's pixel clock and DSI byte clock, whereas the Qualcomm SoC uses the same source. My guess is that the ADV7533's timing generator doesn't like it when the pixel data and clock are out of phase or something.
Since it is a hardware feature which needs tweaking, I thought it qualified as a DT property.
The fact that a hardware generator is present is certainly describes the hardware, but I'm not sure whether to enable it or not also qualifies as a hardware feature.
Are there use cases for using the timing generator conditionally on a given board ? As you implement support for disabling it, I assume it's not mandatory. What feature(s) do we lose if we keep it disabled ?
On 05/16/2016 05:31 PM, Laurent Pinchart wrote:
Hi Archit,
On Friday 22 Apr 2016 11:10:18 Archit Taneja wrote:
On 04/22/2016 04:02 AM, Laurent Pinchart wrote:
On Wednesday 09 Mar 2016 16:27:18 Archit Taneja wrote:
Add description of ADV7533. Add the required and optional properties that are specific to it.
Cc: devicetree@vger.kernel.org Cc: Rob Herring robh@kernel.org
Signed-off-by: Archit Taneja architt@codeaurora.org
.../bindings/display/bridge/adi,adv7511.txt | 25 ++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt b/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt index 96c25ee..420da5a 100644 --- a/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt +++ b/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt
[snip]
+- 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.
Isn't that something that should be selectable at runtime ?
The timing generator can be enabled/disabled at runtime. Although, we don't have a way to tell the driver whether we want to keep it enabled or not.
It's a hardware feature that works well on most platforms, but not on all. In particular, it works well on DB410c, but causes issues with the Hikey 96 board. The DSI host on Hikey has different clock sources that generate the display controller's pixel clock and DSI byte clock, whereas the Qualcomm SoC uses the same source. My guess is that the ADV7533's timing generator doesn't like it when the pixel data and clock are out of phase or something.
Since it is a hardware feature which needs tweaking, I thought it qualified as a DT property.
The fact that a hardware generator is present is certainly describes the hardware, but I'm not sure whether to enable it or not also qualifies as a hardware feature.
Are there use cases for using the timing generator conditionally on a given board ? As you implement support for disabling it, I assume it's not mandatory. What feature(s) do we lose if we keep it disabled ?
The spec says it's recommended to use the internal timing generator. In the case of db410c, I observe an unstable output/flicker for certain modes if I don't enable it.
In the case of hikey platform, it's the other way round.
Xinliang, could you describe the problems you face when the timing generator is enabled?
Thanks, Archit
On 17 May 2016 at 11:43, Archit Taneja architt@codeaurora.org wrote:
On 05/16/2016 05:31 PM, Laurent Pinchart wrote:
Hi Archit,
On Friday 22 Apr 2016 11:10:18 Archit Taneja wrote:
On 04/22/2016 04:02 AM, Laurent Pinchart wrote:
On Wednesday 09 Mar 2016 16:27:18 Archit Taneja wrote:
Add description of ADV7533. Add the required and optional properties that are specific to it.
Cc: devicetree@vger.kernel.org Cc: Rob Herring robh@kernel.org
Signed-off-by: Archit Taneja architt@codeaurora.org
.../bindings/display/bridge/adi,adv7511.txt | 25 ++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt b/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt index 96c25ee..420da5a 100644 --- a/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt +++ b/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt
[snip]
+- 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.
Isn't that something that should be selectable at runtime ?
The timing generator can be enabled/disabled at runtime. Although, we don't have a way to tell the driver whether we want to keep it enabled or not.
It's a hardware feature that works well on most platforms, but not on all. In particular, it works well on DB410c, but causes issues with the Hikey 96 board. The DSI host on Hikey has different clock sources that generate the display controller's pixel clock and DSI byte clock, whereas the Qualcomm SoC uses the same source. My guess is that the ADV7533's timing generator doesn't like it when the pixel data and clock are out of phase or something.
Since it is a hardware feature which needs tweaking, I thought it qualified as a DT property.
The fact that a hardware generator is present is certainly describes the hardware, but I'm not sure whether to enable it or not also qualifies as a hardware feature.
Are there use cases for using the timing generator conditionally on a given board ? As you implement support for disabling it, I assume it's not mandatory. What feature(s) do we lose if we keep it disabled ?
The spec says it's recommended to use the internal timing generator. In the case of db410c, I observe an unstable output/flicker for certain modes if I don't enable it.
In the case of hikey platform, it's the other way round.
Xinliang, could you describe the problems you face when the timing generator is enabled?
Yes, opening the timing generator of ADV7533 can benefit the HDMI output signal. But, for some circumstances, we need to disable timing generator: To make modes work, the timing parameters (hfp, hbp, etc.) used by ADV7533 timing generator should match the ones used in DSI. If the timing parameters changed in DSI and these changing timing parameters can't pass to ADV7533, then it need to disable the timing generator of ADV7533 to make mode work. Some modes in HiKey is in this case.
Thanks, -xinliang
Thanks, Archit
-- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, hosted by The Linux Foundation
On 05/17/2016 09:48 AM, Xinliang Liu wrote:
On 17 May 2016 at 11:43, Archit Taneja architt@codeaurora.org wrote:
On 05/16/2016 05:31 PM, Laurent Pinchart wrote:
Hi Archit,
On Friday 22 Apr 2016 11:10:18 Archit Taneja wrote:
On 04/22/2016 04:02 AM, Laurent Pinchart wrote:
On Wednesday 09 Mar 2016 16:27:18 Archit Taneja wrote:
Add description of ADV7533. Add the required and optional properties that are specific to it.
Cc: devicetree@vger.kernel.org Cc: Rob Herring robh@kernel.org
Signed-off-by: Archit Taneja architt@codeaurora.org
.../bindings/display/bridge/adi,adv7511.txt | 25 ++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt b/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt index 96c25ee..420da5a 100644 --- a/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt +++ b/Documentation/devicetree/bindings/display/bridge/adi,adv7511.txt
[snip]
+- 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.
Isn't that something that should be selectable at runtime ?
The timing generator can be enabled/disabled at runtime. Although, we don't have a way to tell the driver whether we want to keep it enabled or not.
It's a hardware feature that works well on most platforms, but not on all. In particular, it works well on DB410c, but causes issues with the Hikey 96 board. The DSI host on Hikey has different clock sources that generate the display controller's pixel clock and DSI byte clock, whereas the Qualcomm SoC uses the same source. My guess is that the ADV7533's timing generator doesn't like it when the pixel data and clock are out of phase or something.
Since it is a hardware feature which needs tweaking, I thought it qualified as a DT property.
The fact that a hardware generator is present is certainly describes the hardware, but I'm not sure whether to enable it or not also qualifies as a hardware feature.
Are there use cases for using the timing generator conditionally on a given board ? As you implement support for disabling it, I assume it's not mandatory. What feature(s) do we lose if we keep it disabled ?
The spec says it's recommended to use the internal timing generator. In the case of db410c, I observe an unstable output/flicker for certain modes if I don't enable it.
In the case of hikey platform, it's the other way round.
Xinliang, could you describe the problems you face when the timing generator is enabled?
Yes, opening the timing generator of ADV7533 can benefit the HDMI output signal. But, for some circumstances, we need to disable timing generator: To make modes work, the timing parameters (hfp, hbp, etc.) used by ADV7533 timing generator should match the ones used in DSI. If the timing parameters changed in DSI and these changing timing parameters can't pass to ADV7533, then it need to disable the timing generator of ADV7533 to make mode work. Some modes in HiKey is in this case.
The ADV7533 chip's DSI receiver supports the DSI Video mode sub-mode: "Non-burst Mode with sync pulses". Ideally, a DSI receiver supporting this mode should be able to reconstruct the timings using packets embedded in the controller.
With DB410c, it seems to struggle without it, and with Hikey, we need to explicitly disable it because, as I understand, the DSI controller there tweaks some of the timings parameters in the mode it wants to set.
It would have been ideal to not expose this knob in DT, but we need it for ADV7533 to work across multiple platforms.
Laurent, do you have any more thoughts on this? I'd posted out a v4 of this patch set. Please have a look when you get the chance.
Thanks, Archit
Hi Laurent,
On 3/9/2016 4:27 PM, Archit Taneja wrote:
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
Trying to get this driver merged has had some challenges:
ADV7533 has an I2C control bus, but acts as a DSI peripheral too. After discussions, it was concluded that we'd want to provide an API to create MIPI DSI devices, rather than expose two different interfaces on DT. The first version [1] tried the former approach the second version [2] showed how the driver would look like if exposed 2 DT nodes. This lateset patchset relies on the MIPI DSI device creation API provided by [3], this has been accepted and should be merged for 4.6.
The driver was designed as an I2C slave encoder. When ADV7533 patches were posted [1], it was modelled as a bridge, but ADV7511 and others were still left as I2C slave encoders. This wasn't accepted. After discussions, it was decided that ADV7511 too would be converted into a bridge driver, and all the users of ADV7511 should assume it is a bridge. This bridge conversion was done in [4]. There is still some debate over whether the bridge driver be involved in the connector creation, or the KMS driver that has the whole view of the display pipeline. This discussion shouldn't affect this patch set, though.
This patch set enables ADV7533 support with the above two issues now resolved. It also incorporates ADV7533 specific features and fixes that we've discovered since the first version of this patch was posted.
Tested on ADV7533 chips on DB410c. It should work on the Hikey board too. I'd appreaciate if someone could test it on a ADV7511 platform since I don't have one.
Could you try this patch set on the rcar-du platform? It would help to verify if this doesn't break ADV7511.
Thanks, Archit
[4] https://lists.freedesktop.org/archives/dri-devel/2016-January/098287.html
[3] https://lkml.org/lkml/2016/2/12/67
[2] https://lists.freedesktop.org/archives/dri-devel/2015-September/089884.html
Archit Taneja (7): drm/i2c: adv7511: Convert to drm_bridge drm/i2c: adv7511: Fix mutex deadlock when interrupts are disabled drm/i2c: adv7511: Initial support for ADV7533 drm/i2c: adv7511: Create a MIPI DSI device drm/i2c: adv7511: Use internal timing generator drm/i2c: adv7511: Change number of DSI lanes dynamically dt-bindings: drm/bridge: Update bindings for ADV7533
.../bindings/display/bridge/adi,adv7511.txt | 25 +- drivers/gpu/drm/i2c/adv7511.c | 539 +++++++++++++++++---- 2 files changed, 476 insertions(+), 88 deletions(-)
Hi Archit,
On Thursday 14 Apr 2016 20:26:11 Archit Taneja wrote:
On 3/9/2016 4:27 PM, Archit Taneja wrote:
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
Trying to get this driver merged has had some challenges:
ADV7533 has an I2C control bus, but acts as a DSI peripheral too.
After discussions, it was concluded that we'd want to provide an API to create MIPI DSI devices, rather than expose two different interfaces on DT. The first version [1] tried the former approach the second version [2] showed how the driver would look like if exposed 2 DT nodes. This lateset patchset relies on the MIPI DSI device creation API provided by [3], this has been accepted and should be merged for 4.6.
The driver was designed as an I2C slave encoder. When ADV7533
patches were posted [1], it was modelled as a bridge, but ADV7511 and others were still left as I2C slave encoders. This wasn't accepted. After discussions, it was decided that ADV7511 too would be converted into a bridge driver, and all the users of ADV7511 should assume it is a bridge. This bridge conversion was done in [4]. There is still some debate over whether the bridge driver be involved in the connector creation, or the KMS driver that has the whole view of the display pipeline. This discussion shouldn't affect this patch set, though.
This patch set enables ADV7533 support with the above two issues now resolved. It also incorporates ADV7533 specific features and fixes that we've discovered since the first version of this patch was posted.
Tested on ADV7533 chips on DB410c. It should work on the Hikey board too. I'd appreaciate if someone could test it on a ADV7511 platform since I don't have one.
Could you try this patch set on the rcar-du platform? It would help to verify if this doesn't break ADV7511.
Basic testing didn't show any regression. For the whole series,
Tested-by: Laurent Pinchart laurent.pinchart@ideasonboard.com
[4] https://lists.freedesktop.org/archives/dri-devel/2016-January/098287.html [3] https://lkml.org/lkml/2016/2/12/67 [2] https://lists.freedesktop.org/archives/dri-devel/2015-September/089884.htm l [1]: https://lists.freedesktop.org/archives/dri-devel/2015-July/087088.html
Archit Taneja (7): drm/i2c: adv7511: Convert to drm_bridge drm/i2c: adv7511: Fix mutex deadlock when interrupts are disabled drm/i2c: adv7511: Initial support for ADV7533 drm/i2c: adv7511: Create a MIPI DSI device drm/i2c: adv7511: Use internal timing generator drm/i2c: adv7511: Change number of DSI lanes dynamically dt-bindings: drm/bridge: Update bindings for ADV7533
.../bindings/display/bridge/adi,adv7511.txt | 25 +- drivers/gpu/drm/i2c/adv7511.c | 539 +++++++++++---- 2 files changed, 476 insertions(+), 88 deletions(-)
On 04/22/2016 04:03 AM, Laurent Pinchart wrote:
Hi Archit,
On Thursday 14 Apr 2016 20:26:11 Archit Taneja wrote:
On 3/9/2016 4:27 PM, Archit Taneja wrote:
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
Trying to get this driver merged has had some challenges:
ADV7533 has an I2C control bus, but acts as a DSI peripheral too.
After discussions, it was concluded that we'd want to provide an API to create MIPI DSI devices, rather than expose two different interfaces on DT. The first version [1] tried the former approach the second version [2] showed how the driver would look like if exposed 2 DT nodes. This lateset patchset relies on the MIPI DSI device creation API provided by [3], this has been accepted and should be merged for 4.6.
The driver was designed as an I2C slave encoder. When ADV7533
patches were posted [1], it was modelled as a bridge, but ADV7511 and others were still left as I2C slave encoders. This wasn't accepted. After discussions, it was decided that ADV7511 too would be converted into a bridge driver, and all the users of ADV7511 should assume it is a bridge. This bridge conversion was done in [4]. There is still some debate over whether the bridge driver be involved in the connector creation, or the KMS driver that has the whole view of the display pipeline. This discussion shouldn't affect this patch set, though.
This patch set enables ADV7533 support with the above two issues now resolved. It also incorporates ADV7533 specific features and fixes that we've discovered since the first version of this patch was posted.
Tested on ADV7533 chips on DB410c. It should work on the Hikey board too. I'd appreaciate if someone could test it on a ADV7511 platform since I don't have one.
Could you try this patch set on the rcar-du platform? It would help to verify if this doesn't break ADV7511.
Basic testing didn't show any regression. For the whole series,
Tested-by: Laurent Pinchart laurent.pinchart@ideasonboard.com
Thanks!
Archit
Hi Archit, Thank you for the patches.
On 9 March 2016 at 18:57, Archit Taneja architt@codeaurora.org wrote:
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
Trying to get this driver merged has had some challenges:
ADV7533 has an I2C control bus, but acts as a DSI peripheral too. After discussions, it was concluded that we'd want to provide an API to create MIPI DSI devices, rather than expose two different interfaces on DT. The first version [1] tried the former approach the second version [2] showed how the driver would look like if exposed 2 DT nodes. This lateset patchset relies on the MIPI DSI device creation API provided by [3], this has been accepted and should be merged for 4.6.
The driver was designed as an I2C slave encoder. When ADV7533 patches were posted [1], it was modelled as a bridge, but ADV7511 and others were still left as I2C slave encoders. This wasn't accepted. After discussions, it was decided that ADV7511 too would be converted into a bridge driver, and all the users of ADV7511 should assume it is a bridge. This bridge conversion was done in [4]. There is still some debate over whether the bridge driver be involved in the connector creation, or the KMS driver that has the whole view of the display pipeline. This discussion shouldn't affect this patch set, though.
I also agree with Laurent Pinchart that bridge driver shoudn't get involved in in the connector creation. It is better for KMS driver that has the whole view of the display pipeline.
In this case, I mean creating bridge driver to support ADV7533. I think this is something look like panel driver. Maybe we need to add some callback to the bridge to avoid creating connector in bridge driver. We can refer to panel entity to see what callbacks to be added. One callback I can see is the get_modes callback might be need.
This patch set enables ADV7533 support with the above two issues now resolved. It also incorporates ADV7533 specific features and fixes that we've discovered since the first version of this patch was posted.
Tested on ADV7533 chips on DB410c. It should work on the Hikey board too.
I have tested the this tracking branch : https://git.linaro.org/landing-teams/working/qualcomm/kernel.git/shortlog/re... it works for HiKey. But it seems this patch set is a little different from the above branch. I think I need to tested this patch set.
Thanks, -xinliang
I'd appreaciate if someone could test it on a ADV7511 platform since I don't have one.
[4] https://lists.freedesktop.org/archives/dri-devel/2016-January/098287.html
[3] https://lkml.org/lkml/2016/2/12/67
[2] https://lists.freedesktop.org/archives/dri-devel/2015-September/089884.html
Archit Taneja (7): drm/i2c: adv7511: Convert to drm_bridge drm/i2c: adv7511: Fix mutex deadlock when interrupts are disabled drm/i2c: adv7511: Initial support for ADV7533 drm/i2c: adv7511: Create a MIPI DSI device drm/i2c: adv7511: Use internal timing generator drm/i2c: adv7511: Change number of DSI lanes dynamically dt-bindings: drm/bridge: Update bindings for ADV7533
.../bindings/display/bridge/adi,adv7511.txt | 25 +- drivers/gpu/drm/i2c/adv7511.c | 539 +++++++++++++++++---- 2 files changed, 476 insertions(+), 88 deletions(-)
-- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, hosted by The Linux Foundation
Hi,
On 04/17/2016 05:01 PM, Xinliang Liu wrote:
Hi Archit, Thank you for the patches.
On 9 March 2016 at 18:57, Archit Taneja architt@codeaurora.org wrote:
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
Trying to get this driver merged has had some challenges:
ADV7533 has an I2C control bus, but acts as a DSI peripheral too. After discussions, it was concluded that we'd want to provide an API to create MIPI DSI devices, rather than expose two different interfaces on DT. The first version [1] tried the former approach the second version [2] showed how the driver would look like if exposed 2 DT nodes. This lateset patchset relies on the MIPI DSI device creation API provided by [3], this has been accepted and should be merged for 4.6.
The driver was designed as an I2C slave encoder. When ADV7533 patches were posted [1], it was modelled as a bridge, but ADV7511 and others were still left as I2C slave encoders. This wasn't accepted. After discussions, it was decided that ADV7511 too would be converted into a bridge driver, and all the users of ADV7511 should assume it is a bridge. This bridge conversion was done in [4]. There is still some debate over whether the bridge driver be involved in the connector creation, or the KMS driver that has the whole view of the display pipeline. This discussion shouldn't affect this patch set, though.
I also agree with Laurent Pinchart that bridge driver shoudn't get involved in in the connector creation. It is better for KMS driver that has the whole view of the display pipeline.
Yes, that's the eventual plan. We were thinking of creating an additional drm bridge api (drm_bridge_create_connector) that helps the KMS driver create a connector for the bridge.
Since there are certain connector related properties that the KMS driver may not be aware of, we were thinking of creating another drm_bridge op which fills up connector properties. Some properties (like whether we want POLLED HPD or not) are more platform specific, and would be parsed via the connector's DT node (that is, if DT is supported on that platform)
For now, this series creates the connector in the bridge's attach op, but relies on the KMS driver to call drm_connector_register. The other stuff I mentioned above can come later.
In this case, I mean creating bridge driver to support ADV7533. I think this is something look like panel driver. Maybe we need to add some callback to the bridge to avoid creating connector in bridge driver. We can refer to panel entity to see what callbacks to be added. One callback I can see is the get_modes callback might be need.
This patch set enables ADV7533 support with the above two issues now resolved. It also incorporates ADV7533 specific features and fixes that we've discovered since the first version of this patch was posted.
Tested on ADV7533 chips on DB410c. It should work on the Hikey board too.
I have tested the this tracking branch : https://git.linaro.org/landing-teams/working/qualcomm/kernel.git/shortlog/re... it works for HiKey. But it seems this patch set is a little different from the above branch. I think I need to tested this patch set.
Yes, it would be great if you could test the posted patch set and provided a Tested-by. The patches "drm/i2c: adv7511: Init regulators" and above need to still be applied from this branch. I'll post these out in another patch set later.
Thanks, Archit
Thanks, -xinliang
I'd appreaciate if someone could test it on a ADV7511 platform since I don't have one.
[4] https://lists.freedesktop.org/archives/dri-devel/2016-January/098287.html
[3] https://lkml.org/lkml/2016/2/12/67
[2] https://lists.freedesktop.org/archives/dri-devel/2015-September/089884.html
Archit Taneja (7): drm/i2c: adv7511: Convert to drm_bridge drm/i2c: adv7511: Fix mutex deadlock when interrupts are disabled drm/i2c: adv7511: Initial support for ADV7533 drm/i2c: adv7511: Create a MIPI DSI device drm/i2c: adv7511: Use internal timing generator drm/i2c: adv7511: Change number of DSI lanes dynamically dt-bindings: drm/bridge: Update bindings for ADV7533
.../bindings/display/bridge/adi,adv7511.txt | 25 +- drivers/gpu/drm/i2c/adv7511.c | 539 +++++++++++++++++---- 2 files changed, 476 insertions(+), 88 deletions(-)
-- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, hosted by The Linux Foundation
On 18 April 2016 at 17:48, Archit Taneja architt@codeaurora.org wrote:
Hi,
On 04/17/2016 05:01 PM, Xinliang Liu wrote:
Hi Archit, Thank you for the patches.
On 9 March 2016 at 18:57, Archit Taneja architt@codeaurora.org wrote:
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
Trying to get this driver merged has had some challenges:
ADV7533 has an I2C control bus, but acts as a DSI peripheral too. After discussions, it was concluded that we'd want to provide an API to create MIPI DSI devices, rather than expose two different interfaces on DT. The first version [1] tried the former approach the second version [2] showed how the driver would look like if exposed 2 DT nodes. This lateset patchset relies on the MIPI DSI device creation API provided by [3], this has been accepted and should be merged for 4.6.
The driver was designed as an I2C slave encoder. When ADV7533 patches were posted [1], it was modelled as a bridge, but ADV7511 and others were still left as I2C slave encoders. This wasn't accepted. After discussions, it was decided that ADV7511 too would be converted into a bridge driver, and all the users of ADV7511 should assume it is a bridge. This bridge conversion was done in [4]. There is still some debate over whether the bridge driver be involved in the connector creation, or the KMS driver that has the whole view of the display pipeline. This discussion shouldn't affect this patch set, though.
I also agree with Laurent Pinchart that bridge driver shoudn't get involved in in the connector creation. It is better for KMS driver that has the whole view of the display pipeline.
Yes, that's the eventual plan. We were thinking of creating an additional drm bridge api (drm_bridge_create_connector) that helps the KMS driver create a connector for the bridge.
Since there are certain connector related properties that the KMS driver may not be aware of, we were thinking of creating another drm_bridge op which fills up connector properties. Some properties (like whether we want POLLED HPD or not) are more platform specific, and would be parsed via the connector's DT node (that is, if DT is supported on that platform)
For now, this series creates the connector in the bridge's attach op, but relies on the KMS driver to call drm_connector_register. The other stuff I mentioned above can come later.
This is great, if we have the plan.
In this case, I mean creating bridge driver to support ADV7533. I think this is something look like panel driver. Maybe we need to add some callback to the bridge to avoid creating connector in bridge driver. We can refer to panel entity to see what callbacks to be added. One callback I can see is the get_modes callback might be need.
This patch set enables ADV7533 support with the above two issues now resolved. It also incorporates ADV7533 specific features and fixes that we've discovered since the first version of this patch was posted.
Tested on ADV7533 chips on DB410c. It should work on the Hikey board too.
I have tested the this tracking branch :
https://git.linaro.org/landing-teams/working/qualcomm/kernel.git/shortlog/re... it works for HiKey. But it seems this patch set is a little different from the above branch. I think I need to tested this patch set.
Yes, it would be great if you could test the posted patch set and provided a Tested-by. The patches "drm/i2c: adv7511: Init regulators" and above need to still be applied from this branch. I'll post these out in another patch set later.
Sure, I will test them all.
Thanks, -xinliang
Thanks, Archit
Thanks, -xinliang
I'd appreaciate if someone could test it on a ADV7511 platform since I don't have one.
[4] https://lists.freedesktop.org/archives/dri-devel/2016-January/098287.html
[3] https://lkml.org/lkml/2016/2/12/67
[2]
https://lists.freedesktop.org/archives/dri-devel/2015-September/089884.html
Archit Taneja (7): drm/i2c: adv7511: Convert to drm_bridge drm/i2c: adv7511: Fix mutex deadlock when interrupts are disabled drm/i2c: adv7511: Initial support for ADV7533 drm/i2c: adv7511: Create a MIPI DSI device drm/i2c: adv7511: Use internal timing generator drm/i2c: adv7511: Change number of DSI lanes dynamically dt-bindings: drm/bridge: Update bindings for ADV7533
.../bindings/display/bridge/adi,adv7511.txt | 25 +- drivers/gpu/drm/i2c/adv7511.c | 539 +++++++++++++++++---- 2 files changed, 476 insertions(+), 88 deletions(-)
-- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, hosted by The Linux Foundation
-- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, hosted by The Linux Foundation
Hi Archit,
On Monday 18 Apr 2016 15:18:30 Archit Taneja wrote:
On 04/17/2016 05:01 PM, Xinliang Liu wrote:
On 9 March 2016 at 18:57, Archit Taneja architt@codeaurora.org wrote:
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
Trying to get this driver merged has had some challenges:
ADV7533 has an I2C control bus, but acts as a DSI peripheral too. After discussions, it was concluded that we'd want to provide an API to create MIPI DSI devices, rather than expose two different interfaces on DT. The first version [1] tried the former approach the second version [2] showed how the driver would look like if exposed 2 DT nodes. This lateset patchset relies on the MIPI DSI device creation API provided by [3], this has been accepted and should be merged for 4.6.
The driver was designed as an I2C slave encoder. When ADV7533 patches were posted [1], it was modelled as a bridge, but ADV7511 and others were still left as I2C slave encoders. This wasn't accepted. After discussions, it was decided that ADV7511 too would be converted into a bridge driver, and all the users of ADV7511 should assume it is a bridge. This bridge conversion was done in [4]. There is still some debate over whether the bridge driver be involved in the connector creation, or the KMS driver that has the whole view of the display pipeline. This discussion shouldn't affect this patch set, though.
I also agree with Laurent Pinchart that bridge driver shoudn't get involved in in the connector creation. It is better for KMS driver that has the whole view of the display pipeline.
Yes, that's the eventual plan. We were thinking of creating an additional drm bridge api (drm_bridge_create_connector) that helps the KMS driver create a connector for the bridge.
Since there are certain connector related properties that the KMS driver may not be aware of, we were thinking of creating another drm_bridge op which fills up connector properties. Some properties (like whether we want POLLED HPD or not) are more platform specific, and would be parsed via the connector's DT node (that is, if DT is supported on that platform)
For now, this series creates the connector in the bridge's attach op, but relies on the KMS driver to call drm_connector_register. The other stuff I mentioned above can come later.
Do you think you'd have time to lead that effort ?
In this case, I mean creating bridge driver to support ADV7533. I think this is something look like panel driver. Maybe we need to add some callback to the bridge to avoid creating connector in bridge driver. We can refer to panel entity to see what callbacks to be added. One callback I can see is the get_modes callback might be need.
This patch set enables ADV7533 support with the above two issues now resolved. It also incorporates ADV7533 specific features and fixes that we've discovered since the first version of this patch was posted.
Tested on ADV7533 chips on DB410c. It should work on the Hikey board too.
I have tested the this tracking branch : https://git.linaro.org/landing-teams/working/qualcomm/kernel.git/shortlog/ refs/heads/tracking-qcomlt-adv7511 it works for HiKey. But it seems this patch set is a little different from the above branch. I think I need to tested this patch set.
Yes, it would be great if you could test the posted patch set and provided a Tested-by. The patches "drm/i2c: adv7511: Init regulators" and above need to still be applied from this branch. I'll post these out in another patch set later.
I'd appreaciate if someone could test it on a ADV7511 platform since I don't have one.
[4] https://lists.freedesktop.org/archives/dri-devel/2016-January/098287.html [3] https://lkml.org/lkml/2016/2/12/67 [2] https://lists.freedesktop.org/archives/dri-devel/2015-September/089884.ht ml [1]: https://lists.freedesktop.org/archives/dri-devel/2015-July/087088.html
Archit Taneja (7): drm/i2c: adv7511: Convert to drm_bridge drm/i2c: adv7511: Fix mutex deadlock when interrupts are disabled drm/i2c: adv7511: Initial support for ADV7533 drm/i2c: adv7511: Create a MIPI DSI device drm/i2c: adv7511: Use internal timing generator drm/i2c: adv7511: Change number of DSI lanes dynamically dt-bindings: drm/bridge: Update bindings for ADV7533
.../bindings/display/bridge/adi,adv7511.txt | 25 +- drivers/gpu/drm/i2c/adv7511.c | 539 ++++++++++---- 2 files changed, 476 insertions(+), 88 deletions(-)
On 04/22/2016 04:06 AM, Laurent Pinchart wrote:
Hi Archit,
On Monday 18 Apr 2016 15:18:30 Archit Taneja wrote:
On 04/17/2016 05:01 PM, Xinliang Liu wrote:
On 9 March 2016 at 18:57, Archit Taneja architt@codeaurora.org wrote:
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
Trying to get this driver merged has had some challenges:
ADV7533 has an I2C control bus, but acts as a DSI peripheral too. After discussions, it was concluded that we'd want to provide an API to create MIPI DSI devices, rather than expose two different interfaces on DT. The first version [1] tried the former approach the second version [2] showed how the driver would look like if exposed 2 DT nodes. This lateset patchset relies on the MIPI DSI device creation API provided by [3], this has been accepted and should be merged for 4.6.
The driver was designed as an I2C slave encoder. When ADV7533 patches were posted [1], it was modelled as a bridge, but ADV7511 and others were still left as I2C slave encoders. This wasn't accepted. After discussions, it was decided that ADV7511 too would be converted into a bridge driver, and all the users of ADV7511 should assume it is a bridge. This bridge conversion was done in [4]. There is still some debate over whether the bridge driver be involved in the connector creation, or the KMS driver that has the whole view of the display pipeline. This discussion shouldn't affect this patch set, though.
I also agree with Laurent Pinchart that bridge driver shoudn't get involved in in the connector creation. It is better for KMS driver that has the whole view of the display pipeline.
Yes, that's the eventual plan. We were thinking of creating an additional drm bridge api (drm_bridge_create_connector) that helps the KMS driver create a connector for the bridge.
Since there are certain connector related properties that the KMS driver may not be aware of, we were thinking of creating another drm_bridge op which fills up connector properties. Some properties (like whether we want POLLED HPD or not) are more platform specific, and would be parsed via the connector's DT node (that is, if DT is supported on that platform)
For now, this series creates the connector in the bridge's attach op, but relies on the KMS driver to call drm_connector_register. The other stuff I mentioned above can come later.
Do you think you'd have time to lead that effort ?
Yeah, I think I should.
I'll prepare a RFC and we can see how that goes.
Thanks, Archit
On 9 March 2016 at 18:57, Archit Taneja architt@codeaurora.org wrote:
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
Trying to get this driver merged has had some challenges:
ADV7533 has an I2C control bus, but acts as a DSI peripheral too. After discussions, it was concluded that we'd want to provide an API to create MIPI DSI devices, rather than expose two different interfaces on DT. The first version [1] tried the former approach the second version [2] showed how the driver would look like if exposed 2 DT nodes. This lateset patchset relies on the MIPI DSI device creation API provided by [3], this has been accepted and should be merged for 4.6.
The driver was designed as an I2C slave encoder. When ADV7533 patches were posted [1], it was modelled as a bridge, but ADV7511 and others were still left as I2C slave encoders. This wasn't accepted. After discussions, it was decided that ADV7511 too would be converted into a bridge driver, and all the users of ADV7511 should assume it is a bridge. This bridge conversion was done in [4]. There is still some debate over whether the bridge driver be involved in the connector creation, or the KMS driver that has the whole view of the display pipeline. This discussion shouldn't affect this patch set, though.
This patch set enables ADV7533 support with the above two issues now resolved. It also incorporates ADV7533 specific features and fixes that we've discovered since the first version of this patch was posted.
Tested on ADV7533 chips on DB410c. It should work on the Hikey board too. I'd appreaciate if someone could test it on a ADV7511 platform since I don't have one.
[4] https://lists.freedesktop.org/archives/dri-devel/2016-January/098287.html
[3] https://lkml.org/lkml/2016/2/12/67
[2] https://lists.freedesktop.org/archives/dri-devel/2015-September/089884.html
Archit Taneja (7): drm/i2c: adv7511: Convert to drm_bridge drm/i2c: adv7511: Fix mutex deadlock when interrupts are disabled drm/i2c: adv7511: Initial support for ADV7533 drm/i2c: adv7511: Create a MIPI DSI device drm/i2c: adv7511: Use internal timing generator drm/i2c: adv7511: Change number of DSI lanes dynamically dt-bindings: drm/bridge: Update bindings for ADV7533
.../bindings/display/bridge/adi,adv7511.txt | 25 +- drivers/gpu/drm/i2c/adv7511.c | 539 +++++++++++++++++---- 2 files changed, 476 insertions(+), 88 deletions(-)
-- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, hosted by The Linux Foundation
This patch set is Tested-by: Xinliang Liu xinliang.liu@linaro.org
Thanks -xinliang
On 05/03/2016 07:22 AM, Xinliang Liu wrote:
On 9 March 2016 at 18:57, Archit Taneja architt@codeaurora.org wrote:
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
Trying to get this driver merged has had some challenges:
ADV7533 has an I2C control bus, but acts as a DSI peripheral too. After discussions, it was concluded that we'd want to provide an API to create MIPI DSI devices, rather than expose two different interfaces on DT. The first version [1] tried the former approach the second version [2] showed how the driver would look like if exposed 2 DT nodes. This lateset patchset relies on the MIPI DSI device creation API provided by [3], this has been accepted and should be merged for 4.6.
The driver was designed as an I2C slave encoder. When ADV7533 patches were posted [1], it was modelled as a bridge, but ADV7511 and others were still left as I2C slave encoders. This wasn't accepted. After discussions, it was decided that ADV7511 too would be converted into a bridge driver, and all the users of ADV7511 should assume it is a bridge. This bridge conversion was done in [4]. There is still some debate over whether the bridge driver be involved in the connector creation, or the KMS driver that has the whole view of the display pipeline. This discussion shouldn't affect this patch set, though.
This patch set enables ADV7533 support with the above two issues now resolved. It also incorporates ADV7533 specific features and fixes that we've discovered since the first version of this patch was posted.
Tested on ADV7533 chips on DB410c. It should work on the Hikey board too. I'd appreaciate if someone could test it on a ADV7511 platform since I don't have one.
[4] https://lists.freedesktop.org/archives/dri-devel/2016-January/098287.html
[3] https://lkml.org/lkml/2016/2/12/67
[2] https://lists.freedesktop.org/archives/dri-devel/2015-September/089884.html
Archit Taneja (7): drm/i2c: adv7511: Convert to drm_bridge drm/i2c: adv7511: Fix mutex deadlock when interrupts are disabled drm/i2c: adv7511: Initial support for ADV7533 drm/i2c: adv7511: Create a MIPI DSI device drm/i2c: adv7511: Use internal timing generator drm/i2c: adv7511: Change number of DSI lanes dynamically dt-bindings: drm/bridge: Update bindings for ADV7533
.../bindings/display/bridge/adi,adv7511.txt | 25 +- drivers/gpu/drm/i2c/adv7511.c | 539 +++++++++++++++++---- 2 files changed, 476 insertions(+), 88 deletions(-)
-- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, hosted by The Linux Foundation
This patch set is Tested-by: Xinliang Liu xinliang.liu@linaro.org
Thanks for testing!
Archit
Thanks
-xinliang
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
Trying to get this driver merged has had some challenges:
- ADV7533 has an I2C control bus, but acts as a DSI peripheral too. After discussions, it was concluded that we'd want to provide an API to create MIPI DSI devices, rather than expose two different interfaces on DT. The first version [1] tried the former approach the second version [2] showed how the driver would look like if exposed 2 DT nodes. This lateset patchset relies on the MIPI DSI device creation API provided by [3], this has been accepted and should be merged for 4.6.
- The driver was designed as an I2C slave encoder. When ADV7533 patches were posted [1], it was modelled as a bridge, but ADV7511 and others were still left as I2C slave encoders. This wasn't accepted. After discussions, it was decided that ADV7511 too would be converted into a bridge driver, and all the users of ADV7511 should assume it is a bridge. This bridge conversion was done in [4]. There is still some debate over whether the bridge driver be involved in the connector creation, or the KMS driver that has the whole view of the display pipeline. This discussion shouldn't affect this patch set, though.
This patch set enables ADV7533 support with the above two issues now resolved. It also incorporates ADV7533 specific features and fixes that we've discovered since the first version of this patch was posted.
Changes in v4: - Separated out build for ADV7533. The original plan was to stub out the drm_mipi_dsi funcs, but that seemed like an overkill since it helped just this driver. It seems better to stub out the ADV7533 functionality altogether instead. - Some minor DT binding corrections suggested by Laurent.
[4] https://lists.freedesktop.org/archives/dri-devel/2016-January/098287.html
[3] https://lkml.org/lkml/2016/2/12/67
[2] https://lists.freedesktop.org/archives/dri-devel/2015-September/089884.html
[1]: https://lists.freedesktop.org/archives/dri-devel/2015-July/087088.html
Archit Taneja (7): drm/i2c: adv7511: Convert to drm_bridge drm/i2c: adv7511: Fix mutex deadlock when interrupts are disabled drm/i2c: adv7511: Initial support for ADV7533 drm/i2c: adv7533: Create a MIPI DSI device drm/i2c: adv7533: Use internal timing generator drm/i2c: adv7533: Change number of DSI lanes dynamically dt-bindings: drm/bridge: Update bindings for ADV7533
.../bindings/display/bridge/adi,adv7511.txt | 26 +- drivers/gpu/drm/i2c/Kconfig | 8 + drivers/gpu/drm/i2c/Makefile | 4 +- drivers/gpu/drm/i2c/adv7511.c | 333 ++++++++++++++------- drivers/gpu/drm/i2c/adv7511.h | 95 ++++++ drivers/gpu/drm/i2c/adv7533.c | 265 ++++++++++++++++ 6 files changed, 613 insertions(+), 118 deletions(-) create mode 100644 drivers/gpu/drm/i2c/adv7533.c
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.
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 222 ++++++++++++++++++++++++++++-------------- 1 file changed, 148 insertions(+), 74 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index a02112b..fec5627 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -14,9 +14,10 @@ #include <linux/slab.h>
#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> -#include <drm/drm_encoder_slave.h>
#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,120 @@ 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 struct drm_encoder * +adv7511_connector_best_encoder(struct drm_connector *connector) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv->bridge.encoder; +} + +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, + .best_encoder = adv7511_connector_best_encoder, + .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 +1035,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 +1056,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 +1065,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 +1081,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 lars@metafoo.de"); MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver");
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 architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index fec5627..c00d11d 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.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;
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 lars@metafoo.de
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/Kconfig | 7 +++ drivers/gpu/drm/i2c/Makefile | 4 +- drivers/gpu/drm/i2c/adv7511.c | 100 +++++++++++++++++++++--------------------- drivers/gpu/drm/i2c/adv7511.h | 67 ++++++++++++++++++++++++++++ drivers/gpu/drm/i2c/adv7533.c | 100 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 228 insertions(+), 50 deletions(-) create mode 100644 drivers/gpu/drm/i2c/adv7533.c
diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 22c7ed6..03c5528 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -7,6 +7,13 @@ config DRM_I2C_ADV7511 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. + 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..cd2c22c 100644 --- a/drivers/gpu/drm/i2c/Makefile +++ b/drivers/gpu/drm/i2c/Makefile @@ -1,6 +1,8 @@ ccflags-y := -Iinclude/drm
-obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o +adv75xx-y := adv7511.o +adv75xx-$(CONFIG_DRM_I2C_ADV7533) += adv7533.o +obj-$(CONFIG_DRM_I2C_ADV7511) += adv75xx.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 index c00d11d..9deb4fd 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -8,48 +8,17 @@
#include <linux/device.h> #include <linux/gpio/consumer.h> -#include <linux/i2c.h> #include <linux/module.h> -#include <linux/regmap.h> +#include <linux/of_device.h> #include <linux/slab.h>
#include <drm/drmP.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> -#include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h>
#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; }
@@ -871,8 +846,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) @@ -972,9 +945,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 @@ -998,8 +980,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;
@@ -1014,6 +1000,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);
@@ -1022,7 +1014,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 */ @@ -1033,7 +1025,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; @@ -1041,12 +1034,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; @@ -1058,6 +1053,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); @@ -1066,17 +1062,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/i2c/adv7511.h b/drivers/gpu/drm/i2c/adv7511.h index 38515b3..541c5d7 100644 --- a/drivers/gpu/drm/i2c/adv7511.h +++ b/drivers/gpu/drm/i2c/adv7511.h @@ -10,6 +10,10 @@ #define __DRM_I2C_ADV7511_H__
#include <linux/hdmi.h> +#include <linux/i2c.h> +#include <linux/regmap.h> + +#include <drm/drm_crtc_helper.h>
#define ADV7511_REG_CHIP_REVISION 0x00 #define ADV7511_REG_N0 0x01 @@ -286,4 +290,67 @@ 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/i2c/adv7533.c b/drivers/gpu/drm/i2c/adv7533.c new file mode 100644 index 0000000..cb4ca64 --- /dev/null +++ b/drivers/gpu/drm/i2c/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; +}
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 architt@codeaurora.org --- drivers/gpu/drm/i2c/Kconfig | 1 + drivers/gpu/drm/i2c/adv7511.c | 40 ++++++++++++++++--- drivers/gpu/drm/i2c/adv7511.h | 20 ++++++++++ drivers/gpu/drm/i2c/adv7533.c | 91 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 144 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 03c5528..f7cd9e5 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -10,6 +10,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/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 9deb4fd..42bd674 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -826,6 +826,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; }
@@ -952,11 +955,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 @@ -1051,9 +1055,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); @@ -1083,6 +1091,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", @@ -1093,7 +1105,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 lars@metafoo.de"); MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver"); diff --git a/drivers/gpu/drm/i2c/adv7511.h b/drivers/gpu/drm/i2c/adv7511.h index 541c5d7..056f747 100644 --- a/drivers/gpu/drm/i2c/adv7511.h +++ b/drivers/gpu/drm/i2c/adv7511.h @@ -14,6 +14,7 @@ #include <linux/regmap.h>
#include <drm/drm_crtc_helper.h> +#include <drm/drm_mipi_dsi.h>
#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) { @@ -351,6 +360,17 @@ 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/i2c/adv7533.c b/drivers/gpu/drm/i2c/adv7533.c index cb4ca64..ecbcaa0 100644 --- a/drivers/gpu/drm/i2c/adv7533.c +++ b/drivers/gpu/drm/i2c/adv7533.c @@ -11,6 +11,8 @@ * GNU General Public License for more details. */
+#include <linux/of_graph.h> + #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; +}
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 architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 2 ++ drivers/gpu/drm/i2c/adv7511.h | 3 +++ drivers/gpu/drm/i2c/adv7533.c | 60 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 63 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 42bd674..61c751f 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.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/i2c/adv7511.h b/drivers/gpu/drm/i2c/adv7511.h index 056f747..a9745ea 100644 --- a/drivers/gpu/drm/i2c/adv7511.h +++ b/drivers/gpu/drm/i2c/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/i2c/adv7533.c b/drivers/gpu/drm/i2c/adv7533.c index ecbcaa0..d002ac4 100644 --- a/drivers/gpu/drm/i2c/adv7533.c +++ b/drivers/gpu/drm/i2c/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;
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 andy.green@linaro.org
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 3 +++ drivers/gpu/drm/i2c/adv7511.h | 5 +++++ drivers/gpu/drm/i2c/adv7533.c | 22 ++++++++++++++++++++++ 3 files changed, 30 insertions(+)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 61c751f..5dfca1a 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.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/i2c/adv7511.h b/drivers/gpu/drm/i2c/adv7511.h index a9745ea..b113b77 100644 --- a/drivers/gpu/drm/i2c/adv7511.h +++ b/drivers/gpu/drm/i2c/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); @@ -352,6 +353,10 @@ static inline void adv7533_dsi_power_on(struct adv7511 *adv) 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/i2c/adv7533.c b/drivers/gpu/drm/i2c/adv7533.c index d002ac4..5eebd15 100644 --- a/drivers/gpu/drm/i2c/adv7533.c +++ b/drivers/gpu/drm/i2c/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,
Add description of ADV7533. Add the required and optional properties that are specific to it.
Cc: devicetree@vger.kernel.org
Acked-by: Rob Herring robh@kernel.org
Signed-off-by: Archit Taneja architt@codeaurora.org --- .../bindings/display/bridge/adi,adv7511.txt | 26 +++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-)
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
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
Trying to get this driver merged has had some challenges:
- ADV7533 has an I2C control bus, but acts as a DSI peripheral too. After discussions, it was concluded that we'd want to provide an API to create MIPI DSI devices, rather than expose two different interfaces on DT. The first version [1] tried the former approach the second version [2] showed how the driver would look like if exposed 2 DT nodes. This lateset patchset relies on the MIPI DSI device creation API provided by [3], this has been accepted and should be merged for 4.6.
- The driver was designed as an I2C slave encoder. When ADV7533 patches were posted [1], it was modelled as a bridge, but ADV7511 and others were still left as I2C slave encoders. This wasn't accepted. After discussions, it was decided that ADV7511 too would be converted into a bridge driver, and all the users of ADV7511 should assume it is a bridge. This bridge conversion was done in [4]. There is still some debate over whether the bridge driver be involved in the connector creation, or the KMS driver that has the whole view of the display pipeline. This discussion shouldn't affect this patch set, though.
This patch set enables ADV7533 support with the above two issues now resolved. It also incorporates ADV7533 specific features and fixes that we've discovered since the first version of this patch was posted.
If this looks okay, I request that it gets pulled in 4.8. This patchset has been floating around since July last year.
Changes in v5: - Fix break observed when built for x86. - Based off current drm-misc. - Removes best_encoder connector helper function since it isn't needed after this series: https://lkml.org/lkml/2016/6/2/508
Changes in v4: - Separated out build for ADV7533. The original plan was to stub out the drm_mipi_dsi funcs, but that seemed like an overkill since it helped just this driver. It seems better to stub out the ADV7533 functionality altogether instead. - Some minor DT binding corrections suggested by Laurent.
[4] https://lists.freedesktop.org/archives/dri-devel/2016-January/098287.html
[3] https://lkml.org/lkml/2016/2/12/67
[2] https://lists.freedesktop.org/archives/dri-devel/2015-September/089884.html
[1]: https://lists.freedesktop.org/archives/dri-devel/2015-July/087088.html
Archit Taneja (7): drm/i2c: adv7511: Convert to drm_bridge drm/i2c: adv7511: Fix mutex deadlock when interrupts are disabled drm/i2c: adv7511: Initial support for ADV7533 drm/i2c: adv7533: Create a MIPI DSI device drm/i2c: adv7533: Use internal timing generator drm/i2c: adv7533: Change number of DSI lanes dynamically dt-bindings: drm/bridge: Update bindings for ADV7533
.../bindings/display/bridge/adi,adv7511.txt | 26 +- drivers/gpu/drm/i2c/Kconfig | 9 + drivers/gpu/drm/i2c/Makefile | 4 +- drivers/gpu/drm/i2c/adv7511.c | 324 ++++++++++++++------- drivers/gpu/drm/i2c/adv7511.h | 95 ++++++ drivers/gpu/drm/i2c/adv7533.c | 265 +++++++++++++++++ 6 files changed, 605 insertions(+), 118 deletions(-) create mode 100644 drivers/gpu/drm/i2c/adv7533.c
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 architt@codeaurora.org --- Changes in v5: - Make the driver depend on CONFIG_OF - Remove best_encoder connector helper
drivers/gpu/drm/i2c/Kconfig | 1 + drivers/gpu/drm/i2c/adv7511.c | 213 +++++++++++++++++++++++++++--------------- 2 files changed, 140 insertions(+), 74 deletions(-)
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 <linux/slab.h>
#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> -#include <drm/drm_encoder_slave.h>
#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 lars@metafoo.de"); MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver");
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 architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index c2642f9..1fff0ab 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.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;
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 lars@metafoo.de
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/Kconfig | 7 +++ drivers/gpu/drm/i2c/Makefile | 4 +- drivers/gpu/drm/i2c/adv7511.c | 100 +++++++++++++++++++++--------------------- drivers/gpu/drm/i2c/adv7511.h | 67 ++++++++++++++++++++++++++++ drivers/gpu/drm/i2c/adv7533.c | 100 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 228 insertions(+), 50 deletions(-) create mode 100644 drivers/gpu/drm/i2c/adv7533.c
diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 8bb0697..4cbbfd8 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -8,6 +8,13 @@ config DRM_I2C_ADV7511 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. + 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..cd2c22c 100644 --- a/drivers/gpu/drm/i2c/Makefile +++ b/drivers/gpu/drm/i2c/Makefile @@ -1,6 +1,8 @@ ccflags-y := -Iinclude/drm
-obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o +adv75xx-y := adv7511.o +adv75xx-$(CONFIG_DRM_I2C_ADV7533) += adv7533.o +obj-$(CONFIG_DRM_I2C_ADV7511) += adv75xx.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 index 1fff0ab..e33702b 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -8,48 +8,17 @@
#include <linux/device.h> #include <linux/gpio/consumer.h> -#include <linux/i2c.h> #include <linux/module.h> -#include <linux/regmap.h> +#include <linux/of_device.h> #include <linux/slab.h>
#include <drm/drmP.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> -#include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h>
#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/i2c/adv7511.h b/drivers/gpu/drm/i2c/adv7511.h index 38515b3..541c5d7 100644 --- a/drivers/gpu/drm/i2c/adv7511.h +++ b/drivers/gpu/drm/i2c/adv7511.h @@ -10,6 +10,10 @@ #define __DRM_I2C_ADV7511_H__
#include <linux/hdmi.h> +#include <linux/i2c.h> +#include <linux/regmap.h> + +#include <drm/drm_crtc_helper.h>
#define ADV7511_REG_CHIP_REVISION 0x00 #define ADV7511_REG_N0 0x01 @@ -286,4 +290,67 @@ 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/i2c/adv7533.c b/drivers/gpu/drm/i2c/adv7533.c new file mode 100644 index 0000000..cb4ca64 --- /dev/null +++ b/drivers/gpu/drm/i2c/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; +}
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 architt@codeaurora.org --- drivers/gpu/drm/i2c/Kconfig | 1 + drivers/gpu/drm/i2c/adv7511.c | 40 ++++++++++++++++--- drivers/gpu/drm/i2c/adv7511.h | 20 ++++++++++ drivers/gpu/drm/i2c/adv7533.c | 91 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 144 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 4cbbfd8..70ee29d 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -11,6 +11,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/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index e33702b..6586c52 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.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 lars@metafoo.de"); MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver"); diff --git a/drivers/gpu/drm/i2c/adv7511.h b/drivers/gpu/drm/i2c/adv7511.h index 541c5d7..056f747 100644 --- a/drivers/gpu/drm/i2c/adv7511.h +++ b/drivers/gpu/drm/i2c/adv7511.h @@ -14,6 +14,7 @@ #include <linux/regmap.h>
#include <drm/drm_crtc_helper.h> +#include <drm/drm_mipi_dsi.h>
#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) { @@ -351,6 +360,17 @@ 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/i2c/adv7533.c b/drivers/gpu/drm/i2c/adv7533.c index cb4ca64..ecbcaa0 100644 --- a/drivers/gpu/drm/i2c/adv7533.c +++ b/drivers/gpu/drm/i2c/adv7533.c @@ -11,6 +11,8 @@ * GNU General Public License for more details. */
+#include <linux/of_graph.h> + #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; +}
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 architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 2 ++ drivers/gpu/drm/i2c/adv7511.h | 3 +++ drivers/gpu/drm/i2c/adv7533.c | 60 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 63 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 6586c52..e0c353e 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.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/i2c/adv7511.h b/drivers/gpu/drm/i2c/adv7511.h index 056f747..a9745ea 100644 --- a/drivers/gpu/drm/i2c/adv7511.h +++ b/drivers/gpu/drm/i2c/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/i2c/adv7533.c b/drivers/gpu/drm/i2c/adv7533.c index ecbcaa0..d002ac4 100644 --- a/drivers/gpu/drm/i2c/adv7533.c +++ b/drivers/gpu/drm/i2c/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;
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 andy.green@linaro.org
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/i2c/adv7511.c | 3 +++ drivers/gpu/drm/i2c/adv7511.h | 5 +++++ drivers/gpu/drm/i2c/adv7533.c | 22 ++++++++++++++++++++++ 3 files changed, 30 insertions(+)
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index e0c353e..ec8fb2e 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.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/i2c/adv7511.h b/drivers/gpu/drm/i2c/adv7511.h index a9745ea..b113b77 100644 --- a/drivers/gpu/drm/i2c/adv7511.h +++ b/drivers/gpu/drm/i2c/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); @@ -352,6 +353,10 @@ static inline void adv7533_dsi_power_on(struct adv7511 *adv) 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/i2c/adv7533.c b/drivers/gpu/drm/i2c/adv7533.c index d002ac4..5eebd15 100644 --- a/drivers/gpu/drm/i2c/adv7533.c +++ b/drivers/gpu/drm/i2c/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,
Add description of ADV7533. Add the required and optional properties that are specific to it.
Cc: devicetree@vger.kernel.org
Acked-by: Rob Herring robh@kernel.org
Signed-off-by: Archit Taneja architt@codeaurora.org --- .../bindings/display/bridge/adi,adv7511.txt | 26 +++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-)
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
ADV7533 is a DSI to HDMI encoder chip. It's like ADV7511, but with an additional DSI RX block that takes in DSI video mode output.
This revision is quite similar to the previous version with some issues fixed.
Changes in v6: - v5 changed the adv7511 module name to adv75xx.ko, it was previously called adv7511.ko. This revision fixes the Makefile such that the module name doesn't change, and hence not break userspace. - Move the driver to the drivers/gpu/drm/bridge folder. This was planned to be done later, but I thought it would be better if we did it in this patchset itself since the driver is already converted to a bridge.
Changes in v5: - Fix break observed when built for x86. - Based off current drm-misc. - Removes best_encoder connector helper function since it isn't needed after this series: https://lkml.org/lkml/2016/6/2/508
Changes in v4: - Separated out build for ADV7533. The original plan was to stub out the drm_mipi_dsi funcs, but that seemed like an overkill since it helped just this driver. It seems better to stub out the ADV7533 functionality altogether instead. - Some minor DT binding corrections suggested by Laurent.
Archit Taneja (8): drm/i2c: adv7511: Convert to drm_bridge drm/i2c: adv7511: Move to bridge folder drm/bridge: adv7511: Fix mutex deadlock when interrupts are disabled drm/bridge: adv7533: Initial support for ADV7533 drm/bridge: adv7533: Create a MIPI DSI device drm/bridge: adv7533: Use internal timing generator drm/bridge: adv7533: Change number of DSI lanes dynamically dt-bindings: drm/bridge: Update bindings for ADV7533
.../bindings/display/bridge/adi,adv7511.txt | 26 +- drivers/gpu/drm/bridge/Kconfig | 2 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/adv7511/Kconfig | 15 + drivers/gpu/drm/bridge/adv7511/Makefile | 3 + drivers/gpu/drm/bridge/adv7511/adv7511.h | 392 +++++++ drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 1124 ++++++++++++++++++++ drivers/gpu/drm/bridge/adv7511/adv7533.c | 265 +++++ drivers/gpu/drm/i2c/Kconfig | 6 - drivers/gpu/drm/i2c/Makefile | 2 - drivers/gpu/drm/i2c/adv7511.c | 1024 ------------------ drivers/gpu/drm/i2c/adv7511.h | 289 ----- 12 files changed, 1823 insertions(+), 1326 deletions(-) create mode 100644 drivers/gpu/drm/bridge/adv7511/Kconfig create mode 100644 drivers/gpu/drm/bridge/adv7511/Makefile create mode 100644 drivers/gpu/drm/bridge/adv7511/adv7511.h create mode 100644 drivers/gpu/drm/bridge/adv7511/adv7511_drv.c create mode 100644 drivers/gpu/drm/bridge/adv7511/adv7533.c delete mode 100644 drivers/gpu/drm/i2c/adv7511.c delete mode 100644 drivers/gpu/drm/i2c/adv7511.h
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 architt@codeaurora.org --- drivers/gpu/drm/i2c/Kconfig | 1 + drivers/gpu/drm/i2c/adv7511.c | 213 +++++++++++++++++++++++++++--------------- 2 files changed, 140 insertions(+), 74 deletions(-)
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 <linux/slab.h>
#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> -#include <drm/drm_encoder_slave.h>
#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 lars@metafoo.de"); MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver");
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 architt@codeaurora.org --- v6: - New patch
drivers/gpu/drm/bridge/Kconfig | 2 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/adv7511/Kconfig | 7 + drivers/gpu/drm/bridge/adv7511/Makefile | 2 + drivers/gpu/drm/bridge/adv7511/adv7511.h | 289 +++++++ drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 1089 ++++++++++++++++++++++++++ drivers/gpu/drm/i2c/Kconfig | 7 - drivers/gpu/drm/i2c/Makefile | 2 - drivers/gpu/drm/i2c/adv7511.c | 1089 -------------------------- drivers/gpu/drm/i2c/adv7511.h | 289 ------- 10 files changed, 1390 insertions(+), 1387 deletions(-) create mode 100644 drivers/gpu/drm/bridge/adv7511/Kconfig create mode 100644 drivers/gpu/drm/bridge/adv7511/Makefile create mode 100644 drivers/gpu/drm/bridge/adv7511/adv7511.h create mode 100644 drivers/gpu/drm/bridge/adv7511/adv7511_drv.c delete mode 100644 drivers/gpu/drm/i2c/adv7511.c delete mode 100644 drivers/gpu/drm/i2c/adv7511.h
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 8f7423f..9ac6427 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -52,4 +52,6 @@ config DRM_PARADE_PS8622
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 96b13b3..94e7d2f 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -6,3 +6,4 @@ 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_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 <linux/hdmi.h> + +#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 <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> + +#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 lars@metafoo.de"); +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 <linux/device.h> -#include <linux/gpio/consumer.h> -#include <linux/i2c.h> -#include <linux/module.h> -#include <linux/regmap.h> -#include <linux/slab.h> - -#include <drm/drmP.h> -#include <drm/drm_atomic.h> -#include <drm/drm_atomic_helper.h> -#include <drm/drm_crtc_helper.h> -#include <drm/drm_edid.h> - -#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 lars@metafoo.de"); -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 <linux/hdmi.h> - -#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__ */
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 architt@codeaurora.org --- drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
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;
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 lars@metafoo.de
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/bridge/adv7511/Kconfig | 7 ++ drivers/gpu/drm/bridge/adv7511/Makefile | 1 + drivers/gpu/drm/bridge/adv7511/adv7511.h | 71 +++++++++++++++++++ drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 100 ++++++++++++++------------- drivers/gpu/drm/bridge/adv7511/adv7533.c | 100 +++++++++++++++++++++++++++ 5 files changed, 230 insertions(+), 49 deletions(-) create mode 100644 drivers/gpu/drm/bridge/adv7511/adv7533.c
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 <linux/hdmi.h> +#include <linux/i2c.h> +#include <linux/regmap.h> + +#include <drm/drm_crtc_helper.h>
#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 <linux/device.h> #include <linux/gpio/consumer.h> -#include <linux/i2c.h> #include <linux/module.h> -#include <linux/regmap.h> +#include <linux/of_device.h> #include <linux/slab.h>
#include <drm/drmP.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> -#include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h>
#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; +}
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 architt@codeaurora.org --- drivers/gpu/drm/bridge/adv7511/Kconfig | 1 + drivers/gpu/drm/bridge/adv7511/adv7511.h | 23 +++++++ drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 40 ++++++++++-- drivers/gpu/drm/bridge/adv7511/adv7533.c | 91 +++++++++++++++++++++++++++- 4 files changed, 147 insertions(+), 8 deletions(-)
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 <linux/regmap.h>
#include <drm/drm_crtc_helper.h> +#include <drm/drm_mipi_dsi.h>
#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 lars@metafoo.de"); 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 <linux/of_graph.h> + #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; +}
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 architt@codeaurora.org --- drivers/gpu/drm/bridge/adv7511/adv7511.h | 3 ++ drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 2 + drivers/gpu/drm/bridge/adv7511/adv7533.c | 60 +++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 2 deletions(-)
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;
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 andy.green@linaro.org
Signed-off-by: Archit Taneja architt@codeaurora.org --- drivers/gpu/drm/bridge/adv7511/adv7511.h | 6 ++++++ drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 3 +++ drivers/gpu/drm/bridge/adv7511/adv7533.c | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+)
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,
Add description of ADV7533. Add the required and optional properties that are specific to it.
Cc: devicetree@vger.kernel.org
Acked-by: Rob Herring robh@kernel.org
Signed-off-by: Archit Taneja architt@codeaurora.org --- .../bindings/display/bridge/adi,adv7511.txt | 26 +++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-)
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
dri-devel@lists.freedesktop.org