linux/drivers/gpu/drm/mxsfb/mxsfb_drv.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright (C) 2016 Marek Vasut <marex@denx.de>
   4 *
   5 * This code is based on drivers/video/fbdev/mxsfb.c :
   6 * Copyright (C) 2010 Juergen Beisert, Pengutronix
   7 * Copyright (C) 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
   8 * Copyright (C) 2008 Embedded Alley Solutions, Inc All Rights Reserved.
   9 */
  10
  11#include <linux/clk.h>
  12#include <linux/dma-mapping.h>
  13#include <linux/io.h>
  14#include <linux/module.h>
  15#include <linux/of_device.h>
  16#include <linux/platform_device.h>
  17#include <linux/pm_runtime.h>
  18
  19#include <drm/drm_atomic_helper.h>
  20#include <drm/drm_bridge.h>
  21#include <drm/drm_connector.h>
  22#include <drm/drm_drv.h>
  23#include <drm/drm_fb_helper.h>
  24#include <drm/drm_fourcc.h>
  25#include <drm/drm_gem_cma_helper.h>
  26#include <drm/drm_gem_framebuffer_helper.h>
  27#include <drm/drm_mode_config.h>
  28#include <drm/drm_of.h>
  29#include <drm/drm_probe_helper.h>
  30#include <drm/drm_vblank.h>
  31
  32#include "mxsfb_drv.h"
  33#include "mxsfb_regs.h"
  34
  35enum mxsfb_devtype {
  36        MXSFB_V3,
  37        MXSFB_V4,
  38        /*
  39         * Starting at i.MX6 the hardware version register is gone, use the
  40         * i.MX family number as the version.
  41         */
  42        MXSFB_V6,
  43};
  44
  45static const struct mxsfb_devdata mxsfb_devdata[] = {
  46        [MXSFB_V3] = {
  47                .transfer_count = LCDC_V3_TRANSFER_COUNT,
  48                .cur_buf        = LCDC_V3_CUR_BUF,
  49                .next_buf       = LCDC_V3_NEXT_BUF,
  50                .hs_wdth_mask   = 0xff,
  51                .hs_wdth_shift  = 24,
  52                .has_overlay    = false,
  53                .has_ctrl2      = false,
  54        },
  55        [MXSFB_V4] = {
  56                .transfer_count = LCDC_V4_TRANSFER_COUNT,
  57                .cur_buf        = LCDC_V4_CUR_BUF,
  58                .next_buf       = LCDC_V4_NEXT_BUF,
  59                .hs_wdth_mask   = 0x3fff,
  60                .hs_wdth_shift  = 18,
  61                .has_overlay    = false,
  62                .has_ctrl2      = true,
  63        },
  64        [MXSFB_V6] = {
  65                .transfer_count = LCDC_V4_TRANSFER_COUNT,
  66                .cur_buf        = LCDC_V4_CUR_BUF,
  67                .next_buf       = LCDC_V4_NEXT_BUF,
  68                .hs_wdth_mask   = 0x3fff,
  69                .hs_wdth_shift  = 18,
  70                .has_overlay    = true,
  71                .has_ctrl2      = true,
  72        },
  73};
  74
  75void mxsfb_enable_axi_clk(struct mxsfb_drm_private *mxsfb)
  76{
  77        if (mxsfb->clk_axi)
  78                clk_prepare_enable(mxsfb->clk_axi);
  79}
  80
  81void mxsfb_disable_axi_clk(struct mxsfb_drm_private *mxsfb)
  82{
  83        if (mxsfb->clk_axi)
  84                clk_disable_unprepare(mxsfb->clk_axi);
  85}
  86
  87static struct drm_framebuffer *
  88mxsfb_fb_create(struct drm_device *dev, struct drm_file *file_priv,
  89                const struct drm_mode_fb_cmd2 *mode_cmd)
  90{
  91        const struct drm_format_info *info;
  92
  93        info = drm_get_format_info(dev, mode_cmd);
  94        if (!info)
  95                return ERR_PTR(-EINVAL);
  96
  97        if (mode_cmd->width * info->cpp[0] != mode_cmd->pitches[0]) {
  98                dev_dbg(dev->dev, "Invalid pitch: fb width must match pitch\n");
  99                return ERR_PTR(-EINVAL);
 100        }
 101
 102        return drm_gem_fb_create(dev, file_priv, mode_cmd);
 103}
 104
 105static const struct drm_mode_config_funcs mxsfb_mode_config_funcs = {
 106        .fb_create              = mxsfb_fb_create,
 107        .atomic_check           = drm_atomic_helper_check,
 108        .atomic_commit          = drm_atomic_helper_commit,
 109};
 110
 111static const struct drm_mode_config_helper_funcs mxsfb_mode_config_helpers = {
 112        .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
 113};
 114
 115static int mxsfb_attach_bridge(struct mxsfb_drm_private *mxsfb)
 116{
 117        struct drm_device *drm = mxsfb->drm;
 118        struct drm_connector_list_iter iter;
 119        struct drm_panel *panel;
 120        struct drm_bridge *bridge;
 121        int ret;
 122
 123        ret = drm_of_find_panel_or_bridge(drm->dev->of_node, 0, 0, &panel,
 124                                          &bridge);
 125        if (ret)
 126                return ret;
 127
 128        if (panel) {
 129                bridge = devm_drm_panel_bridge_add_typed(drm->dev, panel,
 130                                                         DRM_MODE_CONNECTOR_DPI);
 131                if (IS_ERR(bridge))
 132                        return PTR_ERR(bridge);
 133        }
 134
 135        if (!bridge)
 136                return -ENODEV;
 137
 138        ret = drm_bridge_attach(&mxsfb->encoder, bridge, NULL, 0);
 139        if (ret)
 140                return dev_err_probe(drm->dev, ret, "Failed to attach bridge\n");
 141
 142        mxsfb->bridge = bridge;
 143
 144        /*
 145         * Get hold of the connector. This is a bit of a hack, until the bridge
 146         * API gives us bus flags and formats.
 147         */
 148        drm_connector_list_iter_begin(drm, &iter);
 149        mxsfb->connector = drm_connector_list_iter_next(&iter);
 150        drm_connector_list_iter_end(&iter);
 151
 152        return 0;
 153}
 154
 155static irqreturn_t mxsfb_irq_handler(int irq, void *data)
 156{
 157        struct drm_device *drm = data;
 158        struct mxsfb_drm_private *mxsfb = drm->dev_private;
 159        u32 reg;
 160
 161        reg = readl(mxsfb->base + LCDC_CTRL1);
 162
 163        if (reg & CTRL1_CUR_FRAME_DONE_IRQ)
 164                drm_crtc_handle_vblank(&mxsfb->crtc);
 165
 166        writel(CTRL1_CUR_FRAME_DONE_IRQ, mxsfb->base + LCDC_CTRL1 + REG_CLR);
 167
 168        return IRQ_HANDLED;
 169}
 170
 171static void mxsfb_irq_disable(struct drm_device *drm)
 172{
 173        struct mxsfb_drm_private *mxsfb = drm->dev_private;
 174
 175        mxsfb_enable_axi_clk(mxsfb);
 176
 177        /* Disable and clear VBLANK IRQ */
 178        writel(CTRL1_CUR_FRAME_DONE_IRQ_EN, mxsfb->base + LCDC_CTRL1 + REG_CLR);
 179        writel(CTRL1_CUR_FRAME_DONE_IRQ, mxsfb->base + LCDC_CTRL1 + REG_CLR);
 180
 181        mxsfb_disable_axi_clk(mxsfb);
 182}
 183
 184static int mxsfb_irq_install(struct drm_device *dev, int irq)
 185{
 186        if (irq == IRQ_NOTCONNECTED)
 187                return -ENOTCONN;
 188
 189        mxsfb_irq_disable(dev);
 190
 191        return request_irq(irq, mxsfb_irq_handler, 0,  dev->driver->name, dev);
 192}
 193
 194static void mxsfb_irq_uninstall(struct drm_device *dev)
 195{
 196        struct mxsfb_drm_private *mxsfb = dev->dev_private;
 197
 198        mxsfb_irq_disable(dev);
 199        free_irq(mxsfb->irq, dev);
 200}
 201
 202static int mxsfb_load(struct drm_device *drm,
 203                      const struct mxsfb_devdata *devdata)
 204{
 205        struct platform_device *pdev = to_platform_device(drm->dev);
 206        struct mxsfb_drm_private *mxsfb;
 207        struct resource *res;
 208        int ret;
 209
 210        mxsfb = devm_kzalloc(&pdev->dev, sizeof(*mxsfb), GFP_KERNEL);
 211        if (!mxsfb)
 212                return -ENOMEM;
 213
 214        mxsfb->drm = drm;
 215        drm->dev_private = mxsfb;
 216        mxsfb->devdata = devdata;
 217
 218        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 219        mxsfb->base = devm_ioremap_resource(drm->dev, res);
 220        if (IS_ERR(mxsfb->base))
 221                return PTR_ERR(mxsfb->base);
 222
 223        mxsfb->clk = devm_clk_get(drm->dev, NULL);
 224        if (IS_ERR(mxsfb->clk))
 225                return PTR_ERR(mxsfb->clk);
 226
 227        mxsfb->clk_axi = devm_clk_get(drm->dev, "axi");
 228        if (IS_ERR(mxsfb->clk_axi))
 229                mxsfb->clk_axi = NULL;
 230
 231        mxsfb->clk_disp_axi = devm_clk_get(drm->dev, "disp_axi");
 232        if (IS_ERR(mxsfb->clk_disp_axi))
 233                mxsfb->clk_disp_axi = NULL;
 234
 235        ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32));
 236        if (ret)
 237                return ret;
 238
 239        pm_runtime_enable(drm->dev);
 240
 241        /* Modeset init */
 242        drm_mode_config_init(drm);
 243
 244        ret = mxsfb_kms_init(mxsfb);
 245        if (ret < 0) {
 246                dev_err(drm->dev, "Failed to initialize KMS pipeline\n");
 247                goto err_vblank;
 248        }
 249
 250        ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
 251        if (ret < 0) {
 252                dev_err(drm->dev, "Failed to initialise vblank\n");
 253                goto err_vblank;
 254        }
 255
 256        /* Start with vertical blanking interrupt reporting disabled. */
 257        drm_crtc_vblank_off(&mxsfb->crtc);
 258
 259        ret = mxsfb_attach_bridge(mxsfb);
 260        if (ret) {
 261                if (ret != -EPROBE_DEFER)
 262                        dev_err(drm->dev, "Cannot connect bridge: %d\n", ret);
 263                goto err_vblank;
 264        }
 265
 266        drm->mode_config.min_width      = MXSFB_MIN_XRES;
 267        drm->mode_config.min_height     = MXSFB_MIN_YRES;
 268        drm->mode_config.max_width      = MXSFB_MAX_XRES;
 269        drm->mode_config.max_height     = MXSFB_MAX_YRES;
 270        drm->mode_config.funcs          = &mxsfb_mode_config_funcs;
 271        drm->mode_config.helper_private = &mxsfb_mode_config_helpers;
 272
 273        drm_mode_config_reset(drm);
 274
 275        ret = platform_get_irq(pdev, 0);
 276        if (ret < 0)
 277                goto err_vblank;
 278        mxsfb->irq = ret;
 279
 280        pm_runtime_get_sync(drm->dev);
 281        ret = mxsfb_irq_install(drm, mxsfb->irq);
 282        pm_runtime_put_sync(drm->dev);
 283
 284        if (ret < 0) {
 285                dev_err(drm->dev, "Failed to install IRQ handler\n");
 286                goto err_vblank;
 287        }
 288
 289        drm_kms_helper_poll_init(drm);
 290
 291        platform_set_drvdata(pdev, drm);
 292
 293        drm_helper_hpd_irq_event(drm);
 294
 295        return 0;
 296
 297err_vblank:
 298        pm_runtime_disable(drm->dev);
 299
 300        return ret;
 301}
 302
 303static void mxsfb_unload(struct drm_device *drm)
 304{
 305        drm_kms_helper_poll_fini(drm);
 306        drm_mode_config_cleanup(drm);
 307
 308        pm_runtime_get_sync(drm->dev);
 309        mxsfb_irq_uninstall(drm);
 310        pm_runtime_put_sync(drm->dev);
 311
 312        drm->dev_private = NULL;
 313
 314        pm_runtime_disable(drm->dev);
 315}
 316
 317DEFINE_DRM_GEM_CMA_FOPS(fops);
 318
 319static const struct drm_driver mxsfb_driver = {
 320        .driver_features        = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
 321        DRM_GEM_CMA_DRIVER_OPS,
 322        .fops   = &fops,
 323        .name   = "mxsfb-drm",
 324        .desc   = "MXSFB Controller DRM",
 325        .date   = "20160824",
 326        .major  = 1,
 327        .minor  = 0,
 328};
 329
 330static const struct of_device_id mxsfb_dt_ids[] = {
 331        { .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devdata[MXSFB_V3], },
 332        { .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devdata[MXSFB_V4], },
 333        { .compatible = "fsl,imx6sx-lcdif", .data = &mxsfb_devdata[MXSFB_V6], },
 334        { /* sentinel */ }
 335};
 336MODULE_DEVICE_TABLE(of, mxsfb_dt_ids);
 337
 338static int mxsfb_probe(struct platform_device *pdev)
 339{
 340        struct drm_device *drm;
 341        const struct of_device_id *of_id =
 342                        of_match_device(mxsfb_dt_ids, &pdev->dev);
 343        int ret;
 344
 345        if (!pdev->dev.of_node)
 346                return -ENODEV;
 347
 348        drm = drm_dev_alloc(&mxsfb_driver, &pdev->dev);
 349        if (IS_ERR(drm))
 350                return PTR_ERR(drm);
 351
 352        ret = mxsfb_load(drm, of_id->data);
 353        if (ret)
 354                goto err_free;
 355
 356        ret = drm_dev_register(drm, 0);
 357        if (ret)
 358                goto err_unload;
 359
 360        drm_fbdev_generic_setup(drm, 32);
 361
 362        return 0;
 363
 364err_unload:
 365        mxsfb_unload(drm);
 366err_free:
 367        drm_dev_put(drm);
 368
 369        return ret;
 370}
 371
 372static int mxsfb_remove(struct platform_device *pdev)
 373{
 374        struct drm_device *drm = platform_get_drvdata(pdev);
 375
 376        drm_dev_unregister(drm);
 377        mxsfb_unload(drm);
 378        drm_dev_put(drm);
 379
 380        return 0;
 381}
 382
 383#ifdef CONFIG_PM_SLEEP
 384static int mxsfb_suspend(struct device *dev)
 385{
 386        struct drm_device *drm = dev_get_drvdata(dev);
 387
 388        return drm_mode_config_helper_suspend(drm);
 389}
 390
 391static int mxsfb_resume(struct device *dev)
 392{
 393        struct drm_device *drm = dev_get_drvdata(dev);
 394
 395        return drm_mode_config_helper_resume(drm);
 396}
 397#endif
 398
 399static const struct dev_pm_ops mxsfb_pm_ops = {
 400        SET_SYSTEM_SLEEP_PM_OPS(mxsfb_suspend, mxsfb_resume)
 401};
 402
 403static struct platform_driver mxsfb_platform_driver = {
 404        .probe          = mxsfb_probe,
 405        .remove         = mxsfb_remove,
 406        .driver = {
 407                .name           = "mxsfb",
 408                .of_match_table = mxsfb_dt_ids,
 409                .pm             = &mxsfb_pm_ops,
 410        },
 411};
 412
 413module_platform_driver(mxsfb_platform_driver);
 414
 415MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
 416MODULE_DESCRIPTION("Freescale MXS DRM/KMS driver");
 417MODULE_LICENSE("GPL");
 418