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:
Dave Airlie 2016-12-01 09:26:55 +10:00
commit 0d5320fc19
13 changed files with 1039 additions and 402 deletions

View file

@ -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
-------

View file

@ -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>;

View file

@ -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"

View file

@ -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

View 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", &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");

View file

@ -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;
}

View file

@ -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);

View file

@ -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);

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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:

View file

@ -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);

View file

@ -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: