linux/drivers/media/platform/xilinx/xilinx-gamma.c
<<
>>
Prefs
   1/*
   2 * Xilinx Gamma Correction IP
   3 *
   4 * Copyright (C) 2017 Xilinx, Inc.
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9 */
  10
  11#include <linux/device.h>
  12#include <linux/gpio/consumer.h>
  13#include <linux/module.h>
  14#include <linux/of.h>
  15#include <linux/platform_device.h>
  16#include <linux/xilinx-v4l2-controls.h>
  17
  18#include <media/v4l2-async.h>
  19#include <media/v4l2-ctrls.h>
  20#include <media/v4l2-subdev.h>
  21
  22#include "xilinx-gamma-coeff.h"
  23#include "xilinx-vip.h"
  24
  25#define XGAMMA_MIN_HEIGHT       (64)
  26#define XGAMMA_MAX_HEIGHT       (4320)
  27#define XGAMMA_DEF_HEIGHT       (720)
  28#define XGAMMA_MIN_WIDTH        (64)
  29#define XGAMMA_MAX_WIDTH        (8192)
  30#define XGAMMA_DEF_WIDTH        (1280)
  31
  32#define XGAMMA_AP_CTRL                  (0x0000)
  33#define XGAMMA_GIE                      (0x0004)
  34#define XGAMMA_IER                      (0x0008)
  35#define XGAMMA_ISR                      (0x000c)
  36#define XGAMMA_WIDTH                    (0x0010)
  37#define XGAMMA_HEIGHT                   (0x0018)
  38#define XGAMMA_VIDEO_FORMAT             (0x0020)
  39#define XGAMMA_GAMMA_LUT_0_BASE         (0x0800)
  40#define XGAMMA_GAMMA_LUT_1_BASE         (0x1000)
  41#define XGAMMA_GAMMA_LUT_2_BASE         (0x1800)
  42
  43#define XGAMMA_RESET_DEASSERT   (0)
  44#define XGAMMA_RESET_ASSERT     (1)
  45#define XGAMMA_START            BIT(0)
  46#define XGAMMA_AUTO_RESTART     BIT(7)
  47#define XGAMMA_STREAM_ON        (XGAMMA_START | XGAMMA_AUTO_RESTART)
  48
  49enum xgamma_video_format {
  50        XGAMMA_RGB = 0,
  51};
  52
  53/**
  54 * struct xgamma_dev - Xilinx Video Gamma LUT device structure
  55 * @xvip: Xilinx Video IP device
  56 * @pads: Scaler sub-device media pads
  57 * @formats: V4L2 media bus formats at the sink and source pads
  58 * @default_formats: default V4L2 media bus formats
  59 * @ctrl_handler: V4L2 Control Handler for R,G,B Gamma Controls
  60 * @red_lut: Pointer to the gamma coefficient as per the Red Gamma control
  61 * @green_lut: Pointer to the gamma coefficient as per the Green Gamma control
  62 * @blue_lut: Pointer to the gamma coefficient as per the Blue Gamma control
  63 * @color_depth: Color depth of the Video Gamma IP
  64 * @gamma_table: Pointer to the table containing various gamma values
  65 * @rst_gpio: GPIO reset line to bring VPSS Scaler out of reset
  66 * @max_width: Maximum width supported by this instance.
  67 * @max_height: Maximum height supported by this instance.
  68 */
  69struct xgamma_dev {
  70        struct xvip_device xvip;
  71        struct media_pad pads[2];
  72        struct v4l2_mbus_framefmt formats[2];
  73        struct v4l2_mbus_framefmt default_formats[2];
  74        struct v4l2_ctrl_handler ctrl_handler;
  75
  76        const u16 *red_lut;
  77        const u16 *green_lut;
  78        const u16 *blue_lut;
  79        u32 color_depth;
  80        const u16 **gamma_table;
  81        struct gpio_desc *rst_gpio;
  82        u32 max_width;
  83        u32 max_height;
  84};
  85
  86static inline u32 xg_read(struct xgamma_dev *xg, u32 reg)
  87{
  88        u32 data;
  89
  90        data = xvip_read(&xg->xvip, reg);
  91        dev_dbg(xg->xvip.dev,
  92                "Reading 0x%x from reg offset 0x%x", data, reg);
  93        return data;
  94}
  95
  96static inline void xg_write(struct xgamma_dev *xg, u32 reg, u32 data)
  97{
  98        dev_dbg(xg->xvip.dev,
  99                "Writing 0x%x to reg offset 0x%x", data, reg);
 100        xvip_write(&xg->xvip, reg, data);
 101#ifdef DEBUG
 102        if (xg_read(xg, reg) != data)
 103                dev_err(xg->xvip.dev,
 104                        "Write 0x%x does not match read back", data);
 105#endif
 106}
 107
 108static inline struct xgamma_dev *to_xg(struct v4l2_subdev *subdev)
 109{
 110        return container_of(subdev, struct xgamma_dev, xvip.subdev);
 111}
 112
 113static struct v4l2_mbus_framefmt *
 114__xg_get_pad_format(struct xgamma_dev *xg,
 115                    struct v4l2_subdev_pad_config *cfg,
 116                    unsigned int pad, u32 which)
 117{
 118        switch (which) {
 119        case V4L2_SUBDEV_FORMAT_TRY:
 120                return v4l2_subdev_get_try_format(
 121                                        &xg->xvip.subdev, cfg, pad);
 122        case V4L2_SUBDEV_FORMAT_ACTIVE:
 123                return &xg->formats[pad];
 124        default:
 125                return NULL;
 126        }
 127}
 128
 129static void xg_set_lut_entries(struct xgamma_dev *xg,
 130                               const u16 *lut, const u32 lut_base)
 131{
 132        int itr;
 133        u32 lut_offset, lut_data;
 134
 135        lut_offset = lut_base;
 136        /* Write LUT Entries */
 137        for (itr = 0; itr < BIT(xg->color_depth - 1); itr++) {
 138                lut_data = (lut[2 * itr + 1] << 16) | lut[2 * itr];
 139                xg_write(xg, lut_offset, lut_data);
 140                lut_offset += 4;
 141        }
 142}
 143
 144static int xg_s_stream(struct v4l2_subdev *subdev, int enable)
 145{
 146        struct xgamma_dev *xg = to_xg(subdev);
 147
 148        if (!enable) {
 149                dev_dbg(xg->xvip.dev, "%s : Off", __func__);
 150                gpiod_set_value_cansleep(xg->rst_gpio, XGAMMA_RESET_ASSERT);
 151                gpiod_set_value_cansleep(xg->rst_gpio, XGAMMA_RESET_DEASSERT);
 152                return 0;
 153        }
 154        dev_dbg(xg->xvip.dev, "%s : Started", __func__);
 155
 156        dev_dbg(xg->xvip.dev, "%s : Setting width %d and height %d",
 157                __func__, xg->formats[XVIP_PAD_SINK].width,
 158                xg->formats[XVIP_PAD_SINK].height);
 159        xg_write(xg, XGAMMA_WIDTH, xg->formats[XVIP_PAD_SINK].width);
 160        xg_write(xg, XGAMMA_HEIGHT, xg->formats[XVIP_PAD_SINK].height);
 161        xg_write(xg, XGAMMA_VIDEO_FORMAT, XGAMMA_RGB);
 162        xg_set_lut_entries(xg, xg->red_lut, XGAMMA_GAMMA_LUT_0_BASE);
 163        xg_set_lut_entries(xg, xg->green_lut, XGAMMA_GAMMA_LUT_1_BASE);
 164        xg_set_lut_entries(xg, xg->blue_lut, XGAMMA_GAMMA_LUT_2_BASE);
 165
 166        /* Start GAMMA Correction LUT Video IP */
 167        xg_write(xg, XGAMMA_AP_CTRL, XGAMMA_STREAM_ON);
 168        return 0;
 169}
 170
 171static const struct v4l2_subdev_video_ops xg_video_ops = {
 172        .s_stream = xg_s_stream,
 173};
 174
 175static int xg_get_format(struct v4l2_subdev *subdev,
 176                         struct v4l2_subdev_pad_config *cfg,
 177                         struct v4l2_subdev_format *fmt)
 178{
 179        struct xgamma_dev *xg = to_xg(subdev);
 180
 181        fmt->format = *__xg_get_pad_format(xg, cfg, fmt->pad, fmt->which);
 182        return 0;
 183}
 184
 185static int xg_set_format(struct v4l2_subdev *subdev,
 186                         struct v4l2_subdev_pad_config *cfg,
 187                         struct v4l2_subdev_format *fmt)
 188{
 189        struct xgamma_dev *xg = to_xg(subdev);
 190        struct v4l2_mbus_framefmt *__format;
 191
 192        __format = __xg_get_pad_format(xg, cfg, fmt->pad, fmt->which);
 193        *__format = fmt->format;
 194
 195        if (fmt->pad == XVIP_PAD_SINK) {
 196                if (__format->code != MEDIA_BUS_FMT_RBG888_1X24) {
 197                        dev_dbg(xg->xvip.dev,
 198                                "Unsupported sink media bus code format");
 199                        __format->code = MEDIA_BUS_FMT_RBG888_1X24;
 200                }
 201        }
 202        __format->width = clamp_t(unsigned int, fmt->format.width,
 203                                  XGAMMA_MIN_WIDTH, xg->max_width);
 204        __format->height = clamp_t(unsigned int, fmt->format.height,
 205                                   XGAMMA_MIN_HEIGHT, xg->max_height);
 206
 207        fmt->format = *__format;
 208        /* Propagate to Source Pad */
 209        __format = __xg_get_pad_format(xg, cfg, XVIP_PAD_SOURCE, fmt->which);
 210        *__format = fmt->format;
 211        return 0;
 212}
 213
 214static int xg_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
 215{
 216        struct xgamma_dev *xg = to_xg(subdev);
 217        struct v4l2_mbus_framefmt *format;
 218
 219        format = v4l2_subdev_get_try_format(subdev, fh->pad, XVIP_PAD_SINK);
 220        *format = xg->default_formats[XVIP_PAD_SINK];
 221
 222        format = v4l2_subdev_get_try_format(subdev, fh->pad, XVIP_PAD_SOURCE);
 223        *format = xg->default_formats[XVIP_PAD_SOURCE];
 224        return 0;
 225}
 226
 227static int xg_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
 228{
 229        return 0;
 230}
 231
 232static const struct v4l2_subdev_internal_ops xg_internal_ops = {
 233        .open = xg_open,
 234        .close = xg_close,
 235};
 236
 237static const struct v4l2_subdev_pad_ops xg_pad_ops = {
 238        .enum_mbus_code = xvip_enum_mbus_code,
 239        .enum_frame_size = xvip_enum_frame_size,
 240        .get_fmt = xg_get_format,
 241        .set_fmt = xg_set_format,
 242};
 243
 244static const struct v4l2_subdev_ops xg_ops = {
 245        .video = &xg_video_ops,
 246        .pad = &xg_pad_ops,
 247};
 248
 249static int
 250select_gamma(s32 value, const u16 **coeff, const u16 **xgamma_curves)
 251{
 252        if (!coeff)
 253                return -EINVAL;
 254        if (value <= 0 || value > GAMMA_CURVE_LENGTH)
 255                return -EINVAL;
 256
 257        *coeff = *(xgamma_curves + value - 1);
 258        return 0;
 259}
 260
 261static int xg_s_ctrl(struct v4l2_ctrl *ctrl)
 262{
 263        int rval;
 264        struct xgamma_dev *xg =
 265                container_of(ctrl->handler,
 266                             struct xgamma_dev, ctrl_handler);
 267        dev_dbg(xg->xvip.dev, "%s called", __func__);
 268        switch (ctrl->id) {
 269        case V4L2_CID_XILINX_GAMMA_CORR_RED_GAMMA:
 270                rval = select_gamma(ctrl->val, &xg->red_lut, xg->gamma_table);
 271                if (rval < 0) {
 272                        dev_err(xg->xvip.dev, "Invalid Red Gamma");
 273                        return rval;
 274                }
 275                dev_dbg(xg->xvip.dev, "%s: Setting Red Gamma to %d.%d",
 276                        __func__, ctrl->val / 10, ctrl->val % 10);
 277                xg_set_lut_entries(xg, xg->red_lut, XGAMMA_GAMMA_LUT_0_BASE);
 278                break;
 279        case V4L2_CID_XILINX_GAMMA_CORR_BLUE_GAMMA:
 280                rval = select_gamma(ctrl->val, &xg->blue_lut, xg->gamma_table);
 281                if (rval < 0) {
 282                        dev_err(xg->xvip.dev, "Invalid Blue Gamma");
 283                        return rval;
 284                }
 285                dev_dbg(xg->xvip.dev, "%s: Setting Blue Gamma to %d.%d",
 286                        __func__, ctrl->val / 10, ctrl->val % 10);
 287                xg_set_lut_entries(xg, xg->blue_lut, XGAMMA_GAMMA_LUT_1_BASE);
 288                break;
 289        case V4L2_CID_XILINX_GAMMA_CORR_GREEN_GAMMA:
 290                rval = select_gamma(ctrl->val, &xg->green_lut, xg->gamma_table);
 291                if (rval < 0) {
 292                        dev_err(xg->xvip.dev, "Invalid Green Gamma");
 293                        return -EINVAL;
 294                }
 295                dev_dbg(xg->xvip.dev, "%s: Setting Green Gamma to %d.%d",
 296                        __func__, ctrl->val / 10, ctrl->val % 10);
 297                xg_set_lut_entries(xg, xg->green_lut, XGAMMA_GAMMA_LUT_2_BASE);
 298                break;
 299        }
 300        return 0;
 301}
 302
 303static const struct v4l2_ctrl_ops xg_ctrl_ops = {
 304        .s_ctrl = xg_s_ctrl,
 305};
 306
 307static struct v4l2_ctrl_config xg_ctrls[] = {
 308        /* Red Gamma */
 309        {
 310                .ops = &xg_ctrl_ops,
 311                .id = V4L2_CID_XILINX_GAMMA_CORR_RED_GAMMA,
 312                .name = "Red Gamma Correction|1->0.1|10->1.0",
 313                .type = V4L2_CTRL_TYPE_INTEGER,
 314                .min = 1,
 315                .max = 40,
 316                .step = 1,
 317                .def = 10,
 318                .flags = V4L2_CTRL_FLAG_SLIDER,
 319        },
 320        /* Blue Gamma */
 321        {
 322                .ops = &xg_ctrl_ops,
 323                .id = V4L2_CID_XILINX_GAMMA_CORR_BLUE_GAMMA,
 324                .name = "Blue Gamma Correction|1->0.1|10->1.0",
 325                .type = V4L2_CTRL_TYPE_INTEGER,
 326                .min = 1,
 327                .max = 40,
 328                .step = 1,
 329                .def = 10,
 330                .flags = V4L2_CTRL_FLAG_SLIDER,
 331        },
 332        /* Green Gamma */
 333        {
 334                .ops = &xg_ctrl_ops,
 335                .id = V4L2_CID_XILINX_GAMMA_CORR_GREEN_GAMMA,
 336                .name = "Green Gamma Correction|1->0.1|10->1.0)",
 337                .type = V4L2_CTRL_TYPE_INTEGER,
 338                .min = 1,
 339                .max = 40,
 340                .step = 1,
 341                .def = 10,
 342                .flags = V4L2_CTRL_FLAG_SLIDER,
 343        },
 344};
 345
 346static const struct media_entity_operations xg_media_ops = {
 347        .link_validate = v4l2_subdev_link_validate,
 348};
 349
 350static int xg_parse_of(struct xgamma_dev *xg)
 351{
 352        struct device *dev = xg->xvip.dev;
 353        struct device_node *node = dev->of_node;
 354        struct device_node *ports;
 355        struct device_node *port;
 356        u32 port_id = 0;
 357        int rval;
 358
 359        rval = of_property_read_u32(node, "xlnx,max-height", &xg->max_height);
 360        if (rval < 0) {
 361                dev_err(dev, "xlnx,max-height is missing!");
 362                return -EINVAL;
 363        } else if (xg->max_height > XGAMMA_MAX_HEIGHT ||
 364                   xg->max_height < XGAMMA_MIN_HEIGHT) {
 365                dev_err(dev, "Invalid height in dt");
 366                return -EINVAL;
 367        }
 368
 369        rval = of_property_read_u32(node, "xlnx,max-width", &xg->max_width);
 370        if (rval < 0) {
 371                dev_err(dev, "xlnx,max-width is missing!");
 372                return -EINVAL;
 373        } else if (xg->max_width > XGAMMA_MAX_WIDTH ||
 374                   xg->max_width < XGAMMA_MIN_WIDTH) {
 375                dev_err(dev, "Invalid width in dt");
 376                return -EINVAL;
 377        }
 378
 379        ports = of_get_child_by_name(node, "ports");
 380        if (!ports)
 381                ports = node;
 382
 383        /* Get the format description for each pad */
 384        for_each_child_of_node(ports, port) {
 385                if (port->name && (of_node_cmp(port->name, "port") == 0)) {
 386                        rval = of_property_read_u32(port, "reg", &port_id);
 387                        if (rval < 0) {
 388                                dev_err(dev, "No reg in DT");
 389                                return rval;
 390                        }
 391                        if (port_id != 0 && port_id != 1) {
 392                                dev_err(dev, "Invalid reg in DT");
 393                                return -EINVAL;
 394                        }
 395
 396                        rval = of_property_read_u32(port, "xlnx,video-width",
 397                                                    &xg->color_depth);
 398                        if (rval < 0) {
 399                                dev_err(dev, "Missing xlnx-video-width in DT");
 400                                return rval;
 401                        }
 402                        switch (xg->color_depth) {
 403                        case GAMMA_BPC_8:
 404                                xg->gamma_table = xgamma8_curves;
 405                                break;
 406                        case GAMMA_BPC_10:
 407                                xg->gamma_table = xgamma10_curves;
 408                                break;
 409                        default:
 410                                dev_err(dev, "Unsupported color depth %d",
 411                                        xg->color_depth);
 412                                return -EINVAL;
 413                        }
 414                }
 415        }
 416
 417        xg->rst_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
 418        if (IS_ERR(xg->rst_gpio)) {
 419                if (PTR_ERR(xg->rst_gpio) != -EPROBE_DEFER)
 420                        dev_err(dev, "Reset GPIO not setup in DT");
 421                return PTR_ERR(xg->rst_gpio);
 422        }
 423        return 0;
 424}
 425
 426static int xg_probe(struct platform_device *pdev)
 427{
 428        struct xgamma_dev *xg;
 429        struct v4l2_subdev *subdev;
 430        struct v4l2_mbus_framefmt *def_fmt;
 431        int rval, itr;
 432
 433        dev_dbg(&pdev->dev, "Gamma LUT Probe Started");
 434        xg = devm_kzalloc(&pdev->dev, sizeof(*xg), GFP_KERNEL);
 435        if (!xg)
 436                return -ENOMEM;
 437        xg->xvip.dev = &pdev->dev;
 438        rval = xg_parse_of(xg);
 439        if (rval < 0)
 440                return rval;
 441        rval = xvip_init_resources(&xg->xvip);
 442
 443        dev_dbg(xg->xvip.dev, "Reset Xilinx Video Gamma Corrrection");
 444        gpiod_set_value_cansleep(xg->rst_gpio, XGAMMA_RESET_DEASSERT);
 445
 446        /* Init V4L2 subdev */
 447        subdev = &xg->xvip.subdev;
 448        v4l2_subdev_init(subdev, &xg_ops);
 449        subdev->dev = &pdev->dev;
 450        subdev->internal_ops = &xg_internal_ops;
 451        strlcpy(subdev->name, dev_name(&pdev->dev), sizeof(subdev->name));
 452        subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
 453
 454        /* Default Formats Initialization */
 455        def_fmt = &xg->default_formats[XVIP_PAD_SINK];
 456        /* GAMMA LUT IP only to be supported for RGB */
 457        def_fmt->code = MEDIA_BUS_FMT_RBG888_1X24;
 458        def_fmt->field = V4L2_FIELD_NONE;
 459        def_fmt->colorspace = V4L2_COLORSPACE_SRGB;
 460        def_fmt->width = XGAMMA_DEF_WIDTH;
 461        def_fmt->height = XGAMMA_DEF_HEIGHT;
 462        xg->formats[XVIP_PAD_SINK] = *def_fmt;
 463
 464        def_fmt = &xg->default_formats[XVIP_PAD_SOURCE];
 465        *def_fmt = xg->default_formats[XVIP_PAD_SINK];
 466        xg->formats[XVIP_PAD_SOURCE] = *def_fmt;
 467
 468        xg->pads[XVIP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
 469        xg->pads[XVIP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
 470
 471        /* Init Media Entity */
 472        subdev->entity.ops = &xg_media_ops;
 473        rval = media_entity_pads_init(&subdev->entity, 2, xg->pads);
 474        if (rval < 0)
 475                goto media_error;
 476
 477        /* V4L2 Controls */
 478        v4l2_ctrl_handler_init(&xg->ctrl_handler, ARRAY_SIZE(xg_ctrls));
 479        for (itr = 0; itr < ARRAY_SIZE(xg_ctrls); itr++) {
 480                v4l2_ctrl_new_custom(&xg->ctrl_handler,
 481                                     &xg_ctrls[itr], NULL);
 482        }
 483        if (xg->ctrl_handler.error) {
 484                dev_err(&pdev->dev, "Failed to add V4L2 controls");
 485                rval = xg->ctrl_handler.error;
 486                goto ctrl_error;
 487        }
 488        subdev->ctrl_handler = &xg->ctrl_handler;
 489        rval = v4l2_ctrl_handler_setup(&xg->ctrl_handler);
 490        if (rval < 0) {
 491                dev_err(&pdev->dev, "Failed to setup control handler");
 492                goto  ctrl_error;
 493        }
 494
 495        platform_set_drvdata(pdev, xg);
 496        rval = v4l2_async_register_subdev(subdev);
 497        if (rval < 0) {
 498                dev_err(&pdev->dev, "failed to register subdev");
 499                goto v4l2_subdev_error;
 500        }
 501        dev_info(&pdev->dev,
 502                 "Xilinx %d-bit Video Gamma Correction LUT registered",
 503                 xg->color_depth);
 504        return 0;
 505ctrl_error:
 506        v4l2_ctrl_handler_free(&xg->ctrl_handler);
 507v4l2_subdev_error:
 508        media_entity_cleanup(&subdev->entity);
 509media_error:
 510        xvip_cleanup_resources(&xg->xvip);
 511        return rval;
 512}
 513
 514static int xg_remove(struct platform_device *pdev)
 515{
 516        struct xgamma_dev *xg = platform_get_drvdata(pdev);
 517        struct v4l2_subdev *subdev = &xg->xvip.subdev;
 518
 519        v4l2_async_unregister_subdev(subdev);
 520        /* Add entry to cleanup v4l2 control handle */
 521        media_entity_cleanup(&subdev->entity);
 522        xvip_cleanup_resources(&xg->xvip);
 523        return 0;
 524}
 525
 526static const struct of_device_id xg_of_id_table[] = {
 527        {.compatible = "xlnx,v-gamma-lut"},
 528        { }
 529};
 530MODULE_DEVICE_TABLE(of, xg_of_id_table);
 531
 532static struct platform_driver xg_driver = {
 533        .driver = {
 534                .name = "xilinx-gamma-lut",
 535                .of_match_table = xg_of_id_table,
 536        },
 537        .probe = xg_probe,
 538        .remove = xg_remove,
 539};
 540
 541module_platform_driver(xg_driver);
 542MODULE_DESCRIPTION("Xilinx Video Gamma Correction LUT Driver");
 543MODULE_LICENSE("GPL v2");
 544