Hi sergio, On Wed, Nov 30, 2011 at 06:14:55PM -0600, Sergio Aguirre wrote: > This adds a very simplistic driver to utilize the > CSI2A interface inside the ISS subsystem in OMAP4, > and dump the data to memory. > > Tested so far on omap4430sdp w/ ES2.1 GP & w/ ES2.2 EMU. > > Check newly added Documentation/video4linux/omap4_camera.txt > for details. > > Signed-off-by: Sergio Aguirre <saaguirre@xxxxxx> > --- > Documentation/video4linux/omap4_camera.txt | 60 ++ > arch/arm/plat-omap/include/plat/omap4-iss.h | 42 + Perhaps this could be moved to include/media/omap4iss.h? > drivers/media/video/Kconfig | 13 + > drivers/media/video/Makefile | 1 + > drivers/media/video/omap4iss/Makefile | 6 + > drivers/media/video/omap4iss/iss.c | 1179 ++++++++++++++++++++++++ > drivers/media/video/omap4iss/iss.h | 133 +++ > drivers/media/video/omap4iss/iss_csi2.c | 1324 +++++++++++++++++++++++++++ > drivers/media/video/omap4iss/iss_csi2.h | 166 ++++ > drivers/media/video/omap4iss/iss_csiphy.c | 215 +++++ > drivers/media/video/omap4iss/iss_csiphy.h | 69 ++ > drivers/media/video/omap4iss/iss_regs.h | 238 +++++ > drivers/media/video/omap4iss/iss_video.c | 1192 ++++++++++++++++++++++++ > drivers/media/video/omap4iss/iss_video.h | 205 +++++ > 14 files changed, 4843 insertions(+), 0 deletions(-) > create mode 100644 Documentation/video4linux/omap4_camera.txt > create mode 100644 arch/arm/plat-omap/include/plat/omap4-iss.h > create mode 100644 drivers/media/video/omap4iss/Makefile > create mode 100644 drivers/media/video/omap4iss/iss.c > create mode 100644 drivers/media/video/omap4iss/iss.h > create mode 100644 drivers/media/video/omap4iss/iss_csi2.c > create mode 100644 drivers/media/video/omap4iss/iss_csi2.h > create mode 100644 drivers/media/video/omap4iss/iss_csiphy.c > create mode 100644 drivers/media/video/omap4iss/iss_csiphy.h > create mode 100644 drivers/media/video/omap4iss/iss_regs.h > create mode 100644 drivers/media/video/omap4iss/iss_video.c > create mode 100644 drivers/media/video/omap4iss/iss_video.h > > diff --git a/Documentation/video4linux/omap4_camera.txt b/Documentation/video4linux/omap4_camera.txt > new file mode 100644 > index 0000000..a60c80f > --- /dev/null > +++ b/Documentation/video4linux/omap4_camera.txt > @@ -0,0 +1,60 @@ > + OMAP4 ISS Driver > + ================ > + > +Introduction > +------------ > + > +The OMAP44XX family of chips contains the Imaging SubSystem (a.k.a. ISS), > +Which contains several components that can be categorized in 3 big groups: > + > +- Interfaces (2 Interfaces: CSI2-A & CSI2-B/CCP2) > +- ISP (Image Signal Processor) > +- SIMCOP (Still Image Coprocessor) > + > +For more information, please look in [1] for latest version of: > + "OMAP4430 Multimedia Device Silicon Revision 2.x" > + > +As of Revision L, the ISS is described in detail in section 8. > + > +This driver is supporting _only_ the CSI2-A interface for now. > + > +It makes use of the Media Controller framework [2], and inherited most of the > +code from OMAP3 ISP driver (found under drivers/media/video/omap3isp/*), except > +that it doesn't need an IOMMU now for ISS buffers memory mapping. > + > +Supports usage of MMAP buffers only (for now). > + > +IMPORTANT: It is recommended to have this patchset: > + Contiguous Memory Allocator (v15) [3]. > + > +Tested platforms > +---------------- > + > +- OMAP4430SDP, w/ ES2.1 GP & SEVM4430-CAM-V1-0 (Contains IMX060 & OV5640, in > + which only the last one is supported, outputting YUV422 frames). > + > +- PandaBoard, Rev. A2, w/ OMAP4430 ES2.1 GP & OV adapter board, tested with > + following sensors: > + * OV5640 > + * OV5650 > + > +- Tested on mainline kernel: > + > + http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=summary > + > + Tag: v3.0 (commit 02f8c6aee8df3cdc935e9bdd4f2d020306035dbe) > + > +File list > +--------- > +drivers/media/video/omap4iss/ > + > +References > +---------- > + > +[1] http://focus.ti.com/general/docs/wtbu/wtbudocumentcenter.tsp?navigationId=12037&templateId=6123#62 > +[2] http://lwn.net/Articles/420485/ > +[3] http://lwn.net/Articles/455801/ > +-- > +Author: Sergio Aguirre <saaguirre@xxxxxx> > +Copyright (C) 2011, Texas Instruments > + > diff --git a/arch/arm/plat-omap/include/plat/omap4-iss.h b/arch/arm/plat-omap/include/plat/omap4-iss.h > new file mode 100644 > index 0000000..3a1c6b6 > --- /dev/null > +++ b/arch/arm/plat-omap/include/plat/omap4-iss.h > @@ -0,0 +1,42 @@ > +#ifndef ARCH_ARM_PLAT_OMAP4_ISS_H > +#define ARCH_ARM_PLAT_OMAP4_ISS_H > + > +#include <linux/i2c.h> > + > +struct iss_device; > + > +enum iss_interface_type { > + ISS_INTERFACE_CSI2A_PHY1, > +}; > + > +/** > + * struct iss_csi2_platform_data - CSI2 interface platform data > + * @crc: Enable the cyclic redundancy check > + * @vpclk_div: Video port output clock control > + */ > +struct iss_csi2_platform_data { > + unsigned crc:1; > + unsigned vpclk_div:2; > +}; > + > +struct iss_subdev_i2c_board_info { > + struct i2c_board_info *board_info; > + int i2c_adapter_id; > +}; > + > +struct iss_v4l2_subdevs_group { > + struct iss_subdev_i2c_board_info *subdevs; > + enum iss_interface_type interface; > + union { > + struct iss_csi2_platform_data csi2; > + } bus; /* gcc < 4.6.0 chokes on anonymous union initializers */ > +}; > + > +struct iss_platform_data { > + struct iss_v4l2_subdevs_group *subdevs; > + void (*set_constraints)(struct iss_device *iss, bool enable); > +}; If you could describe constraints in the data rather than having the callback, the driver could use the data and moving to DT would be easier. > +extern int omap4_init_camera(struct iss_platform_data *pdata, > + struct omap_board_data *bdata); > +#endif > diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig > index b303a3f..ae2a99d 100644 > --- a/drivers/media/video/Kconfig > +++ b/drivers/media/video/Kconfig > @@ -796,6 +796,19 @@ config VIDEO_OMAP3_DEBUG > ---help--- > Enable debug messages on OMAP 3 camera controller driver. > > +config VIDEO_OMAP4 > + tristate "OMAP 4 Camera support (EXPERIMENTAL)" > + depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API && ARCH_OMAP4 && EXPERIMENTAL > + select VIDEOBUF2_DMA_CONTIG > + ---help--- > + Driver for an OMAP 4 ISS controller. > + > +config VIDEO_OMAP4_DEBUG > + bool "OMAP 4 Camera debug messages" > + depends on VIDEO_OMAP4 > + ---help--- > + Enable debug messages on OMAP 4 ISS controller driver. > + > config SOC_CAMERA > tristate "SoC camera support" > depends on VIDEO_V4L2 && HAS_DMA && I2C > diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile > index 117f9c4..f02a4c4 100644 > --- a/drivers/media/video/Makefile > +++ b/drivers/media/video/Makefile > @@ -140,6 +140,7 @@ obj-$(CONFIG_VIDEO_MMP_CAMERA) += marvell-ccic/ > obj-$(CONFIG_VIDEO_VIA_CAMERA) += via-camera.o > > obj-$(CONFIG_VIDEO_OMAP3) += omap3isp/ > +obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ > > obj-$(CONFIG_USB_ZR364XX) += zr364xx.o > obj-$(CONFIG_USB_STKWEBCAM) += stkwebcam.o > diff --git a/drivers/media/video/omap4iss/Makefile b/drivers/media/video/omap4iss/Makefile > new file mode 100644 > index 0000000..1d3b0a7 > --- /dev/null > +++ b/drivers/media/video/omap4iss/Makefile > @@ -0,0 +1,6 @@ > +# Makefile for OMAP4 ISS driver > + > +omap4-iss-objs += \ > + iss.o iss_csi2.o iss_csiphy.o iss_video.o > + > +obj-$(CONFIG_VIDEO_OMAP4) += omap4-iss.o > diff --git a/drivers/media/video/omap4iss/iss.c b/drivers/media/video/omap4iss/iss.c > new file mode 100644 > index 0000000..255738b > --- /dev/null > +++ b/drivers/media/video/omap4iss/iss.c > @@ -0,0 +1,1179 @@ > +/* > + * V4L2 Driver for OMAP4 ISS > + * > + * Copyright (C) 2011, Texas Instruments > + * > + * Author: Sergio Aguirre <saaguirre@xxxxxx> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + */ > + > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/device.h> > +#include <linux/dma-mapping.h> > +#include <linux/i2c.h> > +#include <linux/interrupt.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/slab.h> > +#include <linux/sched.h> > +#include <linux/vmalloc.h> > + > +#include <media/v4l2-common.h> > +#include <media/v4l2-device.h> > + > +#include "iss.h" > +#include "iss_regs.h" > + > +static void iss_save_ctx(struct iss_device *iss); > + > +static void iss_restore_ctx(struct iss_device *iss); > + > +/* Structure for saving/restoring ISS module registers */ > +static struct iss_reg iss_reg_list[] = { > + {OMAP4_ISS_MEM_TOP, ISS_HL_SYSCONFIG, 0}, > + {OMAP4_ISS_MEM_TOP, ISS_CTRL, 0}, > + {OMAP4_ISS_MEM_TOP, ISS_CLKCTRL, 0}, > + {0, ISS_TOK_TERM, 0} > +}; > + > +/* > + * omap4iss_flush - Post pending L3 bus writes by doing a register readback > + * @iss: OMAP4 ISS 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: > + * http://www.mail-archive.com/linux-omap@xxxxxxxxxxxxxxx/msg08149.html > + */ > +void omap4iss_flush(struct iss_device *iss) > +{ > + writel(0, iss->regs[OMAP4_ISS_MEM_TOP] + ISS_HL_REVISION); > + readl(iss->regs[OMAP4_ISS_MEM_TOP] + ISS_HL_REVISION); > +} > + > +/* > + * iss_enable_interrupts - Enable ISS interrupts. > + * @iss: OMAP4 ISS device > + */ > +static void iss_enable_interrupts(struct iss_device *iss) > +{ > + static const u32 irq = ISS_HL_IRQ_CSIA; > + > + /* Enable HL interrupts */ > + writel(irq, iss->regs[OMAP4_ISS_MEM_TOP] + ISS_HL_IRQSTATUS_5); > + writel(irq, iss->regs[OMAP4_ISS_MEM_TOP] + ISS_HL_IRQENABLE_5_SET); > +} > + > +/* > + * iss_disable_interrupts - Disable ISS interrupts. > + * @iss: OMAP4 ISS device > + */ > +static void iss_disable_interrupts(struct iss_device *iss) > +{ > + writel(-1, iss->regs[OMAP4_ISS_MEM_TOP] + ISS_HL_IRQENABLE_5_CLR); > +} > + > +static inline void iss_isr_dbg(struct iss_device *iss, u32 irqstatus) > +{ > + static const char *name[] = { > + "ISP_IRQ0", > + "ISP_IRQ1", > + "ISP_IRQ2", > + "ISP_IRQ3", > + "CSIA_IRQ", > + "CSIB_IRQ", > + "CCP2_IRQ0", > + "CCP2_IRQ1", > + "CCP2_IRQ2", > + "CCP2_IRQ3", > + "CBUFF_IRQ", > + "BTE_IRQ", > + "SIMCOP_IRQ0", > + "SIMCOP_IRQ1", > + "SIMCOP_IRQ2", > + "SIMCOP_IRQ3", > + "CCP2_IRQ8", > + "HS_VS_IRQ", > + "res18", > + "res19", > + "res20", > + "res21", > + "res22", > + "res23", > + "res24", > + "res25", > + "res26", > + "res27", > + "res28", > + "res29", > + "res30", > + "res31", > + }; > + int i; > + > + dev_dbg(iss->dev, "ISS IRQ: "); > + > + for (i = 0; i < ARRAY_SIZE(name); i++) { > + if ((1 << i) & irqstatus) > + printk(KERN_CONT "%s ", name[i]); > + } > + printk(KERN_CONT "\n"); pr_cont might be prettier, but I guess it's mostly the matter of personal preference. > +} > + > +/* > + * iss_isr - Interrupt Service Routine for ISS module. > + * @irq: Not used currently. > + * @_iss: Pointer to the OMAP4 ISS device > + * > + * Handles the corresponding callback if plugged in. > + * > + * Returns IRQ_HANDLED when IRQ was correctly handled, or IRQ_NONE when the > + * IRQ wasn't handled. > + */ > +static irqreturn_t iss_isr(int irq, void *_iss) > +{ > + struct iss_device *iss = _iss; > + u32 irqstatus; > + > + irqstatus = readl(iss->regs[OMAP4_ISS_MEM_TOP] + ISS_HL_IRQSTATUS_5); > + writel(irqstatus, iss->regs[OMAP4_ISS_MEM_TOP] + ISS_HL_IRQSTATUS_5); > + > + if (irqstatus & ISS_HL_IRQ_CSIA) > + omap4iss_csi2_isr(&iss->csi2a); > + > + omap4iss_flush(iss); > + > +#if defined(DEBUG) && defined(ISS_ISR_DEBUG) > + iss_isr_dbg(iss, irqstatus); > +#endif > + > + return IRQ_HANDLED; > +} > + > +/* ----------------------------------------------------------------------------- > + * Pipeline power management > + * > + * Entities must be powered up when part of a pipeline that contains at least > + * one open video device node. > + * > + * To achieve this use the entity use_count field to track the number of users. > + * For entities corresponding to video device nodes the use_count field stores > + * the users count of the node. For entities corresponding to subdevs the > + * use_count field stores the total number of users of all video device nodes > + * in the pipeline. > + * > + * The omap4iss_pipeline_pm_use() function must be called in the open() and > + * close() handlers of video device nodes. It increments or decrements the use > + * count of all subdev entities in the pipeline. > + * > + * To react to link management on powered pipelines, the link setup notification > + * callback updates the use count of all entities in the source and sink sides > + * of the link. > + */ > + > +/* > + * iss_pipeline_pm_use_count - Count the number of users of a pipeline > + * @entity: The entity > + * > + * Return the total number of users of all video device nodes in the pipeline. > + */ > +static int iss_pipeline_pm_use_count(struct media_entity *entity) > +{ > + struct media_entity_graph graph; > + int use = 0; > + > + media_entity_graph_walk_start(&graph, entity); > + > + while ((entity = media_entity_graph_walk_next(&graph))) { > + if (media_entity_type(entity) == MEDIA_ENT_T_DEVNODE) > + use += entity->use_count; > + } > + > + return use; > +} > + > +/* > + * iss_pipeline_pm_power_one - Apply power change to an entity > + * @entity: The entity > + * @change: Use count change > + * > + * Change the entity use count by @change. If the entity is a subdev update its > + * power state by calling the core::s_power operation when the use count goes > + * from 0 to != 0 or from != 0 to 0. > + * > + * Return 0 on success or a negative error code on failure. > + */ > +static int iss_pipeline_pm_power_one(struct media_entity *entity, int change) > +{ > + struct v4l2_subdev *subdev; > + > + subdev = media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV > + ? media_entity_to_v4l2_subdev(entity) : NULL; > + > + if (entity->use_count == 0 && change > 0 && subdev != NULL) { > + int ret; > + > + ret = v4l2_subdev_call(subdev, core, s_power, 1); > + if (ret < 0 && ret != -ENOIOCTLCMD) > + return ret; > + } > + > + entity->use_count += change; > + WARN_ON(entity->use_count < 0); > + > + if (entity->use_count == 0 && change < 0 && subdev != NULL) > + v4l2_subdev_call(subdev, core, s_power, 0); > + > + return 0; > +} > + > +/* > + * iss_pipeline_pm_power - Apply power change to all entities in a pipeline > + * @entity: The entity > + * @change: Use count change > + * > + * Walk the pipeline to update the use count and the power state of all non-node > + * entities. > + * > + * Return 0 on success or a negative error code on failure. > + */ > +static int iss_pipeline_pm_power(struct media_entity *entity, int change) > +{ > + struct media_entity_graph graph; > + struct media_entity *first = entity; > + int ret = 0; > + > + if (!change) > + return 0; > + > + media_entity_graph_walk_start(&graph, entity); > + > + while (!ret && (entity = media_entity_graph_walk_next(&graph))) > + if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE) > + ret = iss_pipeline_pm_power_one(entity, change); > + > + if (!ret) > + return 0; > + > + media_entity_graph_walk_start(&graph, first); > + > + while ((first = media_entity_graph_walk_next(&graph)) > + && first != entity) > + if (media_entity_type(first) != MEDIA_ENT_T_DEVNODE) > + iss_pipeline_pm_power_one(first, -change); > + > + return ret; > +} > + > +/* > + * omap4iss_pipeline_pm_use - Update the use count of an entity > + * @entity: The entity > + * @use: Use (1) or stop using (0) the entity > + * > + * Update the use count of all entities in the pipeline and power entities on or > + * off accordingly. > + * > + * Return 0 on success or a negative error code on failure. Powering entities > + * off is assumed to never fail. No failure can occur when the use parameter is > + * set to 0. > + */ > +int omap4iss_pipeline_pm_use(struct media_entity *entity, int use) > +{ > + int change = use ? 1 : -1; > + int ret; > + > + mutex_lock(&entity->parent->graph_mutex); > + > + /* Apply use count to node. */ > + entity->use_count += change; > + WARN_ON(entity->use_count < 0); > + > + /* Apply power change to connected non-nodes. */ > + ret = iss_pipeline_pm_power(entity, change); > + if (ret < 0) > + entity->use_count -= change; > + > + mutex_unlock(&entity->parent->graph_mutex); > + > + return ret; > +} > + > +/* > + * iss_pipeline_link_notify - Link management notification callback > + * @source: Pad at the start of the link > + * @sink: Pad at the end of the link > + * @flags: New link flags that will be applied > + * > + * React to link management on powered pipelines by updating the use count of > + * all entities in the source and sink sides of the link. Entities are powered > + * on or off accordingly. > + * > + * Return 0 on success or a negative error code on failure. Powering entities > + * off is assumed to never fail. This function will not fail for disconnection > + * events. > + */ > +static int iss_pipeline_link_notify(struct media_pad *source, > + struct media_pad *sink, u32 flags) > +{ > + int source_use = iss_pipeline_pm_use_count(source->entity); > + int sink_use = iss_pipeline_pm_use_count(sink->entity); > + int ret; > + > + if (!(flags & MEDIA_LNK_FL_ENABLED)) { > + /* Powering off entities is assumed to never fail. */ > + iss_pipeline_pm_power(source->entity, -sink_use); > + iss_pipeline_pm_power(sink->entity, -source_use); > + return 0; > + } > + > + ret = iss_pipeline_pm_power(source->entity, sink_use); > + if (ret < 0) > + return ret; > + > + ret = iss_pipeline_pm_power(sink->entity, source_use); > + if (ret < 0) > + iss_pipeline_pm_power(source->entity, -sink_use); > + > + return ret; > +} > + > +/* ----------------------------------------------------------------------------- > + * Pipeline stream management > + */ > + > +/* > + * iss_pipeline_enable - Enable streaming on a pipeline > + * @pipe: ISS 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 iss_pipeline_enable(struct iss_pipeline *pipe, > + enum iss_pipeline_stream_state mode) > +{ > + struct media_entity *entity; > + struct media_pad *pad; > + struct v4l2_subdev *subdev; > + unsigned long flags; > + int ret = 0; > + > + spin_lock_irqsave(&pipe->lock, flags); > + pipe->state &= ~(ISS_PIPELINE_IDLE_INPUT | ISS_PIPELINE_IDLE_OUTPUT); > + spin_unlock_irqrestore(&pipe->lock, flags); > + > + pipe->do_propagation = false; > + > + entity = &pipe->output->video.entity; > + while (1) { > + pad = &entity->pads[0]; > + if (!(pad->flags & MEDIA_PAD_FL_SINK)) > + break; > + > + pad = media_entity_remote_source(pad); > + if (pad == NULL || > + media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV) > + break; > + > + entity = pad->entity; > + subdev = media_entity_to_v4l2_subdev(entity); > + > + ret = v4l2_subdev_call(subdev, video, s_stream, mode); > + if (ret < 0 && ret != -ENOIOCTLCMD) > + break; > + } > + > + return ret; > +} > + > +/* > + * iss_pipeline_disable - Disable streaming on a pipeline > + * @pipe: ISS 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. > + */ > +static int iss_pipeline_disable(struct iss_pipeline *pipe) > +{ > + struct media_entity *entity; > + struct media_pad *pad; > + struct v4l2_subdev *subdev; > + int failure = 0; > + > + entity = &pipe->output->video.entity; > + while (1) { > + pad = &entity->pads[0]; > + if (!(pad->flags & MEDIA_PAD_FL_SINK)) > + break; > + > + pad = media_entity_remote_source(pad); > + if (pad == NULL || > + media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV) > + break; > + > + entity = pad->entity; > + subdev = media_entity_to_v4l2_subdev(entity); > + > + v4l2_subdev_call(subdev, video, s_stream, 0); > + } > + > + return failure; > +} > + > +/* > + * omap4iss_pipeline_set_stream - Enable/disable streaming on a pipeline > + * @pipe: ISS 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 omap4iss_pipeline_set_stream(struct iss_pipeline *pipe, > + enum iss_pipeline_stream_state state) > +{ > + int ret; > + > + if (state == ISS_PIPELINE_STREAM_STOPPED) > + ret = iss_pipeline_disable(pipe); > + else > + ret = iss_pipeline_enable(pipe, state); > + > + if (ret == 0 || state == ISS_PIPELINE_STREAM_STOPPED) > + pipe->stream_state = state; > + > + return ret; > +} > + > +/* > + * iss_pipeline_is_last - Verify if entity has an enabled link to the output > + * video node > + * @me: ISS 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 iss_pipeline_is_last(struct media_entity *me) > +{ > + struct iss_pipeline *pipe; > + struct media_pad *pad; > + > + if (!me->pipe) > + return 0; > + pipe = to_iss_pipeline(me); > + if (pipe->stream_state == ISS_PIPELINE_STREAM_STOPPED) > + return 0; > + pad = media_entity_remote_source(&pipe->output->pad); > + return pad->entity == me; > +} > + > +static int iss_reset(struct iss_device *iss) > +{ > + unsigned long timeout = 0; > + > + writel(readl(iss->regs[OMAP4_ISS_MEM_TOP] + ISS_HL_SYSCONFIG) | > + ISS_HL_SYSCONFIG_SOFTRESET, > + iss->regs[OMAP4_ISS_MEM_TOP] + ISS_HL_SYSCONFIG); > + > + while (readl(iss->regs[OMAP4_ISS_MEM_TOP] + ISS_HL_SYSCONFIG) & > + ISS_HL_SYSCONFIG_SOFTRESET) { > + if (timeout++ > 10000) { > + dev_alert(iss->dev, "cannot reset ISS\n"); > + return -ETIMEDOUT; > + } > + udelay(1); > + } > + > + return 0; > +} > + > +/* > + * iss_save_context - Saves the values of the ISS module registers. > + * @iss: OMAP4 ISS device > + * @reg_list: Structure containing pairs of register address and value to > + * modify on OMAP. > + */ > +static void > +iss_save_context(struct iss_device *iss, struct iss_reg *reg_list) > +{ > + struct iss_reg *next = reg_list; > + > + for (; next->reg != ISS_TOK_TERM; next++) > + next->val = readl(iss->regs[next->mmio_range] + next->reg); > +} > + > +/* > + * iss_restore_context - Restores the values of the ISS module registers. > + * @iss: OMAP4 ISS device > + * @reg_list: Structure containing pairs of register address and value to > + * modify on OMAP. > + */ > +static void > +iss_restore_context(struct iss_device *iss, struct iss_reg *reg_list) > +{ > + struct iss_reg *next = reg_list; > + > + for (; next->reg != ISS_TOK_TERM; next++) > + writel(next->val, iss->regs[next->mmio_range] + next->reg); > +} > + > +/* > + * iss_save_ctx - Saves ISS context. > + * @iss: OMAP4 ISS device > + * > + * Routine for saving the context of each module in the ISS. > + */ > +static void iss_save_ctx(struct iss_device *iss) > +{ > + iss_save_context(iss, iss_reg_list); > +} > + > +/* > + * iss_restore_ctx - Restores ISS context. > + * @iss: OMAP4 ISS device > + * > + * Routine for restoring the context of each module in the ISS. > + */ > +static void iss_restore_ctx(struct iss_device *iss) > +{ > + iss_restore_context(iss, iss_reg_list); > +} > + > +/* > + * iss_module_sync_idle - Helper to sync module with its idle state > + * @me: ISS submodule's media entity > + * @wait: ISS submodule's wait queue for streamoff/interrupt synchronization > + * @stopping: flag which tells module wants to stop > + * > + * This function checks if ISS submodule needs to wait for next interrupt. If > + * yes, makes the caller to sleep while waiting for such event. > + */ > +int omap4iss_module_sync_idle(struct media_entity *me, wait_queue_head_t *wait, > + atomic_t *stopping) > +{ > + struct iss_pipeline *pipe = to_iss_pipeline(me); > + > + if (pipe->stream_state == ISS_PIPELINE_STREAM_STOPPED || > + (pipe->stream_state == ISS_PIPELINE_STREAM_SINGLESHOT && > + !iss_pipeline_ready(pipe))) > + return 0; > + > + /* > + * atomic_set() doesn't include memory barrier on ARM platform for SMP > + * scenario. We'll call it here to avoid race conditions. > + */ > + atomic_set(stopping, 1); > + smp_mb(); It was new to me atomic_t requires memory barriers. But shouldn't this be wmb() instead? > + > + /* > + * If module is the last one, it's writing to memory. In this case, > + * it's necessary to check if the module is already paused due to > + * DMA queue underrun or if it has to wait for next interrupt to be > + * idle. > + * If it isn't the last one, the function won't sleep but *stopping > + * will still be set to warn next submodule caller's interrupt the > + * module wants to be idle. > + */ > + if (iss_pipeline_is_last(me)) { You could return here if the function returns zero. > + struct iss_video *video = pipe->output; > + unsigned long flags; > + > + spin_lock_irqsave(&video->qlock, flags); > + if (video->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) { > + spin_unlock_irqrestore(&video->qlock, flags); > + atomic_set(stopping, 0); > + smp_mb(); > + return 0; > + } > + spin_unlock_irqrestore(&video->qlock, flags); > + if (!wait_event_timeout(*wait, !atomic_read(stopping), > + msecs_to_jiffies(1000))) { > + atomic_set(stopping, 0); > + smp_mb(); > + return -ETIMEDOUT; > + } > + } > + > + return 0; > +} > + > +/* > + * omap4iss_module_sync_is_stopped - Helper to verify if module was stopping > + * @wait: ISS submodule's wait queue for streamoff/interrupt synchronization > + * @stopping: flag which tells module wants to stop > + * > + * This function checks if ISS 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 omap4iss_module_sync_is_stopping(wait_queue_head_t *wait, > + atomic_t *stopping) > +{ > + if (atomic_cmpxchg(stopping, 1, 0)) { > + wake_up(wait); > + return 1; > + } > + > + return 0; > +} > + > +/* -------------------------------------------------------------------------- > + * Clock management > + */ > + > +#define ISS_CLKCTRL_MASK (ISS_CLKCTRL_CSI2_A) > + > +static int __iss_subclk_update(struct iss_device *iss) > +{ > + u32 clk = 0; > + int ret = 0, timeout = 1000; > + > + if (iss->subclk_resources & OMAP4_ISS_SUBCLK_CSI2_A) > + clk |= ISS_CLKCTRL_CSI2_A; > + > + writel((readl(iss->regs[OMAP4_ISS_MEM_TOP] + ISS_CLKCTRL) & > + ~ISS_CLKCTRL_MASK) | clk, > + iss->regs[OMAP4_ISS_MEM_TOP] + ISS_CLKCTRL); > + > + /* Wait for HW assertion */ > + while (timeout-- > 0) { > + udelay(1); > + if ((readl(iss->regs[OMAP4_ISS_MEM_TOP] + ISS_CLKSTAT) & > + ISS_CLKCTRL_MASK) == clk) > + break; > + } > + > + if (!timeout) > + ret = -EBUSY; > + > + return ret; > +} > + > +int omap4iss_subclk_enable(struct iss_device *iss, > + enum iss_subclk_resource res) > +{ > + iss->subclk_resources |= res; > + > + return __iss_subclk_update(iss); > +} > + > +int omap4iss_subclk_disable(struct iss_device *iss, > + enum iss_subclk_resource res) > +{ > + iss->subclk_resources &= ~res; > + > + return __iss_subclk_update(iss); > +} > + > +/* > + * iss_enable_clocks - Enable ISS clocks > + * @iss: OMAP4 ISS device > + * > + * Return 0 if successful, or clk_enable return value if any of tthem fails. > + */ > +static int iss_enable_clocks(struct iss_device *iss) > +{ > + int r; > + > + r = clk_enable(iss->iss_fck); > + if (r) { > + dev_err(iss->dev, "clk_enable iss_fck failed\n"); > + goto out_clk_enable_fck; > + } > + > + r = clk_enable(iss->iss_ctrlclk); > + if (r) { > + dev_err(iss->dev, "clk_enable iss_ctrlclk failed\n"); > + goto out_clk_enable_ctrlclk; > + } > + return 0; > + > +out_clk_enable_ctrlclk: > + clk_disable(iss->iss_fck); > +out_clk_enable_fck: > + return r; > +} > + > +/* > + * iss_disable_clocks - Disable ISS clocks > + * @iss: OMAP4 ISS device > + */ > +static void iss_disable_clocks(struct iss_device *iss) > +{ > + clk_disable(iss->iss_ctrlclk); > + clk_disable(iss->iss_fck); > +} > + > +static void iss_put_clocks(struct iss_device *iss) > +{ > + if (iss->iss_fck) { > + clk_put(iss->iss_fck); > + iss->iss_fck = NULL; > + } > + > + if (iss->iss_ctrlclk) { > + clk_put(iss->iss_ctrlclk); > + iss->iss_ctrlclk = NULL; > + } > +} > + > +static int iss_get_clocks(struct iss_device *iss) > +{ > + iss->iss_fck = clk_get(iss->dev, "iss_fck"); > + if (IS_ERR(iss->iss_fck)) { > + dev_err(iss->dev, "Unable to get iss_fck clock info\n"); > + iss_put_clocks(iss); > + return PTR_ERR(iss->iss_fck); > + } > + > + iss->iss_ctrlclk = clk_get(iss->dev, "iss_ctrlclk"); > + if (IS_ERR(iss->iss_ctrlclk)) { > + dev_err(iss->dev, "Unable to get iss_ctrlclk clock info\n"); > + iss_put_clocks(iss); > + return PTR_ERR(iss->iss_fck); > + } > + > + return 0; > +} > + > +/* > + * omap4iss_get - Acquire the ISS resource. > + * > + * Initializes the clocks for the first acquire. > + * > + * Increment the reference count on the ISS. If the first reference is taken, > + * enable clocks and power-up all submodules. > + * > + * Return a pointer to the ISS device structure, or NULL if an error occurred. > + */ > +struct iss_device *omap4iss_get(struct iss_device *iss) > +{ > + struct iss_device *__iss = iss; > + > + if (iss == NULL) > + return NULL; > + > + mutex_lock(&iss->iss_mutex); > + if (iss->ref_count > 0) > + goto out; > + > + if (iss_enable_clocks(iss) < 0) { > + __iss = NULL; > + goto out; > + } > + > + /* We don't want to restore context before saving it! */ > + if (iss->has_context) > + iss_restore_ctx(iss); > + else > + iss->has_context = 1; > + > + iss_enable_interrupts(iss); > + > +out: > + if (__iss != NULL) > + iss->ref_count++; > + mutex_unlock(&iss->iss_mutex); > + > + return __iss; > +} > + > +/* > + * omap4iss_put - Release the ISS > + * > + * Decrement the reference count on the ISS. If the last reference is released, > + * power-down all submodules, disable clocks and free temporary buffers. > + */ > +void omap4iss_put(struct iss_device *iss) > +{ > + if (iss == NULL) > + return; > + > + mutex_lock(&iss->iss_mutex); > + BUG_ON(iss->ref_count == 0); > + if (--iss->ref_count == 0) { > + iss_disable_interrupts(iss); > + iss_save_ctx(iss); > + iss_disable_clocks(iss); > + } > + mutex_unlock(&iss->iss_mutex); > +} > + > +static int iss_map_mem_resource(struct platform_device *pdev, > + struct iss_device *iss, > + enum iss_mem_resources res) > +{ > + struct resource *mem; > + > + /* request the mem region for the camera registers */ > + > + mem = platform_get_resource(pdev, IORESOURCE_MEM, res); > + if (!mem) { > + dev_err(iss->dev, "no mem resource?\n"); > + return -ENODEV; > + } > + > + if (!request_mem_region(mem->start, resource_size(mem), pdev->name)) { > + dev_err(iss->dev, > + "cannot reserve camera register I/O region\n"); > + return -ENODEV; > + } > + iss->res[res] = mem; > + > + /* map the region */ > + iss->regs[res] = ioremap_nocache(mem->start, resource_size(mem)); > + if (!iss->regs[res]) { > + dev_err(iss->dev, "cannot map camera register I/O region\n"); > + return -ENODEV; > + } > + > + return 0; > +} > + > +static void iss_unregister_entities(struct iss_device *iss) > +{ > + omap4iss_csi2_unregister_entities(&iss->csi2a); > + > + v4l2_device_unregister(&iss->v4l2_dev); > + media_device_unregister(&iss->media_dev); > +} > + > +/* > + * iss_register_subdev_group - Register a group of subdevices > + * @iss: OMAP4 ISS device > + * @board_info: I2C subdevs board information array > + * > + * Register all I2C subdevices in the board_info array. The array must be > + * terminated by a NULL entry, and the first entry must be the sensor. > + * > + * Return a pointer to the sensor media entity if it has been successfully > + * registered, or NULL otherwise. > + */ > +static struct v4l2_subdev * > +iss_register_subdev_group(struct iss_device *iss, > + struct iss_subdev_i2c_board_info *board_info) > +{ > + struct v4l2_subdev *sensor = NULL; > + unsigned int first; > + > + if (board_info->board_info == NULL) > + return NULL; > + > + for (first = 1; board_info->board_info; ++board_info, first = 0) { > + struct v4l2_subdev *subdev; > + struct i2c_adapter *adapter; > + > + adapter = i2c_get_adapter(board_info->i2c_adapter_id); > + if (adapter == NULL) { > + printk(KERN_ERR "%s: Unable to get I2C adapter %d for " > + "device %s\n", __func__, > + board_info->i2c_adapter_id, > + board_info->board_info->type); > + continue; > + } > + > + subdev = v4l2_i2c_new_subdev_board(&iss->v4l2_dev, adapter, > + board_info->board_info, NULL); > + if (subdev == NULL) { > + printk(KERN_ERR "%s: Unable to register subdev %s\n", > + __func__, board_info->board_info->type); > + continue; > + } > + > + if (first) > + sensor = subdev; > + } > + > + return sensor; > +} > + > +static int iss_register_entities(struct iss_device *iss) > +{ > + struct iss_platform_data *pdata = iss->pdata; > + struct iss_v4l2_subdevs_group *subdevs; > + int ret; > + > + iss->media_dev.dev = iss->dev; > + strlcpy(iss->media_dev.model, "TI OMAP4 ISS", > + sizeof(iss->media_dev.model)); > + iss->media_dev.link_notify = iss_pipeline_link_notify; > + ret = media_device_register(&iss->media_dev); > + if (ret < 0) { > + printk(KERN_ERR "%s: Media device registration failed (%d)\n", > + __func__, ret); > + return ret; > + } > + > + iss->v4l2_dev.mdev = &iss->media_dev; > + ret = v4l2_device_register(iss->dev, &iss->v4l2_dev); > + if (ret < 0) { > + printk(KERN_ERR "%s: V4L2 device registration failed (%d)\n", > + __func__, ret); > + goto done; > + } > + > + /* Register internal entities */ > + ret = omap4iss_csi2_register_entities(&iss->csi2a, &iss->v4l2_dev); > + if (ret < 0) > + goto done; > + > + /* Register external entities */ > + for (subdevs = pdata->subdevs; subdevs && subdevs->subdevs; ++subdevs) { > + struct v4l2_subdev *sensor; > + struct media_entity *input; > + unsigned int flags; > + unsigned int pad; > + > + sensor = iss_register_subdev_group(iss, subdevs->subdevs); > + if (sensor == NULL) > + continue; > + > + sensor->host_priv = subdevs; > + > + /* Connect the sensor to the correct interface module. > + * CSI2a receiver through CSIPHY1. > + */ > + switch (subdevs->interface) { > + case ISS_INTERFACE_CSI2A_PHY1: > + input = &iss->csi2a.subdev.entity; > + pad = CSI2_PAD_SINK; > + flags = MEDIA_LNK_FL_IMMUTABLE > + | MEDIA_LNK_FL_ENABLED; > + break; > + > + default: > + printk(KERN_ERR "%s: invalid interface type %u\n", > + __func__, subdevs->interface); > + ret = -EINVAL; > + goto done; > + } > + > + ret = media_entity_create_link(&sensor->entity, 0, input, pad, > + flags); > + if (ret < 0) > + goto done; > + } > + > + ret = v4l2_device_register_subdev_nodes(&iss->v4l2_dev); > + > +done: > + if (ret < 0) > + iss_unregister_entities(iss); > + > + return ret; > +} > + > +static void iss_cleanup_modules(struct iss_device *iss) > +{ > + omap4iss_csi2_cleanup(iss); > +} > + > +static int iss_initialize_modules(struct iss_device *iss) > +{ > + int ret; > + > + ret = omap4iss_csiphy_init(iss); > + if (ret < 0) { > + dev_err(iss->dev, "CSI PHY initialization failed\n"); > + goto error_csiphy; > + } > + > + ret = omap4iss_csi2_init(iss); > + if (ret < 0) { > + dev_err(iss->dev, "CSI2 initialization failed\n"); > + goto error_csi2; > + } > + > + return 0; > + > +error_csi2: > +error_csiphy: > + return ret; > +} > + > +static int iss_probe(struct platform_device *pdev) > +{ > + struct iss_platform_data *pdata = pdev->dev.platform_data; > + struct iss_device *iss; > + int i, ret; > + > + if (pdata == NULL) > + return -EINVAL; > + > + iss = kzalloc(sizeof(*iss), GFP_KERNEL); > + if (!iss) { > + dev_err(&pdev->dev, "Could not allocate memory\n"); > + return -ENOMEM; > + } > + > + mutex_init(&iss->iss_mutex); > + > + iss->dev = &pdev->dev; > + iss->pdata = pdata; > + iss->ref_count = 0; > + > + iss->raw_dmamask = DMA_BIT_MASK(32); > + iss->dev->dma_mask = &iss->raw_dmamask; > + iss->dev->coherent_dma_mask = DMA_BIT_MASK(32); > + > + platform_set_drvdata(pdev, iss); > + > + /* Clocks */ > + ret = iss_map_mem_resource(pdev, iss, OMAP4_ISS_MEM_TOP); > + if (ret < 0) > + goto error; > + > + ret = iss_get_clocks(iss); > + if (ret < 0) > + goto error; > + > + if (omap4iss_get(iss) == NULL) > + goto error; > + > + ret = iss_reset(iss); > + if (ret < 0) > + goto error_iss; > + > + iss->revision = readl(iss->regs[OMAP4_ISS_MEM_TOP] + ISS_HL_REVISION); > + dev_info(iss->dev, "Revision %08x found\n", iss->revision); > + > + for (i = 1; i < OMAP4_ISS_MEM_LAST; i++) { > + ret = iss_map_mem_resource(pdev, iss, i); > + if (ret) > + goto error_iss; > + } > + > + /* Interrupt */ > + iss->irq_num = platform_get_irq(pdev, 0); > + if (iss->irq_num <= 0) { > + dev_err(iss->dev, "No IRQ resource\n"); > + ret = -ENODEV; > + goto error_iss; > + } > + > + if (request_irq(iss->irq_num, iss_isr, IRQF_SHARED, "OMAP4 ISS", iss)) { > + dev_err(iss->dev, "Unable to request IRQ\n"); > + ret = -EINVAL; > + goto error_iss; > + } > + > + /* Entities */ > + ret = iss_initialize_modules(iss); > + if (ret < 0) > + goto error_irq; > + > + ret = iss_register_entities(iss); > + if (ret < 0) > + goto error_modules; > + > + omap4iss_put(iss); > + > + return 0; > + > +error_modules: > + iss_cleanup_modules(iss); > +error_irq: > + free_irq(iss->irq_num, iss); > +error_iss: > + omap4iss_put(iss); > +error: > + iss_put_clocks(iss); > + > + for (i = 0; i < OMAP4_ISS_MEM_LAST; i++) { > + if (iss->regs[i]) { > + iounmap(iss->regs[i]); > + iss->regs[i] = NULL; > + } > + > + if (iss->res[i]) { > + release_mem_region(iss->res[i]->start, > + resource_size(iss->res[i])); > + iss->res[i] = NULL; > + } > + } > + platform_set_drvdata(pdev, NULL); > + kfree(iss); > + > + return ret; > +} > + > +static int iss_remove(struct platform_device *pdev) > +{ > + struct iss_device *iss = platform_get_drvdata(pdev); > + int i; > + > + iss_unregister_entities(iss); > + iss_cleanup_modules(iss); > + > + free_irq(iss->irq_num, iss); > + iss_put_clocks(iss); > + > + for (i = 0; i < OMAP4_ISS_MEM_LAST; i++) { > + if (iss->regs[i]) { > + iounmap(iss->regs[i]); > + iss->regs[i] = NULL; > + } > + > + if (iss->res[i]) { > + release_mem_region(iss->res[i]->start, > + resource_size(iss->res[i])); > + iss->res[i] = NULL; > + } > + } > + > + kfree(iss); > + > + return 0; > +} > + > +static struct platform_device_id omap4iss_id_table[] = { > + { "omap4iss", 0 }, > + { }, > +}; > +MODULE_DEVICE_TABLE(platform, omap4iss_id_table); > + > +static struct platform_driver iss_driver = { > + .probe = iss_probe, > + .remove = iss_remove, > + .id_table = omap4iss_id_table, > + .driver = { > + .owner = THIS_MODULE, > + .name = "omap4iss", > + }, > +}; > + > +static int __init iss_init(void) > +{ > + return platform_driver_register(&iss_driver); > +} > + > +static void __exit iss_exit(void) > +{ > + platform_driver_unregister(&iss_driver); > +} > + > +/* > + * FIXME: Had to make it late_initcall. Strangely while being module_init, > + * The I2C communication was failing in the sensor, because no XCLK was > + * provided. > + */ > +late_initcall(iss_init); > +module_exit(iss_exit); > + > +MODULE_DESCRIPTION("TI OMAP4 ISS driver"); > +MODULE_AUTHOR("Sergio Aguirre <saaguirre@xxxxxx>"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/media/video/omap4iss/iss.h b/drivers/media/video/omap4iss/iss.h > new file mode 100644 > index 0000000..8346c80 > --- /dev/null > +++ b/drivers/media/video/omap4iss/iss.h > @@ -0,0 +1,133 @@ > +/* > + * iss.h > + * > + * Copyright (C) 2011 Texas Instruments. > + * > + * Author: Sergio Aguirre <saaguirre@xxxxxx> > + */ > + > +#ifndef _OMAP4_ISS_H_ > +#define _OMAP4_ISS_H_ > + > +#include <media/v4l2-device.h> > +#include <linux/device.h> > +#include <linux/io.h> > +#include <linux/platform_device.h> > +#include <linux/wait.h> > + > +#include <plat/omap4-iss.h> > + > +#include "iss_regs.h" > +#include "iss_csiphy.h" > +#include "iss_csi2.h" > + > +#define ISS_TOK_TERM 0xFFFFFFFF /* > + * terminating token for ISS > + * modules reg list > + */ > +#define to_iss_device(ptr_module) \ > + container_of(ptr_module, struct iss_device, ptr_module) > +#define to_device(ptr_module) \ > + (to_iss_device(ptr_module)->dev) > + > +enum iss_mem_resources { > + OMAP4_ISS_MEM_TOP, > + OMAP4_ISS_MEM_CSI2_A_REGS1, > + OMAP4_ISS_MEM_CAMERARX_CORE1, > + OMAP4_ISS_MEM_LAST, > +}; > + > +enum iss_subclk_resource { > + OMAP4_ISS_SUBCLK_SIMCOP = (1 << 0), > + OMAP4_ISS_SUBCLK_ISP = (1 << 1), > + OMAP4_ISS_SUBCLK_CSI2_A = (1 << 2), > + OMAP4_ISS_SUBCLK_CSI2_B = (1 << 3), > + OMAP4_ISS_SUBCLK_CCP2 = (1 << 4), > +}; > + > +/* > + * struct iss_reg - Structure for ISS register values. > + * @reg: 32-bit Register address. > + * @val: 32-bit Register value. > + */ > +struct iss_reg { > + enum iss_mem_resources mmio_range; > + u32 reg; > + u32 val; > +}; > + > +struct iss_platform_callback { > + int (*csiphy_config)(struct iss_csiphy *phy, > + struct iss_csiphy_dphy_cfg *dphy, > + struct iss_csiphy_lanes_cfg *lanes); > +}; > + > +struct iss_device { > + struct v4l2_device v4l2_dev; > + struct media_device media_dev; > + struct device *dev; > + u32 revision; > + > + /* platform HW resources */ > + struct iss_platform_data *pdata; > + unsigned int irq_num; > + > + struct resource *res[OMAP4_ISS_MEM_LAST]; > + void __iomem *regs[OMAP4_ISS_MEM_LAST]; > + > + u64 raw_dmamask; > + > + struct mutex iss_mutex; /* For handling ref_count field */ > + int has_context; > + int ref_count; > + > + struct clk *iss_fck; > + struct clk *iss_ctrlclk; > + > + /* ISS modules */ > + struct iss_csi2_device csi2a; > + struct iss_csiphy csiphy1; > + > + unsigned int subclk_resources; > + > + struct iss_platform_callback platform_cb; > +}; > + > +#define v4l2_dev_to_iss_device(dev) \ > + container_of(dev, struct iss_device, v4l2_dev) > + > +int omap4iss_module_sync_idle(struct media_entity *me, wait_queue_head_t *wait, > + atomic_t *stopping); > + > +int omap4iss_module_sync_is_stopping(wait_queue_head_t *wait, > + atomic_t *stopping); > + > +int omap4iss_pipeline_set_stream(struct iss_pipeline *pipe, > + enum iss_pipeline_stream_state state); > + > +struct iss_device *omap4iss_get(struct iss_device *iss); > +void omap4iss_put(struct iss_device *iss); > +int omap4iss_subclk_enable(struct iss_device *iss, > + enum iss_subclk_resource res); > +int omap4iss_subclk_disable(struct iss_device *iss, > + enum iss_subclk_resource res); > + > +int omap4iss_pipeline_pm_use(struct media_entity *entity, int use); > + > +int omap4iss_register_entities(struct platform_device *pdev, > + struct v4l2_device *v4l2_dev); > +void omap4iss_unregister_entities(struct platform_device *pdev); > + > +static inline enum v4l2_buf_type > +iss_pad_buffer_type(const struct v4l2_subdev *subdev, int pad) > +{ > + if (pad >= subdev->entity.num_pads) > + return 0; > + > + if (subdev->entity.pads[pad].flags & MEDIA_PAD_FL_SINK) > + return V4L2_BUF_TYPE_VIDEO_OUTPUT; > + else > + return V4L2_BUF_TYPE_VIDEO_CAPTURE; > +} > + > +#endif /* _OMAP4_ISS_H_ */ > diff --git a/drivers/media/video/omap4iss/iss_csi2.c b/drivers/media/video/omap4iss/iss_csi2.c > new file mode 100644 > index 0000000..916d5ef > --- /dev/null > +++ b/drivers/media/video/omap4iss/iss_csi2.c > @@ -0,0 +1,1324 @@ > +/* > + * iss_csi2.c > + * > + * TI OMAP4 ISS - CSI PHY module > + * > + * Copyright (C) 2011 Texas Instruments, Inc. > + * > + * Contacts: Sergio Aguirre <saaguirre@xxxxxx> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > +#include <linux/delay.h> > +#include <media/v4l2-common.h> > +#include <linux/v4l2-mediabus.h> > +#include <linux/mm.h> > + > +#include "iss.h" > +#include "iss_regs.h" > +#include "iss_csi2.h" > + > +/* > + * csi2_if_enable - Enable CSI2 Receiver interface. > + * @enable: enable flag > + * > + */ > +static void csi2_if_enable(struct iss_csi2_device *csi2, u8 enable) > +{ > + struct iss_csi2_ctrl_cfg *currctrl = &csi2->ctrl; > + > + writel((readl(csi2->regs1 + CSI2_CTRL) & ~CSI2_CTRL_IF_EN) | > + (enable ? CSI2_CTRL_IF_EN : 0), > + csi2->regs1 + CSI2_CTRL); > + > + currctrl->if_enable = enable; > +} > + > +/* > + * csi2_recv_config - CSI2 receiver module configuration. > + * @currctrl: iss_csi2_ctrl_cfg structure > + * > + */ > +static void csi2_recv_config(struct iss_csi2_device *csi2, > + struct iss_csi2_ctrl_cfg *currctrl) > +{ > + u32 reg; > + > + reg = readl(csi2->regs1 + CSI2_CTRL); > + > + if (currctrl->frame_mode) > + reg |= CSI2_CTRL_FRAME; > + else > + reg &= ~CSI2_CTRL_FRAME; > + > + if (currctrl->vp_clk_enable) > + reg |= CSI2_CTRL_VP_CLK_EN; > + else > + reg &= ~CSI2_CTRL_VP_CLK_EN; > + > + if (currctrl->vp_only_enable) > + reg |= CSI2_CTRL_VP_ONLY_EN; > + else > + reg &= ~CSI2_CTRL_VP_ONLY_EN; > + > + reg &= ~CSI2_CTRL_VP_OUT_CTRL_MASK; > + reg |= currctrl->vp_out_ctrl << CSI2_CTRL_VP_OUT_CTRL_SHIFT; > + > + if (currctrl->ecc_enable) > + reg |= CSI2_CTRL_ECC_EN; > + else > + reg &= ~CSI2_CTRL_ECC_EN; > + > + /* > + * Set MFlag assertion boundaries to: > + * Low: 4/8 of FIFO size > + * High: 6/8 of FIFO size > + */ > + reg &= ~(CSI2_CTRL_MFLAG_LEVH_MASK | CSI2_CTRL_MFLAG_LEVL_MASK); > + reg |= (2 << CSI2_CTRL_MFLAG_LEVH_SHIFT) | > + (4 << CSI2_CTRL_MFLAG_LEVL_SHIFT); > + > + /* Generation of 16x64-bit bursts (Recommended) */ > + reg |= CSI2_CTRL_BURST_SIZE_EXPAND; > + > + /* Do Non-Posted writes (Recommended) */ > + reg |= CSI2_CTRL_NON_POSTED_WRITE; > + > + /* > + * Enforce Little endian for all formats, including: > + * YUV4:2:2 8-bit and YUV4:2:0 Legacy > + */ > + reg |= CSI2_CTRL_ENDIANNESS; > + > + writel(reg, csi2->regs1 + CSI2_CTRL); > +} > + > +static const unsigned int csi2_input_fmts[] = { > + V4L2_MBUS_FMT_SGRBG10_1X10, > + V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, > + V4L2_MBUS_FMT_SRGGB10_1X10, > + V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8, > + V4L2_MBUS_FMT_SBGGR10_1X10, > + V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8, > + V4L2_MBUS_FMT_SGBRG10_1X10, > + V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8, > + V4L2_MBUS_FMT_UYVY8_1X16, > + V4L2_MBUS_FMT_YUYV8_1X16, > +}; > + > +/* To set the format on the CSI2 requires a mapping function that takes > + * the following inputs: > + * - 3 different formats (at this time) > + * - 2 destinations (mem, vp+mem) (vp only handled separately) > + * - 2 decompression options (on, off) > + * Output should be CSI2 frame format code > + * Array indices as follows: [format][dest][decompr] > + * Not all combinations are valid. 0 means invalid. > + */ > +static const u16 __csi2_fmt_map[][2][2] = { > + /* RAW10 formats */ > + { > + /* Output to memory */ > + { > + /* No DPCM decompression */ > + CSI2_PIX_FMT_RAW10_EXP16, > + /* DPCM decompression */ > + 0, > + }, > + /* Output to both */ > + { > + /* No DPCM decompression */ > + CSI2_PIX_FMT_RAW10_EXP16_VP, > + /* DPCM decompression */ > + 0, > + }, > + }, > + /* RAW10 DPCM8 formats */ > + { > + /* Output to memory */ > + { > + /* No DPCM decompression */ > + CSI2_USERDEF_8BIT_DATA1, > + /* DPCM decompression */ > + CSI2_USERDEF_8BIT_DATA1_DPCM10, > + }, > + /* Output to both */ > + { > + /* No DPCM decompression */ > + CSI2_PIX_FMT_RAW8_VP, > + /* DPCM decompression */ > + CSI2_USERDEF_8BIT_DATA1_DPCM10_VP, > + }, > + }, > + /* YUV422 formats */ > + { > + /* Output to memory */ > + { > + /* No DPCM decompression */ > + CSI2_PIX_FMT_YUV422_8BIT, > + /* DPCM decompression */ > + 0, > + }, > + /* Output to both */ > + { > + /* No DPCM decompression */ > + CSI2_PIX_FMT_YUV422_8BIT_VP, > + /* DPCM decompression */ > + 0, > + }, > + }, > +}; > + > +/* > + * csi2_ctx_map_format - Map CSI2 sink media bus format to CSI2 format ID > + * @csi2: ISS CSI2 device > + * > + * Returns CSI2 physical format id > + */ > +static u16 csi2_ctx_map_format(struct iss_csi2_device *csi2) > +{ > + const struct v4l2_mbus_framefmt *fmt = &csi2->formats[CSI2_PAD_SINK]; > + int fmtidx, destidx; > + > + switch (fmt->code) { > + case V4L2_MBUS_FMT_SGRBG10_1X10: > + case V4L2_MBUS_FMT_SRGGB10_1X10: > + case V4L2_MBUS_FMT_SBGGR10_1X10: > + case V4L2_MBUS_FMT_SGBRG10_1X10: > + fmtidx = 0; > + break; > + case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8: > + case V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8: > + case V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8: > + case V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8: > + fmtidx = 1; > + break; > + case V4L2_MBUS_FMT_UYVY8_1X16: > + case V4L2_MBUS_FMT_YUYV8_1X16: > + fmtidx = 2; > + break; > + default: > + WARN(1, KERN_ERR "CSI2: pixel format %08x unsupported!\n", > + fmt->code); > + return 0; > + } > + > + if (!(csi2->output & CSI2_OUTPUT_IPIPEIF) && > + !(csi2->output & CSI2_OUTPUT_MEMORY)) { > + /* Neither output enabled is a valid combination */ > + return CSI2_PIX_FMT_OTHERS; > + } > + > + /* If we need to skip frames at the beginning of the stream disable the > + * video port to avoid sending the skipped frames to the IPIPEIF. > + */ > + destidx = csi2->frame_skip ? 0 : !!(csi2->output & CSI2_OUTPUT_IPIPEIF); > + > + return __csi2_fmt_map[fmtidx][destidx][csi2->dpcm_decompress]; > +} > + > +/* > + * csi2_set_outaddr - Set memory address to save output image > + * @csi2: Pointer to ISS CSI2a device. > + * @addr: 32-bit memory address aligned on 32 byte boundary. > + * > + * Sets the memory address where the output will be saved. > + * > + * Returns 0 if successful, or -EINVAL if the address is not in the 32 byte > + * boundary. > + */ > +static void csi2_set_outaddr(struct iss_csi2_device *csi2, u32 addr) > +{ > + struct iss_csi2_ctx_cfg *ctx = &csi2->contexts[0]; > + > + ctx->ping_addr = addr; > + ctx->pong_addr = addr; > + writel(ctx->ping_addr, > + csi2->regs1 + CSI2_CTX_PING_ADDR(ctx->ctxnum)); > + writel(ctx->pong_addr, > + csi2->regs1 + CSI2_CTX_PONG_ADDR(ctx->ctxnum)); > +} > + > +/* > + * is_usr_def_mapping - Checks whether USER_DEF_MAPPING should > + * be enabled by CSI2. > + * @format_id: mapped format id > + * > + */ > +static inline int is_usr_def_mapping(u32 format_id) > +{ > + return (format_id & 0x40) ? 1 : 0; > +} > + > +/* > + * csi2_ctx_enable - Enable specified CSI2 context > + * @ctxnum: Context number, valid between 0 and 7 values. > + * @enable: enable > + * > + */ > +static void csi2_ctx_enable(struct iss_csi2_device *csi2, u8 ctxnum, u8 enable) > +{ > + struct iss_csi2_ctx_cfg *ctx = &csi2->contexts[ctxnum]; > + u32 reg; > + > + reg = readl(csi2->regs1 + CSI2_CTX_CTRL1(ctxnum)); > + > + if (enable) { > + unsigned int skip = 0; > + > + if (csi2->frame_skip) > + skip = csi2->frame_skip; > + else if (csi2->output & CSI2_OUTPUT_MEMORY) > + skip = 1; > + > + reg &= ~CSI2_CTX_CTRL1_COUNT_MASK; > + reg |= CSI2_CTX_CTRL1_COUNT_UNLOCK > + | (skip << CSI2_CTX_CTRL1_COUNT_SHIFT) > + | CSI2_CTX_CTRL1_CTX_EN; > + } else { > + reg &= ~CSI2_CTX_CTRL1_CTX_EN; > + } > + > + writel(reg, csi2->regs1 + CSI2_CTX_CTRL1(ctxnum)); > + ctx->enabled = enable; > +} > + > +/* > + * csi2_ctx_config - CSI2 context configuration. > + * @ctx: context configuration > + * > + */ > +static void csi2_ctx_config(struct iss_csi2_device *csi2, > + struct iss_csi2_ctx_cfg *ctx) > +{ > + u32 reg; > + > + /* Set up CSI2_CTx_CTRL1 */ > + reg = readl(csi2->regs1 + CSI2_CTX_CTRL1(ctx->ctxnum)); > + > + if (ctx->eof_enabled) > + reg |= CSI2_CTX_CTRL1_EOF_EN; > + else > + reg &= ~CSI2_CTX_CTRL1_EOF_EN; > + > + if (ctx->eol_enabled) > + reg |= CSI2_CTX_CTRL1_EOL_EN; > + else > + reg &= ~CSI2_CTX_CTRL1_EOL_EN; > + > + if (ctx->checksum_enabled) > + reg |= CSI2_CTX_CTRL1_CS_EN; > + else > + reg &= ~CSI2_CTX_CTRL1_CS_EN; > + > + writel(reg, csi2->regs1 + CSI2_CTX_CTRL1(ctx->ctxnum)); > + > + /* Set up CSI2_CTx_CTRL2 */ > + reg = readl(csi2->regs1 + CSI2_CTX_CTRL2(ctx->ctxnum)); > + > + reg &= ~(CSI2_CTX_CTRL2_VIRTUAL_ID_MASK); > + reg |= ctx->virtual_id << CSI2_CTX_CTRL2_VIRTUAL_ID_SHIFT; > + > + reg &= ~(CSI2_CTX_CTRL2_FORMAT_MASK); > + reg |= ctx->format_id << CSI2_CTX_CTRL2_FORMAT_SHIFT; > + > + if (ctx->dpcm_decompress) { > + if (ctx->dpcm_predictor) > + reg |= CSI2_CTX_CTRL2_DPCM_PRED; > + else > + reg &= ~CSI2_CTX_CTRL2_DPCM_PRED; > + } > + > + if (is_usr_def_mapping(ctx->format_id)) { > + reg &= ~CSI2_CTX_CTRL2_USER_DEF_MAP_MASK; > + reg |= 2 << CSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT; > + } > + > + writel(reg, csi2->regs1 + CSI2_CTX_CTRL2(ctx->ctxnum)); > + > + /* Set up CSI2_CTx_CTRL3 */ > + reg = readl(csi2->regs1 + CSI2_CTX_CTRL3(ctx->ctxnum)); > + reg &= ~(CSI2_CTX_CTRL3_ALPHA_MASK); > + reg |= (ctx->alpha << CSI2_CTX_CTRL3_ALPHA_SHIFT); > + > + writel(reg, csi2->regs1 + CSI2_CTX_CTRL3(ctx->ctxnum)); > + > + /* Set up CSI2_CTx_DAT_OFST */ > + reg = readl(csi2->regs1 + CSI2_CTX_DAT_OFST(ctx->ctxnum)); > + reg &= ~CSI2_CTX_DAT_OFST_MASK; > + reg |= ctx->data_offset; > + writel(reg, csi2->regs1 + CSI2_CTX_DAT_OFST(ctx->ctxnum)); > + > + writel(ctx->ping_addr, > + csi2->regs1 + CSI2_CTX_PING_ADDR(ctx->ctxnum)); > + > + writel(ctx->pong_addr, > + csi2->regs1 + CSI2_CTX_PONG_ADDR(ctx->ctxnum)); > +} > + > +/* > + * csi2_timing_config - CSI2 timing configuration. > + * @timing: csi2_timing_cfg structure > + */ > +static void csi2_timing_config(struct iss_csi2_device *csi2, > + struct iss_csi2_timing_cfg *timing) > +{ > + u32 reg; > + > + reg = readl(csi2->regs1 + CSI2_TIMING); > + > + if (timing->force_rx_mode) > + reg |= CSI2_TIMING_FORCE_RX_MODE_IO1; > + else > + reg &= ~CSI2_TIMING_FORCE_RX_MODE_IO1; > + > + if (timing->stop_state_16x) > + reg |= CSI2_TIMING_STOP_STATE_X16_IO1; > + else > + reg &= ~CSI2_TIMING_STOP_STATE_X16_IO1; > + > + if (timing->stop_state_4x) > + reg |= CSI2_TIMING_STOP_STATE_X4_IO1; > + else > + reg &= ~CSI2_TIMING_STOP_STATE_X4_IO1; > + > + reg &= ~CSI2_TIMING_STOP_STATE_COUNTER_IO1_MASK; > + reg |= timing->stop_state_counter << > + CSI2_TIMING_STOP_STATE_COUNTER_IO1_SHIFT; > + > + writel(reg, csi2->regs1 + CSI2_TIMING); > +} > + > +/* > + * csi2_irq_ctx_set - Enables CSI2 Context IRQs. > + * @enable: Enable/disable CSI2 Context interrupts > + */ > +static void csi2_irq_ctx_set(struct iss_csi2_device *csi2, int enable) > +{ > + u32 reg = CSI2_CTX_IRQ_FE; > + int i; > + > + if (csi2->use_fs_irq) > + reg |= CSI2_CTX_IRQ_FS; > + > + for (i = 0; i < 8; i++) { > + writel(reg, csi2->regs1 + CSI2_CTX_IRQSTATUS(i)); > + if (enable) > + writel(readl(csi2->regs1 + CSI2_CTX_IRQENABLE(i)) | reg, > + csi2->regs1 + CSI2_CTX_IRQENABLE(i)); > + else > + writel(readl(csi2->regs1 + CSI2_CTX_IRQENABLE(i)) & > + ~reg, > + csi2->regs1 + CSI2_CTX_IRQENABLE(i)); > + } > +} > + > +/* > + * csi2_irq_complexio1_set - Enables CSI2 ComplexIO IRQs. > + * @enable: Enable/disable CSI2 ComplexIO #1 interrupts > + */ > +static void csi2_irq_complexio1_set(struct iss_csi2_device *csi2, int enable) > +{ > + u32 reg; > + reg = CSI2_COMPLEXIO_IRQ_STATEALLULPMEXIT | > + CSI2_COMPLEXIO_IRQ_STATEALLULPMENTER | > + CSI2_COMPLEXIO_IRQ_STATEULPM5 | > + CSI2_COMPLEXIO_IRQ_ERRCONTROL5 | > + CSI2_COMPLEXIO_IRQ_ERRESC5 | > + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS5 | > + CSI2_COMPLEXIO_IRQ_ERRSOTHS5 | > + CSI2_COMPLEXIO_IRQ_STATEULPM4 | > + CSI2_COMPLEXIO_IRQ_ERRCONTROL4 | > + CSI2_COMPLEXIO_IRQ_ERRESC4 | > + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS4 | > + CSI2_COMPLEXIO_IRQ_ERRSOTHS4 | > + CSI2_COMPLEXIO_IRQ_STATEULPM3 | > + CSI2_COMPLEXIO_IRQ_ERRCONTROL3 | > + CSI2_COMPLEXIO_IRQ_ERRESC3 | > + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS3 | > + CSI2_COMPLEXIO_IRQ_ERRSOTHS3 | > + CSI2_COMPLEXIO_IRQ_STATEULPM2 | > + CSI2_COMPLEXIO_IRQ_ERRCONTROL2 | > + CSI2_COMPLEXIO_IRQ_ERRESC2 | > + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS2 | > + CSI2_COMPLEXIO_IRQ_ERRSOTHS2 | > + CSI2_COMPLEXIO_IRQ_STATEULPM1 | > + CSI2_COMPLEXIO_IRQ_ERRCONTROL1 | > + CSI2_COMPLEXIO_IRQ_ERRESC1 | > + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS1 | > + CSI2_COMPLEXIO_IRQ_ERRSOTHS1; > + writel(reg, csi2->regs1 + CSI2_COMPLEXIO_IRQSTATUS); > + if (enable) > + reg |= readl(csi2->regs1 + CSI2_COMPLEXIO_IRQENABLE); > + else > + reg = 0; > + writel(reg, csi2->regs1 + CSI2_COMPLEXIO_IRQENABLE); > +} > + > +/* > + * csi2_irq_status_set - Enables CSI2 Status IRQs. > + * @enable: Enable/disable CSI2 Status interrupts > + */ > +static void csi2_irq_status_set(struct iss_csi2_device *csi2, int enable) > +{ > + u32 reg; > + reg = CSI2_IRQ_OCP_ERR | > + CSI2_IRQ_SHORT_PACKET | > + CSI2_IRQ_ECC_CORRECTION | > + CSI2_IRQ_ECC_NO_CORRECTION | > + CSI2_IRQ_COMPLEXIO_ERR | > + CSI2_IRQ_FIFO_OVF | > + CSI2_IRQ_CONTEXT0; > + writel(reg, csi2->regs1 + CSI2_IRQSTATUS); > + if (enable) > + reg |= readl(csi2->regs1 + CSI2_IRQENABLE); > + else > + reg = 0; > + > + writel(reg, csi2->regs1 + CSI2_IRQENABLE); > +} > + > +/* > + * omap4iss_csi2_reset - Resets the CSI2 module. > + * > + * Must be called with the phy lock held. > + * > + * Returns 0 if successful, or -EBUSY if power command didn't respond. > + */ > +int omap4iss_csi2_reset(struct iss_csi2_device *csi2) > +{ > + u8 soft_reset_retries = 0; > + u32 reg; > + int i; > + > + if (!csi2->available) > + return -ENODEV; > + > + if (csi2->phy->phy_in_use) > + return -EBUSY; > + > + writel(readl(csi2->regs1 + CSI2_SYSCONFIG) | > + CSI2_SYSCONFIG_SOFT_RESET, > + csi2->regs1 + CSI2_SYSCONFIG); > + > + do { > + reg = readl(csi2->regs1 + CSI2_SYSSTATUS) & > + CSI2_SYSSTATUS_RESET_DONE; > + if (reg == CSI2_SYSSTATUS_RESET_DONE) > + break; > + soft_reset_retries++; > + if (soft_reset_retries < 5) > + udelay(100); > + } while (soft_reset_retries < 5); > + > + if (soft_reset_retries == 5) { > + printk(KERN_ERR "CSI2: Soft reset try count exceeded!\n"); > + return -EBUSY; > + } > + > + writel(readl(csi2->regs1 + CSI2_COMPLEXIO_CFG) | > + CSI2_COMPLEXIO_CFG_RESET_CTRL, > + csi2->regs1 + CSI2_COMPLEXIO_CFG); > + > + i = 100; > + do { > + reg = readl(csi2->phy->phy_regs + REGISTER1) > + & REGISTER1_RESET_DONE_CTRLCLK; > + if (reg == REGISTER1_RESET_DONE_CTRLCLK) > + break; > + udelay(100); > + } while (--i > 0); > + > + if (i == 0) { > + printk(KERN_ERR > + "CSI2: Reset for CSI2_96M_FCLK domain Failed!\n"); > + return -EBUSY; > + } > + > + writel((readl(csi2->regs1 + CSI2_SYSCONFIG) & > + ~(CSI2_SYSCONFIG_MSTANDBY_MODE_MASK | > + CSI2_SYSCONFIG_AUTO_IDLE)) | > + CSI2_SYSCONFIG_MSTANDBY_MODE_NO, > + csi2->regs1 + CSI2_SYSCONFIG); > + > + return 0; > +} > + > +static int csi2_configure(struct iss_csi2_device *csi2) > +{ > + const struct iss_v4l2_subdevs_group *pdata; > + struct iss_csi2_timing_cfg *timing = &csi2->timing[0]; > + struct v4l2_subdev *sensor; > + struct media_pad *pad; > + > + /* > + * CSI2 fields that can be updated while the context has > + * been enabled or the interface has been enabled are not > + * updated dynamically currently. So we do not allow to > + * reconfigure if either has been enabled > + */ > + if (csi2->contexts[0].enabled || csi2->ctrl.if_enable) > + return -EBUSY; > + > + pad = media_entity_remote_source(&csi2->pads[CSI2_PAD_SINK]); > + sensor = media_entity_to_v4l2_subdev(pad->entity); > + pdata = sensor->host_priv; > + > + csi2->frame_skip = 0; > + v4l2_subdev_call(sensor, sensor, g_skip_frames, &csi2->frame_skip); > + > + csi2->ctrl.vp_out_ctrl = pdata->bus.csi2.vpclk_div; > + csi2->ctrl.frame_mode = ISS_CSI2_FRAME_IMMEDIATE; > + csi2->ctrl.ecc_enable = pdata->bus.csi2.crc; > + > + timing->force_rx_mode = 1; > + timing->stop_state_16x = 1; > + timing->stop_state_4x = 1; > + timing->stop_state_counter = 0x1FF; > + > + /* > + * The CSI2 receiver can't do any format conversion except DPCM > + * decompression, so every set_format call configures both pads > + * and enables DPCM decompression as a special case: > + */ > + if (csi2->formats[CSI2_PAD_SINK].code != > + csi2->formats[CSI2_PAD_SOURCE].code) > + csi2->dpcm_decompress = true; > + else > + csi2->dpcm_decompress = false; > + > + csi2->contexts[0].format_id = csi2_ctx_map_format(csi2); > + > + if (csi2->video_out.bpl_padding == 0) > + csi2->contexts[0].data_offset = 0; > + else > + csi2->contexts[0].data_offset = csi2->video_out.bpl_value; > + > + /* > + * Enable end of frame and end of line signals generation for > + * context 0. These signals are generated from CSI2 receiver to > + * qualify the last pixel of a frame and the last pixel of a line. > + * Without enabling the signals CSI2 receiver writes data to memory > + * beyond buffer size and/or data line offset is not handled correctly. > + */ > + csi2->contexts[0].eof_enabled = 1; > + csi2->contexts[0].eol_enabled = 1; > + > + csi2_irq_complexio1_set(csi2, 1); > + csi2_irq_ctx_set(csi2, 1); > + csi2_irq_status_set(csi2, 1); > + > + /* Set configuration (timings, format and links) */ > + csi2_timing_config(csi2, timing); > + csi2_recv_config(csi2, &csi2->ctrl); > + csi2_ctx_config(csi2, &csi2->contexts[0]); > + > + return 0; > +} > + > +/* > + * csi2_print_status - Prints CSI2 debug information. > + */ > +#define CSI2_PRINT_REGISTER(iss, regs, name)\ > + dev_dbg(iss->dev, "###CSI2 " #name "=0x%08x\n", \ > + readl(regs + CSI2_##name)) > + > +static void csi2_print_status(struct iss_csi2_device *csi2) > +{ > + struct iss_device *iss = csi2->iss; > + > + if (!csi2->available) > + return; > + > + dev_dbg(iss->dev, "-------------CSI2 Register dump-------------\n"); > + > + CSI2_PRINT_REGISTER(iss, csi2->regs1, SYSCONFIG); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, SYSSTATUS); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, IRQENABLE); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, IRQSTATUS); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTRL); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, DBG_H); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, COMPLEXIO_CFG); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, COMPLEXIO_IRQSTATUS); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, SHORT_PACKET); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, COMPLEXIO_IRQENABLE); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, DBG_P); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, TIMING); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_CTRL1(0)); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_CTRL2(0)); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_DAT_OFST(0)); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_PING_ADDR(0)); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_PONG_ADDR(0)); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_IRQENABLE(0)); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_IRQSTATUS(0)); > + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_CTRL3(0)); > + > + dev_dbg(iss->dev, "--------------------------------------------\n"); > +} > + > +/* ----------------------------------------------------------------------------- > + * Interrupt handling > + */ > + > +/* > + * csi2_isr_buffer - Does buffer handling at end-of-frame > + * when writing to memory. > + */ > +static void csi2_isr_buffer(struct iss_csi2_device *csi2) > +{ > + struct iss_buffer *buffer; > + > + csi2_ctx_enable(csi2, 0, 0); > + > + buffer = omap4iss_video_buffer_next(&csi2->video_out, 0); > + > + /* > + * Let video queue operation restart engine if there is an underrun > + * condition. > + */ > + if (buffer == NULL) > + return; > + > + csi2_set_outaddr(csi2, buffer->iss_addr); > + csi2_ctx_enable(csi2, 0, 1); > +} > + > +static void csi2_isr_ctx(struct iss_csi2_device *csi2, > + struct iss_csi2_ctx_cfg *ctx) > +{ > + unsigned int n = ctx->ctxnum; > + u32 status; > + > + status = readl(csi2->regs1 + CSI2_CTX_IRQSTATUS(n)); > + writel(status, csi2->regs1 + CSI2_CTX_IRQSTATUS(n)); > + > + /* Propagate frame number */ > + if (status & CSI2_CTX_IRQ_FS) { > + struct iss_pipeline *pipe = > + to_iss_pipeline(&csi2->subdev.entity); > + if (pipe->do_propagation) > + atomic_inc(&pipe->frame_number); > + } > + > + if (!(status & CSI2_CTX_IRQ_FE)) > + return; > + > + /* Skip interrupts until we reach the frame skip count. The CSI2 will be > + * automatically disabled, as the frame skip count has been programmed > + * in the CSI2_CTx_CTRL1::COUNT field, so reenable it. > + * > + * It would have been nice to rely on the FRAME_NUMBER interrupt instead > + * but it turned out that the interrupt is only generated when the CSI2 > + * writes to memory (the CSI2_CTx_CTRL1::COUNT field is decreased > + * correctly and reaches 0 when data is forwarded to the video port only > + * but no interrupt arrives). Maybe a CSI2 hardware bug. > + */ > + if (csi2->frame_skip) { > + csi2->frame_skip--; > + if (csi2->frame_skip == 0) { > + ctx->format_id = csi2_ctx_map_format(csi2); > + csi2_ctx_config(csi2, ctx); > + csi2_ctx_enable(csi2, n, 1); > + } > + return; > + } > + > + if (csi2->output & CSI2_OUTPUT_MEMORY) > + csi2_isr_buffer(csi2); > +} > + > +/* > + * omap4iss_csi2_isr - CSI2 interrupt handling. > + * > + * Return -EIO on Transmission error > + */ > +int omap4iss_csi2_isr(struct iss_csi2_device *csi2) > +{ > + u32 csi2_irqstatus, cpxio1_irqstatus; > + struct iss_device *iss = csi2->iss; > + int retval = 0; > + > + if (!csi2->available) > + return -ENODEV; > + > + csi2_irqstatus = readl(csi2->regs1 + CSI2_IRQSTATUS); > + writel(csi2_irqstatus, csi2->regs1 + CSI2_IRQSTATUS); > + > + /* Failure Cases */ > + if (csi2_irqstatus & CSI2_IRQ_COMPLEXIO_ERR) { > + cpxio1_irqstatus = readl(csi2->regs1 + > + CSI2_COMPLEXIO_IRQSTATUS); > + writel(cpxio1_irqstatus, > + csi2->regs1 + CSI2_COMPLEXIO_IRQSTATUS); > + dev_dbg(iss->dev, "CSI2: ComplexIO Error IRQ " > + "%x\n", cpxio1_irqstatus); > + retval = -EIO; > + } > + > + if (csi2_irqstatus & (CSI2_IRQ_OCP_ERR | > + CSI2_IRQ_SHORT_PACKET | > + CSI2_IRQ_ECC_NO_CORRECTION | > + CSI2_IRQ_COMPLEXIO_ERR | > + CSI2_IRQ_FIFO_OVF)) { > + dev_dbg(iss->dev, "CSI2 Err:" > + " OCP:%d," > + " Short_pack:%d," > + " ECC:%d," > + " CPXIO:%d," > + " FIFO_OVF:%d," > + "\n", > + (csi2_irqstatus & > + CSI2_IRQ_OCP_ERR) ? 1 : 0, > + (csi2_irqstatus & > + CSI2_IRQ_SHORT_PACKET) ? 1 : 0, > + (csi2_irqstatus & > + CSI2_IRQ_ECC_NO_CORRECTION) ? 1 : 0, > + (csi2_irqstatus & > + CSI2_IRQ_COMPLEXIO_ERR) ? 1 : 0, > + (csi2_irqstatus & > + CSI2_IRQ_FIFO_OVF) ? 1 : 0); > + retval = -EIO; > + } > + > + if (omap4iss_module_sync_is_stopping(&csi2->wait, &csi2->stopping)) > + return 0; > + > + /* Successful cases */ > + if (csi2_irqstatus & CSI2_IRQ_CONTEXT0) > + csi2_isr_ctx(csi2, &csi2->contexts[0]); > + > + if (csi2_irqstatus & CSI2_IRQ_ECC_CORRECTION) > + dev_dbg(iss->dev, "CSI2: ECC correction done\n"); > + > + return retval; > +} > + > +/* ----------------------------------------------------------------------------- > + * ISS video operations > + */ > + > +/* > + * csi2_queue - Queues the first buffer when using memory output > + * @video: The video node > + * @buffer: buffer to queue > + */ > +static int csi2_queue(struct iss_video *video, struct iss_buffer *buffer) > +{ > + struct iss_device *iss = video->iss; > + struct iss_csi2_device *csi2 = &iss->csi2a; > + > + csi2_set_outaddr(csi2, buffer->iss_addr); > + > + /* > + * If streaming was enabled before there was a buffer queued > + * or underrun happened in the ISR, the hardware was not enabled > + * and DMA queue flag ISS_VIDEO_DMAQUEUE_UNDERRUN is still set. > + * Enable it now. > + */ > + if (csi2->video_out.dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) { > + /* Enable / disable context 0 and IRQs */ > + csi2_if_enable(csi2, 1); > + csi2_ctx_enable(csi2, 0, 1); > + iss_video_dmaqueue_flags_clr(&csi2->video_out); > + } > + > + return 0; > +} > + > +static const struct iss_video_operations csi2_issvideo_ops = { > + .queue = csi2_queue, > +}; > + > +/* ----------------------------------------------------------------------------- > + * V4L2 subdev operations > + */ > + > +static struct v4l2_mbus_framefmt * > +__csi2_get_format(struct iss_csi2_device *csi2, struct v4l2_subdev_fh *fh, > + unsigned int pad, enum v4l2_subdev_format_whence which) > +{ > + if (which == V4L2_SUBDEV_FORMAT_TRY) > + return v4l2_subdev_get_try_format(fh, pad); > + else > + return &csi2->formats[pad]; > +} > + > +static void > +csi2_try_format(struct iss_csi2_device *csi2, struct v4l2_subdev_fh *fh, > + unsigned int pad, struct v4l2_mbus_framefmt *fmt, > + enum v4l2_subdev_format_whence which) > +{ > + enum v4l2_mbus_pixelcode pixelcode; > + struct v4l2_mbus_framefmt *format; > + const struct iss_format_info *info; > + unsigned int i; > + > + switch (pad) { > + case CSI2_PAD_SINK: > + /* Clamp the width and height to valid range (1-8191). */ > + for (i = 0; i < ARRAY_SIZE(csi2_input_fmts); i++) { > + if (fmt->code == csi2_input_fmts[i]) > + break; > + } > + > + /* If not found, use SGRBG10 as default */ > + if (i >= ARRAY_SIZE(csi2_input_fmts)) > + fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10; > + > + fmt->width = clamp_t(u32, fmt->width, 1, 8191); > + fmt->height = clamp_t(u32, fmt->height, 1, 8191); > + break; > + > + case CSI2_PAD_SOURCE: > + /* Source format same as sink format, except for DPCM > + * compression. > + */ > + pixelcode = fmt->code; > + format = __csi2_get_format(csi2, fh, CSI2_PAD_SINK, which); > + memcpy(fmt, format, sizeof(*fmt)); > + > + /* > + * Only Allow DPCM decompression, and check that the > + * pattern is preserved > + */ > + info = omap4iss_video_format_info(fmt->code); > + if (info->uncompressed == pixelcode) > + fmt->code = pixelcode; > + break; > + } > + > + /* RGB, non-interlaced */ > + fmt->colorspace = V4L2_COLORSPACE_SRGB; > + fmt->field = V4L2_FIELD_NONE; > +} > + > +/* > + * csi2_enum_mbus_code - Handle pixel format enumeration > + * @sd : pointer to v4l2 subdev structure > + * @fh : V4L2 subdev file handle > + * @code : pointer to v4l2_subdev_mbus_code_enum structure > + * return -EINVAL or zero on success > + */ > +static int csi2_enum_mbus_code(struct v4l2_subdev *sd, > + struct v4l2_subdev_fh *fh, > + struct v4l2_subdev_mbus_code_enum *code) > +{ > + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); > + struct v4l2_mbus_framefmt *format; > + const struct iss_format_info *info; > + > + if (code->pad == CSI2_PAD_SINK) { > + if (code->index >= ARRAY_SIZE(csi2_input_fmts)) > + return -EINVAL; > + > + code->code = csi2_input_fmts[code->index]; > + } else { > + format = __csi2_get_format(csi2, fh, CSI2_PAD_SINK, > + V4L2_SUBDEV_FORMAT_TRY); > + switch (code->index) { > + case 0: > + /* Passthrough sink pad code */ > + code->code = format->code; > + break; > + case 1: > + /* Uncompressed code */ > + info = omap4iss_video_format_info(format->code); > + if (info->uncompressed == format->code) > + return -EINVAL; > + > + code->code = info->uncompressed; > + break; > + default: > + return -EINVAL; > + } > + } > + > + return 0; > +} > + > +static int csi2_enum_frame_size(struct v4l2_subdev *sd, > + struct v4l2_subdev_fh *fh, > + struct v4l2_subdev_frame_size_enum *fse) > +{ > + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); > + struct v4l2_mbus_framefmt format; > + > + if (fse->index != 0) > + return -EINVAL; > + > + format.code = fse->code; > + format.width = 1; > + format.height = 1; > + csi2_try_format(csi2, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); > + fse->min_width = format.width; > + fse->min_height = format.height; > + > + if (format.code != fse->code) > + return -EINVAL; > + > + format.code = fse->code; > + format.width = -1; > + format.height = -1; > + csi2_try_format(csi2, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); > + fse->max_width = format.width; > + fse->max_height = format.height; > + > + return 0; > +} > + > +/* > + * csi2_get_format - Handle get format by pads subdev method > + * @sd : pointer to v4l2 subdev structure > + * @fh : V4L2 subdev file handle > + * @fmt: pointer to v4l2 subdev format structure > + * return -EINVAL or zero on success > + */ > +static int csi2_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, > + struct v4l2_subdev_format *fmt) > +{ > + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); > + struct v4l2_mbus_framefmt *format; > + > + format = __csi2_get_format(csi2, fh, fmt->pad, fmt->which); > + if (format == NULL) > + return -EINVAL; > + > + fmt->format = *format; > + return 0; > +} > + > +/* > + * csi2_set_format - Handle set format by pads subdev method > + * @sd : pointer to v4l2 subdev structure > + * @fh : V4L2 subdev file handle > + * @fmt: pointer to v4l2 subdev format structure > + * return -EINVAL or zero on success > + */ > +static int csi2_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, > + struct v4l2_subdev_format *fmt) > +{ > + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); > + struct v4l2_mbus_framefmt *format; > + > + format = __csi2_get_format(csi2, fh, fmt->pad, fmt->which); > + if (format == NULL) > + return -EINVAL; > + > + csi2_try_format(csi2, fh, fmt->pad, &fmt->format, fmt->which); > + *format = fmt->format; > + > + /* Propagate the format from sink to source */ > + if (fmt->pad == CSI2_PAD_SINK) { > + format = __csi2_get_format(csi2, fh, CSI2_PAD_SOURCE, > + fmt->which); > + *format = fmt->format; > + csi2_try_format(csi2, fh, CSI2_PAD_SOURCE, format, fmt->which); > + } > + > + return 0; > +} > + > +/* > + * csi2_init_formats - Initialize formats on all pads > + * @sd: ISS CSI2 V4L2 subdevice > + * @fh: V4L2 subdev file handle > + * > + * Initialize all pad formats with default values. If fh is not NULL, try > + * formats are initialized on the file handle. Otherwise active formats are > + * initialized on the device. > + */ > +static int csi2_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) > +{ > + struct v4l2_subdev_format format; > + > + memset(&format, 0, sizeof(format)); > + format.pad = CSI2_PAD_SINK; > + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; > + format.format.code = V4L2_MBUS_FMT_SGRBG10_1X10; > + format.format.width = 4096; > + format.format.height = 4096; > + csi2_set_format(sd, fh, &format); > + > + return 0; > +} > + > +/* > + * csi2_set_stream - Enable/Disable streaming on the CSI2 module > + * @sd: ISS CSI2 V4L2 subdevice > + * @enable: ISS pipeline stream state > + * > + * Return 0 on success or a negative error code otherwise. > + */ > +static int csi2_set_stream(struct v4l2_subdev *sd, int enable) > +{ > + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); > + struct iss_device *iss = csi2->iss; > + struct iss_pipeline *pipe = to_iss_pipeline(&csi2->subdev.entity); > + struct iss_video *video_out = &csi2->video_out; > + > + if (csi2->state == ISS_PIPELINE_STREAM_STOPPED) { > + if (enable == ISS_PIPELINE_STREAM_STOPPED) > + return 0; > + > + omap4iss_subclk_enable(iss, OMAP4_ISS_SUBCLK_CSI2_A); > + } > + > + switch (enable) { > + case ISS_PIPELINE_STREAM_CONTINUOUS: > + if (omap4iss_csiphy_acquire(csi2->phy) < 0) > + return -ENODEV; > + csi2->use_fs_irq = pipe->do_propagation; > + csi2_configure(csi2); > + csi2_print_status(csi2); > + > + /* > + * When outputting to memory with no buffer available, let the > + * buffer queue handler start the hardware. A DMA queue flag > + * ISS_VIDEO_DMAQUEUE_QUEUED will be set as soon as there is > + * a buffer available. > + */ > + if (csi2->output & CSI2_OUTPUT_MEMORY && > + !(video_out->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_QUEUED)) > + break; > + /* Enable context 0 and IRQs */ > + atomic_set(&csi2->stopping, 0); > + csi2_ctx_enable(csi2, 0, 1); > + csi2_if_enable(csi2, 1); > + iss_video_dmaqueue_flags_clr(video_out); > + break; > + > + case ISS_PIPELINE_STREAM_STOPPED: > + if (csi2->state == ISS_PIPELINE_STREAM_STOPPED) > + return 0; > + if (omap4iss_module_sync_idle(&sd->entity, &csi2->wait, > + &csi2->stopping)) > + dev_dbg(iss->dev, "%s: module stop timeout.\n", > + sd->name); > + csi2_ctx_enable(csi2, 0, 0); > + csi2_if_enable(csi2, 0); > + csi2_irq_ctx_set(csi2, 0); > + omap4iss_csiphy_release(csi2->phy); > + omap4iss_subclk_disable(iss, OMAP4_ISS_SUBCLK_CSI2_A); > + iss_video_dmaqueue_flags_clr(video_out); > + break; > + } > + > + csi2->state = enable; > + return 0; > +} > + > +/* subdev video operations */ > +static const struct v4l2_subdev_video_ops csi2_video_ops = { > + .s_stream = csi2_set_stream, > +}; > + > +/* subdev pad operations */ > +static const struct v4l2_subdev_pad_ops csi2_pad_ops = { > + .enum_mbus_code = csi2_enum_mbus_code, > + .enum_frame_size = csi2_enum_frame_size, > + .get_fmt = csi2_get_format, > + .set_fmt = csi2_set_format, > +}; > + > +/* subdev operations */ > +static const struct v4l2_subdev_ops csi2_ops = { > + .video = &csi2_video_ops, > + .pad = &csi2_pad_ops, > +}; > + > +/* subdev internal operations */ > +static const struct v4l2_subdev_internal_ops csi2_internal_ops = { > + .open = csi2_init_formats, > +}; > + > +/* ----------------------------------------------------------------------------- > + * Media entity operations > + */ > + > +/* > + * csi2_link_setup - Setup CSI2 connections. > + * @entity : Pointer to media entity structure > + * @local : Pointer to local pad array > + * @remote : Pointer to remote pad array > + * @flags : Link flags > + * return -EINVAL or zero on success > + */ > +static int csi2_link_setup(struct media_entity *entity, > + const struct media_pad *local, > + const struct media_pad *remote, u32 flags) > +{ > + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); > + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); > + struct iss_csi2_ctrl_cfg *ctrl = &csi2->ctrl; > + > + /* > + * The ISS core doesn't support pipelines with multiple video outputs. > + * Revisit this when it will be implemented, and return -EBUSY for now. > + */ > + > + switch (local->index | media_entity_type(remote->entity)) { > + case CSI2_PAD_SOURCE | MEDIA_ENT_T_DEVNODE: > + if (flags & MEDIA_LNK_FL_ENABLED) { > + if (csi2->output & ~CSI2_OUTPUT_MEMORY) > + return -EBUSY; > + csi2->output |= CSI2_OUTPUT_MEMORY; > + } else { > + csi2->output &= ~CSI2_OUTPUT_MEMORY; > + } > + break; > + > + case CSI2_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV: > + if (flags & MEDIA_LNK_FL_ENABLED) { > + if (csi2->output & ~CSI2_OUTPUT_IPIPEIF) > + return -EBUSY; > + csi2->output |= CSI2_OUTPUT_IPIPEIF; > + } else { > + csi2->output &= ~CSI2_OUTPUT_IPIPEIF; > + } > + break; > + > + default: > + /* Link from camera to CSI2 is fixed... */ > + return -EINVAL; > + } > + > + ctrl->vp_only_enable = > + (csi2->output & CSI2_OUTPUT_MEMORY) ? false : true; > + ctrl->vp_clk_enable = !!(csi2->output & CSI2_OUTPUT_IPIPEIF); > + > + return 0; > +} > + > +/* media operations */ > +static const struct media_entity_operations csi2_media_ops = { > + .link_setup = csi2_link_setup, > +}; > + > +/* > + * csi2_init_entities - Initialize subdev and media entity. > + * @csi2: Pointer to csi2 structure. > + * return -ENOMEM or zero on success > + */ > +static int csi2_init_entities(struct iss_csi2_device *csi2) > +{ > + struct v4l2_subdev *sd = &csi2->subdev; > + struct media_pad *pads = csi2->pads; > + struct media_entity *me = &sd->entity; > + int ret; > + > + v4l2_subdev_init(sd, &csi2_ops); > + sd->internal_ops = &csi2_internal_ops; > + strlcpy(sd->name, "OMAP4 ISS CSI2a", sizeof(sd->name)); > + > + sd->grp_id = 1 << 16; /* group ID for iss subdevs */ > + v4l2_set_subdevdata(sd, csi2); > + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; > + > + pads[CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; > + pads[CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; > + > + me->ops = &csi2_media_ops; > + ret = media_entity_init(me, CSI2_PADS_NUM, pads, 0); > + if (ret < 0) > + return ret; > + > + csi2_init_formats(sd, NULL); > + > + /* Video device node */ > + csi2->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > + csi2->video_out.ops = &csi2_issvideo_ops; > + csi2->video_out.bpl_alignment = 32; > + csi2->video_out.bpl_zero_padding = 1; > + csi2->video_out.bpl_max = 0x1ffe0; > + csi2->video_out.iss = csi2->iss; > + csi2->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 3; > + > + ret = omap4iss_video_init(&csi2->video_out, "CSI2a"); > + if (ret < 0) > + return ret; > + > + /* Connect the CSI2 subdev to the video node. */ > + ret = media_entity_create_link(&csi2->subdev.entity, CSI2_PAD_SOURCE, > + &csi2->video_out.video.entity, 0, 0); > + if (ret < 0) > + return ret; > + > + return 0; > +} > + > +void omap4iss_csi2_unregister_entities(struct iss_csi2_device *csi2) > +{ > + media_entity_cleanup(&csi2->subdev.entity); > + > + v4l2_device_unregister_subdev(&csi2->subdev); > + omap4iss_video_unregister(&csi2->video_out); > +} > + > +int omap4iss_csi2_register_entities(struct iss_csi2_device *csi2, > + struct v4l2_device *vdev) > +{ > + int ret; > + > + /* Register the subdev and video nodes. */ > + ret = v4l2_device_register_subdev(vdev, &csi2->subdev); > + if (ret < 0) > + goto error; > + > + ret = omap4iss_video_register(&csi2->video_out, vdev); > + if (ret < 0) > + goto error; > + > + return 0; > + > +error: > + omap4iss_csi2_unregister_entities(csi2); > + return ret; > +} > + > +/* ----------------------------------------------------------------------------- > + * ISS CSI2 initialisation and cleanup > + */ > + > +/* > + * omap4iss_csi2_cleanup - Routine for module driver cleanup > + */ > +void omap4iss_csi2_cleanup(struct iss_device *iss) > +{ > +} > + > +/* > + * omap4iss_csi2_init - Routine for module driver init > + */ > +int omap4iss_csi2_init(struct iss_device *iss) > +{ > + struct iss_csi2_device *csi2a = &iss->csi2a; > + int ret; > + > + csi2a->iss = iss; > + csi2a->available = 1; > + csi2a->regs1 = iss->regs[OMAP4_ISS_MEM_CSI2_A_REGS1]; > + csi2a->phy = &iss->csiphy1; > + csi2a->state = ISS_PIPELINE_STREAM_STOPPED; > + init_waitqueue_head(&csi2a->wait); > + > + ret = csi2_init_entities(csi2a); > + if (ret < 0) > + goto fail; > + > + return 0; > +fail: > + omap4iss_csi2_cleanup(iss); > + return ret; > +} > diff --git a/drivers/media/video/omap4iss/iss_csi2.h b/drivers/media/video/omap4iss/iss_csi2.h > new file mode 100644 > index 0000000..4fa94cf > --- /dev/null > +++ b/drivers/media/video/omap4iss/iss_csi2.h > @@ -0,0 +1,166 @@ > +/* > + * iss_csi2.h > + * > + * TI OMAP4 ISS - CSI2 module > + * > + * Copyright (C) 2011 Texas Instruments, Inc. > + * > + * Contacts: Sergio Aguirre <saaguirre@xxxxxx> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > + > +#ifndef OMAP4_ISS_CSI2_H > +#define OMAP4_ISS_CSI2_H > + > +#include <linux/types.h> > +#include <linux/videodev2.h> > + > +#include "iss_video.h" > + > +struct iss_csiphy; > + > +/* This is not an exhaustive list */ > +enum iss_csi2_pix_formats { > + CSI2_PIX_FMT_OTHERS = 0, > + CSI2_PIX_FMT_YUV422_8BIT = 0x1e, > + CSI2_PIX_FMT_YUV422_8BIT_VP = 0x9e, > + CSI2_PIX_FMT_RAW10_EXP16 = 0xab, > + CSI2_PIX_FMT_RAW10_EXP16_VP = 0x12f, > + CSI2_PIX_FMT_RAW8 = 0x2a, > + CSI2_PIX_FMT_RAW8_DPCM10_EXP16 = 0x2aa, > + CSI2_PIX_FMT_RAW8_DPCM10_VP = 0x32a, > + CSI2_PIX_FMT_RAW8_VP = 0x12a, > + CSI2_USERDEF_8BIT_DATA1_DPCM10_VP = 0x340, > + CSI2_USERDEF_8BIT_DATA1_DPCM10 = 0x2c0, > + CSI2_USERDEF_8BIT_DATA1 = 0x40, > +}; > + > +enum iss_csi2_irqevents { > + OCP_ERR_IRQ = 0x4000, > + SHORT_PACKET_IRQ = 0x2000, > + ECC_CORRECTION_IRQ = 0x1000, > + ECC_NO_CORRECTION_IRQ = 0x800, > + COMPLEXIO2_ERR_IRQ = 0x400, > + COMPLEXIO1_ERR_IRQ = 0x200, > + FIFO_OVF_IRQ = 0x100, > + CONTEXT7 = 0x80, > + CONTEXT6 = 0x40, > + CONTEXT5 = 0x20, > + CONTEXT4 = 0x10, > + CONTEXT3 = 0x8, > + CONTEXT2 = 0x4, > + CONTEXT1 = 0x2, > + CONTEXT0 = 0x1, > +}; > + > +enum iss_csi2_ctx_irqevents { > + CTX_ECC_CORRECTION = 0x100, > + CTX_LINE_NUMBER = 0x80, > + CTX_FRAME_NUMBER = 0x40, > + CTX_CS = 0x20, > + CTX_LE = 0x8, > + CTX_LS = 0x4, > + CTX_FE = 0x2, > + CTX_FS = 0x1, > +}; > + > +enum iss_csi2_frame_mode { > + ISS_CSI2_FRAME_IMMEDIATE, > + ISS_CSI2_FRAME_AFTERFEC, > +}; > + > +#define ISS_CSI2_MAX_CTX_NUM 7 > + > +struct iss_csi2_ctx_cfg { > + u8 ctxnum; /* context number 0 - 7 */ > + u8 dpcm_decompress; > + > + /* Fields in CSI2_CTx_CTRL2 - locked by CSI2_CTx_CTRL1.CTX_EN */ > + u8 virtual_id; > + u16 format_id; /* as in CSI2_CTx_CTRL2[9:0] */ > + u8 dpcm_predictor; /* 1: simple, 0: advanced */ > + > + /* Fields in CSI2_CTx_CTRL1/3 - Shadowed */ > + u16 alpha; > + u16 data_offset; > + u32 ping_addr; > + u32 pong_addr; > + u8 eof_enabled; > + u8 eol_enabled; > + u8 checksum_enabled; > + u8 enabled; > +}; > + > +struct iss_csi2_timing_cfg { > + u8 ionum; /* IO1 or IO2 as in CSI2_TIMING */ > + unsigned force_rx_mode:1; > + unsigned stop_state_16x:1; > + unsigned stop_state_4x:1; > + u16 stop_state_counter; > +}; > + > +struct iss_csi2_ctrl_cfg { > + bool vp_clk_enable; > + bool vp_only_enable; > + u8 vp_out_ctrl; > + enum iss_csi2_frame_mode frame_mode; > + bool ecc_enable; > + bool if_enable; > +}; > + > +#define CSI2_PAD_SINK 0 > +#define CSI2_PAD_SOURCE 1 > +#define CSI2_PADS_NUM 2 > + > +#define CSI2_OUTPUT_IPIPEIF (1 << 0) > +#define CSI2_OUTPUT_MEMORY (1 << 1) > + > +struct iss_csi2_device { > + struct v4l2_subdev subdev; > + struct media_pad pads[CSI2_PADS_NUM]; > + struct v4l2_mbus_framefmt formats[CSI2_PADS_NUM]; > + > + struct iss_video video_out; > + struct iss_device *iss; > + > + u8 available; /* Is the IP present on the silicon? */ > + > + /* Pointer to register remaps into kernel space */ > + void __iomem *regs1; > + void __iomem *regs2; > + > + u32 output; /* output to IPIPEIF, memory or both? */ > + bool dpcm_decompress; > + unsigned int frame_skip; > + bool use_fs_irq; > + > + struct iss_csiphy *phy; > + struct iss_csi2_ctx_cfg contexts[ISS_CSI2_MAX_CTX_NUM + 1]; > + struct iss_csi2_timing_cfg timing[2]; > + struct iss_csi2_ctrl_cfg ctrl; > + enum iss_pipeline_stream_state state; > + wait_queue_head_t wait; > + atomic_t stopping; > +}; > + > +int omap4iss_csi2_isr(struct iss_csi2_device *csi2); > +int omap4iss_csi2_reset(struct iss_csi2_device *csi2); > +int omap4iss_csi2_init(struct iss_device *iss); > +void omap4iss_csi2_cleanup(struct iss_device *iss); > +void omap4iss_csi2_unregister_entities(struct iss_csi2_device *csi2); > +int omap4iss_csi2_register_entities(struct iss_csi2_device *csi2, > + struct v4l2_device *vdev); > +#endif /* OMAP4_ISS_CSI2_H */ > diff --git a/drivers/media/video/omap4iss/iss_csiphy.c b/drivers/media/video/omap4iss/iss_csiphy.c > new file mode 100644 > index 0000000..9545622 > --- /dev/null > +++ b/drivers/media/video/omap4iss/iss_csiphy.c > @@ -0,0 +1,215 @@ > +/* > + * iss_csiphy.c > + * > + * TI OMAP4 ISS - CSI PHY module > + * > + * Copyright (C) 2011 Texas Instruments, Inc. > + * > + * Contacts: Sergio Aguirre <saaguirre@xxxxxx> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > + > +#include <linux/delay.h> > +#include <linux/device.h> > + > +#include "iss.h" > +#include "iss_regs.h" > +#include "iss_csiphy.h" > + > +/* > + * csiphy_lanes_config - Configuration of CSIPHY lanes. > + * > + * Updates HW configuration. > + * Called with phy->mutex taken. > + */ > +static void csiphy_lanes_config(struct iss_csiphy *phy) > +{ > + unsigned int i; > + u32 reg; > + > + reg = readl(phy->cfg_regs + CSI2_COMPLEXIO_CFG); > + > + for (i = 0; i < phy->num_data_lanes; i++) { > + reg &= ~(CSI2_COMPLEXIO_CFG_DATA_POL(i + 1) | > + CSI2_COMPLEXIO_CFG_DATA_POSITION_MASK(i + 1)); > + reg |= (phy->lanes.data[i].pol ? > + CSI2_COMPLEXIO_CFG_DATA_POL(i + 1) : 0); > + reg |= (phy->lanes.data[i].pos << > + CSI2_COMPLEXIO_CFG_DATA_POSITION_SHIFT(i + 1)); > + } > + > + reg &= ~(CSI2_COMPLEXIO_CFG_CLOCK_POL | > + CSI2_COMPLEXIO_CFG_CLOCK_POSITION_MASK); > + reg |= phy->lanes.clk.pol ? CSI2_COMPLEXIO_CFG_CLOCK_POL : 0; > + reg |= phy->lanes.clk.pos << CSI2_COMPLEXIO_CFG_CLOCK_POSITION_SHIFT; > + > + writel(reg, phy->cfg_regs + CSI2_COMPLEXIO_CFG); > +} > + > +/* > + * csiphy_set_power > + * @power: Power state to be set. > + * > + * Returns 0 if successful, or -EBUSY if the retry count is exceeded. > + */ > +static int csiphy_set_power(struct iss_csiphy *phy, u32 power) > +{ > + u32 reg; > + u8 retry_count; > + > + writel((readl(phy->cfg_regs + CSI2_COMPLEXIO_CFG) & > + ~CSI2_COMPLEXIO_CFG_PWD_CMD_MASK) | > + power, > + phy->cfg_regs + CSI2_COMPLEXIO_CFG); > + > + retry_count = 0; > + do { > + udelay(50); > + reg = readl(phy->cfg_regs + CSI2_COMPLEXIO_CFG) & > + CSI2_COMPLEXIO_CFG_PWD_STATUS_MASK; > + > + if (reg != power >> 2) > + retry_count++; > + > + } while ((reg != power >> 2) && (retry_count < 100)); > + > + if (retry_count == 100) { > + printk(KERN_ERR "CSI2 CIO set power failed!\n"); > + return -EBUSY; > + } > + > + return 0; > +} > + > +/* > + * csiphy_dphy_config - Configure CSI2 D-PHY parameters. > + * > + * Called with phy->mutex taken. > + */ > +static void csiphy_dphy_config(struct iss_csiphy *phy) > +{ > + u32 reg; > + > + /* Set up REGISTER0 */ > + reg = readl(phy->phy_regs + REGISTER0); > + > + reg &= ~(REGISTER0_THS_TERM_MASK | > + REGISTER0_THS_SETTLE_MASK); > + reg |= phy->dphy.ths_term << REGISTER0_THS_TERM_SHIFT; > + reg |= phy->dphy.ths_settle << REGISTER0_THS_SETTLE_SHIFT; > + > + writel(reg, phy->phy_regs + REGISTER0); > + > + /* Set up REGISTER1 */ > + reg = readl(phy->phy_regs + REGISTER1); > + > + reg &= ~(REGISTER1_TCLK_TERM_MASK | > + REGISTER1_CTRLCLK_DIV_FACTOR_MASK | > + REGISTER1_TCLK_SETTLE_MASK); > + reg |= phy->dphy.tclk_term << REGISTER1_TCLK_TERM_SHIFT; > + reg |= phy->dphy.tclk_miss << REGISTER1_CTRLCLK_DIV_FACTOR_SHIFT; > + reg |= phy->dphy.tclk_settle << REGISTER1_TCLK_SETTLE_SHIFT; > + > + writel(reg, phy->phy_regs + REGISTER1); > +} > + > +static int csiphy_config(struct iss_csiphy *phy, > + struct iss_csiphy_dphy_cfg *dphy, > + struct iss_csiphy_lanes_cfg *lanes) > +{ > + unsigned int used_lanes = 0; > + unsigned int i; > + > + /* Clock and data lanes verification */ > + for (i = 0; i < phy->num_data_lanes; i++) { > + if (lanes->data[i].pos == 0) > + continue; > + > + if (lanes->data[i].pol > 1 || lanes->data[i].pos > 5) > + return -EINVAL; > + > + if (used_lanes & (1 << lanes->data[i].pos)) > + return -EINVAL; > + > + used_lanes |= 1 << lanes->data[i].pos; > + } > + > + if (lanes->clk.pol > 1 || lanes->clk.pos > 4) > + return -EINVAL; > + > + if (lanes->clk.pos == 0 || used_lanes & (1 << lanes->clk.pos)) > + return -EINVAL; > + > + mutex_lock(&phy->mutex); > + phy->dphy = *dphy; > + phy->lanes = *lanes; > + mutex_unlock(&phy->mutex); > + > + return 0; > +} > + > +int omap4iss_csiphy_acquire(struct iss_csiphy *phy) > +{ > + int rval; > + > + mutex_lock(&phy->mutex); > + > + rval = omap4iss_csi2_reset(phy->csi2); > + if (rval) > + goto done; > + > + csiphy_dphy_config(phy); > + csiphy_lanes_config(phy); > + > + rval = csiphy_set_power(phy, CSI2_COMPLEXIO_CFG_PWD_CMD_ON); > + if (rval) > + goto done; > + > + phy->phy_in_use = 1; > + > +done: > + mutex_unlock(&phy->mutex); > + return rval; > +} > + > +void omap4iss_csiphy_release(struct iss_csiphy *phy) > +{ > + mutex_lock(&phy->mutex); > + if (phy->phy_in_use) { > + csiphy_set_power(phy, CSI2_COMPLEXIO_CFG_PWD_CMD_OFF); > + phy->phy_in_use = 0; > + } > + mutex_unlock(&phy->mutex); > +} > + > +/* > + * omap4iss_csiphy_init - Initialize the CSI PHY frontends > + */ > +int omap4iss_csiphy_init(struct iss_device *iss) > +{ > + struct iss_csiphy *phy1 = &iss->csiphy1; > + > + iss->platform_cb.csiphy_config = csiphy_config; > + > + phy1->iss = iss; > + phy1->csi2 = &iss->csi2a; > + phy1->num_data_lanes = ISS_CSIPHY1_NUM_DATA_LANES; > + phy1->cfg_regs = iss->regs[OMAP4_ISS_MEM_CSI2_A_REGS1]; > + phy1->phy_regs = iss->regs[OMAP4_ISS_MEM_CAMERARX_CORE1]; > + mutex_init(&phy1->mutex); > + > + return 0; > +} > diff --git a/drivers/media/video/omap4iss/iss_csiphy.h b/drivers/media/video/omap4iss/iss_csiphy.h > new file mode 100644 > index 0000000..c513ba8 > --- /dev/null > +++ b/drivers/media/video/omap4iss/iss_csiphy.h > @@ -0,0 +1,69 @@ > +/* > + * iss_csiphy.h > + * > + * TI OMAP4 ISS - CSI PHY module > + * > + * Copyright (C) 2011 Texas Instruments, Inc. > + * > + * Contacts: Sergio Aguirre <saaguirre@xxxxxx> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > + > +#ifndef OMAP4_ISS_CSI_PHY_H > +#define OMAP4_ISS_CSI_PHY_H > + > +struct iss_csi2_device; > + > +struct csiphy_lane { > + u8 pos; > + u8 pol; > +}; > + > +#define ISS_CSIPHY1_NUM_DATA_LANES 4 > + > +struct iss_csiphy_lanes_cfg { > + struct csiphy_lane data[ISS_CSIPHY1_NUM_DATA_LANES]; > + struct csiphy_lane clk; > +}; > + > +struct iss_csiphy_dphy_cfg { > + u8 ths_term; > + u8 ths_settle; > + u8 tclk_term; > + unsigned tclk_miss:1; > + u8 tclk_settle; > +}; > + > +struct iss_csiphy { > + struct iss_device *iss; > + struct mutex mutex; /* serialize csiphy configuration */ > + u8 phy_in_use; > + struct iss_csi2_device *csi2; > + > + /* Pointer to register remaps into kernel space */ > + void __iomem *cfg_regs; > + void __iomem *phy_regs; > + > + u8 num_data_lanes; /* number of CSI2 Data Lanes supported */ > + struct iss_csiphy_lanes_cfg lanes; > + struct iss_csiphy_dphy_cfg dphy; > +}; > + > +int omap4iss_csiphy_acquire(struct iss_csiphy *phy); > +void omap4iss_csiphy_release(struct iss_csiphy *phy); > +int omap4iss_csiphy_init(struct iss_device *iss); > + > +#endif /* OMAP4_ISS_CSI_PHY_H */ > diff --git a/drivers/media/video/omap4iss/iss_regs.h b/drivers/media/video/omap4iss/iss_regs.h > new file mode 100644 > index 0000000..0bd70ac > --- /dev/null > +++ b/drivers/media/video/omap4iss/iss_regs.h > @@ -0,0 +1,238 @@ > +/* > + * iss_regs.h > + * > + * Copyright (C) 2011 Texas Instruments. > + * > + * Author: Sergio Aguirre <saaguirre@xxxxxx> > + */ > + > +#ifndef _OMAP4_ISS_REGS_H_ > +#define _OMAP4_ISS_REGS_H_ > + > +/* ISS */ > +#define ISS_HL_REVISION (0x0) > + > +#define ISS_HL_SYSCONFIG (0x10) > +#define ISS_HL_SYSCONFIG_IDLEMODE_SHIFT 2 > +#define ISS_HL_SYSCONFIG_IDLEMODE_FORCEIDLE 0x0 > +#define ISS_HL_SYSCONFIG_IDLEMODE_NOIDLE 0x1 > +#define ISS_HL_SYSCONFIG_IDLEMODE_SMARTIDLE 0x2 > +#define ISS_HL_SYSCONFIG_SOFTRESET (1 << 0) > + > +#define ISS_HL_IRQSTATUS_5 (0x24 + (0x10 * 5)) > +#define ISS_HL_IRQENABLE_5_SET (0x28 + (0x10 * 5)) > +#define ISS_HL_IRQENABLE_5_CLR (0x2C + (0x10 * 5)) > + > +#define ISS_HL_IRQ_BTE (1 << 11) > +#define ISS_HL_IRQ_CBUFF (1 << 10) > +#define ISS_HL_IRQ_CSIA (1 << 4) > + > +#define ISS_CTRL (0x80) > + > +#define ISS_CLKCTRL (0x84) > +#define ISS_CLKCTRL_VPORT2_CLK (1 << 30) > +#define ISS_CLKCTRL_VPORT1_CLK (1 << 29) > +#define ISS_CLKCTRL_VPORT0_CLK (1 << 28) > +#define ISS_CLKCTRL_CCP2 (1 << 4) > +#define ISS_CLKCTRL_CSI2_B (1 << 3) > +#define ISS_CLKCTRL_CSI2_A (1 << 2) > +#define ISS_CLKCTRL_ISP (1 << 1) > +#define ISS_CLKCTRL_SIMCOP (1 << 0) > + > +#define ISS_CLKSTAT (0x88) > +#define ISS_CLKSTAT_VPORT2_CLK (1 << 30) > +#define ISS_CLKSTAT_VPORT1_CLK (1 << 29) > +#define ISS_CLKSTAT_VPORT0_CLK (1 << 28) > +#define ISS_CLKSTAT_CCP2 (1 << 4) > +#define ISS_CLKSTAT_CSI2_B (1 << 3) > +#define ISS_CLKSTAT_CSI2_A (1 << 2) > +#define ISS_CLKSTAT_ISP (1 << 1) > +#define ISS_CLKSTAT_SIMCOP (1 << 0) > + > +#define ISS_PM_STATUS (0x8C) > +#define ISS_PM_STATUS_CBUFF_PM_MASK (3 << 12) > +#define ISS_PM_STATUS_BTE_PM_MASK (3 << 10) > +#define ISS_PM_STATUS_SIMCOP_PM_MASK (3 << 8) > +#define ISS_PM_STATUS_ISP_PM_MASK (3 << 6) > +#define ISS_PM_STATUS_CCP2_PM_MASK (3 << 4) > +#define ISS_PM_STATUS_CSI2_B_PM_MASK (3 << 2) > +#define ISS_PM_STATUS_CSI2_A_PM_MASK (3 << 0) > + > +#define REGISTER0 (0x0) > +#define REGISTER0_HSCLOCKCONFIG (1 << 24) > +#define REGISTER0_THS_TERM_MASK (0xFF << 8) > +#define REGISTER0_THS_TERM_SHIFT 8 > +#define REGISTER0_THS_SETTLE_MASK (0xFF << 0) > +#define REGISTER0_THS_SETTLE_SHIFT 0 > + > +#define REGISTER1 (0x4) > +#define REGISTER1_RESET_DONE_CTRLCLK (1 << 29) > +#define REGISTER1_CLOCK_MISS_DETECTOR_STATUS (1 << 25) > +#define REGISTER1_TCLK_TERM_MASK (0x3F << 18) > +#define REGISTER1_TCLK_TERM_SHIFT 18 > +#define REGISTER1_DPHY_HS_SYNC_PATTERN_MASK (0xFF << 10) > +#define REGISTER1_CTRLCLK_DIV_FACTOR_MASK (0x3 << 8) > +#define REGISTER1_CTRLCLK_DIV_FACTOR_SHIFT 8 > +#define REGISTER1_TCLK_SETTLE_MASK (0xFF << 0) > +#define REGISTER1_TCLK_SETTLE_SHIFT 0 > + > +#define REGISTER2 (0x8) > + > +#define CSI2_SYSCONFIG (0x10) > +#define CSI2_SYSCONFIG_MSTANDBY_MODE_MASK (3 << 12) > +#define CSI2_SYSCONFIG_MSTANDBY_MODE_FORCE (0 << 12) > +#define CSI2_SYSCONFIG_MSTANDBY_MODE_NO (1 << 12) > +#define CSI2_SYSCONFIG_MSTANDBY_MODE_SMART (2 << 12) > +#define CSI2_SYSCONFIG_SOFT_RESET (1 << 1) > +#define CSI2_SYSCONFIG_AUTO_IDLE (1 << 0) > + > +#define CSI2_SYSSTATUS (0x14) > +#define CSI2_SYSSTATUS_RESET_DONE (1 << 0) > + > +#define CSI2_IRQSTATUS (0x18) > +#define CSI2_IRQENABLE (0x1C) > + > +/* Shared bits across CSI2_IRQENABLE and IRQSTATUS */ > + > +#define CSI2_IRQ_OCP_ERR (1 << 14) > +#define CSI2_IRQ_SHORT_PACKET (1 << 13) > +#define CSI2_IRQ_ECC_CORRECTION (1 << 12) > +#define CSI2_IRQ_ECC_NO_CORRECTION (1 << 11) > +#define CSI2_IRQ_COMPLEXIO_ERR (1 << 9) > +#define CSI2_IRQ_FIFO_OVF (1 << 8) > +#define CSI2_IRQ_CONTEXT0 (1 << 0) > + > +#define CSI2_CTRL (0x40) > +#define CSI2_CTRL_MFLAG_LEVH_MASK (7 << 20) > +#define CSI2_CTRL_MFLAG_LEVH_SHIFT 20 > +#define CSI2_CTRL_MFLAG_LEVL_MASK (7 << 17) > +#define CSI2_CTRL_MFLAG_LEVL_SHIFT 17 > +#define CSI2_CTRL_BURST_SIZE_EXPAND (1 << 16) > +#define CSI2_CTRL_VP_CLK_EN (1 << 15) > +#define CSI2_CTRL_NON_POSTED_WRITE (1 << 13) > +#define CSI2_CTRL_VP_ONLY_EN (1 << 11) > +#define CSI2_CTRL_VP_OUT_CTRL_MASK (3 << 8) > +#define CSI2_CTRL_VP_OUT_CTRL_SHIFT 8 > +#define CSI2_CTRL_DBG_EN (1 << 7) > +#define CSI2_CTRL_BURST_SIZE_MASK (3 << 5) > +#define CSI2_CTRL_ENDIANNESS (1 << 4) > +#define CSI2_CTRL_FRAME (1 << 3) > +#define CSI2_CTRL_ECC_EN (1 << 2) > +#define CSI2_CTRL_IF_EN (1 << 0) > + > +#define CSI2_DBG_H (0x44) > + > +#define CSI2_COMPLEXIO_CFG (0x50) > +#define CSI2_COMPLEXIO_CFG_RESET_CTRL (1 << 30) > +#define CSI2_COMPLEXIO_CFG_RESET_DONE (1 << 29) > +#define CSI2_COMPLEXIO_CFG_PWD_CMD_MASK (3 << 27) > +#define CSI2_COMPLEXIO_CFG_PWD_CMD_OFF (0 << 27) > +#define CSI2_COMPLEXIO_CFG_PWD_CMD_ON (1 << 27) > +#define CSI2_COMPLEXIO_CFG_PWD_CMD_ULP (2 << 27) > +#define CSI2_COMPLEXIO_CFG_PWD_STATUS_MASK (3 << 25) > +#define CSI2_COMPLEXIO_CFG_PWD_STATUS_OFF (0 << 25) > +#define CSI2_COMPLEXIO_CFG_PWD_STATUS_ON (1 << 25) > +#define CSI2_COMPLEXIO_CFG_PWD_STATUS_ULP (2 << 25) > +#define CSI2_COMPLEXIO_CFG_PWR_AUTO (1 << 24) > +#define CSI2_COMPLEXIO_CFG_DATA_POL(i) (1 << (((i) * 4) + 3)) > +#define CSI2_COMPLEXIO_CFG_DATA_POSITION_MASK(i) (7 << ((i) * 4)) > +#define CSI2_COMPLEXIO_CFG_DATA_POSITION_SHIFT(i) ((i) * 4) > +#define CSI2_COMPLEXIO_CFG_CLOCK_POL (1 << 3) > +#define CSI2_COMPLEXIO_CFG_CLOCK_POSITION_MASK (7 << 0) > +#define CSI2_COMPLEXIO_CFG_CLOCK_POSITION_SHIFT 0 > + > +#define CSI2_COMPLEXIO_IRQSTATUS (0x54) > + > +#define CSI2_SHORT_PACKET (0x5C) > + > +#define CSI2_COMPLEXIO_IRQENABLE (0x60) > + > +/* Shared bits across CSI2_COMPLEXIO_IRQENABLE and IRQSTATUS */ > +#define CSI2_COMPLEXIO_IRQ_STATEALLULPMEXIT (1 << 26) > +#define CSI2_COMPLEXIO_IRQ_STATEALLULPMENTER (1 << 25) > +#define CSI2_COMPLEXIO_IRQ_STATEULPM5 (1 << 24) > +#define CSI2_COMPLEXIO_IRQ_STATEULPM4 (1 << 23) > +#define CSI2_COMPLEXIO_IRQ_STATEULPM3 (1 << 22) > +#define CSI2_COMPLEXIO_IRQ_STATEULPM2 (1 << 21) > +#define CSI2_COMPLEXIO_IRQ_STATEULPM1 (1 << 20) > +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL5 (1 << 19) > +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL4 (1 << 18) > +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL3 (1 << 17) > +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL2 (1 << 16) > +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL1 (1 << 15) > +#define CSI2_COMPLEXIO_IRQ_ERRESC5 (1 << 14) > +#define CSI2_COMPLEXIO_IRQ_ERRESC4 (1 << 13) > +#define CSI2_COMPLEXIO_IRQ_ERRESC3 (1 << 12) > +#define CSI2_COMPLEXIO_IRQ_ERRESC2 (1 << 11) > +#define CSI2_COMPLEXIO_IRQ_ERRESC1 (1 << 10) > +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS5 (1 << 9) > +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS4 (1 << 8) > +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS3 (1 << 7) > +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS2 (1 << 6) > +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS1 (1 << 5) > +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS5 (1 << 4) > +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS4 (1 << 3) > +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS3 (1 << 2) > +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS2 (1 << 1) > +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS1 (1 << 0) > + > +#define CSI2_DBG_P (0x68) > + > +#define CSI2_TIMING (0x6C) > +#define CSI2_TIMING_FORCE_RX_MODE_IO1 (1 << 15) > +#define CSI2_TIMING_STOP_STATE_X16_IO1 (1 << 14) > +#define CSI2_TIMING_STOP_STATE_X4_IO1 (1 << 13) > +#define CSI2_TIMING_STOP_STATE_COUNTER_IO1_MASK (0x1FFF << 0) > +#define CSI2_TIMING_STOP_STATE_COUNTER_IO1_SHIFT 0 > + > +#define CSI2_CTX_CTRL1(i) (0x70 + (0x20 * i)) > +#define CSI2_CTX_CTRL1_GENERIC (1 << 30) > +#define CSI2_CTX_CTRL1_TRANSCODE (0xF << 24) > +#define CSI2_CTX_CTRL1_FEC_NUMBER_MASK (0xFF << 16) > +#define CSI2_CTX_CTRL1_COUNT_MASK (0xFF << 8) > +#define CSI2_CTX_CTRL1_COUNT_SHIFT 8 > +#define CSI2_CTX_CTRL1_EOF_EN (1 << 7) > +#define CSI2_CTX_CTRL1_EOL_EN (1 << 6) > +#define CSI2_CTX_CTRL1_CS_EN (1 << 5) > +#define CSI2_CTX_CTRL1_COUNT_UNLOCK (1 << 4) > +#define CSI2_CTX_CTRL1_PING_PONG (1 << 3) > +#define CSI2_CTX_CTRL1_CTX_EN (1 << 0) > + > +#define CSI2_CTX_CTRL2(i) (0x74 + (0x20 * i)) > +#define CSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT 13 > +#define CSI2_CTX_CTRL2_USER_DEF_MAP_MASK \ > + (0x3 << CSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT) > +#define CSI2_CTX_CTRL2_VIRTUAL_ID_MASK (3 << 11) > +#define CSI2_CTX_CTRL2_VIRTUAL_ID_SHIFT 11 > +#define CSI2_CTX_CTRL2_DPCM_PRED (1 << 10) > +#define CSI2_CTX_CTRL2_FORMAT_MASK (0x3FF << 0) > +#define CSI2_CTX_CTRL2_FORMAT_SHIFT 0 > + > +#define CSI2_CTX_DAT_OFST(i) (0x78 + (0x20 * i)) > +#define CSI2_CTX_DAT_OFST_MASK (0xFFF << 5) > + > +#define CSI2_CTX_PING_ADDR(i) (0x7C + (0x20 * i)) > +#define CSI2_CTX_PING_ADDR_MASK 0xFFFFFFE0 > + > +#define CSI2_CTX_PONG_ADDR(i) (0x80 + (0x20 * i)) > +#define CSI2_CTX_PONG_ADDR_MASK CSI2_CTX_PING_ADDR_MASK > + > +#define CSI2_CTX_IRQENABLE(i) (0x84 + (0x20 * i)) > +#define CSI2_CTX_IRQSTATUS(i) (0x88 + (0x20 * i)) > + > +#define CSI2_CTX_CTRL3(i) (0x8C + (0x20 * i)) > +#define CSI2_CTX_CTRL3_ALPHA_SHIFT 5 > +#define CSI2_CTX_CTRL3_ALPHA_MASK \ > + (0x3fff << CSI2_CTX_CTRL3_ALPHA_SHIFT) > + > +/* Shared bits across CSI2_CTX_IRQENABLE and IRQSTATUS */ > +#define CSI2_CTX_IRQ_ECC_CORRECTION (1 << 8) > +#define CSI2_CTX_IRQ_LINE_NUMBER (1 << 7) > +#define CSI2_CTX_IRQ_FRAME_NUMBER (1 << 6) > +#define CSI2_CTX_IRQ_CS (1 << 5) > +#define CSI2_CTX_IRQ_LE (1 << 3) > +#define CSI2_CTX_IRQ_LS (1 << 2) > +#define CSI2_CTX_IRQ_FE (1 << 1) > +#define CSI2_CTX_IRQ_FS (1 << 0) > + > +#endif /* _OMAP4_CAMERA_REGS_H_ */ > diff --git a/drivers/media/video/omap4iss/iss_video.c b/drivers/media/video/omap4iss/iss_video.c > new file mode 100644 > index 0000000..3248711 > --- /dev/null > +++ b/drivers/media/video/omap4iss/iss_video.c > @@ -0,0 +1,1192 @@ > +/* > + * iss_video.c > + * > + * TI OMAP4 ISS - Generic video node > + * > + * Copyright (C) 2011 Texas Instruments, Inc. > + * > + * Contacts: Sergio Aguirre <saaguirre@xxxxxx> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > + > +#include <asm/cacheflush.h> > +#include <linux/clk.h> > +#include <linux/mm.h> > +#include <linux/pagemap.h> > +#include <linux/sched.h> > +#include <linux/slab.h> > +#include <linux/vmalloc.h> > +#include <linux/module.h> > +#include <media/v4l2-dev.h> > +#include <media/v4l2-ioctl.h> > +#include <plat/omap-pm.h> > + > +#include "iss_video.h" > +#include "iss.h" > + > + > +/* ----------------------------------------------------------------------------- > + * Helper functions > + */ > + > +static struct iss_format_info formats[] = { > + { V4L2_MBUS_FMT_Y8_1X8, V4L2_MBUS_FMT_Y8_1X8, > + V4L2_MBUS_FMT_Y8_1X8, V4L2_MBUS_FMT_Y8_1X8, > + V4L2_PIX_FMT_GREY, 8, }, > + { V4L2_MBUS_FMT_Y10_1X10, V4L2_MBUS_FMT_Y10_1X10, > + V4L2_MBUS_FMT_Y10_1X10, V4L2_MBUS_FMT_Y8_1X8, > + V4L2_PIX_FMT_Y10, 10, }, > + { V4L2_MBUS_FMT_Y12_1X12, V4L2_MBUS_FMT_Y10_1X10, > + V4L2_MBUS_FMT_Y12_1X12, V4L2_MBUS_FMT_Y8_1X8, > + V4L2_PIX_FMT_Y12, 12, }, > + { V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_MBUS_FMT_SBGGR8_1X8, > + V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_MBUS_FMT_SBGGR8_1X8, > + V4L2_PIX_FMT_SBGGR8, 8, }, > + { V4L2_MBUS_FMT_SGBRG8_1X8, V4L2_MBUS_FMT_SGBRG8_1X8, > + V4L2_MBUS_FMT_SGBRG8_1X8, V4L2_MBUS_FMT_SGBRG8_1X8, > + V4L2_PIX_FMT_SGBRG8, 8, }, > + { V4L2_MBUS_FMT_SGRBG8_1X8, V4L2_MBUS_FMT_SGRBG8_1X8, > + V4L2_MBUS_FMT_SGRBG8_1X8, V4L2_MBUS_FMT_SGRBG8_1X8, > + V4L2_PIX_FMT_SGRBG8, 8, }, > + { V4L2_MBUS_FMT_SRGGB8_1X8, V4L2_MBUS_FMT_SRGGB8_1X8, > + V4L2_MBUS_FMT_SRGGB8_1X8, V4L2_MBUS_FMT_SRGGB8_1X8, > + V4L2_PIX_FMT_SRGGB8, 8, }, > + { V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, > + V4L2_MBUS_FMT_SGRBG10_1X10, 0, > + V4L2_PIX_FMT_SGRBG10DPCM8, 8, }, > + { V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_MBUS_FMT_SBGGR10_1X10, > + V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_MBUS_FMT_SBGGR8_1X8, > + V4L2_PIX_FMT_SBGGR10, 10, }, > + { V4L2_MBUS_FMT_SGBRG10_1X10, V4L2_MBUS_FMT_SGBRG10_1X10, > + V4L2_MBUS_FMT_SGBRG10_1X10, V4L2_MBUS_FMT_SGBRG8_1X8, > + V4L2_PIX_FMT_SGBRG10, 10, }, > + { V4L2_MBUS_FMT_SGRBG10_1X10, V4L2_MBUS_FMT_SGRBG10_1X10, > + V4L2_MBUS_FMT_SGRBG10_1X10, V4L2_MBUS_FMT_SGRBG8_1X8, > + V4L2_PIX_FMT_SGRBG10, 10, }, > + { V4L2_MBUS_FMT_SRGGB10_1X10, V4L2_MBUS_FMT_SRGGB10_1X10, > + V4L2_MBUS_FMT_SRGGB10_1X10, V4L2_MBUS_FMT_SRGGB8_1X8, > + V4L2_PIX_FMT_SRGGB10, 10, }, > + { V4L2_MBUS_FMT_SBGGR12_1X12, V4L2_MBUS_FMT_SBGGR10_1X10, > + V4L2_MBUS_FMT_SBGGR12_1X12, V4L2_MBUS_FMT_SBGGR8_1X8, > + V4L2_PIX_FMT_SBGGR12, 12, }, > + { V4L2_MBUS_FMT_SGBRG12_1X12, V4L2_MBUS_FMT_SGBRG10_1X10, > + V4L2_MBUS_FMT_SGBRG12_1X12, V4L2_MBUS_FMT_SGBRG8_1X8, > + V4L2_PIX_FMT_SGBRG12, 12, }, > + { V4L2_MBUS_FMT_SGRBG12_1X12, V4L2_MBUS_FMT_SGRBG10_1X10, > + V4L2_MBUS_FMT_SGRBG12_1X12, V4L2_MBUS_FMT_SGRBG8_1X8, > + V4L2_PIX_FMT_SGRBG12, 12, }, > + { V4L2_MBUS_FMT_SRGGB12_1X12, V4L2_MBUS_FMT_SRGGB10_1X10, > + V4L2_MBUS_FMT_SRGGB12_1X12, V4L2_MBUS_FMT_SRGGB8_1X8, > + V4L2_PIX_FMT_SRGGB12, 12, }, > + { V4L2_MBUS_FMT_UYVY8_1X16, V4L2_MBUS_FMT_UYVY8_1X16, > + V4L2_MBUS_FMT_UYVY8_1X16, 0, > + V4L2_PIX_FMT_UYVY, 16, }, > + { V4L2_MBUS_FMT_YUYV8_1X16, V4L2_MBUS_FMT_YUYV8_1X16, > + V4L2_MBUS_FMT_YUYV8_1X16, 0, > + V4L2_PIX_FMT_YUYV, 16, }, > +}; > + > +const struct iss_format_info * > +omap4iss_video_format_info(enum v4l2_mbus_pixelcode code) > +{ > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(formats); ++i) { > + if (formats[i].code == code) > + return &formats[i]; > + } > + > + return NULL; > +} > + > +/* > + * iss_video_mbus_to_pix - Convert v4l2_mbus_framefmt to v4l2_pix_format > + * @video: ISS video instance > + * @mbus: v4l2_mbus_framefmt format (input) > + * @pix: v4l2_pix_format format (output) > + * > + * Fill the output pix structure with information from the input mbus format. > + * The bytesperline and sizeimage fields are computed from the requested bytes > + * per line value in the pix format and information from the video instance. > + * > + * Return the number of padding bytes at end of line. > + */ > +static unsigned int iss_video_mbus_to_pix(const struct iss_video *video, > + const struct v4l2_mbus_framefmt *mbus, > + struct v4l2_pix_format *pix) > +{ > + unsigned int bpl = pix->bytesperline; > + unsigned int min_bpl; > + unsigned int i; > + > + memset(pix, 0, sizeof(*pix)); > + pix->width = mbus->width; > + pix->height = mbus->height; > + > + for (i = 0; i < ARRAY_SIZE(formats); ++i) { > + if (formats[i].code == mbus->code) > + break; > + } > + > + if (WARN_ON(i == ARRAY_SIZE(formats))) > + return 0; > + > + min_bpl = pix->width * ALIGN(formats[i].bpp, 8) / 8; > + > + /* Clamp the requested bytes per line value. If the maximum bytes per > + * line value is zero, the module doesn't support user configurable line > + * sizes. Override the requested value with the minimum in that case. > + */ > + if (video->bpl_max) > + bpl = clamp(bpl, min_bpl, video->bpl_max); > + else > + bpl = min_bpl; > + > + if (!video->bpl_zero_padding || bpl != min_bpl) > + bpl = ALIGN(bpl, video->bpl_alignment); > + > + pix->pixelformat = formats[i].pixelformat; > + pix->bytesperline = bpl; > + pix->sizeimage = pix->bytesperline * pix->height; > + pix->colorspace = mbus->colorspace; > + pix->field = mbus->field; > + > + return bpl - min_bpl; > +} > + > +static void iss_video_pix_to_mbus(const struct v4l2_pix_format *pix, > + struct v4l2_mbus_framefmt *mbus) > +{ > + unsigned int i; > + > + memset(mbus, 0, sizeof(*mbus)); > + mbus->width = pix->width; > + mbus->height = pix->height; > + > + for (i = 0; i < ARRAY_SIZE(formats); ++i) { > + if (formats[i].pixelformat == pix->pixelformat) > + break; > + } > + > + if (WARN_ON(i == ARRAY_SIZE(formats))) > + return; > + > + mbus->code = formats[i].code; > + mbus->colorspace = pix->colorspace; > + mbus->field = pix->field; > +} > + > +static struct v4l2_subdev * > +iss_video_remote_subdev(struct iss_video *video, u32 *pad) > +{ > + struct media_pad *remote; > + > + remote = media_entity_remote_source(&video->pad); > + > + if (remote == NULL || > + media_entity_type(remote->entity) != MEDIA_ENT_T_V4L2_SUBDEV) > + return NULL; > + > + if (pad) > + *pad = remote->index; > + > + return media_entity_to_v4l2_subdev(remote->entity); > +} > + > +/* Return a pointer to the ISS video instance at the far end of the pipeline. */ > +static struct iss_video * > +iss_video_far_end(struct iss_video *video) > +{ > + struct media_entity_graph graph; > + struct media_entity *entity = &video->video.entity; > + struct media_device *mdev = entity->parent; > + struct iss_video *far_end = NULL; > + > + mutex_lock(&mdev->graph_mutex); > + media_entity_graph_walk_start(&graph, entity); > + > + while ((entity = media_entity_graph_walk_next(&graph))) { > + if (entity == &video->video.entity) > + continue; > + > + if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE) > + continue; > + > + far_end = to_iss_video(media_entity_to_video_device(entity)); > + if (far_end->type != video->type) > + break; > + > + far_end = NULL; > + } > + > + mutex_unlock(&mdev->graph_mutex); > + return far_end; > +} > + > +/* > + * Validate a pipeline by checking both ends of all links for format > + * discrepancies. > + * > + * Compute the minimum time per frame value as the maximum of time per frame > + * limits reported by every block in the pipeline. > + * > + * Return 0 if all formats match, or -EPIPE if at least one link is found with > + * different formats on its two ends. > + */ > +static int iss_video_validate_pipeline(struct iss_pipeline *pipe) > +{ > + struct v4l2_subdev_format fmt_source; > + struct v4l2_subdev_format fmt_sink; > + struct media_pad *pad; > + struct v4l2_subdev *subdev; > + int ret; > + > + subdev = iss_video_remote_subdev(pipe->output, NULL); > + if (subdev == NULL) > + return -EPIPE; > + > + while (1) { > + /* Retrieve the sink format */ > + pad = &subdev->entity.pads[0]; > + if (!(pad->flags & MEDIA_PAD_FL_SINK)) > + break; > + > + fmt_sink.pad = pad->index; > + fmt_sink.which = V4L2_SUBDEV_FORMAT_ACTIVE; > + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt_sink); > + if (ret < 0 && ret != -ENOIOCTLCMD) > + return -EPIPE; > + > + /* Retrieve the source format */ > + pad = media_entity_remote_source(pad); > + if (pad == NULL || > + media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV) > + break; > + > + subdev = media_entity_to_v4l2_subdev(pad->entity); > + > + fmt_source.pad = pad->index; > + fmt_source.which = V4L2_SUBDEV_FORMAT_ACTIVE; > + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt_source); > + if (ret < 0 && ret != -ENOIOCTLCMD) > + return -EPIPE; > + > + /* Check if the two ends match */ > + if (fmt_source.format.width != fmt_sink.format.width || > + fmt_source.format.height != fmt_sink.format.height) > + return -EPIPE; > + > + if (fmt_source.format.code != fmt_sink.format.code) > + return -EPIPE; > + } > + > + return 0; > +} > + > +static int > +__iss_video_get_format(struct iss_video *video, struct v4l2_format *format) > +{ > + struct v4l2_subdev_format fmt; > + struct v4l2_subdev *subdev; > + u32 pad; > + int ret; > + > + subdev = iss_video_remote_subdev(video, &pad); > + if (subdev == NULL) > + return -EINVAL; > + > + mutex_lock(&video->mutex); > + > + fmt.pad = pad; > + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; > + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); > + if (ret == -ENOIOCTLCMD) > + ret = -EINVAL; > + > + mutex_unlock(&video->mutex); > + > + if (ret) > + return ret; > + > + format->type = video->type; > + return iss_video_mbus_to_pix(video, &fmt.format, &format->fmt.pix); > +} > + > +static int > +iss_video_check_format(struct iss_video *video, struct iss_video_fh *vfh) > +{ > + struct v4l2_format format; > + int ret; > + > + memcpy(&format, &vfh->format, sizeof(format)); > + ret = __iss_video_get_format(video, &format); > + if (ret < 0) > + return ret; > + > + if (vfh->format.fmt.pix.pixelformat != format.fmt.pix.pixelformat || > + vfh->format.fmt.pix.height != format.fmt.pix.height || > + vfh->format.fmt.pix.width != format.fmt.pix.width || > + vfh->format.fmt.pix.bytesperline != format.fmt.pix.bytesperline || > + vfh->format.fmt.pix.sizeimage != format.fmt.pix.sizeimage) > + return -EINVAL; > + > + return ret; > +} > + > +/* ----------------------------------------------------------------------------- > + * Video queue operations > + */ > + > +static int iss_video_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, > + unsigned int *count, unsigned int *num_planes, > + unsigned int sizes[], void *alloc_ctxs[]) > +{ > + struct iss_video_fh *vfh = vb2_get_drv_priv(vq); > + struct iss_video *video = vfh->video; > + > + /* Revisit multi-planar support for NV12 */ > + *num_planes = 1; > + > + sizes[0] = vfh->format.fmt.pix.sizeimage; > + if (sizes[0] == 0) > + return -EINVAL; > + > + alloc_ctxs[0] = video->alloc_ctx; > + > + *count = min(*count, (unsigned int)(video->capture_mem / PAGE_ALIGN(sizes[0]))); > + > + return 0; > +} > + > +static void iss_video_buf_cleanup(struct vb2_buffer *vb) > +{ > + struct iss_buffer *buffer = container_of(vb, struct iss_buffer, vb); > + > + if (buffer->iss_addr) > + buffer->iss_addr = 0; > +} > + > +static int iss_video_buf_prepare(struct vb2_buffer *vb) > +{ > + struct iss_video_fh *vfh = vb2_get_drv_priv(vb->vb2_queue); > + struct iss_buffer *buffer = container_of(vb, struct iss_buffer, vb); > + struct iss_video *video = vfh->video; > + unsigned long size = vfh->format.fmt.pix.sizeimage; > + dma_addr_t addr; > + > + if (vb2_plane_size(vb, 0) < size) > + return -ENOBUFS; > + > + addr = vb2_dma_contig_plane_dma_addr(vb, 0); > + if (!IS_ALIGNED(addr, 32)) { > + dev_dbg(video->iss->dev, "Buffer address must be " > + "aligned to 32 bytes boundary.\n"); > + return -EINVAL; > + } > + > + vb2_set_plane_payload(vb, 0, size); > + buffer->iss_addr = addr; > + return 0; > +} > + > +static void iss_video_buf_queue(struct vb2_buffer *vb) > +{ > + struct iss_video_fh *vfh = vb2_get_drv_priv(vb->vb2_queue); > + struct iss_video *video = vfh->video; > + struct iss_buffer *buffer = container_of(vb, struct iss_buffer, vb); > + struct iss_pipeline *pipe = to_iss_pipeline(&video->video.entity); > + unsigned int empty; > + unsigned long flags; > + > + spin_lock_irqsave(&video->qlock, flags); > + empty = list_empty(&video->dmaqueue); > + list_add_tail(&buffer->list, &video->dmaqueue); > + spin_unlock_irqrestore(&video->qlock, flags); > + > + if (empty) { > + enum iss_pipeline_state state; > + unsigned int start; > + > + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) > + state = ISS_PIPELINE_QUEUE_OUTPUT; > + else > + state = ISS_PIPELINE_QUEUE_INPUT; > + > + spin_lock_irqsave(&pipe->lock, flags); > + pipe->state |= state; > + video->ops->queue(video, buffer); > + video->dmaqueue_flags |= ISS_VIDEO_DMAQUEUE_QUEUED; > + > + start = iss_pipeline_ready(pipe); > + if (start) > + pipe->state |= ISS_PIPELINE_STREAM; > + spin_unlock_irqrestore(&pipe->lock, flags); > + > + if (start) > + omap4iss_pipeline_set_stream(pipe, > + ISS_PIPELINE_STREAM_SINGLESHOT); > + } > +} > + > +static struct vb2_ops iss_video_vb2ops = { > + .queue_setup = iss_video_queue_setup, > + .buf_prepare = iss_video_buf_prepare, > + .buf_queue = iss_video_buf_queue, > + .buf_cleanup = iss_video_buf_cleanup, > +}; > + > +/* > + * omap4iss_video_buffer_next - Complete the current buffer and return the next > + * @video: ISS video object > + * @error: Whether an error occurred during capture > + * > + * Remove the current video buffer from the DMA queue and fill its timestamp, > + * field count and state fields before waking up its completion handler. > + * > + * The buffer state is set to VIDEOBUF_DONE if no error occurred (@error is 0) > + * or VIDEOBUF_ERROR otherwise (@error is non-zero). > + * > + * The DMA queue is expected to contain at least one buffer. > + * > + * Return a pointer to the next buffer in the DMA queue, or NULL if the queue is > + * empty. > + */ > +struct iss_buffer *omap4iss_video_buffer_next(struct iss_video *video, > + unsigned int error) > +{ > + struct iss_pipeline *pipe = to_iss_pipeline(&video->video.entity); > + enum iss_pipeline_state state; > + struct iss_buffer *buf; > + unsigned long flags; > + struct timespec ts; > + > + spin_lock_irqsave(&video->qlock, flags); > + if (WARN_ON(list_empty(&video->dmaqueue))) { > + spin_unlock_irqrestore(&video->qlock, flags); > + return NULL; > + } > + > + buf = list_first_entry(&video->dmaqueue, struct iss_buffer, > + list); > + list_del(&buf->list); > + spin_unlock_irqrestore(&video->qlock, flags); > + > + ktime_get_ts(&ts); > + buf->vb.v4l2_buf.timestamp.tv_sec = ts.tv_sec; > + buf->vb.v4l2_buf.timestamp.tv_usec = ts.tv_nsec / NSEC_PER_USEC; > + > + /* Do frame number propagation only if this is the output video node. > + * Frame number either comes from the CSI receivers or it gets > + * incremented here if H3A is not active. > + * Note: There is no guarantee that the output buffer will finish > + * first, so the input number might lag behind by 1 in some cases. > + */ > + if (video == pipe->output && !pipe->do_propagation) > + buf->vb.v4l2_buf.sequence = atomic_inc_return(&pipe->frame_number); > + else > + buf->vb.v4l2_buf.sequence = atomic_read(&pipe->frame_number); > + > + vb2_buffer_done(&buf->vb, error < 0 ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); > + > + spin_lock_irqsave(&video->qlock, flags); > + if (list_empty(&video->dmaqueue)) { > + spin_unlock_irqrestore(&video->qlock, flags); > + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) > + state = ISS_PIPELINE_QUEUE_OUTPUT > + | ISS_PIPELINE_STREAM; > + else > + state = ISS_PIPELINE_QUEUE_INPUT > + | ISS_PIPELINE_STREAM; > + > + spin_lock_irqsave(&pipe->lock, flags); > + pipe->state &= ~state; > + if (video->pipe.stream_state == ISS_PIPELINE_STREAM_CONTINUOUS) > + video->dmaqueue_flags |= ISS_VIDEO_DMAQUEUE_UNDERRUN; > + spin_unlock_irqrestore(&pipe->lock, flags); > + return NULL; > + } > + > + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && pipe->input != NULL) { > + spin_lock_irqsave(&pipe->lock, flags); > + pipe->state &= ~ISS_PIPELINE_STREAM; > + spin_unlock_irqrestore(&pipe->lock, flags); > + } > + > + buf = list_first_entry(&video->dmaqueue, struct iss_buffer, > + list); > + spin_unlock_irqrestore(&video->qlock, flags); > + buf->vb.state = VB2_BUF_STATE_ACTIVE; > + return buf; > +} > + > +/* ----------------------------------------------------------------------------- > + * V4L2 ioctls > + */ > + > +static int > +iss_video_querycap(struct file *file, void *fh, struct v4l2_capability *cap) > +{ > + struct iss_video *video = video_drvdata(file); > + > + strlcpy(cap->driver, ISS_VIDEO_DRIVER_NAME, sizeof(cap->driver)); > + strlcpy(cap->card, video->video.name, sizeof(cap->card)); > + strlcpy(cap->bus_info, "media", sizeof(cap->bus_info)); > + cap->version = ISS_VIDEO_DRIVER_VERSION; > + > + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) > + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; > + else > + cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; > + > + return 0; > +} > + > +static int > +iss_video_get_format(struct file *file, void *fh, struct v4l2_format *format) > +{ > + struct iss_video_fh *vfh = to_iss_video_fh(fh); > + struct iss_video *video = video_drvdata(file); > + > + if (format->type != video->type) > + return -EINVAL; > + > + mutex_lock(&video->mutex); > + *format = vfh->format; > + mutex_unlock(&video->mutex); > + > + return 0; > +} > + > +static int > +iss_video_set_format(struct file *file, void *fh, struct v4l2_format *format) > +{ > + struct iss_video_fh *vfh = to_iss_video_fh(fh); > + struct iss_video *video = video_drvdata(file); > + struct v4l2_mbus_framefmt fmt; > + > + if (format->type != video->type) > + return -EINVAL; > + > + mutex_lock(&video->mutex); > + > + /* Fill the bytesperline and sizeimage fields by converting to media bus > + * format and back to pixel format. > + */ > + iss_video_pix_to_mbus(&format->fmt.pix, &fmt); > + iss_video_mbus_to_pix(video, &fmt, &format->fmt.pix); > + > + vfh->format = *format; > + > + mutex_unlock(&video->mutex); > + return 0; > +} > + > +static int > +iss_video_try_format(struct file *file, void *fh, struct v4l2_format *format) > +{ > + struct iss_video *video = video_drvdata(file); > + struct v4l2_subdev_format fmt; > + struct v4l2_subdev *subdev; > + u32 pad; > + int ret; > + > + if (format->type != video->type) > + return -EINVAL; > + > + subdev = iss_video_remote_subdev(video, &pad); > + if (subdev == NULL) > + return -EINVAL; > + > + iss_video_pix_to_mbus(&format->fmt.pix, &fmt.format); > + > + fmt.pad = pad; > + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; > + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); > + if (ret) > + return ret == -ENOIOCTLCMD ? -EINVAL : ret; > + > + iss_video_mbus_to_pix(video, &fmt.format, &format->fmt.pix); > + return 0; > +} > + > +static int > +iss_video_cropcap(struct file *file, void *fh, struct v4l2_cropcap *cropcap) > +{ > + struct iss_video *video = video_drvdata(file); > + struct v4l2_subdev *subdev; > + int ret; > + > + subdev = iss_video_remote_subdev(video, NULL); > + if (subdev == NULL) > + return -EINVAL; > + > + mutex_lock(&video->mutex); > + ret = v4l2_subdev_call(subdev, video, cropcap, cropcap); > + mutex_unlock(&video->mutex); > + > + return ret == -ENOIOCTLCMD ? -EINVAL : ret; > +} > + > +static int > +iss_video_get_crop(struct file *file, void *fh, struct v4l2_crop *crop) > +{ > + struct iss_video *video = video_drvdata(file); > + struct v4l2_subdev_format format; > + struct v4l2_subdev *subdev; > + u32 pad; > + int ret; > + > + subdev = iss_video_remote_subdev(video, &pad); > + if (subdev == NULL) > + return -EINVAL; > + > + /* Try the get crop operation first and fallback to get format if not > + * implemented. > + */ > + ret = v4l2_subdev_call(subdev, video, g_crop, crop); > + if (ret != -ENOIOCTLCMD) > + return ret; > + > + format.pad = pad; > + format.which = V4L2_SUBDEV_FORMAT_ACTIVE; > + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &format); > + if (ret < 0) > + return ret == -ENOIOCTLCMD ? -EINVAL : ret; > + > + crop->c.left = 0; > + crop->c.top = 0; > + crop->c.width = format.format.width; > + crop->c.height = format.format.height; > + > + return 0; > +} > + > +static int > +iss_video_set_crop(struct file *file, void *fh, struct v4l2_crop *crop) > +{ > + struct iss_video *video = video_drvdata(file); > + struct v4l2_subdev *subdev; > + int ret; > + > + subdev = iss_video_remote_subdev(video, NULL); > + if (subdev == NULL) > + return -EINVAL; > + > + mutex_lock(&video->mutex); > + ret = v4l2_subdev_call(subdev, video, s_crop, crop); > + mutex_unlock(&video->mutex); > + > + return ret == -ENOIOCTLCMD ? -EINVAL : ret; > +} > + > +static int > +iss_video_get_param(struct file *file, void *fh, struct v4l2_streamparm *a) > +{ > + struct iss_video_fh *vfh = to_iss_video_fh(fh); > + struct iss_video *video = video_drvdata(file); > + > + if (video->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || > + video->type != a->type) > + return -EINVAL; > + > + memset(a, 0, sizeof(*a)); > + a->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; > + a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; > + a->parm.output.timeperframe = vfh->timeperframe; > + > + return 0; > +} > + > +static int > +iss_video_set_param(struct file *file, void *fh, struct v4l2_streamparm *a) > +{ > + struct iss_video_fh *vfh = to_iss_video_fh(fh); > + struct iss_video *video = video_drvdata(file); > + > + if (video->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || > + video->type != a->type) > + return -EINVAL; > + > + if (a->parm.output.timeperframe.denominator == 0) > + a->parm.output.timeperframe.denominator = 1; > + > + vfh->timeperframe = a->parm.output.timeperframe; > + > + return 0; > +} > + > +static int > +iss_video_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *rb) > +{ > + struct iss_video_fh *vfh = to_iss_video_fh(fh); > + > + return vb2_reqbufs(&vfh->queue, rb); > +} > + > +static int > +iss_video_querybuf(struct file *file, void *fh, struct v4l2_buffer *b) > +{ > + struct iss_video_fh *vfh = to_iss_video_fh(fh); > + > + return vb2_querybuf(&vfh->queue, b); > +} > + > +static int > +iss_video_qbuf(struct file *file, void *fh, struct v4l2_buffer *b) > +{ > + struct iss_video_fh *vfh = to_iss_video_fh(fh); > + > + return vb2_qbuf(&vfh->queue, b); > +} > + > +static int > +iss_video_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b) > +{ > + struct iss_video_fh *vfh = to_iss_video_fh(fh); > + > + return vb2_dqbuf(&vfh->queue, b, file->f_flags & O_NONBLOCK); > +} > + > +/* > + * Stream management > + * > + * Every ISS pipeline has a single input and a single output. The input can be > + * either a sensor or a video node. The output is always a video node. > + * > + * As every pipeline has an output video node, the ISS video objects at the > + * pipeline output stores the pipeline state. It tracks the streaming state of > + * both the input and output, as well as the availability of buffers. > + * > + * In sensor-to-memory mode, frames are always available at the pipeline input. > + * Starting the sensor usually requires I2C transfers and must be done in > + * interruptible context. The pipeline is started and stopped synchronously > + * to the stream on/off commands. All modules in the pipeline will get their > + * subdev set stream handler called. The module at the end of the pipeline must > + * delay starting the hardware until buffers are available at its output. > + * > + * In memory-to-memory mode, starting/stopping the stream requires > + * synchronization between the input and output. ISS modules can't be stopped > + * in the middle of a frame, and at least some of the modules seem to become > + * busy as soon as they're started, even if they don't receive a frame start > + * event. For that reason frames need to be processed in single-shot mode. The > + * driver needs to wait until a frame is completely processed and written to > + * memory before restarting the pipeline for the next frame. Pipelined > + * processing might be possible but requires more testing. > + * > + * Stream start must be delayed until buffers are available at both the input > + * and output. The pipeline must be started in the videobuf queue callback with > + * the buffers queue spinlock held. The modules subdev set stream operation must > + * not sleep. > + */ > +static int > +iss_video_streamon(struct file *file, void *fh, enum v4l2_buf_type type) > +{ > + struct iss_video_fh *vfh = to_iss_video_fh(fh); > + struct iss_video *video = video_drvdata(file); > + enum iss_pipeline_state state; > + struct iss_pipeline *pipe; > + struct iss_video *far_end; > + unsigned long flags; > + int ret; > + > + if (type != video->type) > + return -EINVAL; > + > + mutex_lock(&video->stream_lock); > + > + if (video->streaming) { > + mutex_unlock(&video->stream_lock); > + return -EBUSY; > + } > + > + /* Start streaming on the pipeline. No link touching an entity in the > + * pipeline can be activated or deactivated once streaming is started. > + */ > + pipe = video->video.entity.pipe > + ? to_iss_pipeline(&video->video.entity) : &video->pipe; > + media_entity_pipeline_start(&video->video.entity, &pipe->pipe); > + > + /* Verify that the currently configured format matches the output of > + * the connected subdev. > + */ > + ret = iss_video_check_format(video, vfh); > + if (ret < 0) > + goto error; > + > + video->bpl_padding = ret; > + video->bpl_value = vfh->format.fmt.pix.bytesperline; > + > + /* Find the ISS video node connected at the far end of the pipeline and > + * update the pipeline. > + */ > + far_end = iss_video_far_end(video); > + > + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { > + state = ISS_PIPELINE_STREAM_OUTPUT | ISS_PIPELINE_IDLE_OUTPUT; > + pipe->input = far_end; > + pipe->output = video; > + } else { > + if (far_end == NULL) { > + ret = -EPIPE; > + goto error; > + } > + > + state = ISS_PIPELINE_STREAM_INPUT | ISS_PIPELINE_IDLE_INPUT; > + pipe->input = video; > + pipe->output = far_end; > + } > + > + if (video->iss->pdata->set_constraints) > + video->iss->pdata->set_constraints(video->iss, true); > + > + /* Validate the pipeline and update its state. */ > + ret = iss_video_validate_pipeline(pipe); > + if (ret < 0) > + goto error; > + > + spin_lock_irqsave(&pipe->lock, flags); > + pipe->state &= ~ISS_PIPELINE_STREAM; > + pipe->state |= state; > + spin_unlock_irqrestore(&pipe->lock, flags); > + > + /* Set the maximum time per frame as the value requested by userspace. > + * This is a soft limit that can be overridden if the hardware doesn't > + * support the request limit. > + */ > + if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) > + pipe->max_timeperframe = vfh->timeperframe; > + > + video->queue = &vfh->queue; > + INIT_LIST_HEAD(&video->dmaqueue); > + spin_lock_init(&video->qlock); > + atomic_set(&pipe->frame_number, -1); > + > + ret = vb2_streamon(&vfh->queue, type); > + if (ret < 0) > + goto error; > + > + /* In sensor-to-memory mode, the stream can be started synchronously > + * to the stream on command. In memory-to-memory mode, it will be > + * started when buffers are queued on both the input and output. > + */ > + if (pipe->input == NULL) { > + unsigned long flags; > + ret = omap4iss_pipeline_set_stream(pipe, > + ISS_PIPELINE_STREAM_CONTINUOUS); > + if (ret < 0) > + goto error; > + spin_lock_irqsave(&video->qlock, flags); > + if (list_empty(&video->dmaqueue)) > + video->dmaqueue_flags |= ISS_VIDEO_DMAQUEUE_UNDERRUN; > + spin_unlock_irqrestore(&video->qlock, flags); > + } > + > +error: > + if (ret < 0) { > + vb2_streamoff(&vfh->queue, type); > + if (video->iss->pdata->set_constraints) > + video->iss->pdata->set_constraints(video->iss, false); > + media_entity_pipeline_stop(&video->video.entity); > + video->queue = NULL; > + } > + > + if (!ret) > + video->streaming = 1; > + > + mutex_unlock(&video->stream_lock); > + return ret; > +} > + > +static int > +iss_video_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) > +{ > + struct iss_video_fh *vfh = to_iss_video_fh(fh); > + struct iss_video *video = video_drvdata(file); > + struct iss_pipeline *pipe = to_iss_pipeline(&video->video.entity); > + enum iss_pipeline_state state; > + unsigned long flags; > + > + if (type != video->type) > + return -EINVAL; > + > + mutex_lock(&video->stream_lock); > + > + if (!vb2_is_streaming(&vfh->queue)) > + goto done; > + > + /* Update the pipeline state. */ > + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) > + state = ISS_PIPELINE_STREAM_OUTPUT > + | ISS_PIPELINE_QUEUE_OUTPUT; > + else > + state = ISS_PIPELINE_STREAM_INPUT > + | ISS_PIPELINE_QUEUE_INPUT; > + > + spin_lock_irqsave(&pipe->lock, flags); > + pipe->state &= ~state; > + spin_unlock_irqrestore(&pipe->lock, flags); > + > + /* Stop the stream. */ > + omap4iss_pipeline_set_stream(pipe, ISS_PIPELINE_STREAM_STOPPED); > + vb2_streamoff(&vfh->queue, type); > + video->queue = NULL; > + video->streaming = 0; > + > + if (video->iss->pdata->set_constraints) > + video->iss->pdata->set_constraints(video->iss, false); > + media_entity_pipeline_stop(&video->video.entity); > + > +done: > + mutex_unlock(&video->stream_lock); > + return 0; > +} > + > +static int > +iss_video_enum_input(struct file *file, void *fh, struct v4l2_input *input) > +{ > + if (input->index > 0) > + return -EINVAL; > + > + strlcpy(input->name, "camera", sizeof(input->name)); > + input->type = V4L2_INPUT_TYPE_CAMERA; > + > + return 0; > +} > + > +static int > +iss_video_g_input(struct file *file, void *fh, unsigned int *input) > +{ > + *input = 0; > + > + return 0; > +} > + > +static int > +iss_video_s_input(struct file *file, void *fh, unsigned int input) > +{ > + return input == 0 ? 0 : -EINVAL; > +} > + > +static const struct v4l2_ioctl_ops iss_video_ioctl_ops = { > + .vidioc_querycap = iss_video_querycap, > + .vidioc_g_fmt_vid_cap = iss_video_get_format, > + .vidioc_s_fmt_vid_cap = iss_video_set_format, > + .vidioc_try_fmt_vid_cap = iss_video_try_format, > + .vidioc_g_fmt_vid_out = iss_video_get_format, > + .vidioc_s_fmt_vid_out = iss_video_set_format, > + .vidioc_try_fmt_vid_out = iss_video_try_format, > + .vidioc_cropcap = iss_video_cropcap, > + .vidioc_g_crop = iss_video_get_crop, > + .vidioc_s_crop = iss_video_set_crop, > + .vidioc_g_parm = iss_video_get_param, > + .vidioc_s_parm = iss_video_set_param, > + .vidioc_reqbufs = iss_video_reqbufs, > + .vidioc_querybuf = iss_video_querybuf, > + .vidioc_qbuf = iss_video_qbuf, > + .vidioc_dqbuf = iss_video_dqbuf, > + .vidioc_streamon = iss_video_streamon, > + .vidioc_streamoff = iss_video_streamoff, > + .vidioc_enum_input = iss_video_enum_input, > + .vidioc_g_input = iss_video_g_input, > + .vidioc_s_input = iss_video_s_input, > +}; > + > +/* ----------------------------------------------------------------------------- > + * V4L2 file operations > + */ > + > +static int iss_video_open(struct file *file) > +{ > + struct iss_video *video = video_drvdata(file); > + struct iss_video_fh *handle; > + struct vb2_queue *q; > + int ret = 0; > + > + handle = kzalloc(sizeof(*handle), GFP_KERNEL); > + if (handle == NULL) > + return -ENOMEM; > + > + v4l2_fh_init(&handle->vfh, &video->video); > + v4l2_fh_add(&handle->vfh); > + > + /* If this is the first user, initialise the pipeline. */ > + if (omap4iss_get(video->iss) == NULL) { > + ret = -EBUSY; > + goto done; > + } > + > + ret = omap4iss_pipeline_pm_use(&video->video.entity, 1); > + if (ret < 0) { > + omap4iss_put(video->iss); > + goto done; > + } > + > + video->alloc_ctx = vb2_dma_contig_init_ctx(video->iss->dev); > + if (IS_ERR(video->alloc_ctx)) { > + ret = PTR_ERR(video->alloc_ctx); > + omap4iss_put(video->iss); > + goto done; > + } > + > + q = &handle->queue; > + > + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > + q->io_modes = VB2_MMAP; > + q->drv_priv = handle; > + q->ops = &iss_video_vb2ops; > + q->mem_ops = &vb2_dma_contig_memops; > + q->buf_struct_size = sizeof(struct iss_buffer); > + > + ret = vb2_queue_init(q); > + if (ret) { > + omap4iss_put(video->iss); > + goto done; > + } > + > + memset(&handle->format, 0, sizeof(handle->format)); > + handle->format.type = video->type; > + handle->timeperframe.denominator = 1; > + > + handle->video = video; > + file->private_data = &handle->vfh; > + > +done: > + if (ret < 0) { > + v4l2_fh_del(&handle->vfh); > + kfree(handle); > + } > + > + return ret; > +} > + > +static int iss_video_release(struct file *file) > +{ > + struct iss_video *video = video_drvdata(file); > + struct v4l2_fh *vfh = file->private_data; > + struct iss_video_fh *handle = to_iss_video_fh(vfh); > + > + /* Disable streaming and free the buffers queue resources. */ > + iss_video_streamoff(file, vfh, video->type); > + > + omap4iss_pipeline_pm_use(&video->video.entity, 0); > + > + /* Release the file handle. */ > + v4l2_fh_del(vfh); > + kfree(handle); > + file->private_data = NULL; > + > + omap4iss_put(video->iss); > + > + return 0; > +} > + > +static unsigned int iss_video_poll(struct file *file, poll_table *wait) > +{ > + struct iss_video_fh *vfh = to_iss_video_fh(file->private_data); > + > + return vb2_poll(&vfh->queue, file, wait); > +} > + > +static int iss_video_mmap(struct file *file, struct vm_area_struct *vma) > +{ > + struct iss_video_fh *vfh = to_iss_video_fh(file->private_data); > + > + return vb2_mmap(&vfh->queue, vma);; > +} > + > +static struct v4l2_file_operations iss_video_fops = { > + .owner = THIS_MODULE, > + .unlocked_ioctl = video_ioctl2, > + .open = iss_video_open, > + .release = iss_video_release, > + .poll = iss_video_poll, > + .mmap = iss_video_mmap, > +}; > + > +/* ----------------------------------------------------------------------------- > + * ISS video core > + */ > + > +static const struct iss_video_operations iss_video_dummy_ops = { > +}; > + > +int omap4iss_video_init(struct iss_video *video, const char *name) > +{ > + const char *direction; > + int ret; > + > + switch (video->type) { > + case V4L2_BUF_TYPE_VIDEO_CAPTURE: > + direction = "output"; > + video->pad.flags = MEDIA_PAD_FL_SINK; > + break; > + case V4L2_BUF_TYPE_VIDEO_OUTPUT: > + direction = "input"; > + video->pad.flags = MEDIA_PAD_FL_SOURCE; > + break; > + > + default: > + return -EINVAL; > + } > + > + ret = media_entity_init(&video->video.entity, 1, &video->pad, 0); > + if (ret < 0) > + return ret; > + > + mutex_init(&video->mutex); > + atomic_set(&video->active, 0); > + > + spin_lock_init(&video->pipe.lock); > + mutex_init(&video->stream_lock); > + > + /* Initialize the video device. */ > + if (video->ops == NULL) > + video->ops = &iss_video_dummy_ops; > + > + video->video.fops = &iss_video_fops; > + snprintf(video->video.name, sizeof(video->video.name), > + "OMAP4 ISS %s %s", name, direction); > + video->video.vfl_type = VFL_TYPE_GRABBER; > + video->video.release = video_device_release_empty; > + video->video.ioctl_ops = &iss_video_ioctl_ops; > + video->pipe.stream_state = ISS_PIPELINE_STREAM_STOPPED; > + > + video_set_drvdata(&video->video, video); > + > + return 0; > +} > + > +int omap4iss_video_register(struct iss_video *video, struct v4l2_device *vdev) > +{ > + int ret; > + > + video->video.v4l2_dev = vdev; > + > + ret = video_register_device(&video->video, VFL_TYPE_GRABBER, -1); > + if (ret < 0) > + printk(KERN_ERR "%s: could not register video device (%d)\n", > + __func__, ret); > + > + return ret; > +} > + > +void omap4iss_video_unregister(struct iss_video *video) > +{ > + if (video_is_registered(&video->video)) { > + media_entity_cleanup(&video->video.entity); > + video_unregister_device(&video->video); > + } > +} > diff --git a/drivers/media/video/omap4iss/iss_video.h b/drivers/media/video/omap4iss/iss_video.h > new file mode 100644 > index 0000000..fc123b0 > --- /dev/null > +++ b/drivers/media/video/omap4iss/iss_video.h > @@ -0,0 +1,205 @@ > +/* > + * iss_video.h > + * > + * TI OMAP4 ISS - Generic video node > + * > + * Copyright (C) 2011 Texas Instruments, Inc. > + * > + * Contacts: Sergio Aguirre <saaguirre@xxxxxx> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > + > +#ifndef OMAP4_ISS_VIDEO_H > +#define OMAP4_ISS_VIDEO_H > + > +#include <linux/v4l2-mediabus.h> > +#include <linux/version.h> > +#include <media/media-entity.h> > +#include <media/v4l2-dev.h> > +#include <media/v4l2-fh.h> > +#include <media/videobuf2-core.h> > +#include <media/videobuf2-dma-contig.h> > + > +#define ISS_VIDEO_DRIVER_NAME "issvideo" > +#define ISS_VIDEO_DRIVER_VERSION KERNEL_VERSION(0, 0, 1) > + > +struct iss_device; > +struct iss_video; > +struct v4l2_mbus_framefmt; > +struct v4l2_pix_format; > + > +/* > + * struct iss_format_info - ISS media bus format information > + * @code: V4L2 media bus format code > + * @truncated: V4L2 media bus format code for the same format truncated to 10 > + * bits. Identical to @code if the format is 10 bits wide or less. > + * @uncompressed: V4L2 media bus format code for the corresponding uncompressed > + * format. Identical to @code if the format is not DPCM compressed. > + * @flavor: V4L2 media bus format code for the same pixel layout but > + * shifted to be 8 bits per pixel. =0 if format is not shiftable. > + * @pixelformat: V4L2 pixel format FCC identifier > + * @bpp: Bits per pixel > + */ > +struct iss_format_info { > + enum v4l2_mbus_pixelcode code; > + enum v4l2_mbus_pixelcode truncated; > + enum v4l2_mbus_pixelcode uncompressed; > + enum v4l2_mbus_pixelcode flavor; > + u32 pixelformat; > + unsigned int bpp; > +}; > + > +enum iss_pipeline_stream_state { > + ISS_PIPELINE_STREAM_STOPPED = 0, > + ISS_PIPELINE_STREAM_CONTINUOUS = 1, > + ISS_PIPELINE_STREAM_SINGLESHOT = 2, > +}; > + > +enum iss_pipeline_state { > + /* The stream has been started on the input video node. */ > + ISS_PIPELINE_STREAM_INPUT = 1, > + /* The stream has been started on the output video node. */ > + ISS_PIPELINE_STREAM_OUTPUT = 2, > + /* At least one buffer is queued on the input video node. */ > + ISS_PIPELINE_QUEUE_INPUT = 4, > + /* At least one buffer is queued on the output video node. */ > + ISS_PIPELINE_QUEUE_OUTPUT = 8, > + /* The input entity is idle, ready to be started. */ > + ISS_PIPELINE_IDLE_INPUT = 16, > + /* The output entity is idle, ready to be started. */ > + ISS_PIPELINE_IDLE_OUTPUT = 32, > + /* The pipeline is currently streaming. */ > + ISS_PIPELINE_STREAM = 64, > +}; > + > +struct iss_pipeline { > + struct media_pipeline pipe; > + spinlock_t lock; /* Pipeline state and queue flags */ > + unsigned int state; > + enum iss_pipeline_stream_state stream_state; > + struct iss_video *input; > + struct iss_video *output; > + atomic_t frame_number; > + bool do_propagation; /* of frame number */ > + struct v4l2_fract max_timeperframe; > +}; > + > +#define to_iss_pipeline(__e) \ > + container_of((__e)->pipe, struct iss_pipeline, pipe) > + > +static inline int iss_pipeline_ready(struct iss_pipeline *pipe) > +{ > + return pipe->state == (ISS_PIPELINE_STREAM_INPUT | > + ISS_PIPELINE_STREAM_OUTPUT | > + ISS_PIPELINE_QUEUE_INPUT | > + ISS_PIPELINE_QUEUE_OUTPUT | > + ISS_PIPELINE_IDLE_INPUT | > + ISS_PIPELINE_IDLE_OUTPUT); > +} > + > +/* > + * struct iss_buffer - ISS buffer > + * @buffer: ISS video buffer > + * @iss_addr: Physical address of the buffer. > + */ > +struct iss_buffer { > + /* common v4l buffer stuff -- must be first */ > + struct vb2_buffer vb; > + struct list_head list; > + dma_addr_t iss_addr; > +}; > + > +#define to_iss_buffer(buf) container_of(buf, struct iss_buffer, buffer) > + > +enum iss_video_dmaqueue_flags { > + /* Set if DMA queue becomes empty when ISS_PIPELINE_STREAM_CONTINUOUS */ > + ISS_VIDEO_DMAQUEUE_UNDERRUN = (1 << 0), > + /* Set when queuing buffer to an empty DMA queue */ > + ISS_VIDEO_DMAQUEUE_QUEUED = (1 << 1), > +}; > + > +#define iss_video_dmaqueue_flags_clr(video) \ > + ({ (video)->dmaqueue_flags = 0; }) > + > +/* > + * struct iss_video_operations - ISS video operations > + * @queue: Resume streaming when a buffer is queued. Called on VIDIOC_QBUF > + * if there was no buffer previously queued. > + */ > +struct iss_video_operations { > + int(*queue)(struct iss_video *video, struct iss_buffer *buffer); > +}; > + > +struct iss_video { > + struct video_device video; > + enum v4l2_buf_type type; > + struct media_pad pad; > + > + struct mutex mutex; /* format and crop settings */ > + atomic_t active; > + > + struct iss_device *iss; > + > + unsigned int capture_mem; > + unsigned int bpl_alignment; /* alignment value */ > + unsigned int bpl_zero_padding; /* whether the alignment is optional */ > + unsigned int bpl_max; /* maximum bytes per line value */ > + unsigned int bpl_value; /* bytes per line value */ > + unsigned int bpl_padding; /* padding at end of line */ > + > + /* Entity video node streaming */ > + unsigned int streaming:1; > + > + /* Pipeline state */ > + struct iss_pipeline pipe; > + struct mutex stream_lock; /* pipeline and stream states */ > + > + /* Video buffers queue */ > + struct vb2_queue *queue; > + spinlock_t qlock; /* Spinlock for dmaqueue */ > + struct list_head dmaqueue; > + enum iss_video_dmaqueue_flags dmaqueue_flags; > + struct vb2_alloc_ctx *alloc_ctx; > + > + const struct iss_video_operations *ops; > +}; > + > +#define to_iss_video(vdev) container_of(vdev, struct iss_video, video) > + > +struct iss_video_fh { > + struct v4l2_fh vfh; > + struct iss_video *video; > + struct vb2_queue queue; > + struct v4l2_format format; > + struct v4l2_fract timeperframe; > +}; > + > +#define to_iss_video_fh(fh) container_of(fh, struct iss_video_fh, vfh) > +#define iss_video_queue_to_iss_video_fh(q) \ > + container_of(q, struct iss_video_fh, queue) > + > +int omap4iss_video_init(struct iss_video *video, const char *name); > +int omap4iss_video_register(struct iss_video *video, > + struct v4l2_device *vdev); > +void omap4iss_video_unregister(struct iss_video *video); > +struct iss_buffer *omap4iss_video_buffer_next(struct iss_video *video, > + unsigned int error); > +struct media_pad *omap4iss_video_remote_pad(struct iss_video *video); > + > +const struct iss_format_info * > +omap4iss_video_format_info(enum v4l2_mbus_pixelcode code); > + > +#endif /* OMAP4_ISS_VIDEO_H */ > -- > 1.7.7.4 > -- Sakari Ailus e-mail: sakari.ailus@xxxxxx jabber/XMPP/Gmail: sailus@xxxxxxxxxxxxxx -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html