linux/drivers/gpu/drm/exynos/exynos_drm_rotator.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (C) 2012 Samsung Electronics Co.Ltd
   4 * Authors:
   5 *      YoungJun Cho <yj44.cho@samsung.com>
   6 *      Eunchul Kim <chulspro.kim@samsung.com>
   7 */
   8
   9#include <linux/clk.h>
  10#include <linux/component.h>
  11#include <linux/err.h>
  12#include <linux/interrupt.h>
  13#include <linux/io.h>
  14#include <linux/kernel.h>
  15#include <linux/of_device.h>
  16#include <linux/platform_device.h>
  17#include <linux/pm_runtime.h>
  18#include <linux/sizes.h>
  19
  20#include <drm/drm_fourcc.h>
  21#include <drm/exynos_drm.h>
  22
  23#include "exynos_drm_drv.h"
  24#include "exynos_drm_ipp.h"
  25#include "regs-rotator.h"
  26
  27/*
  28 * Rotator supports image crop/rotator and input/output DMA operations.
  29 * input DMA reads image data from the memory.
  30 * output DMA writes image data to memory.
  31 */
  32
  33#define ROTATOR_AUTOSUSPEND_DELAY       2000
  34
  35#define rot_read(offset)        readl(rot->regs + (offset))
  36#define rot_write(cfg, offset)  writel(cfg, rot->regs + (offset))
  37
  38enum rot_irq_status {
  39        ROT_IRQ_STATUS_COMPLETE = 8,
  40        ROT_IRQ_STATUS_ILLEGAL  = 9,
  41};
  42
  43struct rot_variant {
  44        const struct exynos_drm_ipp_formats *formats;
  45        unsigned int    num_formats;
  46};
  47
  48/*
  49 * A structure of rotator context.
  50 * @ippdrv: prepare initialization using ippdrv.
  51 * @regs: memory mapped io registers.
  52 * @clock: rotator gate clock.
  53 * @limit_tbl: limitation of rotator.
  54 * @irq: irq number.
  55 */
  56struct rot_context {
  57        struct exynos_drm_ipp ipp;
  58        struct drm_device *drm_dev;
  59        void            *dma_priv;
  60        struct device   *dev;
  61        void __iomem    *regs;
  62        struct clk      *clock;
  63        const struct exynos_drm_ipp_formats *formats;
  64        unsigned int    num_formats;
  65        struct exynos_drm_ipp_task      *task;
  66};
  67
  68static void rotator_reg_set_irq(struct rot_context *rot, bool enable)
  69{
  70        u32 val = rot_read(ROT_CONFIG);
  71
  72        if (enable == true)
  73                val |= ROT_CONFIG_IRQ;
  74        else
  75                val &= ~ROT_CONFIG_IRQ;
  76
  77        rot_write(val, ROT_CONFIG);
  78}
  79
  80static enum rot_irq_status rotator_reg_get_irq_status(struct rot_context *rot)
  81{
  82        u32 val = rot_read(ROT_STATUS);
  83
  84        val = ROT_STATUS_IRQ(val);
  85
  86        if (val == ROT_STATUS_IRQ_VAL_COMPLETE)
  87                return ROT_IRQ_STATUS_COMPLETE;
  88
  89        return ROT_IRQ_STATUS_ILLEGAL;
  90}
  91
  92static irqreturn_t rotator_irq_handler(int irq, void *arg)
  93{
  94        struct rot_context *rot = arg;
  95        enum rot_irq_status irq_status;
  96        u32 val;
  97
  98        /* Get execution result */
  99        irq_status = rotator_reg_get_irq_status(rot);
 100
 101        /* clear status */
 102        val = rot_read(ROT_STATUS);
 103        val |= ROT_STATUS_IRQ_PENDING((u32)irq_status);
 104        rot_write(val, ROT_STATUS);
 105
 106        if (rot->task) {
 107                struct exynos_drm_ipp_task *task = rot->task;
 108
 109                rot->task = NULL;
 110                pm_runtime_mark_last_busy(rot->dev);
 111                pm_runtime_put_autosuspend(rot->dev);
 112                exynos_drm_ipp_task_done(task,
 113                        irq_status == ROT_IRQ_STATUS_COMPLETE ? 0 : -EINVAL);
 114        }
 115
 116        return IRQ_HANDLED;
 117}
 118
 119static void rotator_src_set_fmt(struct rot_context *rot, u32 fmt)
 120{
 121        u32 val;
 122
 123        val = rot_read(ROT_CONTROL);
 124        val &= ~ROT_CONTROL_FMT_MASK;
 125
 126        switch (fmt) {
 127        case DRM_FORMAT_NV12:
 128                val |= ROT_CONTROL_FMT_YCBCR420_2P;
 129                break;
 130        case DRM_FORMAT_XRGB8888:
 131                val |= ROT_CONTROL_FMT_RGB888;
 132                break;
 133        }
 134
 135        rot_write(val, ROT_CONTROL);
 136}
 137
 138static void rotator_src_set_buf(struct rot_context *rot,
 139                                struct exynos_drm_ipp_buffer *buf)
 140{
 141        u32 val;
 142
 143        /* Set buffer size configuration */
 144        val = ROT_SET_BUF_SIZE_H(buf->buf.height) |
 145              ROT_SET_BUF_SIZE_W(buf->buf.pitch[0] / buf->format->cpp[0]);
 146        rot_write(val, ROT_SRC_BUF_SIZE);
 147
 148        /* Set crop image position configuration */
 149        val = ROT_CROP_POS_Y(buf->rect.y) | ROT_CROP_POS_X(buf->rect.x);
 150        rot_write(val, ROT_SRC_CROP_POS);
 151        val = ROT_SRC_CROP_SIZE_H(buf->rect.h) |
 152              ROT_SRC_CROP_SIZE_W(buf->rect.w);
 153        rot_write(val, ROT_SRC_CROP_SIZE);
 154
 155        /* Set buffer DMA address */
 156        rot_write(buf->dma_addr[0], ROT_SRC_BUF_ADDR(0));
 157        rot_write(buf->dma_addr[1], ROT_SRC_BUF_ADDR(1));
 158}
 159
 160static void rotator_dst_set_transf(struct rot_context *rot,
 161                                   unsigned int rotation)
 162{
 163        u32 val;
 164
 165        /* Set transform configuration */
 166        val = rot_read(ROT_CONTROL);
 167        val &= ~ROT_CONTROL_FLIP_MASK;
 168
 169        if (rotation & DRM_MODE_REFLECT_X)
 170                val |= ROT_CONTROL_FLIP_VERTICAL;
 171        if (rotation & DRM_MODE_REFLECT_Y)
 172                val |= ROT_CONTROL_FLIP_HORIZONTAL;
 173
 174        val &= ~ROT_CONTROL_ROT_MASK;
 175
 176        if (rotation & DRM_MODE_ROTATE_90)
 177                val |= ROT_CONTROL_ROT_90;
 178        else if (rotation & DRM_MODE_ROTATE_180)
 179                val |= ROT_CONTROL_ROT_180;
 180        else if (rotation & DRM_MODE_ROTATE_270)
 181                val |= ROT_CONTROL_ROT_270;
 182
 183        rot_write(val, ROT_CONTROL);
 184}
 185
 186static void rotator_dst_set_buf(struct rot_context *rot,
 187                                struct exynos_drm_ipp_buffer *buf)
 188{
 189        u32 val;
 190
 191        /* Set buffer size configuration */
 192        val = ROT_SET_BUF_SIZE_H(buf->buf.height) |
 193              ROT_SET_BUF_SIZE_W(buf->buf.pitch[0] / buf->format->cpp[0]);
 194        rot_write(val, ROT_DST_BUF_SIZE);
 195
 196        /* Set crop image position configuration */
 197        val = ROT_CROP_POS_Y(buf->rect.y) | ROT_CROP_POS_X(buf->rect.x);
 198        rot_write(val, ROT_DST_CROP_POS);
 199
 200        /* Set buffer DMA address */
 201        rot_write(buf->dma_addr[0], ROT_DST_BUF_ADDR(0));
 202        rot_write(buf->dma_addr[1], ROT_DST_BUF_ADDR(1));
 203}
 204
 205static void rotator_start(struct rot_context *rot)
 206{
 207        u32 val;
 208
 209        /* Set interrupt enable */
 210        rotator_reg_set_irq(rot, true);
 211
 212        val = rot_read(ROT_CONTROL);
 213        val |= ROT_CONTROL_START;
 214        rot_write(val, ROT_CONTROL);
 215}
 216
 217static int rotator_commit(struct exynos_drm_ipp *ipp,
 218                          struct exynos_drm_ipp_task *task)
 219{
 220        struct rot_context *rot =
 221                        container_of(ipp, struct rot_context, ipp);
 222        int ret;
 223
 224        ret = pm_runtime_resume_and_get(rot->dev);
 225        if (ret < 0) {
 226                dev_err(rot->dev, "failed to enable ROTATOR device.\n");
 227                return ret;
 228        }
 229        rot->task = task;
 230
 231        rotator_src_set_fmt(rot, task->src.buf.fourcc);
 232        rotator_src_set_buf(rot, &task->src);
 233        rotator_dst_set_transf(rot, task->transform.rotation);
 234        rotator_dst_set_buf(rot, &task->dst);
 235        rotator_start(rot);
 236
 237        return 0;
 238}
 239
 240static const struct exynos_drm_ipp_funcs ipp_funcs = {
 241        .commit = rotator_commit,
 242};
 243
 244static int rotator_bind(struct device *dev, struct device *master, void *data)
 245{
 246        struct rot_context *rot = dev_get_drvdata(dev);
 247        struct drm_device *drm_dev = data;
 248        struct exynos_drm_ipp *ipp = &rot->ipp;
 249
 250        rot->drm_dev = drm_dev;
 251        ipp->drm_dev = drm_dev;
 252        exynos_drm_register_dma(drm_dev, dev, &rot->dma_priv);
 253
 254        exynos_drm_ipp_register(dev, ipp, &ipp_funcs,
 255                           DRM_EXYNOS_IPP_CAP_CROP | DRM_EXYNOS_IPP_CAP_ROTATE,
 256                           rot->formats, rot->num_formats, "rotator");
 257
 258        dev_info(dev, "The exynos rotator has been probed successfully\n");
 259
 260        return 0;
 261}
 262
 263static void rotator_unbind(struct device *dev, struct device *master,
 264                        void *data)
 265{
 266        struct rot_context *rot = dev_get_drvdata(dev);
 267        struct exynos_drm_ipp *ipp = &rot->ipp;
 268
 269        exynos_drm_ipp_unregister(dev, ipp);
 270        exynos_drm_unregister_dma(rot->drm_dev, rot->dev, &rot->dma_priv);
 271}
 272
 273static const struct component_ops rotator_component_ops = {
 274        .bind   = rotator_bind,
 275        .unbind = rotator_unbind,
 276};
 277
 278static int rotator_probe(struct platform_device *pdev)
 279{
 280        struct device *dev = &pdev->dev;
 281        struct rot_context *rot;
 282        const struct rot_variant *variant;
 283        int irq;
 284        int ret;
 285
 286        rot = devm_kzalloc(dev, sizeof(*rot), GFP_KERNEL);
 287        if (!rot)
 288                return -ENOMEM;
 289
 290        variant = of_device_get_match_data(dev);
 291        rot->formats = variant->formats;
 292        rot->num_formats = variant->num_formats;
 293        rot->dev = dev;
 294        rot->regs = devm_platform_ioremap_resource(pdev, 0);
 295        if (IS_ERR(rot->regs))
 296                return PTR_ERR(rot->regs);
 297
 298        irq = platform_get_irq(pdev, 0);
 299        if (irq < 0)
 300                return irq;
 301
 302        ret = devm_request_irq(dev, irq, rotator_irq_handler, 0, dev_name(dev),
 303                               rot);
 304        if (ret < 0) {
 305                dev_err(dev, "failed to request irq\n");
 306                return ret;
 307        }
 308
 309        rot->clock = devm_clk_get(dev, "rotator");
 310        if (IS_ERR(rot->clock)) {
 311                dev_err(dev, "failed to get clock\n");
 312                return PTR_ERR(rot->clock);
 313        }
 314
 315        pm_runtime_use_autosuspend(dev);
 316        pm_runtime_set_autosuspend_delay(dev, ROTATOR_AUTOSUSPEND_DELAY);
 317        pm_runtime_enable(dev);
 318        platform_set_drvdata(pdev, rot);
 319
 320        ret = component_add(dev, &rotator_component_ops);
 321        if (ret)
 322                goto err_component;
 323
 324        return 0;
 325
 326err_component:
 327        pm_runtime_dont_use_autosuspend(dev);
 328        pm_runtime_disable(dev);
 329        return ret;
 330}
 331
 332static int rotator_remove(struct platform_device *pdev)
 333{
 334        struct device *dev = &pdev->dev;
 335
 336        component_del(dev, &rotator_component_ops);
 337        pm_runtime_dont_use_autosuspend(dev);
 338        pm_runtime_disable(dev);
 339
 340        return 0;
 341}
 342
 343#ifdef CONFIG_PM
 344static int rotator_runtime_suspend(struct device *dev)
 345{
 346        struct rot_context *rot = dev_get_drvdata(dev);
 347
 348        clk_disable_unprepare(rot->clock);
 349        return 0;
 350}
 351
 352static int rotator_runtime_resume(struct device *dev)
 353{
 354        struct rot_context *rot = dev_get_drvdata(dev);
 355
 356        return clk_prepare_enable(rot->clock);
 357}
 358#endif
 359
 360static const struct drm_exynos_ipp_limit rotator_s5pv210_rbg888_limits[] = {
 361        { IPP_SIZE_LIMIT(BUFFER, .h = { 8, SZ_16K }, .v = { 8, SZ_16K }) },
 362        { IPP_SIZE_LIMIT(AREA, .h.align = 2, .v.align = 2) },
 363};
 364
 365static const struct drm_exynos_ipp_limit rotator_4210_rbg888_limits[] = {
 366        { IPP_SIZE_LIMIT(BUFFER, .h = { 8, SZ_16K }, .v = { 8, SZ_16K }) },
 367        { IPP_SIZE_LIMIT(AREA, .h.align = 4, .v.align = 4) },
 368};
 369
 370static const struct drm_exynos_ipp_limit rotator_4412_rbg888_limits[] = {
 371        { IPP_SIZE_LIMIT(BUFFER, .h = { 8, SZ_8K }, .v = { 8, SZ_8K }) },
 372        { IPP_SIZE_LIMIT(AREA, .h.align = 4, .v.align = 4) },
 373};
 374
 375static const struct drm_exynos_ipp_limit rotator_5250_rbg888_limits[] = {
 376        { IPP_SIZE_LIMIT(BUFFER, .h = { 8, SZ_8K }, .v = { 8, SZ_8K }) },
 377        { IPP_SIZE_LIMIT(AREA, .h.align = 2, .v.align = 2) },
 378};
 379
 380static const struct drm_exynos_ipp_limit rotator_s5pv210_yuv_limits[] = {
 381        { IPP_SIZE_LIMIT(BUFFER, .h = { 32, SZ_64K }, .v = { 32, SZ_64K }) },
 382        { IPP_SIZE_LIMIT(AREA, .h.align = 8, .v.align = 8) },
 383};
 384
 385static const struct drm_exynos_ipp_limit rotator_4210_yuv_limits[] = {
 386        { IPP_SIZE_LIMIT(BUFFER, .h = { 32, SZ_64K }, .v = { 32, SZ_64K }) },
 387        { IPP_SIZE_LIMIT(AREA, .h.align = 8, .v.align = 8) },
 388};
 389
 390static const struct drm_exynos_ipp_limit rotator_4412_yuv_limits[] = {
 391        { IPP_SIZE_LIMIT(BUFFER, .h = { 32, SZ_32K }, .v = { 32, SZ_32K }) },
 392        { IPP_SIZE_LIMIT(AREA, .h.align = 8, .v.align = 8) },
 393};
 394
 395static const struct exynos_drm_ipp_formats rotator_s5pv210_formats[] = {
 396        { IPP_SRCDST_FORMAT(XRGB8888, rotator_s5pv210_rbg888_limits) },
 397        { IPP_SRCDST_FORMAT(NV12, rotator_s5pv210_yuv_limits) },
 398};
 399
 400static const struct exynos_drm_ipp_formats rotator_4210_formats[] = {
 401        { IPP_SRCDST_FORMAT(XRGB8888, rotator_4210_rbg888_limits) },
 402        { IPP_SRCDST_FORMAT(NV12, rotator_4210_yuv_limits) },
 403};
 404
 405static const struct exynos_drm_ipp_formats rotator_4412_formats[] = {
 406        { IPP_SRCDST_FORMAT(XRGB8888, rotator_4412_rbg888_limits) },
 407        { IPP_SRCDST_FORMAT(NV12, rotator_4412_yuv_limits) },
 408};
 409
 410static const struct exynos_drm_ipp_formats rotator_5250_formats[] = {
 411        { IPP_SRCDST_FORMAT(XRGB8888, rotator_5250_rbg888_limits) },
 412        { IPP_SRCDST_FORMAT(NV12, rotator_4412_yuv_limits) },
 413};
 414
 415static const struct rot_variant rotator_s5pv210_data = {
 416        .formats = rotator_s5pv210_formats,
 417        .num_formats = ARRAY_SIZE(rotator_s5pv210_formats),
 418};
 419
 420static const struct rot_variant rotator_4210_data = {
 421        .formats = rotator_4210_formats,
 422        .num_formats = ARRAY_SIZE(rotator_4210_formats),
 423};
 424
 425static const struct rot_variant rotator_4412_data = {
 426        .formats = rotator_4412_formats,
 427        .num_formats = ARRAY_SIZE(rotator_4412_formats),
 428};
 429
 430static const struct rot_variant rotator_5250_data = {
 431        .formats = rotator_5250_formats,
 432        .num_formats = ARRAY_SIZE(rotator_5250_formats),
 433};
 434
 435static const struct of_device_id exynos_rotator_match[] = {
 436        {
 437                .compatible = "samsung,s5pv210-rotator",
 438                .data = &rotator_s5pv210_data,
 439        }, {
 440                .compatible = "samsung,exynos4210-rotator",
 441                .data = &rotator_4210_data,
 442        }, {
 443                .compatible = "samsung,exynos4212-rotator",
 444                .data = &rotator_4412_data,
 445        }, {
 446                .compatible = "samsung,exynos5250-rotator",
 447                .data = &rotator_5250_data,
 448        }, {
 449        },
 450};
 451MODULE_DEVICE_TABLE(of, exynos_rotator_match);
 452
 453static const struct dev_pm_ops rotator_pm_ops = {
 454        SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
 455                                pm_runtime_force_resume)
 456        SET_RUNTIME_PM_OPS(rotator_runtime_suspend, rotator_runtime_resume,
 457                                                                        NULL)
 458};
 459
 460struct platform_driver rotator_driver = {
 461        .probe          = rotator_probe,
 462        .remove         = rotator_remove,
 463        .driver         = {
 464                .name   = "exynos-rotator",
 465                .owner  = THIS_MODULE,
 466                .pm     = &rotator_pm_ops,
 467                .of_match_table = exynos_rotator_match,
 468        },
 469};
 470