// SPDX-License-Identifier: GPL-2.0-only /* * isp.c * * TI OMAP3 ISP - Core * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2007-2009 Texas Instruments, Inc. * * Contacts: Laurent Pinchart <[email protected]> * Sakari Ailus <[email protected]> * * Contributors: * Laurent Pinchart <[email protected]> * Sakari Ailus <[email protected]> * David Cohen <[email protected]> * Stanimir Varbanov <[email protected]> * Vimarsh Zutshi <[email protected]> * Tuukka Toivonen <[email protected]> * Sergio Aguirre <[email protected]> * Antti Koskipaa <[email protected]> * Ivan T. Ivanov <[email protected]> * RaniSuneela <[email protected]> * Atanas Filipov <[email protected]> * Gjorgji Rosikopulos <[email protected]> * Hiroshi DOYU <[email protected]> * Nayden Kanchev <[email protected]> * Phil Carmody <[email protected]> * Artem Bityutskiy <[email protected]> * Dominic Curran <[email protected]> * Ilkka Myllyperkio <[email protected]> * Pallavi Kulkarni <[email protected]> * Vaibhav Hiremath <[email protected]> * Mohit Jalori <[email protected]> * Sameer Venkatraman <[email protected]> * Senthilvadivu Guruswamy <[email protected]> * Thara Gopinath <[email protected]> * Toni Leinonen <[email protected]> * Troy Laramy <[email protected]> */ #include <linux/clk.h> #include <linux/clkdev.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/dma-mapping.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/omap-iommu.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/sched.h> #include <linux/vmalloc.h> #ifdef CONFIG_ARM_DMA_USE_IOMMU #include <asm/dma-iommu.h> #endif #include <media/v4l2-common.h> #include <media/v4l2-fwnode.h> #include <media/v4l2-device.h> #include <media/v4l2-mc.h> #include "isp.h" #include "ispreg.h" #include "ispccdc.h" #include "isppreview.h" #include "ispresizer.h" #include "ispcsi2.h" #include "ispccp2.h" #include "isph3a.h" #include "isphist.h" static unsigned int autoidle; module_param(autoidle, int, 0444); MODULE_PARM_DESC(…) …; static void isp_save_ctx(struct isp_device *isp); static void isp_restore_ctx(struct isp_device *isp); static const struct isp_res_mapping isp_res_maps[] = …; /* Structure for saving/restoring ISP module registers */ static struct isp_reg isp_reg_list[] = …; /* * omap3isp_flush - Post pending L3 bus writes by doing a register readback * @isp: OMAP3 ISP device * * In order to force posting of pending writes, we need to write and * readback the same register, in this case the revision register. * * See this link for reference: * https://www.mail-archive.com/[email protected]/msg08149.html */ void omap3isp_flush(struct isp_device *isp) { … } /* ----------------------------------------------------------------------------- * XCLK */ #define to_isp_xclk(_hw) … static void isp_xclk_update(struct isp_xclk *xclk, u32 divider) { … } static int isp_xclk_prepare(struct clk_hw *hw) { … } static void isp_xclk_unprepare(struct clk_hw *hw) { … } static int isp_xclk_enable(struct clk_hw *hw) { … } static void isp_xclk_disable(struct clk_hw *hw) { … } static unsigned long isp_xclk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { … } static u32 isp_xclk_calc_divider(unsigned long *rate, unsigned long parent_rate) { … } static long isp_xclk_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { … } static int isp_xclk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { … } static const struct clk_ops isp_xclk_ops = …; static const char *isp_xclk_parent_name = …; static struct clk *isp_xclk_src_get(struct of_phandle_args *clkspec, void *data) { … } static int isp_xclk_init(struct isp_device *isp) { … } static void isp_xclk_cleanup(struct isp_device *isp) { … } /* ----------------------------------------------------------------------------- * Interrupts */ /* * isp_enable_interrupts - Enable ISP interrupts. * @isp: OMAP3 ISP device */ static void isp_enable_interrupts(struct isp_device *isp) { … } /* * isp_disable_interrupts - Disable ISP interrupts. * @isp: OMAP3 ISP device */ static void isp_disable_interrupts(struct isp_device *isp) { … } /* * isp_core_init - ISP core settings * @isp: OMAP3 ISP device * @idle: Consider idle state. * * Set the power settings for the ISP and SBL bus and configure the HS/VS * interrupt source. * * We need to configure the HS/VS interrupt source before interrupts get * enabled, as the sensor might be free-running and the ISP default setting * (HS edge) would put an unnecessary burden on the CPU. */ static void isp_core_init(struct isp_device *isp, int idle) { … } /* * Configure the bridge and lane shifter. Valid inputs are * * CCDC_INPUT_PARALLEL: Parallel interface * CCDC_INPUT_CSI2A: CSI2a receiver * CCDC_INPUT_CCP2B: CCP2b receiver * CCDC_INPUT_CSI2C: CSI2c receiver * * The bridge and lane shifter are configured according to the selected input * and the ISP platform data. */ void omap3isp_configure_bridge(struct isp_device *isp, enum ccdc_input_entity input, const struct isp_parallel_cfg *parcfg, unsigned int shift, unsigned int bridge) { … } void omap3isp_hist_dma_done(struct isp_device *isp) { … } static inline void __maybe_unused isp_isr_dbg(struct isp_device *isp, u32 irqstatus) { … } static void isp_isr_sbl(struct isp_device *isp) { … } /* * isp_isr - Interrupt Service Routine for Camera ISP module. * @irq: Not used currently. * @_isp: Pointer to the OMAP3 ISP device * * Handles the corresponding callback if plugged in. */ static irqreturn_t isp_isr(int irq, void *_isp) { … } static const struct media_device_ops isp_media_ops = …; /* ----------------------------------------------------------------------------- * Pipeline stream management */ /* * isp_pipeline_enable - Enable streaming on a pipeline * @pipe: ISP pipeline * @mode: Stream mode (single shot or continuous) * * Walk the entities chain starting at the pipeline output video node and start * all modules in the chain in the given mode. * * Return 0 if successful, or the return value of the failed video::s_stream * operation otherwise. */ static int isp_pipeline_enable(struct isp_pipeline *pipe, enum isp_pipeline_stream_state mode) { … } static int isp_pipeline_wait_resizer(struct isp_device *isp) { … } static int isp_pipeline_wait_preview(struct isp_device *isp) { … } static int isp_pipeline_wait_ccdc(struct isp_device *isp) { … } #define ISP_STOP_TIMEOUT … static int isp_pipeline_wait(struct isp_device *isp, int(*busy)(struct isp_device *isp)) { … } /* * isp_pipeline_disable - Disable streaming on a pipeline * @pipe: ISP pipeline * * Walk the entities chain starting at the pipeline output video node and stop * all modules in the chain. Wait synchronously for the modules to be stopped if * necessary. * * Return 0 if all modules have been properly stopped, or -ETIMEDOUT if a module * can't be stopped (in which case a software reset of the ISP is probably * necessary). */ static int isp_pipeline_disable(struct isp_pipeline *pipe) { … } /* * omap3isp_pipeline_set_stream - Enable/disable streaming on a pipeline * @pipe: ISP pipeline * @state: Stream state (stopped, single shot or continuous) * * Set the pipeline to the given stream state. Pipelines can be started in * single-shot or continuous mode. * * Return 0 if successful, or the return value of the failed video::s_stream * operation otherwise. The pipeline state is not updated when the operation * fails, except when stopping the pipeline. */ int omap3isp_pipeline_set_stream(struct isp_pipeline *pipe, enum isp_pipeline_stream_state state) { … } /* * omap3isp_pipeline_cancel_stream - Cancel stream on a pipeline * @pipe: ISP pipeline * * Cancelling a stream mark all buffers on all video nodes in the pipeline as * erroneous and makes sure no new buffer can be queued. This function is called * when a fatal error that prevents any further operation on the pipeline * occurs. */ void omap3isp_pipeline_cancel_stream(struct isp_pipeline *pipe) { … } /* * isp_pipeline_resume - Resume streaming on a pipeline * @pipe: ISP pipeline * * Resume video output and input and re-enable pipeline. */ static void isp_pipeline_resume(struct isp_pipeline *pipe) { … } /* * isp_pipeline_suspend - Suspend streaming on a pipeline * @pipe: ISP pipeline * * Suspend pipeline. */ static void isp_pipeline_suspend(struct isp_pipeline *pipe) { … } /* * isp_pipeline_is_last - Verify if entity has an enabled link to the output * video node * @me: ISP module's media entity * * Returns 1 if the entity has an enabled link to the output video node or 0 * otherwise. It's true only while pipeline can have no more than one output * node. */ static int isp_pipeline_is_last(struct media_entity *me) { … } /* * isp_suspend_module_pipeline - Suspend pipeline to which belongs the module * @me: ISP module's media entity * * Suspend the whole pipeline if module's entity has an enabled link to the * output video node. It works only while pipeline can have no more than one * output node. */ static void isp_suspend_module_pipeline(struct media_entity *me) { … } /* * isp_resume_module_pipeline - Resume pipeline to which belongs the module * @me: ISP module's media entity * * Resume the whole pipeline if module's entity has an enabled link to the * output video node. It works only while pipeline can have no more than one * output node. */ static void isp_resume_module_pipeline(struct media_entity *me) { … } /* * isp_suspend_modules - Suspend ISP submodules. * @isp: OMAP3 ISP device * * Returns 0 if suspend left in idle state all the submodules properly, * or returns 1 if a general Reset is required to suspend the submodules. */ static int __maybe_unused isp_suspend_modules(struct isp_device *isp) { … } /* * isp_resume_modules - Resume ISP submodules. * @isp: OMAP3 ISP device */ static void __maybe_unused isp_resume_modules(struct isp_device *isp) { … } /* * isp_reset - Reset ISP with a timeout wait for idle. * @isp: OMAP3 ISP device */ static int isp_reset(struct isp_device *isp) { … } /* * isp_save_context - Saves the values of the ISP module registers. * @isp: OMAP3 ISP device * @reg_list: Structure containing pairs of register address and value to * modify on OMAP. */ static void isp_save_context(struct isp_device *isp, struct isp_reg *reg_list) { … } /* * isp_restore_context - Restores the values of the ISP module registers. * @isp: OMAP3 ISP device * @reg_list: Structure containing pairs of register address and value to * modify on OMAP. */ static void isp_restore_context(struct isp_device *isp, struct isp_reg *reg_list) { … } /* * isp_save_ctx - Saves ISP, CCDC, HIST, H3A, PREV, RESZ & MMU context. * @isp: OMAP3 ISP device * * Routine for saving the context of each module in the ISP. * CCDC, HIST, H3A, PREV, RESZ and MMU. */ static void isp_save_ctx(struct isp_device *isp) { … } /* * isp_restore_ctx - Restores ISP, CCDC, HIST, H3A, PREV, RESZ & MMU context. * @isp: OMAP3 ISP device * * Routine for restoring the context of each module in the ISP. * CCDC, HIST, H3A, PREV, RESZ and MMU. */ static void isp_restore_ctx(struct isp_device *isp) { … } /* ----------------------------------------------------------------------------- * SBL resources management */ #define OMAP3_ISP_SBL_READ … #define OMAP3_ISP_SBL_WRITE … void omap3isp_sbl_enable(struct isp_device *isp, enum isp_sbl_resource res) { … } void omap3isp_sbl_disable(struct isp_device *isp, enum isp_sbl_resource res) { … } /* * isp_module_sync_idle - Helper to sync module with its idle state * @me: ISP submodule's media entity * @wait: ISP submodule's wait queue for streamoff/interrupt synchronization * @stopping: flag which tells module wants to stop * * This function checks if ISP submodule needs to wait for next interrupt. If * yes, makes the caller to sleep while waiting for such event. */ int omap3isp_module_sync_idle(struct media_entity *me, wait_queue_head_t *wait, atomic_t *stopping) { … } /* * omap3isp_module_sync_is_stopping - Helper to verify if module was stopping * @wait: ISP submodule's wait queue for streamoff/interrupt synchronization * @stopping: flag which tells module wants to stop * * This function checks if ISP submodule was stopping. In case of yes, it * notices the caller by setting stopping to 0 and waking up the wait queue. * Returns 1 if it was stopping or 0 otherwise. */ int omap3isp_module_sync_is_stopping(wait_queue_head_t *wait, atomic_t *stopping) { … } /* -------------------------------------------------------------------------- * Clock management */ #define ISPCTRL_CLKS_MASK … static void __isp_subclk_update(struct isp_device *isp) { … } void omap3isp_subclk_enable(struct isp_device *isp, enum isp_subclk_resource res) { … } void omap3isp_subclk_disable(struct isp_device *isp, enum isp_subclk_resource res) { … } /* * isp_enable_clocks - Enable ISP clocks * @isp: OMAP3 ISP device * * Return 0 if successful, or clk_prepare_enable return value if any of them * fails. */ static int isp_enable_clocks(struct isp_device *isp) { … } /* * isp_disable_clocks - Disable ISP clocks * @isp: OMAP3 ISP device */ static void isp_disable_clocks(struct isp_device *isp) { … } static const char *isp_clocks[] = …; static int isp_get_clocks(struct isp_device *isp) { … } /* * omap3isp_get - Acquire the ISP resource. * * Initializes the clocks for the first acquire. * * Increment the reference count on the ISP. If the first reference is taken, * enable clocks and power-up all submodules. * * Return a pointer to the ISP device structure, or NULL if an error occurred. */ static struct isp_device *__omap3isp_get(struct isp_device *isp, bool irq) { … } struct isp_device *omap3isp_get(struct isp_device *isp) { … } /* * omap3isp_put - Release the ISP * * Decrement the reference count on the ISP. If the last reference is released, * power-down all submodules, disable clocks and free temporary buffers. */ static void __omap3isp_put(struct isp_device *isp, bool save_ctx) { … } void omap3isp_put(struct isp_device *isp) { … } /* -------------------------------------------------------------------------- * Platform device driver */ /* * omap3isp_print_status - Prints the values of the ISP Control Module registers * @isp: OMAP3 ISP device */ #define ISP_PRINT_REGISTER(isp, name) … #define SBL_PRINT_REGISTER(isp, name) … void omap3isp_print_status(struct isp_device *isp) { … } #ifdef CONFIG_PM /* * Power management support. * * As the ISP can't properly handle an input video stream interruption on a non * frame boundary, the ISP pipelines need to be stopped before sensors get * suspended. However, as suspending the sensors can require a running clock, * which can be provided by the ISP, the ISP can't be completely suspended * before the sensor. * * To solve this problem power management support is split into prepare/complete * and suspend/resume operations. The pipelines are stopped in prepare() and the * ISP clocks get disabled in suspend(). Similarly, the clocks are re-enabled in * resume(), and the pipelines are restarted in complete(). * * TODO: PM dependencies between the ISP and sensors are not modelled explicitly * yet. */ static int isp_pm_prepare(struct device *dev) { … } static int isp_pm_suspend(struct device *dev) { … } static int isp_pm_resume(struct device *dev) { … } static void isp_pm_complete(struct device *dev) { … } #else #define isp_pm_prepare … #define isp_pm_suspend … #define isp_pm_resume … #define isp_pm_complete … #endif /* CONFIG_PM */ static void isp_unregister_entities(struct isp_device *isp) { … } static int isp_link_entity( struct isp_device *isp, struct media_entity *entity, enum isp_interface_type interface) { … } static int isp_register_entities(struct isp_device *isp) { … } /* * isp_create_links() - Create links for internal and external ISP entities * @isp : Pointer to ISP device * * This function creates all links between ISP internal and external entities. * * Return: A negative error code on failure or zero on success. Possible error * codes are those returned by media_create_pad_link(). */ static int isp_create_links(struct isp_device *isp) { … } static void isp_cleanup_modules(struct isp_device *isp) { … } static int isp_initialize_modules(struct isp_device *isp) { … } static void isp_detach_iommu(struct isp_device *isp) { … } static int isp_attach_iommu(struct isp_device *isp) { … } /* * isp_remove - Remove ISP platform device * @pdev: Pointer to ISP platform device * * Always returns 0. */ static void isp_remove(struct platform_device *pdev) { … } enum isp_of_phy { … }; static int isp_subdev_notifier_bound(struct v4l2_async_notifier *async, struct v4l2_subdev *sd, struct v4l2_async_connection *asc) { … } static int isp_subdev_notifier_complete(struct v4l2_async_notifier *async) { … } static void isp_parse_of_parallel_endpoint(struct device *dev, struct v4l2_fwnode_endpoint *vep, struct isp_bus_cfg *buscfg) { … } static void isp_parse_of_csi2_endpoint(struct device *dev, struct v4l2_fwnode_endpoint *vep, struct isp_bus_cfg *buscfg) { … } static void isp_parse_of_csi1_endpoint(struct device *dev, struct v4l2_fwnode_endpoint *vep, struct isp_bus_cfg *buscfg) { … } static struct { … } isp_bus_interfaces[2] = …; static int isp_parse_of_endpoints(struct isp_device *isp) { … } static const struct v4l2_async_notifier_operations isp_subdev_notifier_ops = …; /* * isp_probe - Probe ISP platform device * @pdev: Pointer to ISP platform device * * Returns 0 if successful, * -ENOMEM if no memory available, * -ENODEV if no platform device resources found * or no space for remapping registers, * -EINVAL if couldn't install ISR, * or clk_get return error value. */ static int isp_probe(struct platform_device *pdev) { … } static const struct dev_pm_ops omap3isp_pm_ops = …; static const struct platform_device_id omap3isp_id_table[] = …; MODULE_DEVICE_TABLE(platform, omap3isp_id_table); static const struct of_device_id omap3isp_of_table[] = …; MODULE_DEVICE_TABLE(of, omap3isp_of_table); static struct platform_driver omap3isp_driver = …; module_platform_driver(…) …; MODULE_AUTHOR(…) …; MODULE_DESCRIPTION(…) …; MODULE_LICENSE(…) …; MODULE_VERSION(…);