614 lines
17 KiB
C
614 lines
17 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Rockchip ISP1 Driver - Base driver
|
|
*
|
|
* Copyright (C) 2019 Collabora, Ltd.
|
|
*
|
|
* Based on Rockchip ISP1 driver by Rockchip Electronics Co., Ltd.
|
|
* Copyright (C) 2017 Rockchip Electronics Co., Ltd.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/phy/phy-mipi-dphy.h>
|
|
#include <media/v4l2-fwnode.h>
|
|
|
|
#include "rkisp1-common.h"
|
|
|
|
/*
|
|
* ISP Details
|
|
* -----------
|
|
*
|
|
* ISP Comprises with:
|
|
* MIPI serial camera interface
|
|
* Image Signal Processing
|
|
* Many Image Enhancement Blocks
|
|
* Crop
|
|
* Resizer
|
|
* RBG display ready image
|
|
* Image Rotation
|
|
*
|
|
* ISP Block Diagram
|
|
* -----------------
|
|
* rkisp1-resizer.c rkisp1-capture.c
|
|
* |====================| |=======================|
|
|
* rkisp1-isp.c Main Picture Path
|
|
* |==========================| |===============================================|
|
|
* +-----------+ +--+--+--+--+ +--------+ +--------+ +-----------+
|
|
* | | | | | | | | | | | | |
|
|
* +--------+ |\ | | | | | | | -->| Crop |->| RSZ |------------->| |
|
|
* | MIPI |--->| \ | | | | | | | | | | | | | |
|
|
* +--------+ | | | | |IE|IE|IE|IE| | +--------+ +--------+ | Memory |
|
|
* |MUX|--->| ISP |->|0 |1 |2 |3 |---+ | Interface |
|
|
* +--------+ | | | | | | | | | | +--------+ +--------+ +--------+ | |
|
|
* |Parallel|--->| / | | | | | | | | | | | | | | | |
|
|
* +--------+ |/ | | | | | | | -->| Crop |->| RSZ |->| RGB |->| |
|
|
* | | | | | | | | | | | | Rotate | | |
|
|
* +-----------+ +--+--+--+--+ +--------+ +--------+ +--------+ +-----------+
|
|
* ^
|
|
* +--------+ | |===============================================|
|
|
* | DMA |------------------------------------+ Self Picture Path
|
|
* +--------+
|
|
*
|
|
* rkisp1-stats.c rkisp1-params.c
|
|
* |===============| |===============|
|
|
* +---------------+ +---------------+
|
|
* | | | |
|
|
* | ISP | | ISP |
|
|
* | | | |
|
|
* +---------------+ +---------------+
|
|
*
|
|
*
|
|
* Media Topology
|
|
* --------------
|
|
* +----------+ +----------+
|
|
* | Sensor 2 | | Sensor X |
|
|
* ------------ ... ------------
|
|
* | 0 | | 0 |
|
|
* +----------+ +----------+ +-----------+
|
|
* \ | | params |
|
|
* \ | | (output) |
|
|
* +----------+ \ | +-----------+
|
|
* | Sensor 1 | v v |
|
|
* ------------ +------+------+ |
|
|
* | 0 |----->| 0 | 1 |<---------+
|
|
* +----------+ |------+------|
|
|
* | ISP |
|
|
* |------+------|
|
|
* +-------------| 2 | 3 |----------+
|
|
* | +------+------+ |
|
|
* | | |
|
|
* v v v
|
|
* +- ---------+ +-----------+ +-----------+
|
|
* | 0 | | 0 | | stats |
|
|
* ------------- ------------- | (capture) |
|
|
* | Resizer | | Resizer | +-----------+
|
|
* ------------| ------------|
|
|
* | 1 | | 1 |
|
|
* +-----------+ +-----------+
|
|
* | |
|
|
* v v
|
|
* +-----------+ +-----------+
|
|
* | selfpath | | mainpath |
|
|
* | (capture) | | (capture) |
|
|
* +-----------+ +-----------+
|
|
*/
|
|
|
|
struct rkisp1_isr_data {
|
|
const char *name;
|
|
irqreturn_t (*isr)(int irq, void *ctx);
|
|
};
|
|
|
|
struct rkisp1_match_data {
|
|
const char * const *clks;
|
|
unsigned int clk_size;
|
|
const struct rkisp1_isr_data *isrs;
|
|
unsigned int isr_size;
|
|
enum rkisp1_cif_isp_version isp_ver;
|
|
};
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
* Sensor DT bindings
|
|
*/
|
|
|
|
static int rkisp1_create_links(struct rkisp1_device *rkisp1)
|
|
{
|
|
struct media_entity *source, *sink;
|
|
unsigned int flags, source_pad;
|
|
struct v4l2_subdev *sd;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
/* sensor links */
|
|
flags = MEDIA_LNK_FL_ENABLED;
|
|
list_for_each_entry(sd, &rkisp1->v4l2_dev.subdevs, list) {
|
|
if (sd == &rkisp1->isp.sd ||
|
|
sd == &rkisp1->resizer_devs[RKISP1_MAINPATH].sd ||
|
|
sd == &rkisp1->resizer_devs[RKISP1_SELFPATH].sd)
|
|
continue;
|
|
|
|
ret = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode,
|
|
MEDIA_PAD_FL_SOURCE);
|
|
if (ret < 0) {
|
|
dev_err(rkisp1->dev, "failed to find src pad for %s\n",
|
|
sd->name);
|
|
return ret;
|
|
}
|
|
source_pad = ret;
|
|
|
|
ret = media_create_pad_link(&sd->entity, source_pad,
|
|
&rkisp1->isp.sd.entity,
|
|
RKISP1_ISP_PAD_SINK_VIDEO,
|
|
flags);
|
|
if (ret)
|
|
return ret;
|
|
|
|
flags = 0;
|
|
}
|
|
|
|
flags = MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE;
|
|
|
|
/* create ISP->RSZ->CAP links */
|
|
for (i = 0; i < 2; i++) {
|
|
source = &rkisp1->isp.sd.entity;
|
|
sink = &rkisp1->resizer_devs[i].sd.entity;
|
|
ret = media_create_pad_link(source, RKISP1_ISP_PAD_SOURCE_VIDEO,
|
|
sink, RKISP1_RSZ_PAD_SINK,
|
|
MEDIA_LNK_FL_ENABLED);
|
|
if (ret)
|
|
return ret;
|
|
|
|
source = sink;
|
|
sink = &rkisp1->capture_devs[i].vnode.vdev.entity;
|
|
ret = media_create_pad_link(source, RKISP1_RSZ_PAD_SRC,
|
|
sink, 0, flags);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/* params links */
|
|
source = &rkisp1->params.vnode.vdev.entity;
|
|
sink = &rkisp1->isp.sd.entity;
|
|
ret = media_create_pad_link(source, 0, sink,
|
|
RKISP1_ISP_PAD_SINK_PARAMS, flags);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* 3A stats links */
|
|
source = &rkisp1->isp.sd.entity;
|
|
sink = &rkisp1->stats.vnode.vdev.entity;
|
|
return media_create_pad_link(source, RKISP1_ISP_PAD_SOURCE_STATS,
|
|
sink, 0, flags);
|
|
}
|
|
|
|
static int rkisp1_subdev_notifier_bound(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_subdev *sd,
|
|
struct v4l2_async_subdev *asd)
|
|
{
|
|
struct rkisp1_device *rkisp1 =
|
|
container_of(notifier, struct rkisp1_device, notifier);
|
|
struct rkisp1_sensor_async *s_asd =
|
|
container_of(asd, struct rkisp1_sensor_async, asd);
|
|
|
|
s_asd->pixel_rate_ctrl = v4l2_ctrl_find(sd->ctrl_handler,
|
|
V4L2_CID_PIXEL_RATE);
|
|
s_asd->sd = sd;
|
|
s_asd->dphy = devm_phy_get(rkisp1->dev, "dphy");
|
|
if (IS_ERR(s_asd->dphy)) {
|
|
if (PTR_ERR(s_asd->dphy) != -EPROBE_DEFER)
|
|
dev_err(rkisp1->dev, "Couldn't get the MIPI D-PHY\n");
|
|
return PTR_ERR(s_asd->dphy);
|
|
}
|
|
|
|
phy_init(s_asd->dphy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rkisp1_subdev_notifier_unbind(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_subdev *sd,
|
|
struct v4l2_async_subdev *asd)
|
|
{
|
|
struct rkisp1_sensor_async *s_asd =
|
|
container_of(asd, struct rkisp1_sensor_async, asd);
|
|
|
|
phy_exit(s_asd->dphy);
|
|
}
|
|
|
|
static int rkisp1_subdev_notifier_complete(struct v4l2_async_notifier *notifier)
|
|
{
|
|
struct rkisp1_device *rkisp1 =
|
|
container_of(notifier, struct rkisp1_device, notifier);
|
|
int ret;
|
|
|
|
ret = rkisp1_create_links(rkisp1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = v4l2_device_register_subdev_nodes(&rkisp1->v4l2_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
dev_dbg(rkisp1->dev, "Async subdev notifier completed\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_async_notifier_operations rkisp1_subdev_notifier_ops = {
|
|
.bound = rkisp1_subdev_notifier_bound,
|
|
.unbind = rkisp1_subdev_notifier_unbind,
|
|
.complete = rkisp1_subdev_notifier_complete,
|
|
};
|
|
|
|
static int rkisp1_subdev_notifier(struct rkisp1_device *rkisp1)
|
|
{
|
|
struct v4l2_async_notifier *ntf = &rkisp1->notifier;
|
|
unsigned int next_id = 0;
|
|
int ret;
|
|
|
|
v4l2_async_nf_init(ntf);
|
|
|
|
while (1) {
|
|
struct v4l2_fwnode_endpoint vep = {
|
|
.bus_type = V4L2_MBUS_CSI2_DPHY
|
|
};
|
|
struct rkisp1_sensor_async *rk_asd;
|
|
struct fwnode_handle *ep;
|
|
|
|
ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(rkisp1->dev),
|
|
0, next_id,
|
|
FWNODE_GRAPH_ENDPOINT_NEXT);
|
|
if (!ep)
|
|
break;
|
|
|
|
ret = v4l2_fwnode_endpoint_parse(ep, &vep);
|
|
if (ret)
|
|
goto err_parse;
|
|
|
|
rk_asd = v4l2_async_nf_add_fwnode_remote(ntf, ep,
|
|
struct
|
|
rkisp1_sensor_async);
|
|
if (IS_ERR(rk_asd)) {
|
|
ret = PTR_ERR(rk_asd);
|
|
goto err_parse;
|
|
}
|
|
|
|
rk_asd->mbus_type = vep.bus_type;
|
|
rk_asd->mbus_flags = vep.bus.mipi_csi2.flags;
|
|
rk_asd->lanes = vep.bus.mipi_csi2.num_data_lanes;
|
|
|
|
dev_dbg(rkisp1->dev, "registered ep id %d with %d lanes\n",
|
|
vep.base.id, rk_asd->lanes);
|
|
|
|
next_id = vep.base.id + 1;
|
|
|
|
fwnode_handle_put(ep);
|
|
|
|
continue;
|
|
err_parse:
|
|
fwnode_handle_put(ep);
|
|
v4l2_async_nf_cleanup(ntf);
|
|
return ret;
|
|
}
|
|
|
|
if (next_id == 0)
|
|
dev_dbg(rkisp1->dev, "no remote subdevice found\n");
|
|
ntf->ops = &rkisp1_subdev_notifier_ops;
|
|
ret = v4l2_async_nf_register(&rkisp1->v4l2_dev, ntf);
|
|
if (ret) {
|
|
v4l2_async_nf_cleanup(ntf);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
* Power
|
|
*/
|
|
|
|
static int __maybe_unused rkisp1_runtime_suspend(struct device *dev)
|
|
{
|
|
struct rkisp1_device *rkisp1 = dev_get_drvdata(dev);
|
|
|
|
clk_bulk_disable_unprepare(rkisp1->clk_size, rkisp1->clks);
|
|
return pinctrl_pm_select_sleep_state(dev);
|
|
}
|
|
|
|
static int __maybe_unused rkisp1_runtime_resume(struct device *dev)
|
|
{
|
|
struct rkisp1_device *rkisp1 = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = pinctrl_pm_select_default_state(dev);
|
|
if (ret)
|
|
return ret;
|
|
ret = clk_bulk_prepare_enable(rkisp1->clk_size, rkisp1->clks);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops rkisp1_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
|
pm_runtime_force_resume)
|
|
SET_RUNTIME_PM_OPS(rkisp1_runtime_suspend, rkisp1_runtime_resume, NULL)
|
|
};
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
* Core
|
|
*/
|
|
|
|
static int rkisp1_entities_register(struct rkisp1_device *rkisp1)
|
|
{
|
|
int ret;
|
|
|
|
ret = rkisp1_isp_register(rkisp1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = rkisp1_resizer_devs_register(rkisp1);
|
|
if (ret)
|
|
goto err_unreg_isp_subdev;
|
|
|
|
ret = rkisp1_capture_devs_register(rkisp1);
|
|
if (ret)
|
|
goto err_unreg_resizer_devs;
|
|
|
|
ret = rkisp1_stats_register(rkisp1);
|
|
if (ret)
|
|
goto err_unreg_capture_devs;
|
|
|
|
ret = rkisp1_params_register(rkisp1);
|
|
if (ret)
|
|
goto err_unreg_stats;
|
|
|
|
ret = rkisp1_subdev_notifier(rkisp1);
|
|
if (ret) {
|
|
dev_err(rkisp1->dev,
|
|
"Failed to register subdev notifier(%d)\n", ret);
|
|
goto err_unreg_params;
|
|
}
|
|
|
|
return 0;
|
|
err_unreg_params:
|
|
rkisp1_params_unregister(rkisp1);
|
|
err_unreg_stats:
|
|
rkisp1_stats_unregister(rkisp1);
|
|
err_unreg_capture_devs:
|
|
rkisp1_capture_devs_unregister(rkisp1);
|
|
err_unreg_resizer_devs:
|
|
rkisp1_resizer_devs_unregister(rkisp1);
|
|
err_unreg_isp_subdev:
|
|
rkisp1_isp_unregister(rkisp1);
|
|
return ret;
|
|
}
|
|
|
|
static irqreturn_t rkisp1_isr(int irq, void *ctx)
|
|
{
|
|
/*
|
|
* Call rkisp1_capture_isr() first to handle the frame that
|
|
* potentially completed using the current frame_sequence number before
|
|
* it is potentially incremented by rkisp1_isp_isr() in the vertical
|
|
* sync.
|
|
*/
|
|
rkisp1_capture_isr(irq, ctx);
|
|
rkisp1_isp_isr(irq, ctx);
|
|
rkisp1_mipi_isr(irq, ctx);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static const char * const px30_isp_clks[] = {
|
|
"isp",
|
|
"aclk",
|
|
"hclk",
|
|
"pclk",
|
|
};
|
|
|
|
static const struct rkisp1_isr_data px30_isp_isrs[] = {
|
|
{ "isp", rkisp1_isp_isr },
|
|
{ "mi", rkisp1_capture_isr },
|
|
{ "mipi", rkisp1_mipi_isr },
|
|
};
|
|
|
|
static const struct rkisp1_match_data px30_isp_match_data = {
|
|
.clks = px30_isp_clks,
|
|
.clk_size = ARRAY_SIZE(px30_isp_clks),
|
|
.isrs = px30_isp_isrs,
|
|
.isr_size = ARRAY_SIZE(px30_isp_isrs),
|
|
.isp_ver = RKISP1_V12,
|
|
};
|
|
|
|
static const char * const rk3399_isp_clks[] = {
|
|
"isp",
|
|
"aclk",
|
|
"hclk",
|
|
};
|
|
|
|
static const struct rkisp1_isr_data rk3399_isp_isrs[] = {
|
|
{ NULL, rkisp1_isr },
|
|
};
|
|
|
|
static const struct rkisp1_match_data rk3399_isp_match_data = {
|
|
.clks = rk3399_isp_clks,
|
|
.clk_size = ARRAY_SIZE(rk3399_isp_clks),
|
|
.isrs = rk3399_isp_isrs,
|
|
.isr_size = ARRAY_SIZE(rk3399_isp_isrs),
|
|
.isp_ver = RKISP1_V10,
|
|
};
|
|
|
|
static const struct of_device_id rkisp1_of_match[] = {
|
|
{
|
|
.compatible = "rockchip,px30-cif-isp",
|
|
.data = &px30_isp_match_data,
|
|
},
|
|
{
|
|
.compatible = "rockchip,rk3399-cif-isp",
|
|
.data = &rk3399_isp_match_data,
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, rkisp1_of_match);
|
|
|
|
static void rkisp1_debug_init(struct rkisp1_device *rkisp1)
|
|
{
|
|
struct rkisp1_debug *debug = &rkisp1->debug;
|
|
|
|
debug->debugfs_dir = debugfs_create_dir(dev_name(rkisp1->dev), NULL);
|
|
debugfs_create_ulong("data_loss", 0444, debug->debugfs_dir,
|
|
&debug->data_loss);
|
|
debugfs_create_ulong("outform_size_err", 0444, debug->debugfs_dir,
|
|
&debug->outform_size_error);
|
|
debugfs_create_ulong("img_stabilization_size_error", 0444,
|
|
debug->debugfs_dir,
|
|
&debug->img_stabilization_size_error);
|
|
debugfs_create_ulong("inform_size_error", 0444, debug->debugfs_dir,
|
|
&debug->inform_size_error);
|
|
debugfs_create_ulong("irq_delay", 0444, debug->debugfs_dir,
|
|
&debug->irq_delay);
|
|
debugfs_create_ulong("mipi_error", 0444, debug->debugfs_dir,
|
|
&debug->mipi_error);
|
|
debugfs_create_ulong("stats_error", 0444, debug->debugfs_dir,
|
|
&debug->stats_error);
|
|
debugfs_create_ulong("mp_stop_timeout", 0444, debug->debugfs_dir,
|
|
&debug->stop_timeout[RKISP1_MAINPATH]);
|
|
debugfs_create_ulong("sp_stop_timeout", 0444, debug->debugfs_dir,
|
|
&debug->stop_timeout[RKISP1_SELFPATH]);
|
|
debugfs_create_ulong("mp_frame_drop", 0444, debug->debugfs_dir,
|
|
&debug->frame_drop[RKISP1_MAINPATH]);
|
|
debugfs_create_ulong("sp_frame_drop", 0444, debug->debugfs_dir,
|
|
&debug->frame_drop[RKISP1_SELFPATH]);
|
|
}
|
|
|
|
static int rkisp1_probe(struct platform_device *pdev)
|
|
{
|
|
const struct rkisp1_match_data *match_data;
|
|
struct device *dev = &pdev->dev;
|
|
struct rkisp1_device *rkisp1;
|
|
struct v4l2_device *v4l2_dev;
|
|
unsigned int i;
|
|
int ret, irq;
|
|
|
|
match_data = of_device_get_match_data(&pdev->dev);
|
|
if (!match_data)
|
|
return -ENODEV;
|
|
|
|
rkisp1 = devm_kzalloc(dev, sizeof(*rkisp1), GFP_KERNEL);
|
|
if (!rkisp1)
|
|
return -ENOMEM;
|
|
|
|
dev_set_drvdata(dev, rkisp1);
|
|
rkisp1->dev = dev;
|
|
|
|
mutex_init(&rkisp1->stream_lock);
|
|
|
|
rkisp1->base_addr = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(rkisp1->base_addr))
|
|
return PTR_ERR(rkisp1->base_addr);
|
|
|
|
for (i = 0; i < match_data->isr_size; i++) {
|
|
irq = (match_data->isrs[i].name) ?
|
|
platform_get_irq_byname(pdev, match_data->isrs[i].name) :
|
|
platform_get_irq(pdev, i);
|
|
if (irq < 0)
|
|
return irq;
|
|
|
|
ret = devm_request_irq(dev, irq, match_data->isrs[i].isr, IRQF_SHARED,
|
|
dev_driver_string(dev), dev);
|
|
if (ret) {
|
|
dev_err(dev, "request irq failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < match_data->clk_size; i++)
|
|
rkisp1->clks[i].id = match_data->clks[i];
|
|
ret = devm_clk_bulk_get(dev, match_data->clk_size, rkisp1->clks);
|
|
if (ret)
|
|
return ret;
|
|
rkisp1->clk_size = match_data->clk_size;
|
|
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
rkisp1->media_dev.hw_revision = match_data->isp_ver;
|
|
strscpy(rkisp1->media_dev.model, RKISP1_DRIVER_NAME,
|
|
sizeof(rkisp1->media_dev.model));
|
|
rkisp1->media_dev.dev = &pdev->dev;
|
|
strscpy(rkisp1->media_dev.bus_info, RKISP1_BUS_INFO,
|
|
sizeof(rkisp1->media_dev.bus_info));
|
|
media_device_init(&rkisp1->media_dev);
|
|
|
|
v4l2_dev = &rkisp1->v4l2_dev;
|
|
v4l2_dev->mdev = &rkisp1->media_dev;
|
|
strscpy(v4l2_dev->name, RKISP1_DRIVER_NAME, sizeof(v4l2_dev->name));
|
|
|
|
ret = v4l2_device_register(rkisp1->dev, &rkisp1->v4l2_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = media_device_register(&rkisp1->media_dev);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to register media device: %d\n", ret);
|
|
goto err_unreg_v4l2_dev;
|
|
}
|
|
|
|
ret = rkisp1_entities_register(rkisp1);
|
|
if (ret)
|
|
goto err_unreg_media_dev;
|
|
|
|
rkisp1_debug_init(rkisp1);
|
|
|
|
return 0;
|
|
|
|
err_unreg_media_dev:
|
|
media_device_unregister(&rkisp1->media_dev);
|
|
err_unreg_v4l2_dev:
|
|
v4l2_device_unregister(&rkisp1->v4l2_dev);
|
|
pm_runtime_disable(&pdev->dev);
|
|
return ret;
|
|
}
|
|
|
|
static int rkisp1_remove(struct platform_device *pdev)
|
|
{
|
|
struct rkisp1_device *rkisp1 = platform_get_drvdata(pdev);
|
|
|
|
v4l2_async_nf_unregister(&rkisp1->notifier);
|
|
v4l2_async_nf_cleanup(&rkisp1->notifier);
|
|
|
|
rkisp1_params_unregister(rkisp1);
|
|
rkisp1_stats_unregister(rkisp1);
|
|
rkisp1_capture_devs_unregister(rkisp1);
|
|
rkisp1_resizer_devs_unregister(rkisp1);
|
|
rkisp1_isp_unregister(rkisp1);
|
|
|
|
media_device_unregister(&rkisp1->media_dev);
|
|
v4l2_device_unregister(&rkisp1->v4l2_dev);
|
|
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
debugfs_remove_recursive(rkisp1->debug.debugfs_dir);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver rkisp1_drv = {
|
|
.driver = {
|
|
.name = RKISP1_DRIVER_NAME,
|
|
.of_match_table = of_match_ptr(rkisp1_of_match),
|
|
.pm = &rkisp1_pm_ops,
|
|
},
|
|
.probe = rkisp1_probe,
|
|
.remove = rkisp1_remove,
|
|
};
|
|
|
|
module_platform_driver(rkisp1_drv);
|
|
MODULE_DESCRIPTION("Rockchip ISP1 platform driver");
|
|
MODULE_LICENSE("Dual MIT/GPL");
|