blob: 29a1999edfe5bb60c8e547bbc3f5874a208562e3 [file] [log] [blame]
/*
* Copyright 2017 NXP
*/
/*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
#include "mxc-media-dev.h"
static irqreturn_t mxc_isi_irq_handler(int irq, void *priv)
{
struct mxc_isi_dev *mxc_isi = priv;
struct device *dev = &mxc_isi->pdev->dev;
u32 status;
spin_lock(&mxc_isi->slock);
status = mxc_isi_get_irq_status(mxc_isi);
mxc_isi_clean_irq_status(mxc_isi, status);
if (status & CHNL_STS_FRM_STRD_MASK)
mxc_isi_frame_write_done(mxc_isi);
if (status & (CHNL_STS_AXI_WR_ERR_Y_MASK |
CHNL_STS_AXI_WR_ERR_U_MASK |
CHNL_STS_AXI_WR_ERR_V_MASK))
dev_dbg(dev, "%s, IRQ AXI Error stat=0x%X\n", __func__, status);
if (status & (CHNL_STS_OFLW_PANIC_Y_BUF_MASK |
CHNL_STS_OFLW_PANIC_U_BUF_MASK |
CHNL_STS_OFLW_PANIC_V_BUF_MASK))
dev_dbg(dev, "%s, IRQ Panic OFLW Error stat=0x%X\n", __func__, status);
if (status & (CHNL_STS_OFLW_Y_BUF_MASK |
CHNL_STS_OFLW_U_BUF_MASK |
CHNL_STS_OFLW_V_BUF_MASK))
dev_dbg(dev, "%s, IRQ OFLW Error stat=0x%X\n", __func__, status);
if (status & (CHNL_STS_EXCS_OFLW_Y_BUF_MASK |
CHNL_STS_EXCS_OFLW_U_BUF_MASK |
CHNL_STS_EXCS_OFLW_V_BUF_MASK))
dev_dbg(dev, "%s, IRQ EXCS OFLW Error stat=0x%X\n", __func__, status);
spin_unlock(&mxc_isi->slock);
return IRQ_HANDLED;
}
/**
* mxc_isi_adjust_mplane_format - adjust bytesperline or sizeimage
*/
void mxc_isi_adjust_mplane_format(struct mxc_isi_fmt *fmt, u32 width, u32 height,
struct v4l2_pix_format_mplane *pix)
{
u32 bytesperline = 0;
int i;
pix->colorspace = V4L2_COLORSPACE_JPEG;
pix->field = V4L2_FIELD_NONE;
pix->num_planes = fmt->memplanes;
pix->pixelformat = fmt->fourcc;
pix->height = height;
pix->width = width;
for (i = 0; i < pix->num_planes; ++i) {
struct v4l2_plane_pix_format *plane_fmt = &pix->plane_fmt[i];
u32 bpl = plane_fmt->bytesperline;
if (fmt->colplanes > 1 && (bpl == 0 || bpl < pix->width))
bpl = pix->width; /* Planar */
if (fmt->colplanes == 1 && /* Packed */
(bpl == 0 || ((bpl * 8) / fmt->depth[i]) < pix->width))
bpl = (pix->width * fmt->depth[0]) / 8;
if (i == 0)
bytesperline = bpl;
else if (i == 1 && fmt->memplanes == 3)
bytesperline /= 2;
plane_fmt->bytesperline = bytesperline;
plane_fmt->sizeimage = max((pix->width * pix->height *
fmt->depth[i]) / 8, plane_fmt->sizeimage);
}
}
static int mxc_isi_parse_dt(struct mxc_isi_dev *mxc_isi)
{
struct device *dev = &mxc_isi->pdev->dev;
struct device_node *node = dev->of_node;
int ret = 0;
mxc_isi->id = of_alias_get_id(node, "isi");
ret = of_property_read_u32_array(node, "interface",
mxc_isi->interface, 3);
if (ret < 0)
return ret;
dev_dbg(dev, "%s, isi_%d,interface(%d, %d, %d)\n", __func__, mxc_isi->id,
mxc_isi->interface[0], mxc_isi->interface[1], mxc_isi->interface[2]);
return 0;
}
static int mxc_isi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mxc_isi_dev *mxc_isi;
struct resource *res;
int ret = 0;
mxc_isi = devm_kzalloc(dev, sizeof(*mxc_isi), GFP_KERNEL);
if (!mxc_isi)
return -ENOMEM;
mxc_isi->pdev = pdev;
ret = mxc_isi_parse_dt(mxc_isi);
if (ret < 0)
return ret;
if (mxc_isi->id >= MXC_ISI_MAX_DEVS || mxc_isi->id < 0) {
dev_err(dev, "Invalid driver data or device id (%d)\n",
mxc_isi->id);
return -EINVAL;
}
init_waitqueue_head(&mxc_isi->irq_queue);
spin_lock_init(&mxc_isi->slock);
mutex_init(&mxc_isi->lock);
mxc_isi->clk = devm_clk_get(dev, NULL);
if (IS_ERR(mxc_isi->clk)) {
dev_err(dev, "failed to get isi clk\n");
return PTR_ERR(mxc_isi->clk);
}
ret = clk_prepare(mxc_isi->clk);
if (ret < 0) {
dev_err(dev, "%s, prepare clk error\n", __func__);
return -EINVAL;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mxc_isi->regs = devm_ioremap_resource(dev, res);
if (IS_ERR(mxc_isi->regs)) {
dev_err(dev, "Failed to get ISI register map\n");
return PTR_ERR(mxc_isi->regs);
}
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res == NULL) {
dev_err(dev, "Failed to get IRQ resource\n");
return -ENXIO;
}
ret = devm_request_irq(dev, res->start, mxc_isi_irq_handler,
0, dev_name(dev), mxc_isi);
if (ret < 0) {
dev_err(dev, "failed to install irq (%d)\n", ret);
return -EINVAL;
}
ret = mxc_isi_initialize_capture_subdev(mxc_isi);
if (ret < 0) {
dev_err(dev, "failed to init cap subdev (%d)\n", ret);
return -EINVAL;
}
platform_set_drvdata(pdev, mxc_isi);
ret = clk_enable(mxc_isi->clk);
if (ret < 0) {
dev_err(dev, "%s, enable clk error\n", __func__);
goto err_sclk;
}
mxc_isi->flags = MXC_ISI_PM_POWERED;
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
pm_runtime_put_sync(dev);
dev_dbg(dev, "mxc_isi.%d registered successfully\n", mxc_isi->id);
return 0;
err_sclk:
mxc_isi_unregister_capture_subdev(mxc_isi);
return ret;
}
static int mxc_isi_remove(struct platform_device *pdev)
{
struct mxc_isi_dev *mxc_isi = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
mxc_isi_unregister_capture_subdev(mxc_isi);
clk_disable_unprepare(mxc_isi->clk);
pm_runtime_disable(dev);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int mxc_isi_pm_suspend(struct device *dev)
{
struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev);
if ((mxc_isi->flags & MXC_ISI_PM_SUSPENDED) ||
(mxc_isi->flags & MXC_ISI_RUNTIME_SUSPEND))
return 0;
clk_disable_unprepare(mxc_isi->clk);
mxc_isi->flags |= MXC_ISI_PM_SUSPENDED;
mxc_isi->flags &= ~MXC_ISI_PM_POWERED;
return 0;
}
static int mxc_isi_pm_resume(struct device *dev)
{
struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev);
int ret;
if (mxc_isi->flags & MXC_ISI_PM_POWERED)
return 0;
mxc_isi->flags |= MXC_ISI_PM_POWERED;
mxc_isi->flags &= ~MXC_ISI_PM_SUSPENDED;
ret = clk_prepare_enable(mxc_isi->clk);
return (ret) ? -EAGAIN : 0;
}
#endif
static int mxc_isi_runtime_suspend(struct device *dev)
{
struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev);
if (mxc_isi->flags & MXC_ISI_RUNTIME_SUSPEND)
return 0;
if (mxc_isi->flags & MXC_ISI_PM_POWERED) {
clk_disable_unprepare(mxc_isi->clk);
mxc_isi->flags |= MXC_ISI_RUNTIME_SUSPEND;
mxc_isi->flags &= ~MXC_ISI_PM_POWERED;
}
return 0;
}
static int mxc_isi_runtime_resume(struct device *dev)
{
struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev);
if (mxc_isi->flags & MXC_ISI_PM_POWERED)
return 0;
if (mxc_isi->flags & MXC_ISI_RUNTIME_SUSPEND) {
clk_prepare_enable(mxc_isi->clk);
mxc_isi->flags |= MXC_ISI_PM_POWERED;
mxc_isi->flags &= ~MXC_ISI_RUNTIME_SUSPEND;
}
return 0;
}
static const struct dev_pm_ops mxc_isi_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(mxc_isi_pm_suspend, mxc_isi_pm_resume)
SET_RUNTIME_PM_OPS(mxc_isi_runtime_suspend, mxc_isi_runtime_resume, NULL)
};
static const struct of_device_id mxc_isi_of_match[] = {
{.compatible = "fsl,imx8-isi",},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, mxc_isi_of_match);
static struct platform_driver mxc_isi_driver = {
.probe = mxc_isi_probe,
.remove = mxc_isi_remove,
.driver = {
.of_match_table = mxc_isi_of_match,
.name = MXC_ISI_DRIVER_NAME,
.pm = &mxc_isi_pm_ops,
}
};
module_platform_driver(mxc_isi_driver);