blob: cb15533e73bcf93c7e1b3cd756c9897827da299c [file] [log] [blame]
/*
* Copyright (C) 2017-2018 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;
u32 pll_base;
};
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,
.pll_base = 0x30360000,
};
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;
bool optional;
} clks[] = {
{"apb", &dcss->apb_clk, false},
{"axi", &dcss->axi_clk, false},
{"pix", &dcss->pix_clk, false},
{"rtrm", &dcss->rtrm_clk, false},
{"dtrc", &dcss->dtrc_clk, false},
{"pll", &dcss->pll_clk, true},
{"pll_src1", &dcss->src_clk[0], true},
{"pll_src2", &dcss->src_clk[1], true},
{"pll_src3", &dcss->src_clk[2], true},
};
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) && !clks[i].optional) {
dev_err(dcss->dev, "failed to get %s clock\n",
clks[i].id);
ret = PTR_ERR(*clks[i].clk);
goto err;
}
if (!clks[i].optional)
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->pix_clk);
}
if (!en && dcss->clks_on) {
clk_disable_unprepare(dcss->pix_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>
#include <linux/sched/clock.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_ATOMIC);
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;
}
#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");