summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Airlie <airlied@redhat.com>2016-12-01 09:26:55 +1000
committerDave Airlie <airlied@redhat.com>2016-12-01 09:26:55 +1000
commit0d5320fc194128a1a584a7e91a606cb3af2ded80 (patch)
tree2f97ce75bced0122f1689d4d65f058c5dfc04d7d
parentf5590134365f6f23dba723f140f72effcc71773f (diff)
parent0186fcce896d3cb6fb690ed8b4405c9c1b76977a (diff)
downloadlinux-0d5320fc194128a1a584a7e91a606cb3af2ded80.tar.gz
linux-0d5320fc194128a1a584a7e91a606cb3af2ded80.tar.bz2
linux-0d5320fc194128a1a584a7e91a606cb3af2ded80.zip
Merge tag 'tilcdc-4.10' of https://github.com/jsarha/linux into drm-next
tilcdc changes for v4.10 * tag 'tilcdc-4.10' of https://github.com/jsarha/linux: (23 commits) drm/tilcdc: fix parsing of some DT properties drm/tilcdc: Enable frame done irq and functionality for LCDC rev 1 drm/tilcdc: Configure video mode to HW in enable() not in mode_set_nofb() drm/tilcdc: Load palette at the end of mode_set_nofb() drm/tilcdc: Add timeout wait for palette loading to complete drm/tilcdc: Enable palette loading for revision 2 LCDC too drm/tilcdc: Fix load mode bit-field setting in tilcdc_crtc_enable() drm/tilcdc: Add tilcdc_write_mask() to tilcdc_regs.h drm/tilcdc: Fix tilcdc_crtc_create() return value handling drm/tilcdc: implement palette loading for rev1 drm/tilcdc: Enable sync lost error and recovery handling for rev 1 LCDC drm/tilcdc: Add drm bridge support for attaching drm bridge drivers drm/bridge: Add ti-tfp410 DVI transmitter driver dt-bindings: Move "ti,tfp410.txt" from display/ti to display/bridge drm/tilcdc: Recover from sync lost error flood by resetting the LCDC drm/tilcdc: Fix race from forced shutdown of crtc in unload drm/tilcdc: Use unload to handle initialization failures drm/tilcdc: Stop using struct drm_driver load() callback drm/tilcdc: Remove obsolete drm_connector_register() calls drm/tilcdc: Correct misspelling in error message ...
-rw-r--r--Documentation/devicetree/bindings/display/bridge/ti,tfp410.txt (renamed from Documentation/devicetree/bindings/display/ti/ti,tfp410.txt)9
-rw-r--r--Documentation/devicetree/bindings/display/tilcdc/tilcdc.txt6
-rw-r--r--drivers/gpu/drm/bridge/Kconfig7
-rw-r--r--drivers/gpu/drm/bridge/Makefile1
-rw-r--r--drivers/gpu/drm/bridge/ti-tfp410.c317
-rw-r--r--drivers/gpu/drm/tilcdc/tilcdc_crtc.c580
-rw-r--r--drivers/gpu/drm/tilcdc/tilcdc_drv.c210
-rw-r--r--drivers/gpu/drm/tilcdc/tilcdc_drv.h11
-rw-r--r--drivers/gpu/drm/tilcdc/tilcdc_external.c260
-rw-r--r--drivers/gpu/drm/tilcdc/tilcdc_external.h5
-rw-r--r--drivers/gpu/drm/tilcdc/tilcdc_panel.c2
-rw-r--r--drivers/gpu/drm/tilcdc/tilcdc_regs.h15
-rw-r--r--drivers/gpu/drm/tilcdc/tilcdc_tfp410.c2
13 files changed, 1031 insertions, 394 deletions
diff --git a/Documentation/devicetree/bindings/display/ti/ti,tfp410.txt b/Documentation/devicetree/bindings/display/bridge/ti,tfp410.txt
index 2cbe32a3d0bb..54d7e31525ec 100644
--- a/Documentation/devicetree/bindings/display/ti/ti,tfp410.txt
+++ b/Documentation/devicetree/bindings/display/bridge/ti,tfp410.txt
@@ -6,10 +6,15 @@ Required properties:
Optional properties:
- powerdown-gpios: power-down gpio
+- reg: I2C address. If and only if present the device node
+ should be placed into the i2c controller node where the
+ tfp410 i2c is connected to.
Required nodes:
-- Video port 0 for DPI input
-- Video port 1 for DVI output
+- Video port 0 for DPI input [1].
+- Video port 1 for DVI output [1].
+
+[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
Example
-------
diff --git a/Documentation/devicetree/bindings/display/tilcdc/tilcdc.txt b/Documentation/devicetree/bindings/display/tilcdc/tilcdc.txt
index a83abd79c55c..6fddb4f4f71a 100644
--- a/Documentation/devicetree/bindings/display/tilcdc/tilcdc.txt
+++ b/Documentation/devicetree/bindings/display/tilcdc/tilcdc.txt
@@ -1,7 +1,9 @@
Device-Tree bindings for tilcdc DRM driver
Required properties:
- - compatible: value should be "ti,am33xx-tilcdc".
+ - compatible: value should be one of the following:
+ - "ti,am33xx-tilcdc" for AM335x based boards
+ - "ti,da850-tilcdc" for DA850/AM18x/OMAP-L138 based boards
- interrupts: the interrupt number
- reg: base address and size of the LCDC device
@@ -51,7 +53,7 @@ Optional nodes:
Example:
fb: fb@4830e000 {
- compatible = "ti,am33xx-tilcdc";
+ compatible = "ti,am33xx-tilcdc", "ti,da850-tilcdc";
reg = <0x4830e000 0x1000>;
interrupt-parent = <&intc>;
interrupts = <36>;
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 29755bc32aab..eb8688ec6f18 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -90,6 +90,13 @@ config DRM_TOSHIBA_TC358767
---help---
Toshiba TC358767 eDP bridge chip driver.
+config DRM_TI_TFP410
+ tristate "TI TFP410 DVI/HDMI bridge"
+ depends on OF
+ select DRM_KMS_HELPER
+ ---help---
+ Texas Instruments TFP410 DVI/HDMI Transmitter driver
+
source "drivers/gpu/drm/bridge/analogix/Kconfig"
source "drivers/gpu/drm/bridge/adv7511/Kconfig"
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 1439ad84ccb7..2e83a7855399 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -12,3 +12,4 @@ obj-$(CONFIG_DRM_SII902X) += sii902x.o
obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
+obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c
new file mode 100644
index 000000000000..b054ea349952
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ti-tfp410.c
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2016 Texas Instruments
+ * Author: Jyri Sarha <jsarha@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+
+struct tfp410 {
+ struct drm_bridge bridge;
+ struct drm_connector connector;
+
+ struct i2c_adapter *ddc;
+
+ struct device *dev;
+};
+
+static inline struct tfp410 *
+drm_bridge_to_tfp410(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct tfp410, bridge);
+}
+
+static inline struct tfp410 *
+drm_connector_to_tfp410(struct drm_connector *connector)
+{
+ return container_of(connector, struct tfp410, connector);
+}
+
+static int tfp410_get_modes(struct drm_connector *connector)
+{
+ struct tfp410 *dvi = drm_connector_to_tfp410(connector);
+ struct edid *edid;
+ int ret;
+
+ if (!dvi->ddc)
+ goto fallback;
+
+ edid = drm_get_edid(connector, dvi->ddc);
+ if (!edid) {
+ DRM_INFO("EDID read failed. Fallback to standard modes\n");
+ goto fallback;
+ }
+
+ drm_mode_connector_update_edid_property(connector, edid);
+
+ return drm_add_edid_modes(connector, edid);
+fallback:
+ /* No EDID, fallback on the XGA standard modes */
+ ret = drm_add_modes_noedid(connector, 1920, 1200);
+
+ /* And prefer a mode pretty much anything can handle */
+ drm_set_preferred_mode(connector, 1024, 768);
+
+ return ret;
+}
+
+static const struct drm_connector_helper_funcs tfp410_con_helper_funcs = {
+ .get_modes = tfp410_get_modes,
+};
+
+static enum drm_connector_status
+tfp410_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct tfp410 *dvi = drm_connector_to_tfp410(connector);
+
+ if (dvi->ddc) {
+ if (drm_probe_ddc(dvi->ddc))
+ return connector_status_connected;
+ else
+ return connector_status_disconnected;
+ }
+
+ return connector_status_unknown;
+}
+
+static const struct drm_connector_funcs tfp410_con_funcs = {
+ .dpms = drm_atomic_helper_connector_dpms,
+ .detect = tfp410_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .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,
+};
+
+static int tfp410_attach(struct drm_bridge *bridge)
+{
+ struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
+ int ret;
+
+ if (!bridge->encoder) {
+ dev_err(dvi->dev, "Missing encoder\n");
+ return -ENODEV;
+ }
+
+ drm_connector_helper_add(&dvi->connector,
+ &tfp410_con_helper_funcs);
+ ret = drm_connector_init(bridge->dev, &dvi->connector,
+ &tfp410_con_funcs, DRM_MODE_CONNECTOR_HDMIA);
+ if (ret) {
+ dev_err(dvi->dev, "drm_connector_init() failed: %d\n", ret);
+ return ret;
+ }
+
+ drm_mode_connector_attach_encoder(&dvi->connector,
+ bridge->encoder);
+
+ return 0;
+}
+
+static const struct drm_bridge_funcs tfp410_bridge_funcs = {
+ .attach = tfp410_attach,
+};
+
+static int tfp410_get_connector_ddc(struct tfp410 *dvi)
+{
+ struct device_node *ep = NULL, *connector_node = NULL;
+ struct device_node *ddc_phandle = NULL;
+ int ret = 0;
+
+ /* port@1 is the connector node */
+ ep = of_graph_get_endpoint_by_regs(dvi->dev->of_node, 1, -1);
+ if (!ep)
+ goto fail;
+
+ connector_node = of_graph_get_remote_port_parent(ep);
+ if (!connector_node)
+ goto fail;
+
+ ddc_phandle = of_parse_phandle(connector_node, "ddc-i2c-bus", 0);
+ if (!ddc_phandle)
+ goto fail;
+
+ dvi->ddc = of_get_i2c_adapter_by_node(ddc_phandle);
+ if (dvi->ddc)
+ dev_info(dvi->dev, "Connector's ddc i2c bus found\n");
+ else
+ ret = -EPROBE_DEFER;
+
+fail:
+ of_node_put(ep);
+ of_node_put(connector_node);
+ of_node_put(ddc_phandle);
+ return ret;
+}
+
+static int tfp410_init(struct device *dev)
+{
+ struct tfp410 *dvi;
+ int ret;
+
+ if (!dev->of_node) {
+ dev_err(dev, "device-tree data is missing\n");
+ return -ENXIO;
+ }
+
+ dvi = devm_kzalloc(dev, sizeof(*dvi), GFP_KERNEL);
+ if (!dvi)
+ return -ENOMEM;
+ dev_set_drvdata(dev, dvi);
+
+ dvi->bridge.funcs = &tfp410_bridge_funcs;
+ dvi->bridge.of_node = dev->of_node;
+ dvi->dev = dev;
+
+ ret = tfp410_get_connector_ddc(dvi);
+ if (ret)
+ goto fail;
+
+ ret = drm_bridge_add(&dvi->bridge);
+ if (ret) {
+ dev_err(dev, "drm_bridge_add() failed: %d\n", ret);
+ goto fail;
+ }
+
+ return 0;
+fail:
+ i2c_put_adapter(dvi->ddc);
+ return ret;
+}
+
+static int tfp410_fini(struct device *dev)
+{
+ struct tfp410 *dvi = dev_get_drvdata(dev);
+
+ drm_bridge_remove(&dvi->bridge);
+
+ if (dvi->ddc)
+ i2c_put_adapter(dvi->ddc);
+
+ return 0;
+}
+
+static int tfp410_probe(struct platform_device *pdev)
+{
+ return tfp410_init(&pdev->dev);
+}
+
+static int tfp410_remove(struct platform_device *pdev)
+{
+ return tfp410_fini(&pdev->dev);
+}
+
+static const struct of_device_id tfp410_match[] = {
+ { .compatible = "ti,tfp410" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tfp410_match);
+
+struct platform_driver tfp410_platform_driver = {
+ .probe = tfp410_probe,
+ .remove = tfp410_remove,
+ .driver = {
+ .name = "tfp410-bridge",
+ .of_match_table = tfp410_match,
+ },
+};
+
+#if IS_ENABLED(CONFIG_I2C)
+/* There is currently no i2c functionality. */
+static int tfp410_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int reg;
+
+ if (!client->dev.of_node ||
+ of_property_read_u32(client->dev.of_node, "reg", &reg)) {
+ dev_err(&client->dev,
+ "Can't get i2c reg property from device-tree\n");
+ return -ENXIO;
+ }
+
+ return tfp410_init(&client->dev);
+}
+
+static int tfp410_i2c_remove(struct i2c_client *client)
+{
+ return tfp410_fini(&client->dev);
+}
+
+static const struct i2c_device_id tfp410_i2c_ids[] = {
+ { "tfp410", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tfp410_i2c_ids);
+
+static struct i2c_driver tfp410_i2c_driver = {
+ .driver = {
+ .name = "tfp410",
+ .of_match_table = of_match_ptr(tfp410_match),
+ },
+ .id_table = tfp410_i2c_ids,
+ .probe = tfp410_i2c_probe,
+ .remove = tfp410_i2c_remove,
+};
+#endif /* IS_ENABLED(CONFIG_I2C) */
+
+static struct {
+ uint i2c:1;
+ uint platform:1;
+} tfp410_registered_driver;
+
+static int __init tfp410_module_init(void)
+{
+ int ret;
+
+#if IS_ENABLED(CONFIG_I2C)
+ ret = i2c_add_driver(&tfp410_i2c_driver);
+ if (ret)
+ pr_err("%s: registering i2c driver failed: %d",
+ __func__, ret);
+ else
+ tfp410_registered_driver.i2c = 1;
+#endif
+
+ ret = platform_driver_register(&tfp410_platform_driver);
+ if (ret)
+ pr_err("%s: registering platform driver failed: %d",
+ __func__, ret);
+ else
+ tfp410_registered_driver.platform = 1;
+
+ if (tfp410_registered_driver.i2c ||
+ tfp410_registered_driver.platform)
+ return 0;
+
+ return ret;
+}
+module_init(tfp410_module_init);
+
+static void __exit tfp410_module_exit(void)
+{
+#if IS_ENABLED(CONFIG_I2C)
+ if (tfp410_registered_driver.i2c)
+ i2c_del_driver(&tfp410_i2c_driver);
+#endif
+ if (tfp410_registered_driver.platform)
+ platform_driver_unregister(&tfp410_platform_driver);
+}
+module_exit(tfp410_module_exit);
+
+MODULE_AUTHOR("Jyri Sarha <jsarha@ti.com>");
+MODULE_DESCRIPTION("TI TFP410 DVI bridge driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
index 822531ebd4b0..9942b0577d6e 100644
--- a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
+++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
@@ -21,11 +21,15 @@
#include <drm/drm_flip_work.h>
#include <drm/drm_plane_helper.h>
#include <linux/workqueue.h>
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
#include "tilcdc_drv.h"
#include "tilcdc_regs.h"
-#define TILCDC_VBLANK_SAFETY_THRESHOLD_US 1000
+#define TILCDC_VBLANK_SAFETY_THRESHOLD_US 1000
+#define TILCDC_PALETTE_SIZE 32
+#define TILCDC_PALETTE_FIRST_ENTRY 0x4000
struct tilcdc_crtc {
struct drm_crtc base;
@@ -33,7 +37,9 @@ struct tilcdc_crtc {
struct drm_plane primary;
const struct tilcdc_panel_info *info;
struct drm_pending_vblank_event *event;
+ struct mutex enable_lock;
bool enabled;
+ bool shutdown;
wait_queue_head_t frame_done_wq;
bool frame_done;
spinlock_t irq_lock;
@@ -53,6 +59,11 @@ struct tilcdc_crtc {
int sync_lost_count;
bool frame_intact;
+ struct work_struct recover_work;
+
+ dma_addr_t palette_dma_handle;
+ u16 *palette_base;
+ struct completion palette_loaded;
};
#define to_tilcdc_crtc(x) container_of(x, struct tilcdc_crtc, base)
@@ -71,6 +82,7 @@ static void set_scanout(struct drm_crtc *crtc, struct drm_framebuffer *fb)
{
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
struct drm_device *dev = crtc->dev;
+ struct tilcdc_drm_private *priv = dev->dev_private;
struct drm_gem_cma_object *gem;
dma_addr_t start, end;
u64 dma_base_and_ceiling;
@@ -88,7 +100,10 @@ static void set_scanout(struct drm_crtc *crtc, struct drm_framebuffer *fb)
* unlikely that LCDC would fetch the DMA addresses in the middle of
* an update.
*/
- dma_base_and_ceiling = (u64)(end - 1) << 32 | start;
+ if (priv->rev == 1)
+ end -= 1;
+
+ dma_base_and_ceiling = (u64)end << 32 | start;
tilcdc_write64(dev, LCDC_DMA_FB_BASE_ADDR_0_REG, dma_base_and_ceiling);
if (tilcdc_crtc->curr_fb)
@@ -98,6 +113,56 @@ static void set_scanout(struct drm_crtc *crtc, struct drm_framebuffer *fb)
tilcdc_crtc->curr_fb = fb;
}
+/*
+ * The driver currently only supports only true color formats. For
+ * true color the palette block is bypassed, but a 32 byte palette
+ * should still be loaded. The first 16-bit entry must be 0x4000 while
+ * all other entries must be zeroed.
+ */
+static void tilcdc_crtc_load_palette(struct drm_crtc *crtc)
+{
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ int ret;
+
+ reinit_completion(&tilcdc_crtc->palette_loaded);
+
+ /* Tell the LCDC where the palette is located. */
+ tilcdc_write(dev, LCDC_DMA_FB_BASE_ADDR_0_REG,
+ tilcdc_crtc->palette_dma_handle);
+ tilcdc_write(dev, LCDC_DMA_FB_CEILING_ADDR_0_REG,
+ (u32) tilcdc_crtc->palette_dma_handle +
+ TILCDC_PALETTE_SIZE - 1);
+
+ /* Set dma load mode for palette loading only. */
+ tilcdc_write_mask(dev, LCDC_RASTER_CTRL_REG,
+ LCDC_PALETTE_LOAD_MODE(PALETTE_ONLY),
+ LCDC_PALETTE_LOAD_MODE_MASK);
+
+ /* Enable DMA Palette Loaded Interrupt */
+ if (priv->rev == 1)
+ tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_V1_PL_INT_ENA);
+ else
+ tilcdc_write(dev, LCDC_INT_ENABLE_SET_REG, LCDC_V2_PL_INT_ENA);
+
+ /* Enable LCDC DMA and wait for palette to be loaded. */
+ tilcdc_clear_irqstatus(dev, 0xffffffff);
+ tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
+
+ ret = wait_for_completion_timeout(&tilcdc_crtc->palette_loaded,
+ msecs_to_jiffies(50));
+ if (ret == 0)
+ dev_err(dev->dev, "%s: Palette loading timeout", __func__);
+
+ /* Disable LCDC DMA and DMA Palette Loaded Interrupt. */
+ tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
+ if (priv->rev == 1)
+ tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_V1_PL_INT_ENA);
+ else
+ tilcdc_write(dev, LCDC_INT_ENABLE_CLR_REG, LCDC_V2_PL_INT_ENA);
+}
+
static void tilcdc_crtc_enable_irqs(struct drm_device *dev)
{
struct tilcdc_drm_private *priv = dev->dev_private;
@@ -106,6 +171,7 @@ static void tilcdc_crtc_enable_irqs(struct drm_device *dev)
if (priv->rev == 1) {
tilcdc_set(dev, LCDC_RASTER_CTRL_REG,
+ LCDC_V1_SYNC_LOST_INT_ENA | LCDC_V1_FRAME_DONE_INT_ENA |
LCDC_V1_UNDERFLOW_INT_ENA);
tilcdc_set(dev, LCDC_DMA_CTRL_REG,
LCDC_V1_END_OF_FRAME_INT_ENA);
@@ -124,6 +190,7 @@ static void tilcdc_crtc_disable_irqs(struct drm_device *dev)
/* disable irqs that we might have enabled: */
if (priv->rev == 1) {
tilcdc_clear(dev, LCDC_RASTER_CTRL_REG,
+ LCDC_V1_SYNC_LOST_INT_ENA | LCDC_V1_FRAME_DONE_INT_ENA |
LCDC_V1_UNDERFLOW_INT_ENA | LCDC_V1_PL_INT_ENA);
tilcdc_clear(dev, LCDC_DMA_CTRL_REG,
LCDC_V1_END_OF_FRAME_INT_ENA);
@@ -148,193 +215,68 @@ static void reset(struct drm_crtc *crtc)
tilcdc_clear(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
}
-static void tilcdc_crtc_enable(struct drm_crtc *crtc)
+/*
+ * Calculate the percentage difference between the requested pixel clock rate
+ * and the effective rate resulting from calculating the clock divider value.
+ */
+static unsigned int tilcdc_pclk_diff(unsigned long rate,
+ unsigned long real_rate)
{
- struct drm_device *dev = crtc->dev;
- struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
-
- WARN_ON(!drm_modeset_is_locked(&crtc->mutex));
-
- if (tilcdc_crtc->enabled)
- return;
-
- pm_runtime_get_sync(dev->dev);
-
- reset(crtc);
-
- tilcdc_crtc_enable_irqs(dev);
-
- tilcdc_clear(dev, LCDC_DMA_CTRL_REG, LCDC_DUAL_FRAME_BUFFER_ENABLE);
- tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_PALETTE_LOAD_MODE(DATA_ONLY));
- tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
-
- drm_crtc_vblank_on(crtc);
+ int r = rate / 100, rr = real_rate / 100;
- tilcdc_crtc->enabled = true;
+ return (unsigned int)(abs(((rr - r) * 100) / r));
}
-void tilcdc_crtc_disable(struct drm_crtc *crtc)
+static void tilcdc_crtc_set_clk(struct drm_crtc *crtc)
{
- struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
struct drm_device *dev = crtc->dev;
struct tilcdc_drm_private *priv = dev->dev_private;
-
- WARN_ON(!drm_modeset_is_locked(&crtc->mutex));
-
- if (!tilcdc_crtc->enabled)
- return;
-
- tilcdc_crtc->frame_done = false;
- tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
-
- /*
- * if necessary wait for framedone irq which will still come
- * before putting things to sleep..
- */
- if (priv->rev == 2) {
- int ret = wait_event_timeout(tilcdc_crtc->frame_done_wq,
- tilcdc_crtc->frame_done,
- msecs_to_jiffies(500));
- if (ret == 0)
- dev_err(dev->dev, "%s: timeout waiting for framedone\n",
- __func__);
- }
-
- drm_crtc_vblank_off(crtc);
-
- tilcdc_crtc_disable_irqs(dev);
-
- pm_runtime_put_sync(dev->dev);
-
- if (tilcdc_crtc->next_fb) {
- drm_flip_work_queue(&tilcdc_crtc->unref_work,
- tilcdc_crtc->next_fb);
- tilcdc_crtc->next_fb = NULL;
- }
-
- if (tilcdc_crtc->curr_fb) {
- drm_flip_work_queue(&tilcdc_crtc->unref_work,
- tilcdc_crtc->curr_fb);
- tilcdc_crtc->curr_fb = NULL;
- }
-
- drm_flip_work_commit(&tilcdc_crtc->unref_work, priv->wq);
- tilcdc_crtc->last_vblank = ktime_set(0, 0);
-
- tilcdc_crtc->enabled = false;
-}
-
-static bool tilcdc_crtc_is_on(struct drm_crtc *crtc)
-{
- return crtc->state && crtc->state->enable && crtc->state->active;
-}
-
-static void tilcdc_crtc_destroy(struct drm_crtc *crtc)
-{
- struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
- struct tilcdc_drm_private *priv = crtc->dev->dev_private;
-
- drm_modeset_lock_crtc(crtc, NULL);
- tilcdc_crtc_disable(crtc);
- drm_modeset_unlock_crtc(crtc);
-
- flush_workqueue(priv->wq);
-
- of_node_put(crtc->port);
- drm_crtc_cleanup(crtc);
- drm_flip_work_cleanup(&tilcdc_crtc->unref_work);
-}
-
-int tilcdc_crtc_update_fb(struct drm_crtc *crtc,
- struct drm_framebuffer *fb,
- struct drm_pending_vblank_event *event)
-{
- struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
- struct drm_device *dev = crtc->dev;
- unsigned long flags;
-
- WARN_ON(!drm_modeset_is_locked(&crtc->mutex));
-
- if (tilcdc_crtc->event) {
- dev_err(dev->dev, "already pending page flip!\n");
- return -EBUSY;
- }
-
- drm_framebuffer_reference(fb);
-
- crtc->primary->fb = fb;
-
- spin_lock_irqsave(&tilcdc_crtc->irq_lock, flags);
-
- if (crtc->hwmode.vrefresh && ktime_to_ns(tilcdc_crtc->last_vblank)) {
- ktime_t next_vblank;
- s64 tdiff;
-
- next_vblank = ktime_add_us(tilcdc_crtc->last_vblank,
- 1000000 / crtc->hwmode.vrefresh);
-
- tdiff = ktime_to_us(ktime_sub(next_vblank, ktime_get()));
-
- if (tdiff < TILCDC_VBLANK_SAFETY_THRESHOLD_US)
- tilcdc_crtc->next_fb = fb;
- }
-
- if (tilcdc_crtc->next_fb != fb)
- set_scanout(crtc, fb);
-
- tilcdc_crtc->event = event;
-
- spin_unlock_irqrestore(&tilcdc_crtc->irq_lock, flags);
-
- return 0;
-}
-
-static bool tilcdc_crtc_mode_fixup(struct drm_crtc *crtc,
- const struct drm_display_mode *mode,
- struct drm_display_mode *adjusted_mode)
-{
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ unsigned long clk_rate, real_rate, req_rate;
+ unsigned int clkdiv;
+ int ret;
- if (!tilcdc_crtc->simulate_vesa_sync)
- return true;
+ clkdiv = 2; /* first try using a standard divider of 2 */
- /*
- * tilcdc 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;
+ /* mode.clock is in KHz, set_rate wants parameter in Hz */
+ req_rate = crtc->mode.clock * 1000;
- 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;
- }
+ ret = clk_set_rate(priv->clk, req_rate * clkdiv);
+ clk_rate = clk_get_rate(priv->clk);
+ if (ret < 0) {
+ /*
+ * If we fail to set the clock rate (some architectures don't
+ * use the common clock framework yet and may not implement
+ * all the clk API calls for every clock), try the next best
+ * thing: adjusting the clock divider, unless clk_get_rate()
+ * failed as well.
+ */
+ if (!clk_rate) {
+ /* Nothing more we can do. Just bail out. */
+ dev_err(dev->dev,
+ "failed to set the pixel clock - unable to read current lcdc clock rate\n");
+ return;
+ }
- return true;
-}
+ clkdiv = DIV_ROUND_CLOSEST(clk_rate, req_rate);
-static void tilcdc_crtc_set_clk(struct drm_crtc *crtc)
-{
- struct drm_device *dev = crtc->dev;
- struct tilcdc_drm_private *priv = dev->dev_private;
- struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
- const unsigned clkdiv = 2; /* using a fixed divider of 2 */
- int ret;
+ /*
+ * Emit a warning if the real clock rate resulting from the
+ * calculated divider differs much from the requested rate.
+ *
+ * 5% is an arbitrary value - LCDs are usually quite tolerant
+ * about pixel clock rates.
+ */
+ real_rate = clkdiv * req_rate;
- /* mode.clock is in KHz, set_rate wants parameter in Hz */
- ret = clk_set_rate(priv->clk, crtc->mode.clock * 1000 * clkdiv);
- if (ret < 0) {
- dev_err(dev->dev, "failed to set display clock rate to: %d\n",
- crtc->mode.clock);
- return;
+ if (tilcdc_pclk_diff(clk_rate, real_rate) > 5) {
+ dev_warn(dev->dev,
+ "effective pixel clock rate (%luHz) differs from the calculated rate (%luHz)\n",
+ clk_rate, real_rate);
+ }
}
- tilcdc_crtc->lcd_fck_rate = clk_get_rate(priv->clk);
+ tilcdc_crtc->lcd_fck_rate = clk_rate;
DBG("lcd_clk=%u, mode clock=%d, div=%u",
tilcdc_crtc->lcd_fck_rate, crtc->mode.clock, clkdiv);
@@ -349,7 +291,7 @@ static void tilcdc_crtc_set_clk(struct drm_crtc *crtc)
LCDC_V2_CORE_CLK_EN);
}
-static void tilcdc_crtc_mode_set_nofb(struct drm_crtc *crtc)
+static void tilcdc_crtc_set_mode(struct drm_crtc *crtc)
{
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
struct drm_device *dev = crtc->dev;
@@ -359,8 +301,6 @@ static void tilcdc_crtc_mode_set_nofb(struct drm_crtc *crtc)
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
struct drm_framebuffer *fb = crtc->primary->state->fb;
- WARN_ON(!drm_modeset_is_locked(&crtc->mutex));
-
if (WARN_ON(!info))
return;
@@ -509,15 +449,226 @@ static void tilcdc_crtc_mode_set_nofb(struct drm_crtc *crtc)
else
tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
- drm_framebuffer_reference(fb);
+ tilcdc_crtc_set_clk(crtc);
+
+ tilcdc_crtc_load_palette(crtc);
set_scanout(crtc, fb);
- tilcdc_crtc_set_clk(crtc);
+ drm_framebuffer_reference(fb);
crtc->hwmode = crtc->state->adjusted_mode;
}
+static void tilcdc_crtc_enable(struct drm_crtc *crtc)
+{
+ struct drm_device *dev = crtc->dev;
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+
+ WARN_ON(!drm_modeset_is_locked(&crtc->mutex));
+ mutex_lock(&tilcdc_crtc->enable_lock);
+ if (tilcdc_crtc->enabled || tilcdc_crtc->shutdown) {
+ mutex_unlock(&tilcdc_crtc->enable_lock);
+ return;
+ }
+
+ pm_runtime_get_sync(dev->dev);
+
+ reset(crtc);
+
+ tilcdc_crtc_set_mode(crtc);
+
+ tilcdc_crtc_enable_irqs(dev);
+
+ tilcdc_clear(dev, LCDC_DMA_CTRL_REG, LCDC_DUAL_FRAME_BUFFER_ENABLE);
+ tilcdc_write_mask(dev, LCDC_RASTER_CTRL_REG,
+ LCDC_PALETTE_LOAD_MODE(DATA_ONLY),
+ LCDC_PALETTE_LOAD_MODE_MASK);
+ tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
+
+ drm_crtc_vblank_on(crtc);
+
+ tilcdc_crtc->enabled = true;
+ mutex_unlock(&tilcdc_crtc->enable_lock);
+}
+
+static void tilcdc_crtc_off(struct drm_crtc *crtc, bool shutdown)
+{
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ int ret;
+
+ mutex_lock(&tilcdc_crtc->enable_lock);
+ if (shutdown)
+ tilcdc_crtc->shutdown = true;
+ if (!tilcdc_crtc->enabled) {
+ mutex_unlock(&tilcdc_crtc->enable_lock);
+ return;
+ }
+ tilcdc_crtc->frame_done = false;
+ tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
+
+ /*
+ * Wait for framedone irq which will still come before putting
+ * things to sleep..
+ */
+ ret = wait_event_timeout(tilcdc_crtc->frame_done_wq,
+ tilcdc_crtc->frame_done,
+ msecs_to_jiffies(500));
+ if (ret == 0)
+ dev_err(dev->dev, "%s: timeout waiting for framedone\n",
+ __func__);
+
+ drm_crtc_vblank_off(crtc);
+
+ tilcdc_crtc_disable_irqs(dev);
+
+ pm_runtime_put_sync(dev->dev);
+
+ if (tilcdc_crtc->next_fb) {
+ drm_flip_work_queue(&tilcdc_crtc->unref_work,
+ tilcdc_crtc->next_fb);
+ tilcdc_crtc->next_fb = NULL;
+ }
+
+ if (tilcdc_crtc->curr_fb) {
+ drm_flip_work_queue(&tilcdc_crtc->unref_work,
+ tilcdc_crtc->curr_fb);
+ tilcdc_crtc->curr_fb = NULL;
+ }
+
+ drm_flip_work_commit(&tilcdc_crtc->unref_work, priv->wq);
+ tilcdc_crtc->last_vblank = ktime_set(0, 0);
+
+ tilcdc_crtc->enabled = false;
+ mutex_unlock(&tilcdc_crtc->enable_lock);
+}
+
+static void tilcdc_crtc_disable(struct drm_crtc *crtc)
+{
+ WARN_ON(!drm_modeset_is_locked(&crtc->mutex));
+ tilcdc_crtc_off(crtc, false);
+}
+
+void tilcdc_crtc_shutdown(struct drm_crtc *crtc)
+{
+ tilcdc_crtc_off(crtc, true);
+}
+
+static bool tilcdc_crtc_is_on(struct drm_crtc *crtc)
+{
+ return crtc->state && crtc->state->enable && crtc->state->active;
+}
+
+static void tilcdc_crtc_recover_work(struct work_struct *work)
+{
+ struct tilcdc_crtc *tilcdc_crtc =
+ container_of(work, struct tilcdc_crtc, recover_work);
+ struct drm_crtc *crtc = &tilcdc_crtc->base;
+
+ dev_info(crtc->dev->dev, "%s: Reset CRTC", __func__);
+
+ drm_modeset_lock_crtc(crtc, NULL);
+
+ if (!tilcdc_crtc_is_on(crtc))
+ goto out;
+
+ tilcdc_crtc_disable(crtc);
+ tilcdc_crtc_enable(crtc);
+out:
+ drm_modeset_unlock_crtc(crtc);
+}
+
+static void tilcdc_crtc_destroy(struct drm_crtc *crtc)
+{
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ struct tilcdc_drm_private *priv = crtc->dev->dev_private;
+
+ drm_modeset_lock_crtc(crtc, NULL);
+ tilcdc_crtc_disable(crtc);
+ drm_modeset_unlock_crtc(crtc);
+
+ flush_workqueue(priv->wq);
+
+ of_node_put(crtc->port);
+ drm_crtc_cleanup(crtc);
+ drm_flip_work_cleanup(&tilcdc_crtc->unref_work);
+}
+
+int tilcdc_crtc_update_fb(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb,
+ struct drm_pending_vblank_event *event)
+{
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ unsigned long flags;
+
+ WARN_ON(!drm_modeset_is_locked(&crtc->mutex));
+
+ if (tilcdc_crtc->event) {
+ dev_err(dev->dev, "already pending page flip!\n");
+ return -EBUSY;
+ }
+
+ drm_framebuffer_reference(fb);
+
+ crtc->primary->fb = fb;
+
+ spin_lock_irqsave(&tilcdc_crtc->irq_lock, flags);
+
+ if (crtc->hwmode.vrefresh && ktime_to_ns(tilcdc_crtc->last_vblank)) {
+ ktime_t next_vblank;
+ s64 tdiff;
+
+ next_vblank = ktime_add_us(tilcdc_crtc->last_vblank,
+ 1000000 / crtc->hwmode.vrefresh);
+
+ tdiff = ktime_to_us(ktime_sub(next_vblank, ktime_get()));
+
+ if (tdiff < TILCDC_VBLANK_SAFETY_THRESHOLD_US)
+ tilcd