diff options
Diffstat (limited to 'drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c')
-rw-r--r-- | drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c new file mode 100644 index 0000000000..32c6619be9 --- /dev/null +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c @@ -0,0 +1,604 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for STM32 Digital Camera Memory Interface Pixel Processor + * + * Copyright (C) STMicroelectronics SA 2023 + * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com> + * Alain Volmat <alain.volmat@foss.st.com> + * for STMicroelectronics. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/reset.h> +#include <media/media-device.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#include "dcmipp-common.h" + +#define DCMIPP_MDEV_MODEL_NAME "DCMIPP MDEV" + +#define DCMIPP_ENT_LINK(src, srcpad, sink, sinkpad, link_flags) { \ + .src_ent = src, \ + .src_pad = srcpad, \ + .sink_ent = sink, \ + .sink_pad = sinkpad, \ + .flags = link_flags, \ +} + +struct dcmipp_device { + /* The platform device */ + struct platform_device pdev; + struct device *dev; + + /* Hardware resources */ + void __iomem *regs; + struct clk *kclk; + + /* The pipeline configuration */ + const struct dcmipp_pipeline_config *pipe_cfg; + + /* The Associated media_device parent */ + struct media_device mdev; + + /* Internal v4l2 parent device*/ + struct v4l2_device v4l2_dev; + + /* Entities */ + struct dcmipp_ent_device **entity; + + struct v4l2_async_notifier notifier; +}; + +static inline struct dcmipp_device * +notifier_to_dcmipp(struct v4l2_async_notifier *n) +{ + return container_of(n, struct dcmipp_device, notifier); +} + +/* Structure which describes individual configuration for each entity */ +struct dcmipp_ent_config { + const char *name; + struct dcmipp_ent_device *(*init) + (struct device *dev, const char *entity_name, + struct v4l2_device *v4l2_dev, void __iomem *regs); + void (*release)(struct dcmipp_ent_device *ved); +}; + +/* Structure which describes links between entities */ +struct dcmipp_ent_link { + unsigned int src_ent; + u16 src_pad; + unsigned int sink_ent; + u16 sink_pad; + u32 flags; +}; + +/* Structure which describes the whole topology */ +struct dcmipp_pipeline_config { + const struct dcmipp_ent_config *ents; + size_t num_ents; + const struct dcmipp_ent_link *links; + size_t num_links; +}; + +/* -------------------------------------------------------------------------- + * Topology Configuration + */ + +static const struct dcmipp_ent_config stm32mp13_ent_config[] = { + { + .name = "dcmipp_parallel", + .init = dcmipp_par_ent_init, + .release = dcmipp_par_ent_release, + }, + { + .name = "dcmipp_dump_postproc", + .init = dcmipp_byteproc_ent_init, + .release = dcmipp_byteproc_ent_release, + }, + { + .name = "dcmipp_dump_capture", + .init = dcmipp_bytecap_ent_init, + .release = dcmipp_bytecap_ent_release, + }, +}; + +#define ID_PARALLEL 0 +#define ID_DUMP_BYTEPROC 1 +#define ID_DUMP_CAPTURE 2 + +static const struct dcmipp_ent_link stm32mp13_ent_links[] = { + DCMIPP_ENT_LINK(ID_PARALLEL, 1, ID_DUMP_BYTEPROC, 0, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE), + DCMIPP_ENT_LINK(ID_DUMP_BYTEPROC, 1, ID_DUMP_CAPTURE, 0, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE), +}; + +static const struct dcmipp_pipeline_config stm32mp13_pipe_cfg = { + .ents = stm32mp13_ent_config, + .num_ents = ARRAY_SIZE(stm32mp13_ent_config), + .links = stm32mp13_ent_links, + .num_links = ARRAY_SIZE(stm32mp13_ent_links) +}; + +#define LINK_FLAG_TO_STR(f) ((f) == 0 ? "" :\ + (f) == MEDIA_LNK_FL_ENABLED ? "ENABLED" :\ + (f) == MEDIA_LNK_FL_IMMUTABLE ? "IMMUTABLE" :\ + (f) == (MEDIA_LNK_FL_ENABLED |\ + MEDIA_LNK_FL_IMMUTABLE) ?\ + "ENABLED, IMMUTABLE" :\ + "UNKNOWN") + +static int dcmipp_create_links(struct dcmipp_device *dcmipp) +{ + unsigned int i; + int ret; + + /* Initialize the links between entities */ + for (i = 0; i < dcmipp->pipe_cfg->num_links; i++) { + const struct dcmipp_ent_link *link = + &dcmipp->pipe_cfg->links[i]; + struct dcmipp_ent_device *ved_src = + dcmipp->entity[link->src_ent]; + struct dcmipp_ent_device *ved_sink = + dcmipp->entity[link->sink_ent]; + + dev_dbg(dcmipp->dev, "Create link \"%s\":%d -> %d:\"%s\" [%s]\n", + dcmipp->pipe_cfg->ents[link->src_ent].name, + link->src_pad, link->sink_pad, + dcmipp->pipe_cfg->ents[link->sink_ent].name, + LINK_FLAG_TO_STR(link->flags)); + + ret = media_create_pad_link(ved_src->ent, link->src_pad, + ved_sink->ent, link->sink_pad, + link->flags); + if (ret) + return ret; + } + + return 0; +} + +static int dcmipp_graph_init(struct dcmipp_device *dcmipp); + +static int dcmipp_create_subdevs(struct dcmipp_device *dcmipp) +{ + int ret, i; + + /* Call all subdev inits */ + for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) { + const char *name = dcmipp->pipe_cfg->ents[i].name; + + dev_dbg(dcmipp->dev, "add subdev %s\n", name); + dcmipp->entity[i] = + dcmipp->pipe_cfg->ents[i].init(dcmipp->dev, name, + &dcmipp->v4l2_dev, + dcmipp->regs); + if (IS_ERR(dcmipp->entity[i])) { + dev_err(dcmipp->dev, "failed to init subdev %s\n", + name); + ret = PTR_ERR(dcmipp->entity[i]); + goto err_init_entity; + } + } + + /* Initialize links */ + ret = dcmipp_create_links(dcmipp); + if (ret) + goto err_init_entity; + + ret = dcmipp_graph_init(dcmipp); + if (ret < 0) + goto err_init_entity; + + return 0; + +err_init_entity: + while (i > 0) + dcmipp->pipe_cfg->ents[i - 1].release(dcmipp->entity[i - 1]); + return ret; +} + +static const struct of_device_id dcmipp_of_match[] = { + { .compatible = "st,stm32mp13-dcmipp", .data = &stm32mp13_pipe_cfg }, + { /* end node */ }, +}; +MODULE_DEVICE_TABLE(of, dcmipp_of_match); + +static irqreturn_t dcmipp_irq_thread(int irq, void *arg) +{ + struct dcmipp_device *dcmipp = arg; + struct dcmipp_ent_device *ved; + unsigned int i; + + /* Call irq thread of each entities of pipeline */ + for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) { + ved = dcmipp->entity[i]; + if (ved->thread_fn && ved->handler_ret == IRQ_WAKE_THREAD) + ved->thread_fn(irq, ved); + } + + return IRQ_HANDLED; +} + +static irqreturn_t dcmipp_irq_callback(int irq, void *arg) +{ + struct dcmipp_device *dcmipp = arg; + struct dcmipp_ent_device *ved; + irqreturn_t ret = IRQ_HANDLED; + unsigned int i; + + /* Call irq handler of each entities of pipeline */ + for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) { + ved = dcmipp->entity[i]; + if (ved->handler) + ved->handler_ret = ved->handler(irq, ved); + else if (ved->thread_fn) + ved->handler_ret = IRQ_WAKE_THREAD; + else + ved->handler_ret = IRQ_HANDLED; + if (ved->handler_ret != IRQ_HANDLED) + ret = ved->handler_ret; + } + + return ret; +} + +static int dcmipp_graph_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *asd) +{ + struct dcmipp_device *dcmipp = notifier_to_dcmipp(notifier); + unsigned int ret; + int src_pad; + struct dcmipp_ent_device *sink; + struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_PARALLEL }; + struct fwnode_handle *ep; + + dev_dbg(dcmipp->dev, "Subdev \"%s\" bound\n", subdev->name); + + /* + * Link this sub-device to DCMIPP, it could be + * a parallel camera sensor or a CSI-2 to parallel bridge + */ + src_pad = media_entity_get_fwnode_pad(&subdev->entity, + subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + + /* Get bus characteristics from devicetree */ + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dcmipp->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) { + dev_err(dcmipp->dev, "Could not find the endpoint\n"); + return -ENODEV; + } + + /* Check for parallel bus-type first, then bt656 */ + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) { + vep.bus_type = V4L2_MBUS_BT656; + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) { + dev_err(dcmipp->dev, "Could not parse the endpoint\n"); + fwnode_handle_put(ep); + return ret; + } + } + + fwnode_handle_put(ep); + + if (vep.bus.parallel.bus_width == 0) { + dev_err(dcmipp->dev, "Invalid parallel interface bus-width\n"); + return -ENODEV; + } + + /* Only 8 bits bus width supported with BT656 bus */ + if (vep.bus_type == V4L2_MBUS_BT656 && + vep.bus.parallel.bus_width != 8) { + dev_err(dcmipp->dev, "BT656 bus conflicts with %u bits bus width (8 bits required)\n", + vep.bus.parallel.bus_width); + return -ENODEV; + } + + /* Parallel input device detected, connect it to parallel subdev */ + sink = dcmipp->entity[ID_PARALLEL]; + sink->bus.flags = vep.bus.parallel.flags; + sink->bus.bus_width = vep.bus.parallel.bus_width; + sink->bus.data_shift = vep.bus.parallel.data_shift; + sink->bus_type = vep.bus_type; + ret = media_create_pad_link(&subdev->entity, src_pad, sink->ent, 0, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(dcmipp->dev, "Failed to create media pad link with subdev \"%s\"\n", + subdev->name); + return ret; + } + + dev_dbg(dcmipp->dev, "DCMIPP is now linked to \"%s\"\n", subdev->name); + + return 0; +} + +static void dcmipp_graph_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asd) +{ + struct dcmipp_device *dcmipp = notifier_to_dcmipp(notifier); + + dev_dbg(dcmipp->dev, "Removing %s\n", sd->name); +} + +static int dcmipp_graph_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct dcmipp_device *dcmipp = notifier_to_dcmipp(notifier); + int ret; + + /* Register the media device */ + ret = media_device_register(&dcmipp->mdev); + if (ret) { + dev_err(dcmipp->mdev.dev, + "media device register failed (err=%d)\n", ret); + return ret; + } + + /* Expose all subdev's nodes*/ + ret = v4l2_device_register_subdev_nodes(&dcmipp->v4l2_dev); + if (ret) { + dev_err(dcmipp->mdev.dev, + "dcmipp subdev nodes registration failed (err=%d)\n", + ret); + media_device_unregister(&dcmipp->mdev); + return ret; + } + + dev_dbg(dcmipp->dev, "Notify complete !\n"); + + return 0; +} + +static const struct v4l2_async_notifier_operations dcmipp_graph_notify_ops = { + .bound = dcmipp_graph_notify_bound, + .unbind = dcmipp_graph_notify_unbind, + .complete = dcmipp_graph_notify_complete, +}; + +static int dcmipp_graph_init(struct dcmipp_device *dcmipp) +{ + struct v4l2_async_connection *asd; + struct fwnode_handle *ep; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dcmipp->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) { + dev_err(dcmipp->dev, "Failed to get next endpoint\n"); + return -EINVAL; + } + + v4l2_async_nf_init(&dcmipp->notifier, &dcmipp->v4l2_dev); + + asd = v4l2_async_nf_add_fwnode_remote(&dcmipp->notifier, ep, + struct v4l2_async_connection); + + fwnode_handle_put(ep); + + if (IS_ERR(asd)) { + dev_err(dcmipp->dev, "Failed to add fwnode remote subdev\n"); + return PTR_ERR(asd); + } + + dcmipp->notifier.ops = &dcmipp_graph_notify_ops; + + ret = v4l2_async_nf_register(&dcmipp->notifier); + if (ret < 0) { + dev_err(dcmipp->dev, "Failed to register notifier\n"); + v4l2_async_nf_cleanup(&dcmipp->notifier); + return ret; + } + + return 0; +} + +static int dcmipp_probe(struct platform_device *pdev) +{ + struct dcmipp_device *dcmipp; + struct clk *kclk; + const struct dcmipp_pipeline_config *pipe_cfg; + struct reset_control *rstc; + int irq; + int ret; + + dcmipp = devm_kzalloc(&pdev->dev, sizeof(*dcmipp), GFP_KERNEL); + if (!dcmipp) + return -ENOMEM; + + dcmipp->dev = &pdev->dev; + + pipe_cfg = device_get_match_data(dcmipp->dev); + if (!pipe_cfg) { + dev_err(&pdev->dev, "Can't get device data\n"); + return -ENODEV; + } + dcmipp->pipe_cfg = pipe_cfg; + + platform_set_drvdata(pdev, dcmipp); + + /* Get hardware resources from devicetree */ + rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(rstc)) + return dev_err_probe(&pdev->dev, PTR_ERR(rstc), + "Could not get reset control\n"); + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + if (irq != -EPROBE_DEFER) + dev_err(&pdev->dev, "Could not get irq\n"); + return irq ? irq : -ENXIO; + } + + dcmipp->regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); + if (IS_ERR(dcmipp->regs)) { + dev_err(&pdev->dev, "Could not map registers\n"); + return PTR_ERR(dcmipp->regs); + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, dcmipp_irq_callback, + dcmipp_irq_thread, IRQF_ONESHOT, + dev_name(&pdev->dev), dcmipp); + if (ret) { + dev_err(&pdev->dev, "Unable to request irq %d\n", irq); + return ret; + } + + /* Reset device */ + ret = reset_control_assert(rstc); + if (ret) { + dev_err(&pdev->dev, "Failed to assert the reset line\n"); + return ret; + } + + usleep_range(3000, 5000); + + ret = reset_control_deassert(rstc); + if (ret) { + dev_err(&pdev->dev, "Failed to deassert the reset line\n"); + return ret; + } + + kclk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(kclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(kclk), + "Unable to get kclk\n"); + dcmipp->kclk = kclk; + + dcmipp->entity = devm_kcalloc(&pdev->dev, dcmipp->pipe_cfg->num_ents, + sizeof(*dcmipp->entity), GFP_KERNEL); + if (!dcmipp->entity) + return -ENOMEM; + + /* Register the v4l2 struct */ + ret = v4l2_device_register(&pdev->dev, &dcmipp->v4l2_dev); + if (ret) { + dev_err(&pdev->dev, + "v4l2 device register failed (err=%d)\n", ret); + return ret; + } + + /* Link the media device within the v4l2_device */ + dcmipp->v4l2_dev.mdev = &dcmipp->mdev; + + /* Initialize media device */ + strscpy(dcmipp->mdev.model, DCMIPP_MDEV_MODEL_NAME, + sizeof(dcmipp->mdev.model)); + dcmipp->mdev.dev = &pdev->dev; + media_device_init(&dcmipp->mdev); + + /* Initialize subdevs */ + ret = dcmipp_create_subdevs(dcmipp); + if (ret) { + media_device_cleanup(&dcmipp->mdev); + v4l2_device_unregister(&dcmipp->v4l2_dev); + return ret; + } + + pm_runtime_enable(dcmipp->dev); + + dev_info(&pdev->dev, "Probe done"); + + return 0; +} + +static int dcmipp_remove(struct platform_device *pdev) +{ + struct dcmipp_device *dcmipp = platform_get_drvdata(pdev); + unsigned int i; + + pm_runtime_disable(&pdev->dev); + + v4l2_async_nf_unregister(&dcmipp->notifier); + v4l2_async_nf_cleanup(&dcmipp->notifier); + + for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) + dcmipp->pipe_cfg->ents[i].release(dcmipp->entity[i]); + + media_device_unregister(&dcmipp->mdev); + media_device_cleanup(&dcmipp->mdev); + + v4l2_device_unregister(&dcmipp->v4l2_dev); + + return 0; +} + +static int dcmipp_runtime_suspend(struct device *dev) +{ + struct dcmipp_device *dcmipp = dev_get_drvdata(dev); + + clk_disable_unprepare(dcmipp->kclk); + + return 0; +} + +static int dcmipp_runtime_resume(struct device *dev) +{ + struct dcmipp_device *dcmipp = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(dcmipp->kclk); + if (ret) + dev_err(dev, "%s: Failed to prepare_enable kclk\n", __func__); + + return ret; +} + +static int dcmipp_suspend(struct device *dev) +{ + /* disable clock */ + pm_runtime_force_suspend(dev); + + /* change pinctrl state */ + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static int dcmipp_resume(struct device *dev) +{ + /* restore pinctl default state */ + pinctrl_pm_select_default_state(dev); + + /* clock enable */ + pm_runtime_force_resume(dev); + + return 0; +} + +static const struct dev_pm_ops dcmipp_pm_ops = { + SYSTEM_SLEEP_PM_OPS(dcmipp_suspend, dcmipp_resume) + RUNTIME_PM_OPS(dcmipp_runtime_suspend, dcmipp_runtime_resume, NULL) +}; + +static struct platform_driver dcmipp_pdrv = { + .probe = dcmipp_probe, + .remove = dcmipp_remove, + .driver = { + .name = DCMIPP_PDEV_NAME, + .of_match_table = dcmipp_of_match, + .pm = pm_ptr(&dcmipp_pm_ops), + }, +}; + +module_platform_driver(dcmipp_pdrv); + +MODULE_AUTHOR("Hugues Fruchet <hugues.fruchet@foss.st.com>"); +MODULE_AUTHOR("Alain Volmat <alain.volmat@foss.st.com>"); +MODULE_DESCRIPTION("STMicroelectronics STM32 Digital Camera Memory Interface with Pixel Processor driver"); +MODULE_LICENSE("GPL"); |