Merge branch 'for-next' of http://git.agner.ch/git/linux-drm-fsl-dcu into drm-next
This adds very rudimentary TCON (timing controller for raw LCD displays) support to enable the bypass mode in order to use the DCU controller on Freescale/NXP Vybrid SoC's. Additionally the register clock and pixel clock has been separated, but are currently still enabled and disabled pairwise. Other than that, fixes and cleanups accross the driver. * 'for-next' of http://git.agner.ch/git/linux-drm-fsl-dcu: drm/fsl-dcu: increment version and date drm/fsl-dcu: implement lastclose callback drm/fsl-dcu: disable output polling on driver unload drm/fsl-dcu: deallocate fbdev CMA on unload drm/fsl-dcu: use variable name dev for struct drm_device drm/fsl-dcu: handle missing panel gracefully drm/fsl-dcu: detach panel on destroy drm/layerscape: reduce excessive stack usage drm/fsl-dcu: add TCON driver drm/fsl-dcu: use common clock framework for pixel clock divider drm/fsl-dcu: add extra clock for pixel clock drm/fsl-dcu: disable clock on initialization failure and remove
This commit is contained in:
commit
b89359bdf0
|
@ -6,17 +6,24 @@ Required properties:
|
||||||
* "fsl,vf610-dcu".
|
* "fsl,vf610-dcu".
|
||||||
|
|
||||||
- reg: Address and length of the register set for dcu.
|
- reg: Address and length of the register set for dcu.
|
||||||
- clocks: From common clock binding: handle to dcu clock.
|
- clocks: Handle to "dcu" and "pix" clock (in the order below)
|
||||||
- clock-names: From common clock binding: Shall be "dcu".
|
This can be the same clock (e.g. LS1021a)
|
||||||
|
See ../clocks/clock-bindings.txt for details.
|
||||||
|
- clock-names: Should be "dcu" and "pix"
|
||||||
|
See ../clocks/clock-bindings.txt for details.
|
||||||
- big-endian Boolean property, LS1021A DCU registers are big-endian.
|
- big-endian Boolean property, LS1021A DCU registers are big-endian.
|
||||||
- fsl,panel: The phandle to panel node.
|
- fsl,panel: The phandle to panel node.
|
||||||
|
|
||||||
|
Optional properties:
|
||||||
|
- fsl,tcon: The phandle to the timing controller node.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
dcu: dcu@2ce0000 {
|
dcu: dcu@2ce0000 {
|
||||||
compatible = "fsl,ls1021a-dcu";
|
compatible = "fsl,ls1021a-dcu";
|
||||||
reg = <0x0 0x2ce0000 0x0 0x10000>;
|
reg = <0x0 0x2ce0000 0x0 0x10000>;
|
||||||
clocks = <&platform_clk 0>;
|
clocks = <&platform_clk 0>, <&platform_clk 0>;
|
||||||
clock-names = "dcu";
|
clock-names = "dcu", "pix";
|
||||||
big-endian;
|
big-endian;
|
||||||
fsl,panel = <&panel>;
|
fsl,panel = <&panel>;
|
||||||
|
fsl,tcon = <&tcon>;
|
||||||
};
|
};
|
||||||
|
|
18
Documentation/devicetree/bindings/display/fsl,tcon.txt
Normal file
18
Documentation/devicetree/bindings/display/fsl,tcon.txt
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
Device Tree bindings for Freescale TCON Driver
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: Should be one of
|
||||||
|
* "fsl,vf610-tcon".
|
||||||
|
|
||||||
|
- reg: Address and length of the register set for tcon.
|
||||||
|
- clocks: From common clock binding: handle to tcon ipg clock.
|
||||||
|
- clock-names: From common clock binding: Shall be "ipg".
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
timing-controller@4003d000 {
|
||||||
|
compatible = "fsl,vf610-tcon";
|
||||||
|
reg = <0x4003d000 0x1000>;
|
||||||
|
clocks = <&clks VF610_CLK_TCON0>;
|
||||||
|
clock-names = "ipg";
|
||||||
|
status = "okay";
|
||||||
|
};
|
|
@ -3835,6 +3835,7 @@ L: dri-devel@lists.freedesktop.org
|
||||||
S: Supported
|
S: Supported
|
||||||
F: drivers/gpu/drm/fsl-dcu/
|
F: drivers/gpu/drm/fsl-dcu/
|
||||||
F: Documentation/devicetree/bindings/display/fsl,dcu.txt
|
F: Documentation/devicetree/bindings/display/fsl,dcu.txt
|
||||||
|
F: Documentation/devicetree/bindings/display/fsl,tcon.txt
|
||||||
F: Documentation/devicetree/bindings/display/panel/nec,nl4827hc19_05b.txt
|
F: Documentation/devicetree/bindings/display/panel/nec,nl4827hc19_05b.txt
|
||||||
|
|
||||||
DRM DRIVERS FOR FREESCALE IMX
|
DRM DRIVERS FOR FREESCALE IMX
|
||||||
|
|
|
@ -3,5 +3,6 @@ fsl-dcu-drm-y := fsl_dcu_drm_drv.o \
|
||||||
fsl_dcu_drm_rgb.o \
|
fsl_dcu_drm_rgb.o \
|
||||||
fsl_dcu_drm_plane.o \
|
fsl_dcu_drm_plane.o \
|
||||||
fsl_dcu_drm_crtc.o \
|
fsl_dcu_drm_crtc.o \
|
||||||
fsl_dcu_drm_fbdev.o
|
fsl_dcu_drm_fbdev.o \
|
||||||
|
fsl_tcon.o
|
||||||
obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu-drm.o
|
obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu-drm.o
|
||||||
|
|
|
@ -67,12 +67,10 @@ static void fsl_dcu_drm_crtc_mode_set_nofb(struct drm_crtc *crtc)
|
||||||
struct drm_device *dev = crtc->dev;
|
struct drm_device *dev = crtc->dev;
|
||||||
struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
|
struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
|
||||||
struct drm_display_mode *mode = &crtc->state->mode;
|
struct drm_display_mode *mode = &crtc->state->mode;
|
||||||
unsigned int hbp, hfp, hsw, vbp, vfp, vsw, div, index, pol = 0;
|
unsigned int hbp, hfp, hsw, vbp, vfp, vsw, index, pol = 0;
|
||||||
unsigned long dcuclk;
|
|
||||||
|
|
||||||
index = drm_crtc_index(crtc);
|
index = drm_crtc_index(crtc);
|
||||||
dcuclk = clk_get_rate(fsl_dev->clk);
|
clk_set_rate(fsl_dev->pix_clk, mode->clock * 1000);
|
||||||
div = dcuclk / mode->clock / 1000;
|
|
||||||
|
|
||||||
/* Configure timings: */
|
/* Configure timings: */
|
||||||
hbp = mode->htotal - mode->hsync_end;
|
hbp = mode->htotal - mode->hsync_end;
|
||||||
|
@ -99,7 +97,6 @@ static void fsl_dcu_drm_crtc_mode_set_nofb(struct drm_crtc *crtc)
|
||||||
regmap_write(fsl_dev->regmap, DCU_DISP_SIZE,
|
regmap_write(fsl_dev->regmap, DCU_DISP_SIZE,
|
||||||
DCU_DISP_SIZE_DELTA_Y(mode->vdisplay) |
|
DCU_DISP_SIZE_DELTA_Y(mode->vdisplay) |
|
||||||
DCU_DISP_SIZE_DELTA_X(mode->hdisplay));
|
DCU_DISP_SIZE_DELTA_X(mode->hdisplay));
|
||||||
regmap_write(fsl_dev->regmap, DCU_DIV_RATIO, div);
|
|
||||||
regmap_write(fsl_dev->regmap, DCU_SYN_POL, pol);
|
regmap_write(fsl_dev->regmap, DCU_SYN_POL, pol);
|
||||||
regmap_write(fsl_dev->regmap, DCU_BGND, DCU_BGND_R(0) |
|
regmap_write(fsl_dev->regmap, DCU_BGND, DCU_BGND_R(0) |
|
||||||
DCU_BGND_G(0) | DCU_BGND_B(0));
|
DCU_BGND_G(0) | DCU_BGND_B(0));
|
||||||
|
|
|
@ -23,10 +23,12 @@
|
||||||
|
|
||||||
#include <drm/drmP.h>
|
#include <drm/drmP.h>
|
||||||
#include <drm/drm_crtc_helper.h>
|
#include <drm/drm_crtc_helper.h>
|
||||||
|
#include <drm/drm_fb_cma_helper.h>
|
||||||
#include <drm/drm_gem_cma_helper.h>
|
#include <drm/drm_gem_cma_helper.h>
|
||||||
|
|
||||||
#include "fsl_dcu_drm_crtc.h"
|
#include "fsl_dcu_drm_crtc.h"
|
||||||
#include "fsl_dcu_drm_drv.h"
|
#include "fsl_dcu_drm_drv.h"
|
||||||
|
#include "fsl_tcon.h"
|
||||||
|
|
||||||
static bool fsl_dcu_drm_is_volatile_reg(struct device *dev, unsigned int reg)
|
static bool fsl_dcu_drm_is_volatile_reg(struct device *dev, unsigned int reg)
|
||||||
{
|
{
|
||||||
|
@ -62,46 +64,55 @@ static int fsl_dcu_drm_irq_init(struct drm_device *dev)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int fsl_dcu_load(struct drm_device *drm, unsigned long flags)
|
static int fsl_dcu_load(struct drm_device *dev, unsigned long flags)
|
||||||
{
|
{
|
||||||
struct device *dev = drm->dev;
|
struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
|
||||||
struct fsl_dcu_drm_device *fsl_dev = drm->dev_private;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = fsl_dcu_drm_modeset_init(fsl_dev);
|
ret = fsl_dcu_drm_modeset_init(fsl_dev);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
dev_err(dev, "failed to initialize mode setting\n");
|
dev_err(dev->dev, "failed to initialize mode setting\n");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
|
ret = drm_vblank_init(dev, dev->mode_config.num_crtc);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
dev_err(dev, "failed to initialize vblank\n");
|
dev_err(dev->dev, "failed to initialize vblank\n");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
drm->vblank_disable_allowed = true;
|
dev->vblank_disable_allowed = true;
|
||||||
|
|
||||||
ret = fsl_dcu_drm_irq_init(drm);
|
ret = fsl_dcu_drm_irq_init(dev);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto done;
|
goto done;
|
||||||
drm->irq_enabled = true;
|
dev->irq_enabled = true;
|
||||||
|
|
||||||
fsl_dcu_fbdev_init(drm);
|
fsl_dcu_fbdev_init(dev);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
done:
|
done:
|
||||||
if (ret) {
|
drm_kms_helper_poll_fini(dev);
|
||||||
drm_mode_config_cleanup(drm);
|
|
||||||
drm_vblank_cleanup(drm);
|
if (fsl_dev->fbdev)
|
||||||
drm_irq_uninstall(drm);
|
drm_fbdev_cma_fini(fsl_dev->fbdev);
|
||||||
drm->dev_private = NULL;
|
|
||||||
}
|
drm_mode_config_cleanup(dev);
|
||||||
|
drm_vblank_cleanup(dev);
|
||||||
|
drm_irq_uninstall(dev);
|
||||||
|
dev->dev_private = NULL;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int fsl_dcu_unload(struct drm_device *dev)
|
static int fsl_dcu_unload(struct drm_device *dev)
|
||||||
{
|
{
|
||||||
|
struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
|
||||||
|
|
||||||
|
drm_kms_helper_poll_fini(dev);
|
||||||
|
|
||||||
|
if (fsl_dev->fbdev)
|
||||||
|
drm_fbdev_cma_fini(fsl_dev->fbdev);
|
||||||
|
|
||||||
drm_mode_config_cleanup(dev);
|
drm_mode_config_cleanup(dev);
|
||||||
drm_vblank_cleanup(dev);
|
drm_vblank_cleanup(dev);
|
||||||
drm_irq_uninstall(dev);
|
drm_irq_uninstall(dev);
|
||||||
|
@ -157,6 +168,13 @@ static void fsl_dcu_drm_disable_vblank(struct drm_device *dev,
|
||||||
regmap_write(fsl_dev->regmap, DCU_INT_MASK, value);
|
regmap_write(fsl_dev->regmap, DCU_INT_MASK, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void fsl_dcu_drm_lastclose(struct drm_device *dev)
|
||||||
|
{
|
||||||
|
struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
|
||||||
|
|
||||||
|
drm_fbdev_cma_restore_mode(fsl_dev->fbdev);
|
||||||
|
}
|
||||||
|
|
||||||
static const struct file_operations fsl_dcu_drm_fops = {
|
static const struct file_operations fsl_dcu_drm_fops = {
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.open = drm_open,
|
.open = drm_open,
|
||||||
|
@ -174,6 +192,7 @@ static const struct file_operations fsl_dcu_drm_fops = {
|
||||||
static struct drm_driver fsl_dcu_drm_driver = {
|
static struct drm_driver fsl_dcu_drm_driver = {
|
||||||
.driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET
|
.driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET
|
||||||
| DRIVER_PRIME | DRIVER_ATOMIC,
|
| DRIVER_PRIME | DRIVER_ATOMIC,
|
||||||
|
.lastclose = fsl_dcu_drm_lastclose,
|
||||||
.load = fsl_dcu_load,
|
.load = fsl_dcu_load,
|
||||||
.unload = fsl_dcu_unload,
|
.unload = fsl_dcu_unload,
|
||||||
.irq_handler = fsl_dcu_drm_irq,
|
.irq_handler = fsl_dcu_drm_irq,
|
||||||
|
@ -197,9 +216,9 @@ static struct drm_driver fsl_dcu_drm_driver = {
|
||||||
.fops = &fsl_dcu_drm_fops,
|
.fops = &fsl_dcu_drm_fops,
|
||||||
.name = "fsl-dcu-drm",
|
.name = "fsl-dcu-drm",
|
||||||
.desc = "Freescale DCU DRM",
|
.desc = "Freescale DCU DRM",
|
||||||
.date = "20150213",
|
.date = "20160425",
|
||||||
.major = 1,
|
.major = 1,
|
||||||
.minor = 0,
|
.minor = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef CONFIG_PM_SLEEP
|
#ifdef CONFIG_PM_SLEEP
|
||||||
|
@ -283,6 +302,9 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev)
|
||||||
struct resource *res;
|
struct resource *res;
|
||||||
void __iomem *base;
|
void __iomem *base;
|
||||||
struct drm_driver *driver = &fsl_dcu_drm_driver;
|
struct drm_driver *driver = &fsl_dcu_drm_driver;
|
||||||
|
struct clk *pix_clk_in;
|
||||||
|
char pix_clk_name[32];
|
||||||
|
const char *pix_clk_in_name;
|
||||||
const struct of_device_id *id;
|
const struct of_device_id *id;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
@ -290,6 +312,11 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev)
|
||||||
if (!fsl_dev)
|
if (!fsl_dev)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
id = of_match_node(fsl_dcu_of_match, pdev->dev.of_node);
|
||||||
|
if (!id)
|
||||||
|
return -ENODEV;
|
||||||
|
fsl_dev->soc = id->data;
|
||||||
|
|
||||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
dev_err(dev, "could not get memory IO resource\n");
|
dev_err(dev, "could not get memory IO resource\n");
|
||||||
|
@ -308,24 +335,6 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev)
|
||||||
return -ENXIO;
|
return -ENXIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
fsl_dev->clk = devm_clk_get(dev, "dcu");
|
|
||||||
if (IS_ERR(fsl_dev->clk)) {
|
|
||||||
ret = PTR_ERR(fsl_dev->clk);
|
|
||||||
dev_err(dev, "failed to get dcu clock\n");
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
ret = clk_prepare(fsl_dev->clk);
|
|
||||||
if (ret < 0) {
|
|
||||||
dev_err(dev, "failed to prepare dcu clk\n");
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
ret = clk_enable(fsl_dev->clk);
|
|
||||||
if (ret < 0) {
|
|
||||||
dev_err(dev, "failed to enable dcu clk\n");
|
|
||||||
clk_unprepare(fsl_dev->clk);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
fsl_dev->regmap = devm_regmap_init_mmio(dev, base,
|
fsl_dev->regmap = devm_regmap_init_mmio(dev, base,
|
||||||
&fsl_dcu_regmap_config);
|
&fsl_dcu_regmap_config);
|
||||||
if (IS_ERR(fsl_dev->regmap)) {
|
if (IS_ERR(fsl_dev->regmap)) {
|
||||||
|
@ -333,14 +342,47 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev)
|
||||||
return PTR_ERR(fsl_dev->regmap);
|
return PTR_ERR(fsl_dev->regmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
id = of_match_node(fsl_dcu_of_match, pdev->dev.of_node);
|
fsl_dev->clk = devm_clk_get(dev, "dcu");
|
||||||
if (!id)
|
if (IS_ERR(fsl_dev->clk)) {
|
||||||
return -ENODEV;
|
dev_err(dev, "failed to get dcu clock\n");
|
||||||
fsl_dev->soc = id->data;
|
return PTR_ERR(fsl_dev->clk);
|
||||||
|
}
|
||||||
|
ret = clk_prepare_enable(fsl_dev->clk);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(dev, "failed to enable dcu clk\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
pix_clk_in = devm_clk_get(dev, "pix");
|
||||||
|
if (IS_ERR(pix_clk_in)) {
|
||||||
|
/* legancy binding, use dcu clock as pixel clock input */
|
||||||
|
pix_clk_in = fsl_dev->clk;
|
||||||
|
}
|
||||||
|
|
||||||
|
pix_clk_in_name = __clk_get_name(pix_clk_in);
|
||||||
|
snprintf(pix_clk_name, sizeof(pix_clk_name), "%s_pix", pix_clk_in_name);
|
||||||
|
fsl_dev->pix_clk = clk_register_divider(dev, pix_clk_name,
|
||||||
|
pix_clk_in_name, 0, base + DCU_DIV_RATIO,
|
||||||
|
0, 8, CLK_DIVIDER_ROUND_CLOSEST, NULL);
|
||||||
|
if (IS_ERR(fsl_dev->pix_clk)) {
|
||||||
|
dev_err(dev, "failed to register pix clk\n");
|
||||||
|
ret = PTR_ERR(fsl_dev->pix_clk);
|
||||||
|
goto disable_clk;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(fsl_dev->pix_clk);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(dev, "failed to enable pix clk\n");
|
||||||
|
goto unregister_pix_clk;
|
||||||
|
}
|
||||||
|
|
||||||
|
fsl_dev->tcon = fsl_tcon_init(dev);
|
||||||
|
|
||||||
drm = drm_dev_alloc(driver, dev);
|
drm = drm_dev_alloc(driver, dev);
|
||||||
if (!drm)
|
if (!drm) {
|
||||||
return -ENOMEM;
|
ret = -ENOMEM;
|
||||||
|
goto disable_pix_clk;
|
||||||
|
}
|
||||||
|
|
||||||
fsl_dev->dev = dev;
|
fsl_dev->dev = dev;
|
||||||
fsl_dev->drm = drm;
|
fsl_dev->drm = drm;
|
||||||
|
@ -360,6 +402,12 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
unref:
|
unref:
|
||||||
drm_dev_unref(drm);
|
drm_dev_unref(drm);
|
||||||
|
disable_pix_clk:
|
||||||
|
clk_disable_unprepare(fsl_dev->pix_clk);
|
||||||
|
unregister_pix_clk:
|
||||||
|
clk_unregister(fsl_dev->pix_clk);
|
||||||
|
disable_clk:
|
||||||
|
clk_disable_unprepare(fsl_dev->clk);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,6 +415,9 @@ static int fsl_dcu_drm_remove(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct fsl_dcu_drm_device *fsl_dev = platform_get_drvdata(pdev);
|
struct fsl_dcu_drm_device *fsl_dev = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
clk_disable_unprepare(fsl_dev->clk);
|
||||||
|
clk_disable_unprepare(fsl_dev->pix_clk);
|
||||||
|
clk_unregister(fsl_dev->pix_clk);
|
||||||
drm_put_dev(fsl_dev->drm);
|
drm_put_dev(fsl_dev->drm);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -183,6 +183,8 @@ struct fsl_dcu_drm_device {
|
||||||
struct regmap *regmap;
|
struct regmap *regmap;
|
||||||
int irq;
|
int irq;
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
|
struct clk *pix_clk;
|
||||||
|
struct fsl_tcon *tcon;
|
||||||
/*protects hardware register*/
|
/*protects hardware register*/
|
||||||
spinlock_t irq_lock;
|
spinlock_t irq_lock;
|
||||||
struct drm_device *drm;
|
struct drm_device *drm;
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <drm/drm_panel.h>
|
#include <drm/drm_panel.h>
|
||||||
|
|
||||||
#include "fsl_dcu_drm_drv.h"
|
#include "fsl_dcu_drm_drv.h"
|
||||||
|
#include "fsl_tcon.h"
|
||||||
|
|
||||||
static int
|
static int
|
||||||
fsl_dcu_drm_encoder_atomic_check(struct drm_encoder *encoder,
|
fsl_dcu_drm_encoder_atomic_check(struct drm_encoder *encoder,
|
||||||
|
@ -28,10 +29,20 @@ fsl_dcu_drm_encoder_atomic_check(struct drm_encoder *encoder,
|
||||||
|
|
||||||
static void fsl_dcu_drm_encoder_disable(struct drm_encoder *encoder)
|
static void fsl_dcu_drm_encoder_disable(struct drm_encoder *encoder)
|
||||||
{
|
{
|
||||||
|
struct drm_device *dev = encoder->dev;
|
||||||
|
struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
|
||||||
|
|
||||||
|
if (fsl_dev->tcon)
|
||||||
|
fsl_tcon_bypass_disable(fsl_dev->tcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fsl_dcu_drm_encoder_enable(struct drm_encoder *encoder)
|
static void fsl_dcu_drm_encoder_enable(struct drm_encoder *encoder)
|
||||||
{
|
{
|
||||||
|
struct drm_device *dev = encoder->dev;
|
||||||
|
struct fsl_dcu_drm_device *fsl_dev = dev->dev_private;
|
||||||
|
|
||||||
|
if (fsl_dev->tcon)
|
||||||
|
fsl_tcon_bypass_enable(fsl_dev->tcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
|
static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
|
||||||
|
@ -68,7 +79,10 @@ int fsl_dcu_drm_encoder_create(struct fsl_dcu_drm_device *fsl_dev,
|
||||||
|
|
||||||
static void fsl_dcu_drm_connector_destroy(struct drm_connector *connector)
|
static void fsl_dcu_drm_connector_destroy(struct drm_connector *connector)
|
||||||
{
|
{
|
||||||
|
struct fsl_dcu_drm_connector *fsl_con = to_fsl_dcu_connector(connector);
|
||||||
|
|
||||||
drm_connector_unregister(connector);
|
drm_connector_unregister(connector);
|
||||||
|
drm_panel_detach(fsl_con->panel);
|
||||||
drm_connector_cleanup(connector);
|
drm_connector_cleanup(connector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +145,7 @@ int fsl_dcu_drm_connector_create(struct fsl_dcu_drm_device *fsl_dev,
|
||||||
struct drm_encoder *encoder)
|
struct drm_encoder *encoder)
|
||||||
{
|
{
|
||||||
struct drm_connector *connector = &fsl_dev->connector.base;
|
struct drm_connector *connector = &fsl_dev->connector.base;
|
||||||
struct drm_mode_config mode_config = fsl_dev->drm->mode_config;
|
struct drm_mode_config *mode_config = &fsl_dev->drm->mode_config;
|
||||||
struct device_node *panel_node;
|
struct device_node *panel_node;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
@ -153,18 +167,22 @@ int fsl_dcu_drm_connector_create(struct fsl_dcu_drm_device *fsl_dev,
|
||||||
goto err_sysfs;
|
goto err_sysfs;
|
||||||
|
|
||||||
drm_object_property_set_value(&connector->base,
|
drm_object_property_set_value(&connector->base,
|
||||||
mode_config.dpms_property,
|
mode_config->dpms_property,
|
||||||
DRM_MODE_DPMS_OFF);
|
DRM_MODE_DPMS_OFF);
|
||||||
|
|
||||||
panel_node = of_parse_phandle(fsl_dev->np, "fsl,panel", 0);
|
panel_node = of_parse_phandle(fsl_dev->np, "fsl,panel", 0);
|
||||||
if (panel_node) {
|
if (!panel_node) {
|
||||||
|
dev_err(fsl_dev->dev, "fsl,panel property not found\n");
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto err_sysfs;
|
||||||
|
}
|
||||||
|
|
||||||
fsl_dev->connector.panel = of_drm_find_panel(panel_node);
|
fsl_dev->connector.panel = of_drm_find_panel(panel_node);
|
||||||
if (!fsl_dev->connector.panel) {
|
if (!fsl_dev->connector.panel) {
|
||||||
ret = -EPROBE_DEFER;
|
ret = -EPROBE_DEFER;
|
||||||
goto err_sysfs;
|
goto err_panel;
|
||||||
}
|
}
|
||||||
of_node_put(panel_node);
|
of_node_put(panel_node);
|
||||||
}
|
|
||||||
|
|
||||||
ret = drm_panel_attach(fsl_dev->connector.panel, connector);
|
ret = drm_panel_attach(fsl_dev->connector.panel, connector);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
@ -174,6 +192,8 @@ int fsl_dcu_drm_connector_create(struct fsl_dcu_drm_device *fsl_dev,
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
err_panel:
|
||||||
|
of_node_put(panel_node);
|
||||||
err_sysfs:
|
err_sysfs:
|
||||||
drm_connector_unregister(connector);
|
drm_connector_unregister(connector);
|
||||||
err_cleanup:
|
err_cleanup:
|
||||||
|
|
111
drivers/gpu/drm/fsl-dcu/fsl_tcon.c
Normal file
111
drivers/gpu/drm/fsl-dcu/fsl_tcon.c
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Toradex AG
|
||||||
|
*
|
||||||
|
* Stefan Agner <stefan@agner.ch>
|
||||||
|
*
|
||||||
|
* Freescale TCON device driver
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/clk.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/mm.h>
|
||||||
|
#include <linux/of_address.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
|
||||||
|
#include "fsl_tcon.h"
|
||||||
|
|
||||||
|
void fsl_tcon_bypass_disable(struct fsl_tcon *tcon)
|
||||||
|
{
|
||||||
|
regmap_update_bits(tcon->regs, FSL_TCON_CTRL1,
|
||||||
|
FSL_TCON_CTRL1_TCON_BYPASS, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fsl_tcon_bypass_enable(struct fsl_tcon *tcon)
|
||||||
|
{
|
||||||
|
regmap_update_bits(tcon->regs, FSL_TCON_CTRL1,
|
||||||
|
FSL_TCON_CTRL1_TCON_BYPASS,
|
||||||
|
FSL_TCON_CTRL1_TCON_BYPASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct regmap_config fsl_tcon_regmap_config = {
|
||||||
|
.reg_bits = 32,
|
||||||
|
.reg_stride = 4,
|
||||||
|
.val_bits = 32,
|
||||||
|
|
||||||
|
.name = "tcon",
|
||||||
|
};
|
||||||
|
|
||||||
|
static int fsl_tcon_init_regmap(struct device *dev,
|
||||||
|
struct fsl_tcon *tcon,
|
||||||
|
struct device_node *np)
|
||||||
|
{
|
||||||
|
struct resource res;
|
||||||
|
void __iomem *regs;
|
||||||
|
|
||||||
|
if (of_address_to_resource(np, 0, &res))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
regs = devm_ioremap_resource(dev, &res);
|
||||||
|
if (IS_ERR(regs))
|
||||||
|
return PTR_ERR(regs);
|
||||||
|
|
||||||
|
tcon->regs = devm_regmap_init_mmio(dev, regs,
|
||||||
|
&fsl_tcon_regmap_config);
|
||||||
|
if (IS_ERR(tcon->regs))
|
||||||
|
return PTR_ERR(tcon->regs);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fsl_tcon *fsl_tcon_init(struct device *dev)
|
||||||
|
{
|
||||||
|
struct fsl_tcon *tcon;
|
||||||
|
struct device_node *np;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* TCON node is not mandatory, some devices do not provide TCON */
|
||||||
|
np = of_parse_phandle(dev->of_node, "fsl,tcon", 0);
|
||||||
|
if (!np)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL);
|
||||||
|
if (!tcon) {
|
||||||
|
ret = -ENOMEM;
|
||||||
|
goto err_node_put;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = fsl_tcon_init_regmap(dev, tcon, np);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev, "Couldn't create the TCON regmap\n");
|
||||||
|
goto err_node_put;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcon->ipg_clk = of_clk_get_by_name(np, "ipg");
|
||||||
|
if (IS_ERR(tcon->ipg_clk)) {
|
||||||
|
dev_err(dev, "Couldn't get the TCON bus clock\n");
|
||||||
|
goto err_node_put;
|
||||||
|
}
|
||||||
|
|
||||||
|
clk_prepare_enable(tcon->ipg_clk);
|
||||||
|
|
||||||
|
dev_info(dev, "Using TCON in bypass mode\n");
|
||||||
|
|
||||||
|
return tcon;
|
||||||
|
|
||||||
|
err_node_put:
|
||||||
|
of_node_put(np);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fsl_tcon_free(struct fsl_tcon *tcon)
|
||||||
|
{
|
||||||
|
clk_disable_unprepare(tcon->ipg_clk);
|
||||||
|
clk_put(tcon->ipg_clk);
|
||||||
|
}
|
||||||
|
|
33
drivers/gpu/drm/fsl-dcu/fsl_tcon.h
Normal file
33
drivers/gpu/drm/fsl-dcu/fsl_tcon.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Toradex AG
|
||||||
|
*
|
||||||
|
* Stefan Agner <stefan@agner.ch>
|
||||||
|
*
|
||||||
|
* Freescale TCON device driver
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __FSL_TCON_H__
|
||||||
|
#define __FSL_TCON_H__
|
||||||
|
|
||||||
|
#include <linux/bitops.h>
|
||||||
|
|
||||||
|
#define FSL_TCON_CTRL1 0x0
|
||||||
|
#define FSL_TCON_CTRL1_TCON_BYPASS BIT(29)
|
||||||
|
|
||||||
|
struct fsl_tcon {
|
||||||
|
struct regmap *regs;
|
||||||
|
struct clk *ipg_clk;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fsl_tcon *fsl_tcon_init(struct device *dev);
|
||||||
|
void fsl_tcon_free(struct fsl_tcon *tcon);
|
||||||
|
|
||||||
|
void fsl_tcon_bypass_disable(struct fsl_tcon *tcon);
|
||||||
|
void fsl_tcon_bypass_enable(struct fsl_tcon *tcon);
|
||||||
|
|
||||||
|
#endif /* __FSL_TCON_H__ */
|
Loading…
Reference in a new issue