diff options
Diffstat (limited to 'drivers/gpu/drm/xlnx/xlnx_csc.c')
-rw-r--r-- | drivers/gpu/drm/xlnx/xlnx_csc.c | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/drivers/gpu/drm/xlnx/xlnx_csc.c b/drivers/gpu/drm/xlnx/xlnx_csc.c new file mode 100644 index 000000000000..1d4341dce570 --- /dev/null +++ b/drivers/gpu/drm/xlnx/xlnx_csc.c @@ -0,0 +1,571 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * VPSS CSC DRM bridge driver + * + * Copyright (C) 2017-2018 Xilinx, Inc. + * + * Author: Venkateshwar rao G <vgannava@xilinx.com> + */ + +/* + * Overview: + * This experimentatl driver works as a bridge driver and + * reused the code from V4L2. + * TODO: + * Need to implement in a modular approach to share driver code between + * V4L2 and DRM frameworks. + * Should be integrated with plane + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <uapi/linux/media-bus-format.h> + +#include "xlnx_bridge.h" + +/* Register offset */ +#define XV_CSC_AP_CTRL (0x000) +#define XV_CSC_INVIDEOFORMAT (0x010) +#define XV_CSC_OUTVIDEOFORMAT (0x018) +#define XV_CSC_WIDTH (0x020) +#define XV_CSC_HEIGHT (0x028) +#define XV_CSC_K11 (0x050) +#define XV_CSC_K12 (0x058) +#define XV_CSC_K13 (0x060) +#define XV_CSC_K21 (0x068) +#define XV_CSC_K22 (0x070) +#define XV_CSC_K23 (0x078) +#define XV_CSC_K31 (0x080) +#define XV_CSC_K32 (0x088) +#define XV_CSC_K33 (0x090) +#define XV_CSC_ROFFSET (0x098) +#define XV_CSC_GOFFSET (0x0a0) +#define XV_CSC_BOFFSET (0x0a8) +#define XV_CSC_CLAMPMIN (0x0b0) +#define XV_CSC_CLIPMAX (0x0b8) +#define XV_CSC_SCALE_FACTOR (4096) +#define XV_CSC_DIVISOR (10000) +/* Streaming Macros */ +#define XCSC_CLAMP_MIN_ZERO (0) +#define XCSC_AP_START BIT(0) +#define XCSC_AP_AUTO_RESTART BIT(7) +#define XCSC_STREAM_ON (XCSC_AP_START | XCSC_AP_AUTO_RESTART) +#define XCSC_STREAM_OFF (0) +/* GPIO Reset Assert/De-assert */ +#define XCSC_RESET_ASSERT (1) +#define XCSC_RESET_DEASSERT (0) + +#define XCSC_MIN_WIDTH (64) +#define XCSC_MAX_WIDTH (8192) +#define XCSC_MIN_HEIGHT (64) +#define XCSC_MAX_HEIGHT (4320) + +static const u32 xilinx_csc_video_fmts[] = { + MEDIA_BUS_FMT_RGB888_1X24, + MEDIA_BUS_FMT_VUY8_1X24, + MEDIA_BUS_FMT_UYVY8_1X16, + MEDIA_BUS_FMT_VYYUYY8_1X24, +}; + +/* vpss_csc_color_fmt - Color format type */ +enum vpss_csc_color_fmt { + XVIDC_CSF_RGB = 0, + XVIDC_CSF_YCRCB_444, + XVIDC_CSF_YCRCB_422, + XVIDC_CSF_YCRCB_420, +}; + +/** + * struct xilinx_csc - Core configuration of csc device structure + * @base: pointer to register base address + * @dev: device structure + * @bridge: xilinx bridge + * @cft_in: input color format + * @cft_out: output color format + * @color_depth: color depth + * @k_hw: array of hardware values + * @clip_max: clipping maximum value + * @width: width of the video + * @height: height of video + * @max_width: maximum number of pixels in a line + * @max_height: maximum number of lines per frame + * @rst_gpio: Handle to GPIO specifier to assert/de-assert the reset line + * @aclk: IP clock struct + */ +struct xilinx_csc { + void __iomem *base; + struct device *dev; + struct xlnx_bridge bridge; + enum vpss_csc_color_fmt cft_in; + enum vpss_csc_color_fmt cft_out; + u32 color_depth; + s32 k_hw[3][4]; + s32 clip_max; + u32 width; + u32 height; + u32 max_width; + u32 max_height; + struct gpio_desc *rst_gpio; + struct clk *aclk; +}; + +static inline void xilinx_csc_write(void __iomem *base, u32 offset, u32 val) +{ + writel(val, base + offset); +} + +static inline u32 xilinx_csc_read(void __iomem *base, u32 offset) +{ + return readl(base + offset); +} + +/** + * bridge_to_layer - Gets the parent structure + * @bridge: pointer to the member. + * + * Return: parent structure pointer + */ +static inline struct xilinx_csc *bridge_to_layer(struct xlnx_bridge *bridge) +{ + return container_of(bridge, struct xilinx_csc, bridge); +} + +static void xilinx_csc_write_rgb_3x3(struct xilinx_csc *csc) +{ + xilinx_csc_write(csc->base, XV_CSC_K11, csc->k_hw[0][0]); + xilinx_csc_write(csc->base, XV_CSC_K12, csc->k_hw[0][1]); + xilinx_csc_write(csc->base, XV_CSC_K13, csc->k_hw[0][2]); + xilinx_csc_write(csc->base, XV_CSC_K21, csc->k_hw[1][0]); + xilinx_csc_write(csc->base, XV_CSC_K22, csc->k_hw[1][1]); + xilinx_csc_write(csc->base, XV_CSC_K23, csc->k_hw[1][2]); + xilinx_csc_write(csc->base, XV_CSC_K31, csc->k_hw[2][0]); + xilinx_csc_write(csc->base, XV_CSC_K32, csc->k_hw[2][1]); + xilinx_csc_write(csc->base, XV_CSC_K33, csc->k_hw[2][2]); +} + +static void xilinx_csc_write_rgb_offset(struct xilinx_csc *csc) +{ + xilinx_csc_write(csc->base, XV_CSC_ROFFSET, csc->k_hw[0][3]); + xilinx_csc_write(csc->base, XV_CSC_GOFFSET, csc->k_hw[1][3]); + xilinx_csc_write(csc->base, XV_CSC_BOFFSET, csc->k_hw[2][3]); +} + +static void xilinx_csc_write_coeff(struct xilinx_csc *csc) +{ + xilinx_csc_write_rgb_3x3(csc); + xilinx_csc_write_rgb_offset(csc); +} + +static void xcsc_set_default_state(struct xilinx_csc *csc) +{ + csc->cft_in = XVIDC_CSF_YCRCB_422; + csc->cft_out = XVIDC_CSF_YCRCB_422; + + /* This represents an identity matrix mutliped by 2^12 */ + csc->k_hw[0][0] = XV_CSC_SCALE_FACTOR; + csc->k_hw[0][1] = 0; + csc->k_hw[0][2] = 0; + csc->k_hw[1][0] = 0; + csc->k_hw[1][1] = XV_CSC_SCALE_FACTOR; + csc->k_hw[1][2] = 0; + csc->k_hw[2][0] = 0; + csc->k_hw[2][1] = 0; + csc->k_hw[2][2] = XV_CSC_SCALE_FACTOR; + csc->k_hw[0][3] = 0; + csc->k_hw[1][3] = 0; + csc->k_hw[2][3] = 0; + csc->clip_max = ((1 << csc->color_depth) - 1); + xilinx_csc_write(csc->base, XV_CSC_INVIDEOFORMAT, csc->cft_in); + xilinx_csc_write(csc->base, XV_CSC_OUTVIDEOFORMAT, csc->cft_out); + xilinx_csc_write_coeff(csc); + xilinx_csc_write(csc->base, XV_CSC_CLIPMAX, csc->clip_max); + xilinx_csc_write(csc->base, XV_CSC_CLAMPMIN, XCSC_CLAMP_MIN_ZERO); +} + +static void xcsc_ycrcb_to_rgb(struct xilinx_csc *csc, s32 *clip_max) +{ + u16 bpc_scale = (1 << (csc->color_depth - 8)); + /* + * See http://graficaobscura.com/matrix/index.html for + * how these numbers are derived. The VPSS CSC IP is + * derived from this Matrix style algorithm. And the + * 'magic' numbers here are derived from the algorithm. + * + * XV_CSC_DIVISOR is used to help with floating constants + * while performing multiplicative operations. + * + * Coefficients valid only for BT 709 + */ + csc->k_hw[0][0] = 11644 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[0][1] = 0; + csc->k_hw[0][2] = 17927 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[1][0] = 11644 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[1][1] = -2132 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[1][2] = -5329 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[2][0] = 11644 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[2][1] = 21124 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[2][2] = 0; + csc->k_hw[0][3] = -248 * bpc_scale; + csc->k_hw[1][3] = 77 * bpc_scale; + csc->k_hw[2][3] = -289 * bpc_scale; + *clip_max = ((1 << csc->color_depth) - 1); +} + +static void xcsc_rgb_to_ycrcb(struct xilinx_csc *csc, s32 *clip_max) +{ + u16 bpc_scale = (1 << (csc->color_depth - 8)); + /* + * See http://graficaobscura.com/matrix/index.html for + * how these numbers are derived. The VPSS CSC + * derived from this Matrix style algorithm. And the + * 'magic' numbers here are derived from the algorithm. + * + * XV_CSC_DIVISOR is used to help with floating constants + * while performing multiplicative operations. + * + * Coefficients valid only for BT 709 + */ + csc->k_hw[0][0] = 1826 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[0][1] = 6142 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[0][2] = 620 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[1][0] = -1006 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[1][1] = -3386 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[1][2] = 4392 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[2][0] = 4392 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[2][1] = -3989 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[2][2] = -403 * XV_CSC_SCALE_FACTOR / XV_CSC_DIVISOR; + csc->k_hw[0][3] = 16 * bpc_scale; + csc->k_hw[1][3] = 128 * bpc_scale; + csc->k_hw[2][3] = 128 * bpc_scale; + *clip_max = ((1 << csc->color_depth) - 1); +} + +/** + * xcsc_set_coeff- Sets the coefficients + * @csc: Pointer to csc device structure + * + * This function set the coefficients + * + */ +static void xcsc_set_coeff(struct xilinx_csc *csc) +{ + xilinx_csc_write(csc->base, XV_CSC_INVIDEOFORMAT, csc->cft_in); + xilinx_csc_write(csc->base, XV_CSC_OUTVIDEOFORMAT, csc->cft_out); + xilinx_csc_write_coeff(csc); + xilinx_csc_write(csc->base, XV_CSC_CLIPMAX, csc->clip_max); + xilinx_csc_write(csc->base, XV_CSC_CLAMPMIN, XCSC_CLAMP_MIN_ZERO); +} + +/** + * xilinx_csc_bridge_enable - enabes csc core + * @bridge: bridge instance + * + * This function enables the csc core + * + * Return: 0 on success. + * + */ +static int xilinx_csc_bridge_enable(struct xlnx_bridge *bridge) +{ + struct xilinx_csc *csc = bridge_to_layer(bridge); + + xilinx_csc_write(csc->base, XV_CSC_AP_CTRL, XCSC_STREAM_ON); + + return 0; +} + +/** + * xilinx_csc_bridge_disable - disables csc core + * @bridge: bridge instance + * + * This function disables the csc core + */ +static void xilinx_csc_bridge_disable(struct xlnx_bridge *bridge) +{ + struct xilinx_csc *csc = bridge_to_layer(bridge); + + xilinx_csc_write(csc->base, XV_CSC_AP_CTRL, XCSC_STREAM_OFF); + /* Reset the Global IP Reset through GPIO */ + gpiod_set_value_cansleep(csc->rst_gpio, XCSC_RESET_ASSERT); + gpiod_set_value_cansleep(csc->rst_gpio, XCSC_RESET_DEASSERT); +} + +/** + * xilinx_csc_bridge_set_input - Sets the input parameters of csc + * @bridge: bridge instance + * @width: width of video + * @height: height of video + * @bus_fmt: video bus format + * + * This function sets the input parameters of csc + * Return: 0 on success. -EINVAL for invalid parameters. + */ +static int xilinx_csc_bridge_set_input(struct xlnx_bridge *bridge, u32 width, + u32 height, u32 bus_fmt) +{ + struct xilinx_csc *csc = bridge_to_layer(bridge); + + xcsc_set_default_state(csc); + + if (height > csc->max_height || height < XCSC_MIN_HEIGHT) + return -EINVAL; + + if (width > csc->max_width || width < XCSC_MIN_WIDTH) + return -EINVAL; + + csc->height = height; + csc->width = width; + + switch (bus_fmt) { + case MEDIA_BUS_FMT_RGB888_1X24: + csc->cft_in = XVIDC_CSF_RGB; + break; + case MEDIA_BUS_FMT_VUY8_1X24: + csc->cft_in = XVIDC_CSF_YCRCB_444; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + csc->cft_in = XVIDC_CSF_YCRCB_422; + break; + case MEDIA_BUS_FMT_VYYUYY8_1X24: + csc->cft_in = XVIDC_CSF_YCRCB_420; + break; + default: + dev_dbg(csc->dev, "unsupported input video format\n"); + return -EINVAL; + } + + xilinx_csc_write(csc->base, XV_CSC_WIDTH, width); + xilinx_csc_write(csc->base, XV_CSC_HEIGHT, height); + + return 0; +} + +/** + * xilinx_csc_bridge_get_input_fmts - input formats supported by csc + * @bridge: bridge instance + * @fmts: Pointer to be updated with formats information + * @count: count of video bus formats + * + * This function provides the input video formats information csc + * Return: 0 on success. + */ +static int xilinx_csc_bridge_get_input_fmts(struct xlnx_bridge *bridge, + const u32 **fmts, u32 *count) +{ + *fmts = xilinx_csc_video_fmts; + *count = ARRAY_SIZE(xilinx_csc_video_fmts); + + return 0; +} + +/** + * xilinx_csc_bridge_set_output - Sets the output parameters of csc + * @bridge: bridge instance + * @width: width of video + * @height: height of video + * @bus_fmt: video bus format + * + * This function sets the output parameters of csc + * Return: 0 on success. -EINVAL for invalid parameters. + */ +static int xilinx_csc_bridge_set_output(struct xlnx_bridge *bridge, u32 width, + u32 height, u32 bus_fmt) +{ + struct xilinx_csc *csc = bridge_to_layer(bridge); + + if (width != csc->width || height != csc->height) + return -EINVAL; + + switch (bus_fmt) { + case MEDIA_BUS_FMT_RGB888_1X24: + csc->cft_out = XVIDC_CSF_RGB; + dev_dbg(csc->dev, "Media Format Out : RGB"); + if (csc->cft_in != MEDIA_BUS_FMT_RBG888_1X24) + xcsc_ycrcb_to_rgb(csc, &csc->clip_max); + break; + case MEDIA_BUS_FMT_VUY8_1X24: + csc->cft_out = XVIDC_CSF_YCRCB_444; + dev_dbg(csc->dev, "Media Format Out : YUV 444"); + if (csc->cft_in == MEDIA_BUS_FMT_RBG888_1X24) + xcsc_rgb_to_ycrcb(csc, &csc->clip_max); + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + csc->cft_out = XVIDC_CSF_YCRCB_422; + dev_dbg(csc->dev, "Media Format Out : YUV 422"); + if (csc->cft_in == MEDIA_BUS_FMT_RBG888_1X24) + xcsc_rgb_to_ycrcb(csc, &csc->clip_max); + break; + case MEDIA_BUS_FMT_VYYUYY8_1X24: + csc->cft_out = XVIDC_CSF_YCRCB_420; + dev_dbg(csc->dev, "Media Format Out : YUV 420"); + if (csc->cft_in == MEDIA_BUS_FMT_RBG888_1X24) + xcsc_rgb_to_ycrcb(csc, &csc->clip_max); + break; + default: + dev_info(csc->dev, "unsupported output video format\n"); + return -EINVAL; + } + xcsc_set_coeff(csc); + + return 0; +} + +/** + * xilinx_csc_bridge_get_output_fmts - output formats supported by csc + * @bridge: bridge instance + * @fmts: Pointer to be updated with formats information + * @count: count of video bus formats + * + * This function provides the output video formats information csc + * Return: 0 on success. + */ +static int xilinx_csc_bridge_get_output_fmts(struct xlnx_bridge *bridge, + const u32 **fmts, u32 *count) +{ + *fmts = xilinx_csc_video_fmts; + *count = ARRAY_SIZE(xilinx_csc_video_fmts); + return 0; +} + +static int xcsc_parse_of(struct xilinx_csc *csc) +{ + int ret; + struct device_node *node = csc->dev->of_node; + + csc->aclk = devm_clk_get(csc->dev, NULL); + if (IS_ERR(csc->aclk)) { + ret = PTR_ERR(csc->aclk); + dev_err(csc->dev, "failed to get aclk %d\n", ret); + return ret; + } + + ret = of_property_read_u32(node, "xlnx,video-width", + &csc->color_depth); + if (ret < 0) { + dev_info(csc->dev, "video width not present in DT\n"); + return ret; + } + if (csc->color_depth != 8 && csc->color_depth != 10 && + csc->color_depth != 12 && csc->color_depth != 16) { + dev_err(csc->dev, "Invalid video width in DT\n"); + return -EINVAL; + } + /* Reset GPIO */ + csc->rst_gpio = devm_gpiod_get(csc->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(csc->rst_gpio)) { + if (PTR_ERR(csc->rst_gpio) != -EPROBE_DEFER) + dev_err(csc->dev, "Reset GPIO not setup in DT"); + return PTR_ERR(csc->rst_gpio); + } + + ret = of_property_read_u32(node, "xlnx,max-height", &csc->max_height); + if (ret < 0) { + dev_err(csc->dev, "xlnx,max-height is missing!"); + return -EINVAL; + } else if (csc->max_height > XCSC_MAX_HEIGHT || + csc->max_height < XCSC_MIN_HEIGHT) { + dev_err(csc->dev, "Invalid height in dt"); + return -EINVAL; + } + + ret = of_property_read_u32(node, "xlnx,max-width", &csc->max_width); + if (ret < 0) { + dev_err(csc->dev, "xlnx,max-width is missing!"); + return -EINVAL; + } else if (csc->max_width > XCSC_MAX_WIDTH || + csc->max_width < XCSC_MIN_WIDTH) { + dev_err(csc->dev, "Invalid width in dt"); + return -EINVAL; + } + + return 0; +} + +static int xilinx_csc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct xilinx_csc *csc; + int ret; + + csc = devm_kzalloc(dev, sizeof(*csc), GFP_KERNEL); + if (!csc) + return -ENOMEM; + + csc->dev = dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + csc->base = devm_ioremap_resource(dev, res); + if (IS_ERR(csc->base)) + return -ENOMEM; + + platform_set_drvdata(pdev, csc); + ret = xcsc_parse_of(csc); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(csc->aclk); + if (ret) { + dev_err(csc->dev, "failed to enable clock %d\n", ret); + return ret; + } + + gpiod_set_value_cansleep(csc->rst_gpio, XCSC_RESET_DEASSERT); + csc->bridge.enable = &xilinx_csc_bridge_enable; + csc->bridge.disable = &xilinx_csc_bridge_disable; + csc->bridge.set_input = &xilinx_csc_bridge_set_input; + csc->bridge.get_input_fmts = &xilinx_csc_bridge_get_input_fmts; + csc->bridge.set_output = &xilinx_csc_bridge_set_output; + csc->bridge.get_output_fmts = &xilinx_csc_bridge_get_output_fmts; + csc->bridge.of_node = dev->of_node; + + ret = xlnx_bridge_register(&csc->bridge); + if (ret) { + dev_info(csc->dev, "Bridge registration failed\n"); + goto err_clk; + } + + dev_info(csc->dev, "Xilinx VPSS CSC DRM experimental driver probed\n"); + + return 0; + +err_clk: + clk_disable_unprepare(csc->aclk); + return ret; +} + +static int xilinx_csc_remove(struct platform_device *pdev) +{ + struct xilinx_csc *csc = platform_get_drvdata(pdev); + + xlnx_bridge_unregister(&csc->bridge); + clk_disable_unprepare(csc->aclk); + + return 0; +} + +static const struct of_device_id xilinx_csc_of_match[] = { + { .compatible = "xlnx,vpss-csc"}, + { } +}; +MODULE_DEVICE_TABLE(of, xilinx_csc_of_match); + +static struct platform_driver csc_bridge_driver = { + .probe = xilinx_csc_probe, + .remove = xilinx_csc_remove, + .driver = { + .name = "xlnx,csc-bridge", + .of_match_table = xilinx_csc_of_match, + }, +}; + +module_platform_driver(csc_bridge_driver); + +MODULE_AUTHOR("Venkateshwar Rao <vgannava@xilinx.com>"); +MODULE_DESCRIPTION("Xilinx FPGA CSC Bridge Driver"); +MODULE_LICENSE("GPL v2"); |