diff options
Diffstat (limited to 'drivers/gpu/drm/fsl-dcu')
-rw-r--r-- | drivers/gpu/drm/fsl-dcu/Kconfig | 13 | ||||
-rw-r--r-- | drivers/gpu/drm/fsl-dcu/Makefile | 8 | ||||
-rw-r--r-- | drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c | 192 | ||||
-rw-r--r-- | drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.h | 15 | ||||
-rw-r--r-- | drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c | 372 | ||||
-rw-r--r-- | drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h | 198 | ||||
-rw-r--r-- | drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_kms.c | 53 | ||||
-rw-r--r-- | drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_output.h | 28 | ||||
-rw-r--r-- | drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.c | 230 | ||||
-rw-r--r-- | drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.h | 14 | ||||
-rw-r--r-- | drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c | 139 | ||||
-rw-r--r-- | drivers/gpu/drm/fsl-dcu/fsl_tcon.c | 107 | ||||
-rw-r--r-- | drivers/gpu/drm/fsl-dcu/fsl_tcon.h | 29 |
13 files changed, 1398 insertions, 0 deletions
diff --git a/drivers/gpu/drm/fsl-dcu/Kconfig b/drivers/gpu/drm/fsl-dcu/Kconfig new file mode 100644 index 0000000000..5ca71ef873 --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_FSL_DCU + tristate "DRM Support for Freescale DCU" + depends on DRM && OF && ARM && COMMON_CLK + select BACKLIGHT_CLASS_DEVICE + select DRM_GEM_DMA_HELPER + select DRM_KMS_HELPER + select DRM_PANEL + select REGMAP_MMIO + select VIDEOMODE_HELPERS + help + Choose this option if you have an Freescale DCU chipset. + If M is selected the module will be called fsl-dcu-drm. diff --git a/drivers/gpu/drm/fsl-dcu/Makefile b/drivers/gpu/drm/fsl-dcu/Makefile new file mode 100644 index 0000000000..b55c4482d0 --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +fsl-dcu-drm-y := fsl_dcu_drm_drv.o \ + fsl_dcu_drm_kms.o \ + fsl_dcu_drm_rgb.o \ + fsl_dcu_drm_plane.o \ + fsl_dcu_drm_crtc.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 new file mode 100644 index 0000000000..2af60d98f4 --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2015 Freescale Semiconductor, Inc. + * + * Freescale DCU drm device driver + */ + +#include <linux/clk.h> +#include <linux/regmap.h> + +#include <video/videomode.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "fsl_dcu_drm_crtc.h" +#include "fsl_dcu_drm_drv.h" +#include "fsl_dcu_drm_plane.h" + +static void fsl_dcu_drm_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *dev = crtc->dev; + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + struct drm_pending_vblank_event *event = crtc->state->event; + + regmap_write(fsl_dev->regmap, + DCU_UPDATE_MODE, DCU_UPDATE_MODE_READREG); + + if (event) { + crtc->state->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + if (drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); + } +} + +static void fsl_dcu_drm_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, + crtc); + struct drm_device *dev = crtc->dev; + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + + /* always disable planes on the CRTC */ + drm_atomic_helper_disable_planes_on_crtc(old_crtc_state, true); + + drm_crtc_vblank_off(crtc); + + regmap_update_bits(fsl_dev->regmap, DCU_DCU_MODE, + DCU_MODE_DCU_MODE_MASK, + DCU_MODE_DCU_MODE(DCU_MODE_OFF)); + regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE, + DCU_UPDATE_MODE_READREG); + clk_disable_unprepare(fsl_dev->pix_clk); +} + +static void fsl_dcu_drm_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *dev = crtc->dev; + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + + clk_prepare_enable(fsl_dev->pix_clk); + regmap_update_bits(fsl_dev->regmap, DCU_DCU_MODE, + DCU_MODE_DCU_MODE_MASK, + DCU_MODE_DCU_MODE(DCU_MODE_NORMAL)); + regmap_write(fsl_dev->regmap, DCU_UPDATE_MODE, + DCU_UPDATE_MODE_READREG); + + drm_crtc_vblank_on(crtc); +} + +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_connector *con = &fsl_dev->connector.base; + struct drm_display_mode *mode = &crtc->state->mode; + unsigned int pol = 0; + struct videomode vm; + + clk_set_rate(fsl_dev->pix_clk, mode->clock * 1000); + + drm_display_mode_to_videomode(mode, &vm); + + /* INV_PXCK as default (most display sample data on rising edge) */ + if (!(con->display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE)) + pol |= DCU_SYN_POL_INV_PXCK; + + if (vm.flags & DISPLAY_FLAGS_HSYNC_LOW) + pol |= DCU_SYN_POL_INV_HS_LOW; + + if (vm.flags & DISPLAY_FLAGS_VSYNC_LOW) + pol |= DCU_SYN_POL_INV_VS_LOW; + + regmap_write(fsl_dev->regmap, DCU_HSYN_PARA, + DCU_HSYN_PARA_BP(vm.hback_porch) | + DCU_HSYN_PARA_PW(vm.hsync_len) | + DCU_HSYN_PARA_FP(vm.hfront_porch)); + regmap_write(fsl_dev->regmap, DCU_VSYN_PARA, + DCU_VSYN_PARA_BP(vm.vback_porch) | + DCU_VSYN_PARA_PW(vm.vsync_len) | + DCU_VSYN_PARA_FP(vm.vfront_porch)); + regmap_write(fsl_dev->regmap, DCU_DISP_SIZE, + DCU_DISP_SIZE_DELTA_Y(vm.vactive) | + DCU_DISP_SIZE_DELTA_X(vm.hactive)); + 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)); + regmap_write(fsl_dev->regmap, DCU_DCU_MODE, + DCU_MODE_BLEND_ITER(1) | DCU_MODE_RASTER_EN); + regmap_write(fsl_dev->regmap, DCU_THRESHOLD, + DCU_THRESHOLD_LS_BF_VS(BF_VS_VAL) | + DCU_THRESHOLD_OUT_BUF_HIGH(BUF_MAX_VAL) | + DCU_THRESHOLD_OUT_BUF_LOW(BUF_MIN_VAL)); + return; +} + +static const struct drm_crtc_helper_funcs fsl_dcu_drm_crtc_helper_funcs = { + .atomic_disable = fsl_dcu_drm_crtc_atomic_disable, + .atomic_flush = fsl_dcu_drm_crtc_atomic_flush, + .atomic_enable = fsl_dcu_drm_crtc_atomic_enable, + .mode_set_nofb = fsl_dcu_drm_crtc_mode_set_nofb, +}; + +static int fsl_dcu_drm_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + unsigned int value; + + regmap_read(fsl_dev->regmap, DCU_INT_MASK, &value); + value &= ~DCU_INT_MASK_VBLANK; + regmap_write(fsl_dev->regmap, DCU_INT_MASK, value); + + return 0; +} + +static void fsl_dcu_drm_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + unsigned int value; + + regmap_read(fsl_dev->regmap, DCU_INT_MASK, &value); + value |= DCU_INT_MASK_VBLANK; + regmap_write(fsl_dev->regmap, DCU_INT_MASK, value); +} + +static const struct drm_crtc_funcs fsl_dcu_drm_crtc_funcs = { + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .destroy = drm_crtc_cleanup, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .set_config = drm_atomic_helper_set_config, + .enable_vblank = fsl_dcu_drm_crtc_enable_vblank, + .disable_vblank = fsl_dcu_drm_crtc_disable_vblank, +}; + +int fsl_dcu_drm_crtc_create(struct fsl_dcu_drm_device *fsl_dev) +{ + struct drm_plane *primary; + struct drm_crtc *crtc = &fsl_dev->crtc; + int ret; + + fsl_dcu_drm_init_planes(fsl_dev->drm); + + primary = fsl_dcu_drm_primary_create_plane(fsl_dev->drm); + if (!primary) + return -ENOMEM; + + ret = drm_crtc_init_with_planes(fsl_dev->drm, crtc, primary, NULL, + &fsl_dcu_drm_crtc_funcs, NULL); + if (ret) { + primary->funcs->destroy(primary); + return ret; + } + + drm_crtc_helper_add(crtc, &fsl_dcu_drm_crtc_helper_funcs); + + return 0; +} diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.h b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.h new file mode 100644 index 0000000000..df4b28073a --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2015 Freescale Semiconductor, Inc. + * + * Freescale DCU drm device driver + */ + +#ifndef __FSL_DCU_DRM_CRTC_H__ +#define __FSL_DCU_DRM_CRTC_H__ + +struct fsl_dcu_drm_device; + +int fsl_dcu_drm_crtc_create(struct fsl_dcu_drm_device *fsl_dev); + +#endif /* __FSL_DCU_DRM_CRTC_H__ */ diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c new file mode 100644 index 0000000000..a395f93449 --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2015 Freescale Semiconductor, Inc. + * + * Freescale DCU drm device driver + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fbdev_dma.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_module.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "fsl_dcu_drm_crtc.h" +#include "fsl_dcu_drm_drv.h" +#include "fsl_tcon.h" + +static int legacyfb_depth = 24; +module_param(legacyfb_depth, int, 0444); + +static bool fsl_dcu_drm_is_volatile_reg(struct device *dev, unsigned int reg) +{ + if (reg == DCU_INT_STATUS || reg == DCU_UPDATE_MODE) + return true; + + return false; +} + +static const struct regmap_config fsl_dcu_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .volatile_reg = fsl_dcu_drm_is_volatile_reg, +}; + +static void fsl_dcu_irq_reset(struct drm_device *dev) +{ + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + + regmap_write(fsl_dev->regmap, DCU_INT_STATUS, ~0); + regmap_write(fsl_dev->regmap, DCU_INT_MASK, ~0); +} + +static irqreturn_t fsl_dcu_drm_irq(int irq, void *arg) +{ + struct drm_device *dev = arg; + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + unsigned int int_status; + int ret; + + ret = regmap_read(fsl_dev->regmap, DCU_INT_STATUS, &int_status); + if (ret) { + dev_err(dev->dev, "read DCU_INT_STATUS failed\n"); + return IRQ_NONE; + } + + if (int_status & DCU_INT_STATUS_VBLANK) + drm_handle_vblank(dev, 0); + + regmap_write(fsl_dev->regmap, DCU_INT_STATUS, int_status); + + return IRQ_HANDLED; +} + +static int fsl_dcu_irq_install(struct drm_device *dev, unsigned int irq) +{ + if (irq == IRQ_NOTCONNECTED) + return -ENOTCONN; + + fsl_dcu_irq_reset(dev); + + return request_irq(irq, fsl_dcu_drm_irq, 0, dev->driver->name, dev); +} + +static void fsl_dcu_irq_uninstall(struct drm_device *dev) +{ + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + + fsl_dcu_irq_reset(dev); + free_irq(fsl_dev->irq, dev); +} + +static int fsl_dcu_load(struct drm_device *dev, unsigned long flags) +{ + 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->dev, "failed to initialize mode setting\n"); + return ret; + } + + ret = drm_vblank_init(dev, dev->mode_config.num_crtc); + if (ret < 0) { + dev_err(dev->dev, "failed to initialize vblank\n"); + goto done_vblank; + } + + ret = fsl_dcu_irq_install(dev, fsl_dev->irq); + if (ret < 0) { + dev_err(dev->dev, "failed to install IRQ handler\n"); + goto done_irq; + } + + if (legacyfb_depth != 16 && legacyfb_depth != 24 && + legacyfb_depth != 32) { + dev_warn(dev->dev, + "Invalid legacyfb_depth. Defaulting to 24bpp\n"); + legacyfb_depth = 24; + } + + return 0; +done_irq: + drm_kms_helper_poll_fini(dev); + + drm_mode_config_cleanup(dev); +done_vblank: + dev->dev_private = NULL; + + return ret; +} + +static void fsl_dcu_unload(struct drm_device *dev) +{ + drm_atomic_helper_shutdown(dev); + drm_kms_helper_poll_fini(dev); + + drm_mode_config_cleanup(dev); + fsl_dcu_irq_uninstall(dev); + + dev->dev_private = NULL; +} + +DEFINE_DRM_GEM_DMA_FOPS(fsl_dcu_drm_fops); + +static const struct drm_driver fsl_dcu_drm_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + .load = fsl_dcu_load, + .unload = fsl_dcu_unload, + DRM_GEM_DMA_DRIVER_OPS, + .fops = &fsl_dcu_drm_fops, + .name = "fsl-dcu-drm", + .desc = "Freescale DCU DRM", + .date = "20160425", + .major = 1, + .minor = 1, +}; + +#ifdef CONFIG_PM_SLEEP +static int fsl_dcu_drm_pm_suspend(struct device *dev) +{ + struct fsl_dcu_drm_device *fsl_dev = dev_get_drvdata(dev); + int ret; + + if (!fsl_dev) + return 0; + + disable_irq(fsl_dev->irq); + + ret = drm_mode_config_helper_suspend(fsl_dev->drm); + if (ret) { + enable_irq(fsl_dev->irq); + return ret; + } + + clk_disable_unprepare(fsl_dev->clk); + + return 0; +} + +static int fsl_dcu_drm_pm_resume(struct device *dev) +{ + struct fsl_dcu_drm_device *fsl_dev = dev_get_drvdata(dev); + int ret; + + if (!fsl_dev) + return 0; + + ret = clk_prepare_enable(fsl_dev->clk); + if (ret < 0) { + dev_err(dev, "failed to enable dcu clk\n"); + return ret; + } + + if (fsl_dev->tcon) + fsl_tcon_bypass_enable(fsl_dev->tcon); + fsl_dcu_drm_init_planes(fsl_dev->drm); + enable_irq(fsl_dev->irq); + + drm_mode_config_helper_resume(fsl_dev->drm); + + return 0; +} +#endif + +static const struct dev_pm_ops fsl_dcu_drm_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fsl_dcu_drm_pm_suspend, fsl_dcu_drm_pm_resume) +}; + +static const struct fsl_dcu_soc_data fsl_dcu_ls1021a_data = { + .name = "ls1021a", + .total_layer = 16, + .max_layer = 4, + .layer_regs = LS1021A_LAYER_REG_NUM, +}; + +static const struct fsl_dcu_soc_data fsl_dcu_vf610_data = { + .name = "vf610", + .total_layer = 64, + .max_layer = 6, + .layer_regs = VF610_LAYER_REG_NUM, +}; + +static const struct of_device_id fsl_dcu_of_match[] = { + { + .compatible = "fsl,ls1021a-dcu", + .data = &fsl_dcu_ls1021a_data, + }, { + .compatible = "fsl,vf610-dcu", + .data = &fsl_dcu_vf610_data, + }, { + }, +}; +MODULE_DEVICE_TABLE(of, fsl_dcu_of_match); + +static int fsl_dcu_drm_probe(struct platform_device *pdev) +{ + struct fsl_dcu_drm_device *fsl_dev; + struct drm_device *drm; + struct device *dev = &pdev->dev; + struct resource *res; + void __iomem *base; + struct clk *pix_clk_in; + char pix_clk_name[32]; + const char *pix_clk_in_name; + const struct of_device_id *id; + int ret; + u8 div_ratio_shift = 0; + + fsl_dev = devm_kzalloc(dev, sizeof(*fsl_dev), GFP_KERNEL); + 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); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) { + ret = PTR_ERR(base); + return ret; + } + + fsl_dev->irq = platform_get_irq(pdev, 0); + if (fsl_dev->irq < 0) { + dev_err(dev, "failed to get irq\n"); + return fsl_dev->irq; + } + + fsl_dev->regmap = devm_regmap_init_mmio(dev, base, + &fsl_dcu_regmap_config); + if (IS_ERR(fsl_dev->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(fsl_dev->regmap); + } + + 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; + } + + if (of_property_read_bool(dev->of_node, "big-endian")) + div_ratio_shift = 24; + + 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, + div_ratio_shift, 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; + } + + fsl_dev->tcon = fsl_tcon_init(dev); + + drm = drm_dev_alloc(&fsl_dcu_drm_driver, dev); + if (IS_ERR(drm)) { + ret = PTR_ERR(drm); + goto unregister_pix_clk; + } + + fsl_dev->dev = dev; + fsl_dev->drm = drm; + fsl_dev->np = dev->of_node; + drm->dev_private = fsl_dev; + dev_set_drvdata(dev, fsl_dev); + + ret = drm_dev_register(drm, 0); + if (ret < 0) + goto put; + + drm_fbdev_dma_setup(drm, legacyfb_depth); + + return 0; + +put: + drm_dev_put(drm); +unregister_pix_clk: + clk_unregister(fsl_dev->pix_clk); +disable_clk: + clk_disable_unprepare(fsl_dev->clk); + return ret; +} + +static void fsl_dcu_drm_remove(struct platform_device *pdev) +{ + struct fsl_dcu_drm_device *fsl_dev = platform_get_drvdata(pdev); + + drm_dev_unregister(fsl_dev->drm); + drm_dev_put(fsl_dev->drm); + clk_disable_unprepare(fsl_dev->clk); + clk_unregister(fsl_dev->pix_clk); +} + +static struct platform_driver fsl_dcu_drm_platform_driver = { + .probe = fsl_dcu_drm_probe, + .remove_new = fsl_dcu_drm_remove, + .driver = { + .name = "fsl-dcu", + .pm = &fsl_dcu_drm_pm_ops, + .of_match_table = fsl_dcu_of_match, + }, +}; + +drm_module_platform_driver(fsl_dcu_drm_platform_driver); + +MODULE_DESCRIPTION("Freescale DCU DRM Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h new file mode 100644 index 0000000000..e2049a0e8a --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2015 Freescale Semiconductor, Inc. + * + * Freescale DCU drm device driver + */ + +#ifndef __FSL_DCU_DRM_DRV_H__ +#define __FSL_DCU_DRM_DRV_H__ + +#include <drm/drm_encoder.h> + +#include "fsl_dcu_drm_crtc.h" +#include "fsl_dcu_drm_output.h" +#include "fsl_dcu_drm_plane.h" + +#define DCU_DCU_MODE 0x0010 +#define DCU_MODE_BLEND_ITER(x) ((x) << 20) +#define DCU_MODE_RASTER_EN BIT(14) +#define DCU_MODE_DCU_MODE(x) (x) +#define DCU_MODE_DCU_MODE_MASK 0x03 +#define DCU_MODE_OFF 0 +#define DCU_MODE_NORMAL 1 +#define DCU_MODE_TEST 2 +#define DCU_MODE_COLORBAR 3 + +#define DCU_BGND 0x0014 +#define DCU_BGND_R(x) ((x) << 16) +#define DCU_BGND_G(x) ((x) << 8) +#define DCU_BGND_B(x) (x) + +#define DCU_DISP_SIZE 0x0018 +#define DCU_DISP_SIZE_DELTA_Y(x) ((x) << 16) +/*Regisiter value 1/16 of horizontal resolution*/ +#define DCU_DISP_SIZE_DELTA_X(x) ((x) >> 4) + +#define DCU_HSYN_PARA 0x001c +#define DCU_HSYN_PARA_BP(x) ((x) << 22) +#define DCU_HSYN_PARA_PW(x) ((x) << 11) +#define DCU_HSYN_PARA_FP(x) (x) + +#define DCU_VSYN_PARA 0x0020 +#define DCU_VSYN_PARA_BP(x) ((x) << 22) +#define DCU_VSYN_PARA_PW(x) ((x) << 11) +#define DCU_VSYN_PARA_FP(x) (x) + +#define DCU_SYN_POL 0x0024 +#define DCU_SYN_POL_INV_PXCK BIT(6) +#define DCU_SYN_POL_NEG BIT(5) +#define DCU_SYN_POL_INV_VS_LOW BIT(1) +#define DCU_SYN_POL_INV_HS_LOW BIT(0) + +#define DCU_THRESHOLD 0x0028 +#define DCU_THRESHOLD_LS_BF_VS(x) ((x) << 16) +#define DCU_THRESHOLD_OUT_BUF_HIGH(x) ((x) << 8) +#define DCU_THRESHOLD_OUT_BUF_LOW(x) (x) +#define BF_VS_VAL 0x03 +#define BUF_MAX_VAL 0x78 +#define BUF_MIN_VAL 0x0a + +#define DCU_INT_STATUS 0x002C +#define DCU_INT_STATUS_VSYNC BIT(0) +#define DCU_INT_STATUS_UNDRUN BIT(1) +#define DCU_INT_STATUS_LSBFVS BIT(2) +#define DCU_INT_STATUS_VBLANK BIT(3) +#define DCU_INT_STATUS_CRCREADY BIT(4) +#define DCU_INT_STATUS_CRCOVERFLOW BIT(5) +#define DCU_INT_STATUS_P1FIFOLO BIT(6) +#define DCU_INT_STATUS_P1FIFOHI BIT(7) +#define DCU_INT_STATUS_P2FIFOLO BIT(8) +#define DCU_INT_STATUS_P2FIFOHI BIT(9) +#define DCU_INT_STATUS_PROGEND BIT(10) +#define DCU_INT_STATUS_IPMERROR BIT(11) +#define DCU_INT_STATUS_LYRTRANS BIT(12) +#define DCU_INT_STATUS_DMATRANS BIT(14) +#define DCU_INT_STATUS_P3FIFOLO BIT(16) +#define DCU_INT_STATUS_P3FIFOHI BIT(17) +#define DCU_INT_STATUS_P4FIFOLO BIT(18) +#define DCU_INT_STATUS_P4FIFOHI BIT(19) +#define DCU_INT_STATUS_P1EMPTY BIT(26) +#define DCU_INT_STATUS_P2EMPTY BIT(27) +#define DCU_INT_STATUS_P3EMPTY BIT(28) +#define DCU_INT_STATUS_P4EMPTY BIT(29) + +#define DCU_INT_MASK 0x0030 +#define DCU_INT_MASK_VSYNC BIT(0) +#define DCU_INT_MASK_UNDRUN BIT(1) +#define DCU_INT_MASK_LSBFVS BIT(2) +#define DCU_INT_MASK_VBLANK BIT(3) +#define DCU_INT_MASK_CRCREADY BIT(4) +#define DCU_INT_MASK_CRCOVERFLOW BIT(5) +#define DCU_INT_MASK_P1FIFOLO BIT(6) +#define DCU_INT_MASK_P1FIFOHI BIT(7) +#define DCU_INT_MASK_P2FIFOLO BIT(8) +#define DCU_INT_MASK_P2FIFOHI BIT(9) +#define DCU_INT_MASK_PROGEND BIT(10) +#define DCU_INT_MASK_IPMERROR BIT(11) +#define DCU_INT_MASK_LYRTRANS BIT(12) +#define DCU_INT_MASK_DMATRANS BIT(14) +#define DCU_INT_MASK_P3FIFOLO BIT(16) +#define DCU_INT_MASK_P3FIFOHI BIT(17) +#define DCU_INT_MASK_P4FIFOLO BIT(18) +#define DCU_INT_MASK_P4FIFOHI BIT(19) +#define DCU_INT_MASK_P1EMPTY BIT(26) +#define DCU_INT_MASK_P2EMPTY BIT(27) +#define DCU_INT_MASK_P3EMPTY BIT(28) +#define DCU_INT_MASK_P4EMPTY BIT(29) + +#define DCU_DIV_RATIO 0x0054 + +#define DCU_UPDATE_MODE 0x00cc +#define DCU_UPDATE_MODE_MODE BIT(31) +#define DCU_UPDATE_MODE_READREG BIT(30) + +#define DCU_DCFB_MAX 0x300 + +#define DCU_CTRLDESCLN(layer, reg) (0x200 + (reg - 1) * 4 + (layer) * 0x40) + +#define DCU_LAYER_HEIGHT(x) ((x) << 16) +#define DCU_LAYER_WIDTH(x) (x) + +#define DCU_LAYER_POSY(x) ((x) << 16) +#define DCU_LAYER_POSX(x) (x) + +#define DCU_LAYER_EN BIT(31) +#define DCU_LAYER_TILE_EN BIT(30) +#define DCU_LAYER_DATA_SEL_CLUT BIT(29) +#define DCU_LAYER_SAFETY_EN BIT(28) +#define DCU_LAYER_TRANS(x) ((x) << 20) +#define DCU_LAYER_BPP(x) ((x) << 16) +#define DCU_LAYER_RLE_EN BIT(15) +#define DCU_LAYER_LUOFFS(x) ((x) << 4) +#define DCU_LAYER_BB_ON BIT(2) +#define DCU_LAYER_AB_NONE 0 +#define DCU_LAYER_AB_CHROMA_KEYING 1 +#define DCU_LAYER_AB_WHOLE_FRAME 2 + +#define DCU_LAYER_CKMAX_R(x) ((x) << 16) +#define DCU_LAYER_CKMAX_G(x) ((x) << 8) +#define DCU_LAYER_CKMAX_B(x) (x) + +#define DCU_LAYER_CKMIN_R(x) ((x) << 16) +#define DCU_LAYER_CKMIN_G(x) ((x) << 8) +#define DCU_LAYER_CKMIN_B(x) (x) + +#define DCU_LAYER_TILE_VER(x) ((x) << 16) +#define DCU_LAYER_TILE_HOR(x) (x) + +#define DCU_LAYER_FG_FCOLOR(x) (x) + +#define DCU_LAYER_BG_BCOLOR(x) (x) + +#define DCU_LAYER_POST_SKIP(x) ((x) << 16) +#define DCU_LAYER_PRE_SKIP(x) (x) + +#define FSL_DCU_RGB565 4 +#define FSL_DCU_RGB888 5 +#define FSL_DCU_ARGB8888 6 +#define FSL_DCU_ARGB1555 11 +#define FSL_DCU_ARGB4444 12 +#define FSL_DCU_YUV422 14 + +#define VF610_LAYER_REG_NUM 9 +#define LS1021A_LAYER_REG_NUM 10 + +struct clk; +struct device; +struct drm_device; + +struct fsl_dcu_soc_data { + const char *name; + /*total layer number*/ + unsigned int total_layer; + /*max layer number DCU supported*/ + unsigned int max_layer; + unsigned int layer_regs; +}; + +struct fsl_dcu_drm_device { + struct device *dev; + struct device_node *np; + 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; + struct drm_crtc crtc; + struct drm_encoder encoder; + struct fsl_dcu_drm_connector connector; + const struct fsl_dcu_soc_data *soc; +}; + +int fsl_dcu_drm_modeset_init(struct fsl_dcu_drm_device *fsl_dev); + +#endif /* __FSL_DCU_DRM_DRV_H__ */ diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_kms.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_kms.c new file mode 100644 index 0000000000..5b47000738 --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_kms.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2015 Freescale Semiconductor, Inc. + * + * Freescale DCU drm device driver + */ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_probe_helper.h> + +#include "fsl_dcu_drm_crtc.h" +#include "fsl_dcu_drm_drv.h" + +static const struct drm_mode_config_funcs fsl_dcu_drm_mode_config_funcs = { + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, + .fb_create = drm_gem_fb_create, +}; + +int fsl_dcu_drm_modeset_init(struct fsl_dcu_drm_device *fsl_dev) +{ + int ret; + + drm_mode_config_init(fsl_dev->drm); + + fsl_dev->drm->mode_config.min_width = 0; + fsl_dev->drm->mode_config.min_height = 0; + fsl_dev->drm->mode_config.max_width = 2031; + fsl_dev->drm->mode_config.max_height = 2047; + fsl_dev->drm->mode_config.funcs = &fsl_dcu_drm_mode_config_funcs; + + ret = fsl_dcu_drm_crtc_create(fsl_dev); + if (ret) + goto err; + + ret = fsl_dcu_drm_encoder_create(fsl_dev, &fsl_dev->crtc); + if (ret) + goto err; + + ret = fsl_dcu_create_outputs(fsl_dev); + if (ret) + goto err; + + drm_mode_config_reset(fsl_dev->drm); + drm_kms_helper_poll_init(fsl_dev->drm); + + return 0; + +err: + drm_mode_config_cleanup(fsl_dev->drm); + return ret; +} diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_output.h b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_output.h new file mode 100644 index 0000000000..8d7dedb959 --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_output.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2015 Freescale Semiconductor, Inc. + * + * Freescale DCU drm device driver + */ + +#ifndef __FSL_DCU_DRM_CONNECTOR_H__ +#define __FSL_DCU_DRM_CONNECTOR_H__ + +struct fsl_dcu_drm_connector { + struct drm_connector base; + struct drm_encoder *encoder; + struct drm_panel *panel; +}; + +static inline struct fsl_dcu_drm_connector * +to_fsl_dcu_connector(struct drm_connector *con) +{ + return con ? container_of(con, struct fsl_dcu_drm_connector, base) + : NULL; +} + +int fsl_dcu_drm_encoder_create(struct fsl_dcu_drm_device *fsl_dev, + struct drm_crtc *crtc); +int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev); + +#endif /* __FSL_DCU_DRM_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.c new file mode 100644 index 0000000000..794a87d16f --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2015 Freescale Semiconductor, Inc. + * + * Freescale DCU drm device driver + */ + +#include <linux/regmap.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fb_dma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_plane_helper.h> +#include <drm/drm_probe_helper.h> + +#include "fsl_dcu_drm_drv.h" +#include "fsl_dcu_drm_plane.h" + +static int fsl_dcu_drm_plane_index(struct drm_plane *plane) +{ + struct fsl_dcu_drm_device *fsl_dev = plane->dev->dev_private; + unsigned int total_layer = fsl_dev->soc->total_layer; + unsigned int index; + + index = drm_plane_index(plane); + if (index < total_layer) + return total_layer - index - 1; + + dev_err(fsl_dev->dev, "No more layer left\n"); + return -EINVAL; +} + +static int fsl_dcu_drm_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_framebuffer *fb = new_plane_state->fb; + + if (!new_plane_state->fb || !new_plane_state->crtc) + return 0; + + switch (fb->format->format) { + case DRM_FORMAT_RGB565: + case DRM_FORMAT_RGB888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_YUV422: + return 0; + default: + return -EINVAL; + } +} + +static void fsl_dcu_drm_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct fsl_dcu_drm_device *fsl_dev = plane->dev->dev_private; + unsigned int value; + int index; + + index = fsl_dcu_drm_plane_index(plane); + if (index < 0) + return; + + regmap_read(fsl_dev->regmap, DCU_CTRLDESCLN(index, 4), &value); + value &= ~DCU_LAYER_EN; + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 4), value); +} + +static void fsl_dcu_drm_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) + +{ + struct fsl_dcu_drm_device *fsl_dev = plane->dev->dev_private; + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_framebuffer *fb = plane->state->fb; + struct drm_gem_dma_object *gem; + unsigned int alpha = DCU_LAYER_AB_NONE, bpp; + int index; + + if (!fb) + return; + + index = fsl_dcu_drm_plane_index(plane); + if (index < 0) + return; + + gem = drm_fb_dma_get_gem_obj(fb, 0); + + switch (fb->format->format) { + case DRM_FORMAT_RGB565: + bpp = FSL_DCU_RGB565; + break; + case DRM_FORMAT_RGB888: + bpp = FSL_DCU_RGB888; + break; + case DRM_FORMAT_ARGB8888: + alpha = DCU_LAYER_AB_WHOLE_FRAME; + fallthrough; + case DRM_FORMAT_XRGB8888: + bpp = FSL_DCU_ARGB8888; + break; + case DRM_FORMAT_ARGB4444: + alpha = DCU_LAYER_AB_WHOLE_FRAME; + fallthrough; + case DRM_FORMAT_XRGB4444: + bpp = FSL_DCU_ARGB4444; + break; + case DRM_FORMAT_ARGB1555: + alpha = DCU_LAYER_AB_WHOLE_FRAME; + fallthrough; + case DRM_FORMAT_XRGB1555: + bpp = FSL_DCU_ARGB1555; + break; + case DRM_FORMAT_YUV422: + bpp = FSL_DCU_YUV422; + break; + default: + return; + } + + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 1), + DCU_LAYER_HEIGHT(new_state->crtc_h) | + DCU_LAYER_WIDTH(new_state->crtc_w)); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 2), + DCU_LAYER_POSY(new_state->crtc_y) | + DCU_LAYER_POSX(new_state->crtc_x)); + regmap_write(fsl_dev->regmap, + DCU_CTRLDESCLN(index, 3), gem->dma_addr); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 4), + DCU_LAYER_EN | + DCU_LAYER_TRANS(0xff) | + DCU_LAYER_BPP(bpp) | + alpha); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 5), + DCU_LAYER_CKMAX_R(0xFF) | + DCU_LAYER_CKMAX_G(0xFF) | + DCU_LAYER_CKMAX_B(0xFF)); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 6), + DCU_LAYER_CKMIN_R(0) | + DCU_LAYER_CKMIN_G(0) | + DCU_LAYER_CKMIN_B(0)); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 7), 0); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 8), + DCU_LAYER_FG_FCOLOR(0)); + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 9), + DCU_LAYER_BG_BCOLOR(0)); + + if (!strcmp(fsl_dev->soc->name, "ls1021a")) { + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(index, 10), + DCU_LAYER_POST_SKIP(0) | + DCU_LAYER_PRE_SKIP(0)); + } + + return; +} + +static const struct drm_plane_helper_funcs fsl_dcu_drm_plane_helper_funcs = { + .atomic_check = fsl_dcu_drm_plane_atomic_check, + .atomic_disable = fsl_dcu_drm_plane_atomic_disable, + .atomic_update = fsl_dcu_drm_plane_atomic_update, +}; + +static const struct drm_plane_funcs fsl_dcu_drm_plane_funcs = { + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .destroy = drm_plane_helper_destroy, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .update_plane = drm_atomic_helper_update_plane, +}; + +static const u32 fsl_dcu_drm_plane_formats[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_YUV422, +}; + +void fsl_dcu_drm_init_planes(struct drm_device *dev) +{ + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + int i, j; + + for (i = 0; i < fsl_dev->soc->total_layer; i++) { + for (j = 1; j <= fsl_dev->soc->layer_regs; j++) + regmap_write(fsl_dev->regmap, DCU_CTRLDESCLN(i, j), 0); + } +} + +struct drm_plane *fsl_dcu_drm_primary_create_plane(struct drm_device *dev) +{ + struct drm_plane *primary; + int ret; + + primary = kzalloc(sizeof(*primary), GFP_KERNEL); + if (!primary) { + DRM_DEBUG_KMS("Failed to allocate primary plane\n"); + return NULL; + } + + /* possible_crtc's will be filled in later by crtc_init */ + ret = drm_universal_plane_init(dev, primary, 0, + &fsl_dcu_drm_plane_funcs, + fsl_dcu_drm_plane_formats, + ARRAY_SIZE(fsl_dcu_drm_plane_formats), + NULL, DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) { + kfree(primary); + primary = NULL; + } + drm_plane_helper_add(primary, &fsl_dcu_drm_plane_helper_funcs); + + return primary; +} diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.h b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.h new file mode 100644 index 0000000000..c1d80ae7b8 --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_plane.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2015 Freescale Semiconductor, Inc. + * + * Freescale DCU drm device driver + */ + +#ifndef __FSL_DCU_DRM_PLANE_H__ +#define __FSL_DCU_DRM_PLANE_H__ + +void fsl_dcu_drm_init_planes(struct drm_device *dev); +struct drm_plane *fsl_dcu_drm_primary_create_plane(struct drm_device *dev); + +#endif /* __FSL_DCU_DRM_PLANE_H__ */ diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c new file mode 100644 index 0000000000..2c2b92324a --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2015 Freescale Semiconductor, Inc. + * + * Freescale DCU drm device driver + */ + +#include <linux/backlight.h> +#include <linux/of_graph.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> + +#include "fsl_dcu_drm_drv.h" +#include "fsl_tcon.h" + +int fsl_dcu_drm_encoder_create(struct fsl_dcu_drm_device *fsl_dev, + struct drm_crtc *crtc) +{ + struct drm_encoder *encoder = &fsl_dev->encoder; + int ret; + + encoder->possible_crtcs = 1; + + /* Use bypass mode for parallel RGB/LVDS encoder */ + if (fsl_dev->tcon) + fsl_tcon_bypass_enable(fsl_dev->tcon); + + ret = drm_simple_encoder_init(fsl_dev->drm, encoder, + DRM_MODE_ENCODER_LVDS); + if (ret < 0) + return ret; + + return 0; +} + +static void fsl_dcu_drm_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs fsl_dcu_drm_connector_funcs = { + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .destroy = fsl_dcu_drm_connector_destroy, + .fill_modes = drm_helper_probe_single_connector_modes, + .reset = drm_atomic_helper_connector_reset, +}; + +static int fsl_dcu_drm_connector_get_modes(struct drm_connector *connector) +{ + struct fsl_dcu_drm_connector *fsl_connector; + + fsl_connector = to_fsl_dcu_connector(connector); + return drm_panel_get_modes(fsl_connector->panel, connector); +} + +static enum drm_mode_status +fsl_dcu_drm_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + if (mode->hdisplay & 0xf) + return MODE_ERROR; + + return MODE_OK; +} + +static const struct drm_connector_helper_funcs connector_helper_funcs = { + .get_modes = fsl_dcu_drm_connector_get_modes, + .mode_valid = fsl_dcu_drm_connector_mode_valid, +}; + +static int fsl_dcu_attach_panel(struct fsl_dcu_drm_device *fsl_dev, + struct drm_panel *panel) +{ + struct drm_encoder *encoder = &fsl_dev->encoder; + struct drm_connector *connector = &fsl_dev->connector.base; + int ret; + + fsl_dev->connector.encoder = encoder; + + ret = drm_connector_init(fsl_dev->drm, connector, + &fsl_dcu_drm_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + if (ret < 0) + return ret; + + drm_connector_helper_add(connector, &connector_helper_funcs); + ret = drm_connector_register(connector); + if (ret < 0) + goto err_cleanup; + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret < 0) + goto err_sysfs; + + return 0; + +err_sysfs: + drm_connector_unregister(connector); +err_cleanup: + drm_connector_cleanup(connector); + return ret; +} + +int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev) +{ + struct device_node *panel_node; + struct drm_panel *panel; + struct drm_bridge *bridge; + int ret; + + /* This is for backward compatibility */ + panel_node = of_parse_phandle(fsl_dev->np, "fsl,panel", 0); + if (panel_node) { + fsl_dev->connector.panel = of_drm_find_panel(panel_node); + of_node_put(panel_node); + if (IS_ERR(fsl_dev->connector.panel)) + return PTR_ERR(fsl_dev->connector.panel); + + return fsl_dcu_attach_panel(fsl_dev, fsl_dev->connector.panel); + } + + ret = drm_of_find_panel_or_bridge(fsl_dev->np, 0, 0, &panel, &bridge); + if (ret) + return ret; + + if (panel) { + fsl_dev->connector.panel = panel; + return fsl_dcu_attach_panel(fsl_dev, panel); + } + + return drm_bridge_attach(&fsl_dev->encoder, bridge, NULL, 0); +} 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 0000000000..9eb5abaf7d --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_tcon.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2015 Toradex AG + * + * Stefan Agner <stefan@agner.ch> + * + * Freescale TCON device driver + */ + +#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); + return PTR_ERR_OR_ZERO(tcon->regs); +} + +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) + 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; + } + + ret = clk_prepare_enable(tcon->ipg_clk); + if (ret) { + dev_err(dev, "Couldn't enable the TCON clock\n"); + goto err_node_put; + } + + of_node_put(np); + 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 0000000000..23aa78f14d --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_tcon.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2015 Toradex AG + * + * Stefan Agner <stefan@agner.ch> + * + * Freescale TCON device driver + */ + +#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__ */ |