2019-06-04 10:11:33 +02:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2016-01-06 21:37:26 -02:00
|
|
|
/*
|
2020-07-06 20:36:43 +02:00
|
|
|
* TI Camera Access Layer (CAL) - Driver
|
2016-01-06 21:37:26 -02:00
|
|
|
*
|
2020-07-06 20:36:43 +02:00
|
|
|
* Copyright (c) 2015-2020 Texas Instruments Inc.
|
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* Benoit Parrot <bparrot@ti.com>
|
|
|
|
* Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
2016-01-06 21:37:26 -02:00
|
|
|
*/
|
|
|
|
|
2020-03-25 13:15:10 +01:00
|
|
|
#include <linux/clk.h>
|
2016-01-06 21:37:26 -02:00
|
|
|
#include <linux/interrupt.h>
|
2020-07-06 20:35:22 +02:00
|
|
|
#include <linux/mfd/syscon.h>
|
2016-01-06 21:37:26 -02:00
|
|
|
#include <linux/module.h>
|
2020-07-06 20:35:22 +02:00
|
|
|
#include <linux/of_device.h>
|
2016-01-06 21:37:26 -02:00
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/pm_runtime.h>
|
2019-11-12 15:53:31 +01:00
|
|
|
#include <linux/regmap.h>
|
2020-07-06 20:35:22 +02:00
|
|
|
#include <linux/slab.h>
|
2016-01-06 21:37:26 -02:00
|
|
|
#include <linux/videodev2.h>
|
|
|
|
|
2020-07-06 20:36:34 +02:00
|
|
|
#include <media/media-device.h>
|
2016-01-06 21:37:26 -02:00
|
|
|
#include <media/v4l2-async.h>
|
|
|
|
#include <media/v4l2-common.h>
|
|
|
|
#include <media/v4l2-device.h>
|
|
|
|
#include <media/videobuf2-core.h>
|
|
|
|
#include <media/videobuf2-dma-contig.h>
|
2020-07-06 20:35:22 +02:00
|
|
|
|
2020-07-06 20:36:43 +02:00
|
|
|
#include "cal.h"
|
2016-01-06 21:37:26 -02:00
|
|
|
#include "cal_regs.h"
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("TI CAL driver");
|
|
|
|
MODULE_AUTHOR("Benoit Parrot, <bparrot@ti.com>");
|
|
|
|
MODULE_LICENSE("GPL v2");
|
2020-07-06 20:35:28 +02:00
|
|
|
MODULE_VERSION("0.1.0");
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:36:43 +02:00
|
|
|
int cal_video_nr = -1;
|
|
|
|
module_param_named(video_nr, cal_video_nr, uint, 0644);
|
2016-01-06 21:37:26 -02:00
|
|
|
MODULE_PARM_DESC(video_nr, "videoX start number, -1 is autodetect");
|
|
|
|
|
2020-07-06 20:36:43 +02:00
|
|
|
unsigned int cal_debug;
|
|
|
|
module_param_named(debug, cal_debug, uint, 0644);
|
2016-01-06 21:37:26 -02:00
|
|
|
MODULE_PARM_DESC(debug, "activates debug info");
|
|
|
|
|
2020-07-06 20:35:58 +02:00
|
|
|
/* ------------------------------------------------------------------
|
|
|
|
* Platform Data
|
|
|
|
* ------------------------------------------------------------------
|
|
|
|
*/
|
2020-07-06 20:35:29 +02:00
|
|
|
|
2020-07-06 20:35:58 +02:00
|
|
|
static const struct cal_camerarx_data dra72x_cal_camerarx[] = {
|
|
|
|
{
|
|
|
|
.fields = {
|
|
|
|
[F_CTRLCLKEN] = { 10, 10 },
|
|
|
|
[F_CAMMODE] = { 11, 12 },
|
|
|
|
[F_LANEENABLE] = { 13, 16 },
|
|
|
|
[F_CSI_MODE] = { 17, 17 },
|
|
|
|
},
|
|
|
|
.num_lanes = 4,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.fields = {
|
|
|
|
[F_CTRLCLKEN] = { 0, 0 },
|
|
|
|
[F_CAMMODE] = { 1, 2 },
|
|
|
|
[F_LANEENABLE] = { 3, 4 },
|
|
|
|
[F_CSI_MODE] = { 5, 5 },
|
|
|
|
},
|
|
|
|
.num_lanes = 2,
|
|
|
|
},
|
|
|
|
};
|
2020-07-06 20:35:29 +02:00
|
|
|
|
2020-07-06 20:35:58 +02:00
|
|
|
static const struct cal_data dra72x_cal_data = {
|
|
|
|
.camerarx = dra72x_cal_camerarx,
|
|
|
|
.num_csi2_phy = ARRAY_SIZE(dra72x_cal_camerarx),
|
|
|
|
};
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:35:58 +02:00
|
|
|
static const struct cal_data dra72x_es1_cal_data = {
|
|
|
|
.camerarx = dra72x_cal_camerarx,
|
|
|
|
.num_csi2_phy = ARRAY_SIZE(dra72x_cal_camerarx),
|
|
|
|
.flags = DRA72_CAL_PRE_ES2_LDO_DISABLE,
|
|
|
|
};
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:35:58 +02:00
|
|
|
static const struct cal_camerarx_data dra76x_cal_csi_phy[] = {
|
|
|
|
{
|
|
|
|
.fields = {
|
|
|
|
[F_CTRLCLKEN] = { 8, 8 },
|
|
|
|
[F_CAMMODE] = { 9, 10 },
|
|
|
|
[F_CSI_MODE] = { 11, 11 },
|
|
|
|
[F_LANEENABLE] = { 27, 31 },
|
|
|
|
},
|
|
|
|
.num_lanes = 5,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.fields = {
|
|
|
|
[F_CTRLCLKEN] = { 0, 0 },
|
|
|
|
[F_CAMMODE] = { 1, 2 },
|
|
|
|
[F_CSI_MODE] = { 3, 3 },
|
|
|
|
[F_LANEENABLE] = { 24, 26 },
|
|
|
|
},
|
|
|
|
.num_lanes = 3,
|
|
|
|
},
|
|
|
|
};
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:35:58 +02:00
|
|
|
static const struct cal_data dra76x_cal_data = {
|
|
|
|
.camerarx = dra76x_cal_csi_phy,
|
|
|
|
.num_csi2_phy = ARRAY_SIZE(dra76x_cal_csi_phy),
|
|
|
|
};
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:35:58 +02:00
|
|
|
static const struct cal_camerarx_data am654_cal_csi_phy[] = {
|
|
|
|
{
|
|
|
|
.fields = {
|
|
|
|
[F_CTRLCLKEN] = { 15, 15 },
|
|
|
|
[F_CAMMODE] = { 24, 25 },
|
|
|
|
[F_LANEENABLE] = { 0, 4 },
|
|
|
|
},
|
|
|
|
.num_lanes = 5,
|
|
|
|
},
|
|
|
|
};
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:35:58 +02:00
|
|
|
static const struct cal_data am654_cal_data = {
|
|
|
|
.camerarx = am654_cal_csi_phy,
|
|
|
|
.num_csi2_phy = ARRAY_SIZE(am654_cal_csi_phy),
|
|
|
|
};
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------
|
|
|
|
* I/O Register Accessors
|
|
|
|
* ------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
2020-07-06 20:36:43 +02:00
|
|
|
void cal_quickdump_regs(struct cal_dev *cal)
|
2016-01-06 21:37:26 -02:00
|
|
|
{
|
2020-07-06 20:36:25 +02:00
|
|
|
unsigned int i;
|
|
|
|
|
2020-07-06 20:35:55 +02:00
|
|
|
cal_info(cal, "CAL Registers @ 0x%pa:\n", &cal->res->start);
|
|
|
|
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 4,
|
|
|
|
(__force const void *)cal->base,
|
|
|
|
resource_size(cal->res), false);
|
|
|
|
|
2020-07-06 20:36:25 +02:00
|
|
|
for (i = 0; i < ARRAY_SIZE(cal->phy); ++i) {
|
|
|
|
struct cal_camerarx *phy = cal->phy[i];
|
|
|
|
|
|
|
|
if (!phy)
|
|
|
|
continue;
|
2020-07-06 20:35:55 +02:00
|
|
|
|
2020-07-06 20:36:25 +02:00
|
|
|
cal_info(cal, "CSI2 Core %u Registers @ %pa:\n", i,
|
|
|
|
&phy->res->start);
|
2020-07-06 20:35:55 +02:00
|
|
|
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 4,
|
2020-07-06 20:36:25 +02:00
|
|
|
(__force const void *)phy->base,
|
|
|
|
resource_size(phy->res),
|
2020-07-06 20:35:55 +02:00
|
|
|
false);
|
|
|
|
}
|
2019-11-12 15:53:31 +01:00
|
|
|
}
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:35:57 +02:00
|
|
|
/* ------------------------------------------------------------------
|
|
|
|
* Context Management
|
|
|
|
* ------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
2020-07-06 20:36:43 +02:00
|
|
|
void cal_ctx_csi2_config(struct cal_ctx *ctx)
|
2016-01-06 21:37:26 -02:00
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
2020-07-06 20:36:44 +02:00
|
|
|
val = cal_read(ctx->cal, CAL_CSI2_CTX0(ctx->index));
|
|
|
|
cal_set_field(&val, ctx->cport, CAL_CSI2_CTX_CPORT_MASK);
|
2016-01-06 21:37:26 -02:00
|
|
|
/*
|
|
|
|
* DT type: MIPI CSI-2 Specs
|
|
|
|
* 0x1: All - DT filter is disabled
|
|
|
|
* 0x24: RGB888 1 pixel = 3 bytes
|
|
|
|
* 0x2B: RAW10 4 pixels = 5 bytes
|
|
|
|
* 0x2A: RAW8 1 pixel = 1 byte
|
|
|
|
* 0x1E: YUV422 2 pixels = 4 bytes
|
|
|
|
*/
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_set_field(&val, 0x1, CAL_CSI2_CTX_DT_MASK);
|
|
|
|
cal_set_field(&val, 0, CAL_CSI2_CTX_VC_MASK);
|
|
|
|
cal_set_field(&val, ctx->v_fmt.fmt.pix.height, CAL_CSI2_CTX_LINES_MASK);
|
|
|
|
cal_set_field(&val, CAL_CSI2_CTX_ATT_PIX, CAL_CSI2_CTX_ATT_MASK);
|
|
|
|
cal_set_field(&val, CAL_CSI2_CTX_PACK_MODE_LINE,
|
|
|
|
CAL_CSI2_CTX_PACK_MODE_MASK);
|
|
|
|
cal_write(ctx->cal, CAL_CSI2_CTX0(ctx->index), val);
|
2020-07-06 20:35:59 +02:00
|
|
|
ctx_dbg(3, ctx, "CAL_CSI2_CTX0(%d) = 0x%08x\n", ctx->index,
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_read(ctx->cal, CAL_CSI2_CTX0(ctx->index)));
|
2016-01-06 21:37:26 -02:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:43 +02:00
|
|
|
void cal_ctx_pix_proc_config(struct cal_ctx *ctx)
|
2016-01-06 21:37:26 -02:00
|
|
|
{
|
2019-11-12 15:53:37 +01:00
|
|
|
u32 val, extract, pack;
|
|
|
|
|
|
|
|
switch (ctx->fmt->bpp) {
|
|
|
|
case 8:
|
|
|
|
extract = CAL_PIX_PROC_EXTRACT_B8;
|
|
|
|
pack = CAL_PIX_PROC_PACK_B8;
|
|
|
|
break;
|
|
|
|
case 10:
|
|
|
|
extract = CAL_PIX_PROC_EXTRACT_B10_MIPI;
|
|
|
|
pack = CAL_PIX_PROC_PACK_B16;
|
|
|
|
break;
|
|
|
|
case 12:
|
|
|
|
extract = CAL_PIX_PROC_EXTRACT_B12_MIPI;
|
|
|
|
pack = CAL_PIX_PROC_PACK_B16;
|
|
|
|
break;
|
|
|
|
case 16:
|
|
|
|
extract = CAL_PIX_PROC_EXTRACT_B16_LE;
|
|
|
|
pack = CAL_PIX_PROC_PACK_B16;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/*
|
|
|
|
* If you see this warning then it means that you added
|
|
|
|
* some new entry in the cal_formats[] array with a different
|
|
|
|
* bit per pixel values then the one supported below.
|
|
|
|
* Either add support for the new bpp value below or adjust
|
|
|
|
* the new entry to use one of the value below.
|
|
|
|
*
|
|
|
|
* Instead of failing here just use 8 bpp as a default.
|
|
|
|
*/
|
2020-07-06 20:36:33 +02:00
|
|
|
dev_warn_once(ctx->cal->dev,
|
2019-11-12 15:53:37 +01:00
|
|
|
"%s:%d:%s: bpp:%d unsupported! Overwritten with 8.\n",
|
|
|
|
__FILE__, __LINE__, __func__, ctx->fmt->bpp);
|
|
|
|
extract = CAL_PIX_PROC_EXTRACT_B8;
|
|
|
|
pack = CAL_PIX_PROC_PACK_B8;
|
|
|
|
break;
|
|
|
|
}
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:36:44 +02:00
|
|
|
val = cal_read(ctx->cal, CAL_PIX_PROC(ctx->index));
|
|
|
|
cal_set_field(&val, extract, CAL_PIX_PROC_EXTRACT_MASK);
|
|
|
|
cal_set_field(&val, CAL_PIX_PROC_DPCMD_BYPASS, CAL_PIX_PROC_DPCMD_MASK);
|
|
|
|
cal_set_field(&val, CAL_PIX_PROC_DPCME_BYPASS, CAL_PIX_PROC_DPCME_MASK);
|
|
|
|
cal_set_field(&val, pack, CAL_PIX_PROC_PACK_MASK);
|
|
|
|
cal_set_field(&val, ctx->cport, CAL_PIX_PROC_CPORT_MASK);
|
|
|
|
cal_set_field(&val, 1, CAL_PIX_PROC_EN_MASK);
|
|
|
|
cal_write(ctx->cal, CAL_PIX_PROC(ctx->index), val);
|
2020-07-06 20:35:59 +02:00
|
|
|
ctx_dbg(3, ctx, "CAL_PIX_PROC(%d) = 0x%08x\n", ctx->index,
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_read(ctx->cal, CAL_PIX_PROC(ctx->index)));
|
2016-01-06 21:37:26 -02:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:43 +02:00
|
|
|
void cal_ctx_wr_dma_config(struct cal_ctx *ctx, unsigned int width,
|
|
|
|
unsigned int height)
|
2016-01-06 21:37:26 -02:00
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
2020-07-06 20:36:44 +02:00
|
|
|
val = cal_read(ctx->cal, CAL_WR_DMA_CTRL(ctx->index));
|
|
|
|
cal_set_field(&val, ctx->cport, CAL_WR_DMA_CTRL_CPORT_MASK);
|
|
|
|
cal_set_field(&val, height, CAL_WR_DMA_CTRL_YSIZE_MASK);
|
|
|
|
cal_set_field(&val, CAL_WR_DMA_CTRL_DTAG_PIX_DAT,
|
|
|
|
CAL_WR_DMA_CTRL_DTAG_MASK);
|
|
|
|
cal_set_field(&val, CAL_WR_DMA_CTRL_MODE_CONST,
|
|
|
|
CAL_WR_DMA_CTRL_MODE_MASK);
|
|
|
|
cal_set_field(&val, CAL_WR_DMA_CTRL_PATTERN_LINEAR,
|
|
|
|
CAL_WR_DMA_CTRL_PATTERN_MASK);
|
|
|
|
cal_set_field(&val, 1, CAL_WR_DMA_CTRL_STALL_RD_MASK);
|
|
|
|
cal_write(ctx->cal, CAL_WR_DMA_CTRL(ctx->index), val);
|
2020-07-06 20:35:59 +02:00
|
|
|
ctx_dbg(3, ctx, "CAL_WR_DMA_CTRL(%d) = 0x%08x\n", ctx->index,
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_read(ctx->cal, CAL_WR_DMA_CTRL(ctx->index)));
|
2016-01-06 21:37:26 -02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* width/16 not sure but giving it a whirl.
|
|
|
|
* zero does not work right
|
|
|
|
*/
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_write_field(ctx->cal,
|
2020-07-06 20:35:59 +02:00
|
|
|
CAL_WR_DMA_OFST(ctx->index),
|
2016-01-06 21:37:26 -02:00
|
|
|
(width / 16),
|
|
|
|
CAL_WR_DMA_OFST_MASK);
|
2020-07-06 20:35:59 +02:00
|
|
|
ctx_dbg(3, ctx, "CAL_WR_DMA_OFST(%d) = 0x%08x\n", ctx->index,
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_read(ctx->cal, CAL_WR_DMA_OFST(ctx->index)));
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:36:44 +02:00
|
|
|
val = cal_read(ctx->cal, CAL_WR_DMA_XSIZE(ctx->index));
|
2016-01-06 21:37:26 -02:00
|
|
|
/* 64 bit word means no skipping */
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_set_field(&val, 0, CAL_WR_DMA_XSIZE_XSKIP_MASK);
|
2016-01-06 21:37:26 -02:00
|
|
|
/*
|
|
|
|
* (width*8)/64 this should be size of an entire line
|
|
|
|
* in 64bit word but 0 means all data until the end
|
|
|
|
* is detected automagically
|
|
|
|
*/
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_set_field(&val, (width / 8), CAL_WR_DMA_XSIZE_MASK);
|
|
|
|
cal_write(ctx->cal, CAL_WR_DMA_XSIZE(ctx->index), val);
|
2020-07-06 20:35:59 +02:00
|
|
|
ctx_dbg(3, ctx, "CAL_WR_DMA_XSIZE(%d) = 0x%08x\n", ctx->index,
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_read(ctx->cal, CAL_WR_DMA_XSIZE(ctx->index)));
|
|
|
|
|
|
|
|
val = cal_read(ctx->cal, CAL_CTRL);
|
|
|
|
cal_set_field(&val, CAL_CTRL_BURSTSIZE_BURST128,
|
|
|
|
CAL_CTRL_BURSTSIZE_MASK);
|
|
|
|
cal_set_field(&val, 0xF, CAL_CTRL_TAGCNT_MASK);
|
|
|
|
cal_set_field(&val, CAL_CTRL_POSTED_WRITES_NONPOSTED,
|
|
|
|
CAL_CTRL_POSTED_WRITES_MASK);
|
|
|
|
cal_set_field(&val, 0xFF, CAL_CTRL_MFLAGL_MASK);
|
|
|
|
cal_set_field(&val, 0xFF, CAL_CTRL_MFLAGH_MASK);
|
|
|
|
cal_write(ctx->cal, CAL_CTRL, val);
|
|
|
|
ctx_dbg(3, ctx, "CAL_CTRL = 0x%08x\n", cal_read(ctx->cal, CAL_CTRL));
|
2016-01-06 21:37:26 -02:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:43 +02:00
|
|
|
void cal_ctx_wr_dma_addr(struct cal_ctx *ctx, unsigned int dmaaddr)
|
2016-01-06 21:37:26 -02:00
|
|
|
{
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_write(ctx->cal, CAL_WR_DMA_ADDR(ctx->index), dmaaddr);
|
2016-01-06 21:37:26 -02:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:35:58 +02:00
|
|
|
/* ------------------------------------------------------------------
|
|
|
|
* IRQ Handling
|
|
|
|
* ------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
2016-01-06 21:37:26 -02:00
|
|
|
static inline void cal_schedule_next_buffer(struct cal_ctx *ctx)
|
|
|
|
{
|
|
|
|
struct cal_dmaqueue *dma_q = &ctx->vidq;
|
|
|
|
struct cal_buffer *buf;
|
|
|
|
unsigned long addr;
|
|
|
|
|
|
|
|
buf = list_entry(dma_q->active.next, struct cal_buffer, list);
|
|
|
|
ctx->next_frm = buf;
|
|
|
|
list_del(&buf->list);
|
|
|
|
|
|
|
|
addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
|
2020-07-06 20:35:57 +02:00
|
|
|
cal_ctx_wr_dma_addr(ctx, addr);
|
2016-01-06 21:37:26 -02:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void cal_process_buffer_complete(struct cal_ctx *ctx)
|
|
|
|
{
|
|
|
|
ctx->cur_frm->vb.vb2_buf.timestamp = ktime_get_ns();
|
|
|
|
ctx->cur_frm->vb.field = ctx->m_fmt.field;
|
|
|
|
ctx->cur_frm->vb.sequence = ctx->sequence++;
|
|
|
|
|
|
|
|
vb2_buffer_done(&ctx->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE);
|
|
|
|
ctx->cur_frm = ctx->next_frm;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t cal_irq(int irq_cal, void *data)
|
|
|
|
{
|
2020-07-06 20:35:47 +02:00
|
|
|
struct cal_dev *cal = data;
|
2016-01-06 21:37:26 -02:00
|
|
|
struct cal_ctx *ctx;
|
|
|
|
struct cal_dmaqueue *dma_q;
|
2020-07-06 20:35:27 +02:00
|
|
|
u32 status;
|
2020-03-25 13:14:57 +01:00
|
|
|
|
2020-07-06 20:36:44 +02:00
|
|
|
status = cal_read(cal, CAL_HL_IRQSTATUS(0));
|
2020-07-06 20:35:27 +02:00
|
|
|
if (status) {
|
2020-07-06 20:35:34 +02:00
|
|
|
unsigned int i;
|
2020-03-25 13:14:57 +01:00
|
|
|
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_write(cal, CAL_HL_IRQSTATUS(0), status);
|
2020-03-25 13:14:57 +01:00
|
|
|
|
2020-07-06 20:35:27 +02:00
|
|
|
if (status & CAL_HL_IRQ_OCPO_ERR_MASK)
|
2020-07-06 20:36:33 +02:00
|
|
|
dev_err_ratelimited(cal->dev, "OCPO ERROR\n");
|
2020-03-25 13:14:57 +01:00
|
|
|
|
2020-07-06 20:36:29 +02:00
|
|
|
for (i = 0; i < CAL_NUM_CSI2_PORTS; ++i) {
|
2020-07-06 20:35:27 +02:00
|
|
|
if (status & CAL_HL_IRQ_CIO_MASK(i)) {
|
2020-07-06 20:36:44 +02:00
|
|
|
u32 cio_stat = cal_read(cal,
|
2020-03-25 13:14:57 +01:00
|
|
|
CAL_CSI2_COMPLEXIO_IRQSTATUS(i));
|
|
|
|
|
2020-07-06 20:36:33 +02:00
|
|
|
dev_err_ratelimited(cal->dev,
|
2020-07-06 20:35:34 +02:00
|
|
|
"CIO%u error: %#08x\n", i, cio_stat);
|
2020-03-25 13:14:57 +01:00
|
|
|
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_write(cal, CAL_CSI2_COMPLEXIO_IRQSTATUS(i),
|
2020-03-25 13:14:57 +01:00
|
|
|
cio_stat);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-01-06 21:37:26 -02:00
|
|
|
|
|
|
|
/* Check which DMA just finished */
|
2020-07-06 20:36:44 +02:00
|
|
|
status = cal_read(cal, CAL_HL_IRQSTATUS(1));
|
2020-07-06 20:35:27 +02:00
|
|
|
if (status) {
|
2020-07-06 20:35:34 +02:00
|
|
|
unsigned int i;
|
2020-03-25 13:14:59 +01:00
|
|
|
|
2016-01-06 21:37:26 -02:00
|
|
|
/* Clear Interrupt status */
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_write(cal, CAL_HL_IRQSTATUS(1), status);
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:36:20 +02:00
|
|
|
for (i = 0; i < ARRAY_SIZE(cal->ctx); ++i) {
|
2020-07-06 20:36:28 +02:00
|
|
|
if (status & CAL_HL_IRQ_MASK(i)) {
|
2020-07-06 20:35:47 +02:00
|
|
|
ctx = cal->ctx[i];
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-03-25 13:14:59 +01:00
|
|
|
spin_lock(&ctx->slock);
|
|
|
|
ctx->dma_act = false;
|
media: ti-vpe: cal: fix DMA memory corruption
When the CAL driver stops streaming, it will shut everything down
without waiting for the current frame to finish. This leaves the CAL DMA
in a slightly undefined state, and when CAL DMA is enabled when the
stream is started the next time, the old DMA transfer will continue.
It is not clear if the old DMA transfer continues with the exact
settings of the original transfer, or is it a mix of old and new
settings, but in any case the end result is memory corruption as the
destination memory address is no longer valid.
I could not find any way to ensure that any old DMA transfer would be
discarded, except perhaps full CAL reset. But we cannot do a full reset
when one port is getting enabled, as that would reset both ports.
This patch tries to make sure that the DMA transfer is finished properly
when the stream is being stopped. I say "tries", as, as mentioned above,
I don't see a way to force the DMA transfer to finish. I believe this
fixes the corruptions for normal cases, but if for some reason the DMA
of the final frame would stall a lot, resulting in timeout in the code
waiting for the DMA to finish, we'll again end up with unfinished DMA
transfer. However, I don't know what could cause such a timeout.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Reviewed-by: Benoit Parrot <bparrot@ti.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
2020-03-25 13:14:52 +01:00
|
|
|
|
2020-03-25 13:14:59 +01:00
|
|
|
if (ctx->cur_frm != ctx->next_frm)
|
|
|
|
cal_process_buffer_complete(ctx);
|
media: ti-vpe: cal: fix DMA memory corruption
When the CAL driver stops streaming, it will shut everything down
without waiting for the current frame to finish. This leaves the CAL DMA
in a slightly undefined state, and when CAL DMA is enabled when the
stream is started the next time, the old DMA transfer will continue.
It is not clear if the old DMA transfer continues with the exact
settings of the original transfer, or is it a mix of old and new
settings, but in any case the end result is memory corruption as the
destination memory address is no longer valid.
I could not find any way to ensure that any old DMA transfer would be
discarded, except perhaps full CAL reset. But we cannot do a full reset
when one port is getting enabled, as that would reset both ports.
This patch tries to make sure that the DMA transfer is finished properly
when the stream is being stopped. I say "tries", as, as mentioned above,
I don't see a way to force the DMA transfer to finish. I believe this
fixes the corruptions for normal cases, but if for some reason the DMA
of the final frame would stall a lot, resulting in timeout in the code
waiting for the DMA to finish, we'll again end up with unfinished DMA
transfer. However, I don't know what could cause such a timeout.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Reviewed-by: Benoit Parrot <bparrot@ti.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
2020-03-25 13:14:52 +01:00
|
|
|
|
2020-03-25 13:14:59 +01:00
|
|
|
spin_unlock(&ctx->slock);
|
|
|
|
}
|
2016-01-06 21:37:26 -02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check which DMA just started */
|
2020-07-06 20:36:44 +02:00
|
|
|
status = cal_read(cal, CAL_HL_IRQSTATUS(2));
|
2020-07-06 20:35:27 +02:00
|
|
|
if (status) {
|
2020-07-06 20:35:34 +02:00
|
|
|
unsigned int i;
|
2020-03-25 13:14:59 +01:00
|
|
|
|
2016-01-06 21:37:26 -02:00
|
|
|
/* Clear Interrupt status */
|
2020-07-06 20:36:44 +02:00
|
|
|
cal_write(cal, CAL_HL_IRQSTATUS(2), status);
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:36:20 +02:00
|
|
|
for (i = 0; i < ARRAY_SIZE(cal->ctx); ++i) {
|
2020-07-06 20:36:28 +02:00
|
|
|
if (status & CAL_HL_IRQ_MASK(i)) {
|
2020-07-06 20:35:47 +02:00
|
|
|
ctx = cal->ctx[i];
|
2020-03-25 13:14:59 +01:00
|
|
|
dma_q = &ctx->vidq;
|
|
|
|
|
|
|
|
spin_lock(&ctx->slock);
|
|
|
|
ctx->dma_act = true;
|
|
|
|
if (!list_empty(&dma_q->active) &&
|
|
|
|
ctx->cur_frm == ctx->next_frm)
|
|
|
|
cal_schedule_next_buffer(ctx);
|
|
|
|
spin_unlock(&ctx->slock);
|
|
|
|
}
|
2016-01-06 21:37:26 -02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:15 +02:00
|
|
|
/* ------------------------------------------------------------------
|
2020-07-06 20:36:19 +02:00
|
|
|
* Asynchronous V4L2 subdev binding
|
2020-07-06 20:36:15 +02:00
|
|
|
* ------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
2020-07-06 20:36:22 +02:00
|
|
|
struct cal_v4l2_async_subdev {
|
|
|
|
struct v4l2_async_subdev asd;
|
2020-07-06 20:36:24 +02:00
|
|
|
struct cal_camerarx *phy;
|
2020-07-06 20:36:22 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
static inline struct cal_v4l2_async_subdev *
|
|
|
|
to_cal_asd(struct v4l2_async_subdev *asd)
|
|
|
|
{
|
|
|
|
return container_of(asd, struct cal_v4l2_async_subdev, asd);
|
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:23 +02:00
|
|
|
static int cal_async_notifier_bound(struct v4l2_async_notifier *notifier,
|
|
|
|
struct v4l2_subdev *subdev,
|
|
|
|
struct v4l2_async_subdev *asd)
|
2016-01-06 21:37:26 -02:00
|
|
|
{
|
2020-07-06 20:36:24 +02:00
|
|
|
struct cal_camerarx *phy = to_cal_asd(asd)->phy;
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:36:24 +02:00
|
|
|
if (phy->sensor) {
|
|
|
|
phy_info(phy, "Rejecting subdev %s (Already set!!)",
|
2016-01-06 21:37:26 -02:00
|
|
|
subdev->name);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:24 +02:00
|
|
|
phy->sensor = subdev;
|
|
|
|
phy_dbg(1, phy, "Using sensor %s for capture\n", subdev->name);
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:36:23 +02:00
|
|
|
return 0;
|
2016-01-06 21:37:26 -02:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:23 +02:00
|
|
|
static int cal_async_notifier_complete(struct v4l2_async_notifier *notifier)
|
2016-01-06 21:37:26 -02:00
|
|
|
{
|
2020-07-06 20:36:23 +02:00
|
|
|
struct cal_dev *cal = container_of(notifier, struct cal_dev, notifier);
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cal->ctx); ++i) {
|
2020-07-06 20:36:35 +02:00
|
|
|
if (cal->ctx[i])
|
|
|
|
cal_ctx_v4l2_register(cal->ctx[i]);
|
2020-07-06 20:36:23 +02:00
|
|
|
}
|
2016-01-06 21:37:26 -02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:23 +02:00
|
|
|
static const struct v4l2_async_notifier_operations cal_async_notifier_ops = {
|
|
|
|
.bound = cal_async_notifier_bound,
|
|
|
|
.complete = cal_async_notifier_complete,
|
2017-08-30 13:18:04 -04:00
|
|
|
};
|
|
|
|
|
2020-07-06 20:36:23 +02:00
|
|
|
static int cal_async_notifier_register(struct cal_dev *cal)
|
2016-01-06 21:37:26 -02:00
|
|
|
{
|
2020-07-06 20:36:23 +02:00
|
|
|
unsigned int i;
|
2020-07-06 20:36:19 +02:00
|
|
|
int ret;
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:36:23 +02:00
|
|
|
v4l2_async_notifier_init(&cal->notifier);
|
|
|
|
cal->notifier.ops = &cal_async_notifier_ops;
|
2018-09-29 15:54:18 -04:00
|
|
|
|
2020-07-06 20:36:24 +02:00
|
|
|
for (i = 0; i < ARRAY_SIZE(cal->phy); ++i) {
|
|
|
|
struct cal_camerarx *phy = cal->phy[i];
|
2020-07-06 20:36:23 +02:00
|
|
|
struct cal_v4l2_async_subdev *casd;
|
|
|
|
struct v4l2_async_subdev *asd;
|
|
|
|
struct fwnode_handle *fwnode;
|
|
|
|
|
2020-07-06 20:36:24 +02:00
|
|
|
if (!phy || !phy->sensor_node)
|
2020-07-06 20:36:23 +02:00
|
|
|
continue;
|
2018-09-29 15:54:18 -04:00
|
|
|
|
2020-07-06 20:36:24 +02:00
|
|
|
fwnode = of_fwnode_handle(phy->sensor_node);
|
2020-07-06 20:36:23 +02:00
|
|
|
asd = v4l2_async_notifier_add_fwnode_subdev(&cal->notifier,
|
|
|
|
fwnode,
|
|
|
|
sizeof(*asd));
|
|
|
|
if (IS_ERR(asd)) {
|
2020-07-06 20:36:24 +02:00
|
|
|
phy_err(phy, "Failed to add subdev to notifier\n");
|
2020-07-06 20:36:23 +02:00
|
|
|
ret = PTR_ERR(asd);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
casd = to_cal_asd(asd);
|
2020-07-06 20:36:24 +02:00
|
|
|
casd->phy = phy;
|
2020-07-06 20:36:23 +02:00
|
|
|
}
|
2020-07-06 20:36:22 +02:00
|
|
|
|
2020-07-06 20:36:23 +02:00
|
|
|
ret = v4l2_async_notifier_register(&cal->v4l2_dev, &cal->notifier);
|
2016-01-06 21:37:26 -02:00
|
|
|
if (ret) {
|
2020-07-06 20:36:23 +02:00
|
|
|
cal_err(cal, "Error registering async notifier\n");
|
|
|
|
goto error;
|
2016-01-06 21:37:26 -02:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:19 +02:00
|
|
|
return 0;
|
2020-07-06 20:36:23 +02:00
|
|
|
|
|
|
|
error:
|
|
|
|
v4l2_async_notifier_cleanup(&cal->notifier);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cal_async_notifier_unregister(struct cal_dev *cal)
|
|
|
|
{
|
|
|
|
v4l2_async_notifier_unregister(&cal->notifier);
|
|
|
|
v4l2_async_notifier_cleanup(&cal->notifier);
|
2016-01-06 21:37:26 -02:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:30 +02:00
|
|
|
/* ------------------------------------------------------------------
|
|
|
|
* Media and V4L2 device handling
|
|
|
|
* ------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Register user-facing devices. To be called at the end of the probe function
|
|
|
|
* when all resources are initialized and ready.
|
|
|
|
*/
|
|
|
|
static int cal_media_register(struct cal_dev *cal)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2020-07-06 20:36:34 +02:00
|
|
|
ret = media_device_register(&cal->mdev);
|
|
|
|
if (ret) {
|
|
|
|
cal_err(cal, "Failed to register media device\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:30 +02:00
|
|
|
/*
|
|
|
|
* Register the async notifier. This may trigger registration of the
|
|
|
|
* V4L2 video devices if all subdevs are ready.
|
|
|
|
*/
|
|
|
|
ret = cal_async_notifier_register(cal);
|
2020-07-06 20:36:34 +02:00
|
|
|
if (ret) {
|
|
|
|
media_device_unregister(&cal->mdev);
|
2020-07-06 20:36:30 +02:00
|
|
|
return ret;
|
2020-07-06 20:36:34 +02:00
|
|
|
}
|
2020-07-06 20:36:30 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Unregister the user-facing devices, but don't free memory yet. To be called
|
|
|
|
* at the beginning of the remove function, to disallow access from userspace.
|
|
|
|
*/
|
|
|
|
static void cal_media_unregister(struct cal_dev *cal)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
/* Unregister all the V4L2 video devices. */
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cal->ctx); i++) {
|
|
|
|
if (cal->ctx[i])
|
|
|
|
cal_ctx_v4l2_unregister(cal->ctx[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
cal_async_notifier_unregister(cal);
|
2020-07-06 20:36:34 +02:00
|
|
|
media_device_unregister(&cal->mdev);
|
2020-07-06 20:36:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize the in-kernel objects. To be called at the beginning of the probe
|
|
|
|
* function, before the V4L2 device is used by the driver.
|
|
|
|
*/
|
|
|
|
static int cal_media_init(struct cal_dev *cal)
|
|
|
|
{
|
2020-07-06 20:36:34 +02:00
|
|
|
struct media_device *mdev = &cal->mdev;
|
2020-07-06 20:36:30 +02:00
|
|
|
int ret;
|
|
|
|
|
2020-07-06 20:36:34 +02:00
|
|
|
mdev->dev = cal->dev;
|
|
|
|
mdev->hw_revision = cal->revision;
|
|
|
|
strscpy(mdev->model, "CAL", sizeof(mdev->model));
|
|
|
|
snprintf(mdev->bus_info, sizeof(mdev->bus_info), "platform:%s",
|
|
|
|
dev_name(mdev->dev));
|
|
|
|
media_device_init(mdev);
|
|
|
|
|
2020-07-06 20:36:30 +02:00
|
|
|
/*
|
|
|
|
* Initialize the V4L2 device (despite the function name, this performs
|
|
|
|
* initialization, not registration).
|
|
|
|
*/
|
2020-07-06 20:36:34 +02:00
|
|
|
cal->v4l2_dev.mdev = mdev;
|
2020-07-06 20:36:33 +02:00
|
|
|
ret = v4l2_device_register(cal->dev, &cal->v4l2_dev);
|
2020-07-06 20:36:30 +02:00
|
|
|
if (ret) {
|
|
|
|
cal_err(cal, "Failed to register V4L2 device\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:33 +02:00
|
|
|
vb2_dma_contig_set_max_seg_size(cal->dev, DMA_BIT_MASK(32));
|
2020-07-06 20:36:30 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Cleanup the in-kernel objects, freeing memory. To be called at the very end
|
|
|
|
* of the remove sequence, when nothing (including userspace) can access the
|
|
|
|
* objects anymore.
|
|
|
|
*/
|
|
|
|
static void cal_media_cleanup(struct cal_dev *cal)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cal->ctx); i++) {
|
|
|
|
if (cal->ctx[i])
|
|
|
|
cal_ctx_v4l2_cleanup(cal->ctx[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
v4l2_device_unregister(&cal->v4l2_dev);
|
2020-07-06 20:36:34 +02:00
|
|
|
media_device_cleanup(&cal->mdev);
|
|
|
|
|
2020-07-06 20:36:33 +02:00
|
|
|
vb2_dma_contig_clear_max_seg_size(cal->dev);
|
2020-07-06 20:36:30 +02:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:19 +02:00
|
|
|
/* ------------------------------------------------------------------
|
|
|
|
* Initialization and module stuff
|
|
|
|
* ------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
2020-07-06 20:36:09 +02:00
|
|
|
static struct cal_ctx *cal_ctx_create(struct cal_dev *cal, int inst)
|
2016-01-06 21:37:26 -02:00
|
|
|
{
|
|
|
|
struct cal_ctx *ctx;
|
|
|
|
int ret;
|
|
|
|
|
2020-07-06 20:36:33 +02:00
|
|
|
ctx = devm_kzalloc(cal->dev, sizeof(*ctx), GFP_KERNEL);
|
2016-01-06 21:37:26 -02:00
|
|
|
if (!ctx)
|
2016-02-15 18:01:42 -02:00
|
|
|
return NULL;
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:35:47 +02:00
|
|
|
ctx->cal = cal;
|
2020-07-06 20:35:48 +02:00
|
|
|
ctx->phy = cal->phy[inst];
|
2020-07-06 20:35:59 +02:00
|
|
|
ctx->index = inst;
|
2020-07-06 20:35:25 +02:00
|
|
|
ctx->cport = inst;
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:36:15 +02:00
|
|
|
ret = cal_ctx_v4l2_init(ctx);
|
|
|
|
if (ret)
|
|
|
|
return NULL;
|
|
|
|
|
2020-07-06 20:36:14 +02:00
|
|
|
return ctx;
|
2016-01-06 21:37:26 -02:00
|
|
|
}
|
|
|
|
|
2020-04-28 23:34:19 +02:00
|
|
|
static const struct of_device_id cal_of_match[] = {
|
|
|
|
{
|
|
|
|
.compatible = "ti,dra72-cal",
|
|
|
|
.data = (void *)&dra72x_cal_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.compatible = "ti,dra72-pre-es2-cal",
|
|
|
|
.data = (void *)&dra72x_es1_cal_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.compatible = "ti,dra76-cal",
|
|
|
|
.data = (void *)&dra76x_cal_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.compatible = "ti,am654-cal",
|
|
|
|
.data = (void *)&am654_cal_data,
|
|
|
|
},
|
|
|
|
{},
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, cal_of_match);
|
2019-11-12 15:53:31 +01:00
|
|
|
|
2020-07-06 20:36:32 +02:00
|
|
|
/* Get hardware revision and info. */
|
|
|
|
|
|
|
|
#define CAL_HL_HWINFO_VALUE 0xa3c90469
|
|
|
|
|
2020-07-06 20:35:55 +02:00
|
|
|
static void cal_get_hwinfo(struct cal_dev *cal)
|
|
|
|
{
|
|
|
|
u32 hwinfo;
|
|
|
|
|
2020-07-06 20:36:44 +02:00
|
|
|
cal->revision = cal_read(cal, CAL_HL_REVISION);
|
2020-07-06 20:36:34 +02:00
|
|
|
switch (FIELD_GET(CAL_HL_REVISION_SCHEME_MASK, cal->revision)) {
|
2020-07-06 20:36:32 +02:00
|
|
|
case CAL_HL_REVISION_SCHEME_H08:
|
|
|
|
cal_dbg(3, cal, "CAL HW revision %lu.%lu.%lu (0x%08x)\n",
|
2020-07-06 20:36:34 +02:00
|
|
|
FIELD_GET(CAL_HL_REVISION_MAJOR_MASK, cal->revision),
|
|
|
|
FIELD_GET(CAL_HL_REVISION_MINOR_MASK, cal->revision),
|
|
|
|
FIELD_GET(CAL_HL_REVISION_RTL_MASK, cal->revision),
|
|
|
|
cal->revision);
|
2020-07-06 20:36:32 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CAL_HL_REVISION_SCHEME_LEGACY:
|
|
|
|
default:
|
|
|
|
cal_info(cal, "Unexpected CAL HW revision 0x%08x\n",
|
2020-07-06 20:36:34 +02:00
|
|
|
cal->revision);
|
2020-07-06 20:36:32 +02:00
|
|
|
break;
|
|
|
|
}
|
2020-07-06 20:35:55 +02:00
|
|
|
|
2020-07-06 20:36:44 +02:00
|
|
|
hwinfo = cal_read(cal, CAL_HL_HWINFO);
|
2020-07-06 20:36:32 +02:00
|
|
|
if (hwinfo != CAL_HL_HWINFO_VALUE)
|
|
|
|
cal_info(cal, "CAL_HL_HWINFO = 0x%08x, expected 0x%08x\n",
|
|
|
|
hwinfo, CAL_HL_HWINFO_VALUE);
|
2020-07-06 20:35:55 +02:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:45 +02:00
|
|
|
static int cal_init_camerarx_regmap(struct cal_dev *cal)
|
|
|
|
{
|
|
|
|
struct platform_device *pdev = to_platform_device(cal->dev);
|
|
|
|
struct device_node *np = cal->dev->of_node;
|
|
|
|
struct regmap_config config = { };
|
|
|
|
struct regmap *syscon;
|
|
|
|
struct resource *res;
|
|
|
|
unsigned int offset;
|
|
|
|
void __iomem *base;
|
|
|
|
|
|
|
|
syscon = syscon_regmap_lookup_by_phandle_args(np, "ti,camerrx-control",
|
|
|
|
1, &offset);
|
|
|
|
if (!IS_ERR(syscon)) {
|
|
|
|
cal->syscon_camerrx = syscon;
|
|
|
|
cal->syscon_camerrx_offset = offset;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_warn(cal->dev, "failed to get ti,camerrx-control: %ld\n",
|
|
|
|
PTR_ERR(syscon));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Backward DTS compatibility. If syscon entry is not present then
|
|
|
|
* check if the camerrx_control resource is present.
|
|
|
|
*/
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
|
|
|
"camerrx_control");
|
|
|
|
base = devm_ioremap_resource(cal->dev, res);
|
|
|
|
if (IS_ERR(base)) {
|
|
|
|
cal_err(cal, "failed to ioremap camerrx_control\n");
|
|
|
|
return PTR_ERR(base);
|
|
|
|
}
|
|
|
|
|
|
|
|
cal_dbg(1, cal, "ioresource %s at %pa - %pa\n",
|
|
|
|
res->name, &res->start, &res->end);
|
|
|
|
|
|
|
|
config.reg_bits = 32;
|
|
|
|
config.reg_stride = 4;
|
|
|
|
config.val_bits = 32;
|
|
|
|
config.max_register = resource_size(res) - 4;
|
|
|
|
|
|
|
|
syscon = regmap_init_mmio(NULL, base, &config);
|
|
|
|
if (IS_ERR(syscon)) {
|
|
|
|
pr_err("regmap init failed\n");
|
|
|
|
return PTR_ERR(syscon);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In this case the base already point to the direct CM register so no
|
|
|
|
* need for an offset.
|
|
|
|
*/
|
|
|
|
cal->syscon_camerrx = syscon;
|
|
|
|
cal->syscon_camerrx_offset = 0;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-01-06 21:37:26 -02:00
|
|
|
static int cal_probe(struct platform_device *pdev)
|
|
|
|
{
|
2020-07-06 20:35:47 +02:00
|
|
|
struct cal_dev *cal;
|
2018-09-29 15:54:18 -04:00
|
|
|
struct cal_ctx *ctx;
|
2020-07-06 20:36:19 +02:00
|
|
|
bool connected = false;
|
2020-07-06 20:35:34 +02:00
|
|
|
unsigned int i;
|
2016-01-06 21:37:26 -02:00
|
|
|
int ret;
|
|
|
|
int irq;
|
|
|
|
|
2020-07-06 20:35:47 +02:00
|
|
|
cal = devm_kzalloc(&pdev->dev, sizeof(*cal), GFP_KERNEL);
|
|
|
|
if (!cal)
|
2016-01-06 21:37:26 -02:00
|
|
|
return -ENOMEM;
|
|
|
|
|
2020-07-06 20:35:47 +02:00
|
|
|
cal->data = of_device_get_match_data(&pdev->dev);
|
|
|
|
if (!cal->data) {
|
2019-11-12 15:53:31 +01:00
|
|
|
dev_err(&pdev->dev, "Could not get feature data based on compatible version\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:33 +02:00
|
|
|
cal->dev = &pdev->dev;
|
2020-07-06 20:36:08 +02:00
|
|
|
platform_set_drvdata(pdev, cal);
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:36:08 +02:00
|
|
|
/* Acquire resources: clocks, CAMERARX regmap, I/O memory and IRQ. */
|
2020-07-06 20:35:47 +02:00
|
|
|
cal->fclk = devm_clk_get(&pdev->dev, "fck");
|
|
|
|
if (IS_ERR(cal->fclk)) {
|
2020-03-25 13:15:10 +01:00
|
|
|
dev_err(&pdev->dev, "cannot get CAL fclk\n");
|
2020-07-06 20:35:47 +02:00
|
|
|
return PTR_ERR(cal->fclk);
|
2020-03-25 13:15:10 +01:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:45 +02:00
|
|
|
ret = cal_init_camerarx_regmap(cal);
|
2020-07-06 20:36:05 +02:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2019-11-12 15:53:31 +01:00
|
|
|
|
2020-07-06 20:35:47 +02:00
|
|
|
cal->res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
2016-01-06 21:37:26 -02:00
|
|
|
"cal_top");
|
2020-07-06 20:35:47 +02:00
|
|
|
cal->base = devm_ioremap_resource(&pdev->dev, cal->res);
|
|
|
|
if (IS_ERR(cal->base))
|
|
|
|
return PTR_ERR(cal->base);
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:35:47 +02:00
|
|
|
cal_dbg(1, cal, "ioresource %s at %pa - %pa\n",
|
|
|
|
cal->res->name, &cal->res->start, &cal->res->end);
|
2016-01-06 21:37:26 -02:00
|
|
|
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
2020-07-06 20:35:47 +02:00
|
|
|
cal_dbg(1, cal, "got irq# %d\n", irq);
|
2016-01-06 21:37:26 -02:00
|
|
|
ret = devm_request_irq(&pdev->dev, irq, cal_irq, 0, CAL_MODULE_NAME,
|
2020-07-06 20:35:47 +02:00
|
|
|
cal);
|
2016-01-06 21:37:26 -02:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2020-07-06 20:36:31 +02:00
|
|
|
/* Read the revision and hardware info to verify hardware access. */
|
|
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
ret = pm_runtime_get_sync(&pdev->dev);
|
|
|
|
if (ret)
|
|
|
|
goto error_pm_runtime;
|
|
|
|
|
|
|
|
cal_get_hwinfo(cal);
|
|
|
|
pm_runtime_put_sync(&pdev->dev);
|
|
|
|
|
2020-07-06 20:36:08 +02:00
|
|
|
/* Create CAMERARX PHYs. */
|
2020-07-06 20:36:03 +02:00
|
|
|
for (i = 0; i < cal->data->num_csi2_phy; ++i) {
|
|
|
|
cal->phy[i] = cal_camerarx_create(cal, i);
|
2020-07-06 20:36:18 +02:00
|
|
|
if (IS_ERR(cal->phy[i])) {
|
|
|
|
ret = PTR_ERR(cal->phy[i]);
|
|
|
|
cal->phy[i] = NULL;
|
|
|
|
goto error_camerarx;
|
|
|
|
}
|
2020-07-06 20:36:19 +02:00
|
|
|
|
|
|
|
if (cal->phy[i]->sensor_node)
|
|
|
|
connected = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!connected) {
|
|
|
|
cal_err(cal, "Neither port is configured, no point in staying up\n");
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto error_camerarx;
|
2019-11-12 15:53:31 +01:00
|
|
|
}
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:36:30 +02:00
|
|
|
/* Initialize the media device. */
|
|
|
|
ret = cal_media_init(cal);
|
|
|
|
if (ret < 0)
|
2020-07-06 20:36:18 +02:00
|
|
|
goto error_camerarx;
|
2020-07-06 20:36:14 +02:00
|
|
|
|
2020-07-06 20:36:08 +02:00
|
|
|
/* Create contexts. */
|
2020-07-06 20:36:19 +02:00
|
|
|
for (i = 0; i < cal->data->num_csi2_phy; ++i) {
|
|
|
|
if (!cal->phy[i]->sensor_node)
|
|
|
|
continue;
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:36:19 +02:00
|
|
|
cal->ctx[i] = cal_ctx_create(cal, i);
|
|
|
|
if (!cal->ctx[i]) {
|
|
|
|
cal_err(cal, "Failed to create context %u\n", i);
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto error_context;
|
|
|
|
}
|
2016-01-06 21:37:26 -02:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:30 +02:00
|
|
|
/* Register the media device. */
|
|
|
|
ret = cal_media_register(cal);
|
2020-07-06 20:36:23 +02:00
|
|
|
if (ret)
|
2020-07-06 20:36:31 +02:00
|
|
|
goto error_context;
|
2020-07-06 20:36:23 +02:00
|
|
|
|
2016-01-06 21:37:26 -02:00
|
|
|
return 0;
|
|
|
|
|
2020-07-06 20:36:19 +02:00
|
|
|
error_context:
|
2020-07-06 20:36:20 +02:00
|
|
|
for (i = 0; i < ARRAY_SIZE(cal->ctx); i++) {
|
2020-07-06 20:35:47 +02:00
|
|
|
ctx = cal->ctx[i];
|
2020-07-06 20:36:23 +02:00
|
|
|
if (ctx)
|
2020-07-06 20:36:16 +02:00
|
|
|
cal_ctx_v4l2_cleanup(ctx);
|
2018-09-29 15:54:18 -04:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:30 +02:00
|
|
|
cal_media_cleanup(cal);
|
2020-07-06 20:36:18 +02:00
|
|
|
|
|
|
|
error_camerarx:
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cal->phy); i++)
|
|
|
|
cal_camerarx_destroy(cal->phy[i]);
|
|
|
|
|
2020-07-06 20:36:31 +02:00
|
|
|
error_pm_runtime:
|
|
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
|
2016-01-06 21:37:26 -02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cal_remove(struct platform_device *pdev)
|
|
|
|
{
|
2020-07-06 20:35:47 +02:00
|
|
|
struct cal_dev *cal = platform_get_drvdata(pdev);
|
2020-07-06 20:35:34 +02:00
|
|
|
unsigned int i;
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:35:47 +02:00
|
|
|
cal_dbg(1, cal, "Removing %s\n", CAL_MODULE_NAME);
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-03-25 13:14:56 +01:00
|
|
|
pm_runtime_get_sync(&pdev->dev);
|
2016-01-06 21:37:26 -02:00
|
|
|
|
2020-07-06 20:36:30 +02:00
|
|
|
cal_media_unregister(cal);
|
2020-07-06 20:36:26 +02:00
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cal->phy); i++) {
|
|
|
|
if (cal->phy[i])
|
|
|
|
cal_camerarx_disable(cal->phy[i]);
|
|
|
|
}
|
|
|
|
|
2020-07-06 20:36:30 +02:00
|
|
|
cal_media_cleanup(cal);
|
2020-07-06 20:36:14 +02:00
|
|
|
|
2020-07-06 20:36:18 +02:00
|
|
|
for (i = 0; i < ARRAY_SIZE(cal->phy); i++)
|
|
|
|
cal_camerarx_destroy(cal->phy[i]);
|
|
|
|
|
2020-03-25 13:14:56 +01:00
|
|
|
pm_runtime_put_sync(&pdev->dev);
|
2016-01-06 21:37:26 -02:00
|
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-03-25 13:14:55 +01:00
|
|
|
static int cal_runtime_resume(struct device *dev)
|
|
|
|
{
|
2020-07-06 20:35:47 +02:00
|
|
|
struct cal_dev *cal = dev_get_drvdata(dev);
|
2020-03-25 13:14:55 +01:00
|
|
|
|
2020-07-06 20:35:47 +02:00
|
|
|
if (cal->data->flags & DRA72_CAL_PRE_ES2_LDO_DISABLE) {
|
2020-03-25 13:14:55 +01:00
|
|
|
/*
|
|
|
|
* Apply errata on both port everytime we (re-)enable
|
|
|
|
* the clock
|
|
|
|
*/
|
2020-07-06 20:35:54 +02:00
|
|
|
cal_camerarx_i913_errata(cal->phy[0]);
|
|
|
|
cal_camerarx_i913_errata(cal->phy[1]);
|
2020-03-25 13:14:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dev_pm_ops cal_pm_ops = {
|
|
|
|
.runtime_resume = cal_runtime_resume,
|
|
|
|
};
|
|
|
|
|
2016-01-06 21:37:26 -02:00
|
|
|
static struct platform_driver cal_pdrv = {
|
|
|
|
.probe = cal_probe,
|
|
|
|
.remove = cal_remove,
|
|
|
|
.driver = {
|
|
|
|
.name = CAL_MODULE_NAME,
|
2020-03-25 13:14:55 +01:00
|
|
|
.pm = &cal_pm_ops,
|
2020-04-28 23:34:19 +02:00
|
|
|
.of_match_table = cal_of_match,
|
2016-01-06 21:37:26 -02:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
module_platform_driver(cal_pdrv);
|