diff options
Diffstat (limited to 'drivers/staging/xlnx_ctrl_driver')
-rw-r--r-- | drivers/staging/xlnx_ctrl_driver/Kconfig | 15 | ||||
-rw-r--r-- | drivers/staging/xlnx_ctrl_driver/MAINTAINERS | 4 | ||||
-rw-r--r-- | drivers/staging/xlnx_ctrl_driver/Makefile | 2 | ||||
-rw-r--r-- | drivers/staging/xlnx_ctrl_driver/xlnx_frmb.c | 290 | ||||
-rw-r--r-- | drivers/staging/xlnx_ctrl_driver/xlnx_vpss.c | 595 |
5 files changed, 906 insertions, 0 deletions
diff --git a/drivers/staging/xlnx_ctrl_driver/Kconfig b/drivers/staging/xlnx_ctrl_driver/Kconfig new file mode 100644 index 000000000000..3bff5e6d1aca --- /dev/null +++ b/drivers/staging/xlnx_ctrl_driver/Kconfig @@ -0,0 +1,15 @@ +config XLNX_CTRL_FRMBUF + tristate "FB Control driver" + help + This driver is to support Xilinx Framebuffer read and write IP. This + driver is simple control plane driver which is controlled by ioctls + from userspace. It is free from any other media framework like V4l2 or + DRM hence, doesn't need to adhere to V4L2 or DRM. + +config XLNX_CTRL_VPSS + tristate "VPSS Control driver" + help + This driver is to support Xilinx VPSS IP. This driver is simple + control plane driver which is controlled by ioctls from userspace. It + is free from any media framework like V4l2 or DRM hence, doesn't need + to adhere to V4L2 or DRM. diff --git a/drivers/staging/xlnx_ctrl_driver/MAINTAINERS b/drivers/staging/xlnx_ctrl_driver/MAINTAINERS new file mode 100644 index 000000000000..bcfd70d359ec --- /dev/null +++ b/drivers/staging/xlnx_ctrl_driver/MAINTAINERS @@ -0,0 +1,4 @@ +XILINX CONTROL DRIVER +M: Saurabh Sengar <saurabh.singh@xilinx.com> +S: Maintained +F: drivers/staging/xlnx_ctrl_driver diff --git a/drivers/staging/xlnx_ctrl_driver/Makefile b/drivers/staging/xlnx_ctrl_driver/Makefile new file mode 100644 index 000000000000..312bd1f5d233 --- /dev/null +++ b/drivers/staging/xlnx_ctrl_driver/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_XLNX_CTRL_FRMBUF) += xlnx_frmb.o +obj-$(CONFIG_XLNX_CTRL_VPSS) += xlnx_vpss.o diff --git a/drivers/staging/xlnx_ctrl_driver/xlnx_frmb.c b/drivers/staging/xlnx_ctrl_driver/xlnx_frmb.c new file mode 100644 index 000000000000..0b36575e493b --- /dev/null +++ b/drivers/staging/xlnx_ctrl_driver/xlnx_frmb.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx FPGA Xilinx Framebuffer read control driver + * + * Copyright (c) 2018-2019 Xilinx Pvt., Ltd + * + * Author: Saurabh Sengar <saurabh.singh@xilinx.com> + */ + +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/dma-buf.h> +#include <linux/dmaengine.h> +#include <linux/gpio/consumer.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/miscdevice.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/xlnx_ctrl.h> + +/* TODO: clock framework */ + +#define XFBWR_FB_CTRL 0x00 +#define XFBWR_FB_WIDTH 0x10 +#define XFBWR_FB_HEIGHT 0x18 +#define XFBWR_FB_STRIDE 0x20 +#define XFBWR_FB_COLOR 0x28 +#define XFBWR_FB_PLANE1 0x30 +#define XFBWR_FB_PLANE2 0x3C + +#define XFBWR_FB_CTRL_START BIT(0) +#define XFBWR_FB_CTRL_IDLE BIT(2) +#define XFBWR_FB_CTRL_RESTART BIT(7) +#define XFBWR_FB_CTRL_OFF 0 + +static u64 dma_mask = -1ULL; + +struct frmb_dmabuf_reg { + s32 dmabuf_fd; + struct dma_buf *dbuf; + struct dma_buf_attachment *dbuf_attach; + struct sg_table *dbuf_sg_table; +}; + +/** + * struct frmb_struct - Xilinx framebuffer ctrl object + * + * @dev: device structure + * @db: framebuffer ctrl driver dmabuf structure + * @frmb_miscdev: The misc device registered + * @regs: Base address of framebuffer IP + * @is_fbrd: True for framebuffer Read else false + */ +struct frmb_struct { + struct device *dev; + struct frmb_dmabuf_reg db; + struct miscdevice frmb_miscdev; + void __iomem *regs; + bool is_fbrd; +}; + +struct frmb_data { + u32 fd; + u32 height; + u32 width; + u32 stride; + u32 color; + u32 n_planes; + u32 offset; +}; + +struct match_struct { + char name[8]; + bool is_read; +}; + +static const struct match_struct read_struct = { + .name = "fbrd", + .is_read = true, +}; + +static const struct match_struct write_struct = { + .name = "fbwr", + .is_read = false, +}; + +/* Match table for of_platform binding */ +static const struct of_device_id frmb_of_match[] = { + { .compatible = "xlnx,ctrl-fbwr-1.0", .data = &write_struct}, + { .compatible = "xlnx,ctrl-fbrd-1.0", .data = &read_struct}, + {}, +}; + +MODULE_DEVICE_TABLE(of, frmb_of_match); + +static inline struct frmb_struct *to_frmb_struct(struct file *file) +{ + struct miscdevice *miscdev = file->private_data; + + return container_of(miscdev, struct frmb_struct, frmb_miscdev); +} + +static inline u32 frmb_ior(void __iomem *lp, off_t offset) +{ + return readl(lp + offset); +} + +static inline void frmb_iow(void __iomem *lp, off_t offset, u32 value) +{ + writel(value, (lp + offset)); +} + +phys_addr_t frmb_add_dmabuf(u32 fd, struct frmb_struct *frmb_g) +{ + frmb_g->db.dbuf = dma_buf_get(fd); + frmb_g->db.dbuf_attach = dma_buf_attach(frmb_g->db.dbuf, frmb_g->dev); + if (IS_ERR(frmb_g->db.dbuf_attach)) { + dma_buf_put(frmb_g->db.dbuf); + dev_err(frmb_g->dev, "Failed DMA-BUF attach\n"); + return -EINVAL; + } + + frmb_g->db.dbuf_sg_table = dma_buf_map_attachment(frmb_g->db.dbuf_attach + , DMA_BIDIRECTIONAL); + + if (!frmb_g->db.dbuf_sg_table) { + dev_err(frmb_g->dev, "Failed DMA-BUF map_attachment\n"); + dma_buf_detach(frmb_g->db.dbuf, frmb_g->db.dbuf_attach); + dma_buf_put(frmb_g->db.dbuf); + return -EINVAL; + } + + return (u32)sg_dma_address(frmb_g->db.dbuf_sg_table->sgl); +} + +static void xlnk_clear_dmabuf(struct frmb_struct *frmb_g) +{ + dma_buf_unmap_attachment(frmb_g->db.dbuf_attach, + frmb_g->db.dbuf_sg_table, + DMA_BIDIRECTIONAL); + dma_buf_detach(frmb_g->db.dbuf, frmb_g->db.dbuf_attach); + dma_buf_put(frmb_g->db.dbuf); +} + +static long frmb_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + long retval = 0; + struct frmb_data data; + phys_addr_t phys_y = 0, phys_uv = 0; + struct frmb_struct *frmb_g = to_frmb_struct(file); + + switch (cmd) { + case XSET_FB_POLL: + retval = frmb_ior(frmb_g->regs, XFBWR_FB_CTRL); + if (retval == XFBWR_FB_CTRL_IDLE) + retval = 0; + else + retval = 1; + break; + case XSET_FB_ENABLE_SNGL: + frmb_iow(frmb_g->regs, XFBWR_FB_CTRL, XFBWR_FB_CTRL_START); + break; + case XSET_FB_ENABLE: + frmb_iow(frmb_g->regs, XFBWR_FB_CTRL, XFBWR_FB_CTRL_START); + frmb_iow(frmb_g->regs, XFBWR_FB_CTRL, + XFBWR_FB_CTRL_RESTART | XFBWR_FB_CTRL_START); + break; + case XSET_FB_DISABLE: + frmb_iow(frmb_g->regs, XFBWR_FB_CTRL, XFBWR_FB_CTRL_OFF); + break; + case XSET_FB_CONFIGURE: + if (copy_from_user(&data, (char __user *)arg, sizeof(data))) { + pr_err("Copy from user failed\n"); + retval = -EINVAL; + break; + } + frmb_iow(frmb_g->regs, XFBWR_FB_WIDTH, data.width); + frmb_iow(frmb_g->regs, XFBWR_FB_HEIGHT, data.height); + frmb_iow(frmb_g->regs, XFBWR_FB_STRIDE, data.stride); + frmb_iow(frmb_g->regs, XFBWR_FB_COLOR, data.color); + break; + case XSET_FB_CAPTURE: + if (copy_from_user(&data, (char __user *)arg, sizeof(data))) { + pr_err("Copy from user failed\n"); + retval = -EINVAL; + break; + } + phys_y = frmb_add_dmabuf(data.fd, frmb_g); + frmb_iow(frmb_g->regs, XFBWR_FB_PLANE1, phys_y); + if (data.n_planes == 2) { + phys_uv = phys_y + data.offset; + frmb_iow(frmb_g->regs, XFBWR_FB_PLANE2, phys_uv); + } + break; + case XSET_FB_RELEASE: + xlnk_clear_dmabuf(frmb_g); + break; + default: + retval = -EINVAL; + } + return retval; +} + +static const struct file_operations frmb_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = frmb_ioctl, +}; + +static int frmb_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + int ret; + struct resource *res_frmb; + const struct of_device_id *match; + struct frmb_struct *frmb_g; + struct gpio_desc *reset_gpio; + const struct match_struct *config; + + pdev->dev.dma_mask = &dma_mask; + pdev->dev.coherent_dma_mask = dma_mask; + + frmb_g = devm_kzalloc(&pdev->dev, sizeof(*frmb_g), GFP_KERNEL); + if (!frmb_g) + return -ENOMEM; + + reset_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset_gpio)) { + ret = PTR_ERR(reset_gpio); + if (ret == -EPROBE_DEFER) + dev_dbg(&pdev->dev, "No gpio probed, Deferring...\n"); + else + dev_err(&pdev->dev, "No reset gpio info from dts\n"); + return ret; + } + gpiod_set_value_cansleep(reset_gpio, 0); + + platform_set_drvdata(pdev, frmb_g); + frmb_g->dev = &pdev->dev; + res_frmb = platform_get_resource(pdev, IORESOURCE_MEM, 0); + frmb_g->regs = devm_ioremap_resource(&pdev->dev, res_frmb); + if (IS_ERR(frmb_g->regs)) + return PTR_ERR(frmb_g->regs); + + match = of_match_node(frmb_of_match, node); + if (!match) + return -ENODEV; + + config = match->data; + frmb_g->frmb_miscdev.name = config->name; + frmb_g->is_fbrd = config->is_read; + + frmb_g->frmb_miscdev.minor = MISC_DYNAMIC_MINOR; + frmb_g->frmb_miscdev.fops = &frmb_fops; + frmb_g->frmb_miscdev.parent = NULL; + ret = misc_register(&frmb_g->frmb_miscdev); + if (ret < 0) { + dev_err(&pdev->dev, "FrameBuffer control driver registration failed!\n"); + return ret; + } + + dev_info(&pdev->dev, "FrameBuffer control driver success!\n"); + + return ret; +} + +static int frmb_remove(struct platform_device *pdev) +{ + struct frmb_struct *frmb_g = platform_get_drvdata(pdev); + + misc_deregister(&frmb_g->frmb_miscdev); + return 0; +} + +static struct platform_driver frmb_driver = { + .probe = frmb_probe, + .remove = frmb_remove, + .driver = { + .name = "xlnx_ctrl-frmb", + .of_match_table = frmb_of_match, + }, +}; + +module_platform_driver(frmb_driver); + +MODULE_DESCRIPTION("Xilinx Framebuffer control driver"); +MODULE_AUTHOR("Saurabh Sengar"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/xlnx_ctrl_driver/xlnx_vpss.c b/drivers/staging/xlnx_ctrl_driver/xlnx_vpss.c new file mode 100644 index 000000000000..017ad0a4cffd --- /dev/null +++ b/drivers/staging/xlnx_ctrl_driver/xlnx_vpss.c @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx FPGA Xilinx VPSS control driver. + * + * Copyright (c) 2018-2019 Xilinx Pvt., Ltd + * + * Author: Saurabh Sengar <saurabh.singh@xilinx.com> + */ + +/* TODO: clock framework */ + +#include <linux/fs.h> +#include <linux/gpio/consumer.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/miscdevice.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/xlnx_ctrl.h> + +/* VPSS block offset */ +#define XHSCALER_OFFSET 0 +#define XSAXIS_RST_OFFSET 0x10000 +#define XVSCALER_OFFSET 0x20000 + +#define XVPSS_GPIO_CHAN 8 + +#define XVPSS_MAX_WIDTH 3840 +#define XVPSS_MAX_HEIGHT 2160 + +#define XVPSS_STEPPREC 65536 + +/* Video IP PPC */ +#define XVPSS_PPC_1 1 +#define XVPSS_PPC_2 2 + +#define XVPSS_MAX_TAPS 12 +#define XVPSS_PHASES 64 +#define XVPSS_TAPS_6 6 + +/* Mask definitions for Low and high 16 bits in a 32 bit number */ +#define XVPSS_MASK_LOW_16BITS GENMASK(15, 0) +#define XVPSS_MASK_LOW_32BITS GENMASK(31, 0) +#define XVPSS_STEP_PRECISION_SHIFT (16) +#define XVPSS_PHASE_SHIFT_BY_6 (6) +#define XVPSS_PHASE_MULTIPLIER (9) +#define XVPSS_BITSHIFT_16 (16) + +/* VPSS AP Control Registers */ +#define XVPSS_START BIT(0) +#define XVPSS_RESTART BIT(7) +#define XVPSS_STREAM_ON (XVPSS_START | XVPSS_RESTART) + +/* H-scaler registers */ +#define XVPSS_H_AP_CTRL (0x0000) +#define XVPSS_H_GIE (0x0004) +#define XVPSS_H_IER (0x0008) +#define XVPSS_H_ISR (0x000c) +#define XVPSS_H_HEIGHT (0x0010) +#define XVPSS_H_WIDTHIN (0x0018) +#define XVPSS_H_WIDTHOUT (0x0020) +#define XVPSS_H_COLOR (0x0028) +#define XVPSS_H_PIXELRATE (0x0030) +#define XVPSS_H_COLOROUT (0X0038) +#define XVPSS_H_HFLTCOEFF_BASE (0x0800) +#define XVPSS_H_HFLTCOEFF_HIGH (0x0bff) +#define XVPSS_H_PHASESH_V_BASE (0x2000) +#define XVPSS_H_PHASESH_V_HIGH (0x3fff) + +/* H-scaler masks */ +#define XVPSS_PHASESH_WR_EN BIT(8) + +/* V-scaler registers */ +#define XVPSS_V_AP_CTRL (0x000) +#define XVPSS_V_GIE (0x004) +#define XVPSS_V_IER (0x008) +#define XVPSS_V_ISR (0x00c) +#define XVPSS_V_HEIGHTIN (0x010) +#define XVPSS_V_WIDTH (0x018) +#define XVPSS_V_HEIGHTOUT (0x020) +#define XVPSS_V_LINERATE (0x028) +#define XVPSS_V_COLOR (0x030) +#define XVPSS_V_VFLTCOEFF_BASE (0x800) +#define XVPSS_V_VFLTCOEFF_HIGH (0xbff) + +#define XVPSS_GPIO_RST_SEL 1 +#define XVPSS_GPIO_VIDEO_IN BIT(0) +#define XVPSS_RST_IP_AXIS BIT(1) +#define XVPSS_GPIO_MASK_ALL (XVPSS_GPIO_VIDEO_IN | XVPSS_RST_IP_AXIS) + +enum xvpss_color { + XVPSS_YUV_RGB, + XVPSS_YUV_444, + XVPSS_YUV_422, + XVPSS_YUV_420, +}; + +/* VPSS coefficients for 6 tap filters */ +static const u16 +xvpss_coeff_taps6[XVPSS_PHASES][XVPSS_TAPS_6] = { + { -132, 236, 3824, 236, -132, 64, }, + { -116, 184, 3816, 292, -144, 64, }, + { -100, 132, 3812, 348, -160, 64, }, + { -88, 84, 3808, 404, -176, 64, }, + { -72, 36, 3796, 464, -192, 64, }, + { -60, -8, 3780, 524, -208, 68, }, + { -48, -52, 3768, 588, -228, 68, }, + { -32, -96, 3748, 652, -244, 68, }, + { -20, -136, 3724, 716, -260, 72, }, + { -8, -172, 3696, 784, -276, 72, }, + { 0, -208, 3676, 848, -292, 72, }, + { 12, -244, 3640, 920, -308, 76, }, + { 20, -276, 3612, 988, -324, 76, }, + { 32, -304, 3568, 1060, -340, 80, }, + { 40, -332, 3532, 1132, -356, 80, }, + { 48, -360, 3492, 1204, -372, 84, }, + { 56, -384, 3448, 1276, -388, 88, }, + { 64, -408, 3404, 1352, -404, 88, }, + { 72, -428, 3348, 1428, -416, 92, }, + { 76, -448, 3308, 1500, -432, 92, }, + { 84, -464, 3248, 1576, -444, 96, }, + { 88, -480, 3200, 1652, -460, 96, }, + { 92, -492, 3140, 1728, -472, 100, }, + { 96, -504, 3080, 1804, -484, 104, }, + { 100, -516, 3020, 1880, -492, 104, }, + { 104, -524, 2956, 1960, -504, 104, }, + { 104, -532, 2892, 2036, -512, 108, }, + { 108, -540, 2832, 2108, -520, 108, }, + { 108, -544, 2764, 2184, -528, 112, }, + { 112, -544, 2688, 2260, -532, 112, }, + { 112, -548, 2624, 2336, -540, 112, }, + { 112, -548, 2556, 2408, -544, 112, }, + { 112, -544, 2480, 2480, -544, 112, }, + { 112, -544, 2408, 2556, -548, 112, }, + { 112, -540, 2336, 2624, -548, 112, }, + { 112, -532, 2260, 2688, -544, 112, }, + { 112, -528, 2184, 2764, -544, 108, }, + { 108, -520, 2108, 2832, -540, 108, }, + { 108, -512, 2036, 2892, -532, 104, }, + { 104, -504, 1960, 2956, -524, 104, }, + { 104, -492, 1880, 3020, -516, 100, }, + { 104, -484, 1804, 3080, -504, 96, }, + { 100, -472, 1728, 3140, -492, 92, }, + { 96, -460, 1652, 3200, -480, 88, }, + { 96, -444, 1576, 3248, -464, 84, }, + { 92, -432, 1500, 3308, -448, 76, }, + { 92, -416, 1428, 3348, -428, 72, }, + { 88, -404, 1352, 3404, -408, 64, }, + { 88, -388, 1276, 3448, -384, 56, }, + { 84, -372, 1204, 3492, -360, 48, }, + { 80, -356, 1132, 3532, -332, 40, }, + { 80, -340, 1060, 3568, -304, 32, }, + { 76, -324, 988, 3612, -276, 20, }, + { 76, -308, 920, 3640, -244, 12, }, + { 72, -292, 848, 3676, -208, 0, }, + { 72, -276, 784, 3696, -172, -8, }, + { 72, -260, 716, 3724, -136, -20, }, + { 68, -244, 652, 3748, -96, -32, }, + { 68, -228, 588, 3768, -52, -48, }, + { 68, -208, 524, 3780, -8, -60, }, + { 64, -192, 464, 3796, 36, -72, }, + { 64, -176, 404, 3808, 84, -88, }, + { 64, -160, 348, 3812, 132, -100, }, + { 64, -144, 292, 3816, 184, -116, } +}; + +/** + * struct xvpss_struct - Xilinx VPSS ctrl object + * + * @dev: device structure + * @xvpss_miscdev: The misc device registered + * @regs: Base address of VPSS + * @n_taps: number of horizontal/vertical taps + * @ppc: Pixels per Clock cycle the IP operates upon + * @is_polyphase: True for polypshase else false + * @vpss_coeff: The complete array of H-scaler/V-scaler coefficients + * @H_phases: The phases needed to program the H-scaler for different taps + * @reset_gpio: GPIO reset line to bring VPSS Scaler out of reset + */ +struct xvpss_struct { + struct device *dev; + struct miscdevice xvpss_miscdev; + void __iomem *regs; + int n_taps; + int ppc; + bool is_polyphase; + short vpss_coeff[XVPSS_PHASES][XVPSS_MAX_TAPS]; + u32 H_phases[XVPSS_MAX_WIDTH]; + struct gpio_desc *reset_gpio; +}; + +struct xvpss_data { + u32 height_in; + u32 width_in; + u32 height_out; + u32 width_out; + u32 color_in; + u32 color_out; +}; + +/* Match table for of_platform binding */ +static const struct of_device_id xvpss_of_match[] = { + { .compatible = "xlnx,ctrl-xvpss-1.0", }, + {}, +}; +MODULE_DEVICE_TABLE(of, xvpss_of_match); + +static inline struct xvpss_struct *to_xvpss_struct(struct file *file) +{ + struct miscdevice *miscdev = file->private_data; + + return container_of(miscdev, struct xvpss_struct, xvpss_miscdev); +} + +static inline u32 xvpss_ior(void __iomem *lp, off_t offset) +{ + return readl(lp + offset); +} + +static inline void xvpss_iow(void __iomem *lp, off_t offset, u32 value) +{ + writel(value, (lp + offset)); +} + +static inline void xvpss_clr(void __iomem *base, u32 offset, u32 clr) +{ + xvpss_iow(base, offset, xvpss_ior(base, offset) & ~clr); +} + +static inline void xvpss_set(void __iomem *base, u32 offset, u32 set) +{ + xvpss_iow(base, offset, xvpss_ior(base, offset) | set); +} + +static inline void xvpss_disable_block(struct xvpss_struct *xvpss_g, + u32 channel, u32 ip_block) +{ + xvpss_clr(xvpss_g->regs, ((channel - 1) * XVPSS_GPIO_CHAN) + + XSAXIS_RST_OFFSET, ip_block); +} + +static inline void +xvpss_enable_block(struct xvpss_struct *xvpss_g, u32 channel, u32 ip_block) +{ + xvpss_set(xvpss_g->regs, ((channel - 1) * XVPSS_GPIO_CHAN) + + XSAXIS_RST_OFFSET, ip_block); +} + +static void xvpss_reset(struct xvpss_struct *xvpss_g) +{ + xvpss_disable_block(xvpss_g, XVPSS_GPIO_RST_SEL, XVPSS_GPIO_MASK_ALL); + xvpss_enable_block(xvpss_g, XVPSS_GPIO_RST_SEL, XVPSS_RST_IP_AXIS); +} + +static void xvpss_enable(struct xvpss_struct *xvpss_g) +{ + xvpss_iow(xvpss_g->regs, XHSCALER_OFFSET + + XVPSS_H_AP_CTRL, XVPSS_STREAM_ON); + xvpss_iow(xvpss_g->regs, XVSCALER_OFFSET + + XVPSS_V_AP_CTRL, XVPSS_STREAM_ON); + xvpss_enable_block(xvpss_g, XVPSS_GPIO_RST_SEL, XVPSS_RST_IP_AXIS); +} + +static void xvpss_disable(struct xvpss_struct *xvpss_g) +{ + xvpss_disable_block(xvpss_g, XVPSS_GPIO_RST_SEL, XVPSS_GPIO_MASK_ALL); +} + +static void xvpss_set_input(struct xvpss_struct *xvpss_g, + u32 width, u32 height, u32 color) +{ + xvpss_iow(xvpss_g->regs, XVSCALER_OFFSET + XVPSS_V_HEIGHTIN, height); + xvpss_iow(xvpss_g->regs, XVSCALER_OFFSET + XVPSS_V_WIDTH, width); + xvpss_iow(xvpss_g->regs, XHSCALER_OFFSET + XVPSS_H_WIDTHIN, width); + xvpss_iow(xvpss_g->regs, XVSCALER_OFFSET + XVPSS_V_COLOR, color); +} + +static void xvpss_set_output(struct xvpss_struct *xvpss_g, u32 width, + u32 height, u32 color) +{ + xvpss_iow(xvpss_g->regs, XVSCALER_OFFSET + XVPSS_V_HEIGHTOUT, height); + xvpss_iow(xvpss_g->regs, XHSCALER_OFFSET + XVPSS_H_HEIGHT, height); + xvpss_iow(xvpss_g->regs, XHSCALER_OFFSET + XVPSS_H_WIDTHOUT, width); + xvpss_iow(xvpss_g->regs, XHSCALER_OFFSET + XVPSS_H_COLOROUT, color); +} + +static void xvpss_load_ext_coeff(struct xvpss_struct *xvpss_g, + const short *coeff, u32 ntaps) +{ + unsigned int i, j, pad, offset; + + /* Determine if coefficient needs padding (effective vs. max taps) */ + pad = XVPSS_MAX_TAPS - ntaps; + offset = pad >> 1; + /* Load coefficients into vpss coefficient table */ + for (i = 0; i < XVPSS_PHASES; i++) { + for (j = 0; j < ntaps; ++j) + xvpss_g->vpss_coeff[i][j + offset] = + coeff[i * ntaps + j]; + } + if (pad) { + for (i = 0; i < XVPSS_PHASES; i++) { + for (j = 0; j < offset; j++) + xvpss_g->vpss_coeff[i][j] = 0; + j = ntaps + offset; + for (; j < XVPSS_MAX_TAPS; j++) + xvpss_g->vpss_coeff[i][j] = 0; + } + } +} + +static void xvpss_select_coeff(struct xvpss_struct *xvpss_g) +{ + const short *coeff; + u32 ntaps; + + coeff = &xvpss_coeff_taps6[0][0]; + ntaps = XVPSS_TAPS_6; + + xvpss_load_ext_coeff(xvpss_g, coeff, ntaps); +} + +static void xvpss_set_coeff(struct xvpss_struct *xvpss_g) +{ + u32 nphases = XVPSS_PHASES; + u32 ntaps = xvpss_g->n_taps; + int val, i, j, offset, rd_indx; + u32 v_addr, h_addr; + + offset = (XVPSS_MAX_TAPS - ntaps) / 2; + v_addr = XVSCALER_OFFSET + XVPSS_V_VFLTCOEFF_BASE; + h_addr = XHSCALER_OFFSET + XVPSS_H_HFLTCOEFF_BASE; + + for (i = 0; i < nphases; i++) { + for (j = 0; j < ntaps / 2; j++) { + rd_indx = j * 2 + offset; + val = (xvpss_g->vpss_coeff[i][rd_indx + 1] << + XVPSS_BITSHIFT_16) | (xvpss_g->vpss_coeff[i][rd_indx] & + XVPSS_MASK_LOW_16BITS); + xvpss_iow(xvpss_g->regs, v_addr + + ((i * ntaps / 2 + j) * 4), val); + xvpss_iow(xvpss_g->regs, h_addr + + ((i * ntaps / 2 + j) * 4), val); + } + } +} + +static void xvpss_h_calculate_phases(struct xvpss_struct *xvpss_g, + u32 width_in, u32 width_out, + u32 pixel_rate) +{ + unsigned int loop_width, x, s, nphases = XVPSS_PHASES; + unsigned int nppc = xvpss_g->ppc; + unsigned int shift = XVPSS_STEP_PRECISION_SHIFT - ilog2(nphases); + int offset = 0, xwrite_pos = 0, nr_rds, nr_rds_clck; + bool output_write_en, get_new_pix; + u64 phaseH; + u32 array_idx = 0; + + loop_width = max_t(u32, width_in, width_out); + loop_width = ALIGN(loop_width + nppc - 1, nppc); + + memset(xvpss_g->H_phases, 0, sizeof(xvpss_g->H_phases)); + for (x = 0; x < loop_width; x++) { + nr_rds_clck = 0; + for (s = 0; s < nppc; s++) { + phaseH = (offset >> shift) & (nphases - 1); + get_new_pix = false; + output_write_en = false; + if ((offset >> XVPSS_STEP_PRECISION_SHIFT) != 0) { + get_new_pix = true; + offset -= (1 << XVPSS_STEP_PRECISION_SHIFT); + array_idx++; + } + + if (((offset >> XVPSS_STEP_PRECISION_SHIFT) == 0) && + xwrite_pos < width_out) { + offset += pixel_rate; + output_write_en = true; + xwrite_pos++; + } + + xvpss_g->H_phases[x] |= (phaseH << + (s * XVPSS_PHASE_MULTIPLIER)); + xvpss_g->H_phases[x] |= (array_idx << + (XVPSS_PHASE_SHIFT_BY_6 + + (s * XVPSS_PHASE_MULTIPLIER))); + if (output_write_en) { + xvpss_g->H_phases[x] |= (XVPSS_PHASESH_WR_EN << + (s * XVPSS_PHASE_MULTIPLIER)); + } + + if (get_new_pix) + nr_rds_clck++; + } + if (array_idx >= nppc) + array_idx &= (nppc - 1); + + nr_rds += nr_rds_clck; + if (nr_rds >= nppc) + nr_rds -= nppc; + } +} + +static void xvpss_h_set_phases(struct xvpss_struct *xvpss_g) +{ + u32 loop_width, index, val, offset, i, lsb, msb; + + loop_width = XVPSS_MAX_WIDTH / xvpss_g->ppc; + offset = XHSCALER_OFFSET + XVPSS_H_PHASESH_V_BASE; + + switch (xvpss_g->ppc) { + case XVPSS_PPC_1: + index = 0; + for (i = 0; i < loop_width; i += 2) { + lsb = xvpss_g->H_phases[i] & XVPSS_MASK_LOW_16BITS; + msb = xvpss_g->H_phases[i + 1] & XVPSS_MASK_LOW_16BITS; + val = (msb << 16 | lsb); + xvpss_iow(xvpss_g->regs, offset + + (index * 4), val); + ++index; + } + return; + case XVPSS_PPC_2: + for (i = 0; i < loop_width; i++) { + val = (xvpss_g->H_phases[i] & XVPSS_MASK_LOW_32BITS); + xvpss_iow(xvpss_g->regs, offset + (i * 4), val); + } + return; + } +} + +static void xvpss_algo_config(struct xvpss_struct *xvpss_g, + struct xvpss_data data) +{ + u32 pxl_rate, line_rate; + u32 width_in = data.width_in; + u32 width_out = data.width_out; + u32 height_in = data.height_in; + u32 height_out = data.height_out; + + line_rate = (height_in * XVPSS_STEPPREC) / height_out; + + if (xvpss_g->is_polyphase) { + xvpss_select_coeff(xvpss_g); + xvpss_set_coeff(xvpss_g); + } + xvpss_iow(xvpss_g->regs, XVSCALER_OFFSET + XVPSS_V_LINERATE, line_rate); + pxl_rate = (width_in * XVPSS_STEPPREC) / width_out; + xvpss_iow(xvpss_g->regs, XHSCALER_OFFSET + XVPSS_H_PIXELRATE, pxl_rate); + + xvpss_h_calculate_phases(xvpss_g, width_in, width_out, pxl_rate); + xvpss_h_set_phases(xvpss_g); +} + +static long xvpss_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + long retval = 0; + struct xvpss_data data; + struct xvpss_struct *xvpss_g = to_xvpss_struct(file); + u32 hcol; + + switch (cmd) { + case XVPSS_SET_CONFIGURE: + if (copy_from_user(&data, (char __user *)arg, sizeof(data))) { + pr_err("Copy from user failed\n"); + retval = -EINVAL; + goto end; + } + xvpss_reset(xvpss_g); + xvpss_set_input(xvpss_g, data.width_in, data.height_in, + data.color_in); + hcol = data.color_in; + if (hcol == XVPSS_YUV_420) + hcol = XVPSS_YUV_422; + xvpss_iow(xvpss_g->regs, XHSCALER_OFFSET + XVPSS_H_COLOR, hcol); + xvpss_set_output(xvpss_g, data.width_out, data.height_out, + data.color_out); + xvpss_algo_config(xvpss_g, data); + break; + case XVPSS_SET_ENABLE: + xvpss_enable(xvpss_g); + break; + case XVPSS_SET_DISABLE: + xvpss_disable(xvpss_g); + break; + default: + retval = -EINVAL; + } +end: + return retval; +} + +static const struct file_operations xvpss_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = xvpss_ioctl, +}; + +static int xvpss_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + struct xvpss_struct *xvpss_g; + struct device_node *node; + + xvpss_g = devm_kzalloc(&pdev->dev, sizeof(*xvpss_g), GFP_KERNEL); + if (!xvpss_g) + return -ENOMEM; + + xvpss_g->reset_gpio = devm_gpiod_get(&pdev->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(xvpss_g->reset_gpio)) { + ret = PTR_ERR(xvpss_g->reset_gpio); + if (ret == -EPROBE_DEFER) + dev_dbg(&pdev->dev, "No gpio probed, Deferring...\n"); + else + dev_err(&pdev->dev, "No reset gpio info from dts\n"); + return ret; + } + gpiod_set_value_cansleep(xvpss_g->reset_gpio, 0); + + platform_set_drvdata(pdev, &xvpss_g); + xvpss_g->dev = &pdev->dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + xvpss_g->regs = devm_ioremap_resource(xvpss_g->dev, res); + if (IS_ERR(xvpss_g->regs)) + return PTR_ERR(xvpss_g->regs); + + node = pdev->dev.of_node; + ret = of_property_read_u32(node, "xlnx,vpss-taps", &xvpss_g->n_taps); + if (ret < 0) { + dev_err(xvpss_g->dev, "taps not present in DT\n"); + return ret; + } + + switch (xvpss_g->n_taps) { + case 2: + case 4: + break; + case 6: + xvpss_g->is_polyphase = true; + break; + default: + dev_err(xvpss_g->dev, "taps value not supported\n"); + return -EINVAL; + } + + ret = of_property_read_u32(node, "xlnx,vpss-ppc", &xvpss_g->ppc); + if (ret < 0) { + dev_err(xvpss_g->dev, "PPC is missing in DT\n"); + return ret; + } + if (xvpss_g->ppc != XVPSS_PPC_1 && xvpss_g->ppc != XVPSS_PPC_2) { + dev_err(xvpss_g->dev, "Unsupported ppc: %d", xvpss_g->ppc); + return -EINVAL; + } + + xvpss_g->xvpss_miscdev.minor = MISC_DYNAMIC_MINOR; + xvpss_g->xvpss_miscdev.name = "xvpss"; + xvpss_g->xvpss_miscdev.fops = &xvpss_fops; + ret = misc_register(&xvpss_g->xvpss_miscdev); + if (ret < 0) { + pr_err("Xilinx VPSS registration failed!\n"); + return ret; + } + + dev_info(xvpss_g->dev, "Xlnx VPSS control driver initialized!\n"); + + return ret; +} + +static int xvpss_remove(struct platform_device *pdev) +{ + struct xvpss_struct *xvpss_g = platform_get_drvdata(pdev); + + misc_deregister(&xvpss_g->xvpss_miscdev); + return 0; +} + +static struct platform_driver xvpss_driver = { + .probe = xvpss_probe, + .remove = xvpss_remove, + .driver = { + .name = "xlnx_vpss", + .of_match_table = xvpss_of_match, + }, +}; + +module_platform_driver(xvpss_driver); + +MODULE_DESCRIPTION("Xilinx VPSS control driver"); +MODULE_AUTHOR("Saurabh Sengar"); +MODULE_LICENSE("GPL v2"); |