linux/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c
Keke Li fb2e135208 media: platform: Add C3 ISP driver
The C3 ISP supports multi-camera and multi-exposure HDR, integrates
advanced imaging technologies for optimal quality, and drives the
core pipeline to transform raw sensor data into high-fidelity images
through demosaicing, color correction, and tone mapping operations.

Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Signed-off-by: Keke Li <keke.li@amlogic.com>
Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
[hverkuil: drop unnecessary vb2_ops_wait_prepare/finish callbacks]
2025-05-09 12:08:37 +02:00

421 lines
9.6 KiB
C

// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
/*
* Copyright (C) 2024 Amlogic, Inc. All rights reserved
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <media/v4l2-common.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-mc.h>
#include "c3-isp-common.h"
#include "c3-isp-regs.h"
u32 c3_isp_read(struct c3_isp_device *isp, u32 reg)
{
return readl(isp->base + reg);
}
void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val)
{
writel(val, isp->base + reg);
}
void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val)
{
u32 orig, tmp;
orig = c3_isp_read(isp, reg);
tmp = orig & ~mask;
tmp |= val & mask;
if (tmp != orig)
c3_isp_write(isp, reg, tmp);
}
/* PM runtime suspend */
static int c3_isp_runtime_suspend(struct device *dev)
{
struct c3_isp_device *isp = dev_get_drvdata(dev);
clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks);
return 0;
}
/* PM runtime resume */
static int c3_isp_runtime_resume(struct device *dev)
{
struct c3_isp_device *isp = dev_get_drvdata(dev);
return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks);
}
static const struct dev_pm_ops c3_isp_pm_ops = {
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
RUNTIME_PM_OPS(c3_isp_runtime_suspend,
c3_isp_runtime_resume, NULL)
};
/* IRQ handling */
static irqreturn_t c3_isp_irq_handler(int irq, void *dev)
{
struct c3_isp_device *isp = dev;
u32 status;
/* Get irq status and clear irq status */
status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT);
c3_isp_write(isp, ISP_TOP_IRQ_CLR, status);
if (status & ISP_TOP_RO_IRQ_STAT_FRM_END_MASK) {
c3_isp_stats_isr(isp);
c3_isp_params_isr(isp);
c3_isp_captures_isr(isp);
isp->frm_sequence++;
}
if (status & ISP_TOP_RO_IRQ_STAT_FRM_RST_MASK)
c3_isp_core_queue_sof(isp);
return IRQ_HANDLED;
}
/* Subdev notifier register */
static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd,
struct v4l2_async_connection *asc)
{
struct c3_isp_device *isp =
container_of(notifier, struct c3_isp_device, notifier);
struct media_pad *sink =
&isp->core.sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO];
return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
MEDIA_LNK_FL_IMMUTABLE);
}
static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier)
{
struct c3_isp_device *isp =
container_of(notifier, struct c3_isp_device, notifier);
return v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
}
static const struct v4l2_async_notifier_operations c3_isp_notify_ops = {
.bound = c3_isp_notify_bound,
.complete = c3_isp_notify_complete,
};
static int c3_isp_async_nf_register(struct c3_isp_device *isp)
{
struct v4l2_async_connection *asc;
struct fwnode_handle *ep;
int ret;
v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev);
ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0,
FWNODE_GRAPH_ENDPOINT_NEXT);
if (!ep)
return -ENOTCONN;
asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
struct v4l2_async_connection);
fwnode_handle_put(ep);
if (IS_ERR(asc))
return PTR_ERR(asc);
isp->notifier.ops = &c3_isp_notify_ops;
ret = v4l2_async_nf_register(&isp->notifier);
if (ret)
v4l2_async_nf_cleanup(&isp->notifier);
return ret;
}
static void c3_isp_async_nf_unregister(struct c3_isp_device *isp)
{
v4l2_async_nf_unregister(&isp->notifier);
v4l2_async_nf_cleanup(&isp->notifier);
}
static int c3_isp_media_register(struct c3_isp_device *isp)
{
struct media_device *media_dev = &isp->media_dev;
struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
int ret;
/* Initialize media device */
strscpy(media_dev->model, C3_ISP_DRIVER_NAME, sizeof(media_dev->model));
media_dev->dev = isp->dev;
media_device_init(media_dev);
/* Initialize v4l2 device */
v4l2_dev->mdev = media_dev;
strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME, sizeof(v4l2_dev->name));
ret = v4l2_device_register(isp->dev, v4l2_dev);
if (ret)
goto err_media_dev_cleanup;
ret = media_device_register(&isp->media_dev);
if (ret) {
dev_err(isp->dev, "Failed to register media device: %d\n", ret);
goto err_unreg_v4l2_dev;
}
return 0;
err_unreg_v4l2_dev:
v4l2_device_unregister(&isp->v4l2_dev);
err_media_dev_cleanup:
media_device_cleanup(media_dev);
return ret;
}
static void c3_isp_media_unregister(struct c3_isp_device *isp)
{
media_device_unregister(&isp->media_dev);
v4l2_device_unregister(&isp->v4l2_dev);
media_device_cleanup(&isp->media_dev);
}
static void c3_isp_remove_links(struct c3_isp_device *isp)
{
unsigned int i;
media_entity_remove_links(&isp->core.sd.entity);
for (i = 0; i < C3_ISP_NUM_RSZ; i++)
media_entity_remove_links(&isp->resizers[i].sd.entity);
for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++)
media_entity_remove_links(&isp->caps[i].vdev.entity);
}
static int c3_isp_create_links(struct c3_isp_device *isp)
{
unsigned int i;
int ret;
for (i = 0; i < C3_ISP_NUM_RSZ; i++) {
ret = media_create_pad_link(&isp->resizers[i].sd.entity,
C3_ISP_RSZ_PAD_SOURCE,
&isp->caps[i].vdev.entity, 0,
MEDIA_LNK_FL_ENABLED |
MEDIA_LNK_FL_IMMUTABLE);
if (ret) {
dev_err(isp->dev,
"Failed to link rsz %u and cap %u\n", i, i);
goto err_remove_links;
}
ret = media_create_pad_link(&isp->core.sd.entity,
C3_ISP_CORE_PAD_SOURCE_VIDEO_0 + i,
&isp->resizers[i].sd.entity,
C3_ISP_RSZ_PAD_SINK,
MEDIA_LNK_FL_ENABLED);
if (ret) {
dev_err(isp->dev,
"Failed to link core and rsz %u\n", i);
goto err_remove_links;
}
}
ret = media_create_pad_link(&isp->core.sd.entity,
C3_ISP_CORE_PAD_SOURCE_STATS,
&isp->stats.vdev.entity,
0, MEDIA_LNK_FL_ENABLED);
if (ret) {
dev_err(isp->dev, "Failed to link core and stats\n");
goto err_remove_links;
}
ret = media_create_pad_link(&isp->params.vdev.entity, 0,
&isp->core.sd.entity,
C3_ISP_CORE_PAD_SINK_PARAMS,
MEDIA_LNK_FL_ENABLED);
if (ret) {
dev_err(isp->dev, "Failed to link params and core\n");
goto err_remove_links;
}
return 0;
err_remove_links:
c3_isp_remove_links(isp);
return ret;
}
static int c3_isp_videos_register(struct c3_isp_device *isp)
{
int ret;
ret = c3_isp_captures_register(isp);
if (ret)
return ret;
ret = c3_isp_stats_register(isp);
if (ret)
goto err_captures_unregister;
ret = c3_isp_params_register(isp);
if (ret)
goto err_stats_unregister;
ret = c3_isp_create_links(isp);
if (ret)
goto err_params_unregister;
return 0;
err_params_unregister:
c3_isp_params_unregister(isp);
err_stats_unregister:
c3_isp_stats_unregister(isp);
err_captures_unregister:
c3_isp_captures_unregister(isp);
return ret;
}
static void c3_isp_videos_unregister(struct c3_isp_device *isp)
{
c3_isp_remove_links(isp);
c3_isp_params_unregister(isp);
c3_isp_stats_unregister(isp);
c3_isp_captures_unregister(isp);
}
static int c3_isp_get_clocks(struct c3_isp_device *isp)
{
const struct c3_isp_info *info = isp->info;
for (unsigned int i = 0; i < info->clock_num; i++)
isp->clks[i].id = info->clocks[i];
return devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks);
}
static int c3_isp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct c3_isp_device *isp;
int irq;
int ret;
isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
if (!isp)
return -ENOMEM;
isp->info = of_device_get_match_data(dev);
isp->dev = dev;
isp->base = devm_platform_ioremap_resource_byname(pdev, "isp");
if (IS_ERR(isp->base))
return dev_err_probe(dev, PTR_ERR(isp->base),
"Failed to ioremap resource\n");
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = c3_isp_get_clocks(isp);
if (ret)
return dev_err_probe(dev, ret, "Failed to get clocks\n");
platform_set_drvdata(pdev, isp);
pm_runtime_enable(dev);
ret = c3_isp_media_register(isp);
if (ret)
goto err_runtime_disable;
ret = c3_isp_core_register(isp);
if (ret)
goto err_v4l2_unregister;
ret = c3_isp_resizers_register(isp);
if (ret)
goto err_core_unregister;
ret = c3_isp_async_nf_register(isp);
if (ret)
goto err_resizers_unregister;
ret = devm_request_irq(dev, irq,
c3_isp_irq_handler, IRQF_SHARED,
dev_driver_string(dev), isp);
if (ret)
goto err_nf_unregister;
ret = c3_isp_videos_register(isp);
if (ret)
goto err_nf_unregister;
return 0;
err_nf_unregister:
c3_isp_async_nf_unregister(isp);
err_resizers_unregister:
c3_isp_resizers_unregister(isp);
err_core_unregister:
c3_isp_core_unregister(isp);
err_v4l2_unregister:
c3_isp_media_unregister(isp);
err_runtime_disable:
pm_runtime_disable(dev);
return ret;
};
static void c3_isp_remove(struct platform_device *pdev)
{
struct c3_isp_device *isp = platform_get_drvdata(pdev);
c3_isp_videos_unregister(isp);
c3_isp_async_nf_unregister(isp);
c3_isp_core_unregister(isp);
c3_isp_resizers_unregister(isp);
c3_isp_media_unregister(isp);
pm_runtime_disable(isp->dev);
};
static const struct c3_isp_info isp_info = {
.clocks = {"vapb", "isp0"},
.clock_num = 2
};
static const struct of_device_id c3_isp_of_match[] = {
{ .compatible = "amlogic,c3-isp",
.data = &isp_info },
{ },
};
MODULE_DEVICE_TABLE(of, c3_isp_of_match);
static struct platform_driver c3_isp_driver = {
.probe = c3_isp_probe,
.remove = c3_isp_remove,
.driver = {
.name = "c3-isp",
.of_match_table = c3_isp_of_match,
.pm = pm_ptr(&c3_isp_pm_ops),
},
};
module_platform_driver(c3_isp_driver);
MODULE_AUTHOR("Keke Li <keke.li@amlogic.com>");
MODULE_DESCRIPTION("Amlogic C3 ISP pipeline");
MODULE_LICENSE("GPL");