diff options
Diffstat (limited to 'drivers/gpu/imx/dcss/dcss-common.c')
-rw-r--r-- | drivers/gpu/imx/dcss/dcss-common.c | 739 |
1 files changed, 739 insertions, 0 deletions
diff --git a/drivers/gpu/imx/dcss/dcss-common.c b/drivers/gpu/imx/dcss/dcss-common.c new file mode 100644 index 000000000000..528ef258f513 --- /dev/null +++ b/drivers/gpu/imx/dcss/dcss-common.c @@ -0,0 +1,739 @@ +/* + * Copyright (C) 2017 NXP + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <linux/busfreq-imx.h> +#include <linux/pm_qos.h> +#include <video/imx-dcss.h> + +#include <drm/drm_fourcc.h> + +#include <video/imx-dcss.h> +#include "dcss-prv.h" + +struct dcss_devtype { + const char *name; + u32 blkctl_ofs; + u32 ctxld_ofs; + u32 rdsrc_ofs; + u32 wrscl_ofs; + u32 dtg_ofs; + u32 scaler_ofs; + u32 ss_ofs; + u32 dpr_ofs; + u32 dtrc_ofs; + u32 dec400d_ofs; + u32 hdr10_ofs; +}; + +static struct dcss_devtype dcss_type_imx8m = { + .name = "DCSS_imx8m", + .blkctl_ofs = 0x2F000, + .ctxld_ofs = 0x23000, + .rdsrc_ofs = 0x22000, + .wrscl_ofs = 0x21000, + .dtg_ofs = 0x20000, + .scaler_ofs = 0x1C000, + .ss_ofs = 0x1B000, + .dpr_ofs = 0x18000, + .dtrc_ofs = 0x16000, + .dec400d_ofs = 0x15000, + .hdr10_ofs = 0x00000, +}; + +enum dcss_color_space dcss_drm_fourcc_to_colorspace(u32 drm_fourcc) +{ + switch (drm_fourcc) { + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_BGRA5551: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_BGRX1010102: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: + return DCSS_COLORSPACE_RGB; + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + case DRM_FORMAT_P010: + return DCSS_COLORSPACE_YUV; + default: + return DCSS_COLORSPACE_UNKNOWN; + } +} +EXPORT_SYMBOL_GPL(dcss_drm_fourcc_to_colorspace); + +int dcss_vblank_irq_get(struct dcss_soc *dcss) +{ + struct platform_device *pdev = to_platform_device(dcss->dev); + + return platform_get_irq_byname(pdev, "dtg_prg1"); +} +EXPORT_SYMBOL(dcss_vblank_irq_get); + +void dcss_vblank_irq_enable(struct dcss_soc *dcss, bool en) +{ + dcss_dtg_vblank_irq_enable(dcss, en); +} +EXPORT_SYMBOL(dcss_vblank_irq_enable); + +void dcss_vblank_irq_clear(struct dcss_soc *dcss) +{ + dcss_dtg_vblank_irq_clear(dcss); +} +EXPORT_SYMBOL(dcss_vblank_irq_clear); + +static int dcss_submodules_init(struct dcss_soc *dcss) +{ + int ret; + u32 dcss_base = dcss->start_addr; + + ret = dcss_blkctl_init(dcss, dcss_base + dcss->devtype->blkctl_ofs); + if (ret) + goto blkctl_err; + + ret = dcss_ctxld_init(dcss, dcss_base + dcss->devtype->ctxld_ofs); + if (ret) + goto ctxld_err; + + ret = dcss_dtrc_init(dcss, dcss_base + dcss->devtype->dtrc_ofs); + if (ret) + goto dtrc_err; + + ret = dcss_dec400d_init(dcss, dcss_base + dcss->devtype->dec400d_ofs); + if (ret) + goto dec400d_err; + + ret = dcss_dtg_init(dcss, dcss_base + dcss->devtype->dtg_ofs); + if (ret) + goto dtg_err; + + ret = dcss_ss_init(dcss, dcss_base + dcss->devtype->ss_ofs); + if (ret) + goto ss_err; + + ret = dcss_dpr_init(dcss, dcss_base + dcss->devtype->dpr_ofs); + if (ret) + goto dpr_err; + + ret = dcss_scaler_init(dcss, dcss_base + dcss->devtype->scaler_ofs); + if (ret) + goto scaler_err; + + ret = dcss_hdr10_init(dcss, dcss_base + dcss->devtype->hdr10_ofs); + if (ret) + goto hdr10_err; + + ret = dcss_wrscl_init(dcss, dcss_base + dcss->devtype->wrscl_ofs); + if (ret) + goto wrscl_err; + + ret = dcss_rdsrc_init(dcss, dcss_base + dcss->devtype->rdsrc_ofs); + if (ret) + goto rdsrc_err; + + return 0; + +rdsrc_err: + dcss_rdsrc_exit(dcss); + +wrscl_err: + dcss_wrscl_exit(dcss); + +hdr10_err: + dcss_hdr10_exit(dcss); + +scaler_err: + dcss_scaler_exit(dcss); + +dpr_err: + dcss_dpr_exit(dcss); + +ss_err: + dcss_ss_exit(dcss); + +dtg_err: + dcss_dtg_exit(dcss); + +dec400d_err: + dcss_dec400d_exit(dcss); + +dtrc_err: + dcss_dtrc_exit(dcss); + +ctxld_err: + dcss_ctxld_exit(dcss); + +blkctl_err: + dcss_blkctl_exit(dcss); + + return ret; +} + +struct dcss_platform_reg { + struct dcss_client_platformdata pdata; + const char *name; +}; + +static struct dcss_platform_reg client_reg = { + .pdata = { }, + .name = "imx-dcss-crtc", +}; + +static int dcss_add_client_devices(struct dcss_soc *dcss) +{ + struct device *dev = dcss->dev; + struct platform_device *pdev; + struct device_node *of_node; + int ret; + + of_node = of_graph_get_port_by_id(dev->of_node, 0); + if (!of_node) { + dev_err(dev, "no port@0 node in %s\n", dev->of_node->full_name); + return -ENODEV; + } + + pdev = platform_device_alloc(client_reg.name, 0); + if (!pdev) { + dev_err(dev, "cannot allocate platform device\n"); + return -ENOMEM; + } + + pdev->dev.parent = dev; + + client_reg.pdata.of_node = of_node; + ret = platform_device_add_data(pdev, &client_reg.pdata, + sizeof(client_reg.pdata)); + if (!ret) + ret = platform_device_add(pdev); + if (ret) { + platform_device_put(pdev); + goto err_register; + } + + pdev->dev.of_node = of_node; + + return 0; + +err_register: + platform_device_unregister(pdev); + return ret; +} + +static int dcss_clks_init(struct dcss_soc *dcss) +{ + int ret, i, j; + struct { + const char *id; + struct clk **clk; + } clks[] = { + {"apb", &dcss->apb_clk}, + {"axi", &dcss->axi_clk}, + {"pix_div", &dcss->pdiv_clk}, + {"pix_out", &dcss->pout_clk}, + {"rtrm", &dcss->apb_clk}, + {"dtrc", &dcss->dtrc_clk}, + }; + + for (i = 0; i < ARRAY_SIZE(clks); i++) { + *clks[i].clk = devm_clk_get(dcss->dev, clks[i].id); + if (IS_ERR(*clks[i].clk)) { + dev_err(dcss->dev, "failed to get %s clock\n", + clks[i].id); + ret = PTR_ERR(*clks[i].clk); + goto err; + } + + clk_prepare_enable(*clks[i].clk); + } + + dcss->clks_on = true; + + return 0; + +err: + for (j = 0; j < i; j++) + clk_disable_unprepare(*clks[j].clk); + + return ret; +} + +static void dcss_clocks_enable(struct dcss_soc *dcss, bool en) +{ + if (en && !dcss->clks_on) { + clk_prepare_enable(dcss->axi_clk); + clk_prepare_enable(dcss->apb_clk); + clk_prepare_enable(dcss->rtrm_clk); + clk_prepare_enable(dcss->dtrc_clk); + clk_prepare_enable(dcss->pdiv_clk); + clk_prepare_enable(dcss->pout_clk); + } + + if (!en && dcss->clks_on) { + clk_disable_unprepare(dcss->pout_clk); + clk_disable_unprepare(dcss->pdiv_clk); + clk_disable_unprepare(dcss->dtrc_clk); + clk_disable_unprepare(dcss->rtrm_clk); + clk_disable_unprepare(dcss->apb_clk); + clk_disable_unprepare(dcss->axi_clk); + } + + dcss->clks_on = en; +} + +#ifdef CONFIG_DEBUG_FS +#include <linux/debugfs.h> +#include <linux/slab.h> + +static unsigned int dcss_tracing; +EXPORT_SYMBOL(dcss_tracing); + +module_param_named(tracing, dcss_tracing, int, 0600); + +struct dcss_trace { + u64 seq; + u64 time_ns; + u64 tag; + struct list_head node; +}; + +static LIST_HEAD(dcss_trace_list); +static spinlock_t lock; +static u64 seq; + +void dcss_trace_write(u64 tag) +{ + struct dcss_trace *trace; + unsigned long flags; + + if (!dcss_tracing) + return; + + trace = kzalloc(sizeof(*trace), GFP_KERNEL); + if (!trace) + return; + + trace->time_ns = local_clock(); + trace->tag = tag; + trace->seq = seq; + + spin_lock_irqsave(&lock, flags); + list_add_tail(&trace->node, &dcss_trace_list); + seq++; + spin_unlock_irqrestore(&lock, flags); +} +EXPORT_SYMBOL(dcss_trace_write); + +static int dcss_trace_dump_show(struct seq_file *s, void *data) +{ + struct dcss_trace *trace = data; + + if (trace) + seq_printf(s, "%lld %lld %lld\n", + trace->seq, trace->time_ns, trace->tag); + + return 0; +} + +static void *dcss_trace_dump_start(struct seq_file *s, loff_t *pos) +{ + unsigned long flags; + struct dcss_trace *trace = NULL; + + spin_lock_irqsave(&lock, flags); + if (!list_empty(&dcss_trace_list)) { + trace = list_first_entry(&dcss_trace_list, + struct dcss_trace, node); + goto exit; + } + +exit: + spin_unlock_irqrestore(&lock, flags); + return trace; +} + +static void *dcss_trace_dump_next(struct seq_file *s, void *v, loff_t *pos) +{ + unsigned long flags; + struct dcss_trace *next_trace = NULL; + struct dcss_trace *trace = v; + + ++*pos; + spin_lock_irqsave(&lock, flags); + if (!list_is_last(&trace->node, &dcss_trace_list)) { + next_trace = list_entry(trace->node.next, + struct dcss_trace, node); + goto exit; + } + +exit: + spin_unlock_irqrestore(&lock, flags); + return next_trace; +} + +static void dcss_trace_dump_stop(struct seq_file *s, void *v) +{ + unsigned long flags; + struct dcss_trace *trace, *tmp; + struct dcss_trace *last_trace = v; + + spin_lock_irqsave(&lock, flags); + if (!list_empty(&dcss_trace_list)) { + list_for_each_entry_safe(trace, tmp, &dcss_trace_list, node) { + if (last_trace && trace->seq >= last_trace->seq) + break; + + list_del(&trace->node); + kfree(trace); + } + } + spin_unlock_irqrestore(&lock, flags); +} + +static const struct seq_operations dcss_trace_seq_ops = { + .start = dcss_trace_dump_start, + .next = dcss_trace_dump_next, + .stop = dcss_trace_dump_stop, + .show = dcss_trace_dump_show, +}; + +static int dcss_trace_dump_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &dcss_trace_seq_ops); +} + +static int dcss_dump_regs_show(struct seq_file *s, void *data) +{ + struct dcss_soc *dcss = s->private; + + pm_runtime_get_sync(dcss->dev); + + dcss_blkctl_dump_regs(s, s->private); + dcss_dtrc_dump_regs(s, s->private); + dcss_dpr_dump_regs(s, s->private); + dcss_scaler_dump_regs(s, s->private); + dcss_wrscl_dump_regs(s, s->private); + dcss_rdsrc_dump_regs(s, s->private); + dcss_dtg_dump_regs(s, s->private); + dcss_ss_dump_regs(s, s->private); + dcss_hdr10_dump_regs(s, s->private); + dcss_ctxld_dump_regs(s, s->private); + + pm_runtime_put_sync(dcss->dev); + + return 0; +} + +static int dcss_dump_ctx_show(struct seq_file *s, void *data) +{ + dcss_ctxld_dump(s, s->private); + + return 0; +} + +static int dcss_dump_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, dcss_dump_regs_show, inode->i_private); +} + +static int dcss_dump_ctx_open(struct inode *inode, struct file *file) +{ + return single_open(file, dcss_dump_ctx_show, inode->i_private); +} + +static const struct file_operations dcss_dump_regs_fops = { + .open = dcss_dump_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations dcss_dump_ctx_fops = { + .open = dcss_dump_ctx_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations dcss_dump_trace_fops = { + .open = dcss_trace_dump_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static void dcss_debugfs_init(struct dcss_soc *dcss) +{ + struct dentry *d, *root; + + root = debugfs_create_dir("imx-dcss", NULL); + if (IS_ERR(root) || !root) + goto err; + + d = debugfs_create_file("dump_registers", 0444, root, dcss, + &dcss_dump_regs_fops); + if (!d) + goto err; + + d = debugfs_create_file("dump_context", 0444, root, dcss, + &dcss_dump_ctx_fops); + if (!d) + goto err; + + d = debugfs_create_file("dump_trace_log", 0444, root, dcss, + &dcss_dump_trace_fops); + if (!d) + goto err; + + return; + +err: + dev_err(dcss->dev, "Unable to create debugfs entries\n"); +} +#else +static void dcss_debugfs_init(struct dcss_soc *dcss) +{ +} + +void dcss_trace_write(u64 tag) +{ +} +EXPORT_SYMBOL(dcss_trace_write); +#endif + +static void dcss_bus_freq(struct dcss_soc *dcss, bool en) +{ + if (en && !dcss->bus_freq_req) + request_bus_freq(BUS_FREQ_HIGH); + + if (!en && dcss->bus_freq_req) + release_bus_freq(BUS_FREQ_HIGH); + + dcss->bus_freq_req = en; +} + +static int dcss_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + struct dcss_soc *dcss; + const struct dcss_devtype *devtype; + + devtype = of_device_get_match_data(&pdev->dev); + if (!devtype) { + dev_err(&pdev->dev, "no device match found\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "cannot get memory resource\n"); + return -EINVAL; + } + + dcss = devm_kzalloc(&pdev->dev, sizeof(struct dcss_soc), GFP_KERNEL); + if (!dcss) + return -ENOMEM; + + dcss->dev = &pdev->dev; + dcss->devtype = devtype; + + platform_set_drvdata(pdev, dcss); + + ret = dcss_clks_init(dcss); + if (ret) { + dev_err(&pdev->dev, "clocks initialization failed\n"); + return ret; + } + + dcss->start_addr = res->start; + + ret = dcss_submodules_init(dcss); + if (ret) { + dev_err(&pdev->dev, "submodules initialization failed\n"); + return ret; + } + + dcss_debugfs_init(dcss); + + pm_runtime_enable(&pdev->dev); + + dcss_bus_freq(dcss, true); + + return dcss_add_client_devices(dcss); +} + +static int dcss_remove(struct platform_device *pdev) +{ + struct dcss_soc *dcss = platform_get_drvdata(pdev); + + dcss_bus_freq(dcss, false); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +void dcss_req_pm_qos(struct dcss_soc *dcss, bool en) +{ + if (en && !dcss->pm_req_active) { + pm_qos_add_request(&dcss->pm_qos_req, + PM_QOS_CPU_DMA_LATENCY, 0); + dcss->pm_req_active = true; + return; + } + + if (dcss_dtrc_is_running(dcss, 1) || dcss_dtrc_is_running(dcss, 2)) + return; + + pm_qos_remove_request(&dcss->pm_qos_req); + dcss->pm_req_active = false; +} +EXPORT_SYMBOL(dcss_req_pm_qos); + +#ifdef CONFIG_PM_SLEEP +static int dcss_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dcss_soc *dcss = platform_get_drvdata(pdev); + int ret; + + if (pm_runtime_suspended(dev)) + return 0; + + ret = dcss_ctxld_suspend(dcss); + if (ret) + return ret; + + dcss_clocks_enable(dcss, false); + + dcss_bus_freq(dcss, false); + + return 0; +} + +static int dcss_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dcss_soc *dcss = platform_get_drvdata(pdev); + + if (pm_runtime_suspended(dev)) + return 0; + + dcss_bus_freq(dcss, true); + + dcss_clocks_enable(dcss, true); + + dcss_blkctl_cfg(dcss); + + dcss_ctxld_resume(dcss); + + return 0; +} +#endif + +#ifdef CONFIG_PM +static int dcss_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dcss_soc *dcss = platform_get_drvdata(pdev); + int ret; + + ret = dcss_ctxld_suspend(dcss); + if (ret) + return ret; + + dcss_clocks_enable(dcss, false); + + dcss_bus_freq(dcss, false); + + return 0; +} + +static int dcss_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dcss_soc *dcss = platform_get_drvdata(pdev); + + dcss_bus_freq(dcss, true); + + dcss_clocks_enable(dcss, true); + + dcss_blkctl_cfg(dcss); + + dcss_ctxld_resume(dcss); + + return 0; +} +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops dcss_pm = { + SET_SYSTEM_SLEEP_PM_OPS(dcss_suspend, dcss_resume) + SET_RUNTIME_PM_OPS(dcss_runtime_suspend, + dcss_runtime_resume, NULL) +}; + +static const struct of_device_id dcss_dt_ids[] = { + { .compatible = "nxp,imx8mq-dcss", .data = &dcss_type_imx8m, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dcss_dt_ids); + +static struct platform_driver dcss_driver = { + .driver = { + .name = "dcss-core", + .of_match_table = dcss_dt_ids, + .pm = &dcss_pm, + }, + .probe = dcss_probe, + .remove = dcss_remove, +}; + +module_platform_driver(dcss_driver); + +MODULE_DESCRIPTION("i.MX DCSS driver"); +MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@nxp.com>"); +MODULE_LICENSE("GPL"); |