tilcdc changes for v4.10
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJYPsQiAAoJEJA2s1DX1hlBHgIP/1EqQo2xYsShSRcnuCx5nahc xruBS+J0ApA0QpB6bzzxWoChuSVkF3IaCmkwQG5ZUS9HljGWcEy4ljNA0zpCFZMj UmMWOkbu4Z8aZ/IjvCS9JgRnS98faem2KapLWOLM+TGj8li/Xj1y/CvS8FdsFhZQ 6YmnF6XPKYi/0TZd3ATZs9EwruBOqPH2XUYluJazDlbpmQMa4N6sBO5bgd3x57mu CAXidAFTtfIi92UEtHjKQJLWXwrQIUDWr/qcDychFOBIJgxpjjFJeEhi9GsMBNFP /LZsSq7Kv8EJSBvORBkre04jggU5W7yrF44y57JA7F5sQo2oS8TCkht0Qj/+ZuUO HChjUqOLbsVezzwxj107Jgf+R/U1xtt5Rkku3s/UwQUqwf3qRjZ2Ff2JtxoaR2TQ ClDu+Nmh2pLMe7HkBnHw0QQ2wVV2bwMHRbqJZGHjzC/k0GrdO8gEBi64TvAfVM4s 3s2qssTRcu8s50Wnw9RkRCJLuISn7T4oCaGghBVfZ+adjZQAyqUrzIcoeMYSER7F K62eaFzW1/DIsvBX+HzW4J00Vg7+f1tQz3eihZ8Z84w139YK9z5e8+Dml1RnYy8v 28fLHZm1/0pUKz8G/KnBGHt9v+EK61v/EfaNyOwf3lchIkWQmXYNoGGklcRlMpA8 OizdeF20vJ2LiE0tsZ4P =7Fpy -----END PGP SIGNATURE----- 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 ...
This commit is contained in:
commit
0d5320fc19
|
@ -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
|
||||
-------
|
|
@ -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>;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
317
drivers/gpu/drm/bridge/ti-tfp410.c
Normal file
317
drivers/gpu/drm/bridge/ti-tfp410.c
Normal file
|
@ -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", ®)) {
|
||||
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");
|
|
@ -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,174 +215,16 @@ 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);
|
||||
int r = rate / 100, rr = real_rate / 100;
|
||||
|
||||
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);
|
||||
|
||||
tilcdc_crtc->enabled = true;
|
||||
}
|
||||
|
||||
void tilcdc_crtc_disable(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);
|
||||
|
||||
if (!tilcdc_crtc->simulate_vesa_sync)
|
||||
return true;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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 true;
|
||||
return (unsigned int)(abs(((rr - r) * 100) / r));
|
||||
}
|
||||
|
||||
static void tilcdc_crtc_set_clk(struct drm_crtc *crtc)
|
||||
|
@ -323,18 +232,51 @@ 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 */
|
||||
unsigned long clk_rate, real_rate, req_rate;
|
||||
unsigned int clkdiv;
|
||||
int ret;
|
||||
|
||||
clkdiv = 2; /* first try using a standard divider of 2 */
|
||||
|
||||
/* mode.clock is in KHz, set_rate wants parameter in Hz */
|
||||
ret = clk_set_rate(priv->clk, crtc->mode.clock * 1000 * clkdiv);
|
||||
req_rate = crtc->mode.clock * 1000;
|
||||
|
||||
ret = clk_set_rate(priv->clk, req_rate * clkdiv);
|
||||
clk_rate = clk_get_rate(priv->clk);
|
||||
if (ret < 0) {
|
||||
dev_err(dev->dev, "failed to set display clock rate to: %d\n",
|
||||
crtc->mode.clock);
|
||||
return;
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
clkdiv = DIV_ROUND_CLOSEST(clk_rate, req_rate);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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)
|
||||
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);
|
||||
|
||||
if (!tilcdc_crtc->simulate_vesa_sync)
|
||||
return true;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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 true;
|
||||
}
|
||||
|
||||
static int tilcdc_crtc_atomic_check(struct drm_crtc *crtc,
|
||||
struct drm_crtc_state *state)
|
||||
{
|
||||
|
@ -558,7 +709,6 @@ static const struct drm_crtc_helper_funcs tilcdc_crtc_helper_funcs = {
|
|||
.enable = tilcdc_crtc_enable,
|
||||
.disable = tilcdc_crtc_disable,
|
||||
.atomic_check = tilcdc_crtc_atomic_check,
|
||||
.mode_set_nofb = tilcdc_crtc_mode_set_nofb,
|
||||
};
|
||||
|
||||
int tilcdc_crtc_max_width(struct drm_crtc *crtc)
|
||||
|
@ -754,28 +904,48 @@ irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc)
|
|||
}
|
||||
|
||||
if (stat & LCDC_FIFO_UNDERFLOW)
|
||||
dev_err_ratelimited(dev->dev, "%s(0x%08x): FIFO underfow",
|
||||
dev_err_ratelimited(dev->dev, "%s(0x%08x): FIFO underflow",
|
||||
__func__, stat);
|
||||
|
||||
if (stat & LCDC_PL_LOAD_DONE) {
|
||||
complete(&tilcdc_crtc->palette_loaded);
|
||||
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);
|
||||
}
|
||||
|
||||
if (stat & LCDC_SYNC_LOST) {
|
||||
dev_err_ratelimited(dev->dev, "%s(0x%08x): Sync lost",
|
||||
__func__, stat);
|
||||
tilcdc_crtc->frame_intact = false;
|
||||
if (tilcdc_crtc->sync_lost_count++ >
|
||||
SYNC_LOST_COUNT_LIMIT) {
|
||||
dev_err(dev->dev, "%s(0x%08x): Sync lost flood detected, recovering", __func__, stat);
|
||||
queue_work(system_wq, &tilcdc_crtc->recover_work);
|
||||
if (priv->rev == 1)
|
||||
tilcdc_clear(dev, LCDC_RASTER_CTRL_REG,
|
||||
LCDC_V1_SYNC_LOST_INT_ENA);
|
||||
else
|
||||
tilcdc_write(dev, LCDC_INT_ENABLE_CLR_REG,
|
||||
LCDC_SYNC_LOST);
|
||||
tilcdc_crtc->sync_lost_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (stat & LCDC_FRAME_DONE) {
|
||||
tilcdc_crtc->frame_done = true;
|
||||
wake_up(&tilcdc_crtc->frame_done_wq);
|
||||
/* rev 1 lcdc appears to hang if irq is not disbaled here */
|
||||
if (priv->rev == 1)
|
||||
tilcdc_clear(dev, LCDC_RASTER_CTRL_REG,
|
||||
LCDC_V1_FRAME_DONE_INT_ENA);
|
||||
}
|
||||
|
||||
/* For revision 2 only */
|
||||
if (priv->rev == 2) {
|
||||
if (stat & LCDC_FRAME_DONE) {
|
||||
tilcdc_crtc->frame_done = true;
|
||||
wake_up(&tilcdc_crtc->frame_done_wq);
|
||||
}
|
||||
|
||||
if (stat & LCDC_SYNC_LOST) {
|
||||
dev_err_ratelimited(dev->dev, "%s(0x%08x): Sync lost",
|
||||
__func__, stat);
|
||||
tilcdc_crtc->frame_intact = false;
|
||||
if (tilcdc_crtc->sync_lost_count++ >
|
||||
SYNC_LOST_COUNT_LIMIT) {
|
||||
dev_err(dev->dev, "%s(0x%08x): Sync lost flood detected, disabling the interrupt", __func__, stat);
|
||||
tilcdc_write(dev, LCDC_INT_ENABLE_CLR_REG,
|
||||
LCDC_SYNC_LOST);
|
||||
}
|
||||
}
|
||||
|
||||
/* Indicate to LCDC that the interrupt service routine has
|
||||
* completed, see 13.3.6.1.6 in AM335x TRM.
|
||||
*/
|
||||
|
@ -785,7 +955,7 @@ irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev)
|
||||
int tilcdc_crtc_create(struct drm_device *dev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
struct tilcdc_crtc *tilcdc_crtc;
|
||||
|
@ -795,21 +965,33 @@ struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev)
|
|||
tilcdc_crtc = devm_kzalloc(dev->dev, sizeof(*tilcdc_crtc), GFP_KERNEL);
|
||||
if (!tilcdc_crtc) {
|
||||
dev_err(dev->dev, "allocation failed\n");
|
||||
return NULL;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
init_completion(&tilcdc_crtc->palette_loaded);
|
||||
tilcdc_crtc->palette_base = dmam_alloc_coherent(dev->dev,
|
||||
TILCDC_PALETTE_SIZE,
|
||||
&tilcdc_crtc->palette_dma_handle,
|
||||
GFP_KERNEL | __GFP_ZERO);
|
||||
if (!tilcdc_crtc->palette_base)
|
||||
return -ENOMEM;
|
||||
*tilcdc_crtc->palette_base = TILCDC_PALETTE_FIRST_ENTRY;
|
||||
|
||||
crtc = &tilcdc_crtc->base;
|
||||
|
||||
ret = tilcdc_plane_init(dev, &tilcdc_crtc->primary);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
mutex_init(&tilcdc_crtc->enable_lock);
|
||||
|
||||
init_waitqueue_head(&tilcdc_crtc->frame_done_wq);
|
||||
|
||||
drm_flip_work_init(&tilcdc_crtc->unref_work,
|
||||
"unref", unref_worker);
|
||||
|
||||
spin_lock_init(&tilcdc_crtc->irq_lock);
|
||||
INIT_WORK(&tilcdc_crtc->recover_work, tilcdc_crtc_recover_work);
|
||||
|
||||
ret = drm_crtc_init_with_planes(dev, crtc,
|
||||
&tilcdc_crtc->primary,
|
||||
|
@ -835,13 +1017,15 @@ struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev)
|
|||
if (!crtc->port) { /* This should never happen */
|
||||
dev_err(dev->dev, "Port node not found in %s\n",
|
||||
dev->dev->of_node->full_name);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
return crtc;
|
||||
priv->crtc = crtc;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
tilcdc_crtc_destroy(crtc);
|
||||
return NULL;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
|
|
@ -127,18 +127,12 @@ static int tilcdc_commit(struct drm_device *dev,
|
|||
* current layout.
|
||||
*/
|
||||
|
||||
/* Keep HW on while we commit the state. */
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
|
||||
drm_atomic_helper_commit_modeset_disables(dev, state);
|
||||
|
||||
drm_atomic_helper_commit_planes(dev, state, 0);
|
||||
|
||||
drm_atomic_helper_commit_modeset_enables(dev, state);
|
||||
|
||||
/* Now HW should remain on if need becase the crtc is enabled */
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
|
||||
drm_atomic_helper_wait_for_vblanks(dev, state);
|
||||
|
||||
drm_atomic_helper_cleanup_planes(dev, state);
|
||||
|
@ -153,15 +147,11 @@ static const struct drm_mode_config_funcs mode_config_funcs = {
|
|||
.atomic_commit = tilcdc_commit,
|
||||
};
|
||||
|
||||
static int modeset_init(struct drm_device *dev)
|
||||
static void modeset_init(struct drm_device *dev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
struct tilcdc_module *mod;
|
||||
|
||||
drm_mode_config_init(dev);
|
||||
|
||||
priv->crtc = tilcdc_crtc_create(dev);
|
||||
|
||||
list_for_each_entry(mod, &module_list, list) {
|
||||
DBG("loading module: %s", mod->name);
|
||||
mod->funcs->modeset_init(mod, dev);
|
||||
|
@ -172,8 +162,6 @@ static int modeset_init(struct drm_device *dev)
|
|||
dev->mode_config.max_width = tilcdc_crtc_max_width(priv->crtc);
|
||||
dev->mode_config.max_height = 2048;
|
||||
dev->mode_config.funcs = &mode_config_funcs;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CPU_FREQ
|
||||
|
@ -194,22 +182,29 @@ static int cpufreq_transition(struct notifier_block *nb,
|
|||
* DRM operations:
|
||||
*/
|
||||
|
||||
static int tilcdc_unload(struct drm_device *dev)
|
||||
static void tilcdc_fini(struct drm_device *dev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
|
||||
tilcdc_remove_external_encoders(dev);
|
||||
if (priv->crtc)
|
||||
tilcdc_crtc_shutdown(priv->crtc);
|
||||
|
||||
if (priv->is_registered)
|
||||
drm_dev_unregister(dev);
|
||||
|
||||
drm_fbdev_cma_fini(priv->fbdev);
|
||||
drm_kms_helper_poll_fini(dev);
|
||||
drm_mode_config_cleanup(dev);
|
||||
drm_vblank_cleanup(dev);
|
||||
|
||||
if (priv->fbdev)
|
||||
drm_fbdev_cma_fini(priv->fbdev);
|
||||
|
||||
drm_irq_uninstall(dev);
|
||||
drm_mode_config_cleanup(dev);
|
||||
tilcdc_remove_external_device(dev);
|
||||
|
||||
#ifdef CONFIG_CPU_FREQ
|
||||
cpufreq_unregister_notifier(&priv->freq_transition,
|
||||
CPUFREQ_TRANSITION_NOTIFIER);
|
||||
if (priv->freq_transition.notifier_call)
|
||||
cpufreq_unregister_notifier(&priv->freq_transition,
|
||||
CPUFREQ_TRANSITION_NOTIFIER);
|
||||
#endif
|
||||
|
||||
if (priv->clk)
|
||||
|
@ -218,61 +213,71 @@ static int tilcdc_unload(struct drm_device *dev)
|
|||
if (priv->mmio)
|
||||
iounmap(priv->mmio);
|
||||
|
||||
flush_workqueue(priv->wq);
|
||||
destroy_workqueue(priv->wq);
|
||||
if (priv->wq) {
|
||||
flush_workqueue(priv->wq);
|
||||
destroy_workqueue(priv->wq);
|
||||
}
|
||||
|
||||
dev->dev_private = NULL;
|
||||
|
||||
pm_runtime_disable(dev->dev);
|
||||
|
||||
return 0;
|
||||
drm_dev_unref(dev);
|
||||
}
|
||||
|
||||
static int tilcdc_load(struct drm_device *dev, unsigned long flags)
|
||||
static int tilcdc_init(struct drm_driver *ddrv, struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = dev->platformdev;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct drm_device *ddev;
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct device_node *node = dev->of_node;
|
||||
struct tilcdc_drm_private *priv;
|
||||
struct resource *res;
|
||||
u32 bpp = 0;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv) {
|
||||
dev_err(dev->dev, "failed to allocate private data\n");
|
||||
dev_err(dev, "failed to allocate private data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dev->dev_private = priv;
|
||||
ddev = drm_dev_alloc(ddrv, dev);
|
||||
if (IS_ERR(ddev))
|
||||
return PTR_ERR(ddev);
|
||||
|
||||
ddev->platformdev = pdev;
|
||||
ddev->dev_private = priv;
|
||||
platform_set_drvdata(pdev, ddev);
|
||||
drm_mode_config_init(ddev);
|
||||
|
||||
priv->is_componentized =
|
||||
tilcdc_get_external_components(dev->dev, NULL) > 0;
|
||||
tilcdc_get_external_components(dev, NULL) > 0;
|
||||
|
||||
priv->wq = alloc_ordered_workqueue("tilcdc", 0);
|
||||
if (!priv->wq) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_unset_priv;
|
||||
goto init_failed;
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(dev->dev, "failed to get memory resource\n");
|
||||
dev_err(dev, "failed to get memory resource\n");
|
||||
ret = -EINVAL;
|
||||
goto fail_free_wq;
|
||||
goto init_failed;
|
||||
}
|
||||
|
||||
priv->mmio = ioremap_nocache(res->start, resource_size(res));
|
||||
if (!priv->mmio) {
|
||||
dev_err(dev->dev, "failed to ioremap\n");
|
||||
dev_err(dev, "failed to ioremap\n");
|
||||
ret = -ENOMEM;
|
||||
goto fail_free_wq;
|
||||
goto init_failed;
|
||||
}
|
||||
|
||||
priv->clk = clk_get(dev->dev, "fck");
|
||||
priv->clk = clk_get(dev, "fck");
|
||||
if (IS_ERR(priv->clk)) {
|
||||
dev_err(dev->dev, "failed to get functional clock\n");
|
||||
dev_err(dev, "failed to get functional clock\n");
|
||||
ret = -ENODEV;
|
||||
goto fail_iounmap;
|
||||
goto init_failed;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CPU_FREQ
|
||||
|
@ -280,8 +285,9 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags)
|
|||
ret = cpufreq_register_notifier(&priv->freq_transition,
|
||||
CPUFREQ_TRANSITION_NOTIFIER);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to register cpufreq notifier\n");
|
||||
goto fail_put_clk;
|
||||
dev_err(dev, "failed to register cpufreq notifier\n");
|
||||
priv->freq_transition.notifier_call = NULL;
|
||||
goto init_failed;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -290,22 +296,22 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags)
|
|||
|
||||
DBG("Maximum Bandwidth Value %d", priv->max_bandwidth);
|
||||
|
||||
if (of_property_read_u32(node, "ti,max-width", &priv->max_width))
|
||||
if (of_property_read_u32(node, "max-width", &priv->max_width))
|
||||
priv->max_width = TILCDC_DEFAULT_MAX_WIDTH;
|
||||
|
||||
DBG("Maximum Horizontal Pixel Width Value %dpixels", priv->max_width);
|
||||
|
||||
if (of_property_read_u32(node, "ti,max-pixelclock",
|
||||
if (of_property_read_u32(node, "max-pixelclock",
|
||||
&priv->max_pixelclock))
|
||||
priv->max_pixelclock = TILCDC_DEFAULT_MAX_PIXELCLOCK;
|
||||
|
||||
DBG("Maximum Pixel Clock Value %dKHz", priv->max_pixelclock);
|
||||
|
||||
pm_runtime_enable(dev->dev);
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
/* Determine LCD IP Version */
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
switch (tilcdc_read(dev, LCDC_PID_REG)) {
|
||||
pm_runtime_get_sync(dev);
|
||||
switch (tilcdc_read(ddev, LCDC_PID_REG)) {
|
||||
case 0x4c100102:
|
||||
priv->rev = 1;
|
||||
break;
|
||||
|
@ -314,14 +320,14 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags)
|
|||
priv->rev = 2;
|
||||
break;
|
||||
default:
|
||||
dev_warn(dev->dev, "Unknown PID Reg value 0x%08x, "
|
||||
"defaulting to LCD revision 1\n",
|
||||
tilcdc_read(dev, LCDC_PID_REG));
|
||||
dev_warn(dev, "Unknown PID Reg value 0x%08x, "
|
||||
"defaulting to LCD revision 1\n",
|
||||
tilcdc_read(ddev, LCDC_PID_REG));
|
||||
priv->rev = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
pm_runtime_put_sync(dev);
|
||||
|
||||
if (priv->rev == 1) {
|
||||
DBG("Revision 1 LCDC supports only RGB565 format");
|
||||
|
@ -354,91 +360,67 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags)
|
|||
}
|
||||
}
|
||||
|
||||
ret = modeset_init(dev);
|
||||
ret = tilcdc_crtc_create(ddev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev->dev, "failed to initialize mode setting\n");
|
||||
goto fail_cpufreq_unregister;
|
||||
dev_err(dev, "failed to create crtc\n");
|
||||
goto init_failed;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dev);
|
||||
modeset_init(ddev);
|
||||
|
||||
if (priv->is_componentized) {
|
||||
ret = component_bind_all(dev->dev, dev);
|
||||
ret = component_bind_all(dev, ddev);
|
||||
if (ret < 0)
|
||||
goto fail_mode_config_cleanup;
|
||||
goto init_failed;
|
||||
|
||||
ret = tilcdc_add_external_encoders(dev);
|
||||
ret = tilcdc_add_component_encoder(ddev);
|
||||
if (ret < 0)
|
||||
goto fail_component_cleanup;
|
||||
goto init_failed;
|
||||
} else {
|
||||
ret = tilcdc_attach_external_device(ddev);
|
||||
if (ret)
|
||||
goto init_failed;
|
||||
}
|
||||
|
||||
if ((priv->num_encoders == 0) || (priv->num_connectors == 0)) {
|
||||
dev_err(dev->dev, "no encoders/connectors found\n");
|
||||
if (!priv->external_connector &&
|
||||
((priv->num_encoders == 0) || (priv->num_connectors == 0))) {
|
||||
dev_err(dev, "no encoders/connectors found\n");
|
||||
ret = -ENXIO;
|
||||
goto fail_external_cleanup;
|
||||
goto init_failed;
|
||||
}
|
||||
|
||||
ret = drm_vblank_init(dev, 1);
|
||||
ret = drm_vblank_init(ddev, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(dev->dev, "failed to initialize vblank\n");
|
||||
goto fail_external_cleanup;
|
||||
dev_err(dev, "failed to initialize vblank\n");
|
||||
goto init_failed;
|
||||
}
|
||||
|
||||
ret = drm_irq_install(dev, platform_get_irq(dev->platformdev, 0));
|
||||
ret = drm_irq_install(ddev, platform_get_irq(pdev, 0));
|
||||
if (ret < 0) {
|
||||
dev_err(dev->dev, "failed to install IRQ handler\n");
|
||||
goto fail_vblank_cleanup;
|
||||
dev_err(dev, "failed to install IRQ handler\n");
|
||||
goto init_failed;
|
||||
}
|
||||
|
||||
drm_mode_config_reset(dev);
|
||||
drm_mode_config_reset(ddev);
|
||||
|
||||
priv->fbdev = drm_fbdev_cma_init(dev, bpp,
|
||||
dev->mode_config.num_crtc,
|
||||
dev->mode_config.num_connector);
|
||||
priv->fbdev = drm_fbdev_cma_init(ddev, bpp,
|
||||
ddev->mode_config.num_crtc,
|
||||
ddev->mode_config.num_connector);
|
||||
if (IS_ERR(priv->fbdev)) {
|
||||
ret = PTR_ERR(priv->fbdev);
|
||||
goto fail_irq_uninstall;
|
||||
goto init_failed;
|
||||
}
|
||||
|
||||
drm_kms_helper_poll_init(dev);
|
||||
drm_kms_helper_poll_init(ddev);
|
||||
|
||||
ret = drm_dev_register(ddev, 0);
|
||||
if (ret)
|
||||
goto init_failed;
|
||||
|
||||
priv->is_registered = true;
|
||||
return 0;
|
||||
|
||||
fail_irq_uninstall:
|
||||
drm_irq_uninstall(dev);
|
||||
|
||||
fail_vblank_cleanup:
|
||||
drm_vblank_cleanup(dev);
|
||||
|
||||
fail_component_cleanup:
|
||||
if (priv->is_componentized)
|
||||
component_unbind_all(dev->dev, dev);
|
||||
|
||||
fail_mode_config_cleanup:
|
||||
drm_mode_config_cleanup(dev);
|
||||
|
||||
fail_external_cleanup:
|
||||
tilcdc_remove_external_encoders(dev);
|
||||
|
||||
fail_cpufreq_unregister:
|
||||
pm_runtime_disable(dev->dev);
|
||||
#ifdef CONFIG_CPU_FREQ
|
||||
cpufreq_unregister_notifier(&priv->freq_transition,
|
||||
CPUFREQ_TRANSITION_NOTIFIER);
|
||||
|
||||
fail_put_clk:
|
||||
#endif
|
||||
clk_put(priv->clk);
|
||||
|
||||
fail_iounmap:
|
||||
iounmap(priv->mmio);
|
||||
|
||||
fail_free_wq:
|
||||
flush_workqueue(priv->wq);
|
||||
destroy_workqueue(priv->wq);
|
||||
|
||||
fail_unset_priv:
|
||||
dev->dev_private = NULL;
|
||||
init_failed:
|
||||
tilcdc_fini(ddev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -583,8 +565,6 @@ static const struct file_operations fops = {
|
|||
static struct drm_driver tilcdc_driver = {
|
||||
.driver_features = (DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET |
|
||||
DRIVER_PRIME | DRIVER_ATOMIC),
|
||||
.load = tilcdc_load,
|
||||
.unload = tilcdc_unload,
|
||||
.lastclose = tilcdc_lastclose,
|
||||
.irq_handler = tilcdc_irq,
|
||||
.get_vblank_counter = drm_vblank_no_hw_counter,
|
||||
|
@ -658,10 +638,9 @@ static const struct dev_pm_ops tilcdc_pm_ops = {
|
|||
/*
|
||||
* Platform driver:
|
||||
*/
|
||||
|
||||
static int tilcdc_bind(struct device *dev)
|
||||
{
|
||||
return drm_platform_init(&tilcdc_driver, to_platform_device(dev));
|
||||
return tilcdc_init(&tilcdc_driver, dev);
|
||||
}
|
||||
|
||||
static void tilcdc_unbind(struct device *dev)
|
||||
|
@ -672,7 +651,7 @@ static void tilcdc_unbind(struct device *dev)
|
|||
if (!ddev->dev_private)
|
||||
return;
|
||||
|
||||
drm_put_dev(dev_get_drvdata(dev));
|
||||
tilcdc_fini(dev_get_drvdata(dev));
|
||||
}
|
||||
|
||||
static const struct component_master_ops tilcdc_comp_ops = {
|
||||
|
@ -695,7 +674,7 @@ static int tilcdc_pdev_probe(struct platform_device *pdev)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
else if (ret == 0)
|
||||
return drm_platform_init(&tilcdc_driver, pdev);
|
||||
return tilcdc_init(&tilcdc_driver, &pdev->dev);
|
||||
else
|
||||
return component_master_add_with_match(&pdev->dev,
|
||||
&tilcdc_comp_ops,
|
||||
|
@ -710,7 +689,7 @@ static int tilcdc_pdev_remove(struct platform_device *pdev)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
else if (ret == 0)
|
||||
drm_put_dev(platform_get_drvdata(pdev));
|
||||
tilcdc_fini(platform_get_drvdata(pdev));
|
||||
else
|
||||
component_master_del(&pdev->dev, &tilcdc_comp_ops);
|
||||
|
||||
|
@ -719,6 +698,7 @@ static int tilcdc_pdev_remove(struct platform_device *pdev)
|
|||
|
||||
static struct of_device_id tilcdc_of_match[] = {
|
||||
{ .compatible = "ti,am33xx-tilcdc", },
|
||||
{ .compatible = "ti,da850-tilcdc", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tilcdc_of_match);
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_gem_cma_helper.h>
|
||||
#include <drm/drm_fb_cma_helper.h>
|
||||
#include <drm/drm_bridge.h>
|
||||
|
||||
/* Defaulting to pixel clock defined on AM335x */
|
||||
#define TILCDC_DEFAULT_MAX_PIXELCLOCK 126000
|
||||
|
@ -87,8 +88,12 @@ struct tilcdc_drm_private {
|
|||
|
||||
unsigned int num_connectors;
|
||||
struct drm_connector *connectors[8];
|
||||
const struct drm_connector_helper_funcs *connector_funcs[8];
|
||||
|
||||
struct drm_encoder *external_encoder;
|
||||
struct drm_connector *external_connector;
|
||||
const struct drm_connector_helper_funcs *connector_funcs;
|
||||
|
||||
bool is_registered;
|
||||
bool is_componentized;
|
||||
};
|
||||
|
||||
|
@ -163,7 +168,7 @@ struct tilcdc_panel_info {
|
|||
|
||||
#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
|
||||
|
||||
struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev);
|
||||
int tilcdc_crtc_create(struct drm_device *dev);
|
||||
irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc);
|
||||
void tilcdc_crtc_update_clk(struct drm_crtc *crtc);
|
||||
void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
|
||||
|
@ -172,7 +177,7 @@ void tilcdc_crtc_set_simulate_vesa_sync(struct drm_crtc *crtc,
|
|||
bool simulate_vesa_sync);
|
||||
int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode);
|
||||
int tilcdc_crtc_max_width(struct drm_crtc *crtc);
|
||||
void tilcdc_crtc_disable(struct drm_crtc *crtc);
|
||||
void tilcdc_crtc_shutdown(struct drm_crtc *crtc);
|
||||
int tilcdc_crtc_update_fb(struct drm_crtc *crtc,
|
||||
struct drm_framebuffer *fb,
|
||||
struct drm_pending_vblank_event *event);
|
||||
|
|
|
@ -28,44 +28,50 @@ static const struct tilcdc_panel_info panel_info_tda998x = {
|
|||
.raster_order = 0,
|
||||
};
|
||||
|
||||
static const struct tilcdc_panel_info panel_info_default = {
|
||||
.ac_bias = 255,
|
||||
.ac_bias_intrpt = 0,
|
||||
.dma_burst_sz = 16,
|
||||
.bpp = 16,
|
||||
.fdd = 0x80,
|
||||
.tft_alt_mode = 0,
|
||||
.sync_edge = 0,
|
||||
.sync_ctrl = 1,
|
||||
.raster_order = 0,
|
||||
};
|
||||
|
||||
static int tilcdc_external_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = connector->dev->dev_private;
|
||||
int ret, i;
|
||||
int ret;
|
||||
|
||||
ret = tilcdc_crtc_mode_valid(priv->crtc, mode);
|
||||
if (ret != MODE_OK)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < priv->num_connectors &&
|
||||
priv->connectors[i] != connector; i++)
|
||||
;
|
||||
|
||||
BUG_ON(priv->connectors[i] != connector);
|
||||
BUG_ON(!priv->connector_funcs[i]);
|
||||
BUG_ON(priv->external_connector != connector);
|
||||
BUG_ON(!priv->connector_funcs);
|
||||
|
||||
/* If the connector has its own mode_valid call it. */
|
||||
if (!IS_ERR(priv->connector_funcs[i]) &&
|
||||
priv->connector_funcs[i]->mode_valid)
|
||||
return priv->connector_funcs[i]->mode_valid(connector, mode);
|
||||
if (!IS_ERR(priv->connector_funcs) &&
|
||||
priv->connector_funcs->mode_valid)
|
||||
return priv->connector_funcs->mode_valid(connector, mode);
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static int tilcdc_add_external_encoder(struct drm_device *dev,
|
||||
struct drm_connector *connector)
|
||||
static int tilcdc_add_external_connector(struct drm_device *dev,
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
struct drm_connector_helper_funcs *connector_funcs;
|
||||
|
||||
priv->connectors[priv->num_connectors] = connector;
|
||||
priv->encoders[priv->num_encoders++] = connector->encoder;
|
||||
|
||||
/* Only tda998x is supported at the moment. */
|
||||
tilcdc_crtc_set_simulate_vesa_sync(priv->crtc, true);
|
||||
tilcdc_crtc_set_panel_info(priv->crtc, &panel_info_tda998x);
|
||||
/* There should never be more than one connector */
|
||||
if (WARN_ON(priv->external_connector))
|
||||
return -EINVAL;
|
||||
|
||||
priv->external_connector = connector;
|
||||
connector_funcs = devm_kzalloc(dev->dev, sizeof(*connector_funcs),
|
||||
GFP_KERNEL);
|
||||
if (!connector_funcs)
|
||||
|
@ -78,56 +84,177 @@ static int tilcdc_add_external_encoder(struct drm_device *dev,
|
|||
* everything else but use our own mode_valid() (above).
|
||||
*/
|
||||
if (connector->helper_private) {
|
||||
priv->connector_funcs[priv->num_connectors] =
|
||||
connector->helper_private;
|
||||
*connector_funcs = *priv->connector_funcs[priv->num_connectors];
|
||||
priv->connector_funcs = connector->helper_private;
|
||||
*connector_funcs = *priv->connector_funcs;
|
||||
} else {
|
||||
priv->connector_funcs[priv->num_connectors] = ERR_PTR(-ENOENT);
|
||||
priv->connector_funcs = ERR_PTR(-ENOENT);
|
||||
}
|
||||
connector_funcs->mode_valid = tilcdc_external_mode_valid;
|
||||
drm_connector_helper_add(connector, connector_funcs);
|
||||
priv->num_connectors++;
|
||||
|
||||
dev_dbg(dev->dev, "External encoder '%s' connected\n",
|
||||
connector->encoder->name);
|
||||
dev_dbg(dev->dev, "External connector '%s' connected\n",
|
||||
connector->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tilcdc_add_external_encoders(struct drm_device *dev)
|
||||
static
|
||||
struct drm_connector *tilcdc_encoder_find_connector(struct drm_device *ddev,
|
||||
struct drm_encoder *encoder)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
struct drm_connector *connector;
|
||||
int num_internal_connectors = priv->num_connectors;
|
||||
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
||||
bool found = false;
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < num_internal_connectors; i++)
|
||||
if (connector == priv->connectors[i])
|
||||
found = true;
|
||||
if (!found) {
|
||||
ret = tilcdc_add_external_encoder(dev, connector);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tilcdc_remove_external_encoders(struct drm_device *dev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
int i;
|
||||
|
||||
list_for_each_entry(connector, &ddev->mode_config.connector_list, head)
|
||||
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++)
|
||||
if (connector->encoder_ids[i] == encoder->base.id)
|
||||
return connector;
|
||||
|
||||
dev_err(ddev->dev, "No connector found for %s encoder (id %d)\n",
|
||||
encoder->name, encoder->base.id);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int tilcdc_add_component_encoder(struct drm_device *ddev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = ddev->dev_private;
|
||||
struct drm_connector *connector;
|
||||
struct drm_encoder *encoder;
|
||||
|
||||
list_for_each_entry(encoder, &ddev->mode_config.encoder_list, head)
|
||||
if (encoder->possible_crtcs & (1 << priv->crtc->index))
|
||||
break;
|
||||
|
||||
if (!encoder) {
|
||||
dev_err(ddev->dev, "%s: No suitable encoder found\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
connector = tilcdc_encoder_find_connector(ddev, encoder);
|
||||
|
||||
if (!connector)
|
||||
return -ENODEV;
|
||||
|
||||
/* Only tda998x is supported at the moment. */
|
||||
tilcdc_crtc_set_simulate_vesa_sync(priv->crtc, true);
|
||||
tilcdc_crtc_set_panel_info(priv->crtc, &panel_info_tda998x);
|
||||
|
||||
return tilcdc_add_external_connector(ddev, connector);
|
||||
}
|
||||
|
||||
void tilcdc_remove_external_device(struct drm_device *dev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
|
||||
/* Restore the original helper functions, if any. */
|
||||
for (i = 0; i < priv->num_connectors; i++)
|
||||
if (IS_ERR(priv->connector_funcs[i]))
|
||||
drm_connector_helper_add(priv->connectors[i], NULL);
|
||||
else if (priv->connector_funcs[i])
|
||||
drm_connector_helper_add(priv->connectors[i],
|
||||
priv->connector_funcs[i]);
|
||||
if (IS_ERR(priv->connector_funcs))
|
||||
drm_connector_helper_add(priv->external_connector, NULL);
|
||||
else if (priv->connector_funcs)
|
||||
drm_connector_helper_add(priv->external_connector,
|
||||
priv->connector_funcs);
|
||||
}
|
||||
|
||||
static const struct drm_encoder_funcs tilcdc_external_encoder_funcs = {
|
||||
.destroy = drm_encoder_cleanup,
|
||||
};
|
||||
|
||||
static
|
||||
int tilcdc_attach_bridge(struct drm_device *ddev, struct drm_bridge *bridge)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = ddev->dev_private;
|
||||
struct drm_connector *connector;
|
||||
int ret;
|
||||
|
||||
priv->external_encoder->possible_crtcs = BIT(0);
|
||||
priv->external_encoder->bridge = bridge;
|
||||
bridge->encoder = priv->external_encoder;
|
||||
|
||||
ret = drm_bridge_attach(ddev, bridge);
|
||||
if (ret) {
|
||||
dev_err(ddev->dev, "drm_bridge_attach() failed %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
tilcdc_crtc_set_panel_info(priv->crtc, &panel_info_default);
|
||||
|
||||
connector = tilcdc_encoder_find_connector(ddev, priv->external_encoder);
|
||||
if (!connector)
|
||||
return -ENODEV;
|
||||
|
||||
ret = tilcdc_add_external_connector(ddev, connector);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tilcdc_node_has_port(struct device_node *dev_node)
|
||||
{
|
||||
struct device_node *node;
|
||||
|
||||
node = of_get_child_by_name(dev_node, "ports");
|
||||
if (!node)
|
||||
node = of_get_child_by_name(dev_node, "port");
|
||||
if (!node)
|
||||
return 0;
|
||||
of_node_put(node);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static
|
||||
struct device_node *tilcdc_get_remote_node(struct device_node *node)
|
||||
{
|
||||
struct device_node *ep;
|
||||
struct device_node *parent;
|
||||
|
||||
if (!tilcdc_node_has_port(node))
|
||||
return NULL;
|
||||
|
||||
ep = of_graph_get_next_endpoint(node, NULL);
|
||||
if (!ep)
|
||||
return NULL;
|
||||
|
||||
parent = of_graph_get_remote_port_parent(ep);
|
||||
of_node_put(ep);
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
int tilcdc_attach_external_device(struct drm_device *ddev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = ddev->dev_private;
|
||||
struct device_node *remote_node;
|
||||
struct drm_bridge *bridge;
|
||||
int ret;
|
||||
|
||||
remote_node = tilcdc_get_remote_node(ddev->dev->of_node);
|
||||
if (!remote_node)
|
||||
return 0;
|
||||
|
||||
bridge = of_drm_find_bridge(remote_node);
|
||||
of_node_put(remote_node);
|
||||
if (!bridge)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
priv->external_encoder = devm_kzalloc(ddev->dev,
|
||||
sizeof(*priv->external_encoder),
|
||||
GFP_KERNEL);
|
||||
if (!priv->external_encoder)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = drm_encoder_init(ddev, priv->external_encoder,
|
||||
&tilcdc_external_encoder_funcs,
|
||||
DRM_MODE_ENCODER_NONE, NULL);
|
||||
if (ret) {
|
||||
dev_err(ddev->dev, "drm_encoder_init() failed %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = tilcdc_attach_bridge(ddev, bridge);
|
||||
if (ret)
|
||||
drm_encoder_cleanup(priv->external_encoder);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dev_match_of(struct device *dev, void *data)
|
||||
|
@ -141,16 +268,10 @@ int tilcdc_get_external_components(struct device *dev,
|
|||
struct device_node *node;
|
||||
struct device_node *ep = NULL;
|
||||
int count = 0;
|
||||
int ret = 0;
|
||||
|
||||
/* Avoid error print by of_graph_get_next_endpoint() if there
|
||||
* is no ports present.
|
||||
*/
|
||||
node = of_get_child_by_name(dev->of_node, "ports");
|
||||
if (!node)
|
||||
node = of_get_child_by_name(dev->of_node, "port");
|
||||
if (!node)
|
||||
if (!tilcdc_node_has_port(dev->of_node))
|
||||
return 0;
|
||||
of_node_put(node);
|
||||
|
||||
while ((ep = of_graph_get_next_endpoint(dev->of_node, ep))) {
|
||||
node = of_graph_get_remote_port_parent(ep);
|
||||
|
@ -160,17 +281,20 @@ int tilcdc_get_external_components(struct device *dev,
|
|||
}
|
||||
|
||||
dev_dbg(dev, "Subdevice node '%s' found\n", node->name);
|
||||
if (match)
|
||||
drm_of_component_match_add(dev, match, dev_match_of,
|
||||
node);
|
||||
|
||||
if (of_device_is_compatible(node, "nxp,tda998x")) {
|
||||
if (match)
|
||||
drm_of_component_match_add(dev, match,
|
||||
dev_match_of, node);
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
of_node_put(node);
|
||||
count++;
|
||||
if (count++ > 1) {
|
||||
dev_err(dev, "Only one port is supported\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 1) {
|
||||
dev_err(dev, "Only one external encoder is supported\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return count;
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
#ifndef __TILCDC_EXTERNAL_H__
|
||||
#define __TILCDC_EXTERNAL_H__
|
||||
|
||||
int tilcdc_add_external_encoders(struct drm_device *dev);
|
||||
void tilcdc_remove_external_encoders(struct drm_device *dev);
|
||||
int tilcdc_add_component_encoder(struct drm_device *dev);
|
||||
void tilcdc_remove_external_device(struct drm_device *dev);
|
||||
int tilcdc_get_external_components(struct device *dev,
|
||||
struct component_match **match);
|
||||
int tilcdc_attach_external_device(struct drm_device *ddev);
|
||||
#endif /* __TILCDC_SLAVE_H__ */
|
||||
|
|
|
@ -240,8 +240,6 @@ static struct drm_connector *panel_connector_create(struct drm_device *dev,
|
|||
if (ret)
|
||||
goto fail;
|
||||
|
||||
drm_connector_register(connector);
|
||||
|
||||
return connector;
|
||||
|
||||
fail:
|
||||
|
|
|
@ -34,11 +34,14 @@
|
|||
|
||||
/* LCDC DMA Control Register */
|
||||
#define LCDC_DMA_BURST_SIZE(x) ((x) << 4)
|
||||
#define LCDC_DMA_BURST_SIZE_MASK ((0x7) << 4)
|
||||
#define LCDC_DMA_BURST_1 0x0
|
||||
#define LCDC_DMA_BURST_2 0x1
|
||||
#define LCDC_DMA_BURST_4 0x2
|
||||
#define LCDC_DMA_BURST_8 0x3
|
||||
#define LCDC_DMA_BURST_16 0x4
|
||||
#define LCDC_DMA_FIFO_THRESHOLD(x) ((x) << 8)
|
||||
#define LCDC_DMA_FIFO_THRESHOLD_MASK ((0x3) << 8)
|
||||
#define LCDC_V1_END_OF_FRAME_INT_ENA BIT(2)
|
||||
#define LCDC_V2_END_OF_FRAME0_INT_ENA BIT(8)
|
||||
#define LCDC_V2_END_OF_FRAME1_INT_ENA BIT(9)
|
||||
|
@ -46,10 +49,12 @@
|
|||
|
||||
/* LCDC Control Register */
|
||||
#define LCDC_CLK_DIVISOR(x) ((x) << 8)
|
||||
#define LCDC_CLK_DIVISOR_MASK ((0xFF) << 8)
|
||||
#define LCDC_RASTER_MODE 0x01
|
||||
|
||||
/* LCDC Raster Control Register */
|
||||
#define LCDC_PALETTE_LOAD_MODE(x) ((x) << 20)
|
||||
#define LCDC_PALETTE_LOAD_MODE_MASK ((0x3) << 20)
|
||||
#define PALETTE_AND_DATA 0x00
|
||||
#define PALETTE_ONLY 0x01
|
||||
#define DATA_ONLY 0x02
|
||||
|
@ -61,6 +66,8 @@
|
|||
#define LCDC_V2_UNDERFLOW_INT_ENA BIT(5)
|
||||
#define LCDC_V1_PL_INT_ENA BIT(4)
|
||||
#define LCDC_V2_PL_INT_ENA BIT(6)
|
||||
#define LCDC_V1_SYNC_LOST_INT_ENA BIT(5)
|
||||
#define LCDC_V1_FRAME_DONE_INT_ENA BIT(3)
|
||||
#define LCDC_MONOCHROME_MODE BIT(1)
|
||||
#define LCDC_RASTER_ENABLE BIT(0)
|
||||
#define LCDC_TFT_ALT_ENABLE BIT(23)
|
||||
|
@ -74,7 +81,9 @@
|
|||
|
||||
/* LCDC Raster Timing 2 Register */
|
||||
#define LCDC_AC_BIAS_TRANSITIONS_PER_INT(x) ((x) << 16)
|
||||
#define LCDC_AC_BIAS_TRANSITIONS_PER_INT_MASK ((0xF) << 16)
|
||||
#define LCDC_AC_BIAS_FREQUENCY(x) ((x) << 8)
|
||||
#define LCDC_AC_BIAS_FREQUENCY_MASK ((0xFF) << 8)
|
||||
#define LCDC_SYNC_CTRL BIT(25)
|
||||
#define LCDC_SYNC_EDGE BIT(24)
|
||||
#define LCDC_INVERT_PIXEL_CLOCK BIT(22)
|
||||
|
@ -139,6 +148,12 @@ static inline u32 tilcdc_read(struct drm_device *dev, u32 reg)
|
|||
return ioread32(priv->mmio + reg);
|
||||
}
|
||||
|
||||
static inline void tilcdc_write_mask(struct drm_device *dev, u32 reg,
|
||||
u32 val, u32 mask)
|
||||
{
|
||||
tilcdc_write(dev, reg, (tilcdc_read(dev, reg) & ~mask) | (val & mask));
|
||||
}
|
||||
|
||||
static inline void tilcdc_set(struct drm_device *dev, u32 reg, u32 mask)
|
||||
{
|
||||
tilcdc_write(dev, reg, tilcdc_read(dev, reg) | mask);
|
||||
|
|
|
@ -249,8 +249,6 @@ static struct drm_connector *tfp410_connector_create(struct drm_device *dev,
|
|||
if (ret)
|
||||
goto fail;
|
||||
|
||||
drm_connector_register(connector);
|
||||
|
||||
return connector;
|
||||
|
||||
fail:
|
||||
|
|
Loading…
Reference in a new issue