Hi Sakari, On Sat, Jan 14, 2012 at 11:51 AM, Sakari Ailus <sakari.ailus@xxxxxx> wrote: > Hi Sergio, > > Thanks again for the patch! My previous review escaped me too fast. The rest > is here. No problem. Thanks for reviewing such a long patch :) > > 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 + >> 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); >> +}; >> + >> +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"); >> +} >> + >> +/* >> + * 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); >> +} > > Do you really, really need to save context related data? Couldn't you > initialise the device the usual way when e.g. returning from suspended > state? I'll actually have to revisit this more carefuly. I haven't really tested Runtime PM on this. I think I'll remove this for now, until i come up with something better. > >> +/* >> + * 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(); >> + >> + /* >> + * 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)) { >> + 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) { > > The value must be decremented before the comparison. Otherwise the result > could be negative, and the check a few lines below will fail even if the > timeout happened. Oops yes. Missed that. Will fix. > > I guess it's not a crime to use a for loop either in situations like this. Ok. > >> + 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; > > You can return the error here and your error handling below becomes more > simple. Right. Will fix. > >> + } >> + >> + 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); > > Why printk? I think you already have access to struct device. You're right. Will fix. > >> + 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; > > If there's nothing special you need to do here, I suggest to return the > error where it happened. Agreed. Will fix. > >> +} >> + >> +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> >> + */ > > Have you intentionally left out the more verbose notice? It's also 2012 now. > :) :) Not intentional... Will fix. > >> +#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 /* > > Minor thing, but lowercase hex are preferred. I'll actually get rid of this, along with the context save-restore for now. > >> + * 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); >> +}; > > You might want to look my recent patchset (a few days ago). You can probably > get rid of this callback easily. Ok. I'll pay attention to it. > >> +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; > > Hmm. Is it just me or is this function unused? Oops, yes. Will fix. > >> + 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); > > Is this register accessed elsewhere? If not, you could skip reading it and > just write the value there. This was very common in the original OMAP 3 ISP > driver. :) Right. Will fix. > >> +} >> + >> +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)); > > CTX_CTRL2 is only used in this function. I don't think you'd need to read it > back. How about other CTX_CTRL regs? Right. Will fix. > >> + 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++) { > > 8 == number of contexts? Yes. This loops from 0 to 7. Are you implying that I need to add a define to this? > >> + 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); > > How about usleep_range() here? Or omit such a long busyloop. Hardware > typically resets quite fast. I guess i'll try to fine-tune this then. > >> + } 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); > > Same here. Ok. Will do too. > >> + } 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"); > > _If_ this is user-triggered, you might want to consider using debugfs for > the same. Just FYI. Ok. I'll see what can I do. > >> +} >> + >> +/* ----------------------------------------------------------------------------- >> + * 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); > > Is configuration of the context needed elsewhere than in streamon? What > changes while streaming? Nothing I think... Same thing is done for OMAP3, so I tried to keep the driver as similar as possible. I'll try removing this then. > >> + 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, > > What are the USERDEF formats? Again, inherited and adapted these from OMAP3 driver. Should I delete them? > >> +}; >> + >> +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); > > I guess udelay(1) should do. There's no reason to busyloop longer, if at > all. How much is a typical value for retry_count? Ok. Will check this, not sure what's the count. > >> + 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); > > REGISTER0 or 1 aren't really used elsewhere. You could omit reading them > back. Right. Will fix. > >> +} >> + >> +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) > > No need for parenthesis for hexadecimals. Ok. Will fix. > >> +#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, }, > > I had a few patches (not acked yet) to add more supported media bus formats. > It might make sense to add support for those for this driver as well once > we've discussed 4cc's with Hans. Ok. Will wait for that then. > >> + { 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; >> +} > > You won't need pipeline validation function anymore. This will be moved to > media controller. The patches for the OMAP 3 ISP are one the list: > > <URL:http://www.spinics.net/lists/linux-media/msg42991.html> Ok. Will wait for this to be accepted then. > >> +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; >> +} > > While it's possible to support s_input support it provides no useful > functionality for the user. The OMAP 3 ISP driver also supports it. I'd just > remove it. Ok. Will fix. > > The same will be properly provided to apps by the libv4l2pile(auto) in the > future. > >> +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, > > I'd suggest using 1 << n to define flags instead. Agreed. Will fix. > >> +}; >> + >> +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; > > Format shouldn't be part of the file handle anymore. There was a reason for > it in the past before PREPARE_BUF --- which is also supported by videobuf2. Ok. So should I just remove it completely? Sorry if i'm not understanding this PREPARE_BUF thing... What should I have there? Regards, Sergio > >> + 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 */ > > Cheers, > > -- > 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