diff options
Diffstat (limited to 'drivers/media/platform/ti/omap3isp/ispccp2.c')
-rw-r--r-- | drivers/media/platform/ti/omap3isp/ispccp2.c | 1181 |
1 files changed, 1181 insertions, 0 deletions
diff --git a/drivers/media/platform/ti/omap3isp/ispccp2.c b/drivers/media/platform/ti/omap3isp/ispccp2.c new file mode 100644 index 000000000..fc90ff884 --- /dev/null +++ b/drivers/media/platform/ti/omap3isp/ispccp2.c @@ -0,0 +1,1181 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ispccp2.c + * + * TI OMAP3 ISP - CCP2 module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Texas Instruments, Inc. + * + * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + * Sakari Ailus <sakari.ailus@iki.fi> + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <linux/regulator/consumer.h> + +#include "isp.h" +#include "ispreg.h" +#include "ispccp2.h" + +/* Number of LCX channels */ +#define CCP2_LCx_CHANS_NUM 3 +/* Max/Min size for CCP2 video port */ +#define ISPCCP2_DAT_START_MIN 0 +#define ISPCCP2_DAT_START_MAX 4095 +#define ISPCCP2_DAT_SIZE_MIN 0 +#define ISPCCP2_DAT_SIZE_MAX 4095 +#define ISPCCP2_VPCLK_FRACDIV 65536 +#define ISPCCP2_LCx_CTRL_FORMAT_RAW8_DPCM10_VP 0x12 +#define ISPCCP2_LCx_CTRL_FORMAT_RAW10_VP 0x16 +/* Max/Min size for CCP2 memory channel */ +#define ISPCCP2_LCM_HSIZE_COUNT_MIN 16 +#define ISPCCP2_LCM_HSIZE_COUNT_MAX 8191 +#define ISPCCP2_LCM_HSIZE_SKIP_MIN 0 +#define ISPCCP2_LCM_HSIZE_SKIP_MAX 8191 +#define ISPCCP2_LCM_VSIZE_MIN 1 +#define ISPCCP2_LCM_VSIZE_MAX 8191 +#define ISPCCP2_LCM_HWORDS_MIN 1 +#define ISPCCP2_LCM_HWORDS_MAX 4095 +#define ISPCCP2_LCM_CTRL_BURST_SIZE_32X 5 +#define ISPCCP2_LCM_CTRL_READ_THROTTLE_FULL 0 +#define ISPCCP2_LCM_CTRL_SRC_DECOMPR_DPCM10 2 +#define ISPCCP2_LCM_CTRL_SRC_FORMAT_RAW8 2 +#define ISPCCP2_LCM_CTRL_SRC_FORMAT_RAW10 3 +#define ISPCCP2_LCM_CTRL_DST_FORMAT_RAW10 3 +#define ISPCCP2_LCM_CTRL_DST_PORT_VP 0 +#define ISPCCP2_LCM_CTRL_DST_PORT_MEM 1 + +/* Set only the required bits */ +#define BIT_SET(var, shift, mask, val) \ + do { \ + var = ((var) & ~((mask) << (shift))) \ + | ((val) << (shift)); \ + } while (0) + +/* + * ccp2_print_status - Print current CCP2 module register values. + */ +#define CCP2_PRINT_REGISTER(isp, name)\ + dev_dbg(isp->dev, "###CCP2 " #name "=0x%08x\n", \ + isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_##name)) + +static void ccp2_print_status(struct isp_ccp2_device *ccp2) +{ + struct isp_device *isp = to_isp_device(ccp2); + + dev_dbg(isp->dev, "-------------CCP2 Register dump-------------\n"); + + CCP2_PRINT_REGISTER(isp, SYSCONFIG); + CCP2_PRINT_REGISTER(isp, SYSSTATUS); + CCP2_PRINT_REGISTER(isp, LC01_IRQENABLE); + CCP2_PRINT_REGISTER(isp, LC01_IRQSTATUS); + CCP2_PRINT_REGISTER(isp, LC23_IRQENABLE); + CCP2_PRINT_REGISTER(isp, LC23_IRQSTATUS); + CCP2_PRINT_REGISTER(isp, LCM_IRQENABLE); + CCP2_PRINT_REGISTER(isp, LCM_IRQSTATUS); + CCP2_PRINT_REGISTER(isp, CTRL); + CCP2_PRINT_REGISTER(isp, LCx_CTRL(0)); + CCP2_PRINT_REGISTER(isp, LCx_CODE(0)); + CCP2_PRINT_REGISTER(isp, LCx_STAT_START(0)); + CCP2_PRINT_REGISTER(isp, LCx_STAT_SIZE(0)); + CCP2_PRINT_REGISTER(isp, LCx_SOF_ADDR(0)); + CCP2_PRINT_REGISTER(isp, LCx_EOF_ADDR(0)); + CCP2_PRINT_REGISTER(isp, LCx_DAT_START(0)); + CCP2_PRINT_REGISTER(isp, LCx_DAT_SIZE(0)); + CCP2_PRINT_REGISTER(isp, LCx_DAT_PING_ADDR(0)); + CCP2_PRINT_REGISTER(isp, LCx_DAT_PONG_ADDR(0)); + CCP2_PRINT_REGISTER(isp, LCx_DAT_OFST(0)); + CCP2_PRINT_REGISTER(isp, LCM_CTRL); + CCP2_PRINT_REGISTER(isp, LCM_VSIZE); + CCP2_PRINT_REGISTER(isp, LCM_HSIZE); + CCP2_PRINT_REGISTER(isp, LCM_PREFETCH); + CCP2_PRINT_REGISTER(isp, LCM_SRC_ADDR); + CCP2_PRINT_REGISTER(isp, LCM_SRC_OFST); + CCP2_PRINT_REGISTER(isp, LCM_DST_ADDR); + CCP2_PRINT_REGISTER(isp, LCM_DST_OFST); + + dev_dbg(isp->dev, "--------------------------------------------\n"); +} + +/* + * ccp2_reset - Reset the CCP2 + * @ccp2: pointer to ISP CCP2 device + */ +static void ccp2_reset(struct isp_ccp2_device *ccp2) +{ + struct isp_device *isp = to_isp_device(ccp2); + int i = 0; + + /* Reset the CSI1/CCP2B and wait for reset to complete */ + isp_reg_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_SYSCONFIG, + ISPCCP2_SYSCONFIG_SOFT_RESET); + while (!(isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_SYSSTATUS) & + ISPCCP2_SYSSTATUS_RESET_DONE)) { + udelay(10); + if (i++ > 10) { /* try read 10 times */ + dev_warn(isp->dev, + "omap3_isp: timeout waiting for ccp2 reset\n"); + break; + } + } +} + +/* + * ccp2_pwr_cfg - Configure the power mode settings + * @ccp2: pointer to ISP CCP2 device + */ +static void ccp2_pwr_cfg(struct isp_ccp2_device *ccp2) +{ + struct isp_device *isp = to_isp_device(ccp2); + + isp_reg_writel(isp, ISPCCP2_SYSCONFIG_MSTANDBY_MODE_SMART | + ((isp->revision == ISP_REVISION_15_0 && isp->autoidle) ? + ISPCCP2_SYSCONFIG_AUTO_IDLE : 0), + OMAP3_ISP_IOMEM_CCP2, ISPCCP2_SYSCONFIG); +} + +/* + * ccp2_if_enable - Enable CCP2 interface. + * @ccp2: pointer to ISP CCP2 device + * @enable: enable/disable flag + */ +static int ccp2_if_enable(struct isp_ccp2_device *ccp2, u8 enable) +{ + struct isp_device *isp = to_isp_device(ccp2); + int ret; + int i; + + if (enable && ccp2->vdds_csib) { + ret = regulator_enable(ccp2->vdds_csib); + if (ret < 0) + return ret; + } + + /* Enable/Disable all the LCx channels */ + for (i = 0; i < CCP2_LCx_CHANS_NUM; i++) + isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCx_CTRL(i), + ISPCCP2_LCx_CTRL_CHAN_EN, + enable ? ISPCCP2_LCx_CTRL_CHAN_EN : 0); + + /* Enable/Disable ccp2 interface in ccp2 mode */ + isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL, + ISPCCP2_CTRL_MODE | ISPCCP2_CTRL_IF_EN, + enable ? (ISPCCP2_CTRL_MODE | ISPCCP2_CTRL_IF_EN) : 0); + + if (!enable && ccp2->vdds_csib) + regulator_disable(ccp2->vdds_csib); + + return 0; +} + +/* + * ccp2_mem_enable - Enable CCP2 memory interface. + * @ccp2: pointer to ISP CCP2 device + * @enable: enable/disable flag + */ +static void ccp2_mem_enable(struct isp_ccp2_device *ccp2, u8 enable) +{ + struct isp_device *isp = to_isp_device(ccp2); + + if (enable) + ccp2_if_enable(ccp2, 0); + + /* Enable/Disable ccp2 interface in ccp2 mode */ + isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL, + ISPCCP2_CTRL_MODE, enable ? ISPCCP2_CTRL_MODE : 0); + + isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_CTRL, + ISPCCP2_LCM_CTRL_CHAN_EN, + enable ? ISPCCP2_LCM_CTRL_CHAN_EN : 0); +} + +/* + * ccp2_phyif_config - Initialize CCP2 phy interface config + * @ccp2: Pointer to ISP CCP2 device + * @buscfg: CCP2 platform data + * + * Configure the CCP2 physical interface module from platform data. + * + * Returns -EIO if strobe is chosen in CSI1 mode, or 0 on success. + */ +static int ccp2_phyif_config(struct isp_ccp2_device *ccp2, + const struct isp_ccp2_cfg *buscfg) +{ + struct isp_device *isp = to_isp_device(ccp2); + u32 val; + + val = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL) | + ISPCCP2_CTRL_MODE; + /* Data/strobe physical layer */ + BIT_SET(val, ISPCCP2_CTRL_PHY_SEL_SHIFT, ISPCCP2_CTRL_PHY_SEL_MASK, + buscfg->phy_layer); + BIT_SET(val, ISPCCP2_CTRL_IO_OUT_SEL_SHIFT, + ISPCCP2_CTRL_IO_OUT_SEL_MASK, buscfg->ccp2_mode); + BIT_SET(val, ISPCCP2_CTRL_INV_SHIFT, ISPCCP2_CTRL_INV_MASK, + buscfg->strobe_clk_pol); + BIT_SET(val, ISPCCP2_CTRL_VP_CLK_POL_SHIFT, + ISPCCP2_CTRL_VP_CLK_POL_MASK, buscfg->vp_clk_pol); + isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL); + + val = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL); + if (!(val & ISPCCP2_CTRL_MODE)) { + if (buscfg->ccp2_mode == ISP_CCP2_MODE_CCP2) + dev_warn(isp->dev, "OMAP3 CCP2 bus not available\n"); + if (buscfg->phy_layer == ISP_CCP2_PHY_DATA_STROBE) + /* Strobe mode requires CCP2 */ + return -EIO; + } + + return 0; +} + +/* + * ccp2_vp_config - Initialize CCP2 video port interface. + * @ccp2: Pointer to ISP CCP2 device + * @vpclk_div: Video port divisor + * + * Configure the CCP2 video port with the given clock divisor. The valid divisor + * values depend on the ISP revision: + * + * - revision 1.0 and 2.0 1 to 4 + * - revision 15.0 1 to 65536 + * + * The exact divisor value used might differ from the requested value, as ISP + * revision 15.0 represent the divisor by 65536 divided by an integer. + */ +static void ccp2_vp_config(struct isp_ccp2_device *ccp2, + unsigned int vpclk_div) +{ + struct isp_device *isp = to_isp_device(ccp2); + u32 val; + + /* ISPCCP2_CTRL Video port */ + val = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL); + val |= ISPCCP2_CTRL_VP_ONLY_EN; /* Disable the memory write port */ + + if (isp->revision == ISP_REVISION_15_0) { + vpclk_div = clamp_t(unsigned int, vpclk_div, 1, 65536); + vpclk_div = min(ISPCCP2_VPCLK_FRACDIV / vpclk_div, 65535U); + BIT_SET(val, ISPCCP2_CTRL_VPCLK_DIV_SHIFT, + ISPCCP2_CTRL_VPCLK_DIV_MASK, vpclk_div); + } else { + vpclk_div = clamp_t(unsigned int, vpclk_div, 1, 4); + BIT_SET(val, ISPCCP2_CTRL_VP_OUT_CTRL_SHIFT, + ISPCCP2_CTRL_VP_OUT_CTRL_MASK, vpclk_div - 1); + } + + isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL); +} + +/* + * ccp2_lcx_config - Initialize CCP2 logical channel interface. + * @ccp2: Pointer to ISP CCP2 device + * @config: Pointer to ISP LCx config structure. + * + * This will analyze the parameters passed by the interface config + * and configure CSI1/CCP2 logical channel + * + */ +static void ccp2_lcx_config(struct isp_ccp2_device *ccp2, + struct isp_interface_lcx_config *config) +{ + struct isp_device *isp = to_isp_device(ccp2); + u32 val, format; + + switch (config->format) { + case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8: + format = ISPCCP2_LCx_CTRL_FORMAT_RAW8_DPCM10_VP; + break; + case MEDIA_BUS_FMT_SGRBG10_1X10: + default: + format = ISPCCP2_LCx_CTRL_FORMAT_RAW10_VP; /* RAW10+VP */ + break; + } + /* ISPCCP2_LCx_CTRL logical channel #0 */ + val = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCx_CTRL(0)) + | (ISPCCP2_LCx_CTRL_REGION_EN); /* Region */ + + if (isp->revision == ISP_REVISION_15_0) { + /* CRC */ + BIT_SET(val, ISPCCP2_LCx_CTRL_CRC_SHIFT_15_0, + ISPCCP2_LCx_CTRL_CRC_MASK, + config->crc); + /* Format = RAW10+VP or RAW8+DPCM10+VP*/ + BIT_SET(val, ISPCCP2_LCx_CTRL_FORMAT_SHIFT_15_0, + ISPCCP2_LCx_CTRL_FORMAT_MASK_15_0, format); + } else { + BIT_SET(val, ISPCCP2_LCx_CTRL_CRC_SHIFT, + ISPCCP2_LCx_CTRL_CRC_MASK, + config->crc); + + BIT_SET(val, ISPCCP2_LCx_CTRL_FORMAT_SHIFT, + ISPCCP2_LCx_CTRL_FORMAT_MASK, format); + } + isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCx_CTRL(0)); + + /* ISPCCP2_DAT_START for logical channel #0 */ + isp_reg_writel(isp, config->data_start << ISPCCP2_LCx_DAT_SHIFT, + OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCx_DAT_START(0)); + + /* ISPCCP2_DAT_SIZE for logical channel #0 */ + isp_reg_writel(isp, config->data_size << ISPCCP2_LCx_DAT_SHIFT, + OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCx_DAT_SIZE(0)); + + /* Enable error IRQs for logical channel #0 */ + val = ISPCCP2_LC01_IRQSTATUS_LC0_FIFO_OVF_IRQ | + ISPCCP2_LC01_IRQSTATUS_LC0_CRC_IRQ | + ISPCCP2_LC01_IRQSTATUS_LC0_FSP_IRQ | + ISPCCP2_LC01_IRQSTATUS_LC0_FW_IRQ | + ISPCCP2_LC01_IRQSTATUS_LC0_FSC_IRQ | + ISPCCP2_LC01_IRQSTATUS_LC0_SSC_IRQ; + + isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LC01_IRQSTATUS); + isp_reg_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LC01_IRQENABLE, val); +} + +/* + * ccp2_if_configure - Configure ccp2 with data from sensor + * @ccp2: Pointer to ISP CCP2 device + * + * Return 0 on success or a negative error code + */ +static int ccp2_if_configure(struct isp_ccp2_device *ccp2) +{ + struct isp_pipeline *pipe = to_isp_pipeline(&ccp2->subdev.entity); + const struct isp_bus_cfg *buscfg; + struct v4l2_mbus_framefmt *format; + struct media_pad *pad; + struct v4l2_subdev *sensor; + u32 lines = 0; + int ret; + + ccp2_pwr_cfg(ccp2); + + pad = media_pad_remote_pad_first(&ccp2->pads[CCP2_PAD_SINK]); + sensor = media_entity_to_v4l2_subdev(pad->entity); + buscfg = v4l2_subdev_to_bus_cfg(pipe->external); + + ret = ccp2_phyif_config(ccp2, &buscfg->bus.ccp2); + if (ret < 0) + return ret; + + ccp2_vp_config(ccp2, buscfg->bus.ccp2.vpclk_div + 1); + + v4l2_subdev_call(sensor, sensor, g_skip_top_lines, &lines); + + format = &ccp2->formats[CCP2_PAD_SINK]; + + ccp2->if_cfg.data_start = lines; + ccp2->if_cfg.crc = buscfg->bus.ccp2.crc; + ccp2->if_cfg.format = format->code; + ccp2->if_cfg.data_size = format->height; + + ccp2_lcx_config(ccp2, &ccp2->if_cfg); + + return 0; +} + +static int ccp2_adjust_bandwidth(struct isp_ccp2_device *ccp2) +{ + struct isp_pipeline *pipe = to_isp_pipeline(&ccp2->subdev.entity); + struct isp_device *isp = to_isp_device(ccp2); + const struct v4l2_mbus_framefmt *ofmt = &ccp2->formats[CCP2_PAD_SOURCE]; + unsigned long l3_ick = pipe->l3_ick; + struct v4l2_fract *timeperframe; + unsigned int vpclk_div = 2; + unsigned int value; + u64 bound; + u64 area; + + /* Compute the minimum clock divisor, based on the pipeline maximum + * data rate. This is an absolute lower bound if we don't want SBL + * overflows, so round the value up. + */ + vpclk_div = max_t(unsigned int, DIV_ROUND_UP(l3_ick, pipe->max_rate), + vpclk_div); + + /* Compute the maximum clock divisor, based on the requested frame rate. + * This is a soft lower bound to achieve a frame rate equal or higher + * than the requested value, so round the value down. + */ + timeperframe = &pipe->max_timeperframe; + + if (timeperframe->numerator) { + area = ofmt->width * ofmt->height; + bound = div_u64(area * timeperframe->denominator, + timeperframe->numerator); + value = min_t(u64, bound, l3_ick); + vpclk_div = max_t(unsigned int, l3_ick / value, vpclk_div); + } + + dev_dbg(isp->dev, "%s: minimum clock divisor = %u\n", __func__, + vpclk_div); + + return vpclk_div; +} + +/* + * ccp2_mem_configure - Initialize CCP2 memory input/output interface + * @ccp2: Pointer to ISP CCP2 device + * @config: Pointer to ISP mem interface config structure + * + * This will analyze the parameters passed by the interface config + * structure, and configure the respective registers for proper + * CSI1/CCP2 memory input. + */ +static void ccp2_mem_configure(struct isp_ccp2_device *ccp2, + struct isp_interface_mem_config *config) +{ + struct isp_device *isp = to_isp_device(ccp2); + u32 sink_pixcode = ccp2->formats[CCP2_PAD_SINK].code; + u32 source_pixcode = ccp2->formats[CCP2_PAD_SOURCE].code; + unsigned int dpcm_decompress = 0; + u32 val, hwords; + + if (sink_pixcode != source_pixcode && + sink_pixcode == MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8) + dpcm_decompress = 1; + + ccp2_pwr_cfg(ccp2); + + /* Hsize, Skip */ + isp_reg_writel(isp, ISPCCP2_LCM_HSIZE_SKIP_MIN | + (config->hsize_count << ISPCCP2_LCM_HSIZE_SHIFT), + OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_HSIZE); + + /* Vsize, no. of lines */ + isp_reg_writel(isp, config->vsize_count << ISPCCP2_LCM_VSIZE_SHIFT, + OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_VSIZE); + + if (ccp2->video_in.bpl_padding == 0) + config->src_ofst = 0; + else + config->src_ofst = ccp2->video_in.bpl_value; + + isp_reg_writel(isp, config->src_ofst, OMAP3_ISP_IOMEM_CCP2, + ISPCCP2_LCM_SRC_OFST); + + /* Source and Destination formats */ + val = ISPCCP2_LCM_CTRL_DST_FORMAT_RAW10 << + ISPCCP2_LCM_CTRL_DST_FORMAT_SHIFT; + + if (dpcm_decompress) { + /* source format is RAW8 */ + val |= ISPCCP2_LCM_CTRL_SRC_FORMAT_RAW8 << + ISPCCP2_LCM_CTRL_SRC_FORMAT_SHIFT; + + /* RAW8 + DPCM10 - simple predictor */ + val |= ISPCCP2_LCM_CTRL_SRC_DPCM_PRED; + + /* enable source DPCM decompression */ + val |= ISPCCP2_LCM_CTRL_SRC_DECOMPR_DPCM10 << + ISPCCP2_LCM_CTRL_SRC_DECOMPR_SHIFT; + } else { + /* source format is RAW10 */ + val |= ISPCCP2_LCM_CTRL_SRC_FORMAT_RAW10 << + ISPCCP2_LCM_CTRL_SRC_FORMAT_SHIFT; + } + + /* Burst size to 32x64 */ + val |= ISPCCP2_LCM_CTRL_BURST_SIZE_32X << + ISPCCP2_LCM_CTRL_BURST_SIZE_SHIFT; + + isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_CTRL); + + /* Prefetch setup */ + if (dpcm_decompress) + hwords = (ISPCCP2_LCM_HSIZE_SKIP_MIN + + config->hsize_count) >> 3; + else + hwords = (ISPCCP2_LCM_HSIZE_SKIP_MIN + + config->hsize_count) >> 2; + + isp_reg_writel(isp, hwords << ISPCCP2_LCM_PREFETCH_SHIFT, + OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_PREFETCH); + + /* Video port */ + isp_reg_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL, + ISPCCP2_CTRL_IO_OUT_SEL | ISPCCP2_CTRL_MODE); + ccp2_vp_config(ccp2, ccp2_adjust_bandwidth(ccp2)); + + /* Clear LCM interrupts */ + isp_reg_writel(isp, ISPCCP2_LCM_IRQSTATUS_OCPERROR_IRQ | + ISPCCP2_LCM_IRQSTATUS_EOF_IRQ, + OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_IRQSTATUS); + + /* Enable LCM interrupts */ + isp_reg_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_IRQENABLE, + ISPCCP2_LCM_IRQSTATUS_EOF_IRQ | + ISPCCP2_LCM_IRQSTATUS_OCPERROR_IRQ); +} + +/* + * ccp2_set_inaddr - Sets memory address of input frame. + * @ccp2: Pointer to ISP CCP2 device + * @addr: 32bit memory address aligned on 32byte boundary. + * + * Configures the memory address from which the input frame is to be read. + */ +static void ccp2_set_inaddr(struct isp_ccp2_device *ccp2, u32 addr) +{ + struct isp_device *isp = to_isp_device(ccp2); + + isp_reg_writel(isp, addr, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_SRC_ADDR); +} + +/* ----------------------------------------------------------------------------- + * Interrupt handling + */ + +static void ccp2_isr_buffer(struct isp_ccp2_device *ccp2) +{ + struct isp_pipeline *pipe = to_isp_pipeline(&ccp2->subdev.entity); + struct isp_buffer *buffer; + + buffer = omap3isp_video_buffer_next(&ccp2->video_in); + if (buffer != NULL) + ccp2_set_inaddr(ccp2, buffer->dma); + + pipe->state |= ISP_PIPELINE_IDLE_INPUT; + + if (ccp2->state == ISP_PIPELINE_STREAM_SINGLESHOT) { + if (isp_pipeline_ready(pipe)) + omap3isp_pipeline_set_stream(pipe, + ISP_PIPELINE_STREAM_SINGLESHOT); + } +} + +/* + * omap3isp_ccp2_isr - Handle ISP CCP2 interrupts + * @ccp2: Pointer to ISP CCP2 device + * + * This will handle the CCP2 interrupts + */ +void omap3isp_ccp2_isr(struct isp_ccp2_device *ccp2) +{ + struct isp_pipeline *pipe = to_isp_pipeline(&ccp2->subdev.entity); + struct isp_device *isp = to_isp_device(ccp2); + static const u32 ISPCCP2_LC01_ERROR = + ISPCCP2_LC01_IRQSTATUS_LC0_FIFO_OVF_IRQ | + ISPCCP2_LC01_IRQSTATUS_LC0_CRC_IRQ | + ISPCCP2_LC01_IRQSTATUS_LC0_FSP_IRQ | + ISPCCP2_LC01_IRQSTATUS_LC0_FW_IRQ | + ISPCCP2_LC01_IRQSTATUS_LC0_FSC_IRQ | + ISPCCP2_LC01_IRQSTATUS_LC0_SSC_IRQ; + u32 lcx_irqstatus, lcm_irqstatus; + + /* First clear the interrupts */ + lcx_irqstatus = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, + ISPCCP2_LC01_IRQSTATUS); + isp_reg_writel(isp, lcx_irqstatus, OMAP3_ISP_IOMEM_CCP2, + ISPCCP2_LC01_IRQSTATUS); + + lcm_irqstatus = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, + ISPCCP2_LCM_IRQSTATUS); + isp_reg_writel(isp, lcm_irqstatus, OMAP3_ISP_IOMEM_CCP2, + ISPCCP2_LCM_IRQSTATUS); + /* Errors */ + if (lcx_irqstatus & ISPCCP2_LC01_ERROR) { + pipe->error = true; + dev_dbg(isp->dev, "CCP2 err:%x\n", lcx_irqstatus); + return; + } + + if (lcm_irqstatus & ISPCCP2_LCM_IRQSTATUS_OCPERROR_IRQ) { + pipe->error = true; + dev_dbg(isp->dev, "CCP2 OCP err:%x\n", lcm_irqstatus); + } + + if (omap3isp_module_sync_is_stopping(&ccp2->wait, &ccp2->stopping)) + return; + + /* Handle queued buffers on frame end interrupts */ + if (lcm_irqstatus & ISPCCP2_LCM_IRQSTATUS_EOF_IRQ) + ccp2_isr_buffer(ccp2); +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static const unsigned int ccp2_fmts[] = { + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, +}; + +/* + * __ccp2_get_format - helper function for getting ccp2 format + * @ccp2 : Pointer to ISP CCP2 device + * @cfg: V4L2 subdev pad configuration + * @pad : pad number + * @which : wanted subdev format + * return format structure or NULL on error + */ +static struct v4l2_mbus_framefmt * +__ccp2_get_format(struct isp_ccp2_device *ccp2, + struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&ccp2->subdev, sd_state, + pad); + else + return &ccp2->formats[pad]; +} + +/* + * ccp2_try_format - Handle try format by pad subdev method + * @ccp2 : Pointer to ISP CCP2 device + * @cfg: V4L2 subdev pad configuration + * @pad : pad num + * @fmt : pointer to v4l2 mbus format structure + * @which : wanted subdev format + */ +static void ccp2_try_format(struct isp_ccp2_device *ccp2, + struct v4l2_subdev_state *sd_state, + unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + struct v4l2_mbus_framefmt *format; + + switch (pad) { + case CCP2_PAD_SINK: + if (fmt->code != MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8) + fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + if (ccp2->input == CCP2_INPUT_SENSOR) { + fmt->width = clamp_t(u32, fmt->width, + ISPCCP2_DAT_START_MIN, + ISPCCP2_DAT_START_MAX); + fmt->height = clamp_t(u32, fmt->height, + ISPCCP2_DAT_SIZE_MIN, + ISPCCP2_DAT_SIZE_MAX); + } else if (ccp2->input == CCP2_INPUT_MEMORY) { + fmt->width = clamp_t(u32, fmt->width, + ISPCCP2_LCM_HSIZE_COUNT_MIN, + ISPCCP2_LCM_HSIZE_COUNT_MAX); + fmt->height = clamp_t(u32, fmt->height, + ISPCCP2_LCM_VSIZE_MIN, + ISPCCP2_LCM_VSIZE_MAX); + } + break; + + case CCP2_PAD_SOURCE: + /* Source format - copy sink format and change pixel code + * to SGRBG10_1X10 as we don't support CCP2 write to memory. + * When CCP2 write to memory feature will be added this + * should be changed properly. + */ + format = __ccp2_get_format(ccp2, sd_state, CCP2_PAD_SINK, + which); + memcpy(fmt, format, sizeof(*fmt)); + fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + break; + } + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +/* + * ccp2_enum_mbus_code - Handle pixel format enumeration + * @sd : pointer to v4l2 subdev structure + * @cfg: V4L2 subdev pad configuration + * @code : pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int ccp2_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct isp_ccp2_device *ccp2 = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + if (code->pad == CCP2_PAD_SINK) { + if (code->index >= ARRAY_SIZE(ccp2_fmts)) + return -EINVAL; + + code->code = ccp2_fmts[code->index]; + } else { + if (code->index != 0) + return -EINVAL; + + format = __ccp2_get_format(ccp2, sd_state, CCP2_PAD_SINK, + code->which); + code->code = format->code; + } + + return 0; +} + +static int ccp2_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct isp_ccp2_device *ccp2 = 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; + ccp2_try_format(ccp2, sd_state, fse->pad, &format, fse->which); + 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; + ccp2_try_format(ccp2, sd_state, fse->pad, &format, fse->which); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * ccp2_get_format - Handle get format by pads subdev method + * @sd : pointer to v4l2 subdev structure + * @cfg: V4L2 subdev pad configuration + * @fmt : pointer to v4l2 subdev format structure + * return -EINVAL or zero on success + */ +static int ccp2_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct isp_ccp2_device *ccp2 = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ccp2_get_format(ccp2, sd_state, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + return 0; +} + +/* + * ccp2_set_format - Handle set format by pads subdev method + * @sd : pointer to v4l2 subdev structure + * @cfg: V4L2 subdev pad configuration + * @fmt : pointer to v4l2 subdev format structure + * returns zero + */ +static int ccp2_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct isp_ccp2_device *ccp2 = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ccp2_get_format(ccp2, sd_state, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + ccp2_try_format(ccp2, sd_state, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == CCP2_PAD_SINK) { + format = __ccp2_get_format(ccp2, sd_state, CCP2_PAD_SOURCE, + fmt->which); + *format = fmt->format; + ccp2_try_format(ccp2, sd_state, CCP2_PAD_SOURCE, format, + fmt->which); + } + + return 0; +} + +/* + * ccp2_init_formats - Initialize formats on all pads + * @sd: ISP CCP2 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 ccp2_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format; + + memset(&format, 0, sizeof(format)); + format.pad = CCP2_PAD_SINK; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + format.format.width = 4096; + format.format.height = 4096; + ccp2_set_format(sd, fh ? fh->state : NULL, &format); + + return 0; +} + +/* + * ccp2_s_stream - Enable/Disable streaming on ccp2 subdev + * @sd : pointer to v4l2 subdev structure + * @enable: 1 == Enable, 0 == Disable + * return zero + */ +static int ccp2_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct isp_ccp2_device *ccp2 = v4l2_get_subdevdata(sd); + struct isp_device *isp = to_isp_device(ccp2); + struct device *dev = to_device(ccp2); + int ret; + + if (ccp2->state == ISP_PIPELINE_STREAM_STOPPED) { + if (enable == ISP_PIPELINE_STREAM_STOPPED) + return 0; + atomic_set(&ccp2->stopping, 0); + } + + switch (enable) { + case ISP_PIPELINE_STREAM_CONTINUOUS: + if (ccp2->phy) { + ret = omap3isp_csiphy_acquire(ccp2->phy, &sd->entity); + if (ret < 0) + return ret; + } + + ccp2_if_configure(ccp2); + ccp2_print_status(ccp2); + + /* Enable CSI1/CCP2 interface */ + ret = ccp2_if_enable(ccp2, 1); + if (ret < 0) { + if (ccp2->phy) + omap3isp_csiphy_release(ccp2->phy); + return ret; + } + break; + + case ISP_PIPELINE_STREAM_SINGLESHOT: + if (ccp2->state != ISP_PIPELINE_STREAM_SINGLESHOT) { + struct v4l2_mbus_framefmt *format; + + format = &ccp2->formats[CCP2_PAD_SINK]; + + ccp2->mem_cfg.hsize_count = format->width; + ccp2->mem_cfg.vsize_count = format->height; + ccp2->mem_cfg.src_ofst = 0; + + ccp2_mem_configure(ccp2, &ccp2->mem_cfg); + omap3isp_sbl_enable(isp, OMAP3_ISP_SBL_CSI1_READ); + ccp2_print_status(ccp2); + } + ccp2_mem_enable(ccp2, 1); + break; + + case ISP_PIPELINE_STREAM_STOPPED: + if (omap3isp_module_sync_idle(&sd->entity, &ccp2->wait, + &ccp2->stopping)) + dev_dbg(dev, "%s: module stop timeout.\n", sd->name); + if (ccp2->input == CCP2_INPUT_MEMORY) { + ccp2_mem_enable(ccp2, 0); + omap3isp_sbl_disable(isp, OMAP3_ISP_SBL_CSI1_READ); + } else if (ccp2->input == CCP2_INPUT_SENSOR) { + /* Disable CSI1/CCP2 interface */ + ccp2_if_enable(ccp2, 0); + if (ccp2->phy) + omap3isp_csiphy_release(ccp2->phy); + } + break; + } + + ccp2->state = enable; + return 0; +} + +/* subdev video operations */ +static const struct v4l2_subdev_video_ops ccp2_sd_video_ops = { + .s_stream = ccp2_s_stream, +}; + +/* subdev pad operations */ +static const struct v4l2_subdev_pad_ops ccp2_sd_pad_ops = { + .enum_mbus_code = ccp2_enum_mbus_code, + .enum_frame_size = ccp2_enum_frame_size, + .get_fmt = ccp2_get_format, + .set_fmt = ccp2_set_format, +}; + +/* subdev operations */ +static const struct v4l2_subdev_ops ccp2_sd_ops = { + .video = &ccp2_sd_video_ops, + .pad = &ccp2_sd_pad_ops, +}; + +/* subdev internal operations */ +static const struct v4l2_subdev_internal_ops ccp2_sd_internal_ops = { + .open = ccp2_init_formats, +}; + +/* -------------------------------------------------------------------------- + * ISP ccp2 video device node + */ + +/* + * ccp2_video_queue - Queue video buffer. + * @video : Pointer to isp video structure + * @buffer: Pointer to isp_buffer structure + * return -EIO or zero on success + */ +static int ccp2_video_queue(struct isp_video *video, struct isp_buffer *buffer) +{ + struct isp_ccp2_device *ccp2 = &video->isp->isp_ccp2; + + ccp2_set_inaddr(ccp2, buffer->dma); + return 0; +} + +static const struct isp_video_operations ccp2_video_ops = { + .queue = ccp2_video_queue, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * ccp2_link_setup - Setup ccp2 connections. + * @entity : Pointer to media entity structure + * @local : Pointer to local pad array + * @remote : Pointer to remote pad array + * @flags : Link flags + * return -EINVAL on error or zero on success + */ +static int ccp2_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 isp_ccp2_device *ccp2 = v4l2_get_subdevdata(sd); + unsigned int index = local->index; + + /* FIXME: this is actually a hack! */ + if (is_media_entity_v4l2_subdev(remote->entity)) + index |= 2 << 16; + + switch (index) { + case CCP2_PAD_SINK: + /* read from memory */ + if (flags & MEDIA_LNK_FL_ENABLED) { + if (ccp2->input == CCP2_INPUT_SENSOR) + return -EBUSY; + ccp2->input = CCP2_INPUT_MEMORY; + } else { + if (ccp2->input == CCP2_INPUT_MEMORY) + ccp2->input = CCP2_INPUT_NONE; + } + break; + + case CCP2_PAD_SINK | 2 << 16: + /* read from sensor/phy */ + if (flags & MEDIA_LNK_FL_ENABLED) { + if (ccp2->input == CCP2_INPUT_MEMORY) + return -EBUSY; + ccp2->input = CCP2_INPUT_SENSOR; + } else { + if (ccp2->input == CCP2_INPUT_SENSOR) + ccp2->input = CCP2_INPUT_NONE; + } break; + + case CCP2_PAD_SOURCE | 2 << 16: + /* write to video port/ccdc */ + if (flags & MEDIA_LNK_FL_ENABLED) + ccp2->output = CCP2_OUTPUT_CCDC; + else + ccp2->output = CCP2_OUTPUT_NONE; + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* media operations */ +static const struct media_entity_operations ccp2_media_ops = { + .link_setup = ccp2_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * omap3isp_ccp2_unregister_entities - Unregister media entities: subdev + * @ccp2: Pointer to ISP CCP2 device + */ +void omap3isp_ccp2_unregister_entities(struct isp_ccp2_device *ccp2) +{ + v4l2_device_unregister_subdev(&ccp2->subdev); + omap3isp_video_unregister(&ccp2->video_in); +} + +/* + * omap3isp_ccp2_register_entities - Register the subdev media entity + * @ccp2: Pointer to ISP CCP2 device + * @vdev: Pointer to v4l device + * return negative error code or zero on success + */ + +int omap3isp_ccp2_register_entities(struct isp_ccp2_device *ccp2, + struct v4l2_device *vdev) +{ + int ret; + + /* Register the subdev and video nodes. */ + ccp2->subdev.dev = vdev->mdev->dev; + ret = v4l2_device_register_subdev(vdev, &ccp2->subdev); + if (ret < 0) + goto error; + + ret = omap3isp_video_register(&ccp2->video_in, vdev); + if (ret < 0) + goto error; + + return 0; + +error: + omap3isp_ccp2_unregister_entities(ccp2); + return ret; +} + +/* ----------------------------------------------------------------------------- + * ISP ccp2 initialisation and cleanup + */ + +/* + * ccp2_init_entities - Initialize ccp2 subdev and media entity. + * @ccp2: Pointer to ISP CCP2 device + * return negative error code or zero on success + */ +static int ccp2_init_entities(struct isp_ccp2_device *ccp2) +{ + struct v4l2_subdev *sd = &ccp2->subdev; + struct media_pad *pads = ccp2->pads; + struct media_entity *me = &sd->entity; + int ret; + + ccp2->input = CCP2_INPUT_NONE; + ccp2->output = CCP2_OUTPUT_NONE; + + v4l2_subdev_init(sd, &ccp2_sd_ops); + sd->internal_ops = &ccp2_sd_internal_ops; + strscpy(sd->name, "OMAP3 ISP CCP2", sizeof(sd->name)); + sd->grp_id = 1 << 16; /* group ID for isp subdevs */ + v4l2_set_subdevdata(sd, ccp2); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[CCP2_PAD_SINK].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; + pads[CCP2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + me->ops = &ccp2_media_ops; + ret = media_entity_pads_init(me, CCP2_PADS_NUM, pads); + if (ret < 0) + return ret; + + ccp2_init_formats(sd, NULL); + + /* + * The CCP2 has weird line alignment requirements, possibly caused by + * DPCM8 decompression. Line length for data read from memory must be a + * multiple of 128 bits (16 bytes) in continuous mode (when no padding + * is present at end of lines). Additionally, if padding is used, the + * padded line length must be a multiple of 32 bytes. To simplify the + * implementation we use a fixed 32 bytes alignment regardless of the + * input format and width. If strict 128 bits alignment support is + * required ispvideo will need to be made aware of this special dual + * alignment requirements. + */ + ccp2->video_in.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + ccp2->video_in.bpl_alignment = 32; + ccp2->video_in.bpl_max = 0xffffffe0; + ccp2->video_in.isp = to_isp_device(ccp2); + ccp2->video_in.ops = &ccp2_video_ops; + ccp2->video_in.capture_mem = PAGE_ALIGN(4096 * 4096) * 3; + + ret = omap3isp_video_init(&ccp2->video_in, "CCP2"); + if (ret < 0) + goto error; + + return 0; + +error: + media_entity_cleanup(&ccp2->subdev.entity); + return ret; +} + +/* + * omap3isp_ccp2_init - CCP2 initialization. + * @isp : Pointer to ISP device + * return negative error code or zero on success + */ +int omap3isp_ccp2_init(struct isp_device *isp) +{ + struct isp_ccp2_device *ccp2 = &isp->isp_ccp2; + int ret; + + init_waitqueue_head(&ccp2->wait); + + /* + * On the OMAP34xx the CSI1 receiver is operated in the CSIb IO + * complex, which is powered by vdds_csib power rail. Hence the + * request for the regulator. + * + * On the OMAP36xx, the CCP2 uses the CSI PHY1 or PHY2, shared with + * the CSI2c or CSI2a receivers. The PHY then needs to be explicitly + * configured. + * + * TODO: Don't hardcode the usage of PHY1 (shared with CSI2c). + */ + if (isp->revision == ISP_REVISION_2_0) { + ccp2->vdds_csib = devm_regulator_get(isp->dev, "vdds_csib"); + if (IS_ERR(ccp2->vdds_csib)) { + if (PTR_ERR(ccp2->vdds_csib) == -EPROBE_DEFER) { + dev_dbg(isp->dev, + "Can't get regulator vdds_csib, deferring probing\n"); + return -EPROBE_DEFER; + } + dev_dbg(isp->dev, + "Could not get regulator vdds_csib\n"); + ccp2->vdds_csib = NULL; + } + ccp2->phy = &isp->isp_csiphy2; + } else if (isp->revision == ISP_REVISION_15_0) { + ccp2->phy = &isp->isp_csiphy1; + } + + ret = ccp2_init_entities(ccp2); + if (ret < 0) + return ret; + + ccp2_reset(ccp2); + return 0; +} + +/* + * omap3isp_ccp2_cleanup - CCP2 un-initialization + * @isp : Pointer to ISP device + */ +void omap3isp_ccp2_cleanup(struct isp_device *isp) +{ + struct isp_ccp2_device *ccp2 = &isp->isp_ccp2; + + omap3isp_video_cleanup(&ccp2->video_in); + media_entity_cleanup(&ccp2->subdev.entity); +} |