diff --git a/Documentation/devicetree/bindings/display/fsl,dcu.txt b/Documentation/devicetree/bindings/display/fsl,dcu.txt index ebf1be9ae393..ae55cde1b69e 100644 --- a/Documentation/devicetree/bindings/display/fsl,dcu.txt +++ b/Documentation/devicetree/bindings/display/fsl,dcu.txt @@ -6,17 +6,24 @@ Required properties: * "fsl,vf610-dcu". - reg: Address and length of the register set for dcu. -- clocks: From common clock binding: handle to dcu clock. -- clock-names: From common clock binding: Shall be "dcu". +- clocks: Handle to "dcu" and "pix" clock (in the order below) + 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. - fsl,panel: The phandle to panel node. +Optional properties: +- fsl,tcon: The phandle to the timing controller node. + Examples: dcu: dcu@2ce0000 { compatible = "fsl,ls1021a-dcu"; reg = <0x0 0x2ce0000 0x0 0x10000>; - clocks = <&platform_clk 0>; - clock-names = "dcu"; + clocks = <&platform_clk 0>, <&platform_clk 0>; + clock-names = "dcu", "pix"; big-endian; fsl,panel = <&panel>; + fsl,tcon = <&tcon>; }; diff --git a/Documentation/devicetree/bindings/display/fsl,tcon.txt b/Documentation/devicetree/bindings/display/fsl,tcon.txt new file mode 100644 index 000000000000..6fa4ab668db5 --- /dev/null +++ b/Documentation/devicetree/bindings/display/fsl,tcon.txt @@ -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"; +}; diff --git a/MAINTAINERS b/MAINTAINERS index 4c38e9e7ef62..9ef7b408beb3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3835,6 +3835,7 @@ L: dri-devel@lists.freedesktop.org S: Supported F: drivers/gpu/drm/fsl-dcu/ 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 DRM DRIVERS FOR FREESCALE IMX diff --git a/drivers/gpu/drm/fsl-dcu/Makefile b/drivers/gpu/drm/fsl-dcu/Makefile index 6ea1523ae6ec..b35a292287f3 100644 --- a/drivers/gpu/drm/fsl-dcu/Makefile +++ b/drivers/gpu/drm/fsl-dcu/Makefile @@ -3,5 +3,6 @@ fsl-dcu-drm-y := fsl_dcu_drm_drv.o \ fsl_dcu_drm_rgb.o \ fsl_dcu_drm_plane.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 diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c index 4ed7798533f9..365809edf29a 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c @@ -67,12 +67,10 @@ static void fsl_dcu_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) struct drm_device *dev = crtc->dev; struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; struct drm_display_mode *mode = &crtc->state->mode; - unsigned int hbp, hfp, hsw, vbp, vfp, vsw, div, index, pol = 0; - unsigned long dcuclk; + unsigned int hbp, hfp, hsw, vbp, vfp, vsw, index, pol = 0; index = drm_crtc_index(crtc); - dcuclk = clk_get_rate(fsl_dev->clk); - div = dcuclk / mode->clock / 1000; + clk_set_rate(fsl_dev->pix_clk, mode->clock * 1000); /* Configure timings: */ 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, DCU_DISP_SIZE_DELTA_Y(mode->vdisplay) | 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_BGND, DCU_BGND_R(0) | DCU_BGND_G(0) | DCU_BGND_B(0)); diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c index e8d9337a66d8..44f6f262d75a 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c @@ -23,10 +23,12 @@ #include #include +#include #include #include "fsl_dcu_drm_crtc.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) { @@ -62,46 +64,55 @@ static int fsl_dcu_drm_irq_init(struct drm_device *dev) 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 = drm->dev_private; + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; int ret; ret = fsl_dcu_drm_modeset_init(fsl_dev); if (ret < 0) { - dev_err(dev, "failed to initialize mode setting\n"); + dev_err(dev->dev, "failed to initialize mode setting\n"); 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) { - dev_err(dev, "failed to initialize vblank\n"); + dev_err(dev->dev, "failed to initialize vblank\n"); 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) goto done; - drm->irq_enabled = true; + dev->irq_enabled = true; - fsl_dcu_fbdev_init(drm); + fsl_dcu_fbdev_init(dev); return 0; done: - if (ret) { - drm_mode_config_cleanup(drm); - drm_vblank_cleanup(drm); - drm_irq_uninstall(drm); - drm->dev_private = NULL; - } + drm_kms_helper_poll_fini(dev); + + if (fsl_dev->fbdev) + drm_fbdev_cma_fini(fsl_dev->fbdev); + + drm_mode_config_cleanup(dev); + drm_vblank_cleanup(dev); + drm_irq_uninstall(dev); + dev->dev_private = NULL; return ret; } 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_vblank_cleanup(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); } +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 = { .owner = THIS_MODULE, .open = drm_open, @@ -174,6 +192,7 @@ static const struct file_operations fsl_dcu_drm_fops = { static struct drm_driver fsl_dcu_drm_driver = { .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC, + .lastclose = fsl_dcu_drm_lastclose, .load = fsl_dcu_load, .unload = fsl_dcu_unload, .irq_handler = fsl_dcu_drm_irq, @@ -197,9 +216,9 @@ static struct drm_driver fsl_dcu_drm_driver = { .fops = &fsl_dcu_drm_fops, .name = "fsl-dcu-drm", .desc = "Freescale DCU DRM", - .date = "20150213", + .date = "20160425", .major = 1, - .minor = 0, + .minor = 1, }; #ifdef CONFIG_PM_SLEEP @@ -283,6 +302,9 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev) struct resource *res; void __iomem *base; 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; int ret; @@ -290,6 +312,11 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev) if (!fsl_dev) 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); if (!res) { 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; } - 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_dcu_regmap_config); 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); } - id = of_match_node(fsl_dcu_of_match, pdev->dev.of_node); - if (!id) - return -ENODEV; - fsl_dev->soc = id->data; + fsl_dev->clk = devm_clk_get(dev, "dcu"); + if (IS_ERR(fsl_dev->clk)) { + dev_err(dev, "failed to get dcu clock\n"); + 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); - if (!drm) - return -ENOMEM; + if (!drm) { + ret = -ENOMEM; + goto disable_pix_clk; + } fsl_dev->dev = dev; fsl_dev->drm = drm; @@ -360,6 +402,12 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev) unref: 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; } @@ -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); + 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); return 0; diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h index 6413ac9e4769..5bb7c261fe95 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h @@ -183,6 +183,8 @@ struct fsl_dcu_drm_device { struct regmap *regmap; int irq; struct clk *clk; + struct clk *pix_clk; + struct fsl_tcon *tcon; /*protects hardware register*/ spinlock_t irq_lock; struct drm_device *drm; diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c index 8780deba5e8a..98c998da91eb 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c @@ -17,6 +17,7 @@ #include #include "fsl_dcu_drm_drv.h" +#include "fsl_tcon.h" static int 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) { + 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) { + 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 = { @@ -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) { + struct fsl_dcu_drm_connector *fsl_con = to_fsl_dcu_connector(connector); + drm_connector_unregister(connector); + drm_panel_detach(fsl_con->panel); 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_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; int ret; @@ -153,19 +167,23 @@ int fsl_dcu_drm_connector_create(struct fsl_dcu_drm_device *fsl_dev, goto err_sysfs; drm_object_property_set_value(&connector->base, - mode_config.dpms_property, + mode_config->dpms_property, DRM_MODE_DPMS_OFF); panel_node = of_parse_phandle(fsl_dev->np, "fsl,panel", 0); - if (panel_node) { - fsl_dev->connector.panel = of_drm_find_panel(panel_node); - if (!fsl_dev->connector.panel) { - ret = -EPROBE_DEFER; - goto err_sysfs; - } - of_node_put(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); + if (!fsl_dev->connector.panel) { + ret = -EPROBE_DEFER; + goto err_panel; + } + of_node_put(panel_node); + ret = drm_panel_attach(fsl_dev->connector.panel, connector); if (ret) { dev_err(fsl_dev->dev, "failed to attach panel\n"); @@ -174,6 +192,8 @@ int fsl_dcu_drm_connector_create(struct fsl_dcu_drm_device *fsl_dev, return 0; +err_panel: + of_node_put(panel_node); err_sysfs: drm_connector_unregister(connector); err_cleanup: diff --git a/drivers/gpu/drm/fsl-dcu/fsl_tcon.c b/drivers/gpu/drm/fsl-dcu/fsl_tcon.c new file mode 100644 index 000000000000..bbe34f1c0505 --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_tcon.c @@ -0,0 +1,111 @@ +/* + * Copyright 2015 Toradex AG + * + * Stefan Agner + * + * 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 +#include +#include +#include +#include +#include + +#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); +} + diff --git a/drivers/gpu/drm/fsl-dcu/fsl_tcon.h b/drivers/gpu/drm/fsl-dcu/fsl_tcon.h new file mode 100644 index 000000000000..80a7617de58f --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_tcon.h @@ -0,0 +1,33 @@ +/* + * Copyright 2015 Toradex AG + * + * Stefan Agner + * + * 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 + +#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__ */