diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c index 725d7c18e65ef5..a98f7cc31551a2 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c @@ -63,6 +63,8 @@ struct atmel_hlcdc_crtc { struct drm_pending_vblank_event *event; int id; bool enabled; + bool simulate_vesa_sync; + bool invert_pixel_clock; }; static inline struct atmel_hlcdc_crtc * @@ -105,25 +107,6 @@ static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c) cfg = 0; - prate = clk_get_rate(crtc->dc->hlcdc->sys_clk); - mode_rate = adj->crtc_clock * 1000; - if ((prate / 2) < mode_rate) { - prate *= 2; - cfg |= ATMEL_HLCDC_CLKSEL; - } - - div = DIV_ROUND_UP(prate, mode_rate); - if (div < 2) - div = 2; - - cfg |= ATMEL_HLCDC_CLKDIV(div); - - regmap_update_bits(regmap, ATMEL_HLCDC_CFG(0), - ATMEL_HLCDC_CLKSEL | ATMEL_HLCDC_CLKDIV_MASK | - ATMEL_HLCDC_CLKPOL, cfg); - - cfg = 0; - if (adj->flags & DRM_MODE_FLAG_NVSYNC) cfg |= ATMEL_HLCDC_VSPOL; @@ -140,6 +123,32 @@ static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c) ATMEL_HLCDC_VSPSU | ATMEL_HLCDC_VSPHO | ATMEL_HLCDC_GUARDTIME_MASK | ATMEL_HLCDC_MODE_MASK, cfg); + + cfg = 0; + + prate = clk_get_rate(crtc->dc->hlcdc->sys_clk); + mode_rate = adj->crtc_clock * 1000; + + /* always use 2x system clock to reduce rounding error */ + prate *= 2; + cfg |= ATMEL_HLCDC_CLKSEL; + + div = DIV_ROUND_CLOSEST(prate, mode_rate); + if (div < 2) + div = 2; + + dev_info(c->dev->dev, "pixel clock: %d rounded to %d kHz\n", adj->crtc_clock, (int)(prate/div/1000)); + + cfg |= ATMEL_HLCDC_CLKDIV(div); + + if (crtc->invert_pixel_clock) + { + cfg |= ATMEL_HLCDC_CLKPOL; + } + + regmap_update_bits(regmap, ATMEL_HLCDC_CFG(0), + ATMEL_HLCDC_CLKSEL | ATMEL_HLCDC_CLKDIV_MASK | + ATMEL_HLCDC_CLKPOL, cfg); } static bool atmel_hlcdc_crtc_mode_fixup(struct drm_crtc *c, @@ -148,6 +157,24 @@ static bool atmel_hlcdc_crtc_mode_fixup(struct drm_crtc *c, { struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + if (crtc->simulate_vesa_sync) { + /* + * hlcdc does not generate VESA-compliant sync but aligns + * VS on the second edge of HS instead of first edge. + * We use adjusted_mode, to fixup sync by aligning both rising + * edges and add HSKEW offset to fix the sync. + */ + adjusted_mode->hskew = mode->hsync_end - mode->hsync_start; + adjusted_mode->flags |= DRM_MODE_FLAG_HSKEW; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) { + adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC; + adjusted_mode->flags &= ~DRM_MODE_FLAG_NHSYNC; + } else { + adjusted_mode->flags |= DRM_MODE_FLAG_NHSYNC; + adjusted_mode->flags &= ~DRM_MODE_FLAG_PHSYNC; + } + } return atmel_hlcdc_dc_mode_valid(crtc->dc, adjusted_mode) == MODE_OK; } @@ -503,3 +530,14 @@ int atmel_hlcdc_crtc_create(struct drm_device *dev) return ret; } +void atmel_hlcdc_crtc_set_simulate_vesa_sync(struct drm_crtc *c, bool enabled) +{ + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + crtc->simulate_vesa_sync = enabled; +} + +void atmel_hlcdc_crtc_set_invert_pixel_clock(struct drm_crtc *c, bool enabled) +{ + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + crtc->invert_pixel_clock = enabled; +} diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c index 8aa856d6a15b86..6efe620a5fdbd9 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c @@ -550,10 +550,12 @@ static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev) drm_mode_config_init(dev); - ret = atmel_hlcdc_create_outputs(dev); - if (ret) { - dev_err(dev->dev, "failed to create HLCDC outputs: %d\n", ret); - return ret; + if (!dc->is_componentized) { + ret = atmel_hlcdc_create_outputs(dev); + if (ret) { + dev_err(dev->dev, "failed to create HLCDC outputs: %d\n", ret); + return ret; + } } planes = atmel_hlcdc_create_planes(dev); @@ -595,6 +597,7 @@ static int atmel_hlcdc_dc_load(struct drm_device *dev) struct platform_device *pdev = to_platform_device(dev->dev); const struct of_device_id *match; struct atmel_hlcdc_dc *dc; + struct drm_crtc *crtc; int ret; match = of_match_node(atmel_hlcdc_of_match, dev->dev->parent->of_node); @@ -619,6 +622,7 @@ static int atmel_hlcdc_dc_load(struct drm_device *dev) init_waitqueue_head(&dc->commit.wait); dc->desc = match->data; dc->hlcdc = dev_get_drvdata(dev->dev->parent); + dc->is_componentized = atmel_hlcdc_get_external_components(dev->dev, NULL) > 0; dev->dev_private = dc; ret = clk_prepare_enable(dc->hlcdc->periph_clk); @@ -641,6 +645,26 @@ static int atmel_hlcdc_dc_load(struct drm_device *dev) goto err_periph_clk_disable; } + if (dc->is_componentized) { + ret = component_bind_all(dev->dev, dev); + if (ret < 0) + { + dev_err(dev->dev, "failed to bind components: %d\n", ret); + goto err_periph_clk_disable; + } + } + + if (of_property_read_bool(dev->dev->of_node, "simulate_vesa_sync")) { + /* enable simulate_vesa_sync */ + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) + atmel_hlcdc_crtc_set_simulate_vesa_sync(crtc, true); + } + if (of_property_read_bool(dev->dev->of_node, "invert_pixel_clock")) { + /* set clock_polarity */ + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) + atmel_hlcdc_crtc_set_invert_pixel_clock(crtc, true); + } + drm_mode_config_reset(dev); pm_runtime_get_sync(dev->dev); @@ -678,6 +702,8 @@ static void atmel_hlcdc_dc_unload(struct drm_device *dev) drm_fbdev_cma_fini(dc->fbdev); flush_workqueue(dc->wq); drm_kms_helper_poll_fini(dev); + if (dc->is_componentized) + component_unbind_all(dev->dev, dev); drm_mode_config_cleanup(dev); drm_vblank_cleanup(dev); @@ -874,12 +900,13 @@ static struct drm_driver atmel_hlcdc_dc_driver = { .minor = 0, }; -static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev) +static int atmel_hlcdc_dc_drm_bind(struct device *dev) { struct drm_device *ddev; + struct atmel_hlcdc_dc *dc; int ret; - ddev = drm_dev_alloc(&atmel_hlcdc_dc_driver, &pdev->dev); + ddev = drm_dev_alloc(&atmel_hlcdc_dc_driver, dev); if (!ddev) return -ENOMEM; @@ -887,26 +914,30 @@ static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev) if (ret) goto err_unref; - ret = atmel_hlcdc_dc_load(ddev); - if (ret) - goto err_unref; - ret = drm_dev_register(ddev, 0); if (ret) - goto err_unload; + goto err_unref; - ret = atmel_hlcdc_dc_connector_plug_all(ddev); + ret = atmel_hlcdc_dc_load(ddev); if (ret) goto err_unregister; + dc = ddev->dev_private; + if (!dc->is_componentized) { + ret = atmel_hlcdc_dc_connector_plug_all(ddev); + if (ret) + goto err_unload; + } + dev_info(ddev->dev, "DRM device successfully registered\n"); return 0; +err_unload: + atmel_hlcdc_dc_unload(ddev); + err_unregister: drm_dev_unregister(ddev); -err_unload: - atmel_hlcdc_dc_unload(ddev); err_unref: drm_dev_unref(ddev); @@ -914,16 +945,16 @@ static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev) return ret; } -static int atmel_hlcdc_dc_drm_remove(struct platform_device *pdev) +static void atmel_hlcdc_dc_drm_unbind(struct device *dev) { - struct drm_device *ddev = platform_get_drvdata(pdev); + struct drm_device *ddev = dev_get_drvdata(dev); + struct atmel_hlcdc_dc *dc = ddev->dev_private; - atmel_hlcdc_dc_connector_unplug_all(ddev); + if (!dc->is_componentized) + atmel_hlcdc_dc_connector_unplug_all(ddev); drm_dev_unregister(ddev); atmel_hlcdc_dc_unload(ddev); drm_dev_unref(ddev); - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -958,6 +989,50 @@ static int atmel_hlcdc_dc_drm_resume(struct device *dev) } #endif +static const struct component_master_ops hlcdc_comp_ops = { + .bind = atmel_hlcdc_dc_drm_bind, + .unbind = atmel_hlcdc_dc_drm_unbind, +}; + +static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct component_match *match = NULL; + int ret; + + /* bail out early if no DT data: */ + if (!dev->of_node) { + dev_err(dev, "device-tree data is missing\n"); + return -ENXIO; + } + + /* find components, if none found proceed to bind */ + ret = atmel_hlcdc_get_external_components(dev, &match); + if (ret < 0) + return ret; + else if (ret == 0) + return atmel_hlcdc_dc_drm_bind(dev); + else + return component_master_add_with_match(dev, &hlcdc_comp_ops, match); +} + +static int atmel_hlcdc_dc_drm_remove(struct platform_device *pdev) +{ + struct drm_device *ddev = dev_get_drvdata(&pdev->dev); + struct atmel_hlcdc_dc *dc = ddev->dev_private; + + /* Check if a subcomponent has already triggered the unloading. */ + if (!dc) + return 0; + + if (dc->is_componentized) + component_master_del(&pdev->dev, &hlcdc_comp_ops); + else + drm_put_dev(platform_get_drvdata(pdev)); + + return 0; +} + static SIMPLE_DEV_PM_OPS(atmel_hlcdc_dc_drm_pm_ops, atmel_hlcdc_dc_drm_suspend, atmel_hlcdc_dc_drm_resume); diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h index 31bd002390e23d..c6fb5f2e43634e 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -151,6 +152,7 @@ struct atmel_hlcdc_dc { wait_queue_head_t wait; bool pending; } commit; + bool is_componentized; }; extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats; @@ -174,7 +176,10 @@ void atmel_hlcdc_crtc_suspend(struct drm_crtc *crtc); void atmel_hlcdc_crtc_resume(struct drm_crtc *crtc); int atmel_hlcdc_crtc_create(struct drm_device *dev); +void atmel_hlcdc_crtc_set_simulate_vesa_sync(struct drm_crtc *c, bool enabled); +void atmel_hlcdc_crtc_set_invert_pixel_clock(struct drm_crtc *c, bool enabled); int atmel_hlcdc_create_outputs(struct drm_device *dev); +int atmel_hlcdc_get_external_components(struct device *dev, struct component_match **match); #endif /* DRM_ATMEL_HLCDC_H */ diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c index ccb2d7828e5bcd..ea3e82408dd465 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c @@ -294,3 +294,38 @@ int atmel_hlcdc_create_outputs(struct drm_device *dev) return 0; } + +static int dev_match_of(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +int atmel_hlcdc_get_external_components(struct device *dev, + struct component_match **match) +{ + struct device_node *ep = NULL; + int count = 0; + + while ((ep = of_graph_get_next_endpoint(dev->of_node, ep))) { + struct device_node *node; + + node = of_graph_get_remote_port_parent(ep); + if (!node && !of_device_is_available(node)) { + of_node_put(node); + continue; + } + + dev_dbg(dev, "Subdevice node '%s' found\n", node->name); + if (match) + component_match_add(dev, match, dev_match_of, node); + of_node_put(node); + count++; + } + + if (count > 1) { + dev_err(dev, "Only one external encoder is supported\n"); + return -EINVAL; + } + + return count; +} diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index 896b6aaf8c4d0e..06d08dc745cca0 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -878,7 +879,10 @@ tda998x_encoder_mode_fixup(struct drm_encoder *encoder, static int tda998x_connector_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode) { - if (mode->clock > 150000) + /* TDA19988 dotclock can go up to 165MHz */ + struct tda998x_priv *priv = conn_to_tda998x_priv(connector); + + if (mode->clock > ((priv->rev == TDA19988) ? 165000 : 150000)) return MODE_CLOCK_HIGH; if (mode->htotal >= BIT(13)) return MODE_BAD_HVALUE; @@ -1149,6 +1153,8 @@ static int tda998x_connector_get_modes(struct drm_connector *connector) { struct tda998x_priv *priv = conn_to_tda998x_priv(connector); struct edid *edid; + u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + int ret; int n; /* @@ -1177,6 +1183,10 @@ static int tda998x_connector_get_modes(struct drm_connector *connector) priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid); kfree(edid); + ret = drm_display_info_set_bus_formats(&connector->display_info, &bus_format, 1); + if (ret) + return ret; + return n; } @@ -1392,11 +1402,24 @@ static void tda998x_connector_destroy(struct drm_connector *connector) drm_connector_cleanup(connector); } +static int tda998x_connector_dpms(struct drm_connector *connector, int mode) +{ + DRM_DEBUG_DRIVER("connector_dpms: %d\n", mode); + + if (drm_core_check_feature(connector->dev, DRIVER_ATOMIC)) + return drm_atomic_helper_connector_dpms(connector, mode); + else + return drm_helper_connector_dpms(connector, mode); +} + static const struct drm_connector_funcs tda998x_connector_funcs = { - .dpms = drm_helper_connector_dpms, + .dpms = tda998x_connector_dpms, + .reset = drm_atomic_helper_connector_reset, .fill_modes = drm_helper_probe_single_connector_modes, .detect = tda998x_connector_detect, .destroy = tda998x_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; static int tda998x_bind(struct device *dev, struct device *master, void *data) @@ -1453,7 +1476,6 @@ static int tda998x_bind(struct device *dev, struct device *master, void *data) if (ret) goto err_sysfs; - priv->connector.encoder = &priv->encoder; drm_mode_connector_attach_encoder(&priv->connector, &priv->encoder); return 0; @@ -1472,6 +1494,7 @@ static void tda998x_unbind(struct device *dev, struct device *master, { struct tda998x_priv *priv = dev_get_drvdata(dev); + drm_connector_unregister(&priv->connector); drm_connector_cleanup(&priv->connector); drm_encoder_cleanup(&priv->encoder); tda998x_destroy(priv);