* [PATCH v5 3/6] cx25840: add pin to pad mapping and output format configuration
From: Maciej S. Szmigiero @ 2017-12-22 23:18 UTC (permalink / raw)
To: Michael Krufky, Mauro Carvalho Chehab
Cc: Andy Walls, linux-kernel, linux-media, Hans Verkuil,
Philippe Ombredanne
In-Reply-To: <cover.1513982691.git.mail@maciej.szmigiero.name>
This commit adds pin to pad mapping and output format configuration support
in CX2584x-series chips to cx25840 driver.
This functionality is then used to allow disabling ivtv-specific hacks
(called a "generic mode"), so cx25840 driver can be used for other devices
not needing them without risking compatibility problems.
Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
---
drivers/media/i2c/cx25840/cx25840-core.c | 396 ++++++++++++++++++++++++++++++-
drivers/media/i2c/cx25840/cx25840-core.h | 13 +
drivers/media/i2c/cx25840/cx25840-vbi.c | 3 +
include/media/drv-intf/cx25840.h | 74 +++++-
4 files changed, 484 insertions(+), 2 deletions(-)
diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c
index 2189980a0f29..e2fd33a64550 100644
--- a/drivers/media/i2c/cx25840/cx25840-core.c
+++ b/drivers/media/i2c/cx25840/cx25840-core.c
@@ -21,6 +21,9 @@
* CX23888 DIF support for the HVR1850
* Copyright (C) 2011 Steven Toth <stoth@kernellabs.com>
*
+ * CX2584x pin to pad mapping and output format configuration support are
+ * Copyright (C) 2011 Maciej S. Szmigiero <mail@maciej.szmigiero.name>
+ *
* 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
@@ -316,6 +319,260 @@ static int cx23885_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
return 0;
}
+static u8 cx25840_function_to_pad(struct i2c_client *client, u8 function)
+{
+ switch (function) {
+ case CX25840_PAD_ACTIVE:
+ return 1;
+
+ case CX25840_PAD_VACTIVE:
+ return 2;
+
+ case CX25840_PAD_CBFLAG:
+ return 3;
+
+ case CX25840_PAD_VID_DATA_EXT0:
+ return 4;
+
+ case CX25840_PAD_VID_DATA_EXT1:
+ return 5;
+
+ case CX25840_PAD_GPO0:
+ return 6;
+
+ case CX25840_PAD_GPO1:
+ return 7;
+
+ case CX25840_PAD_GPO2:
+ return 8;
+
+ case CX25840_PAD_GPO3:
+ return 9;
+
+ case CX25840_PAD_IRQ_N:
+ return 10;
+
+ case CX25840_PAD_AC_SYNC:
+ return 11;
+
+ case CX25840_PAD_AC_SDOUT:
+ return 12;
+
+ case CX25840_PAD_PLL_CLK:
+ return 13;
+
+ case CX25840_PAD_VRESET:
+ return 14;
+
+ default:
+ if (function != CX25840_PAD_DEFAULT)
+ v4l_err(client,
+ "invalid function %u, assuming default\n",
+ (unsigned int)function);
+ return 0;
+ }
+}
+
+static void cx25840_set_invert(u8 *pinctrl3, u8 *voutctrl4, u8 function,
+ u8 pin, bool invert)
+{
+ switch (function) {
+ case CX25840_PAD_IRQ_N:
+ if (invert)
+ *pinctrl3 &= ~2;
+ else
+ *pinctrl3 |= 2;
+ break;
+
+ case CX25840_PAD_ACTIVE:
+ if (invert)
+ *voutctrl4 |= BIT(2);
+ else
+ *voutctrl4 &= ~BIT(2);
+ break;
+
+ case CX25840_PAD_VACTIVE:
+ if (invert)
+ *voutctrl4 |= BIT(5);
+ else
+ *voutctrl4 &= ~BIT(5);
+ break;
+
+ case CX25840_PAD_CBFLAG:
+ if (invert)
+ *voutctrl4 |= BIT(4);
+ else
+ *voutctrl4 &= ~BIT(4);
+ break;
+
+ case CX25840_PAD_VRESET:
+ if (invert)
+ *voutctrl4 |= BIT(0);
+ else
+ *voutctrl4 &= ~BIT(0);
+ break;
+ }
+
+ if (function != CX25840_PAD_DEFAULT)
+ return;
+
+ switch (pin) {
+ case CX25840_PIN_DVALID_PRGM0:
+ if (invert)
+ *voutctrl4 |= BIT(6);
+ else
+ *voutctrl4 &= ~BIT(6);
+ break;
+
+ case CX25840_PIN_HRESET_PRGM2:
+ if (invert)
+ *voutctrl4 |= BIT(1);
+ else
+ *voutctrl4 &= ~BIT(1);
+ break;
+ }
+}
+
+static int cx25840_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
+ struct v4l2_subdev_io_pin_config *p)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ unsigned int i;
+ u8 pinctrl[6], pinconf[10], voutctrl4;
+
+ for (i = 0; i < 6; i++)
+ pinctrl[i] = cx25840_read(client, 0x114 + i);
+
+ for (i = 0; i < 10; i++)
+ pinconf[i] = cx25840_read(client, 0x11c + i);
+
+ voutctrl4 = cx25840_read(client, 0x407);
+
+ for (i = 0; i < n; i++) {
+ u8 strength = p[i].strength;
+
+ if (strength != CX25840_PIN_DRIVE_SLOW &&
+ strength != CX25840_PIN_DRIVE_MEDIUM &&
+ strength != CX25840_PIN_DRIVE_FAST) {
+
+ v4l_err(client,
+ "invalid drive speed for pin %u (%u), assuming fast\n",
+ (unsigned int)p[i].pin,
+ (unsigned int)strength);
+
+ strength = CX25840_PIN_DRIVE_FAST;
+ }
+
+ switch (p[i].pin) {
+ case CX25840_PIN_DVALID_PRGM0:
+ if (p[i].flags & V4L2_SUBDEV_IO_PIN_DISABLE)
+ pinctrl[0] &= ~BIT(6);
+ else
+ pinctrl[0] |= BIT(6);
+
+ pinconf[3] &= 0xf0;
+ pinconf[3] |= cx25840_function_to_pad(client,
+ p[i].function);
+
+ cx25840_set_invert(&pinctrl[3], &voutctrl4,
+ p[i].function,
+ CX25840_PIN_DVALID_PRGM0,
+ p[i].flags &
+ V4L2_SUBDEV_IO_PIN_ACTIVE_LOW);
+
+ pinctrl[4] &= ~(3 << 2); /* CX25840_PIN_DRIVE_MEDIUM */
+ switch (strength) {
+ case CX25840_PIN_DRIVE_SLOW:
+ pinctrl[4] |= 1 << 2;
+ break;
+
+ case CX25840_PIN_DRIVE_FAST:
+ pinctrl[4] |= 2 << 2;
+ break;
+ }
+
+ break;
+
+ case CX25840_PIN_HRESET_PRGM2:
+ if (p[i].flags & V4L2_SUBDEV_IO_PIN_DISABLE)
+ pinctrl[1] &= ~BIT(0);
+ else
+ pinctrl[1] |= BIT(0);
+
+ pinconf[4] &= 0xf0;
+ pinconf[4] |= cx25840_function_to_pad(client,
+ p[i].function);
+
+ cx25840_set_invert(&pinctrl[3], &voutctrl4,
+ p[i].function,
+ CX25840_PIN_HRESET_PRGM2,
+ p[i].flags &
+ V4L2_SUBDEV_IO_PIN_ACTIVE_LOW);
+
+ pinctrl[4] &= ~(3 << 2); /* CX25840_PIN_DRIVE_MEDIUM */
+ switch (strength) {
+ case CX25840_PIN_DRIVE_SLOW:
+ pinctrl[4] |= 1 << 2;
+ break;
+
+ case CX25840_PIN_DRIVE_FAST:
+ pinctrl[4] |= 2 << 2;
+ break;
+ }
+
+ break;
+
+ case CX25840_PIN_PLL_CLK_PRGM7:
+ if (p[i].flags & V4L2_SUBDEV_IO_PIN_DISABLE)
+ pinctrl[2] &= ~BIT(2);
+ else
+ pinctrl[2] |= BIT(2);
+
+ switch (p[i].function) {
+ case CX25840_PAD_XTI_X5_DLL:
+ pinconf[6] = 0;
+ break;
+
+ case CX25840_PAD_AUX_PLL:
+ pinconf[6] = 1;
+ break;
+
+ case CX25840_PAD_VID_PLL:
+ pinconf[6] = 5;
+ break;
+
+ case CX25840_PAD_XTI:
+ pinconf[6] = 2;
+ break;
+
+ default:
+ pinconf[6] = 3;
+ pinconf[6] |=
+ cx25840_function_to_pad(client,
+ p[i].function)
+ << 4;
+ }
+
+ break;
+
+ default:
+ v4l_err(client, "invalid or unsupported pin %u\n",
+ (unsigned int)p[i].pin);
+ break;
+ }
+ }
+
+ cx25840_write(client, 0x407, voutctrl4);
+
+ for (i = 0; i < 6; i++)
+ cx25840_write(client, 0x114 + i, pinctrl[i]);
+
+ for (i = 0; i < 10; i++)
+ cx25840_write(client, 0x11c + i, pinconf[i]);
+
+ return 0;
+}
+
static int common_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
struct v4l2_subdev_io_pin_config *pincfg)
{
@@ -323,6 +580,8 @@ static int common_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
if (is_cx2388x(state))
return cx23885_s_io_pin_config(sd, n, pincfg);
+ else if (is_cx2584x(state))
+ return cx25840_s_io_pin_config(sd, n, pincfg);
return 0;
}
@@ -455,6 +714,22 @@ static void cx25840_initialize(struct i2c_client *client)
/* (re)set input */
set_input(client, state->vid_input, state->aud_input);
+ if (state->generic_mode) {
+ /* set datasheet video output defaults */
+ cx25840_vconfig(client, CX25840_VCONFIG_FMT_BT656 |
+ CX25840_VCONFIG_RES_8BIT |
+ CX25840_VCONFIG_VBIRAW_DISABLED |
+ CX25840_VCONFIG_ANCDATA_ENABLED |
+ CX25840_VCONFIG_TASKBIT_ONE |
+ CX25840_VCONFIG_ACTIVE_HORIZONTAL |
+ CX25840_VCONFIG_VALID_NORMAL |
+ CX25840_VCONFIG_HRESETW_NORMAL |
+ CX25840_VCONFIG_CLKGATE_NONE |
+ CX25840_VCONFIG_DCMODE_DWORDS |
+ CX25840_VCONFIG_IDID0S_NORMAL |
+ CX25840_VCONFIG_VIPCLAMP_DISABLED);
+ }
+
/* start microcontroller */
cx25840_and_or(client, 0x803, ~0x10, 0x10);
}
@@ -1387,7 +1662,9 @@ static int cx25840_set_fmt(struct v4l2_subdev *sd,
Hsrc |= (cx25840_read(client, 0x471) & 0xf0) >> 4;
}
- Vlines = fmt->height + (is_50Hz ? 4 : 7);
+ Vlines = fmt->height;
+ if (!state->generic_mode)
+ Vlines += is_50Hz ? 4 : 7;
/*
* We keep 1 margin for the Vsrc < Vlines check since the
@@ -1631,6 +1908,119 @@ static void log_audio_status(struct i2c_client *client)
}
}
+#define CX25840_VCONFIG_OPTION(state, cfg_in, opt_msk) \
+ do { \
+ if ((cfg_in) & (opt_msk)) { \
+ (state)->vid_config &= ~(opt_msk); \
+ (state)->vid_config |= (cfg_in) & (opt_msk); \
+ } \
+ } while (0)
+
+#define CX25840_VCONFIG_SET_BIT(state, opt_msk, voc, idx, bit, oneval) \
+ do { \
+ if ((state)->vid_config & (opt_msk)) { \
+ if (((state)->vid_config & (opt_msk)) == \
+ (oneval)) \
+ (voc)[idx] |= BIT(bit); \
+ else \
+ (voc)[idx] &= ~BIT(bit); \
+ } \
+ } while (0)
+
+int cx25840_vconfig(struct i2c_client *client, u32 cfg_in)
+{
+ struct cx25840_state *state = to_state(i2c_get_clientdata(client));
+ u8 voutctrl[3];
+ unsigned int i;
+
+ /* apply incoming options to the current state */
+ CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_FMT_MASK);
+ CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_RES_MASK);
+ CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VBIRAW_MASK);
+ CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ANCDATA_MASK);
+ CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_TASKBIT_MASK);
+ CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ACTIVE_MASK);
+ CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VALID_MASK);
+ CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_HRESETW_MASK);
+ CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_CLKGATE_MASK);
+ CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_DCMODE_MASK);
+ CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_IDID0S_MASK);
+ CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VIPCLAMP_MASK);
+
+ for (i = 0; i < 3; i++)
+ voutctrl[i] = cx25840_read(client, 0x404 + i);
+
+ /* apply state to hardware regs */
+ if (state->vid_config & CX25840_VCONFIG_FMT_MASK)
+ voutctrl[0] &= ~3;
+ switch (state->vid_config & CX25840_VCONFIG_FMT_MASK) {
+ case CX25840_VCONFIG_FMT_BT656:
+ voutctrl[0] |= 1;
+ break;
+
+ case CX25840_VCONFIG_FMT_VIP11:
+ voutctrl[0] |= 2;
+ break;
+
+ case CX25840_VCONFIG_FMT_VIP2:
+ voutctrl[0] |= 3;
+ break;
+
+ case CX25840_VCONFIG_FMT_BT601:
+ /* zero */
+ default:
+ break;
+ }
+
+ CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_RES_MASK, voutctrl,
+ 0, 2, CX25840_VCONFIG_RES_10BIT);
+ CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VBIRAW_MASK, voutctrl,
+ 0, 3, CX25840_VCONFIG_VBIRAW_ENABLED);
+ CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ANCDATA_MASK, voutctrl,
+ 0, 4, CX25840_VCONFIG_ANCDATA_ENABLED);
+ CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_TASKBIT_MASK, voutctrl,
+ 0, 5, CX25840_VCONFIG_TASKBIT_ONE);
+ CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ACTIVE_MASK, voutctrl,
+ 1, 2, CX25840_VCONFIG_ACTIVE_HORIZONTAL);
+ CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VALID_MASK, voutctrl,
+ 1, 3, CX25840_VCONFIG_VALID_ANDACTIVE);
+ CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_HRESETW_MASK, voutctrl,
+ 1, 4, CX25840_VCONFIG_HRESETW_PIXCLK);
+
+ if (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK)
+ voutctrl[1] &= ~(3 << 6);
+ switch (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK) {
+ case CX25840_VCONFIG_CLKGATE_VALID:
+ voutctrl[1] |= 2;
+ break;
+
+ case CX25840_VCONFIG_CLKGATE_VALIDACTIVE:
+ voutctrl[1] |= 3;
+ break;
+
+ case CX25840_VCONFIG_CLKGATE_NONE:
+ /* zero */
+ default:
+ break;
+ }
+
+
+ CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_DCMODE_MASK, voutctrl,
+ 2, 0, CX25840_VCONFIG_DCMODE_BYTES);
+ CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_IDID0S_MASK, voutctrl,
+ 2, 1, CX25840_VCONFIG_IDID0S_LINECNT);
+ CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VIPCLAMP_MASK, voutctrl,
+ 2, 4, CX25840_VCONFIG_VIPCLAMP_ENABLED);
+
+ for (i = 0; i < 3; i++)
+ cx25840_write(client, 0x404 + i, voutctrl[i]);
+
+ return 0;
+}
+
+#undef CX25840_VCONFIG_SET_BIT
+#undef CX25840_VCONFIG_OPTION
+
/* ----------------------------------------------------------------------- */
/* This load_fw operation must be called to load the driver's firmware.
@@ -1820,6 +2210,9 @@ static int cx25840_s_video_routing(struct v4l2_subdev *sd,
if (is_cx23888(state))
cx23888_std_setup(client);
+ if (is_cx2584x(state) && state->generic_mode)
+ cx25840_vconfig(client, config);
+
return set_input(client, input, state->aud_input);
}
@@ -5319,6 +5712,7 @@ static int cx25840_probe(struct i2c_client *client,
struct cx25840_platform_data *pdata = client->dev.platform_data;
state->pvr150_workaround = pdata->pvr150_workaround;
+ state->generic_mode = pdata->generic_mode;
}
cx25840_ir_probe(sd);
diff --git a/drivers/media/i2c/cx25840/cx25840-core.h b/drivers/media/i2c/cx25840/cx25840-core.h
index 877b930e5b1f..8666048d6d64 100644
--- a/drivers/media/i2c/cx25840/cx25840-core.h
+++ b/drivers/media/i2c/cx25840/cx25840-core.h
@@ -54,10 +54,12 @@ enum cx25840_media_pads {
* @mute: audio mute V4L2 control (non-cx2583x devices only)
* @pvr150_workaround: whether we enable workaround for Hauppauge PVR150
* hardware bug (audio dropping out)
+ * @generic_mode: whether we disable ivtv-specific hacks
* @radio: set if we are currently in the radio mode, otherwise
* the current mode is non-radio (that is, video)
* @std: currently set video standard
* @vid_input: currently set video input
+ * @vid_config: currently set video output configuration
* @aud_input: currently set audio input
* @audclk_freq: currently set audio sample rate
* @audmode: currently set audio mode (when in non-radio mode)
@@ -84,9 +86,11 @@ struct cx25840_state {
struct v4l2_ctrl *mute;
};
int pvr150_workaround;
+ int generic_mode;
int radio;
v4l2_std_id std;
enum cx25840_video_input vid_input;
+ u32 vid_config;
enum cx25840_audio_input aud_input;
u32 audclk_freq;
int audmode;
@@ -119,6 +123,14 @@ static inline bool is_cx2583x(struct cx25840_state *state)
state->id == CX25837;
}
+static inline bool is_cx2584x(struct cx25840_state *state)
+{
+ return state->id == CX25840 ||
+ state->id == CX25841 ||
+ state->id == CX25842 ||
+ state->id == CX25843;
+}
+
static inline bool is_cx231xx(struct cx25840_state *state)
{
return state->id == CX2310X_AV;
@@ -156,6 +168,7 @@ int cx25840_and_or(struct i2c_client *client, u16 addr, unsigned mask, u8 value)
int cx25840_and_or4(struct i2c_client *client, u16 addr, u32 and_mask,
u32 or_value);
void cx25840_std_setup(struct i2c_client *client);
+int cx25840_vconfig(struct i2c_client *client, u32 cfg_in);
/* ----------------------------------------------------------------------- */
/* cx25850-firmware.c */
diff --git a/drivers/media/i2c/cx25840/cx25840-vbi.c b/drivers/media/i2c/cx25840/cx25840-vbi.c
index 8c99a79fb726..23b7c1fb28ab 100644
--- a/drivers/media/i2c/cx25840/cx25840-vbi.c
+++ b/drivers/media/i2c/cx25840/cx25840-vbi.c
@@ -95,6 +95,7 @@ int cx25840_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *
memset(svbi->service_lines, 0, sizeof(svbi->service_lines));
svbi->service_set = 0;
/* we're done if raw VBI is active */
+ /* this will have to be changed for generic_mode VBI */
if ((cx25840_read(client, 0x404) & 0x10) == 0)
return 0;
@@ -137,6 +138,7 @@ int cx25840_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt)
cx25840_write(client, 0x54f, vbi_offset);
else
cx25840_write(client, 0x47f, vbi_offset);
+ /* this will have to be changed for generic_mode VBI */
cx25840_write(client, 0x404, 0x2e);
return 0;
}
@@ -157,6 +159,7 @@ int cx25840_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *
cx25840_std_setup(client);
/* Sliced VBI */
+ /* this will have to be changed for generic_mode VBI */
cx25840_write(client, 0x404, 0x32); /* Ancillary data */
cx25840_write(client, 0x406, 0x13);
if (is_cx23888(state))
diff --git a/include/media/drv-intf/cx25840.h b/include/media/drv-intf/cx25840.h
index 783c5bdd63eb..dc104b4f09eb 100644
--- a/include/media/drv-intf/cx25840.h
+++ b/include/media/drv-intf/cx25840.h
@@ -88,6 +88,70 @@ enum cx25840_video_input {
CX25840_DIF_ON = 0x80000400,
};
+/* arguments to video s_routing config param */
+#define CX25840_VCONFIG_FMT_SHIFT 0
+#define CX25840_VCONFIG_FMT_MASK GENMASK(2, 0)
+#define CX25840_VCONFIG_FMT_BT601 BIT(0)
+#define CX25840_VCONFIG_FMT_BT656 BIT(1)
+#define CX25840_VCONFIG_FMT_VIP11 GENMASK(1, 0)
+#define CX25840_VCONFIG_FMT_VIP2 BIT(2)
+
+#define CX25840_VCONFIG_RES_SHIFT 3
+#define CX25840_VCONFIG_RES_MASK GENMASK(4, 3)
+#define CX25840_VCONFIG_RES_8BIT BIT(3)
+#define CX25840_VCONFIG_RES_10BIT BIT(4)
+
+#define CX25840_VCONFIG_VBIRAW_SHIFT 5
+#define CX25840_VCONFIG_VBIRAW_MASK GENMASK(6, 5)
+#define CX25840_VCONFIG_VBIRAW_DISABLED BIT(5)
+#define CX25840_VCONFIG_VBIRAW_ENABLED BIT(6)
+
+#define CX25840_VCONFIG_ANCDATA_SHIFT 7
+#define CX25840_VCONFIG_ANCDATA_MASK GENMASK(8, 7)
+#define CX25840_VCONFIG_ANCDATA_DISABLED BIT(7)
+#define CX25840_VCONFIG_ANCDATA_ENABLED BIT(8)
+
+#define CX25840_VCONFIG_TASKBIT_SHIFT 9
+#define CX25840_VCONFIG_TASKBIT_MASK GENMASK(10, 9)
+#define CX25840_VCONFIG_TASKBIT_ZERO BIT(9)
+#define CX25840_VCONFIG_TASKBIT_ONE BIT(10)
+
+#define CX25840_VCONFIG_ACTIVE_SHIFT 11
+#define CX25840_VCONFIG_ACTIVE_MASK GENMASK(12, 11)
+#define CX25840_VCONFIG_ACTIVE_COMPOSITE BIT(11)
+#define CX25840_VCONFIG_ACTIVE_HORIZONTAL BIT(12)
+
+#define CX25840_VCONFIG_VALID_SHIFT 13
+#define CX25840_VCONFIG_VALID_MASK GENMASK(14, 13)
+#define CX25840_VCONFIG_VALID_NORMAL BIT(13)
+#define CX25840_VCONFIG_VALID_ANDACTIVE BIT(14)
+
+#define CX25840_VCONFIG_HRESETW_SHIFT 15
+#define CX25840_VCONFIG_HRESETW_MASK GENMASK(16, 15)
+#define CX25840_VCONFIG_HRESETW_NORMAL BIT(15)
+#define CX25840_VCONFIG_HRESETW_PIXCLK BIT(16)
+
+#define CX25840_VCONFIG_CLKGATE_SHIFT 17
+#define CX25840_VCONFIG_CLKGATE_MASK GENMASK(18, 17)
+#define CX25840_VCONFIG_CLKGATE_NONE BIT(17)
+#define CX25840_VCONFIG_CLKGATE_VALID BIT(18)
+#define CX25840_VCONFIG_CLKGATE_VALIDACTIVE GENMASK(18, 17)
+
+#define CX25840_VCONFIG_DCMODE_SHIFT 19
+#define CX25840_VCONFIG_DCMODE_MASK GENMASK(20, 19)
+#define CX25840_VCONFIG_DCMODE_DWORDS BIT(19)
+#define CX25840_VCONFIG_DCMODE_BYTES BIT(20)
+
+#define CX25840_VCONFIG_IDID0S_SHIFT 21
+#define CX25840_VCONFIG_IDID0S_MASK GENMASK(22, 21)
+#define CX25840_VCONFIG_IDID0S_NORMAL BIT(21)
+#define CX25840_VCONFIG_IDID0S_LINECNT BIT(22)
+
+#define CX25840_VCONFIG_VIPCLAMP_SHIFT 23
+#define CX25840_VCONFIG_VIPCLAMP_MASK GENMASK(24, 23)
+#define CX25840_VCONFIG_VIPCLAMP_ENABLED BIT(23)
+#define CX25840_VCONFIG_VIPCLAMP_DISABLED BIT(24)
+
enum cx25840_audio_input {
/* Audio inputs: serial or In4-In8 */
CX25840_AUDIO_SERIAL,
@@ -180,9 +244,17 @@ enum cx23885_io_pad {
audio autodetect fails on some channels for these models and the workaround
is to select the audio standard explicitly. Many thanks to Hauppauge for
providing this information.
- This platform data only needs to be supplied by the ivtv driver. */
+ This platform data only needs to be supplied by the ivtv driver.
+
+ generic_mode disables some of the ivtv-related hacks in this driver,
+ enables setting video output config and sets it according to datasheet
+ defaults on initialization.
+ This flag is to be used for example with USB video capture devices
+ using this chip.
+*/
struct cx25840_platform_data {
int pvr150_workaround;
+ int generic_mode;
};
#endif
^ permalink raw reply related
* [PATCH v5 2/6] cx25840: add kernel-doc description of struct cx25840_state
From: Maciej S. Szmigiero @ 2017-12-22 23:18 UTC (permalink / raw)
To: Michael Krufky, Mauro Carvalho Chehab
Cc: Andy Walls, linux-kernel, linux-media, Hans Verkuil,
Philippe Ombredanne
In-Reply-To: <cover.1513982691.git.mail@maciej.szmigiero.name>
This commit describes a device instance private data of the driver
(struct cx25840_state) in a kernel-doc style comment.
Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
---
drivers/media/i2c/cx25840/cx25840-core.h | 33 ++++++++++++++++++++++++++++++--
1 file changed, 31 insertions(+), 2 deletions(-)
diff --git a/drivers/media/i2c/cx25840/cx25840-core.h b/drivers/media/i2c/cx25840/cx25840-core.h
index 55432ed42714..877b930e5b1f 100644
--- a/drivers/media/i2c/cx25840/cx25840-core.h
+++ b/drivers/media/i2c/cx25840/cx25840-core.h
@@ -45,6 +45,35 @@ enum cx25840_media_pads {
CX25840_NUM_PADS
};
+/**
+ * struct cx25840_state - a device instance private data
+ * @c: i2c_client struct representing this device
+ * @sd: our V4L2 sub-device
+ * @hdl: our V4L2 control handler
+ * @volume: audio volume V4L2 control (non-cx2583x devices only)
+ * @mute: audio mute V4L2 control (non-cx2583x devices only)
+ * @pvr150_workaround: whether we enable workaround for Hauppauge PVR150
+ * hardware bug (audio dropping out)
+ * @radio: set if we are currently in the radio mode, otherwise
+ * the current mode is non-radio (that is, video)
+ * @std: currently set video standard
+ * @vid_input: currently set video input
+ * @aud_input: currently set audio input
+ * @audclk_freq: currently set audio sample rate
+ * @audmode: currently set audio mode (when in non-radio mode)
+ * @vbi_line_offset: vbi line number offset
+ * @id: exact device model
+ * @rev: raw device id read from the chip
+ * @is_initialized: whether we have already loaded firmware into the chip
+ * and initialized it
+ * @vbi_regs_offset: offset of vbi regs
+ * @fw_wait: wait queue to wake an initalization function up when
+ * firmware loading (on a separate workqueue) finishes
+ * @fw_work: a work that actually loads the firmware on a separate
+ * workqueue
+ * @ir_state: a pointer to chip IR controller private data
+ * @pads: array of supported chip pads (currently only a stub)
+ */
struct cx25840_state {
struct i2c_client *c;
struct v4l2_subdev sd;
@@ -66,8 +95,8 @@ struct cx25840_state {
u32 rev;
int is_initialized;
unsigned vbi_regs_offset;
- wait_queue_head_t fw_wait; /* wake up when the fw load is finished */
- struct work_struct fw_work; /* work entry for fw load */
+ wait_queue_head_t fw_wait;
+ struct work_struct fw_work;
struct cx25840_ir_state *ir_state;
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_pad pads[CX25840_NUM_PADS];
^ permalink raw reply related
* [PATCH v5 1/6] ivtv: zero-initialize cx25840 platform data
From: Maciej S. Szmigiero @ 2017-12-22 23:18 UTC (permalink / raw)
To: Michael Krufky, Mauro Carvalho Chehab
Cc: Andy Walls, linux-kernel, linux-media, Hans Verkuil,
Philippe Ombredanne
In-Reply-To: <cover.1513982691.git.mail@maciej.szmigiero.name>
We need to zero-initialize cx25840 platform data structure to make sure
that its future members do not contain random stack garbage.
Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
---
drivers/media/pci/ivtv/ivtv-i2c.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/media/pci/ivtv/ivtv-i2c.c b/drivers/media/pci/ivtv/ivtv-i2c.c
index 66696e6ee587..b755337ec938 100644
--- a/drivers/media/pci/ivtv/ivtv-i2c.c
+++ b/drivers/media/pci/ivtv/ivtv-i2c.c
@@ -293,6 +293,7 @@ int ivtv_i2c_register(struct ivtv *itv, unsigned idx)
.platform_data = &pdata,
};
+ memset(&pdata, 0, sizeof(pdata));
pdata.pvr150_workaround = itv->pvr150_workaround;
sd = v4l2_i2c_new_subdev_board(&itv->v4l2_dev, adap,
&cx25840_info, NULL);
^ permalink raw reply related
* [PATCH v5 0/6] [media] Add analog mode support for Medion MD95700
From: Maciej S. Szmigiero @ 2017-12-22 23:18 UTC (permalink / raw)
To: Michael Krufky, Mauro Carvalho Chehab
Cc: Andy Walls, linux-kernel, linux-media, Hans Verkuil,
Philippe Ombredanne
This series adds support for analog part of Medion 95700 in the cxusb
driver.
What works:
* Video capture at various sizes with sequential fields,
* Input switching (TV Tuner, Composite, S-Video),
* TV and radio tuning,
* Video standard switching and auto detection,
* Radio mode switching (stereo / mono),
* Unplugging while capturing,
* DVB / analog coexistence,
* Raw BT.656 stream support.
What does not work yet:
* Audio,
* VBI,
* Picture controls.
This series (as a one patch) was submitted for inclusion few years ago,
then waited few months in a patch queue.
Unfortunately, by the time it was supposed to be merged there
were enough changes in media that it was no longer mergable.
I thought at that time that I will be able to rebase and retest it soon
but unfortunately up till now I was never able to find enough time to do
so.
Also, with the passing of time the implementation diverged more and
more from the current kernel code, necessitating even more reworking.
That last iteration can be found here:
https://patchwork.linuxtv.org/patch/8048/
Since that version there had been the following changes:
* Adaptation to changes in V4L2 / DVB core,
* Radio device was added, with a possibility to tune to a FM radio
station and switch between stereo and mono modes (tested by taping
audio signal directly at tuner output pin),
* DVB / analog coexistence was improved - resolved a few cases where
DVB core would switch off power or reset the tuner when the device
was still being used but in the analog mode,
* Fixed issues reported by v4l2-compliance,
* Switching to raw BT.656 mode is now done by a custom streaming
parameter set via VIDIOC_S_PARM ioctl instead of using a
V4L2_BUF_TYPE_PRIVATE buffer (which was removed from V4L2),
* General small code cleanups (like using BIT() or ARRAY_SIZE() macros
instead of open coding them, code formatting improvements, etc.).
Changes from v1:
* Only support configuration of cx25840 pins that the cxusb driver is
actually using so there is no need for an ugly CX25840_PIN() macro,
* Split cxusb changes into two patches: first one implementing
digital / analog coexistence in this driver, second one adding the
actual implementation of the analog mode,
* Fix a warning reported by kbuild test robot.
Changes from v2:
* Split out ivtv cx25840 platform data zero-initialization to a separate
commit,
* Add kernel-doc description of struct cx25840_state,
* Make sure that all variables used in CX25840_VCONFIG_OPTION() and
CX25840_VCONFIG_SET_BIT() macros are their explicit parameters,
* Split out some code from cxusb_medion_copy_field() and
cxusb_medion_v_complete_work() functions to separate ones to increase
their readability,
* Generate masks using GENMASK() and BIT() macros in cx25840.h and
cxusb.h.
Changes from v3:
Add SPDX tag to a newly added "cxusb-analog.c" file.
Changes from v4:
* Make analog support conditional on a new DVB_USB_CXUSB_ANALOG Kconfig
option,
* Use '//' comments in the header of a newly added "cxusb-analog.c"
file,
* Don't print errors on memory allocation failures,
* Get rid of the driver MODULE_VERSION(),
* Small formating fix of a one line.
Maciej S. Szmigiero (6):
ivtv: zero-initialize cx25840 platform data
cx25840: add kernel-doc description of struct cx25840_state
cx25840: add pin to pad mapping and output format configuration
tuner-simple: allow setting mono radio mode
[media] cxusb: implement Medion MD95700 digital / analog coexistence
[media] cxusb: add analog mode support for Medion MD95700
drivers/media/i2c/cx25840/cx25840-core.c | 396 ++++++-
drivers/media/i2c/cx25840/cx25840-core.h | 46 +-
drivers/media/i2c/cx25840/cx25840-vbi.c | 3 +
drivers/media/pci/ivtv/ivtv-i2c.c | 1 +
drivers/media/tuners/tuner-simple.c | 5 +-
drivers/media/usb/dvb-usb/Kconfig | 16 +-
drivers/media/usb/dvb-usb/Makefile | 3 +
drivers/media/usb/dvb-usb/cxusb-analog.c | 1914 ++++++++++++++++++++++++++++++
drivers/media/usb/dvb-usb/cxusb.c | 454 ++++++-
drivers/media/usb/dvb-usb/cxusb.h | 154 +++
drivers/media/usb/dvb-usb/dvb-usb-dvb.c | 20 +-
drivers/media/usb/dvb-usb/dvb-usb-init.c | 13 +
drivers/media/usb/dvb-usb/dvb-usb.h | 8 +
include/media/drv-intf/cx25840.h | 74 +-
14 files changed, 3043 insertions(+), 64 deletions(-)
create mode 100644 drivers/media/usb/dvb-usb/cxusb-analog.c
^ permalink raw reply
* Re: [PATCH v4 6/6] [media] cxusb: add analog mode support for Medion MD95700
From: Maciej S. Szmigiero @ 2017-12-22 23:17 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Hans Verkuil, Michael Krufky, Andy Walls, linux-kernel,
linux-media, Philippe Ombredanne
In-Reply-To: <20171219105329.3d6b2fb4@vento.lan>
On 19.12.2017 13:53, Mauro Carvalho Chehab wrote:
> Em Sun, 17 Dec 2017 19:47:25 +0100
> "Maciej S. Szmigiero" <mail@maciej.szmigiero.name> escreveu:
>
>> This patch adds support for analog part of Medion 95700 in the cxusb
>> driver.
>>
>> What works:
>> * Video capture at various sizes with sequential fields,
>> * Input switching (TV Tuner, Composite, S-Video),
>> * TV and radio tuning,
>> * Video standard switching and auto detection,
>> * Radio mode switching (stereo / mono),
>> * Unplugging while capturing,
>> * DVB / analog coexistence,
>> * Raw BT.656 stream support.
>>
>> What does not work yet:
>> * Audio,
>> * VBI,
>> * Picture controls.
>
> Patches 1 to 5 look OK to me (although checkpatch do a few complains).
>
> This one, however, require some adjustments.
>
> I'd like to also have Hans eyes on it, as he's doing a lot more V4L2
> new driver reviews than me nowadays.
>
(..)
>> +static int cxusb_medion_try_s_fmt_vid_cap(struct file *file,
>> + struct v4l2_format *f,
>> + bool isset)
>> +{
>> + struct dvb_usb_device *dvbdev = video_drvdata(file);
>> + struct cxusb_medion_dev *cxdev = dvbdev->priv;
>> + struct v4l2_subdev_format subfmt;
>> + int ret;
>> +
>> + if (isset && (cxusb_medion_stream_busy(cxdev) ||
>> + vb2_is_busy(&cxdev->videoqueue)))
>> + return -EBUSY;
>> +
>> + memset(&subfmt, 0, sizeof(subfmt));
>> + subfmt.which = isset ? V4L2_SUBDEV_FORMAT_ACTIVE :
>> + V4L2_SUBDEV_FORMAT_TRY;
>> + subfmt.format.width = f->fmt.pix.width & ~1;
>> + subfmt.format.height = f->fmt.pix.height & ~1;
>> + subfmt.format.code = MEDIA_BUS_FMT_FIXED;
>> + subfmt.format.field = V4L2_FIELD_SEQ_TB;
>> + subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M;
>> +
>> + ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, &subfmt);
>> + if (ret != 0) {
>> + if (ret != -ERANGE)
>> + return ret;
>> +
>> + /* try some common formats */
>> + subfmt.format.width = 720;
>> + subfmt.format.height = 576;
>> + ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL,
>> + &subfmt);
>> + if (ret != 0) {
>> + if (ret != -ERANGE)
>> + return ret;
>> +
>> + subfmt.format.width = 640;
>> + subfmt.format.height = 480;
>> + ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt,
>> + NULL, &subfmt);
>> + if (ret != 0)
>> + return ret;
>> + }
>> + }
>
> That looks weird... Why are you trying two different formats here,
> instead of just using the width/height that userspace passes?
>
V4L2 docs say that VIDIOC_{S,TRY}_FMT ioctls "should not return an error
code unless the type field is invalid", that is, they should not return
an error for invalid or unsupported image widths or heights.
They should instead return something sensible for these image parameters.
However, cx25840 driver set_fmt callback simply returns -ERANGE if it
does not like the provided width or height.
In this case the code above simply tries first the bigger PAL capture
resolution then the smaller NTSC one.
Which one will be accepted by the cx25840 depends on the currently set
broadcast standard and parameters of the last signal that was received,
at least one of these resolutions seem to work even without any
signal being received since the chip was powered up.
This way the API guarantees should be kept by the driver.
(All other your comments were implemented in a respin).
>
> Thanks,
> Mauro
>
Thanks,
Maciej
^ permalink raw reply
* Re: [PATCH] [media] dvb-frontends: remove self assignments
From: Mauro Carvalho Chehab @ 2017-12-22 19:02 UTC (permalink / raw)
To: Nick Desaulniers
Cc: Hans Verkuil, Colin Ian King, Markus Elfring, Sakari Ailus,
linux-media, linux-kernel
In-Reply-To: <20171218171454.139245-1-ndesaulniers@google.com>
Em Mon, 18 Dec 2017 09:14:50 -0800
Nick Desaulniers <ndesaulniers@google.com> escreveu:
> These were leftover from:
> commit 469ffe083665 ("[media] tda18271c2dd: Remove the CHK_ERROR macro")
> and
> commit 58d5eaec9f87 ("[media] drxd: Don't use a macro for CHK_ERROR ...")
> that programmatically removed the CHK_ERROR macro, which left behind a
> few self assignments that Clang warns about. These instances aren't
> errors.
>
> Signed-off-by: Nick Desaulniers <ndesaulniers@google.com>
Thanks for the patch, but a similar one was already merged:
commit 2ddc125de832f4d8e1820dc923cb2029170beea0
Author: Colin Ian King <colin.king@canonical.com>
Date: Thu Nov 23 05:19:19 2017 -0500
media: dvb_frontend: remove redundant status self assignment
The assignment status to itself is redundant and can be removed.
Detected with Coccinelle.
Signed-off-by: Colin Ian King <colin.king@canonical.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
> ---
> drivers/media/dvb-frontends/drxd_hard.c | 3 ---
> drivers/media/dvb-frontends/tda18271c2dd.c | 1 -
> 2 files changed, 4 deletions(-)
>
> diff --git a/drivers/media/dvb-frontends/drxd_hard.c b/drivers/media/dvb-frontends/drxd_hard.c
> index 0696bc62dcc9..ff18a0f7dc41 100644
> --- a/drivers/media/dvb-frontends/drxd_hard.c
> +++ b/drivers/media/dvb-frontends/drxd_hard.c
> @@ -2140,7 +2140,6 @@ static int DRX_Start(struct drxd_state *state, s32 off)
> }
> break;
> }
> - status = status;
> if (status < 0)
> break;
>
> @@ -2251,7 +2250,6 @@ static int DRX_Start(struct drxd_state *state, s32 off)
> break;
>
> }
> - status = status;
> if (status < 0)
> break;
>
> @@ -2318,7 +2316,6 @@ static int DRX_Start(struct drxd_state *state, s32 off)
> }
> break;
> }
> - status = status;
> if (status < 0)
> break;
>
> diff --git a/drivers/media/dvb-frontends/tda18271c2dd.c b/drivers/media/dvb-frontends/tda18271c2dd.c
> index 2d2778be2d2f..45cd5ba0cf8a 100644
> --- a/drivers/media/dvb-frontends/tda18271c2dd.c
> +++ b/drivers/media/dvb-frontends/tda18271c2dd.c
> @@ -674,7 +674,6 @@ static int PowerScan(struct tda_state *state,
> Count = 200000;
> wait = true;
> }
> - status = status;
> if (status < 0)
> break;
> if (CID_Gain >= CID_Target) {
Thanks,
Mauro
^ permalink raw reply
* Re: [PATCH 05/11] media: dvb_vb2: fix a warning about streamoff logic
From: Mauro Carvalho Chehab @ 2017-12-22 15:48 UTC (permalink / raw)
To: Linux Media Mailing List, Jonathan Corbet
Cc: Mauro Carvalho Chehab, Linux Doc Mailing List,
Satendra Singh Thakur, Inki Dae, Seung-Woo Kim, Junghak Sung
In-Reply-To: <1bb5247a5eb355693098ed715170b7523fc20530.1513872637.git.mchehab@s-opensource.com>
Em Thu, 21 Dec 2017 14:18:04 -0200
Mauro Carvalho Chehab <mchehab@s-opensource.com> escreveu:
> The streamoff logic is causing those warnings:
>
> WARNING: CPU: 3 PID: 3382 at drivers/media/v4l2-core/videobuf2-core.c:1652 __vb2_queue_cancel+0x177/0x250 [videobuf2_core]
> Modules linked in: bnep fuse xt_CHECKSUM iptable_mangle tun ebtable_filter ebtables ip6table_filter ip6_tables xt_physdev br_netfilter bluetooth bridge rfkill ecdh_generic stp llc nf_log_ipv4 nf_log_common xt_LOG xt_conntrack ipt_MASQUERADE nf_nat_masquerade_ipv4 iptable_nat nf_conntrack_ipv4 nf_defrag_ipv4 nf_nat_ipv4 nf_nat nf_conntrack libcrc32c sunrpc vfat fat snd_hda_codec_hdmi rc_dib0700_nec i915 rc_pinnacle_pctv_hd em28xx_rc a8293 ts2020 m88ds3103 i2c_mux em28xx_dvb dib8000 dvb_usb_dib0700 dib0070 dib7000m dib0090 dvb_usb dvb_core uvcvideo snd_usb_audio videobuf2_v4l2 dib3000mc videobuf2_vmalloc videobuf2_memops dibx000_common videobuf2_core rc_core snd_usbmidi_lib snd_rawmidi em28xx tveeprom v4l2_common videodev media intel_rapl x86_pkg_temp_thermal intel_powerclamp coretemp snd_hda_intel
> kvm_intel snd_hda_codec kvm snd_hwdep snd_hda_core snd_seq irqbypass crct10dif_pclmul crc32_pclmul i2c_algo_bit ghash_clmulni_intel snd_seq_device drm_kms_helper snd_pcm intel_cstate intel_uncore snd_timer tpm_tis drm mei_wdt iTCO_wdt iTCO_vendor_support tpm_tis_core snd intel_rapl_perf mei_me mei tpm i2c_i801 soundcore lpc_ich video binfmt_misc hid_logitech_hidpp hid_logitech_dj e1000e crc32c_intel ptp pps_core analog gameport joydev
> CPU: 3 PID: 3382 Comm: lt-dvbv5-zap Not tainted 4.14.0+ #3
> Hardware name: /D53427RKE, BIOS RKPPT10H.86A.0048.2017.0506.1545 05/06/2017
> task: ffff94b93bbe1e40 task.stack: ffffb7a98320c000
> RIP: 0010:__vb2_queue_cancel+0x177/0x250 [videobuf2_core]
> RSP: 0018:ffffb7a98320fd40 EFLAGS: 00010202
> RAX: 0000000000000001 RBX: ffff94b92ff72428 RCX: 0000000000000000
> RDX: 0000000000000001 RSI: 0000000000000001 RDI: ffff94b92ff72428
> RBP: ffffb7a98320fd68 R08: ffff94b92ff725d8 R09: ffffb7a98320fcc8
> R10: ffff94b978003d98 R11: ffff94b92ff72428 R12: ffff94b92ff72428
> R13: 0000000000000282 R14: ffff94b92059ae20 R15: dead000000000100
> FS: 0000000000000000(0000) GS:ffff94b99e380000(0000) knlGS:0000000000000000
> CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 0000555953007d70 CR3: 000000012be09004 CR4: 00000000001606e0
> Call Trace:
> vb2_core_streamoff+0x28/0x90 [videobuf2_core]
> dvb_vb2_stream_off+0xd1/0x150 [dvb_core]
> dvb_dvr_release+0x114/0x120 [dvb_core]
> __fput+0xdf/0x1e0
> ____fput+0xe/0x10
> task_work_run+0x94/0xc0
> do_exit+0x2dc/0xba0
> do_group_exit+0x47/0xb0
> SyS_exit_group+0x14/0x20
> entry_SYSCALL_64_fastpath+0x1a/0xa5
> RIP: 0033:0x7f775e931ed8
> RSP: 002b:00007fff07019d68 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7
> RAX: ffffffffffffffda RBX: 0000000001d02690 RCX: 00007f775e931ed8
> RDX: 0000000000000001 RSI: 000000000000003c RDI: 0000000000000001
> RBP: 00007fff0701a500 R08: 00000000000000e7 R09: ffffffffffffff70
> R10: 00007f775e854dd8 R11: 0000000000000246 R12: 0000000000000000
> R13: 00000000035fa000 R14: 000000000000000a R15: 000000000000000a
> Code: 00 00 04 74 1c 44 89 e8 49 83 c5 01 41 39 84 24 88 01 00 00 77 8a 5b 41 5c 41 5d 41 5e 41 5f 5d c3 48 89 df e8 bb fd ff ff eb da <0f> ff 41 8b b4 24 88 01 00 00 85 f6 74 34 bb 01 00 00 00 eb 10
>
> There are actually two issues here:
>
> 1) list_del() should be called when changing the buffer state;
>
> 2) The logic with marks the buffers as done is at the wrong place.
>
> Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
I ended by sending a wrong version. The one I sent causes a list
corruption, as it deletes a queued value without marking the buffer
as done:
[ 627.233534] list_del corruption, ffffa0aa01182e20->next is LIST_POISON1 (dead000000000100)
Regards,
Mauro
[PATCH] media: dvb_vb2: fix a warning about streamoff logic
The streamoff logic is causing those warnings:
WARNING: CPU: 3 PID: 3382 at drivers/media/v4l2-core/videobuf2-core.c:1652 __vb2_queue_cancel+0x177/0x250 [videobuf2_core]
Modules linked in: bnep fuse xt_CHECKSUM iptable_mangle tun ebtable_filter ebtables ip6table_filter ip6_tables xt_physdev br_netfilter bluetooth bridge rfkill ecdh_generic stp llc nf_log_ipv4 nf_log_common xt_LOG xt_conntrack ipt_MASQUERADE nf_nat_masquerade_ipv4 iptable_nat nf_conntrack_ipv4 nf_defrag_ipv4 nf_nat_ipv4 nf_nat nf_conntrack libcrc32c sunrpc vfat fat snd_hda_codec_hdmi rc_dib0700_nec i915 rc_pinnacle_pctv_hd em28xx_rc a8293 ts2020 m88ds3103 i2c_mux em28xx_dvb dib8000 dvb_usb_dib0700 dib0070 dib7000m dib0090 dvb_usb dvb_core uvcvideo snd_usb_audio videobuf2_v4l2 dib3000mc videobuf2_vmalloc videobuf2_memops dibx000_common videobuf2_core rc_core snd_usbmidi_lib snd_rawmidi em28xx tveeprom v4l2_common videodev media intel_rapl x86_pkg_temp_thermal intel_powerclamp coretemp snd_hda_intel
kvm_intel snd_hda_codec kvm snd_hwdep snd_hda_core snd_seq irqbypass crct10dif_pclmul crc32_pclmul i2c_algo_bit ghash_clmulni_intel snd_seq_device drm_kms_helper snd_pcm intel_cstate intel_uncore snd_timer tpm_tis drm mei_wdt iTCO_wdt iTCO_vendor_support tpm_tis_core snd intel_rapl_perf mei_me mei tpm i2c_i801 soundcore lpc_ich video binfmt_misc hid_logitech_hidpp hid_logitech_dj e1000e crc32c_intel ptp pps_core analog gameport joydev
CPU: 3 PID: 3382 Comm: lt-dvbv5-zap Not tainted 4.14.0+ #3
Hardware name: /D53427RKE, BIOS RKPPT10H.86A.0048.2017.0506.1545 05/06/2017
task: ffff94b93bbe1e40 task.stack: ffffb7a98320c000
RIP: 0010:__vb2_queue_cancel+0x177/0x250 [videobuf2_core]
RSP: 0018:ffffb7a98320fd40 EFLAGS: 00010202
RAX: 0000000000000001 RBX: ffff94b92ff72428 RCX: 0000000000000000
RDX: 0000000000000001 RSI: 0000000000000001 RDI: ffff94b92ff72428
RBP: ffffb7a98320fd68 R08: ffff94b92ff725d8 R09: ffffb7a98320fcc8
R10: ffff94b978003d98 R11: ffff94b92ff72428 R12: ffff94b92ff72428
R13: 0000000000000282 R14: ffff94b92059ae20 R15: dead000000000100
FS: 0000000000000000(0000) GS:ffff94b99e380000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000555953007d70 CR3: 000000012be09004 CR4: 00000000001606e0
Call Trace:
vb2_core_streamoff+0x28/0x90 [videobuf2_core]
dvb_vb2_stream_off+0xd1/0x150 [dvb_core]
dvb_dvr_release+0x114/0x120 [dvb_core]
__fput+0xdf/0x1e0
____fput+0xe/0x10
task_work_run+0x94/0xc0
do_exit+0x2dc/0xba0
do_group_exit+0x47/0xb0
SyS_exit_group+0x14/0x20
entry_SYSCALL_64_fastpath+0x1a/0xa5
RIP: 0033:0x7f775e931ed8
RSP: 002b:00007fff07019d68 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7
RAX: ffffffffffffffda RBX: 0000000001d02690 RCX: 00007f775e931ed8
RDX: 0000000000000001 RSI: 000000000000003c RDI: 0000000000000001
RBP: 00007fff0701a500 R08: 00000000000000e7 R09: ffffffffffffff70
R10: 00007f775e854dd8 R11: 0000000000000246 R12: 0000000000000000
R13: 00000000035fa000 R14: 000000000000000a R15: 000000000000000a
Code: 00 00 04 74 1c 44 89 e8 49 83 c5 01 41 39 84 24 88 01 00 00 77 8a 5b 41 5c 41 5d 41 5e 41 5f 5d c3 48 89 df e8 bb fd ff ff eb da <0f> ff 41 8b b4 24 88 01 00 00 85 f6 74 34 bb 01 00 00 00 eb 10
There are actually two issues here:
1) list_del() should be called when changing the buffer state;
2) The logic with marks the buffers as done is at the wrong place.
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
diff --git a/drivers/media/dvb-core/dvb_vb2.c b/drivers/media/dvb-core/dvb_vb2.c
index 01424e67b42e..0588c5520419 100644
--- a/drivers/media/dvb-core/dvb_vb2.c
+++ b/drivers/media/dvb-core/dvb_vb2.c
@@ -90,8 +90,19 @@ static int _start_streaming(struct vb2_queue *vq, unsigned int count)
static void _stop_streaming(struct vb2_queue *vq)
{
struct dvb_vb2_ctx *ctx = vb2_get_drv_priv(vq);
+ struct dvb_buffer *buf;
+ unsigned long flags = 0;
dprintk(3, "[%s]\n", ctx->name);
+
+ spin_lock_irqsave(&ctx->slock, flags);
+ while (!list_empty(&ctx->dvb_q)) {
+ buf = list_entry(ctx->dvb_q.next,
+ struct dvb_buffer, list);
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
+ list_del(&buf->list);
+ }
+ spin_unlock_irqrestore(&ctx->slock, flags);
}
static void _dmxdev_lock(struct vb2_queue *vq)
@@ -225,21 +236,8 @@ int dvb_vb2_stream_off(struct dvb_vb2_ctx *ctx)
{
struct vb2_queue *q = (struct vb2_queue *)&ctx->vb_q;
int ret;
- unsigned long flags = 0;
ctx->state &= ~DVB_VB2_STATE_STREAMON;
- spin_lock_irqsave(&ctx->slock, flags);
- while (!list_empty(&ctx->dvb_q)) {
- struct dvb_buffer *buf;
-
- buf = list_entry(ctx->dvb_q.next,
- struct dvb_buffer, list);
- list_del(&buf->list);
- spin_unlock_irqrestore(&ctx->slock, flags);
- vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
- spin_lock_irqsave(&ctx->slock, flags);
- }
- spin_unlock_irqrestore(&ctx->slock, flags);
ret = vb2_core_streamoff(q, q->type);
if (ret) {
ctx->state = DVB_VB2_STATE_NONE;
@@ -273,11 +271,10 @@ int dvb_vb2_fill_buffer(struct dvb_vb2_ctx *ctx,
*/
return 0;
}
+ spin_lock_irqsave(&ctx->slock, flags);
while (todo) {
if (!ctx->buf) {
- spin_lock_irqsave(&ctx->slock, flags);
if (list_empty(&ctx->dvb_q)) {
- spin_unlock_irqrestore(&ctx->slock, flags);
dprintk(3, "[%s] Buffer overflow!!!\n",
ctx->name);
break;
@@ -285,14 +282,13 @@ int dvb_vb2_fill_buffer(struct dvb_vb2_ctx *ctx,
ctx->buf = list_entry(ctx->dvb_q.next,
struct dvb_buffer, list);
- list_del(&ctx->buf->list);
- spin_unlock_irqrestore(&ctx->slock, flags);
ctx->remain = vb2_plane_size(&ctx->buf->vb, 0);
ctx->offset = 0;
}
if (!dvb_vb2_is_streaming(ctx)) {
vb2_buffer_done(&ctx->buf->vb, VB2_BUF_STATE_ERROR);
+ list_del(&ctx->buf->list);
ctx->buf = NULL;
break;
}
@@ -309,6 +305,7 @@ int dvb_vb2_fill_buffer(struct dvb_vb2_ctx *ctx,
if (ctx->remain == 0) {
vb2_buffer_done(&ctx->buf->vb, VB2_BUF_STATE_DONE);
+ list_del(&ctx->buf->list);
ctx->buf = NULL;
}
}
@@ -316,8 +313,10 @@ int dvb_vb2_fill_buffer(struct dvb_vb2_ctx *ctx,
if (ctx->nonblocking && ctx->buf) {
vb2_set_plane_payload(&ctx->buf->vb, 0, ll);
vb2_buffer_done(&ctx->buf->vb, VB2_BUF_STATE_DONE);
+ list_del(&ctx->buf->list);
ctx->buf = NULL;
}
+ spin_unlock_irqrestore(&ctx->slock, flags);
if (todo)
dprintk(1, "[%s] %d bytes are dropped.\n", ctx->name, todo);
Thanks,
Mauro
^ permalink raw reply related
* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
From: jacopo mondi @ 2017-12-22 14:40 UTC (permalink / raw)
To: Laurent Pinchart
Cc: Jacopo Mondi, magnus.damm, geert, mchehab, hverkuil,
linux-renesas-soc, linux-media, linux-sh, linux-kernel
In-Reply-To: <12311953.yR0pctoD7P@avalon>
Hi Laurent,
On Fri, Dec 22, 2017 at 02:03:41PM +0200, Laurent Pinchart wrote:
> Hi Jacopo,
>
> On Thursday, 21 December 2017 18:27:02 EET jacopo mondi wrote:
> > On Mon, Dec 18, 2017 at 05:28:43PM +0200, Laurent Pinchart wrote:
> > > On Monday, 18 December 2017 14:25:12 EET jacopo mondi wrote:
> > >> On Mon, Dec 11, 2017 at 06:15:23PM +0200, Laurent Pinchart wrote:
> > >
> > [snip]
> >
> > >>>> +/**
> > >>>> + * ceu_soft_reset() - Software reset the CEU interface
> > >>>> + */
> > >>>> +static int ceu_soft_reset(struct ceu_device *ceudev)
> > >>>> +{
> > >>>> + unsigned int reset_done;
> > >>>> + unsigned int i;
> > >>>> +
> > >>>> + ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL);
> > >>>> +
> > >>>> + reset_done = 0;
> > >>>> + for (i = 0; i < 1000 && !reset_done; i++) {
> > >>>> + udelay(1);
> > >>>> + if (!(ceu_read(ceudev, CEU_CSTSR) & CEU_CSTRST_CPTON))
> > >>>> + reset_done++;
> > >>>> + }
> > >>>
> > >>> How many iterations does this typically require ? Wouldn't a sleep be
> > >>> better than a delay ? As far as I can tell the ceu_soft_reset()
> > >>> function is only called with interrupts disabled (in interrupt context)
> > >>> from ceu_capture() in an error path, and that code should be reworked
> > >>> to make it possible to sleep if a reset takes too long.
> > >>
> > >> The HW manual does not provide any indication about absolute timings.
> > >> I can empirically try and see, but that would just be a hint.
> > >
> > > That's why I asked how many iterations it typically takes :-) A hint is
> > > enough to start with, preferably on both SH and ARM SoCs.
> >
> > I've seen only 0s when printing out how many cycles it takes to clear
> > both registers. This means 1usec is enough, therefore I would keep using
> > udelay here. Also, I would reduce the attempts to 100 here (or even
> > 10), as if a single one is typically enough, 1000 is definitely an
> > overkill.
>
> I'd go for 10. This being said, please make sure you run tests where the reset
> is performed during capture in the middle of a frame, to see if it changes the
> number of iterations.
>
The only way I can think to do this is to stream_on then immediately
stream_off before we get the frame and thus casue the interface reset.
Any other idea?
[snip]
> > >>>> +
> > >>>> +/**
> > >>>> + * ceu_capture() - Trigger start of a capture sequence
> > >>>> + *
> > >>>> + * Return value doesn't reflect the success/failure to queue the new
> > >>>> buffer,
> > >>>> + * but rather the status of the previous capture.
> > >>>> + */
> > >>>> +static int ceu_capture(struct ceu_device *ceudev)
> > >>>> +{
> > >>>> + struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> > >>>> + dma_addr_t phys_addr_top;
> > >>>> + u32 status;
> > >>>> +
> > >>>> + /* Clean interrupt status and re-enable interrupts */
> > >>>> + status = ceu_read(ceudev, CEU_CETCR);
> > >>>> + ceu_write(ceudev, CEU_CEIER,
> > >>>> + ceu_read(ceudev, CEU_CEIER) & ~CEU_CEIER_MASK);
> > >>>> + ceu_write(ceudev, CEU_CETCR, ~status & CEU_CETCR_MAGIC);
> > >>>> + ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK);
> > >>>
> > >>> I wonder why there's a need to disable and reenable interrupts here.
> > >>
> > >> The original driver clearly said "The hardware is -very- picky about
> > >> this sequence" and I got scared and nerver touched this.
> > >
> > > How about experimenting to see how the hardware reacts ?
> >
> > Turns out this was not needed at all, both on RZ and SH4. I captured
> > several images without any issues in both platforms just clearing the
> > interrupt state without disabling interrutps.
>
> I wonder whether it could cause an issue when interrupts are raised by the
> hardware at the same time they are cleared by the driver. That's hard to test
> though.
>
> What happens when an interrupt source is masked by the CEIER register, is it
> still reported by the status CETCR register (obviously without raising an
> interrupt to the CPU), or does it not get flagged at all ?
They get flagged, yes, and right now I'm clearing all of them at the
beginning of IRQ handler writing ~CEU_CETR_ALL_INT to CETCR.
>
> > >> Also, I very much dislike the CEU_CETRC_MAGIC mask, but again the old
> > >> driver said "Acknoledge magical interrupt sources" and I was afraid to
> > >> change it (I can rename it though, to something lioke CEU_CETCR_ALL_INT
> > >> because that's what it is, a mask with all available interrupt source
> > >> enabled).
> > >
> > > I think renaming it is a good idea. Additionally, regardless of whether
> > > there is any hidden interrupt source, the datasheet mentions for all
> > > reserved bits that "The write value should always be 0". They should
> > > read as 0, but masking them would be an additional safeguard.
> >
> > The HW manual is a bit confused (and confusing) on this point.
> > Yes, there is the statement you have cited here, but there's also
> > "to clear only the CPE bit to 0, write H'FFFF FFFE to CETCR" a few
> > lines above, which clearly contradicts the "write 0 to reserved bits"
> > thing.
> >
> > In practice, I'm now writing to 0 only bits to be cleared, and thus
> > writing 1s to everything else, reserved included. I haven't seen any
> > issue both on RZ and SH4 platforms.
And this is the above "wirting ~CEU_CETR_ALL_INT" to CETCR" I
mentioned above.
> >
> > > Also not that on the RZ/A1 platform bit 22 is documented as reserved, so
> > > you might want to compute the mask based on the CEU model.
> >
> > While I can use the .data pointer of 'of_device_id' for OF based
> > devices (RZ) to store the opportune IRQ mask, I'm not sure how to
> > do that for platform devices. Can I assume (platform data == SH) in
> > you opinion?
>
> Yes you can.
Awesome!
Thanks
j
^ permalink raw reply
* Re: [linux-sunxi] [PATCH v4 0/2] Initial Allwinner V3s CSI Support
From: Ondřej Jirman @ 2017-12-22 13:46 UTC (permalink / raw)
To: yong.deng, "Maxime Ripard
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
David S. Miller, Greg Kroah-Hartman, Randy Dunlap, Hans Verkuil,
Stanimir Varbanov, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Arnd Bergmann, Benjamin Gaignard, Ramesh Shanmugasundaram,
Sakari Ailus, Rick Chang, linux-media, devicetree,
linux-arm-kernel, linux-kernel, linux-sunxi
In-Reply-To: <1513935138-35223-1-git-send-email-yong.deng@magewell.com>
[-- Attachment #1: Type: text/plain, Size: 9601 bytes --]
Hello,
Yong Deng píše v Pá 22. 12. 2017 v 17:32 +0800:
> This patchset add initial support for Allwinner V3s CSI.
>
> Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> and CSI1 is used for parallel interface. This is not documented in
> datasheet but by testing and guess.
>
> This patchset implement a v4l2 framework driver and add a binding
> documentation for it.
>
> Currently, the driver only support the parallel interface. And has been
> tested with a BT1120 signal which generating from FPGA. The following
> fetures are not support with this patchset:
> - ISP
> - MIPI-CSI2
> - Master clock for camera sensor
> - Power regulator for the front end IC
>
> Thanks for Ondřej Jirman's help.
>
> Changes in v4:
> * Deal with the CSI 'INNER QUEUE'.
> CSI will lookup the next dma buffer for next frame before the
> the current frame done IRQ triggered. This is not documented
> but reported by Ondřej Jirman.
> The BSP code has workaround for this too. It skip to mark the
> first buffer as frame done for VB2 and pass the second buffer
> to CSI in the first frame done ISR call. Then in second frame
> done ISR call, it mark the first buffer as frame done for VB2
> and pass the third buffer to CSI. And so on. The bad thing is
> that the first buffer will be written twice and the first frame
> is dropped even the queued buffer is sufficient.
> So, I make some improvement here. Pass the next buffer to CSI
> just follow starting the CSI. In this case, the first frame
> will be stored in first buffer, second frame in second buffer.
> This mothed is used to avoid dropping the first frame, it
> would also drop frame when lacking of queued buffer.
> * Fix: using a wrong mbus_code when getting the supported formats
> * Change all fourcc to pixformat
> * Change some function names
>
> Changes in v3:
> * Get rid of struct sun6i_csi_ops
> * Move sun6i-csi to new directory drivers/media/platform/sunxi
> * Merge sun6i_csi.c and sun6i_csi_v3s.c into sun6i_csi.c
> * Use generic fwnode endpoints parser
> * Only support a single subdev to make things simple
> * Many complaintion fix
>
> Changes in v2:
> * Change sunxi-csi to sun6i-csi
> * Rebase to media_tree master branch
>
> Following is the 'v4l2-compliance -s -f' output, I have test this
> with both interlaced and progressive signal:
>
> # ./v4l2-compliance -s -f
> v4l2-compliance SHA : 6049ea8bd64f9d78ef87ef0c2b3dc9b5de1ca4a1
>
> Driver Info:
> Driver name : sun6i-video
> Card type : sun6i-csi
> Bus info : platform:csi
> Driver version: 4.15.0
> Capabilities : 0x84200001
> Video Capture
> Streaming
> Extended Pix Format
> Device Capabilities
> Device Caps : 0x04200001
> Video Capture
> Streaming
> Extended Pix Format
>
> Compliance test for device /dev/video0 (not using libv4l2):
>
> Required ioctls:
> test VIDIOC_QUERYCAP: OK
>
> Allow for multiple opens:
> test second video open: OK
> test VIDIOC_QUERYCAP: OK
> test VIDIOC_G/S_PRIORITY: OK
> test for unlimited opens: OK
>
> Debug ioctls:
> test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
> test VIDIOC_LOG_STATUS: OK (Not Supported)
>
> Input ioctls:
> test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
> test VIDIOC_ENUMAUDIO: OK (Not Supported)
> test VIDIOC_G/S/ENUMINPUT: OK
> test VIDIOC_G/S_AUDIO: OK (Not Supported)
> Inputs: 1 Audio Inputs: 0 Tuners: 0
>
> Output ioctls:
> test VIDIOC_G/S_MODULATOR: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_ENUMAUDOUT: OK (Not Supported)
> test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
> test VIDIOC_G/S_AUDOUT: OK (Not Supported)
> Outputs: 0 Audio Outputs: 0 Modulators: 0
>
> Input/Output configuration ioctls:
> test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
> test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
> test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
> test VIDIOC_G/S_EDID: OK (Not Supported)
>
> Test input 0:
>
> Control ioctls:
> test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
> test VIDIOC_QUERYCTRL: OK (Not Supported)
> test VIDIOC_G/S_CTRL: OK (Not Supported)
> test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
> test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
> test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
> Standard Controls: 0 Private Controls: 0
I'm not sure if your driver passes control queries to the subdev. It
did not originally, and I'm not sure you picked up the change from my
version of the driver. "Not supported" here seems to indicate that it
does not.
I'd be interested what's the recommended practice here. It sure helps
with some apps that expect to be able to modify various input controls
directly on the /dev/video# device. These are then supported out of the
box.
It's a one-line change. See:
https://www.kernel.org/doc/html/latest/media/kapi/v4l2-controls.html#in
heriting-controls
> Format ioctls:
> test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
> test VIDIOC_G/S_PARM: OK (Not Supported)
> test VIDIOC_G_FBUF: OK (Not Supported)
> test VIDIOC_G_FMT: OK
> test VIDIOC_TRY_FMT: OK
> test VIDIOC_S_FMT: OK
> test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
> test Cropping: OK (Not Supported)
> test Composing: OK (Not Supported)
> test Scaling: OK (Not Supported)
>
> Codec ioctls:
> test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
> test VIDIOC_G_ENC_INDEX: OK (Not Supported)
> test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
>
> Buffer ioctls:
> test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
> test VIDIOC_EXPBUF: OK
>
> Test input 0:
>
> Streaming ioctls:
> test read/write: OK (Not Supported)
> test MMAP: OK
> test USERPTR: OK (Not Supported)
> test DMABUF: Cannot test, specify --expbuf-device
>
> Stream using all formats:
> test MMAP for Format HM12, Frame Size 1280x720:
> Stride 1920, Field None: OK
> test MMAP for Format NV12, Frame Size 1280x720:
> Stride 1920, Field None: OK
> test MMAP for Format NV21, Frame Size 1280x720:
> Stride 1920, Field None: OK
> test MMAP for Format YU12, Frame Size 1280x720:
> Stride 1920, Field None: OK
> test MMAP for Format YV12, Frame Size 1280x720:
> Stride 1920, Field None: OK
> test MMAP for Format NV16, Frame Size 1280x720:
> Stride 2560, Field None: OK
> test MMAP for Format NV61, Frame Size 1280x720:
> Stride 2560, Field None: OK
> test MMAP for Format 422P, Frame Size 1280x720:
> Stride 2560, Field None: OK
>
> Total: 54, Succeeded: 54, Failed: 0, Warnings: 0
>
> Yong Deng (2):
> dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI)
> media: V3s: Add support for Allwinner CSI.
>
> .../devicetree/bindings/media/sun6i-csi.txt | 51 ++
> MAINTAINERS | 8 +
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 2 +
> drivers/media/platform/sunxi/sun6i-csi/Kconfig | 9 +
> drivers/media/platform/sunxi/sun6i-csi/Makefile | 3 +
> drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c | 878 +++++++++++++++++++++
> drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h | 147 ++++
> .../media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h | 203 +++++
> .../media/platform/sunxi/sun6i-csi/sun6i_video.c | 752 ++++++++++++++++++
> .../media/platform/sunxi/sun6i-csi/sun6i_video.h | 60 ++
> 11 files changed, 2114 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/sun6i-csi.txt
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/Kconfig
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/Makefile
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h
>
> --
> 1.8.3.1
>
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [linux-sunxi] [PATCH v4 2/2] media: V3s: Add support for Allwinner CSI.
From: Philippe Ombredanne @ 2017-12-22 13:40 UTC (permalink / raw)
To: Yong Deng
Cc: Maxime Ripard, Mauro Carvalho Chehab, Rob Herring, Mark Rutland,
Chen-Yu Tsai, David S. Miller, Greg Kroah-Hartman, Randy Dunlap,
Hans Verkuil, Stanimir Varbanov, Hugues Fruchet, Yannick Fertre,
Philipp Zabel, Arnd Bergmann, Benjamin Gaignard,
Ramesh Shanmugasundaram, Sakari Ailus, Rick Chang,
Linux Media Mailing List,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE, LKML,
linux-sunxi, Priit Laes
In-Reply-To: <20171222102156.cfemen6ouxxxbrem@plaes.org>
Yong,
On Fri, Dec 22, 2017 at 11:21 AM, Priit Laes <plaes@plaes.org> wrote:
> On Fri, Dec 22, 2017 at 05:47:00PM +0800, Yong Deng wrote:
>> Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
>> and CSI1 is used for parallel interface. This is not documented in
>> datasheet but by testing and guess.
>>
>> This patch implement a v4l2 framework driver for it.
>>
>> Currently, the driver only support the parallel interface. MIPI-CSI2,
>> ISP's support are not included in this patch.
>>
>> Signed-off-by: Yong Deng <yong.deng@magewell.com>
<snip>
>> --- /dev/null
>> +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
>> @@ -0,0 +1,878 @@
>> +/*
>> + * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
>> + * All rights reserved.
>> + * Author: Yong Deng <yong.deng@magewell.com>
>> + *
>> + * 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.
>> + */
Would you mind using the new SPDX tags documented in Thomas patch set
[1] rather than this fine but longer legalese?
>> +MODULE_LICENSE("GPL v2");
Per module.h this means GPL2 only. This is not matching your top
license above which is GPL2 or later.
Please make sure your MODULE_LICENSE is consistent with the top level license.
[1] https://lkml.org/lkml/2017/12/4/934
--
Cordially
Philippe Ombredanne
^ permalink raw reply
* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
From: Laurent Pinchart @ 2017-12-22 12:03 UTC (permalink / raw)
To: jacopo mondi
Cc: Jacopo Mondi, magnus.damm, geert, mchehab, hverkuil,
linux-renesas-soc, linux-media, linux-sh, linux-kernel
In-Reply-To: <20171221162702.GE27115@w540>
Hi Jacopo,
On Thursday, 21 December 2017 18:27:02 EET jacopo mondi wrote:
> On Mon, Dec 18, 2017 at 05:28:43PM +0200, Laurent Pinchart wrote:
> > On Monday, 18 December 2017 14:25:12 EET jacopo mondi wrote:
> >> On Mon, Dec 11, 2017 at 06:15:23PM +0200, Laurent Pinchart wrote:
> >
> [snip]
>
> >>>> +/**
> >>>> + * ceu_soft_reset() - Software reset the CEU interface
> >>>> + */
> >>>> +static int ceu_soft_reset(struct ceu_device *ceudev)
> >>>> +{
> >>>> + unsigned int reset_done;
> >>>> + unsigned int i;
> >>>> +
> >>>> + ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL);
> >>>> +
> >>>> + reset_done = 0;
> >>>> + for (i = 0; i < 1000 && !reset_done; i++) {
> >>>> + udelay(1);
> >>>> + if (!(ceu_read(ceudev, CEU_CSTSR) & CEU_CSTRST_CPTON))
> >>>> + reset_done++;
> >>>> + }
> >>>
> >>> How many iterations does this typically require ? Wouldn't a sleep be
> >>> better than a delay ? As far as I can tell the ceu_soft_reset()
> >>> function is only called with interrupts disabled (in interrupt context)
> >>> from ceu_capture() in an error path, and that code should be reworked
> >>> to make it possible to sleep if a reset takes too long.
> >>
> >> The HW manual does not provide any indication about absolute timings.
> >> I can empirically try and see, but that would just be a hint.
> >
> > That's why I asked how many iterations it typically takes :-) A hint is
> > enough to start with, preferably on both SH and ARM SoCs.
>
> I've seen only 0s when printing out how many cycles it takes to clear
> both registers. This means 1usec is enough, therefore I would keep using
> udelay here. Also, I would reduce the attempts to 100 here (or even
> 10), as if a single one is typically enough, 1000 is definitely an
> overkill.
I'd go for 10. This being said, please make sure you run tests where the reset
is performed during capture in the middle of a frame, to see if it changes the
number of iterations.
> >> Also, the reset function is called in many places (runtime_pm
> >> suspend/resume) s_stream(0) and in error path of ceu_capture().
> >>
> >> In ceu_capture() we reset the interface if the previous frame was bad,
> >> and we do that before re-enabling the capture interrupt (so interrupts
> >> are not -disabled-, just the one we care about is not enabled yet..)
> >
> > The ceu_capture() function is called from the driver's interrupt handler,
> > so interrupts are disabled in that code path.
>
> I have removed that reset call from capture and re-worked the irq
> handler to manage state before calling capture().
>
> >> But that's not big deal, as if we fail there, we are about to abort
> >> capture anyhow and so if we miss some spurious capture interrupt it's
> >> ok...
> >>
> >> >> + if (!reset_done) {
> >> >> + v4l2_err(&ceudev->v4l2_dev, "soft reset time out\n");
> >> >
> >> > How about dev_err() instead ?
> >>
> >> Is dev_err/dev_dbg preferred over v4l2_err/v4l2_dbg? Is this because
> >> of dynamic debug?
> >
> > Yes, and the fact that the V4L2 macros don't provide us anymore with much
> > compared to the dev_* macros.
> >
> >>>> +
> >>>> +/**
> >>>> + * ceu_capture() - Trigger start of a capture sequence
> >>>> + *
> >>>> + * Return value doesn't reflect the success/failure to queue the new
> >>>> buffer,
> >>>> + * but rather the status of the previous capture.
> >>>> + */
> >>>> +static int ceu_capture(struct ceu_device *ceudev)
> >>>> +{
> >>>> + struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> >>>> + dma_addr_t phys_addr_top;
> >>>> + u32 status;
> >>>> +
> >>>> + /* Clean interrupt status and re-enable interrupts */
> >>>> + status = ceu_read(ceudev, CEU_CETCR);
> >>>> + ceu_write(ceudev, CEU_CEIER,
> >>>> + ceu_read(ceudev, CEU_CEIER) & ~CEU_CEIER_MASK);
> >>>> + ceu_write(ceudev, CEU_CETCR, ~status & CEU_CETCR_MAGIC);
> >>>> + ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK);
> >>>
> >>> I wonder why there's a need to disable and reenable interrupts here.
> >>
> >> The original driver clearly said "The hardware is -very- picky about
> >> this sequence" and I got scared and nerver touched this.
> >
> > How about experimenting to see how the hardware reacts ?
>
> Turns out this was not needed at all, both on RZ and SH4. I captured
> several images without any issues in both platforms just clearing the
> interrupt state without disabling interrutps.
I wonder whether it could cause an issue when interrupts are raised by the
hardware at the same time they are cleared by the driver. That's hard to test
though.
What happens when an interrupt source is masked by the CEIER register, is it
still reported by the status CETCR register (obviously without raising an
interrupt to the CPU), or does it not get flagged at all ?
> >> Also, I very much dislike the CEU_CETRC_MAGIC mask, but again the old
> >> driver said "Acknoledge magical interrupt sources" and I was afraid to
> >> change it (I can rename it though, to something lioke CEU_CETCR_ALL_INT
> >> because that's what it is, a mask with all available interrupt source
> >> enabled).
> >
> > I think renaming it is a good idea. Additionally, regardless of whether
> > there is any hidden interrupt source, the datasheet mentions for all
> > reserved bits that "The write value should always be 0". They should
> > read as 0, but masking them would be an additional safeguard.
>
> The HW manual is a bit confused (and confusing) on this point.
> Yes, there is the statement you have cited here, but there's also
> "to clear only the CPE bit to 0, write H'FFFF FFFE to CETCR" a few
> lines above, which clearly contradicts the "write 0 to reserved bits"
> thing.
>
> In practice, I'm now writing to 0 only bits to be cleared, and thus
> writing 1s to everything else, reserved included. I haven't seen any
> issue both on RZ and SH4 platforms.
>
> > Also not that on the RZ/A1 platform bit 22 is documented as reserved, so
> > you might want to compute the mask based on the CEU model.
>
> While I can use the .data pointer of 'of_device_id' for OF based
> devices (RZ) to store the opportune IRQ mask, I'm not sure how to
> do that for platform devices. Can I assume (platform data == SH) in
> you opinion?
Yes you can.
> > If you have time you could add a debug print when an undocumented
> > interrupt is flagged and see if that happens for real.
>
> It's not "undocumented interrupt sources" but "magical interrupt
> sources". It's very different :-D
:-)
> By the way, no, never seen any!
--
Regards,
Laurent Pinchart
^ permalink raw reply
* Re: [linux-sunxi] [PATCH v4 1/2] dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI)
From: Yong @ 2017-12-22 10:58 UTC (permalink / raw)
To: plaes
Cc: Maxime Ripard, Mauro Carvalho Chehab, Rob Herring, Mark Rutland,
Chen-Yu Tsai, David S. Miller, Greg Kroah-Hartman, Randy Dunlap,
Hans Verkuil, Stanimir Varbanov, Hugues Fruchet, Yannick Fertre,
Philipp Zabel, Arnd Bergmann, Benjamin Gaignard,
Ramesh Shanmugasundaram, Sakari Ailus, Rick Chang, linux-media,
devicetree, linux-arm-kernel, linux-kernel, linux-sunxi
In-Reply-To: <20171222100008.nmmzwhtmputizn7d@plaes.org>
On Fri, 22 Dec 2017 10:00:08 +0000
Priit Laes <plaes@plaes.org> wrote:
> On Fri, Dec 22, 2017 at 05:41:29PM +0800, Yong Deng wrote:
> > Add binding documentation for Allwinner V3s CSI.
> >
> > Signed-off-by: Yong Deng <yong.deng@magewell.com>
> > ---
> > .../devicetree/bindings/media/sun6i-csi.txt | 51 ++++++++++++++++++++++
> > 1 file changed, 51 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/media/sun6i-csi.txt
> >
> > diff --git a/Documentation/devicetree/bindings/media/sun6i-csi.txt b/Documentation/devicetree/bindings/media/sun6i-csi.txt
> > new file mode 100644
> > index 0000000..b5bfe3f
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/sun6i-csi.txt
> > @@ -0,0 +1,51 @@
> > +Allwinner V3s Camera Sensor Interface
> > +------------------------------
>
> Not sure whether syntax for these files is proper reStructuredText/Markdown,
> but the underline-ish style expects the title and underline having same length.
OK.
>
> > +
> > +Required properties:
> > + - compatible: value must be "allwinner,sun8i-v3s-csi"
> > + - reg: base address and size of the memory-mapped region.
> > + - interrupts: interrupt associated to this IP
> > + - clocks: phandles to the clocks feeding the CSI
> > + * bus: the CSI interface clock
> > + * mod: the CSI module clock
> > + * ram: the CSI DRAM clock
> > + - clock-names: the clock names mentioned above
> > + - resets: phandles to the reset line driving the CSI
> > +
> > +- ports: A ports node with endpoint definitions as defined in
> > + Documentation/devicetree/bindings/media/video-interfaces.txt.
> > + Currently, the driver only support the parallel interface. So, a single port
> ^^ supports
> > + node with one endpoint and parallel bus is supported.
> > +
> > +Example:
> > +
> > + csi1: csi@1cb4000 {
> > + compatible = "allwinner,sun8i-v3s-csi";
> > + reg = <0x01cb4000 0x1000>;
> > + interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> > + clocks = <&ccu CLK_BUS_CSI>,
> > + <&ccu CLK_CSI1_SCLK>,
> > + <&ccu CLK_DRAM_CSI>;
> > + clock-names = "bus", "mod", "ram";
> > + resets = <&ccu RST_BUS_CSI>;
> > +
> > + port {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + /* Parallel bus endpoint */
> > + csi1_ep: endpoint {
> > + remote-endpoint = <&adv7611_ep>;
> > + bus-width = <16>;
> > + data-shift = <0>;
> > +
> > + /* If hsync-active/vsync-active are missing,
> > + embedded BT.656 sync is used */
> > + hsync-active = <0>; /* Active low */
> > + vsync-active = <0>; /* Active low */
> > + data-active = <1>; /* Active high */
> > + pclk-sample = <1>; /* Rising */
> > + };
> > + };
> > + };
> > +
> > --
> > 1.8.3.1
> >
> > --
> > You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
> > To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe@googlegroups.com.
> > For more options, visit https://groups.google.com/d/optout.
>
> --
> You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
Thanks,
Yong
^ permalink raw reply
* Re: [linux-sunxi] [PATCH v4 2/2] media: V3s: Add support for Allwinner CSI.
From: Yong @ 2017-12-22 10:55 UTC (permalink / raw)
To: Priit Laes
Cc: Maxime Ripard, Mauro Carvalho Chehab, Rob Herring, Mark Rutland,
Chen-Yu Tsai, David S. Miller, Greg Kroah-Hartman, Randy Dunlap,
Hans Verkuil, Stanimir Varbanov, Hugues Fruchet, Yannick Fertre,
Philipp Zabel, Arnd Bergmann, Benjamin Gaignard,
Ramesh Shanmugasundaram, Sakari Ailus, Rick Chang, linux-media,
devicetree, linux-arm-kernel, linux-kernel, linux-sunxi
In-Reply-To: <20171222102156.cfemen6ouxxxbrem@plaes.org>
Hi,
On Fri, 22 Dec 2017 10:21:56 +0000
Priit Laes <plaes@plaes.org> wrote:
> On Fri, Dec 22, 2017 at 05:47:00PM +0800, Yong Deng wrote:
> > Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> > and CSI1 is used for parallel interface. This is not documented in
> > datasheet but by testing and guess.
> >
> > This patch implement a v4l2 framework driver for it.
...
> > + if ((sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_PARALLEL
> > + || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_BT656)
> > + && sdev->csi.v4l2_ep.bus.parallel.bus_width == 16) {
> > + switch (pixformat) {
> > + case V4L2_PIX_FMT_HM12:
> > + case V4L2_PIX_FMT_NV12:
> > + case V4L2_PIX_FMT_NV21:
> > + case V4L2_PIX_FMT_NV16:
> > + case V4L2_PIX_FMT_NV61:
> > + case V4L2_PIX_FMT_YUV420:
> > + case V4L2_PIX_FMT_YVU420:
> > + case V4L2_PIX_FMT_YUV422P:
> > + switch (mbus_code) {
> > + case MEDIA_BUS_FMT_UYVY8_1X16:
> > + case MEDIA_BUS_FMT_VYUY8_1X16:
> > + case MEDIA_BUS_FMT_YUYV8_1X16:
> > + case MEDIA_BUS_FMT_YVYU8_1X16:
> > + return true;
> > + }
> > + break;
> > + }
> Should we add default cases and warning messages here for debug purposes?
OK. I will add all the default cases and messages.
Thanks,
Yong
^ permalink raw reply
* Re: [linux-sunxi] [PATCH v4 2/2] media: V3s: Add support for Allwinner CSI.
From: Priit Laes @ 2017-12-22 10:21 UTC (permalink / raw)
To: Yong Deng
Cc: Maxime Ripard, Mauro Carvalho Chehab, Rob Herring, Mark Rutland,
Chen-Yu Tsai, David S. Miller, Greg Kroah-Hartman, Randy Dunlap,
Hans Verkuil, Stanimir Varbanov, Hugues Fruchet, Yannick Fertre,
Philipp Zabel, Arnd Bergmann, Benjamin Gaignard,
Ramesh Shanmugasundaram, Sakari Ailus, Rick Chang, linux-media,
devicetree, linux-arm-kernel, linux-kernel, linux-sunxi
In-Reply-To: <1513936020-35569-1-git-send-email-yong.deng@magewell.com>
On Fri, Dec 22, 2017 at 05:47:00PM +0800, Yong Deng wrote:
> Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> and CSI1 is used for parallel interface. This is not documented in
> datasheet but by testing and guess.
>
> This patch implement a v4l2 framework driver for it.
>
> Currently, the driver only support the parallel interface. MIPI-CSI2,
> ISP's support are not included in this patch.
>
> Signed-off-by: Yong Deng <yong.deng@magewell.com>
> ---
> MAINTAINERS | 8 +
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 2 +
> drivers/media/platform/sunxi/sun6i-csi/Kconfig | 9 +
> drivers/media/platform/sunxi/sun6i-csi/Makefile | 3 +
> drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c | 878 +++++++++++++++++++++
> drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h | 147 ++++
> .../media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h | 203 +++++
> .../media/platform/sunxi/sun6i-csi/sun6i_video.c | 752 ++++++++++++++++++
> .../media/platform/sunxi/sun6i-csi/sun6i_video.h | 60 ++
> 10 files changed, 2063 insertions(+)
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/Kconfig
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/Makefile
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
> create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9501403..b792fe5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -3783,6 +3783,14 @@ M: Jaya Kumar <jayakumar.alsa@gmail.com>
> S: Maintained
> F: sound/pci/cs5535audio/
>
> +CSI DRIVERS FOR ALLWINNER V3s
> +M: Yong Deng <yong.deng@magewell.com>
> +L: linux-media@vger.kernel.org
> +T: git git://linuxtv.org/media_tree.git
> +S: Maintained
> +F: drivers/media/platform/sunxi/sun6i-csi/
> +F: Documentation/devicetree/bindings/media/sun6i-csi.txt
> +
> CW1200 WLAN driver
> M: Solomon Peachy <pizza@shaftnet.org>
> S: Maintained
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index fd0c998..41017e3 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -150,6 +150,7 @@ source "drivers/media/platform/am437x/Kconfig"
> source "drivers/media/platform/xilinx/Kconfig"
> source "drivers/media/platform/rcar-vin/Kconfig"
> source "drivers/media/platform/atmel/Kconfig"
> +source "drivers/media/platform/sunxi/sun6i-csi/Kconfig"
>
> config VIDEO_TI_CAL
> tristate "TI CAL (Camera Adaptation Layer) driver"
> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> index 003b0bb..e6e9ce7 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -97,3 +97,5 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss-8x16/
> obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
>
> obj-y += meson/
> +
> +obj-$(CONFIG_VIDEO_SUN6I_CSI) += sunxi/sun6i-csi/
> diff --git a/drivers/media/platform/sunxi/sun6i-csi/Kconfig b/drivers/media/platform/sunxi/sun6i-csi/Kconfig
> new file mode 100644
> index 0000000..314188a
> --- /dev/null
> +++ b/drivers/media/platform/sunxi/sun6i-csi/Kconfig
> @@ -0,0 +1,9 @@
> +config VIDEO_SUN6I_CSI
> + tristate "Allwinner V3s Camera Sensor Interface driver"
> + depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API && HAS_DMA
> + depends on ARCH_SUNXI || COMPILE_TEST
> + select VIDEOBUF2_DMA_CONTIG
> + select REGMAP_MMIO
> + select V4L2_FWNODE
> + ---help---
> + Support for the Allwinner Camera Sensor Interface Controller on V3s.
> diff --git a/drivers/media/platform/sunxi/sun6i-csi/Makefile b/drivers/media/platform/sunxi/sun6i-csi/Makefile
> new file mode 100644
> index 0000000..213cb6b
> --- /dev/null
> +++ b/drivers/media/platform/sunxi/sun6i-csi/Makefile
> @@ -0,0 +1,3 @@
> +sun6i-csi-y += sun6i_video.o sun6i_csi.o
> +
> +obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o
> diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
> new file mode 100644
> index 0000000..8f3f2d6
> --- /dev/null
> +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
> @@ -0,0 +1,878 @@
> +/*
> + * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
> + * All rights reserved.
> + * Author: Yong Deng <yong.deng@magewell.com>
> + *
> + * 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.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/err.h>
> +#include <linux/fs.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/ioctl.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +#include <linux/sched.h>
> +#include <linux/sizes.h>
> +#include <linux/slab.h>
> +
> +#include "sun6i_csi.h"
> +#include "sun6i_csi_reg.h"
> +
> +#define MODULE_NAME "sun6i-csi"
> +
> +struct sun6i_csi_dev {
> + struct sun6i_csi csi;
> + struct device *dev;
> +
> + struct regmap *regmap;
> + struct clk *clk_mod;
> + struct clk *clk_ram;
> + struct reset_control *rstc_bus;
> +
> + int planar_offset[3];
> +};
> +
> +static const u32 supported_pixformats[] = {
> + V4L2_PIX_FMT_SBGGR8,
> + V4L2_PIX_FMT_SGBRG8,
> + V4L2_PIX_FMT_SGRBG8,
> + V4L2_PIX_FMT_SRGGB8,
> + V4L2_PIX_FMT_SBGGR10,
> + V4L2_PIX_FMT_SGBRG10,
> + V4L2_PIX_FMT_SGRBG10,
> + V4L2_PIX_FMT_SRGGB10,
> + V4L2_PIX_FMT_SBGGR12,
> + V4L2_PIX_FMT_SGBRG12,
> + V4L2_PIX_FMT_SGRBG12,
> + V4L2_PIX_FMT_SRGGB12,
> + V4L2_PIX_FMT_YUYV,
> + V4L2_PIX_FMT_YVYU,
> + V4L2_PIX_FMT_UYVY,
> + V4L2_PIX_FMT_VYUY,
> + V4L2_PIX_FMT_HM12,
> + V4L2_PIX_FMT_NV12,
> + V4L2_PIX_FMT_NV21,
> + V4L2_PIX_FMT_YUV420,
> + V4L2_PIX_FMT_YVU420,
> + V4L2_PIX_FMT_NV16,
> + V4L2_PIX_FMT_NV61,
> + V4L2_PIX_FMT_YUV422P,
> +};
> +
> +static inline struct sun6i_csi_dev *sun6i_csi_to_dev(struct sun6i_csi *csi)
> +{
> + return container_of(csi, struct sun6i_csi_dev, csi);
> +}
> +
> +int sun6i_csi_get_supported_pixformats(struct sun6i_csi *csi,
> + const u32 **pixformats)
> +{
> + if (pixformats != NULL)
> + *pixformats = supported_pixformats;
> +
> + return ARRAY_SIZE(supported_pixformats);
> +}
> +
> +/* TODO add 10&12 bit YUV, RGB support */
> +bool sun6i_csi_is_format_support(struct sun6i_csi *csi,
> + u32 pixformat, u32 mbus_code)
> +{
> + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> +
> + /*
> + * Some video receivers have the ability to be compatible with
> + * 8bit and 16bit bus width.
> + * Identify the media bus format from device tree.
> + */
> + if ((sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_PARALLEL
> + || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_BT656)
> + && sdev->csi.v4l2_ep.bus.parallel.bus_width == 16) {
> + switch (pixformat) {
> + case V4L2_PIX_FMT_HM12:
> + case V4L2_PIX_FMT_NV12:
> + case V4L2_PIX_FMT_NV21:
> + case V4L2_PIX_FMT_NV16:
> + case V4L2_PIX_FMT_NV61:
> + case V4L2_PIX_FMT_YUV420:
> + case V4L2_PIX_FMT_YVU420:
> + case V4L2_PIX_FMT_YUV422P:
> + switch (mbus_code) {
> + case MEDIA_BUS_FMT_UYVY8_1X16:
> + case MEDIA_BUS_FMT_VYUY8_1X16:
> + case MEDIA_BUS_FMT_YUYV8_1X16:
> + case MEDIA_BUS_FMT_YVYU8_1X16:
> + return true;
> + }
> + break;
> + }
Should we add default cases and warning messages here for debug purposes?
> + return false;
> + }
> +
> + switch (pixformat) {
> + case V4L2_PIX_FMT_SBGGR8:
> + return (mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8);
> + case V4L2_PIX_FMT_SGBRG8:
> + return (mbus_code == MEDIA_BUS_FMT_SGBRG8_1X8);
> + case V4L2_PIX_FMT_SGRBG8:
> + return (mbus_code == MEDIA_BUS_FMT_SGRBG8_1X8);
> + case V4L2_PIX_FMT_SRGGB8:
> + return (mbus_code == MEDIA_BUS_FMT_SRGGB8_1X8);
> + case V4L2_PIX_FMT_SBGGR10:
> + return (mbus_code == MEDIA_BUS_FMT_SBGGR10_1X10);
> + case V4L2_PIX_FMT_SGBRG10:
> + return (mbus_code == MEDIA_BUS_FMT_SGBRG10_1X10);
> + case V4L2_PIX_FMT_SGRBG10:
> + return (mbus_code == MEDIA_BUS_FMT_SGRBG10_1X10);
> + case V4L2_PIX_FMT_SRGGB10:
> + return (mbus_code == MEDIA_BUS_FMT_SRGGB10_1X10);
> + case V4L2_PIX_FMT_SBGGR12:
> + return (mbus_code == MEDIA_BUS_FMT_SBGGR12_1X12);
> + case V4L2_PIX_FMT_SGBRG12:
> + return (mbus_code == MEDIA_BUS_FMT_SGBRG12_1X12);
> + case V4L2_PIX_FMT_SGRBG12:
> + return (mbus_code == MEDIA_BUS_FMT_SGRBG12_1X12);
> + case V4L2_PIX_FMT_SRGGB12:
> + return (mbus_code == MEDIA_BUS_FMT_SRGGB12_1X12);
> +
> + case V4L2_PIX_FMT_YUYV:
> + return (mbus_code == MEDIA_BUS_FMT_YUYV8_2X8);
> + case V4L2_PIX_FMT_YVYU:
> + return (mbus_code == MEDIA_BUS_FMT_YVYU8_2X8);
> + case V4L2_PIX_FMT_UYVY:
> + return (mbus_code == MEDIA_BUS_FMT_UYVY8_2X8);
> + case V4L2_PIX_FMT_VYUY:
> + return (mbus_code == MEDIA_BUS_FMT_VYUY8_2X8);
> +
> + case V4L2_PIX_FMT_HM12:
> + case V4L2_PIX_FMT_NV12:
> + case V4L2_PIX_FMT_NV21:
> + case V4L2_PIX_FMT_NV16:
> + case V4L2_PIX_FMT_NV61:
> + case V4L2_PIX_FMT_YUV420:
> + case V4L2_PIX_FMT_YVU420:
> + case V4L2_PIX_FMT_YUV422P:
> + switch (mbus_code) {
> + case MEDIA_BUS_FMT_UYVY8_2X8:
> + case MEDIA_BUS_FMT_VYUY8_2X8:
> + case MEDIA_BUS_FMT_YUYV8_2X8:
> + case MEDIA_BUS_FMT_YVYU8_2X8:
> + return true;
> + }
> + break;
> + }
> +
Should we add default cases and warning messages here for debug purposes?
> + return false;
> +}
> +
> +int sun6i_csi_set_power(struct sun6i_csi *csi, bool enable)
> +{
> + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> + struct regmap *regmap = sdev->regmap;
> + int ret;
> +
> + if (!enable) {
> + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
> +
> + clk_disable_unprepare(sdev->clk_ram);
> + clk_disable_unprepare(sdev->clk_mod);
> + reset_control_assert(sdev->rstc_bus);
> + return 0;
> + }
> +
> + ret = clk_prepare_enable(sdev->clk_mod);
> + if (ret) {
> + dev_err(sdev->dev, "Enable csi clk err %d\n", ret);
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(sdev->clk_ram);
> + if (ret) {
> + dev_err(sdev->dev, "Enable clk_dram_csi clk err %d\n", ret);
> + return ret;
> + }
> +
> + ret = reset_control_deassert(sdev->rstc_bus);
> + if (ret) {
> + dev_err(sdev->dev, "reset err %d\n", ret);
> + return ret;
> + }
> +
> + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN);
> +
> + return 0;
> +}
> +
> +static enum csi_input_fmt get_csi_input_format(u32 mbus_code, u32 pixformat)
> +{
> + /* bayer */
> + if ((mbus_code & 0xF000) == 0x3000)
> + return CSI_INPUT_FORMAT_RAW;
> +
> + switch (pixformat) {
> + case V4L2_PIX_FMT_YUYV:
> + case V4L2_PIX_FMT_YVYU:
> + case V4L2_PIX_FMT_UYVY:
> + case V4L2_PIX_FMT_VYUY:
> + return CSI_INPUT_FORMAT_RAW;
> + }
> +
> + /* not support YUV420 input format yet */
Please add message here for debug purposes.
> + return CSI_INPUT_FORMAT_YUV422;
> +}
> +
> +static enum csi_output_fmt get_csi_output_format(u32 pixformat, u32 field)
> +{
> + bool buf_interlaced = false;
> +
> + if (field == V4L2_FIELD_INTERLACED
> + || field == V4L2_FIELD_INTERLACED_TB
> + || field == V4L2_FIELD_INTERLACED_BT)
> + buf_interlaced = true;
> +
> + switch (pixformat) {
> + case V4L2_PIX_FMT_SBGGR8:
> + case V4L2_PIX_FMT_SGBRG8:
> + case V4L2_PIX_FMT_SGRBG8:
> + case V4L2_PIX_FMT_SRGGB8:
> + return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
> + case V4L2_PIX_FMT_SBGGR10:
> + case V4L2_PIX_FMT_SGBRG10:
> + case V4L2_PIX_FMT_SGRBG10:
> + case V4L2_PIX_FMT_SRGGB10:
> + return buf_interlaced ? CSI_FRAME_RAW_10 : CSI_FIELD_RAW_10;
> + case V4L2_PIX_FMT_SBGGR12:
> + case V4L2_PIX_FMT_SGBRG12:
> + case V4L2_PIX_FMT_SGRBG12:
> + case V4L2_PIX_FMT_SRGGB12:
> + return buf_interlaced ? CSI_FRAME_RAW_12 : CSI_FIELD_RAW_12;
> +
> + case V4L2_PIX_FMT_YUYV:
> + case V4L2_PIX_FMT_YVYU:
> + case V4L2_PIX_FMT_UYVY:
> + case V4L2_PIX_FMT_VYUY:
> + return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
> +
> + case V4L2_PIX_FMT_HM12:
> + return buf_interlaced ? CSI_FRAME_MB_YUV420 :
> + CSI_FIELD_MB_YUV420;
> + case V4L2_PIX_FMT_NV12:
> + case V4L2_PIX_FMT_NV21:
> + return buf_interlaced ? CSI_FRAME_UV_CB_YUV420 :
> + CSI_FIELD_UV_CB_YUV420;
> + case V4L2_PIX_FMT_YUV420:
> + case V4L2_PIX_FMT_YVU420:
> + return buf_interlaced ? CSI_FRAME_PLANAR_YUV420 :
> + CSI_FIELD_PLANAR_YUV420;
> + case V4L2_PIX_FMT_NV16:
> + case V4L2_PIX_FMT_NV61:
> + return buf_interlaced ? CSI_FRAME_UV_CB_YUV422 :
> + CSI_FIELD_UV_CB_YUV422;
> + case V4L2_PIX_FMT_YUV422P:
> + return buf_interlaced ? CSI_FRAME_PLANAR_YUV422 :
> + CSI_FIELD_PLANAR_YUV422;
> + }
Missing default case (gcc might complain).
And also would be nice to have message here for debug purposes.
> +
> + return 0;
> +}
> +
> +static enum csi_input_seq get_csi_input_seq(u32 mbus_code, u32 pixformat)
> +{
> +
> + switch (pixformat) {
> + case V4L2_PIX_FMT_HM12:
> + case V4L2_PIX_FMT_NV12:
> + case V4L2_PIX_FMT_NV16:
> + case V4L2_PIX_FMT_YUV420:
> + case V4L2_PIX_FMT_YUV422P:
> + switch (mbus_code) {
> + case MEDIA_BUS_FMT_UYVY8_2X8:
> + case MEDIA_BUS_FMT_UYVY8_1X16:
> + return CSI_INPUT_SEQ_UYVY;
> + case MEDIA_BUS_FMT_VYUY8_2X8:
> + case MEDIA_BUS_FMT_VYUY8_1X16:
> + return CSI_INPUT_SEQ_VYUY;
> + case MEDIA_BUS_FMT_YUYV8_2X8:
> + case MEDIA_BUS_FMT_YUYV8_1X16:
> + return CSI_INPUT_SEQ_YUYV;
> + case MEDIA_BUS_FMT_YVYU8_1X16:
> + case MEDIA_BUS_FMT_YVYU8_2X8:
> + return CSI_INPUT_SEQ_YVYU;
> + }
> + break;
> + case V4L2_PIX_FMT_NV21:
> + case V4L2_PIX_FMT_NV61:
> + case V4L2_PIX_FMT_YVU420:
> + switch (mbus_code) {
> + case MEDIA_BUS_FMT_UYVY8_2X8:
> + case MEDIA_BUS_FMT_UYVY8_1X16:
> + return CSI_INPUT_SEQ_VYUY;
> + case MEDIA_BUS_FMT_VYUY8_2X8:
> + case MEDIA_BUS_FMT_VYUY8_1X16:
> + return CSI_INPUT_SEQ_UYVY;
> + case MEDIA_BUS_FMT_YUYV8_2X8:
> + case MEDIA_BUS_FMT_YUYV8_1X16:
> + return CSI_INPUT_SEQ_YVYU;
> + case MEDIA_BUS_FMT_YVYU8_1X16:
> + case MEDIA_BUS_FMT_YVYU8_2X8:
> + return CSI_INPUT_SEQ_YUYV;
> + }
> + break;
> + }
Missing default case (gcc might complain).
And also would be nice to have message here for debug purposes.
> +
> + return CSI_INPUT_SEQ_YUYV;
> +}
> +
> +static void sun6i_csi_setup_bus(struct sun6i_csi_dev *sdev)
> +{
> + struct v4l2_fwnode_endpoint *endpoint = &sdev->csi.v4l2_ep;
> + unsigned char bus_width;
> + u32 flags;
> + u32 cfg;
> +
> + bus_width = endpoint->bus.parallel.bus_width;
> +
> + regmap_read(sdev->regmap, CSI_IF_CFG_REG, &cfg);
> +
> + cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK |
> + CSI_IF_CFG_IF_DATA_WIDTH_MASK |
> + CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK |
> + CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK);
> +
> + switch (endpoint->bus_type) {
> + case V4L2_MBUS_PARALLEL:
> + cfg |= CSI_IF_CFG_MIPI_IF_CSI;
> +
> + flags = endpoint->bus.parallel.flags;
> +
> + cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT :
> + CSI_IF_CFG_CSI_IF_YUV422_INTLV;
> +
> + if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
> + cfg |= CSI_IF_CFG_FIELD_POSITIVE;
> +
> + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
> + cfg |= CSI_IF_CFG_VREF_POL_POSITIVE;
> + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
> + cfg |= CSI_IF_CFG_HREF_POL_POSITIVE;
> +
> + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
> + cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
> + break;
> + case V4L2_MBUS_BT656:
> + cfg |= CSI_IF_CFG_MIPI_IF_CSI;
> +
> + flags = endpoint->bus.parallel.flags;
> +
> + cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 :
> + CSI_IF_CFG_CSI_IF_BT656;
> +
> + if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
> + cfg |= CSI_IF_CFG_FIELD_POSITIVE;
> +
> + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
> + cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
> + break;
> + default:
> + dev_warn(sdev->dev, "Unsupported bus type: %d\n",
> + endpoint->bus_type);
> + break;
> + }
> +
> + switch (bus_width) {
> + case 8:
> + cfg |= CSI_IF_CFG_IF_DATA_WIDTH_8BIT;
> + break;
> + case 10:
> + cfg |= CSI_IF_CFG_IF_DATA_WIDTH_10BIT;
> + break;
> + case 12:
> + cfg |= CSI_IF_CFG_IF_DATA_WIDTH_12BIT;
> + break;
> + case 16: /* No need to configure DATA_WIDTH for 16bit */
> + break;
> + default:
> + dev_warn(sdev->dev, "Unsupported bus width: %d\n", bus_width);
> + break;
> + }
> +
> + regmap_write(sdev->regmap, CSI_IF_CFG_REG, cfg);
> +}
> +
> +static void sun6i_csi_set_format(struct sun6i_csi_dev *sdev)
> +{
> + struct sun6i_csi *csi = &sdev->csi;
> + u32 cfg;
> + u32 val;
> +
> + regmap_read(sdev->regmap, CSI_CH_CFG_REG, &cfg);
> +
> + cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK |
> + CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN |
> + CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK |
> + CSI_CH_CFG_INPUT_SEQ_MASK);
> +
> + val = get_csi_input_format(csi->config.code, csi->config.pixelformat);
> + cfg |= CSI_CH_CFG_INPUT_FMT(val);
> +
> + val = get_csi_output_format(csi->config.pixelformat, csi->config.field);
> + cfg |= CSI_CH_CFG_OUTPUT_FMT(val);
> +
> + val = get_csi_input_seq(csi->config.code, csi->config.pixelformat);
> + cfg |= CSI_CH_CFG_INPUT_SEQ(val);
> +
> + if (csi->config.field == V4L2_FIELD_TOP)
> + cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0;
> + else if (csi->config.field == V4L2_FIELD_BOTTOM)
> + cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1;
> + else
> + cfg |= CSI_CH_CFG_FIELD_SEL_BOTH;
> +
> + regmap_write(sdev->regmap, CSI_CH_CFG_REG, cfg);
> +}
> +
> +static void sun6i_csi_set_window(struct sun6i_csi_dev *sdev)
> +{
> + struct sun6i_csi_config *config = &sdev->csi.config;
> + u32 bytesperline_y;
> + u32 bytesperline_c;
> + int *planar_offset = sdev->planar_offset;
> + u32 width = config->width;
> + u32 height = config->height;
> + u32 hor_len = width;
> +
> + switch (config->pixelformat) {
> + case V4L2_PIX_FMT_YUYV:
> + case V4L2_PIX_FMT_YVYU:
> + case V4L2_PIX_FMT_UYVY:
> + case V4L2_PIX_FMT_VYUY:
> + hor_len = width * 2;
> + break;
Missing default case. And also would be nice to have message
here for debug purposes.
> + }
> +
> + regmap_write(sdev->regmap, CSI_CH_HSIZE_REG,
> + CSI_CH_HSIZE_HOR_LEN(hor_len) |
> + CSI_CH_HSIZE_HOR_START(0));
> + regmap_write(sdev->regmap, CSI_CH_VSIZE_REG,
> + CSI_CH_VSIZE_VER_LEN(height) |
> + CSI_CH_VSIZE_VER_START(0));
> +
> + planar_offset[0] = 0;
> + switch (config->pixelformat) {
> + case V4L2_PIX_FMT_HM12:
> + case V4L2_PIX_FMT_NV12:
> + case V4L2_PIX_FMT_NV21:
> + case V4L2_PIX_FMT_NV16:
> + case V4L2_PIX_FMT_NV61:
> + bytesperline_y = width;
> + bytesperline_c = width;
> + planar_offset[1] = bytesperline_y * height;
> + planar_offset[2] = -1;
> + break;
> + case V4L2_PIX_FMT_YUV420:
> + case V4L2_PIX_FMT_YVU420:
> + bytesperline_y = width;
> + bytesperline_c = width / 2;
> + planar_offset[1] = bytesperline_y * height;
> + planar_offset[2] = planar_offset[1] +
> + bytesperline_c * height / 2;
> + break;
> + case V4L2_PIX_FMT_YUV422P:
> + bytesperline_y = width;
> + bytesperline_c = width / 2;
> + planar_offset[1] = bytesperline_y * height;
> + planar_offset[2] = planar_offset[1] +
> + bytesperline_c * height;
> + break;
> + default: /* raw */
And would be nice to have message here for debug purposes.
> + bytesperline_y = (sun6i_csi_get_bpp(config->pixelformat) *
> + config->width) / 8;
> + bytesperline_c = 0;
> + planar_offset[1] = -1;
> + planar_offset[2] = -1;
> + break;
> + }
> +
> + regmap_write(sdev->regmap, CSI_CH_BUF_LEN_REG,
> + CSI_CH_BUF_LEN_BUF_LEN_C(bytesperline_c) |
> + CSI_CH_BUF_LEN_BUF_LEN_Y(bytesperline_y));
> +}
> +
> +int sun6i_csi_update_config(struct sun6i_csi *csi,
> + struct sun6i_csi_config *config)
> +{
> + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> +
> + if (config == NULL)
> + return -EINVAL;
> +
> + memcpy(&csi->config, config, sizeof(csi->config));
> +
> + sun6i_csi_setup_bus(sdev);
> + sun6i_csi_set_format(sdev);
> + sun6i_csi_set_window(sdev);
> +
> + return 0;
> +}
> +
> +void sun6i_csi_update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
> +{
> + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> + /* transform physical address to bus address */
> + dma_addr_t bus_addr = addr - PHYS_OFFSET;
> +
> + regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
> + (bus_addr + sdev->planar_offset[0]) >> 2);
> + if (sdev->planar_offset[1] != -1)
> + regmap_write(sdev->regmap, CSI_CH_F1_BUFA_REG,
> + (bus_addr + sdev->planar_offset[1]) >> 2);
> + if (sdev->planar_offset[2] != -1)
> + regmap_write(sdev->regmap, CSI_CH_F2_BUFA_REG,
> + (bus_addr + sdev->planar_offset[2]) >> 2);
> +}
> +
> +void sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable)
> +{
> + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> + struct regmap *regmap = sdev->regmap;
> +
> + if (!enable) {
> + regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0);
> + regmap_write(regmap, CSI_CH_INT_EN_REG, 0);
> + return;
> + }
> +
> + regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF);
> + regmap_write(regmap, CSI_CH_INT_EN_REG,
> + CSI_CH_INT_EN_HB_OF_INT_EN |
> + CSI_CH_INT_EN_FIFO2_OF_INT_EN |
> + CSI_CH_INT_EN_FIFO1_OF_INT_EN |
> + CSI_CH_INT_EN_FIFO0_OF_INT_EN |
> + CSI_CH_INT_EN_FD_INT_EN |
> + CSI_CH_INT_EN_CD_INT_EN);
> +
> + regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON,
> + CSI_CAP_CH0_VCAP_ON);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Media Controller and V4L2
> + */
> +static int sun6i_csi_link_entity(struct sun6i_csi *csi,
> + struct media_entity *entity)
> +{
> + struct media_entity *sink;
> + struct media_pad *sink_pad;
> + int ret;
> + int i;
> +
> + if (!entity->num_pads) {
> + dev_err(csi->dev, "%s: invalid entity\n", entity->name);
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < entity->num_pads; i++) {
> + if (entity->pads[i].flags & MEDIA_PAD_FL_SOURCE)
> + break;
> + }
> +
> + if (i == entity->num_pads) {
> + dev_err(csi->dev, "%s: no source pad in external entity %s\n",
> + __func__, entity->name);
> + return -EINVAL;
> + }
> +
> + sink = &csi->video.vdev.entity;
> + sink_pad = &csi->video.pad;
> +
> + dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
> + entity->name, i, sink->name, sink_pad->index);
> + ret = media_create_pad_link(entity, i, sink, sink_pad->index,
> + MEDIA_LNK_FL_ENABLED);
> + if (ret < 0) {
> + dev_err(csi->dev, "failed to create %s:%u -> %s:%u link\n",
> + entity->name, i, sink->name, sink_pad->index);
> + return ret;
> + }
> +
> + return media_entity_call(sink, link_setup, sink_pad, &entity->pads[i],
> + MEDIA_LNK_FL_ENABLED);
> +}
> +
> +static int sun6i_subdev_notify_complete(struct v4l2_async_notifier *notifier)
> +{
> + struct sun6i_csi *csi = container_of(notifier, struct sun6i_csi,
> + notifier);
> + struct v4l2_device *v4l2_dev = &csi->v4l2_dev;
> + struct v4l2_subdev *sd;
> + int ret;
> +
> + dev_dbg(csi->dev, "notify complete, all subdevs registered\n");
> +
> + if (notifier->num_subdevs != 1)
> + return -EINVAL;
> +
> + sd = list_first_entry(&v4l2_dev->subdevs, struct v4l2_subdev, list);
> + if (sd == NULL)
> + return -EINVAL;
> +
> + ret = sun6i_csi_link_entity(csi, &sd->entity);
> + if (ret < 0)
> + return ret;
> +
> + ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev);
> + if (ret < 0)
> + return ret;
> +
> + return media_device_register(&csi->media_dev);
> +}
> +
> +static const struct v4l2_async_notifier_operations sun6i_csi_async_ops = {
> + .complete = sun6i_subdev_notify_complete,
> +};
> +
> +static int sun6i_csi_fwnode_parse(struct device *dev,
> + struct v4l2_fwnode_endpoint *vep,
> + struct v4l2_async_subdev *asd)
> +{
> + struct sun6i_csi *csi = dev_get_drvdata(dev);
> +
> + if (vep->base.port || vep->base.id) {
> + dev_warn(dev, "Only support a single port with one endpoint\n");
> + return -ENOTCONN;
> + }
> +
> + switch (vep->bus_type) {
> + case V4L2_MBUS_PARALLEL:
> + case V4L2_MBUS_BT656:
> + csi->v4l2_ep = *vep;
> + return 0;
> + default:
> + dev_err(dev, "Unsupported media bus type\n");
> + return -ENOTCONN;
> + }
> +}
> +
> +static void sun6i_csi_v4l2_cleanup(struct sun6i_csi *csi)
> +{
> + v4l2_async_notifier_cleanup(&csi->notifier);
> + v4l2_async_notifier_unregister(&csi->notifier);
> + sun6i_video_cleanup(&csi->video);
> + v4l2_device_unregister(&csi->v4l2_dev);
> + media_device_unregister(&csi->media_dev);
> + media_device_cleanup(&csi->media_dev);
> +}
> +
> +static int sun6i_csi_v4l2_init(struct sun6i_csi *csi)
> +{
> + int ret;
> +
> + csi->media_dev.dev = csi->dev;
> + strlcpy(csi->media_dev.model, "Allwinner Video Capture Device",
> + sizeof(csi->media_dev.model));
> + csi->media_dev.hw_revision = 0;
> +
> + media_device_init(&csi->media_dev);
> +
> + csi->v4l2_dev.mdev = &csi->media_dev;
> + ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
> + if (ret) {
> + dev_err(csi->dev, "V4L2 device registration failed (%d)\n",
> + ret);
> + goto clean_media;
> + }
> +
> + ret = sun6i_video_init(&csi->video, csi, "sun6i-csi");
> + if (ret)
> + goto unreg_v4l2;
> +
> + ret = v4l2_async_notifier_parse_fwnode_endpoints(
> + csi->dev, &csi->notifier, sizeof(struct v4l2_async_subdev),
> + sun6i_csi_fwnode_parse);
> + if (ret)
> + goto clean_video;
> +
> + csi->notifier.ops = &sun6i_csi_async_ops;
> +
> + ret = v4l2_async_notifier_register(&csi->v4l2_dev, &csi->notifier);
> + if (ret) {
> + dev_err(csi->dev, "notifier registration failed\n");
> + goto clean_notifier;
> + }
> +
> + return 0;
> +
> +clean_notifier:
> + v4l2_async_notifier_cleanup(&csi->notifier);
> +clean_video:
> + sun6i_video_cleanup(&csi->video);
> +unreg_v4l2:
> + v4l2_device_unregister(&csi->v4l2_dev);
> +clean_media:
> + media_device_cleanup(&csi->media_dev);
> +
> + return ret;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Resources and IRQ
> + */
> +static irqreturn_t sun6i_csi_isr(int irq, void *dev_id)
> +{
> + struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id;
> + struct regmap *regmap = sdev->regmap;
> + u32 status;
> +
> + regmap_read(regmap, CSI_CH_INT_STA_REG, &status);
> +
> + if (!(status & 0xFF))
> + return IRQ_NONE;
> +
> + if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) ||
> + (status & CSI_CH_INT_STA_FIFO1_OF_PD) ||
> + (status & CSI_CH_INT_STA_FIFO2_OF_PD) ||
> + (status & CSI_CH_INT_STA_HB_OF_PD)) {
> + regmap_write(regmap, CSI_CH_INT_STA_REG, status);
> + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
> + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
> + CSI_EN_CSI_EN);
> + return IRQ_HANDLED;
> + }
> +
> + if (status & CSI_CH_INT_STA_FD_PD)
> + sun6i_video_frame_done(&sdev->csi.video);
> +
> + regmap_write(regmap, CSI_CH_INT_STA_REG, status);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static const struct regmap_config sun6i_csi_regmap_config = {
> + .reg_bits = 32,
> + .reg_stride = 4,
> + .val_bits = 32,
> + .max_register = 0x1000,
> +};
> +
> +static int sun6i_csi_resource_request(struct sun6i_csi_dev *sdev,
> + struct platform_device *pdev)
> +{
> + struct resource *res;
> + void __iomem *io_base;
> + int ret;
> + int irq;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + io_base = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(io_base))
> + return PTR_ERR(io_base);
> +
> + sdev->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "bus", io_base,
> + &sun6i_csi_regmap_config);
> + if (IS_ERR(sdev->regmap)) {
> + dev_err(&pdev->dev, "Failed to init register map\n");
> + return PTR_ERR(sdev->regmap);
> + }
> +
> + sdev->clk_mod = devm_clk_get(&pdev->dev, "mod");
> + if (IS_ERR(sdev->clk_mod)) {
> + dev_err(&pdev->dev, "Unable to acquire csi clock\n");
> + return PTR_ERR(sdev->clk_mod);
> + }
> +
> + sdev->clk_ram = devm_clk_get(&pdev->dev, "ram");
> + if (IS_ERR(sdev->clk_ram)) {
> + dev_err(&pdev->dev, "Unable to acquire dram-csi clock\n");
> + return PTR_ERR(sdev->clk_ram);
> + }
> +
> + sdev->rstc_bus = devm_reset_control_get_shared(&pdev->dev, NULL);
> + if (IS_ERR(sdev->rstc_bus)) {
> + dev_err(&pdev->dev, "Cannot get reset controller\n");
> + return PTR_ERR(sdev->rstc_bus);
> + }
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0) {
> + dev_err(&pdev->dev, "No csi IRQ specified\n");
> + ret = -ENXIO;
> + return ret;
> + }
> +
> + ret = devm_request_irq(&pdev->dev, irq, sun6i_csi_isr, 0, MODULE_NAME,
> + sdev);
> + if (ret) {
> + dev_err(&pdev->dev, "Cannot request csi IRQ\n");
> + return ret;
> + }
> + return 0;
> +}
> +
> +static int sun6i_csi_probe(struct platform_device *pdev)
> +{
> + struct sun6i_csi_dev *sdev;
> + int ret;
> +
> + sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
> + if (!sdev)
> + return -ENOMEM;
> +
> + sdev->dev = &pdev->dev;
> +
> + ret = sun6i_csi_resource_request(sdev, pdev);
> + if (ret)
> + return ret;
> +
> + platform_set_drvdata(pdev, sdev);
> +
> + sdev->csi.dev = &pdev->dev;
> + ret = sun6i_csi_v4l2_init(&sdev->csi);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int sun6i_csi_remove(struct platform_device *pdev)
> +{
> + struct sun6i_csi_dev *sdev = platform_get_drvdata(pdev);
> +
> + sun6i_csi_v4l2_cleanup(&sdev->csi);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id sun6i_csi_of_match[] = {
> + { .compatible = "allwinner,sun8i-v3s-csi", },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, sun6i_csi_of_match);
> +
> +static struct platform_driver sun6i_csi_platform_driver = {
> + .probe = sun6i_csi_probe,
> + .remove = sun6i_csi_remove,
> + .driver = {
> + .name = MODULE_NAME,
> + .of_match_table = of_match_ptr(sun6i_csi_of_match),
> + },
> +};
> +module_platform_driver(sun6i_csi_platform_driver);
> +
> +MODULE_DESCRIPTION("Allwinner V3s Camera Sensor Interface driver");
> +MODULE_AUTHOR("Yong Deng <yong.deng@magewell.com>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
> new file mode 100644
> index 0000000..6733a1e
> --- /dev/null
> +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
> @@ -0,0 +1,147 @@
> +/*
> + * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
> + *
> + * 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.
> + *
> + * 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.
> + */
> +
> +#ifndef __SUN6I_CSI_H__
> +#define __SUN6I_CSI_H__
> +
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +
> +#include "sun6i_video.h"
> +
> +struct sun6i_csi;
> +
> +/**
> + * struct sun6i_csi_config - configs for sun6i csi
> + * @pixelformat: v4l2 pixel format (V4L2_PIX_FMT_*)
> + * @code: media bus format code (MEDIA_BUS_FMT_*)
> + * @field: used interlacing type (enum v4l2_field)
> + * @width: frame width
> + * @height: frame height
> + */
> +struct sun6i_csi_config {
> + u32 pixelformat;
> + u32 code;
> + u32 field;
> + u32 width;
> + u32 height;
> +};
> +
> +struct sun6i_csi {
> + struct device *dev;
> + struct v4l2_device v4l2_dev;
> + struct media_device media_dev;
> +
> + struct v4l2_async_notifier notifier;
> +
> + /* video port settings */
> + struct v4l2_fwnode_endpoint v4l2_ep;
> +
> + struct sun6i_csi_config config;
> +
> + struct sun6i_video video;
> +};
> +
> +/**
> + * sun6i_csi_get_supported_pixformats() - get csi supported pixformats
> + * @csi: pointer to the csi
> + * @pixformats: supported pixformats return from csi
> + *
> + * @return the count of pixformats or error(< 0)
> + */
> +int sun6i_csi_get_supported_pixformats(struct sun6i_csi *csi,
> + const u32 **pixformats);
> +
> +/**
> + * sun6i_csi_is_format_support() - check if the format supported by csi
> + * @csi: pointer to the csi
> + * @pixformat: v4l2 pixel format (V4L2_PIX_FMT_*)
> + * @mbus_code: media bus format code (MEDIA_BUS_FMT_*)
> + */
> +bool sun6i_csi_is_format_support(struct sun6i_csi *csi, u32 pixformat,
> + u32 mbus_code);
> +
> +/**
> + * sun6i_csi_set_power() - power on/off the csi
> + * @csi: pointer to the csi
> + * @enable: on/off
> + */
> +int sun6i_csi_set_power(struct sun6i_csi *csi, bool enable);
> +
> +/**
> + * sun6i_csi_update_config() - update the csi register setttings
> + * @csi: pointer to the csi
> + * @config: see struct sun6i_csi_config
> + */
> +int sun6i_csi_update_config(struct sun6i_csi *csi,
> + struct sun6i_csi_config *config);
> +
> +/**
> + * sun6i_csi_update_buf_addr() - update the csi frame buffer address
> + * @csi: pointer to the csi
> + * @addr: frame buffer's physical address
> + */
> +void sun6i_csi_update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr);
> +
> +/**
> + * sun6i_csi_set_stream() - start/stop csi streaming
> + * @csi: pointer to the csi
> + * @enable: start/stop
> + */
> +void sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable);
> +
> +/* get bpp form v4l2 pixformat */
> +static inline int sun6i_csi_get_bpp(unsigned int pixformat)
> +{
> + switch (pixformat) {
> + case V4L2_PIX_FMT_SBGGR8:
> + case V4L2_PIX_FMT_SGBRG8:
> + case V4L2_PIX_FMT_SGRBG8:
> + case V4L2_PIX_FMT_SRGGB8:
> + return 8;
> + case V4L2_PIX_FMT_SBGGR10:
> + case V4L2_PIX_FMT_SGBRG10:
> + case V4L2_PIX_FMT_SGRBG10:
> + case V4L2_PIX_FMT_SRGGB10:
> + return 10;
> + case V4L2_PIX_FMT_SBGGR12:
> + case V4L2_PIX_FMT_SGBRG12:
> + case V4L2_PIX_FMT_SGRBG12:
> + case V4L2_PIX_FMT_SRGGB12:
> + case V4L2_PIX_FMT_HM12:
> + case V4L2_PIX_FMT_NV12:
> + case V4L2_PIX_FMT_NV21:
> + case V4L2_PIX_FMT_YUV420:
> + case V4L2_PIX_FMT_YVU420:
> + return 12;
> + case V4L2_PIX_FMT_YUYV:
> + case V4L2_PIX_FMT_YVYU:
> + case V4L2_PIX_FMT_UYVY:
> + case V4L2_PIX_FMT_VYUY:
> + case V4L2_PIX_FMT_NV16:
> + case V4L2_PIX_FMT_NV61:
> + case V4L2_PIX_FMT_YUV422P:
> + return 16;
> + case V4L2_PIX_FMT_RGB24:
> + case V4L2_PIX_FMT_BGR24:
> + return 24;
> + case V4L2_PIX_FMT_RGB32:
> + case V4L2_PIX_FMT_BGR32:
> + return 32;
Please add default case with warning.
> + }
> +
> + return 0;
> +}
> +
> +#endif /* __SUN6I_CSI_H__ */
> diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h
> new file mode 100644
> index 0000000..aad674a
> --- /dev/null
> +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h
> @@ -0,0 +1,203 @@
> +/*
> + * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
> + *
> + * 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.
> + *
> + * 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.
> + */
> +
> +#ifndef __SUN6I_CSI_REG_H__
> +#define __SUN6I_CSI_REG_H__
> +
> +#include <linux/kernel.h>
> +
> +#define CSI_EN_REG 0x0
> +#define CSI_EN_VER_EN BIT(30)
> +#define CSI_EN_CSI_EN BIT(0)
> +
> +#define CSI_IF_CFG_REG 0x4
> +#define CSI_IF_CFG_SRC_TYPE_MASK BIT(21)
> +#define CSI_IF_CFG_SRC_TYPE_PROGRESSED ((0 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
> +#define CSI_IF_CFG_SRC_TYPE_INTERLACED ((1 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
> +#define CSI_IF_CFG_FPS_DS_EN BIT(20)
> +#define CSI_IF_CFG_FIELD_MASK BIT(19)
> +#define CSI_IF_CFG_FIELD_NEGATIVE ((0 << 19) & CSI_IF_CFG_FIELD_MASK)
> +#define CSI_IF_CFG_FIELD_POSITIVE ((1 << 19) & CSI_IF_CFG_FIELD_MASK)
> +#define CSI_IF_CFG_VREF_POL_MASK BIT(18)
> +#define CSI_IF_CFG_VREF_POL_NEGATIVE ((0 << 18) & CSI_IF_CFG_VREF_POL_MASK)
> +#define CSI_IF_CFG_VREF_POL_POSITIVE ((1 << 18) & CSI_IF_CFG_VREF_POL_MASK)
> +#define CSI_IF_CFG_HREF_POL_MASK BIT(17)
> +#define CSI_IF_CFG_HREF_POL_NEGATIVE ((0 << 17) & CSI_IF_CFG_HREF_POL_MASK)
> +#define CSI_IF_CFG_HREF_POL_POSITIVE ((1 << 17) & CSI_IF_CFG_HREF_POL_MASK)
> +#define CSI_IF_CFG_CLK_POL_MASK BIT(16)
> +#define CSI_IF_CFG_CLK_POL_RISING_EDGE ((0 << 16) & CSI_IF_CFG_CLK_POL_MASK)
> +#define CSI_IF_CFG_CLK_POL_FALLING_EDGE ((1 << 16) & CSI_IF_CFG_CLK_POL_MASK)
> +#define CSI_IF_CFG_IF_DATA_WIDTH_MASK GENMASK(10, 8)
> +#define CSI_IF_CFG_IF_DATA_WIDTH_8BIT ((0 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
> +#define CSI_IF_CFG_IF_DATA_WIDTH_10BIT ((1 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
> +#define CSI_IF_CFG_IF_DATA_WIDTH_12BIT ((2 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
> +#define CSI_IF_CFG_MIPI_IF_MASK BIT(7)
> +#define CSI_IF_CFG_MIPI_IF_CSI (0 << 7)
> +#define CSI_IF_CFG_MIPI_IF_MIPI (1 << 7)
> +#define CSI_IF_CFG_CSI_IF_MASK GENMASK(4, 0)
> +#define CSI_IF_CFG_CSI_IF_YUV422_INTLV ((0 << 0) & CSI_IF_CFG_CSI_IF_MASK)
> +#define CSI_IF_CFG_CSI_IF_YUV422_16BIT ((1 << 0) & CSI_IF_CFG_CSI_IF_MASK)
> +#define CSI_IF_CFG_CSI_IF_BT656 ((4 << 0) & CSI_IF_CFG_CSI_IF_MASK)
> +#define CSI_IF_CFG_CSI_IF_BT1120 ((5 << 0) & CSI_IF_CFG_CSI_IF_MASK)
> +
> +#define CSI_CAP_REG 0x8
> +#define CSI_CAP_CH0_CAP_MASK_MASK GENMASK(5, 2)
> +#define CSI_CAP_CH0_CAP_MASK(count) ((count << 2) & CSI_CAP_CH0_CAP_MASK_MASK)
> +#define CSI_CAP_CH0_VCAP_ON BIT(1)
> +#define CSI_CAP_CH0_SCAP_ON BIT(0)
> +
> +#define CSI_SYNC_CNT_REG 0xc
> +#define CSI_FIFO_THRS_REG 0x10
> +#define CSI_BT656_HEAD_CFG_REG 0x14
> +#define CSI_PTN_LEN_REG 0x30
> +#define CSI_PTN_ADDR_REG 0x34
> +#define CSI_VER_REG 0x3c
> +
> +#define CSI_CH_CFG_REG 0x44
> +#define CSI_CH_CFG_INPUT_FMT_MASK GENMASK(23, 20)
> +#define CSI_CH_CFG_INPUT_FMT(fmt) ((fmt << 20) & CSI_CH_CFG_INPUT_FMT_MASK)
> +#define CSI_CH_CFG_OUTPUT_FMT_MASK GENMASK(19, 16)
> +#define CSI_CH_CFG_OUTPUT_FMT(fmt) ((fmt << 16) & CSI_CH_CFG_OUTPUT_FMT_MASK)
> +#define CSI_CH_CFG_VFLIP_EN BIT(13)
> +#define CSI_CH_CFG_HFLIP_EN BIT(12)
> +#define CSI_CH_CFG_FIELD_SEL_MASK GENMASK(11, 10)
> +#define CSI_CH_CFG_FIELD_SEL_FIELD0 ((0 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
> +#define CSI_CH_CFG_FIELD_SEL_FIELD1 ((1 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
> +#define CSI_CH_CFG_FIELD_SEL_BOTH ((2 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
> +#define CSI_CH_CFG_INPUT_SEQ_MASK GENMASK(9, 8)
> +#define CSI_CH_CFG_INPUT_SEQ(seq) ((seq << 8) & CSI_CH_CFG_INPUT_SEQ_MASK)
> +
> +#define CSI_CH_SCALE_REG 0x4c
> +#define CSI_CH_SCALE_QUART_EN BIT(0)
> +
> +#define CSI_CH_F0_BUFA_REG 0x50
> +
> +#define CSI_CH_F1_BUFA_REG 0x58
> +
> +#define CSI_CH_F2_BUFA_REG 0x60
> +
> +#define CSI_CH_STA_REG 0x6c
> +#define CSI_CH_STA_FIELD_STA_MASK BIT(2)
> +#define CSI_CH_STA_FIELD_STA_FIELD0 ((0 << 2) & CSI_CH_STA_FIELD_STA_MASK)
> +#define CSI_CH_STA_FIELD_STA_FIELD1 ((1 << 2) & CSI_CH_STA_FIELD_STA_MASK)
> +#define CSI_CH_STA_VCAP_STA BIT(1)
> +#define CSI_CH_STA_SCAP_STA BIT(0)
> +
> +#define CSI_CH_INT_EN_REG 0x70
> +#define CSI_CH_INT_EN_VS_INT_EN BIT(7)
> +#define CSI_CH_INT_EN_HB_OF_INT_EN BIT(6)
> +#define CSI_CH_INT_EN_MUL_ERR_INT_EN BIT(5)
> +#define CSI_CH_INT_EN_FIFO2_OF_INT_EN BIT(4)
> +#define CSI_CH_INT_EN_FIFO1_OF_INT_EN BIT(3)
> +#define CSI_CH_INT_EN_FIFO0_OF_INT_EN BIT(2)
> +#define CSI_CH_INT_EN_FD_INT_EN BIT(1)
> +#define CSI_CH_INT_EN_CD_INT_EN BIT(0)
> +
> +#define CSI_CH_INT_STA_REG 0x74
> +#define CSI_CH_INT_STA_VS_PD BIT(7)
> +#define CSI_CH_INT_STA_HB_OF_PD BIT(6)
> +#define CSI_CH_INT_STA_MUL_ERR_PD BIT(5)
> +#define CSI_CH_INT_STA_FIFO2_OF_PD BIT(4)
> +#define CSI_CH_INT_STA_FIFO1_OF_PD BIT(3)
> +#define CSI_CH_INT_STA_FIFO0_OF_PD BIT(2)
> +#define CSI_CH_INT_STA_FD_PD BIT(1)
> +#define CSI_CH_INT_STA_CD_PD BIT(0)
> +
> +#define CSI_CH_FLD1_VSIZE_REG 0x78
> +
> +#define CSI_CH_HSIZE_REG 0x80
> +#define CSI_CH_HSIZE_HOR_LEN_MASK GENMASK(28, 16)
> +#define CSI_CH_HSIZE_HOR_LEN(len) ((len << 16) & CSI_CH_HSIZE_HOR_LEN_MASK)
> +#define CSI_CH_HSIZE_HOR_START_MASK GENMASK(12, 0)
> +#define CSI_CH_HSIZE_HOR_START(start) ((start << 0) & CSI_CH_HSIZE_HOR_START_MASK)
> +
> +#define CSI_CH_VSIZE_REG 0x84
> +#define CSI_CH_VSIZE_VER_LEN_MASK GENMASK(28, 16)
> +#define CSI_CH_VSIZE_VER_LEN(len) ((len << 16) & CSI_CH_VSIZE_VER_LEN_MASK)
> +#define CSI_CH_VSIZE_VER_START_MASK GENMASK(12, 0)
> +#define CSI_CH_VSIZE_VER_START(start) ((start << 0) & CSI_CH_VSIZE_VER_START_MASK)
> +
> +#define CSI_CH_BUF_LEN_REG 0x88
> +#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK GENMASK(29, 16)
> +#define CSI_CH_BUF_LEN_BUF_LEN_C(len) ((len << 16) & CSI_CH_BUF_LEN_BUF_LEN_C_MASK)
> +#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK GENMASK(13, 0)
> +#define CSI_CH_BUF_LEN_BUF_LEN_Y(len) ((len << 0) & CSI_CH_BUF_LEN_BUF_LEN_Y_MASK)
> +
> +#define CSI_CH_FLIP_SIZE_REG 0x8c
> +#define CSI_CH_FLIP_SIZE_VER_LEN_MASK GENMASK(28, 16)
> +#define CSI_CH_FLIP_SIZE_VER_LEN(len) ((len << 16) & CSI_CH_FLIP_SIZE_VER_LEN_MASK)
> +#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK GENMASK(12, 0)
> +#define CSI_CH_FLIP_SIZE_VALID_LEN(len) ((len << 0) & CSI_CH_FLIP_SIZE_VALID_LEN_MASK)
> +
> +#define CSI_CH_FRM_CLK_CNT_REG 0x90
> +#define CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94
> +#define CSI_CH_FIFO_STAT_REG 0x98
> +#define CSI_CH_PCLK_STAT_REG 0x9c
> +
> +/*
> + * csi input data format
> + */
> +enum csi_input_fmt {
> + CSI_INPUT_FORMAT_RAW = 0,
> + CSI_INPUT_FORMAT_YUV422 = 3,
> + CSI_INPUT_FORMAT_YUV420 = 4,
> +};
> +
> +/*
> + * csi output data format
> + */
> +enum csi_output_fmt {
> + /* only when input format is RAW */
> + CSI_FIELD_RAW_8 = 0,
> + CSI_FIELD_RAW_10 = 1,
> + CSI_FIELD_RAW_12 = 2,
> + CSI_FIELD_RGB565 = 4,
> + CSI_FIELD_RGB888 = 5,
> + CSI_FIELD_PRGB888 = 6,
> + CSI_FRAME_RAW_8 = 8,
> + CSI_FRAME_RAW_10 = 9,
> + CSI_FRAME_RAW_12 = 10,
> + CSI_FRAME_RGB565 = 12,
> + CSI_FRAME_RGB888 = 13,
> + CSI_FRAME_PRGB888 = 14,
> +
> + /* only when input format is YUV422 */
> + CSI_FIELD_PLANAR_YUV422 = 0,
> + CSI_FIELD_PLANAR_YUV420 = 1,
> + CSI_FRAME_PLANAR_YUV420 = 2,
> + CSI_FRAME_PLANAR_YUV422 = 3,
> + CSI_FIELD_UV_CB_YUV422 = 4,
> + CSI_FIELD_UV_CB_YUV420 = 5,
> + CSI_FRAME_UV_CB_YUV420 = 6,
> + CSI_FRAME_UV_CB_YUV422 = 7,
> + CSI_FIELD_MB_YUV422 = 8,
> + CSI_FIELD_MB_YUV420 = 9,
> + CSI_FRAME_MB_YUV420 = 10,
> + CSI_FRAME_MB_YUV422 = 11,
> + CSI_FIELD_UV_CB_YUV422_10 = 12,
> + CSI_FIELD_UV_CB_YUV420_10 = 13,
> +};
> +
> +/*
> + * csi YUV input data sequence
> + */
> +enum csi_input_seq {
> + /* only when input format is YUV422 */
> + CSI_INPUT_SEQ_YUYV = 0,
> + CSI_INPUT_SEQ_YVYU,
> + CSI_INPUT_SEQ_UYVY,
> + CSI_INPUT_SEQ_VYUY,
> +};
> +
> +#endif /* __SUN6I_CSI_REG_H__ */
> diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
> new file mode 100644
> index 0000000..2b683ac
> --- /dev/null
> +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
> @@ -0,0 +1,752 @@
> +/*
> + * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
> + * All rights reserved.
> + * Author: Yong Deng <yong.deng@magewell.com>
> + *
> + * 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.
> + */
> +
> +#include <linux/of.h>
> +
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#include "sun6i_csi.h"
> +#include "sun6i_video.h"
> +
> +struct sun6i_csi_buffer {
> + struct vb2_v4l2_buffer vb;
> + struct list_head list;
> +
> + dma_addr_t dma_addr;
> + bool queued_to_csi;
> +};
> +
> +static struct sun6i_csi_format *
> +find_format_by_pixformat(struct sun6i_video *video, unsigned int pixformat)
> +{
> + unsigned int num_formats = video->num_formats;
> + struct sun6i_csi_format *fmt;
> + unsigned int i;
> +
> + for (i = 0; i < num_formats; i++) {
> + fmt = &video->formats[i];
> + if (fmt->pixformat == pixformat)
> + return fmt;
> + }
> +
> + return NULL;
> +}
> +
> +static struct v4l2_subdev *
> +sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad)
> +{
> + struct media_pad *remote;
> +
> + remote = media_entity_remote_pad(&video->pad);
> +
> + if (!remote || !is_media_entity_v4l2_subdev(remote->entity))
> + return NULL;
> +
> + if (pad)
> + *pad = remote->index;
> +
> + return media_entity_to_v4l2_subdev(remote->entity);
> +}
> +
> +static int sun6i_video_queue_setup(struct vb2_queue *vq,
> + unsigned int *nbuffers, unsigned int *nplanes,
> + unsigned int sizes[],
> + struct device *alloc_devs[])
> +{
> + struct sun6i_video *video = vb2_get_drv_priv(vq);
> + unsigned int size = video->fmt.fmt.pix.sizeimage;
> +
> + if (*nplanes)
> + return sizes[0] < size ? -EINVAL : 0;
> +
> + *nplanes = 1;
> + sizes[0] = size;
> +
> + return 0;
> +}
> +
> +static int sun6i_video_buffer_prepare(struct vb2_buffer *vb)
> +{
> + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> + struct sun6i_csi_buffer *buf =
> + container_of(vbuf, struct sun6i_csi_buffer, vb);
> + struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
> + unsigned long size = video->fmt.fmt.pix.sizeimage;
> +
> + if (vb2_plane_size(vb, 0) < size) {
> + v4l2_err(video->vdev.v4l2_dev, "buffer too small (%lu < %lu)\n",
> + vb2_plane_size(vb, 0), size);
> + return -EINVAL;
> + }
> +
> + vb2_set_plane_payload(vb, 0, size);
> +
> + buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
> +
> + vbuf->field = video->fmt.fmt.pix.field;
> +
> + return 0;
> +}
> +
> +static int sun6i_pipeline_set_stream(struct sun6i_video *video, bool enable)
> +{
> + struct media_entity *entity;
> + struct media_pad *pad;
> + struct v4l2_subdev *subdev;
> + int ret;
> +
> + entity = &video->vdev.entity;
> + while (1) {
> + pad = &entity->pads[0];
> + if (!(pad->flags & MEDIA_PAD_FL_SINK))
> + break;
> +
> + pad = media_entity_remote_pad(pad);
> + if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
> + break;
> +
> + entity = pad->entity;
> + subdev = media_entity_to_v4l2_subdev(entity);
> +
> + ret = v4l2_subdev_call(subdev, video, s_stream, enable);
> + if (enable && ret < 0 && ret != -ENOIOCTLCMD)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count)
> +{
> + struct sun6i_video *video = vb2_get_drv_priv(vq);
> + struct sun6i_csi_buffer *buf;
> + struct sun6i_csi_buffer *next_buf;
> + struct sun6i_csi_config config;
> + unsigned long flags;
> + int ret;
> +
> + video->sequence = 0;
> +
> + ret = media_pipeline_start(&video->vdev.entity, &video->vdev.pipe);
> + if (ret < 0)
> + goto clear_dma_queue;
> +
> + config.pixelformat = video->fmt.fmt.pix.pixelformat;
> + config.code = video->current_fmt->mbus_code;
> + config.field = video->fmt.fmt.pix.field;
> + config.width = video->fmt.fmt.pix.width;
> + config.height = video->fmt.fmt.pix.height;
> +
> + ret = sun6i_csi_update_config(video->csi, &config);
> + if (ret < 0)
> + goto stop_media_pipeline;
> +
> + spin_lock_irqsave(&video->dma_queue_lock, flags);
> +
> + buf = list_first_entry(&video->dma_queue,
> + struct sun6i_csi_buffer, list);
> + buf->queued_to_csi = true;
> + sun6i_csi_update_buf_addr(video->csi, buf->dma_addr);
> +
> + sun6i_csi_set_stream(video->csi, true);
> +
> + /*
> + * CSI will lookup the next dma buffer for next frame before the
> + * the current frame done IRQ triggered. This is not documented
> + * but reported by Ondřej Jirman.
> + * The BSP code has workaround for this too. It skip to mark the
> + * first buffer as frame done for VB2 and pass the second buffer
> + * to CSI in the first frame done ISR call. Then in second frame
> + * done ISR call, it mark the first buffer as frame done for VB2
> + * and pass the third buffer to CSI. And so on. The bad thing is
> + * that the first buffer will be written twice and the first frame
> + * is dropped even the queued buffer is sufficient.
> + * So, I make some improvement here. Pass the next buffer to CSI
> + * just follow starting the CSI. In this case, the first frame
> + * will be stored in first buffer, second frame in second buffer.
> + * This mothed is used to avoid dropping the first frame, it
/s/mothed/method
> + * would also drop frame when lacking of queued buffer.
> + */
> + next_buf = list_next_entry(buf, list);
> + next_buf->queued_to_csi = true;
> + sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr);
> +
> + spin_unlock_irqrestore(&video->dma_queue_lock, flags);
> +
> + ret = sun6i_pipeline_set_stream(video, true);
> + if (ret < 0)
> + goto stop_csi_stream;
> +
> + return 0;
> +
> +stop_csi_stream:
> + sun6i_csi_set_stream(video->csi, false);
> +stop_media_pipeline:
> + media_pipeline_stop(&video->vdev.entity);
> +clear_dma_queue:
> + spin_lock_irqsave(&video->dma_queue_lock, flags);
> + list_for_each_entry(buf, &video->dma_queue, list)
> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
> + INIT_LIST_HEAD(&video->dma_queue);
> + spin_unlock_irqrestore(&video->dma_queue_lock, flags);
> +
> + return ret;
> +}
> +
> +static void sun6i_video_stop_streaming(struct vb2_queue *vq)
> +{
> + struct sun6i_video *video = vb2_get_drv_priv(vq);
> + unsigned long flags;
> + struct sun6i_csi_buffer *buf;
> +
> + sun6i_pipeline_set_stream(video, false);
> +
> + sun6i_csi_set_stream(video->csi, false);
> +
> + media_pipeline_stop(&video->vdev.entity);
> +
> + /* Release all active buffers */
> + spin_lock_irqsave(&video->dma_queue_lock, flags);
> + list_for_each_entry(buf, &video->dma_queue, list)
> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> + INIT_LIST_HEAD(&video->dma_queue);
> + spin_unlock_irqrestore(&video->dma_queue_lock, flags);
> +}
> +
> +static void sun6i_video_buffer_queue(struct vb2_buffer *vb)
> +{
> + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> + struct sun6i_csi_buffer *buf =
> + container_of(vbuf, struct sun6i_csi_buffer, vb);
> + struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&video->dma_queue_lock, flags);
> + buf->queued_to_csi = false;
> + list_add_tail(&buf->list, &video->dma_queue);
> + spin_unlock_irqrestore(&video->dma_queue_lock, flags);
> +}
> +
> +void sun6i_video_frame_done(struct sun6i_video *video)
> +{
> + struct sun6i_csi_buffer *buf;
> + struct sun6i_csi_buffer *next_buf;
> + struct vb2_v4l2_buffer *vbuf;
> +
> + spin_lock(&video->dma_queue_lock);
> +
> + buf = list_first_entry(&video->dma_queue,
> + struct sun6i_csi_buffer, list);
> + if (list_is_last(&buf->list, &video->dma_queue)) {
> + dev_dbg(video->csi->dev, "Frame droped!\n");
> + goto unlock;
> + }
> +
> + next_buf = list_next_entry(buf, list);
> + /* If a new buffer (#next_buf) had not been queued to CSI, the old
> + * buffer (#buf) is still holding by CSI for storing the next
> + * frame. So, we queue a new buffer (#next_buf) to CSI then wait
> + * for next ISR call.
> + */
> + if (!next_buf->queued_to_csi) {
> + next_buf->queued_to_csi = true;
> + sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr);
> + dev_dbg(video->csi->dev, "Frame droped!\n");
> + goto unlock;
> + }
> +
> + list_del(&buf->list);
> + vbuf = &buf->vb;
> + vbuf->vb2_buf.timestamp = ktime_get_ns();
> + vbuf->sequence = video->sequence;
> + vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE);
> +
> + /* Prepare buffer for next frame but one. */
> + if (!list_is_last(&next_buf->list, &video->dma_queue)) {
> + next_buf = list_next_entry(next_buf, list);
> + next_buf->queued_to_csi = true;
> + sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr);
> + } else {
> + dev_dbg(video->csi->dev, "Next frame will be droped!\n");
s/droped/dropped
> + }
> +
> +unlock:
> + video->sequence++;
> + spin_unlock(&video->dma_queue_lock);
> +}
> +
> +static struct vb2_ops sun6i_csi_vb2_ops = {
> + .queue_setup = sun6i_video_queue_setup,
> + .wait_prepare = vb2_ops_wait_prepare,
> + .wait_finish = vb2_ops_wait_finish,
> + .buf_prepare = sun6i_video_buffer_prepare,
> + .start_streaming = sun6i_video_start_streaming,
> + .stop_streaming = sun6i_video_stop_streaming,
> + .buf_queue = sun6i_video_buffer_queue,
> +};
> +
> +static int vidioc_querycap(struct file *file, void *priv,
> + struct v4l2_capability *cap)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> +
> + strlcpy(cap->driver, "sun6i-video", sizeof(cap->driver));
> + strlcpy(cap->card, video->vdev.name, sizeof(cap->card));
> + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
> + video->csi->dev->of_node->name);
> +
> + return 0;
> +}
> +
> +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_fmtdesc *f)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> + u32 index = f->index;
> +
> + if (index >= video->num_formats)
> + return -EINVAL;
> +
> + f->pixelformat = video->formats[index].pixformat;
> +
> + return 0;
> +}
> +
> +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_format *fmt)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> +
> + *fmt = video->fmt;
> +
> + return 0;
> +}
> +
> +static int sun6i_video_try_fmt(struct sun6i_video *video, struct v4l2_format *f,
> + struct sun6i_csi_format **current_fmt)
> +{
> + struct sun6i_csi_format *csi_fmt;
> + struct v4l2_pix_format *pixfmt = &f->fmt.pix;
> + struct v4l2_subdev_format format;
> + struct v4l2_subdev *subdev;
> + u32 pad;
> + int ret;
> +
> + subdev = sun6i_video_remote_subdev(video, &pad);
> + if (subdev == NULL)
> + return -ENXIO;
> +
> + csi_fmt = find_format_by_pixformat(video, pixfmt->pixelformat);
> + if (csi_fmt == NULL) {
> + if (video->num_formats > 0) {
> + csi_fmt = &video->formats[0];
> + pixfmt->pixelformat = csi_fmt->pixformat;
> + } else
> + return -EINVAL;
> + }
> +
> + format.pad = pad;
> + format.which = V4L2_SUBDEV_FORMAT_TRY;
> + v4l2_fill_mbus_format(&format.format, pixfmt, csi_fmt->mbus_code);
> + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &format);
> + if (ret)
> + return ret;
> +
> + v4l2_fill_pix_format(pixfmt, &format.format);
> +
> + pixfmt->bytesperline = (pixfmt->width * csi_fmt->bpp) >> 3;
> + pixfmt->sizeimage = (pixfmt->width * csi_fmt->bpp * pixfmt->height) / 8;
> +
> + if (current_fmt)
> + *current_fmt = csi_fmt;
> +
> + return 0;
> +}
> +
> +static int sun6i_video_set_fmt(struct sun6i_video *video, struct v4l2_format *f)
> +{
> + struct v4l2_subdev_format format;
> + struct sun6i_csi_format *current_fmt;
> + struct v4l2_subdev *subdev;
> + u32 pad;
> + int ret;
> +
> + subdev = sun6i_video_remote_subdev(video, &pad);
> + if (subdev == NULL)
> + return -ENXIO;
> +
> + ret = sun6i_video_try_fmt(video, f, ¤t_fmt);
> + if (ret)
> + return ret;
> +
> + format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> + v4l2_fill_mbus_format(&format.format, &f->fmt.pix,
> + current_fmt->mbus_code);
> + ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &format);
> + if (ret < 0)
> + return ret;
> +
> + video->fmt = *f;
> + video->current_fmt = current_fmt;
> +
> + return 0;
> +}
> +
> +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> +
> + if (vb2_is_busy(&video->vb2_vidq))
> + return -EBUSY;
> +
> + return sun6i_video_set_fmt(video, f);
> +}
> +
> +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> +
> + return sun6i_video_try_fmt(video, f, NULL);
> +}
> +
> +static int vidioc_enum_input(struct file *file, void *fh,
> + struct v4l2_input *inp)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> + struct v4l2_subdev *subdev;
> + u32 pad;
> + int ret;
> +
> + if (inp->index != 0)
> + return -EINVAL;
> +
> + subdev = sun6i_video_remote_subdev(video, &pad);
> + if (subdev == NULL)
> + return -ENXIO;
> +
> + ret = v4l2_subdev_call(subdev, video, g_input_status, &inp->status);
> + if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
> + return ret;
> +
> + inp->type = V4L2_INPUT_TYPE_CAMERA;
> +
> + if (v4l2_subdev_has_op(subdev, pad, dv_timings_cap)) {
> + inp->capabilities = V4L2_IN_CAP_DV_TIMINGS;
> + inp->std = 0;
> + } else {
> + inp->capabilities = 0;
> + inp->std = 0;
> + }
You can set std and capabilities initially to 0 and get rid of else.
> +
> + strlcpy(inp->name, subdev->name, sizeof(inp->name));
> +
> + return 0;
> +}
> +
> +static int vidioc_g_input(struct file *file, void *fh, unsigned int *i)
> +{
> + *i = 0;
> +
> + return 0;
> +}
> +
> +static int vidioc_s_input(struct file *file, void *fh, unsigned int i)
> +{
> + if (i != 0)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = {
> + .vidioc_querycap = vidioc_querycap,
> + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
> + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
> + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
> + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
> +
> + .vidioc_enum_input = vidioc_enum_input,
> + .vidioc_s_input = vidioc_s_input,
> + .vidioc_g_input = vidioc_g_input,
> +
> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> + .vidioc_querybuf = vb2_ioctl_querybuf,
> + .vidioc_qbuf = vb2_ioctl_qbuf,
> + .vidioc_expbuf = vb2_ioctl_expbuf,
> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
> + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> + .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamoff = vb2_ioctl_streamoff,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 file operations
> + */
> +static int sun6i_video_open(struct file *file)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> + int ret;
> +
> + if (mutex_lock_interruptible(&video->lock))
> + return -ERESTARTSYS;
> +
> + ret = v4l2_fh_open(file);
> + if (ret < 0)
> + goto unlock;
> +
> + ret = v4l2_pipeline_pm_use(&video->vdev.entity, 1);
> + if (ret < 0)
> + goto fh_release;
> +
> + /* check if already powered */
> + if (!v4l2_fh_is_singular_file(file))
> + goto unlock;
> +
> + ret = sun6i_csi_set_power(video->csi, true);
> + if (ret < 0)
> + goto fh_release;
> +
> + mutex_unlock(&video->lock);
> + return 0;
> +
> +fh_release:
> + v4l2_fh_release(file);
> +unlock:
> + mutex_unlock(&video->lock);
> + return ret;
> +}
> +
> +static int sun6i_video_close(struct file *file)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> + bool last_fh;
> +
> + mutex_lock(&video->lock);
> +
> + last_fh = v4l2_fh_is_singular_file(file);
> +
> + _vb2_fop_release(file, NULL);
> +
> + v4l2_pipeline_pm_use(&video->vdev.entity, 0);
> +
> + if (last_fh)
> + sun6i_csi_set_power(video->csi, false);
> +
> + mutex_unlock(&video->lock);
> +
> + return 0;
> +}
> +
> +static const struct v4l2_file_operations sun6i_video_fops = {
> + .owner = THIS_MODULE,
> + .open = sun6i_video_open,
> + .release = sun6i_video_close,
> + .unlocked_ioctl = video_ioctl2,
> + .mmap = vb2_fop_mmap,
> + .poll = vb2_fop_poll
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Media Operations
> + */
> +static int sun6i_video_formats_init(struct sun6i_video *video)
> +{
> + struct v4l2_subdev_mbus_code_enum mbus_code = { 0 };
> + struct sun6i_csi *csi = video->csi;
> + struct v4l2_format format;
> + struct v4l2_subdev *subdev;
> + u32 pad;
> + const u32 *pixformats;
> + int pixformat_count = 0;
> + u32 subdev_codes[32]; /* subdev format codes, 32 should be enough */
> + int codes_count = 0;
> + int num_fmts = 0;
> + int i, j;
> +
> + subdev = sun6i_video_remote_subdev(video, &pad);
> + if (subdev == NULL)
> + return -ENXIO;
> +
> + /* Get supported pixformats of CSI */
> + pixformat_count = sun6i_csi_get_supported_pixformats(csi, &pixformats);
> + if (pixformat_count <= 0)
> + return -ENXIO;
> +
> + /* Get subdev formats codes */
> + mbus_code.pad = pad;
> + mbus_code.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> + while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL,
> + &mbus_code)) {
> + if (codes_count >= ARRAY_SIZE(subdev_codes)) {
> + dev_warn(video->csi->dev,
> + "subdev_codes array is full!\n");
> + break;
> + }
> + subdev_codes[codes_count] = mbus_code.code;
> + codes_count++;
> + mbus_code.index++;
> + }
> +
> + if (!codes_count)
> + return -ENXIO;
> +
> + /* Get supported formats count */
> + for (i = 0; i < codes_count; i++) {
> + for (j = 0; j < pixformat_count; j++) {
> + if (!sun6i_csi_is_format_support(csi, pixformats[j],
> + subdev_codes[i])) {
> + continue;
> + }
> + num_fmts++;
> + }
> + }
> +
> + if (!num_fmts)
> + return -ENXIO;
> +
> + video->num_formats = num_fmts;
> + video->formats = devm_kcalloc(video->csi->dev, num_fmts,
> + sizeof(struct sun6i_csi_format), GFP_KERNEL);
> + if (!video->formats)
> + return -ENOMEM;
> +
> + /* Get supported formats */
> + num_fmts = 0;
> + for (i = 0; i < codes_count; i++) {
> + for (j = 0; j < pixformat_count; j++) {
> + if (!sun6i_csi_is_format_support(csi, pixformats[j],
> + subdev_codes[i])) {
> + continue;
> + }
> +
> + video->formats[num_fmts].pixformat = pixformats[j];
> + video->formats[num_fmts].mbus_code = subdev_codes[i];
> + video->formats[num_fmts].bpp =
> + sun6i_csi_get_bpp(pixformats[j]);
> + num_fmts++;
> + }
> + }
> +
> + /* setup default format */
> + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> + format.fmt.pix.width = 1280;
> + format.fmt.pix.height = 720;
> + format.fmt.pix.pixelformat = video->formats[0].pixformat;
> + sun6i_video_set_fmt(video, &format);
> +
> + return 0;
> +}
> +
> +static int sun6i_video_link_setup(struct media_entity *entity,
> + const struct media_pad *local,
> + const struct media_pad *remote, u32 flags)
> +{
> + struct video_device *vdev = media_entity_to_video_device(entity);
> + struct sun6i_video *video = video_get_drvdata(vdev);
> +
> + if (WARN_ON(video == NULL))
> + return 0;
> +
> + return sun6i_video_formats_init(video);
> +}
> +
> +static const struct media_entity_operations sun6i_video_media_ops = {
> + .link_setup = sun6i_video_link_setup,
> +};
> +
> +int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
> + const char *name)
> +{
> + struct video_device *vdev = &video->vdev;
> + struct vb2_queue *vidq = &video->vb2_vidq;
> + int ret;
> +
> + video->csi = csi;
> +
> + /* Initialize the media entity... */
> + video->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
> + vdev->entity.ops = &sun6i_video_media_ops;
> + ret = media_entity_pads_init(&vdev->entity, 1, &video->pad);
> + if (ret < 0)
> + return ret;
> +
> + mutex_init(&video->lock);
> +
> + INIT_LIST_HEAD(&video->dma_queue);
> + spin_lock_init(&video->dma_queue_lock);
> +
> + video->sequence = 0;
> + video->num_formats = 0;
> +
> + /* Initialize videobuf2 queue */
> + vidq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> + vidq->io_modes = VB2_MMAP | VB2_DMABUF;
> + vidq->drv_priv = video;
> + vidq->buf_struct_size = sizeof(struct sun6i_csi_buffer);
> + vidq->ops = &sun6i_csi_vb2_ops;
> + vidq->mem_ops = &vb2_dma_contig_memops;
> + vidq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> + vidq->lock = &video->lock;
> + /* Make sure non-dropped frame */
> + vidq->min_buffers_needed = 3;
> + vidq->dev = csi->dev;
> +
> + ret = vb2_queue_init(vidq);
> + if (ret) {
> + v4l2_err(&csi->v4l2_dev, "vb2_queue_init failed: %d\n", ret);
> + goto error;
> + }
> +
> + /* Register video device */
> + strlcpy(vdev->name, name, sizeof(vdev->name));
> + vdev->release = video_device_release_empty;
> + vdev->fops = &sun6i_video_fops;
> + vdev->ioctl_ops = &sun6i_video_ioctl_ops;
> + vdev->vfl_type = VFL_TYPE_GRABBER;
> + vdev->vfl_dir = VFL_DIR_RX;
> + vdev->v4l2_dev = &csi->v4l2_dev;
> + vdev->queue = vidq;
> + vdev->lock = &video->lock;
> + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
> + video_set_drvdata(vdev, video);
> +
> + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
> + if (ret < 0) {
> + v4l2_err(&csi->v4l2_dev,
> + "video_register_device failed: %d\n", ret);
> + goto error;
> + }
> +
> + return 0;
> +
> +error:
> + sun6i_video_cleanup(video);
> + return ret;
> +}
> +
> +void sun6i_video_cleanup(struct sun6i_video *video)
> +{
> + if (video_is_registered(&video->vdev))
> + video_unregister_device(&video->vdev);
> +
> + media_entity_cleanup(&video->vdev.entity);
> +}
> diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h
> new file mode 100644
> index 0000000..f20928a
> --- /dev/null
> +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h
> @@ -0,0 +1,60 @@
> +/*
> + * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
> + *
> + * 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.
> + *
> + * 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.
> + */
> +
> +#ifndef __SUN6I_VIDEO_H__
> +#define __SUN6I_VIDEO_H__
> +
> +#include <media/v4l2-dev.h>
> +#include <media/videobuf2-core.h>
> +
> +/*
> + * struct sun6i_csi_format - CSI media bus format information
> + * @pixformat: V4l2 pixformat for this format
> + * @mbus_code: V4L2 media bus format code.
> + * @bpp: Bytes per pixel (when stored in memory)
> + */
> +struct sun6i_csi_format {
> + u32 pixformat;
> + u32 mbus_code;
> + u8 bpp;
> +};
> +
> +struct sun6i_csi;
> +
> +struct sun6i_video {
> + struct video_device vdev;
> + struct media_pad pad;
> + struct sun6i_csi *csi;
> +
> + struct mutex lock;
> +
> + struct vb2_queue vb2_vidq;
> + spinlock_t dma_queue_lock;
> + struct list_head dma_queue;
> +
> + unsigned int sequence;
> +
> + struct sun6i_csi_format *formats;
> + unsigned int num_formats;
> + struct sun6i_csi_format *current_fmt;
> + struct v4l2_format fmt;
> +};
> +
> +int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
> + const char *name);
> +void sun6i_video_cleanup(struct sun6i_video *video);
> +
> +void sun6i_video_frame_done(struct sun6i_video *video);
> +
> +#endif /* __SUN6I_VIDEO_H__ */
> --
> 1.8.3.1
>
> --
> You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply
* Re: [linux-sunxi] [PATCH v4 1/2] dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI)
From: Priit Laes @ 2017-12-22 10:00 UTC (permalink / raw)
To: Yong Deng
Cc: Maxime Ripard, Mauro Carvalho Chehab, Rob Herring, Mark Rutland,
Chen-Yu Tsai, David S. Miller, Greg Kroah-Hartman, Randy Dunlap,
Hans Verkuil, Stanimir Varbanov, Hugues Fruchet, Yannick Fertre,
Philipp Zabel, Arnd Bergmann, Benjamin Gaignard,
Ramesh Shanmugasundaram, Sakari Ailus, Rick Chang, linux-media,
devicetree, linux-arm-kernel, linux-kernel, linux-sunxi
In-Reply-To: <1513935689-35415-1-git-send-email-yong.deng@magewell.com>
On Fri, Dec 22, 2017 at 05:41:29PM +0800, Yong Deng wrote:
> Add binding documentation for Allwinner V3s CSI.
>
> Signed-off-by: Yong Deng <yong.deng@magewell.com>
> ---
> .../devicetree/bindings/media/sun6i-csi.txt | 51 ++++++++++++++++++++++
> 1 file changed, 51 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/sun6i-csi.txt
>
> diff --git a/Documentation/devicetree/bindings/media/sun6i-csi.txt b/Documentation/devicetree/bindings/media/sun6i-csi.txt
> new file mode 100644
> index 0000000..b5bfe3f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/sun6i-csi.txt
> @@ -0,0 +1,51 @@
> +Allwinner V3s Camera Sensor Interface
> +------------------------------
Not sure whether syntax for these files is proper reStructuredText/Markdown,
but the underline-ish style expects the title and underline having same length.
> +
> +Required properties:
> + - compatible: value must be "allwinner,sun8i-v3s-csi"
> + - reg: base address and size of the memory-mapped region.
> + - interrupts: interrupt associated to this IP
> + - clocks: phandles to the clocks feeding the CSI
> + * bus: the CSI interface clock
> + * mod: the CSI module clock
> + * ram: the CSI DRAM clock
> + - clock-names: the clock names mentioned above
> + - resets: phandles to the reset line driving the CSI
> +
> +- ports: A ports node with endpoint definitions as defined in
> + Documentation/devicetree/bindings/media/video-interfaces.txt.
> + Currently, the driver only support the parallel interface. So, a single port
^^ supports
> + node with one endpoint and parallel bus is supported.
> +
> +Example:
> +
> + csi1: csi@1cb4000 {
> + compatible = "allwinner,sun8i-v3s-csi";
> + reg = <0x01cb4000 0x1000>;
> + interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&ccu CLK_BUS_CSI>,
> + <&ccu CLK_CSI1_SCLK>,
> + <&ccu CLK_DRAM_CSI>;
> + clock-names = "bus", "mod", "ram";
> + resets = <&ccu RST_BUS_CSI>;
> +
> + port {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + /* Parallel bus endpoint */
> + csi1_ep: endpoint {
> + remote-endpoint = <&adv7611_ep>;
> + bus-width = <16>;
> + data-shift = <0>;
> +
> + /* If hsync-active/vsync-active are missing,
> + embedded BT.656 sync is used */
> + hsync-active = <0>; /* Active low */
> + vsync-active = <0>; /* Active low */
> + data-active = <1>; /* Active high */
> + pclk-sample = <1>; /* Rising */
> + };
> + };
> + };
> +
> --
> 1.8.3.1
>
> --
> You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply
* [PATCH v4 2/2] media: V3s: Add support for Allwinner CSI.
From: Yong Deng @ 2017-12-22 9:47 UTC (permalink / raw)
To: Maxime Ripard
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
David S. Miller, Greg Kroah-Hartman, Randy Dunlap, Hans Verkuil,
Stanimir Varbanov, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Arnd Bergmann, Benjamin Gaignard, Ramesh Shanmugasundaram,
Sakari Ailus, Rick Chang, linux-media, devicetree,
linux-arm-kernel, linux-kernel, linux-sunxi, Yong Deng
Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
and CSI1 is used for parallel interface. This is not documented in
datasheet but by testing and guess.
This patch implement a v4l2 framework driver for it.
Currently, the driver only support the parallel interface. MIPI-CSI2,
ISP's support are not included in this patch.
Signed-off-by: Yong Deng <yong.deng@magewell.com>
---
MAINTAINERS | 8 +
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/sunxi/sun6i-csi/Kconfig | 9 +
drivers/media/platform/sunxi/sun6i-csi/Makefile | 3 +
drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c | 878 +++++++++++++++++++++
drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h | 147 ++++
.../media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h | 203 +++++
.../media/platform/sunxi/sun6i-csi/sun6i_video.c | 752 ++++++++++++++++++
.../media/platform/sunxi/sun6i-csi/sun6i_video.h | 60 ++
10 files changed, 2063 insertions(+)
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/Kconfig
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/Makefile
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 9501403..b792fe5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3783,6 +3783,14 @@ M: Jaya Kumar <jayakumar.alsa@gmail.com>
S: Maintained
F: sound/pci/cs5535audio/
+CSI DRIVERS FOR ALLWINNER V3s
+M: Yong Deng <yong.deng@magewell.com>
+L: linux-media@vger.kernel.org
+T: git git://linuxtv.org/media_tree.git
+S: Maintained
+F: drivers/media/platform/sunxi/sun6i-csi/
+F: Documentation/devicetree/bindings/media/sun6i-csi.txt
+
CW1200 WLAN driver
M: Solomon Peachy <pizza@shaftnet.org>
S: Maintained
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index fd0c998..41017e3 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -150,6 +150,7 @@ source "drivers/media/platform/am437x/Kconfig"
source "drivers/media/platform/xilinx/Kconfig"
source "drivers/media/platform/rcar-vin/Kconfig"
source "drivers/media/platform/atmel/Kconfig"
+source "drivers/media/platform/sunxi/sun6i-csi/Kconfig"
config VIDEO_TI_CAL
tristate "TI CAL (Camera Adaptation Layer) driver"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 003b0bb..e6e9ce7 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -97,3 +97,5 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss-8x16/
obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
obj-y += meson/
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI) += sunxi/sun6i-csi/
diff --git a/drivers/media/platform/sunxi/sun6i-csi/Kconfig b/drivers/media/platform/sunxi/sun6i-csi/Kconfig
new file mode 100644
index 0000000..314188a
--- /dev/null
+++ b/drivers/media/platform/sunxi/sun6i-csi/Kconfig
@@ -0,0 +1,9 @@
+config VIDEO_SUN6I_CSI
+ tristate "Allwinner V3s Camera Sensor Interface driver"
+ depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API && HAS_DMA
+ depends on ARCH_SUNXI || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ select REGMAP_MMIO
+ select V4L2_FWNODE
+ ---help---
+ Support for the Allwinner Camera Sensor Interface Controller on V3s.
diff --git a/drivers/media/platform/sunxi/sun6i-csi/Makefile b/drivers/media/platform/sunxi/sun6i-csi/Makefile
new file mode 100644
index 0000000..213cb6b
--- /dev/null
+++ b/drivers/media/platform/sunxi/sun6i-csi/Makefile
@@ -0,0 +1,3 @@
+sun6i-csi-y += sun6i_video.o sun6i_csi.o
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o
diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
new file mode 100644
index 0000000..8f3f2d6
--- /dev/null
+++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
@@ -0,0 +1,878 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng@magewell.com>
+ *
+ * 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.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/sched.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+
+#include "sun6i_csi.h"
+#include "sun6i_csi_reg.h"
+
+#define MODULE_NAME "sun6i-csi"
+
+struct sun6i_csi_dev {
+ struct sun6i_csi csi;
+ struct device *dev;
+
+ struct regmap *regmap;
+ struct clk *clk_mod;
+ struct clk *clk_ram;
+ struct reset_control *rstc_bus;
+
+ int planar_offset[3];
+};
+
+static const u32 supported_pixformats[] = {
+ V4L2_PIX_FMT_SBGGR8,
+ V4L2_PIX_FMT_SGBRG8,
+ V4L2_PIX_FMT_SGRBG8,
+ V4L2_PIX_FMT_SRGGB8,
+ V4L2_PIX_FMT_SBGGR10,
+ V4L2_PIX_FMT_SGBRG10,
+ V4L2_PIX_FMT_SGRBG10,
+ V4L2_PIX_FMT_SRGGB10,
+ V4L2_PIX_FMT_SBGGR12,
+ V4L2_PIX_FMT_SGBRG12,
+ V4L2_PIX_FMT_SGRBG12,
+ V4L2_PIX_FMT_SRGGB12,
+ V4L2_PIX_FMT_YUYV,
+ V4L2_PIX_FMT_YVYU,
+ V4L2_PIX_FMT_UYVY,
+ V4L2_PIX_FMT_VYUY,
+ V4L2_PIX_FMT_HM12,
+ V4L2_PIX_FMT_NV12,
+ V4L2_PIX_FMT_NV21,
+ V4L2_PIX_FMT_YUV420,
+ V4L2_PIX_FMT_YVU420,
+ V4L2_PIX_FMT_NV16,
+ V4L2_PIX_FMT_NV61,
+ V4L2_PIX_FMT_YUV422P,
+};
+
+static inline struct sun6i_csi_dev *sun6i_csi_to_dev(struct sun6i_csi *csi)
+{
+ return container_of(csi, struct sun6i_csi_dev, csi);
+}
+
+int sun6i_csi_get_supported_pixformats(struct sun6i_csi *csi,
+ const u32 **pixformats)
+{
+ if (pixformats != NULL)
+ *pixformats = supported_pixformats;
+
+ return ARRAY_SIZE(supported_pixformats);
+}
+
+/* TODO add 10&12 bit YUV, RGB support */
+bool sun6i_csi_is_format_support(struct sun6i_csi *csi,
+ u32 pixformat, u32 mbus_code)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+ /*
+ * Some video receivers have the ability to be compatible with
+ * 8bit and 16bit bus width.
+ * Identify the media bus format from device tree.
+ */
+ if ((sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_PARALLEL
+ || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_BT656)
+ && sdev->csi.v4l2_ep.bus.parallel.bus_width == 16) {
+ switch (pixformat) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_SBGGR8:
+ return (mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8);
+ case V4L2_PIX_FMT_SGBRG8:
+ return (mbus_code == MEDIA_BUS_FMT_SGBRG8_1X8);
+ case V4L2_PIX_FMT_SGRBG8:
+ return (mbus_code == MEDIA_BUS_FMT_SGRBG8_1X8);
+ case V4L2_PIX_FMT_SRGGB8:
+ return (mbus_code == MEDIA_BUS_FMT_SRGGB8_1X8);
+ case V4L2_PIX_FMT_SBGGR10:
+ return (mbus_code == MEDIA_BUS_FMT_SBGGR10_1X10);
+ case V4L2_PIX_FMT_SGBRG10:
+ return (mbus_code == MEDIA_BUS_FMT_SGBRG10_1X10);
+ case V4L2_PIX_FMT_SGRBG10:
+ return (mbus_code == MEDIA_BUS_FMT_SGRBG10_1X10);
+ case V4L2_PIX_FMT_SRGGB10:
+ return (mbus_code == MEDIA_BUS_FMT_SRGGB10_1X10);
+ case V4L2_PIX_FMT_SBGGR12:
+ return (mbus_code == MEDIA_BUS_FMT_SBGGR12_1X12);
+ case V4L2_PIX_FMT_SGBRG12:
+ return (mbus_code == MEDIA_BUS_FMT_SGBRG12_1X12);
+ case V4L2_PIX_FMT_SGRBG12:
+ return (mbus_code == MEDIA_BUS_FMT_SGRBG12_1X12);
+ case V4L2_PIX_FMT_SRGGB12:
+ return (mbus_code == MEDIA_BUS_FMT_SRGGB12_1X12);
+
+ case V4L2_PIX_FMT_YUYV:
+ return (mbus_code == MEDIA_BUS_FMT_YUYV8_2X8);
+ case V4L2_PIX_FMT_YVYU:
+ return (mbus_code == MEDIA_BUS_FMT_YVYU8_2X8);
+ case V4L2_PIX_FMT_UYVY:
+ return (mbus_code == MEDIA_BUS_FMT_UYVY8_2X8);
+ case V4L2_PIX_FMT_VYUY:
+ return (mbus_code == MEDIA_BUS_FMT_VYUY8_2X8);
+
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+int sun6i_csi_set_power(struct sun6i_csi *csi, bool enable)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ struct regmap *regmap = sdev->regmap;
+ int ret;
+
+ if (!enable) {
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+
+ clk_disable_unprepare(sdev->clk_ram);
+ clk_disable_unprepare(sdev->clk_mod);
+ reset_control_assert(sdev->rstc_bus);
+ return 0;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_mod);
+ if (ret) {
+ dev_err(sdev->dev, "Enable csi clk err %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_ram);
+ if (ret) {
+ dev_err(sdev->dev, "Enable clk_dram_csi clk err %d\n", ret);
+ return ret;
+ }
+
+ ret = reset_control_deassert(sdev->rstc_bus);
+ if (ret) {
+ dev_err(sdev->dev, "reset err %d\n", ret);
+ return ret;
+ }
+
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN);
+
+ return 0;
+}
+
+static enum csi_input_fmt get_csi_input_format(u32 mbus_code, u32 pixformat)
+{
+ /* bayer */
+ if ((mbus_code & 0xF000) == 0x3000)
+ return CSI_INPUT_FORMAT_RAW;
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ return CSI_INPUT_FORMAT_RAW;
+ }
+
+ /* not support YUV420 input format yet */
+ return CSI_INPUT_FORMAT_YUV422;
+}
+
+static enum csi_output_fmt get_csi_output_format(u32 pixformat, u32 field)
+{
+ bool buf_interlaced = false;
+
+ if (field == V4L2_FIELD_INTERLACED
+ || field == V4L2_FIELD_INTERLACED_TB
+ || field == V4L2_FIELD_INTERLACED_BT)
+ buf_interlaced = true;
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_SBGGR8:
+ case V4L2_PIX_FMT_SGBRG8:
+ case V4L2_PIX_FMT_SGRBG8:
+ case V4L2_PIX_FMT_SRGGB8:
+ return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+ case V4L2_PIX_FMT_SBGGR10:
+ case V4L2_PIX_FMT_SGBRG10:
+ case V4L2_PIX_FMT_SGRBG10:
+ case V4L2_PIX_FMT_SRGGB10:
+ return buf_interlaced ? CSI_FRAME_RAW_10 : CSI_FIELD_RAW_10;
+ case V4L2_PIX_FMT_SBGGR12:
+ case V4L2_PIX_FMT_SGBRG12:
+ case V4L2_PIX_FMT_SGRBG12:
+ case V4L2_PIX_FMT_SRGGB12:
+ return buf_interlaced ? CSI_FRAME_RAW_12 : CSI_FIELD_RAW_12;
+
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+
+ case V4L2_PIX_FMT_HM12:
+ return buf_interlaced ? CSI_FRAME_MB_YUV420 :
+ CSI_FIELD_MB_YUV420;
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ return buf_interlaced ? CSI_FRAME_UV_CB_YUV420 :
+ CSI_FIELD_UV_CB_YUV420;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ return buf_interlaced ? CSI_FRAME_PLANAR_YUV420 :
+ CSI_FIELD_PLANAR_YUV420;
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ return buf_interlaced ? CSI_FRAME_UV_CB_YUV422 :
+ CSI_FIELD_UV_CB_YUV422;
+ case V4L2_PIX_FMT_YUV422P:
+ return buf_interlaced ? CSI_FRAME_PLANAR_YUV422 :
+ CSI_FIELD_PLANAR_YUV422;
+ }
+
+ return 0;
+}
+
+static enum csi_input_seq get_csi_input_seq(u32 mbus_code, u32 pixformat)
+{
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ return CSI_INPUT_SEQ_UYVY;
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return CSI_INPUT_SEQ_VYUY;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return CSI_INPUT_SEQ_YUYV;
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return CSI_INPUT_SEQ_YVYU;
+ }
+ break;
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YVU420:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ return CSI_INPUT_SEQ_VYUY;
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return CSI_INPUT_SEQ_UYVY;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return CSI_INPUT_SEQ_YVYU;
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return CSI_INPUT_SEQ_YUYV;
+ }
+ break;
+ }
+
+ return CSI_INPUT_SEQ_YUYV;
+}
+
+static void sun6i_csi_setup_bus(struct sun6i_csi_dev *sdev)
+{
+ struct v4l2_fwnode_endpoint *endpoint = &sdev->csi.v4l2_ep;
+ unsigned char bus_width;
+ u32 flags;
+ u32 cfg;
+
+ bus_width = endpoint->bus.parallel.bus_width;
+
+ regmap_read(sdev->regmap, CSI_IF_CFG_REG, &cfg);
+
+ cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK |
+ CSI_IF_CFG_IF_DATA_WIDTH_MASK |
+ CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK |
+ CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK);
+
+ switch (endpoint->bus_type) {
+ case V4L2_MBUS_PARALLEL:
+ cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+ flags = endpoint->bus.parallel.flags;
+
+ cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT :
+ CSI_IF_CFG_CSI_IF_YUV422_INTLV;
+
+ if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+ if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
+ cfg |= CSI_IF_CFG_VREF_POL_POSITIVE;
+ if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
+ cfg |= CSI_IF_CFG_HREF_POL_POSITIVE;
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+ break;
+ case V4L2_MBUS_BT656:
+ cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+ flags = endpoint->bus.parallel.flags;
+
+ cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 :
+ CSI_IF_CFG_CSI_IF_BT656;
+
+ if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+ break;
+ default:
+ dev_warn(sdev->dev, "Unsupported bus type: %d\n",
+ endpoint->bus_type);
+ break;
+ }
+
+ switch (bus_width) {
+ case 8:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_8BIT;
+ break;
+ case 10:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_10BIT;
+ break;
+ case 12:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_12BIT;
+ break;
+ case 16: /* No need to configure DATA_WIDTH for 16bit */
+ break;
+ default:
+ dev_warn(sdev->dev, "Unsupported bus width: %d\n", bus_width);
+ break;
+ }
+
+ regmap_write(sdev->regmap, CSI_IF_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_format(struct sun6i_csi_dev *sdev)
+{
+ struct sun6i_csi *csi = &sdev->csi;
+ u32 cfg;
+ u32 val;
+
+ regmap_read(sdev->regmap, CSI_CH_CFG_REG, &cfg);
+
+ cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK |
+ CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN |
+ CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK |
+ CSI_CH_CFG_INPUT_SEQ_MASK);
+
+ val = get_csi_input_format(csi->config.code, csi->config.pixelformat);
+ cfg |= CSI_CH_CFG_INPUT_FMT(val);
+
+ val = get_csi_output_format(csi->config.pixelformat, csi->config.field);
+ cfg |= CSI_CH_CFG_OUTPUT_FMT(val);
+
+ val = get_csi_input_seq(csi->config.code, csi->config.pixelformat);
+ cfg |= CSI_CH_CFG_INPUT_SEQ(val);
+
+ if (csi->config.field == V4L2_FIELD_TOP)
+ cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0;
+ else if (csi->config.field == V4L2_FIELD_BOTTOM)
+ cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1;
+ else
+ cfg |= CSI_CH_CFG_FIELD_SEL_BOTH;
+
+ regmap_write(sdev->regmap, CSI_CH_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_window(struct sun6i_csi_dev *sdev)
+{
+ struct sun6i_csi_config *config = &sdev->csi.config;
+ u32 bytesperline_y;
+ u32 bytesperline_c;
+ int *planar_offset = sdev->planar_offset;
+ u32 width = config->width;
+ u32 height = config->height;
+ u32 hor_len = width;
+
+ switch (config->pixelformat) {
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ hor_len = width * 2;
+ break;
+ }
+
+ regmap_write(sdev->regmap, CSI_CH_HSIZE_REG,
+ CSI_CH_HSIZE_HOR_LEN(hor_len) |
+ CSI_CH_HSIZE_HOR_START(0));
+ regmap_write(sdev->regmap, CSI_CH_VSIZE_REG,
+ CSI_CH_VSIZE_VER_LEN(height) |
+ CSI_CH_VSIZE_VER_START(0));
+
+ planar_offset[0] = 0;
+ switch (config->pixelformat) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ bytesperline_y = width;
+ bytesperline_c = width;
+ planar_offset[1] = bytesperline_y * height;
+ planar_offset[2] = -1;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ bytesperline_y = width;
+ bytesperline_c = width / 2;
+ planar_offset[1] = bytesperline_y * height;
+ planar_offset[2] = planar_offset[1] +
+ bytesperline_c * height / 2;
+ break;
+ case V4L2_PIX_FMT_YUV422P:
+ bytesperline_y = width;
+ bytesperline_c = width / 2;
+ planar_offset[1] = bytesperline_y * height;
+ planar_offset[2] = planar_offset[1] +
+ bytesperline_c * height;
+ break;
+ default: /* raw */
+ bytesperline_y = (sun6i_csi_get_bpp(config->pixelformat) *
+ config->width) / 8;
+ bytesperline_c = 0;
+ planar_offset[1] = -1;
+ planar_offset[2] = -1;
+ break;
+ }
+
+ regmap_write(sdev->regmap, CSI_CH_BUF_LEN_REG,
+ CSI_CH_BUF_LEN_BUF_LEN_C(bytesperline_c) |
+ CSI_CH_BUF_LEN_BUF_LEN_Y(bytesperline_y));
+}
+
+int sun6i_csi_update_config(struct sun6i_csi *csi,
+ struct sun6i_csi_config *config)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+ if (config == NULL)
+ return -EINVAL;
+
+ memcpy(&csi->config, config, sizeof(csi->config));
+
+ sun6i_csi_setup_bus(sdev);
+ sun6i_csi_set_format(sdev);
+ sun6i_csi_set_window(sdev);
+
+ return 0;
+}
+
+void sun6i_csi_update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ /* transform physical address to bus address */
+ dma_addr_t bus_addr = addr - PHYS_OFFSET;
+
+ regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
+ (bus_addr + sdev->planar_offset[0]) >> 2);
+ if (sdev->planar_offset[1] != -1)
+ regmap_write(sdev->regmap, CSI_CH_F1_BUFA_REG,
+ (bus_addr + sdev->planar_offset[1]) >> 2);
+ if (sdev->planar_offset[2] != -1)
+ regmap_write(sdev->regmap, CSI_CH_F2_BUFA_REG,
+ (bus_addr + sdev->planar_offset[2]) >> 2);
+}
+
+void sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ struct regmap *regmap = sdev->regmap;
+
+ if (!enable) {
+ regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0);
+ regmap_write(regmap, CSI_CH_INT_EN_REG, 0);
+ return;
+ }
+
+ regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF);
+ regmap_write(regmap, CSI_CH_INT_EN_REG,
+ CSI_CH_INT_EN_HB_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO2_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO1_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO0_OF_INT_EN |
+ CSI_CH_INT_EN_FD_INT_EN |
+ CSI_CH_INT_EN_CD_INT_EN);
+
+ regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON,
+ CSI_CAP_CH0_VCAP_ON);
+}
+
+/* -----------------------------------------------------------------------------
+ * Media Controller and V4L2
+ */
+static int sun6i_csi_link_entity(struct sun6i_csi *csi,
+ struct media_entity *entity)
+{
+ struct media_entity *sink;
+ struct media_pad *sink_pad;
+ int ret;
+ int i;
+
+ if (!entity->num_pads) {
+ dev_err(csi->dev, "%s: invalid entity\n", entity->name);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < entity->num_pads; i++) {
+ if (entity->pads[i].flags & MEDIA_PAD_FL_SOURCE)
+ break;
+ }
+
+ if (i == entity->num_pads) {
+ dev_err(csi->dev, "%s: no source pad in external entity %s\n",
+ __func__, entity->name);
+ return -EINVAL;
+ }
+
+ sink = &csi->video.vdev.entity;
+ sink_pad = &csi->video.pad;
+
+ dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
+ entity->name, i, sink->name, sink_pad->index);
+ ret = media_create_pad_link(entity, i, sink, sink_pad->index,
+ MEDIA_LNK_FL_ENABLED);
+ if (ret < 0) {
+ dev_err(csi->dev, "failed to create %s:%u -> %s:%u link\n",
+ entity->name, i, sink->name, sink_pad->index);
+ return ret;
+ }
+
+ return media_entity_call(sink, link_setup, sink_pad, &entity->pads[i],
+ MEDIA_LNK_FL_ENABLED);
+}
+
+static int sun6i_subdev_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct sun6i_csi *csi = container_of(notifier, struct sun6i_csi,
+ notifier);
+ struct v4l2_device *v4l2_dev = &csi->v4l2_dev;
+ struct v4l2_subdev *sd;
+ int ret;
+
+ dev_dbg(csi->dev, "notify complete, all subdevs registered\n");
+
+ if (notifier->num_subdevs != 1)
+ return -EINVAL;
+
+ sd = list_first_entry(&v4l2_dev->subdevs, struct v4l2_subdev, list);
+ if (sd == NULL)
+ return -EINVAL;
+
+ ret = sun6i_csi_link_entity(csi, &sd->entity);
+ if (ret < 0)
+ return ret;
+
+ ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev);
+ if (ret < 0)
+ return ret;
+
+ return media_device_register(&csi->media_dev);
+}
+
+static const struct v4l2_async_notifier_operations sun6i_csi_async_ops = {
+ .complete = sun6i_subdev_notify_complete,
+};
+
+static int sun6i_csi_fwnode_parse(struct device *dev,
+ struct v4l2_fwnode_endpoint *vep,
+ struct v4l2_async_subdev *asd)
+{
+ struct sun6i_csi *csi = dev_get_drvdata(dev);
+
+ if (vep->base.port || vep->base.id) {
+ dev_warn(dev, "Only support a single port with one endpoint\n");
+ return -ENOTCONN;
+ }
+
+ switch (vep->bus_type) {
+ case V4L2_MBUS_PARALLEL:
+ case V4L2_MBUS_BT656:
+ csi->v4l2_ep = *vep;
+ return 0;
+ default:
+ dev_err(dev, "Unsupported media bus type\n");
+ return -ENOTCONN;
+ }
+}
+
+static void sun6i_csi_v4l2_cleanup(struct sun6i_csi *csi)
+{
+ v4l2_async_notifier_cleanup(&csi->notifier);
+ v4l2_async_notifier_unregister(&csi->notifier);
+ sun6i_video_cleanup(&csi->video);
+ v4l2_device_unregister(&csi->v4l2_dev);
+ media_device_unregister(&csi->media_dev);
+ media_device_cleanup(&csi->media_dev);
+}
+
+static int sun6i_csi_v4l2_init(struct sun6i_csi *csi)
+{
+ int ret;
+
+ csi->media_dev.dev = csi->dev;
+ strlcpy(csi->media_dev.model, "Allwinner Video Capture Device",
+ sizeof(csi->media_dev.model));
+ csi->media_dev.hw_revision = 0;
+
+ media_device_init(&csi->media_dev);
+
+ csi->v4l2_dev.mdev = &csi->media_dev;
+ ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
+ if (ret) {
+ dev_err(csi->dev, "V4L2 device registration failed (%d)\n",
+ ret);
+ goto clean_media;
+ }
+
+ ret = sun6i_video_init(&csi->video, csi, "sun6i-csi");
+ if (ret)
+ goto unreg_v4l2;
+
+ ret = v4l2_async_notifier_parse_fwnode_endpoints(
+ csi->dev, &csi->notifier, sizeof(struct v4l2_async_subdev),
+ sun6i_csi_fwnode_parse);
+ if (ret)
+ goto clean_video;
+
+ csi->notifier.ops = &sun6i_csi_async_ops;
+
+ ret = v4l2_async_notifier_register(&csi->v4l2_dev, &csi->notifier);
+ if (ret) {
+ dev_err(csi->dev, "notifier registration failed\n");
+ goto clean_notifier;
+ }
+
+ return 0;
+
+clean_notifier:
+ v4l2_async_notifier_cleanup(&csi->notifier);
+clean_video:
+ sun6i_video_cleanup(&csi->video);
+unreg_v4l2:
+ v4l2_device_unregister(&csi->v4l2_dev);
+clean_media:
+ media_device_cleanup(&csi->media_dev);
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Resources and IRQ
+ */
+static irqreturn_t sun6i_csi_isr(int irq, void *dev_id)
+{
+ struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id;
+ struct regmap *regmap = sdev->regmap;
+ u32 status;
+
+ regmap_read(regmap, CSI_CH_INT_STA_REG, &status);
+
+ if (!(status & 0xFF))
+ return IRQ_NONE;
+
+ if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) ||
+ (status & CSI_CH_INT_STA_FIFO1_OF_PD) ||
+ (status & CSI_CH_INT_STA_FIFO2_OF_PD) ||
+ (status & CSI_CH_INT_STA_HB_OF_PD)) {
+ regmap_write(regmap, CSI_CH_INT_STA_REG, status);
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
+ CSI_EN_CSI_EN);
+ return IRQ_HANDLED;
+ }
+
+ if (status & CSI_CH_INT_STA_FD_PD)
+ sun6i_video_frame_done(&sdev->csi.video);
+
+ regmap_write(regmap, CSI_CH_INT_STA_REG, status);
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_config sun6i_csi_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x1000,
+};
+
+static int sun6i_csi_resource_request(struct sun6i_csi_dev *sdev,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+ void __iomem *io_base;
+ int ret;
+ int irq;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ io_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(io_base))
+ return PTR_ERR(io_base);
+
+ sdev->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "bus", io_base,
+ &sun6i_csi_regmap_config);
+ if (IS_ERR(sdev->regmap)) {
+ dev_err(&pdev->dev, "Failed to init register map\n");
+ return PTR_ERR(sdev->regmap);
+ }
+
+ sdev->clk_mod = devm_clk_get(&pdev->dev, "mod");
+ if (IS_ERR(sdev->clk_mod)) {
+ dev_err(&pdev->dev, "Unable to acquire csi clock\n");
+ return PTR_ERR(sdev->clk_mod);
+ }
+
+ sdev->clk_ram = devm_clk_get(&pdev->dev, "ram");
+ if (IS_ERR(sdev->clk_ram)) {
+ dev_err(&pdev->dev, "Unable to acquire dram-csi clock\n");
+ return PTR_ERR(sdev->clk_ram);
+ }
+
+ sdev->rstc_bus = devm_reset_control_get_shared(&pdev->dev, NULL);
+ if (IS_ERR(sdev->rstc_bus)) {
+ dev_err(&pdev->dev, "Cannot get reset controller\n");
+ return PTR_ERR(sdev->rstc_bus);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "No csi IRQ specified\n");
+ ret = -ENXIO;
+ return ret;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, sun6i_csi_isr, 0, MODULE_NAME,
+ sdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot request csi IRQ\n");
+ return ret;
+ }
+ return 0;
+}
+
+static int sun6i_csi_probe(struct platform_device *pdev)
+{
+ struct sun6i_csi_dev *sdev;
+ int ret;
+
+ sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
+ if (!sdev)
+ return -ENOMEM;
+
+ sdev->dev = &pdev->dev;
+
+ ret = sun6i_csi_resource_request(sdev, pdev);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, sdev);
+
+ sdev->csi.dev = &pdev->dev;
+ ret = sun6i_csi_v4l2_init(&sdev->csi);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int sun6i_csi_remove(struct platform_device *pdev)
+{
+ struct sun6i_csi_dev *sdev = platform_get_drvdata(pdev);
+
+ sun6i_csi_v4l2_cleanup(&sdev->csi);
+
+ return 0;
+}
+
+static const struct of_device_id sun6i_csi_of_match[] = {
+ { .compatible = "allwinner,sun8i-v3s-csi", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sun6i_csi_of_match);
+
+static struct platform_driver sun6i_csi_platform_driver = {
+ .probe = sun6i_csi_probe,
+ .remove = sun6i_csi_remove,
+ .driver = {
+ .name = MODULE_NAME,
+ .of_match_table = of_match_ptr(sun6i_csi_of_match),
+ },
+};
+module_platform_driver(sun6i_csi_platform_driver);
+
+MODULE_DESCRIPTION("Allwinner V3s Camera Sensor Interface driver");
+MODULE_AUTHOR("Yong Deng <yong.deng@magewell.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
new file mode 100644
index 0000000..6733a1e
--- /dev/null
+++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+#ifndef __SUN6I_CSI_H__
+#define __SUN6I_CSI_H__
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+
+#include "sun6i_video.h"
+
+struct sun6i_csi;
+
+/**
+ * struct sun6i_csi_config - configs for sun6i csi
+ * @pixelformat: v4l2 pixel format (V4L2_PIX_FMT_*)
+ * @code: media bus format code (MEDIA_BUS_FMT_*)
+ * @field: used interlacing type (enum v4l2_field)
+ * @width: frame width
+ * @height: frame height
+ */
+struct sun6i_csi_config {
+ u32 pixelformat;
+ u32 code;
+ u32 field;
+ u32 width;
+ u32 height;
+};
+
+struct sun6i_csi {
+ struct device *dev;
+ struct v4l2_device v4l2_dev;
+ struct media_device media_dev;
+
+ struct v4l2_async_notifier notifier;
+
+ /* video port settings */
+ struct v4l2_fwnode_endpoint v4l2_ep;
+
+ struct sun6i_csi_config config;
+
+ struct sun6i_video video;
+};
+
+/**
+ * sun6i_csi_get_supported_pixformats() - get csi supported pixformats
+ * @csi: pointer to the csi
+ * @pixformats: supported pixformats return from csi
+ *
+ * @return the count of pixformats or error(< 0)
+ */
+int sun6i_csi_get_supported_pixformats(struct sun6i_csi *csi,
+ const u32 **pixformats);
+
+/**
+ * sun6i_csi_is_format_support() - check if the format supported by csi
+ * @csi: pointer to the csi
+ * @pixformat: v4l2 pixel format (V4L2_PIX_FMT_*)
+ * @mbus_code: media bus format code (MEDIA_BUS_FMT_*)
+ */
+bool sun6i_csi_is_format_support(struct sun6i_csi *csi, u32 pixformat,
+ u32 mbus_code);
+
+/**
+ * sun6i_csi_set_power() - power on/off the csi
+ * @csi: pointer to the csi
+ * @enable: on/off
+ */
+int sun6i_csi_set_power(struct sun6i_csi *csi, bool enable);
+
+/**
+ * sun6i_csi_update_config() - update the csi register setttings
+ * @csi: pointer to the csi
+ * @config: see struct sun6i_csi_config
+ */
+int sun6i_csi_update_config(struct sun6i_csi *csi,
+ struct sun6i_csi_config *config);
+
+/**
+ * sun6i_csi_update_buf_addr() - update the csi frame buffer address
+ * @csi: pointer to the csi
+ * @addr: frame buffer's physical address
+ */
+void sun6i_csi_update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr);
+
+/**
+ * sun6i_csi_set_stream() - start/stop csi streaming
+ * @csi: pointer to the csi
+ * @enable: start/stop
+ */
+void sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable);
+
+/* get bpp form v4l2 pixformat */
+static inline int sun6i_csi_get_bpp(unsigned int pixformat)
+{
+ switch (pixformat) {
+ case V4L2_PIX_FMT_SBGGR8:
+ case V4L2_PIX_FMT_SGBRG8:
+ case V4L2_PIX_FMT_SGRBG8:
+ case V4L2_PIX_FMT_SRGGB8:
+ return 8;
+ case V4L2_PIX_FMT_SBGGR10:
+ case V4L2_PIX_FMT_SGBRG10:
+ case V4L2_PIX_FMT_SGRBG10:
+ case V4L2_PIX_FMT_SRGGB10:
+ return 10;
+ case V4L2_PIX_FMT_SBGGR12:
+ case V4L2_PIX_FMT_SGBRG12:
+ case V4L2_PIX_FMT_SGRBG12:
+ case V4L2_PIX_FMT_SRGGB12:
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ return 12;
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV422P:
+ return 16;
+ case V4L2_PIX_FMT_RGB24:
+ case V4L2_PIX_FMT_BGR24:
+ return 24;
+ case V4L2_PIX_FMT_RGB32:
+ case V4L2_PIX_FMT_BGR32:
+ return 32;
+ }
+
+ return 0;
+}
+
+#endif /* __SUN6I_CSI_H__ */
diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h
new file mode 100644
index 0000000..aad674a
--- /dev/null
+++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+#ifndef __SUN6I_CSI_REG_H__
+#define __SUN6I_CSI_REG_H__
+
+#include <linux/kernel.h>
+
+#define CSI_EN_REG 0x0
+#define CSI_EN_VER_EN BIT(30)
+#define CSI_EN_CSI_EN BIT(0)
+
+#define CSI_IF_CFG_REG 0x4
+#define CSI_IF_CFG_SRC_TYPE_MASK BIT(21)
+#define CSI_IF_CFG_SRC_TYPE_PROGRESSED ((0 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
+#define CSI_IF_CFG_SRC_TYPE_INTERLACED ((1 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
+#define CSI_IF_CFG_FPS_DS_EN BIT(20)
+#define CSI_IF_CFG_FIELD_MASK BIT(19)
+#define CSI_IF_CFG_FIELD_NEGATIVE ((0 << 19) & CSI_IF_CFG_FIELD_MASK)
+#define CSI_IF_CFG_FIELD_POSITIVE ((1 << 19) & CSI_IF_CFG_FIELD_MASK)
+#define CSI_IF_CFG_VREF_POL_MASK BIT(18)
+#define CSI_IF_CFG_VREF_POL_NEGATIVE ((0 << 18) & CSI_IF_CFG_VREF_POL_MASK)
+#define CSI_IF_CFG_VREF_POL_POSITIVE ((1 << 18) & CSI_IF_CFG_VREF_POL_MASK)
+#define CSI_IF_CFG_HREF_POL_MASK BIT(17)
+#define CSI_IF_CFG_HREF_POL_NEGATIVE ((0 << 17) & CSI_IF_CFG_HREF_POL_MASK)
+#define CSI_IF_CFG_HREF_POL_POSITIVE ((1 << 17) & CSI_IF_CFG_HREF_POL_MASK)
+#define CSI_IF_CFG_CLK_POL_MASK BIT(16)
+#define CSI_IF_CFG_CLK_POL_RISING_EDGE ((0 << 16) & CSI_IF_CFG_CLK_POL_MASK)
+#define CSI_IF_CFG_CLK_POL_FALLING_EDGE ((1 << 16) & CSI_IF_CFG_CLK_POL_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_MASK GENMASK(10, 8)
+#define CSI_IF_CFG_IF_DATA_WIDTH_8BIT ((0 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_10BIT ((1 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_12BIT ((2 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_MIPI_IF_MASK BIT(7)
+#define CSI_IF_CFG_MIPI_IF_CSI (0 << 7)
+#define CSI_IF_CFG_MIPI_IF_MIPI (1 << 7)
+#define CSI_IF_CFG_CSI_IF_MASK GENMASK(4, 0)
+#define CSI_IF_CFG_CSI_IF_YUV422_INTLV ((0 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_YUV422_16BIT ((1 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_BT656 ((4 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_BT1120 ((5 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+
+#define CSI_CAP_REG 0x8
+#define CSI_CAP_CH0_CAP_MASK_MASK GENMASK(5, 2)
+#define CSI_CAP_CH0_CAP_MASK(count) ((count << 2) & CSI_CAP_CH0_CAP_MASK_MASK)
+#define CSI_CAP_CH0_VCAP_ON BIT(1)
+#define CSI_CAP_CH0_SCAP_ON BIT(0)
+
+#define CSI_SYNC_CNT_REG 0xc
+#define CSI_FIFO_THRS_REG 0x10
+#define CSI_BT656_HEAD_CFG_REG 0x14
+#define CSI_PTN_LEN_REG 0x30
+#define CSI_PTN_ADDR_REG 0x34
+#define CSI_VER_REG 0x3c
+
+#define CSI_CH_CFG_REG 0x44
+#define CSI_CH_CFG_INPUT_FMT_MASK GENMASK(23, 20)
+#define CSI_CH_CFG_INPUT_FMT(fmt) ((fmt << 20) & CSI_CH_CFG_INPUT_FMT_MASK)
+#define CSI_CH_CFG_OUTPUT_FMT_MASK GENMASK(19, 16)
+#define CSI_CH_CFG_OUTPUT_FMT(fmt) ((fmt << 16) & CSI_CH_CFG_OUTPUT_FMT_MASK)
+#define CSI_CH_CFG_VFLIP_EN BIT(13)
+#define CSI_CH_CFG_HFLIP_EN BIT(12)
+#define CSI_CH_CFG_FIELD_SEL_MASK GENMASK(11, 10)
+#define CSI_CH_CFG_FIELD_SEL_FIELD0 ((0 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_FIELD_SEL_FIELD1 ((1 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_FIELD_SEL_BOTH ((2 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_INPUT_SEQ_MASK GENMASK(9, 8)
+#define CSI_CH_CFG_INPUT_SEQ(seq) ((seq << 8) & CSI_CH_CFG_INPUT_SEQ_MASK)
+
+#define CSI_CH_SCALE_REG 0x4c
+#define CSI_CH_SCALE_QUART_EN BIT(0)
+
+#define CSI_CH_F0_BUFA_REG 0x50
+
+#define CSI_CH_F1_BUFA_REG 0x58
+
+#define CSI_CH_F2_BUFA_REG 0x60
+
+#define CSI_CH_STA_REG 0x6c
+#define CSI_CH_STA_FIELD_STA_MASK BIT(2)
+#define CSI_CH_STA_FIELD_STA_FIELD0 ((0 << 2) & CSI_CH_STA_FIELD_STA_MASK)
+#define CSI_CH_STA_FIELD_STA_FIELD1 ((1 << 2) & CSI_CH_STA_FIELD_STA_MASK)
+#define CSI_CH_STA_VCAP_STA BIT(1)
+#define CSI_CH_STA_SCAP_STA BIT(0)
+
+#define CSI_CH_INT_EN_REG 0x70
+#define CSI_CH_INT_EN_VS_INT_EN BIT(7)
+#define CSI_CH_INT_EN_HB_OF_INT_EN BIT(6)
+#define CSI_CH_INT_EN_MUL_ERR_INT_EN BIT(5)
+#define CSI_CH_INT_EN_FIFO2_OF_INT_EN BIT(4)
+#define CSI_CH_INT_EN_FIFO1_OF_INT_EN BIT(3)
+#define CSI_CH_INT_EN_FIFO0_OF_INT_EN BIT(2)
+#define CSI_CH_INT_EN_FD_INT_EN BIT(1)
+#define CSI_CH_INT_EN_CD_INT_EN BIT(0)
+
+#define CSI_CH_INT_STA_REG 0x74
+#define CSI_CH_INT_STA_VS_PD BIT(7)
+#define CSI_CH_INT_STA_HB_OF_PD BIT(6)
+#define CSI_CH_INT_STA_MUL_ERR_PD BIT(5)
+#define CSI_CH_INT_STA_FIFO2_OF_PD BIT(4)
+#define CSI_CH_INT_STA_FIFO1_OF_PD BIT(3)
+#define CSI_CH_INT_STA_FIFO0_OF_PD BIT(2)
+#define CSI_CH_INT_STA_FD_PD BIT(1)
+#define CSI_CH_INT_STA_CD_PD BIT(0)
+
+#define CSI_CH_FLD1_VSIZE_REG 0x78
+
+#define CSI_CH_HSIZE_REG 0x80
+#define CSI_CH_HSIZE_HOR_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_HSIZE_HOR_LEN(len) ((len << 16) & CSI_CH_HSIZE_HOR_LEN_MASK)
+#define CSI_CH_HSIZE_HOR_START_MASK GENMASK(12, 0)
+#define CSI_CH_HSIZE_HOR_START(start) ((start << 0) & CSI_CH_HSIZE_HOR_START_MASK)
+
+#define CSI_CH_VSIZE_REG 0x84
+#define CSI_CH_VSIZE_VER_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_VSIZE_VER_LEN(len) ((len << 16) & CSI_CH_VSIZE_VER_LEN_MASK)
+#define CSI_CH_VSIZE_VER_START_MASK GENMASK(12, 0)
+#define CSI_CH_VSIZE_VER_START(start) ((start << 0) & CSI_CH_VSIZE_VER_START_MASK)
+
+#define CSI_CH_BUF_LEN_REG 0x88
+#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK GENMASK(29, 16)
+#define CSI_CH_BUF_LEN_BUF_LEN_C(len) ((len << 16) & CSI_CH_BUF_LEN_BUF_LEN_C_MASK)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK GENMASK(13, 0)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y(len) ((len << 0) & CSI_CH_BUF_LEN_BUF_LEN_Y_MASK)
+
+#define CSI_CH_FLIP_SIZE_REG 0x8c
+#define CSI_CH_FLIP_SIZE_VER_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_FLIP_SIZE_VER_LEN(len) ((len << 16) & CSI_CH_FLIP_SIZE_VER_LEN_MASK)
+#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK GENMASK(12, 0)
+#define CSI_CH_FLIP_SIZE_VALID_LEN(len) ((len << 0) & CSI_CH_FLIP_SIZE_VALID_LEN_MASK)
+
+#define CSI_CH_FRM_CLK_CNT_REG 0x90
+#define CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94
+#define CSI_CH_FIFO_STAT_REG 0x98
+#define CSI_CH_PCLK_STAT_REG 0x9c
+
+/*
+ * csi input data format
+ */
+enum csi_input_fmt {
+ CSI_INPUT_FORMAT_RAW = 0,
+ CSI_INPUT_FORMAT_YUV422 = 3,
+ CSI_INPUT_FORMAT_YUV420 = 4,
+};
+
+/*
+ * csi output data format
+ */
+enum csi_output_fmt {
+ /* only when input format is RAW */
+ CSI_FIELD_RAW_8 = 0,
+ CSI_FIELD_RAW_10 = 1,
+ CSI_FIELD_RAW_12 = 2,
+ CSI_FIELD_RGB565 = 4,
+ CSI_FIELD_RGB888 = 5,
+ CSI_FIELD_PRGB888 = 6,
+ CSI_FRAME_RAW_8 = 8,
+ CSI_FRAME_RAW_10 = 9,
+ CSI_FRAME_RAW_12 = 10,
+ CSI_FRAME_RGB565 = 12,
+ CSI_FRAME_RGB888 = 13,
+ CSI_FRAME_PRGB888 = 14,
+
+ /* only when input format is YUV422 */
+ CSI_FIELD_PLANAR_YUV422 = 0,
+ CSI_FIELD_PLANAR_YUV420 = 1,
+ CSI_FRAME_PLANAR_YUV420 = 2,
+ CSI_FRAME_PLANAR_YUV422 = 3,
+ CSI_FIELD_UV_CB_YUV422 = 4,
+ CSI_FIELD_UV_CB_YUV420 = 5,
+ CSI_FRAME_UV_CB_YUV420 = 6,
+ CSI_FRAME_UV_CB_YUV422 = 7,
+ CSI_FIELD_MB_YUV422 = 8,
+ CSI_FIELD_MB_YUV420 = 9,
+ CSI_FRAME_MB_YUV420 = 10,
+ CSI_FRAME_MB_YUV422 = 11,
+ CSI_FIELD_UV_CB_YUV422_10 = 12,
+ CSI_FIELD_UV_CB_YUV420_10 = 13,
+};
+
+/*
+ * csi YUV input data sequence
+ */
+enum csi_input_seq {
+ /* only when input format is YUV422 */
+ CSI_INPUT_SEQ_YUYV = 0,
+ CSI_INPUT_SEQ_YVYU,
+ CSI_INPUT_SEQ_UYVY,
+ CSI_INPUT_SEQ_VYUY,
+};
+
+#endif /* __SUN6I_CSI_REG_H__ */
diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
new file mode 100644
index 0000000..2b683ac
--- /dev/null
+++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
@@ -0,0 +1,752 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng@magewell.com>
+ *
+ * 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.
+ */
+
+#include <linux/of.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "sun6i_csi.h"
+#include "sun6i_video.h"
+
+struct sun6i_csi_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+
+ dma_addr_t dma_addr;
+ bool queued_to_csi;
+};
+
+static struct sun6i_csi_format *
+find_format_by_pixformat(struct sun6i_video *video, unsigned int pixformat)
+{
+ unsigned int num_formats = video->num_formats;
+ struct sun6i_csi_format *fmt;
+ unsigned int i;
+
+ for (i = 0; i < num_formats; i++) {
+ fmt = &video->formats[i];
+ if (fmt->pixformat == pixformat)
+ return fmt;
+ }
+
+ return NULL;
+}
+
+static struct v4l2_subdev *
+sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad)
+{
+ struct media_pad *remote;
+
+ remote = media_entity_remote_pad(&video->pad);
+
+ if (!remote || !is_media_entity_v4l2_subdev(remote->entity))
+ return NULL;
+
+ if (pad)
+ *pad = remote->index;
+
+ return media_entity_to_v4l2_subdev(remote->entity);
+}
+
+static int sun6i_video_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ unsigned int size = video->fmt.fmt.pix.sizeimage;
+
+ if (*nplanes)
+ return sizes[0] < size ? -EINVAL : 0;
+
+ *nplanes = 1;
+ sizes[0] = size;
+
+ return 0;
+}
+
+static int sun6i_video_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct sun6i_csi_buffer *buf =
+ container_of(vbuf, struct sun6i_csi_buffer, vb);
+ struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long size = video->fmt.fmt.pix.sizeimage;
+
+ if (vb2_plane_size(vb, 0) < size) {
+ v4l2_err(video->vdev.v4l2_dev, "buffer too small (%lu < %lu)\n",
+ vb2_plane_size(vb, 0), size);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, size);
+
+ buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ vbuf->field = video->fmt.fmt.pix.field;
+
+ return 0;
+}
+
+static int sun6i_pipeline_set_stream(struct sun6i_video *video, bool enable)
+{
+ struct media_entity *entity;
+ struct media_pad *pad;
+ struct v4l2_subdev *subdev;
+ int ret;
+
+ entity = &video->vdev.entity;
+ while (1) {
+ pad = &entity->pads[0];
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
+ break;
+
+ pad = media_entity_remote_pad(pad);
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+ break;
+
+ entity = pad->entity;
+ subdev = media_entity_to_v4l2_subdev(entity);
+
+ ret = v4l2_subdev_call(subdev, video, s_stream, enable);
+ if (enable && ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ struct sun6i_csi_buffer *buf;
+ struct sun6i_csi_buffer *next_buf;
+ struct sun6i_csi_config config;
+ unsigned long flags;
+ int ret;
+
+ video->sequence = 0;
+
+ ret = media_pipeline_start(&video->vdev.entity, &video->vdev.pipe);
+ if (ret < 0)
+ goto clear_dma_queue;
+
+ config.pixelformat = video->fmt.fmt.pix.pixelformat;
+ config.code = video->current_fmt->mbus_code;
+ config.field = video->fmt.fmt.pix.field;
+ config.width = video->fmt.fmt.pix.width;
+ config.height = video->fmt.fmt.pix.height;
+
+ ret = sun6i_csi_update_config(video->csi, &config);
+ if (ret < 0)
+ goto stop_media_pipeline;
+
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+
+ buf = list_first_entry(&video->dma_queue,
+ struct sun6i_csi_buffer, list);
+ buf->queued_to_csi = true;
+ sun6i_csi_update_buf_addr(video->csi, buf->dma_addr);
+
+ sun6i_csi_set_stream(video->csi, true);
+
+ /*
+ * CSI will lookup the next dma buffer for next frame before the
+ * the current frame done IRQ triggered. This is not documented
+ * but reported by Ondřej Jirman.
+ * The BSP code has workaround for this too. It skip to mark the
+ * first buffer as frame done for VB2 and pass the second buffer
+ * to CSI in the first frame done ISR call. Then in second frame
+ * done ISR call, it mark the first buffer as frame done for VB2
+ * and pass the third buffer to CSI. And so on. The bad thing is
+ * that the first buffer will be written twice and the first frame
+ * is dropped even the queued buffer is sufficient.
+ * So, I make some improvement here. Pass the next buffer to CSI
+ * just follow starting the CSI. In this case, the first frame
+ * will be stored in first buffer, second frame in second buffer.
+ * This mothed is used to avoid dropping the first frame, it
+ * would also drop frame when lacking of queued buffer.
+ */
+ next_buf = list_next_entry(buf, list);
+ next_buf->queued_to_csi = true;
+ sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr);
+
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+
+ ret = sun6i_pipeline_set_stream(video, true);
+ if (ret < 0)
+ goto stop_csi_stream;
+
+ return 0;
+
+stop_csi_stream:
+ sun6i_csi_set_stream(video->csi, false);
+stop_media_pipeline:
+ media_pipeline_stop(&video->vdev.entity);
+clear_dma_queue:
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ list_for_each_entry(buf, &video->dma_queue, list)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+
+ return ret;
+}
+
+static void sun6i_video_stop_streaming(struct vb2_queue *vq)
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ unsigned long flags;
+ struct sun6i_csi_buffer *buf;
+
+ sun6i_pipeline_set_stream(video, false);
+
+ sun6i_csi_set_stream(video->csi, false);
+
+ media_pipeline_stop(&video->vdev.entity);
+
+ /* Release all active buffers */
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ list_for_each_entry(buf, &video->dma_queue, list)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+}
+
+static void sun6i_video_buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct sun6i_csi_buffer *buf =
+ container_of(vbuf, struct sun6i_csi_buffer, vb);
+ struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long flags;
+
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ buf->queued_to_csi = false;
+ list_add_tail(&buf->list, &video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+}
+
+void sun6i_video_frame_done(struct sun6i_video *video)
+{
+ struct sun6i_csi_buffer *buf;
+ struct sun6i_csi_buffer *next_buf;
+ struct vb2_v4l2_buffer *vbuf;
+
+ spin_lock(&video->dma_queue_lock);
+
+ buf = list_first_entry(&video->dma_queue,
+ struct sun6i_csi_buffer, list);
+ if (list_is_last(&buf->list, &video->dma_queue)) {
+ dev_dbg(video->csi->dev, "Frame droped!\n");
+ goto unlock;
+ }
+
+ next_buf = list_next_entry(buf, list);
+ /* If a new buffer (#next_buf) had not been queued to CSI, the old
+ * buffer (#buf) is still holding by CSI for storing the next
+ * frame. So, we queue a new buffer (#next_buf) to CSI then wait
+ * for next ISR call.
+ */
+ if (!next_buf->queued_to_csi) {
+ next_buf->queued_to_csi = true;
+ sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr);
+ dev_dbg(video->csi->dev, "Frame droped!\n");
+ goto unlock;
+ }
+
+ list_del(&buf->list);
+ vbuf = &buf->vb;
+ vbuf->vb2_buf.timestamp = ktime_get_ns();
+ vbuf->sequence = video->sequence;
+ vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE);
+
+ /* Prepare buffer for next frame but one. */
+ if (!list_is_last(&next_buf->list, &video->dma_queue)) {
+ next_buf = list_next_entry(next_buf, list);
+ next_buf->queued_to_csi = true;
+ sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr);
+ } else {
+ dev_dbg(video->csi->dev, "Next frame will be droped!\n");
+ }
+
+unlock:
+ video->sequence++;
+ spin_unlock(&video->dma_queue_lock);
+}
+
+static struct vb2_ops sun6i_csi_vb2_ops = {
+ .queue_setup = sun6i_video_queue_setup,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .buf_prepare = sun6i_video_buffer_prepare,
+ .start_streaming = sun6i_video_start_streaming,
+ .stop_streaming = sun6i_video_stop_streaming,
+ .buf_queue = sun6i_video_buffer_queue,
+};
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ strlcpy(cap->driver, "sun6i-video", sizeof(cap->driver));
+ strlcpy(cap->card, video->vdev.name, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+ video->csi->dev->of_node->name);
+
+ return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ u32 index = f->index;
+
+ if (index >= video->num_formats)
+ return -EINVAL;
+
+ f->pixelformat = video->formats[index].pixformat;
+
+ return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *fmt)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ *fmt = video->fmt;
+
+ return 0;
+}
+
+static int sun6i_video_try_fmt(struct sun6i_video *video, struct v4l2_format *f,
+ struct sun6i_csi_format **current_fmt)
+{
+ struct sun6i_csi_format *csi_fmt;
+ struct v4l2_pix_format *pixfmt = &f->fmt.pix;
+ struct v4l2_subdev_format format;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ csi_fmt = find_format_by_pixformat(video, pixfmt->pixelformat);
+ if (csi_fmt == NULL) {
+ if (video->num_formats > 0) {
+ csi_fmt = &video->formats[0];
+ pixfmt->pixelformat = csi_fmt->pixformat;
+ } else
+ return -EINVAL;
+ }
+
+ format.pad = pad;
+ format.which = V4L2_SUBDEV_FORMAT_TRY;
+ v4l2_fill_mbus_format(&format.format, pixfmt, csi_fmt->mbus_code);
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &format);
+ if (ret)
+ return ret;
+
+ v4l2_fill_pix_format(pixfmt, &format.format);
+
+ pixfmt->bytesperline = (pixfmt->width * csi_fmt->bpp) >> 3;
+ pixfmt->sizeimage = (pixfmt->width * csi_fmt->bpp * pixfmt->height) / 8;
+
+ if (current_fmt)
+ *current_fmt = csi_fmt;
+
+ return 0;
+}
+
+static int sun6i_video_set_fmt(struct sun6i_video *video, struct v4l2_format *f)
+{
+ struct v4l2_subdev_format format;
+ struct sun6i_csi_format *current_fmt;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ ret = sun6i_video_try_fmt(video, f, ¤t_fmt);
+ if (ret)
+ return ret;
+
+ format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ v4l2_fill_mbus_format(&format.format, &f->fmt.pix,
+ current_fmt->mbus_code);
+ ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &format);
+ if (ret < 0)
+ return ret;
+
+ video->fmt = *f;
+ video->current_fmt = current_fmt;
+
+ return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ if (vb2_is_busy(&video->vb2_vidq))
+ return -EBUSY;
+
+ return sun6i_video_set_fmt(video, f);
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ return sun6i_video_try_fmt(video, f, NULL);
+}
+
+static int vidioc_enum_input(struct file *file, void *fh,
+ struct v4l2_input *inp)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ if (inp->index != 0)
+ return -EINVAL;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ ret = v4l2_subdev_call(subdev, video, g_input_status, &inp->status);
+ if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+ return ret;
+
+ inp->type = V4L2_INPUT_TYPE_CAMERA;
+
+ if (v4l2_subdev_has_op(subdev, pad, dv_timings_cap)) {
+ inp->capabilities = V4L2_IN_CAP_DV_TIMINGS;
+ inp->std = 0;
+ } else {
+ inp->capabilities = 0;
+ inp->std = 0;
+ }
+
+ strlcpy(inp->name, subdev->name, sizeof(inp->name));
+
+ return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *fh, unsigned int *i)
+{
+ *i = 0;
+
+ return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *fh, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+
+ .vidioc_enum_input = vidioc_enum_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_g_input = vidioc_g_input,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 file operations
+ */
+static int sun6i_video_open(struct file *file)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ int ret;
+
+ if (mutex_lock_interruptible(&video->lock))
+ return -ERESTARTSYS;
+
+ ret = v4l2_fh_open(file);
+ if (ret < 0)
+ goto unlock;
+
+ ret = v4l2_pipeline_pm_use(&video->vdev.entity, 1);
+ if (ret < 0)
+ goto fh_release;
+
+ /* check if already powered */
+ if (!v4l2_fh_is_singular_file(file))
+ goto unlock;
+
+ ret = sun6i_csi_set_power(video->csi, true);
+ if (ret < 0)
+ goto fh_release;
+
+ mutex_unlock(&video->lock);
+ return 0;
+
+fh_release:
+ v4l2_fh_release(file);
+unlock:
+ mutex_unlock(&video->lock);
+ return ret;
+}
+
+static int sun6i_video_close(struct file *file)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ bool last_fh;
+
+ mutex_lock(&video->lock);
+
+ last_fh = v4l2_fh_is_singular_file(file);
+
+ _vb2_fop_release(file, NULL);
+
+ v4l2_pipeline_pm_use(&video->vdev.entity, 0);
+
+ if (last_fh)
+ sun6i_csi_set_power(video->csi, false);
+
+ mutex_unlock(&video->lock);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations sun6i_video_fops = {
+ .owner = THIS_MODULE,
+ .open = sun6i_video_open,
+ .release = sun6i_video_close,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+ .poll = vb2_fop_poll
+};
+
+/* -----------------------------------------------------------------------------
+ * Media Operations
+ */
+static int sun6i_video_formats_init(struct sun6i_video *video)
+{
+ struct v4l2_subdev_mbus_code_enum mbus_code = { 0 };
+ struct sun6i_csi *csi = video->csi;
+ struct v4l2_format format;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ const u32 *pixformats;
+ int pixformat_count = 0;
+ u32 subdev_codes[32]; /* subdev format codes, 32 should be enough */
+ int codes_count = 0;
+ int num_fmts = 0;
+ int i, j;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ /* Get supported pixformats of CSI */
+ pixformat_count = sun6i_csi_get_supported_pixformats(csi, &pixformats);
+ if (pixformat_count <= 0)
+ return -ENXIO;
+
+ /* Get subdev formats codes */
+ mbus_code.pad = pad;
+ mbus_code.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL,
+ &mbus_code)) {
+ if (codes_count >= ARRAY_SIZE(subdev_codes)) {
+ dev_warn(video->csi->dev,
+ "subdev_codes array is full!\n");
+ break;
+ }
+ subdev_codes[codes_count] = mbus_code.code;
+ codes_count++;
+ mbus_code.index++;
+ }
+
+ if (!codes_count)
+ return -ENXIO;
+
+ /* Get supported formats count */
+ for (i = 0; i < codes_count; i++) {
+ for (j = 0; j < pixformat_count; j++) {
+ if (!sun6i_csi_is_format_support(csi, pixformats[j],
+ subdev_codes[i])) {
+ continue;
+ }
+ num_fmts++;
+ }
+ }
+
+ if (!num_fmts)
+ return -ENXIO;
+
+ video->num_formats = num_fmts;
+ video->formats = devm_kcalloc(video->csi->dev, num_fmts,
+ sizeof(struct sun6i_csi_format), GFP_KERNEL);
+ if (!video->formats)
+ return -ENOMEM;
+
+ /* Get supported formats */
+ num_fmts = 0;
+ for (i = 0; i < codes_count; i++) {
+ for (j = 0; j < pixformat_count; j++) {
+ if (!sun6i_csi_is_format_support(csi, pixformats[j],
+ subdev_codes[i])) {
+ continue;
+ }
+
+ video->formats[num_fmts].pixformat = pixformats[j];
+ video->formats[num_fmts].mbus_code = subdev_codes[i];
+ video->formats[num_fmts].bpp =
+ sun6i_csi_get_bpp(pixformats[j]);
+ num_fmts++;
+ }
+ }
+
+ /* setup default format */
+ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ format.fmt.pix.width = 1280;
+ format.fmt.pix.height = 720;
+ format.fmt.pix.pixelformat = video->formats[0].pixformat;
+ sun6i_video_set_fmt(video, &format);
+
+ return 0;
+}
+
+static int sun6i_video_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct video_device *vdev = media_entity_to_video_device(entity);
+ struct sun6i_video *video = video_get_drvdata(vdev);
+
+ if (WARN_ON(video == NULL))
+ return 0;
+
+ return sun6i_video_formats_init(video);
+}
+
+static const struct media_entity_operations sun6i_video_media_ops = {
+ .link_setup = sun6i_video_link_setup,
+};
+
+int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
+ const char *name)
+{
+ struct video_device *vdev = &video->vdev;
+ struct vb2_queue *vidq = &video->vb2_vidq;
+ int ret;
+
+ video->csi = csi;
+
+ /* Initialize the media entity... */
+ video->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
+ vdev->entity.ops = &sun6i_video_media_ops;
+ ret = media_entity_pads_init(&vdev->entity, 1, &video->pad);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&video->lock);
+
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_lock_init(&video->dma_queue_lock);
+
+ video->sequence = 0;
+ video->num_formats = 0;
+
+ /* Initialize videobuf2 queue */
+ vidq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ vidq->io_modes = VB2_MMAP | VB2_DMABUF;
+ vidq->drv_priv = video;
+ vidq->buf_struct_size = sizeof(struct sun6i_csi_buffer);
+ vidq->ops = &sun6i_csi_vb2_ops;
+ vidq->mem_ops = &vb2_dma_contig_memops;
+ vidq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ vidq->lock = &video->lock;
+ /* Make sure non-dropped frame */
+ vidq->min_buffers_needed = 3;
+ vidq->dev = csi->dev;
+
+ ret = vb2_queue_init(vidq);
+ if (ret) {
+ v4l2_err(&csi->v4l2_dev, "vb2_queue_init failed: %d\n", ret);
+ goto error;
+ }
+
+ /* Register video device */
+ strlcpy(vdev->name, name, sizeof(vdev->name));
+ vdev->release = video_device_release_empty;
+ vdev->fops = &sun6i_video_fops;
+ vdev->ioctl_ops = &sun6i_video_ioctl_ops;
+ vdev->vfl_type = VFL_TYPE_GRABBER;
+ vdev->vfl_dir = VFL_DIR_RX;
+ vdev->v4l2_dev = &csi->v4l2_dev;
+ vdev->queue = vidq;
+ vdev->lock = &video->lock;
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
+ video_set_drvdata(vdev, video);
+
+ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+ if (ret < 0) {
+ v4l2_err(&csi->v4l2_dev,
+ "video_register_device failed: %d\n", ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ sun6i_video_cleanup(video);
+ return ret;
+}
+
+void sun6i_video_cleanup(struct sun6i_video *video)
+{
+ if (video_is_registered(&video->vdev))
+ video_unregister_device(&video->vdev);
+
+ media_entity_cleanup(&video->vdev.entity);
+}
diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h
new file mode 100644
index 0000000..f20928a
--- /dev/null
+++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+#ifndef __SUN6I_VIDEO_H__
+#define __SUN6I_VIDEO_H__
+
+#include <media/v4l2-dev.h>
+#include <media/videobuf2-core.h>
+
+/*
+ * struct sun6i_csi_format - CSI media bus format information
+ * @pixformat: V4l2 pixformat for this format
+ * @mbus_code: V4L2 media bus format code.
+ * @bpp: Bytes per pixel (when stored in memory)
+ */
+struct sun6i_csi_format {
+ u32 pixformat;
+ u32 mbus_code;
+ u8 bpp;
+};
+
+struct sun6i_csi;
+
+struct sun6i_video {
+ struct video_device vdev;
+ struct media_pad pad;
+ struct sun6i_csi *csi;
+
+ struct mutex lock;
+
+ struct vb2_queue vb2_vidq;
+ spinlock_t dma_queue_lock;
+ struct list_head dma_queue;
+
+ unsigned int sequence;
+
+ struct sun6i_csi_format *formats;
+ unsigned int num_formats;
+ struct sun6i_csi_format *current_fmt;
+ struct v4l2_format fmt;
+};
+
+int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
+ const char *name);
+void sun6i_video_cleanup(struct sun6i_video *video);
+
+void sun6i_video_frame_done(struct sun6i_video *video);
+
+#endif /* __SUN6I_VIDEO_H__ */
--
1.8.3.1
^ permalink raw reply related
* [PATCH v4 1/2] dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI)
From: Yong Deng @ 2017-12-22 9:41 UTC (permalink / raw)
To: Maxime Ripard
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
David S. Miller, Greg Kroah-Hartman, Randy Dunlap, Hans Verkuil,
Stanimir Varbanov, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Arnd Bergmann, Benjamin Gaignard, Ramesh Shanmugasundaram,
Sakari Ailus, Rick Chang, linux-media, devicetree,
linux-arm-kernel, linux-kernel, linux-sunxi, Yong Deng
Add binding documentation for Allwinner V3s CSI.
Signed-off-by: Yong Deng <yong.deng@magewell.com>
---
.../devicetree/bindings/media/sun6i-csi.txt | 51 ++++++++++++++++++++++
1 file changed, 51 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/sun6i-csi.txt
diff --git a/Documentation/devicetree/bindings/media/sun6i-csi.txt b/Documentation/devicetree/bindings/media/sun6i-csi.txt
new file mode 100644
index 0000000..b5bfe3f
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/sun6i-csi.txt
@@ -0,0 +1,51 @@
+Allwinner V3s Camera Sensor Interface
+------------------------------
+
+Required properties:
+ - compatible: value must be "allwinner,sun8i-v3s-csi"
+ - reg: base address and size of the memory-mapped region.
+ - interrupts: interrupt associated to this IP
+ - clocks: phandles to the clocks feeding the CSI
+ * bus: the CSI interface clock
+ * mod: the CSI module clock
+ * ram: the CSI DRAM clock
+ - clock-names: the clock names mentioned above
+ - resets: phandles to the reset line driving the CSI
+
+- ports: A ports node with endpoint definitions as defined in
+ Documentation/devicetree/bindings/media/video-interfaces.txt.
+ Currently, the driver only support the parallel interface. So, a single port
+ node with one endpoint and parallel bus is supported.
+
+Example:
+
+ csi1: csi@1cb4000 {
+ compatible = "allwinner,sun8i-v3s-csi";
+ reg = <0x01cb4000 0x1000>;
+ interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_CSI>,
+ <&ccu CLK_CSI1_SCLK>,
+ <&ccu CLK_DRAM_CSI>;
+ clock-names = "bus", "mod", "ram";
+ resets = <&ccu RST_BUS_CSI>;
+
+ port {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* Parallel bus endpoint */
+ csi1_ep: endpoint {
+ remote-endpoint = <&adv7611_ep>;
+ bus-width = <16>;
+ data-shift = <0>;
+
+ /* If hsync-active/vsync-active are missing,
+ embedded BT.656 sync is used */
+ hsync-active = <0>; /* Active low */
+ vsync-active = <0>; /* Active low */
+ data-active = <1>; /* Active high */
+ pclk-sample = <1>; /* Rising */
+ };
+ };
+ };
+
--
1.8.3.1
^ permalink raw reply related
* [PATCH v4 0/2] Initial Allwinner V3s CSI Support
From: Yong Deng @ 2017-12-22 9:32 UTC (permalink / raw)
To: "Maxime Ripard
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
David S. Miller, Greg Kroah-Hartman, Randy Dunlap, Hans Verkuil,
Stanimir Varbanov, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Arnd Bergmann, Benjamin Gaignard, Ramesh Shanmugasundaram,
Sakari Ailus, Rick Chang, linux-media, devicetree,
linux-arm-kernel, linux-kernel, linux-sunxi, Yong Deng
This patchset add initial support for Allwinner V3s CSI.
Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
and CSI1 is used for parallel interface. This is not documented in
datasheet but by testing and guess.
This patchset implement a v4l2 framework driver and add a binding
documentation for it.
Currently, the driver only support the parallel interface. And has been
tested with a BT1120 signal which generating from FPGA. The following
fetures are not support with this patchset:
- ISP
- MIPI-CSI2
- Master clock for camera sensor
- Power regulator for the front end IC
Thanks for Ondřej Jirman's help.
Changes in v4:
* Deal with the CSI 'INNER QUEUE'.
CSI will lookup the next dma buffer for next frame before the
the current frame done IRQ triggered. This is not documented
but reported by Ondřej Jirman.
The BSP code has workaround for this too. It skip to mark the
first buffer as frame done for VB2 and pass the second buffer
to CSI in the first frame done ISR call. Then in second frame
done ISR call, it mark the first buffer as frame done for VB2
and pass the third buffer to CSI. And so on. The bad thing is
that the first buffer will be written twice and the first frame
is dropped even the queued buffer is sufficient.
So, I make some improvement here. Pass the next buffer to CSI
just follow starting the CSI. In this case, the first frame
will be stored in first buffer, second frame in second buffer.
This mothed is used to avoid dropping the first frame, it
would also drop frame when lacking of queued buffer.
* Fix: using a wrong mbus_code when getting the supported formats
* Change all fourcc to pixformat
* Change some function names
Changes in v3:
* Get rid of struct sun6i_csi_ops
* Move sun6i-csi to new directory drivers/media/platform/sunxi
* Merge sun6i_csi.c and sun6i_csi_v3s.c into sun6i_csi.c
* Use generic fwnode endpoints parser
* Only support a single subdev to make things simple
* Many complaintion fix
Changes in v2:
* Change sunxi-csi to sun6i-csi
* Rebase to media_tree master branch
Following is the 'v4l2-compliance -s -f' output, I have test this
with both interlaced and progressive signal:
# ./v4l2-compliance -s -f
v4l2-compliance SHA : 6049ea8bd64f9d78ef87ef0c2b3dc9b5de1ca4a1
Driver Info:
Driver name : sun6i-video
Card type : sun6i-csi
Bus info : platform:csi
Driver version: 4.15.0
Capabilities : 0x84200001
Video Capture
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x04200001
Video Capture
Streaming
Extended Pix Format
Compliance test for device /dev/video0 (not using libv4l2):
Required ioctls:
test VIDIOC_QUERYCAP: OK
Allow for multiple opens:
test second video open: OK
test VIDIOC_QUERYCAP: OK
test VIDIOC_G/S_PRIORITY: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
test VIDIOC_LOG_STATUS: OK (Not Supported)
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 1 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)
Test input 0:
Control ioctls:
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
test VIDIOC_QUERYCTRL: OK (Not Supported)
test VIDIOC_G/S_CTRL: OK (Not Supported)
test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 0 Private Controls: 0
Format ioctls:
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK
test VIDIOC_TRY_FMT: OK
test VIDIOC_S_FMT: OK
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK (Not Supported)
Codec ioctls:
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls:
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
test VIDIOC_EXPBUF: OK
Test input 0:
Streaming ioctls:
test read/write: OK (Not Supported)
test MMAP: OK
test USERPTR: OK (Not Supported)
test DMABUF: Cannot test, specify --expbuf-device
Stream using all formats:
test MMAP for Format HM12, Frame Size 1280x720:
Stride 1920, Field None: OK
test MMAP for Format NV12, Frame Size 1280x720:
Stride 1920, Field None: OK
test MMAP for Format NV21, Frame Size 1280x720:
Stride 1920, Field None: OK
test MMAP for Format YU12, Frame Size 1280x720:
Stride 1920, Field None: OK
test MMAP for Format YV12, Frame Size 1280x720:
Stride 1920, Field None: OK
test MMAP for Format NV16, Frame Size 1280x720:
Stride 2560, Field None: OK
test MMAP for Format NV61, Frame Size 1280x720:
Stride 2560, Field None: OK
test MMAP for Format 422P, Frame Size 1280x720:
Stride 2560, Field None: OK
Total: 54, Succeeded: 54, Failed: 0, Warnings: 0
Yong Deng (2):
dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI)
media: V3s: Add support for Allwinner CSI.
.../devicetree/bindings/media/sun6i-csi.txt | 51 ++
MAINTAINERS | 8 +
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/sunxi/sun6i-csi/Kconfig | 9 +
drivers/media/platform/sunxi/sun6i-csi/Makefile | 3 +
drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c | 878 +++++++++++++++++++++
drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h | 147 ++++
.../media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h | 203 +++++
.../media/platform/sunxi/sun6i-csi/sun6i_video.c | 752 ++++++++++++++++++
.../media/platform/sunxi/sun6i-csi/sun6i_video.h | 60 ++
11 files changed, 2114 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/sun6i-csi.txt
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/Kconfig
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/Makefile
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h
--
1.8.3.1
^ permalink raw reply
* Re: [PATCH] devicetree: Add video bus switch
From: Pavel Machek @ 2017-12-22 9:24 UTC (permalink / raw)
To: Laurent Pinchart
Cc: Sakari Ailus, robh+dt, devicetree, ivo.g.dimitrov.75, sre,
pali.rohar, linux-media, galak, mchehab, linux-kernel
In-Reply-To: <75694885.3PuLWzx4qN@avalon>
[-- Attachment #1: Type: text/plain, Size: 1945 bytes --]
Hi!
> > > I don't really object using g_endpoint_config() as a temporary solution;
> > > I'd like to have Laurent's opinion on that though. Another option is to
> > > wait, but we've already waited a looong time (as in total).
> >
> > Laurent, do you have some input here? We have simple "2 cameras
> > connected to one signal processor" situation here. We need some way of
> > passing endpoint configuration from the sensors through the switch. I
> > did this:
>
> Could you give me a bit more information about the platform you're targeting:
> how the switch is connected, what kind of switch it is, and what endpoint
> configuration data you need ?
Platform is Nokia N900, Ivaylo already gave pointer to schematics.
Switch is controlled using GPIO, and basically there's CSI
configuration that would normally be in the device tree, but now we
have two CSI configurations to select from...
> > 9) Highly reconfigurable hardware - Julien Beraud
> >
> > - 44 sub-devices connected with an interconnect.
> > - As long as formats match, any sub-device could be connected to any
> > - other sub-device through a link.
> > - The result is 44 * 44 links at worst.
> > - A switch sub-device proposed as the solution to model the
> > - interconnect. The sub-devices are connected to the switch
> > - sub-devices through the hardware links that connect to the
> > - interconnect.
> > - The switch would be controlled through new IOCTLs S_ROUTING and
> > - G_ROUTING.
> > - Patches available:
> > http://git.linuxtv.org/cgit.cgi/pinchartl/media.git/log/?h=xilinx-wip
> >
> > but the patches are from 2005. So I guess I'll need some guidance here...
>
> You made me feel very old for a moment. The patches are from 2015 :-)
Sorry about that :-).
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]
^ permalink raw reply
* Re: [PATCH 1/2] [media] Add Rockchip RK1608 driver
From: Philippe Ombredanne @ 2017-12-22 8:45 UTC (permalink / raw)
To: Leo Wen
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, David S. Miller,
Greg Kroah-Hartman, rdunlap, Linux Media Mailing List,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS, LKML,
open list:ARM/Rockchip SoC..., Eddie Cai, Jacob Chen
In-Reply-To: <CAFLEztTxXRQu-VJ2FzYbA_vkYZtkDur1nQ6goftdZdnbn63aQQ@mail.gmail.com>
Dear Leo,
On Fri, Dec 22, 2017 at 5:33 AM, Jacob Chen <jacobchen110@gmail.com> wrote:
> Hi leo,
>
>
> 2017-12-12 14:28 GMT+08:00 Leo Wen <leo.wen@rock-chips.com>:
>> Rk1608 is used as a PreISP to link on Soc, which mainly has two functions.
>> One is to download the firmware of RK1608, and the other is to match the
>> extra sensor such as camera and enable sensor by calling sensor's s_power.
>>
>> use below v4l2-ctl command to capture frames.
>>
>> v4l2-ctl --verbose -d /dev/video1 --stream-mmap=2
>> --stream-to=/tmp/stream.out --stream-count=60 --stream-poll
>>
>> use below command to playback the video on your PC.
>>
>> mplayer ./stream.out -loop 0 -demuxer rawvideo -rawvideo
>> w=640:h=480:size=$((640*480*3/2)):format=NV12
>>
>> Signed-off-by: Leo Wen <leo.wen@rock-chips.com>
<snip>
>> --- /dev/null
>> +++ b/drivers/media/spi/rk1608.c
>> @@ -0,0 +1,1165 @@
>> +/**
>> + * Rockchip rk1608 driver
>> + *
>> + * Copyright (C) 2017 Rockchip Electronics Co., Ltd.
>> + *
>> + * This software is available to you under a choice of one of two
>> + * licenses. You may choose to be licensed under the terms of the GNU
>> + * General Public License (GPL) Version 2, available from the file
>> + * COPYING in the main directory of this source tree, or the
>> + * OpenIB.org BSD license below:
>> + *
>> + * Redistribution and use in source and binary forms, with or
>> + * without modification, are permitted provided that the following
>> + * conditions are met:
>> + *
>> + * - Redistributions of source code must retain the above
>> + * copyright notice, this list of conditions and the following
>> + * disclaimer.
>> + *
>> + * - Redistributions in binary form must reproduce the above
>> + * copyright notice, this list of conditions and the following
>> + * disclaimer in the documentation and/or other materials
>> + * provided with the distribution.
>> + *
>> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
>> + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
>> + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
>> + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
>> + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
>> + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
>> + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
>> + * SOFTWARE.
>> + */
Have you considered using the new SPDX tags instead of this fine but
long legalese?
I know what you are about to say: everyone loves legalese so much that
it is hard to let go of so much of it and replace all this only with a
single SPDX tag line ;)
But then everyone loves code much more than legalese too! so you would
be making the world a service anyway.
And if other contributors in your team could follow suit and you could
spread the word that would be even better!
See Thomas doc patches [1] for details.
[1] https://lkml.org/lkml/2017/12/4/934
--
Cordially
Philippe Ombredanne
^ permalink raw reply
* cron job: media_tree daily build: ERRORS
From: Hans Verkuil @ 2017-12-22 4:54 UTC (permalink / raw)
To: linux-media
This message is generated daily by a cron job that builds media_tree for
the kernels and architectures in the list below.
Results of the daily build of media_tree:
date: Fri Dec 22 05:00:15 CET 2017
media-tree git hash: ae49432810c5cca2143afc1445edad6582c9f270
media_build git hash: fd6010ac1bcdf54a3a2b2752088def1b21b5e457
v4l-utils git hash: 6049ea8bd64f9d78ef87ef0c2b3dc9b5de1ca4a1
gcc version: i686-linux-gcc (GCC) 7.1.0
sparse version: v0.5.0-3911-g6f737e1f
smatch version: v0.5.0-3911-g6f737e1f
host hardware: x86_64
host os: 4.13.0-164
linux-git-arm-at91: OK
linux-git-arm-davinci: OK
linux-git-arm-multi: OK
linux-git-arm-pxa: OK
linux-git-arm-stm32: OK
linux-git-blackfin-bf561: OK
linux-git-i686: OK
linux-git-m32r: OK
linux-git-mips: OK
linux-git-powerpc64: OK
linux-git-sh: OK
linux-git-x86_64: OK
linux-2.6.36.4-i686: WARNINGS
linux-2.6.37.6-i686: WARNINGS
linux-2.6.38.8-i686: WARNINGS
linux-2.6.39.4-i686: WARNINGS
linux-3.0.60-i686: WARNINGS
linux-3.1.10-i686: WARNINGS
linux-3.2.37-i686: WARNINGS
linux-3.3.8-i686: WARNINGS
linux-3.4.27-i686: WARNINGS
linux-3.5.7-i686: WARNINGS
linux-3.6.11-i686: WARNINGS
linux-3.7.4-i686: ERRORS
linux-3.8-i686: ERRORS
linux-3.9.2-i686: ERRORS
linux-3.10.1-i686: ERRORS
linux-3.11.1-i686: ERRORS
linux-3.12.67-i686: ERRORS
linux-3.13.11-i686: WARNINGS
linux-3.14.9-i686: WARNINGS
linux-3.15.2-i686: WARNINGS
linux-3.16.7-i686: WARNINGS
linux-3.17.8-i686: WARNINGS
linux-3.18.7-i686: WARNINGS
linux-3.19-i686: WARNINGS
linux-4.0.9-i686: WARNINGS
linux-4.1.33-i686: WARNINGS
linux-4.2.8-i686: ERRORS
linux-4.3.6-i686: WARNINGS
linux-4.4.22-i686: OK
linux-4.5.7-i686: WARNINGS
linux-4.6.7-i686: OK
linux-4.7.5-i686: OK
linux-4.8-i686: OK
linux-4.9.26-i686: OK
linux-4.10.14-i686: OK
linux-4.11-i686: OK
linux-4.12.1-i686: OK
linux-4.13-i686: OK
linux-4.14-i686: OK
linux-2.6.36.4-x86_64: WARNINGS
linux-2.6.37.6-x86_64: WARNINGS
linux-2.6.38.8-x86_64: WARNINGS
linux-2.6.39.4-x86_64: WARNINGS
linux-3.0.60-x86_64: WARNINGS
linux-3.1.10-x86_64: WARNINGS
linux-3.2.37-x86_64: WARNINGS
linux-3.3.8-x86_64: WARNINGS
linux-3.4.27-x86_64: WARNINGS
linux-3.5.7-x86_64: WARNINGS
linux-3.6.11-x86_64: WARNINGS
linux-3.7.4-x86_64: ERRORS
linux-3.8-x86_64: ERRORS
linux-3.9.2-x86_64: ERRORS
linux-3.10.1-x86_64: ERRORS
linux-3.11.1-x86_64: ERRORS
linux-3.12.67-x86_64: ERRORS
linux-3.13.11-x86_64: WARNINGS
linux-3.14.9-x86_64: WARNINGS
linux-3.15.2-x86_64: WARNINGS
linux-3.16.7-x86_64: WARNINGS
linux-3.17.8-x86_64: WARNINGS
linux-3.18.7-x86_64: WARNINGS
linux-3.19-x86_64: WARNINGS
linux-4.0.9-x86_64: WARNINGS
linux-4.1.33-x86_64: WARNINGS
linux-4.2.8-x86_64: ERRORS
linux-4.3.6-x86_64: WARNINGS
linux-4.4.22-x86_64: WARNINGS
linux-4.5.7-x86_64: WARNINGS
linux-4.6.7-x86_64: WARNINGS
linux-4.7.5-x86_64: WARNINGS
linux-4.8-x86_64: WARNINGS
linux-4.9.26-x86_64: WARNINGS
linux-4.10.14-x86_64: WARNINGS
linux-4.11-x86_64: WARNINGS
linux-4.12.1-x86_64: WARNINGS
linux-4.13-x86_64: OK
linux-4.14-x86_64: OK
apps: OK
spec-git: OK
smatch: OK
Detailed results are available here:
http://www.xs4all.nl/~hverkuil/logs/Friday.log
Full logs are available here:
http://www.xs4all.nl/~hverkuil/logs/Friday.tar.bz2
The Media Infrastructure API from this daily build is here:
http://www.xs4all.nl/~hverkuil/spec/index.html
^ permalink raw reply
* Re: [PATCH 1/2] [media] Add Rockchip RK1608 driver
From: Jacob Chen @ 2017-12-22 4:33 UTC (permalink / raw)
To: Leo Wen
Cc: Mauro Carvalho Chehab, robh+dt, mark.rutland, davem, gregkh,
rdunlap, Linux Media Mailing List, devicetree, linux-kernel,
open list:ARM/Rockchip SoC..., Eddie Cai
In-Reply-To: <1513060095-29588-2-git-send-email-leo.wen@rock-chips.com>
Hi leo,
2017-12-12 14:28 GMT+08:00 Leo Wen <leo.wen@rock-chips.com>:
> Rk1608 is used as a PreISP to link on Soc, which mainly has two functions.
> One is to download the firmware of RK1608, and the other is to match the
> extra sensor such as camera and enable sensor by calling sensor's s_power.
>
> use below v4l2-ctl command to capture frames.
>
> v4l2-ctl --verbose -d /dev/video1 --stream-mmap=2
> --stream-to=/tmp/stream.out --stream-count=60 --stream-poll
>
> use below command to playback the video on your PC.
>
> mplayer ./stream.out -loop 0 -demuxer rawvideo -rawvideo
> w=640:h=480:size=$((640*480*3/2)):format=NV12
>
> Signed-off-by: Leo Wen <leo.wen@rock-chips.com>
> ---
> MAINTAINERS | 6 +
> drivers/media/spi/Makefile | 1 +
> drivers/media/spi/rk1608.c | 1165 ++++++++++++++++++++++++++++++++++++++++++++
> drivers/media/spi/rk1608.h | 366 ++++++++++++++
> 4 files changed, 1538 insertions(+)
> create mode 100644 drivers/media/spi/rk1608.c
> create mode 100644 drivers/media/spi/rk1608.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 82ad0ea..48235d8 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -128,6 +128,12 @@ Maintainers List (try to look for most precise areas first)
>
> -----------------------------------
>
> +ROCKCHIP RK1608 DRIVER
> +M: Leo Wen <leo.wen@rock-chips.com>
> +S: Maintained
> +F: drivers/media/platform/spi/rk1608.c
> +F: drivers/media/platform/spi/rk1608.h
> +
> 3C59X NETWORK DRIVER
> M: Steffen Klassert <klassert@mathematik.tu-chemnitz.de>
> L: netdev@vger.kernel.org
> diff --git a/drivers/media/spi/Makefile b/drivers/media/spi/Makefile
> index ea64013..9d9d9ec 100644
> --- a/drivers/media/spi/Makefile
> +++ b/drivers/media/spi/Makefile
> @@ -1 +1,2 @@
> obj-$(CONFIG_VIDEO_GS1662) += gs1662.o
> +obj-$(CONFIG_ROCKCHIP_RK1608) += rk1608.o
> diff --git a/drivers/media/spi/rk1608.c b/drivers/media/spi/rk1608.c
> new file mode 100644
> index 0000000..e646204
> --- /dev/null
> +++ b/drivers/media/spi/rk1608.c
> @@ -0,0 +1,1165 @@
> +/**
> + * Rockchip rk1608 driver
> + *
> + * Copyright (C) 2017 Rockchip Electronics Co., Ltd.
> + *
> + * This software is available to you under a choice of one of two
> + * licenses. You may choose to be licensed under the terms of the GNU
> + * General Public License (GPL) Version 2, available from the file
> + * COPYING in the main directory of this source tree, or the
> + * OpenIB.org BSD license below:
> + *
> + * Redistribution and use in source and binary forms, with or
> + * without modification, are permitted provided that the following
> + * conditions are met:
> + *
> + * - Redistributions of source code must retain the above
> + * copyright notice, this list of conditions and the following
> + * disclaimer.
> + *
> + * - Redistributions in binary form must reproduce the above
> + * copyright notice, this list of conditions and the following
> + * disclaimer in the documentation and/or other materials
> + * provided with the distribution.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
> + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
> + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
> + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
> + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> + * SOFTWARE.
> + */
> +#include <linux/delay.h>
> +#include <linux/firmware.h>
> +#include <linux/interrupt.h>
> +#include <linux/of_platform.h>
> +#include <linux/of_gpio.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-image-sizes.h>
> +#include <media/v4l2-mediabus.h>
> +#include <media/v4l2-of.h>
> +#include "rk1608.h"
> +
> +/**
> + * Rk1608 is used as the Pre-ISP to link on Soc, which mainly has two
> + * functions. One is to download the firmware of RK1608, and the other
> + * is to match the extra sensor such as camera and enable sensor by
> + * calling sensor's s_power.
> + * |-----------------------|
> + * | Sensor Camera |
> + * |-----------------------|
> + * |-----------||----------|
> + * |-----------||----------|
> + * |-----------\/----------|
> + * | Pre-ISP RK1608 |
> + * |-----------------------|
> + * |-----------||----------|
> + * |-----------||----------|
> + * |-----------\/----------|
> + * | Rockchip Soc |
> + * |-----------------------|
> + * Data Transfer As shown above. In RK1608, the data received from the
> + * extra sensor,and it is passed to the Soc through ISP.
> + */
> +struct rk1608_state {
> + struct v4l2_subdev sd;
> + struct v4l2_subdev *sensor_sd;
> + struct device *dev;
> + struct spi_device *spi;
> + struct media_pad pad;
> + struct clk *mclk;
> + struct mutex lock; /* protect resource */
> + struct mutex sensor_lock; /* protect sensor */
> + struct mutex send_msg_lock; /* protect msg */
> + int power_count;
> + int reset_gpio;
> + int reset_active;
> + int irq_gpio;
> + int irq;
> + int sleepst_gpio;
> + int sleepst_irq;
> + int wakeup_gpio;
> + int wakeup_active;
> + int powerdown_gpio;
> + int powerdown_active;
> + int msg_num;
> + u32 sensor_cnt;
> + u32 sensor_nums;
> + u32 max_speed_hz;
> + u32 min_speed_hz;
> + atomic_t msg_done[8];
> + wait_queue_head_t msg_wait;
> + struct v4l2_ctrl *link_freq;
> + struct v4l2_ctrl_handler ctrl_handler;
> +};
> +
> +static const s64 link_freq_menu_items[] = {
> + 1000000000
> +};
> +
> +static inline struct rk1608_state *to_state(struct v4l2_subdev *sd)
> +{
> + return container_of(sd, struct rk1608_state, sd);
> +}
> +
> +/**
> + * rk1608_operation_query - RK1608 last operation state query
> + *
> + * @spi: device from which data will be read
> + * @state: last operation state [out]
> + * Context: can sleep
> + *
> + * It returns zero on success, else a negative error code.
> + */
> +int rk1608_operation_query(struct spi_device *spi, s32 *state)
> +{
> + s32 query_cmd = RK1608_CMD_QUERY;
> + struct spi_transfer query_cmd_packet = {
> + .tx_buf = &query_cmd,
> + .len = sizeof(query_cmd),
> + };
> + struct spi_transfer state_packet = {
> + .rx_buf = state,
> + .len = sizeof(*state),
> + };
> + struct spi_message m;
> +
> + spi_message_init(&m);
> + spi_message_add_tail(&query_cmd_packet, &m);
> + spi_message_add_tail(&state_packet, &m);
> + spi_sync(spi, &m);
> +
> + return ((*state & RK1608_STATE_ID_MASK) == RK1608_STATE_ID) ? 0 : -1;
> +}
> +
> +int rk1608_write(struct spi_device *spi,
> + s32 addr, const s32 *data, size_t data_len)
> +{
> + s32 write_cmd = RK1608_CMD_WRITE;
> + struct spi_transfer write_cmd_packet = {
> + .tx_buf = &write_cmd,
> + .len = sizeof(write_cmd),
> + };
> + struct spi_transfer addr_packet = {
> + .tx_buf = &addr,
> + .len = sizeof(addr),
> + };
> + struct spi_transfer data_packet = {
> + .tx_buf = data,
> + .len = data_len,
> + };
> + struct spi_message m;
> +
> + spi_message_init(&m);
> + spi_message_add_tail(&write_cmd_packet, &m);
> + spi_message_add_tail(&addr_packet, &m);
> + spi_message_add_tail(&data_packet, &m);
> + return spi_sync(spi, &m);
> +}
> +
> +/**
> + * rk1608_safe_write - RK1608 synchronous write with state check
> + *
> + * @spi: spi device
> + * @addr: resource address
> + * @data: data buffer
> + * @data_len: data buffer size, in bytes
> + * Context: can sleep
> + *
> + * It returns zero on success, else operation state code.
> + */
> +int rk1608_safe_write(struct spi_device *spi,
> + s32 addr, const s32 *data, size_t data_len)
> +{
> + int ret = 0;
> + s32 state, retry = 0;
> +
> + while (data_len > 0) {
> + size_t slen = MIN(data_len, RK1608_MAX_OP_BYTES);
> +
> + do {
> + rk1608_write(spi, addr, data, data_len);
> + if (rk1608_operation_query(spi, &state) != 0)
> + return -1;
> + if ((state & RK1608_STATE_MASK) == 0)
> + break;
> +
> + udelay(RK1608_OP_TRY_DELAY);
> + } while (retry++ != RK1608_OP_TRY_MAX);
> +
> + data_len = data_len - slen;
> + data = (s32 *)((s8 *)data + slen);
> + addr += slen;
> + }
> + return ret;
> +}
> +
> +void rk1608_hw_init(struct spi_device *spi)
> +{
> + s32 write_data = SPI0_PLL_SEL_APLL;
> +
> + /* modify rk1608 spi slave clk to 300M */
> + rk1608_safe_write(spi, CRUPMU_CLKSEL14_CON, &write_data, 4);
> +
> + /* modify rk1608 spi io driver strength to 8mA */
> + write_data = BIT7_6_SEL_8MA;
> + rk1608_safe_write(spi, PMUGRF_GPIO1A_E, &write_data, 4);
> + write_data = BIT1_0_SEL_8MA;
> + rk1608_safe_write(spi, PMUGRF_GPIO1B_E, &write_data, 4);
> +}
> +
> +/**
> + * rk1608_read - RK1608 synchronous read
> + *
> + * @spi: spi device
> + * @addr: resource address
> + * @data: data buffer [out]
> + * @data_len: data buffer size, in bytes
> + * Context: can sleep
> + *
> + * It returns zero on success, else a negative error code.
> + */
> +int rk1608_read(struct spi_device *spi,
> + s32 addr, s32 *data, size_t data_len)
> +{
> + s32 real_len = MIN(data_len, RK1608_MAX_OP_BYTES);
> + s32 read_cmd = RK1608_CMD_READ | (real_len << 14 &
> + RK1608_STATE_ID_MASK);
> + s32 read_begin_cmd = RK1608_CMD_READ_BEGIN;
> + s32 dummy = 0;
> + struct spi_transfer read_cmd_packet = {
> + .tx_buf = &read_cmd,
> + .len = sizeof(read_cmd),
> + };
> + struct spi_transfer addr_packet = {
> + .tx_buf = &addr,
> + .len = sizeof(addr),
> + };
> + struct spi_transfer read_dummy_packet = {
> + .tx_buf = &dummy,
> + .len = sizeof(dummy),
> + };
> + struct spi_transfer read_begin_cmd_packet = {
> + .tx_buf = &read_begin_cmd,
> + .len = sizeof(read_begin_cmd),
> + };
> + struct spi_transfer data_packet = {
> + .rx_buf = data,
> + .len = data_len,
> + };
> + struct spi_message m;
> +
> + spi_message_init(&m);
> + spi_message_add_tail(&read_cmd_packet, &m);
> + spi_message_add_tail(&addr_packet, &m);
> + spi_message_add_tail(&read_dummy_packet, &m);
> + spi_message_add_tail(&read_begin_cmd_packet, &m);
> + spi_message_add_tail(&data_packet, &m);
> + return spi_sync(spi, &m);
> +}
> +
> +/**
> + * rk1608_safe_read - RK1608 synchronous read with state check
> + *
> + * @spi: spi device
> + * @addr: resource address
> + * @data: data buffer [out]
> + * @data_len: data buffer size, in bytes
> + * Context: can sleep
> + *
> + * It returns zero on success, else operation state code.
> + */
> +int rk1608_safe_read(struct spi_device *spi,
> + s32 addr, s32 *data, size_t data_len)
> +{
> + s32 state = 0;
> + s32 retry = 0;
> +
> + do {
> + rk1608_read(spi, addr, data, data_len);
> + if (rk1608_operation_query(spi, &state) != 0)
> + return -1;
> + if ((state & RK1608_STATE_MASK) == 0)
> + break;
> + udelay(RK1608_OP_TRY_DELAY);
> + } while (retry++ != RK1608_OP_TRY_MAX);
> +
> + return (state & RK1608_STATE_MASK);
> +}
> +
> +static int rk1608_read_wait(struct spi_device *spi,
> + const struct rk1608_section *sec)
> +{
> + s32 value = 0;
> + int retry = 0;
> + int ret = 0;
> +
> + do {
> + ret = rk1608_safe_read(spi, sec->wait_addr, &value, 4);
> + if (!ret && value == sec->wait_value)
> + break;
> +
> + if (retry++ == sec->timeout) {
> + ret = -1;
> + dev_err(&spi->dev, "read 0x%x is %x != %x timeout\n",
> + sec->wait_addr, value, sec->wait_value);
> + break;
> + }
> + mdelay(sec->wait_time);
> + } while (1);
> +
> + return ret;
> +}
> +
> +static int rk1608_boot_request(struct spi_device *spi,
> + const struct rk1608_section *sec)
> +{
> + struct rk1608_boot_req boot_req;
> + int retry = 0;
> + int ret = 0;
> +
> + /*send boot request to rk1608 for ddr init*/
> + boot_req.flag = sec->flag;
> + boot_req.load_addr = sec->load_addr;
> + boot_req.boot_len = sec->size;
> + boot_req.status = 1;
> + boot_req.cmd = 2;
> +
> + ret = rk1608_safe_write(spi, BOOT_REQUEST_ADDR,
> + (s32 *)&boot_req, sizeof(boot_req));
> + if (ret)
> + return ret;
> +
> + if (sec->flag & BOOT_FLAG_READ_WAIT) {
> + /*waitting for rk1608 init ddr done*/
> + do {
> + ret = rk1608_safe_read(spi, BOOT_REQUEST_ADDR,
> + (s32 *)&boot_req,
> + sizeof(boot_req));
> +
> + if (!ret && boot_req.status == 0)
> + break;
> +
> + if (retry++ == sec->timeout) {
> + ret = -1;
> + dev_err(&spi->dev, "boot_request timeout\n");
> + break;
> + }
> + mdelay(sec->wait_time);
> + } while (1);
> + }
> +
> + return ret;
> +}
> +
> +static int rk1608_download_section(struct spi_device *spi, const u8 *data,
> + const struct rk1608_section *sec)
> +{
> + int ret = 0;
> +
> + dev_info(&spi->dev, "offset:%x,size:%x,addr:%x,wait_time:%x",
> + sec->offset, sec->size, sec->load_addr, sec->wait_time);
> + dev_info(&spi->dev, "timeout:%x,crc:%x,flag:%x,type:%x",
> + sec->timeout, sec->crc_16, sec->flag, sec->type);
> +
> + if (sec->size > 0) {
> + ret = rk1608_safe_write(spi, sec->load_addr,
> + (s32 *)(data + sec->offset),
> + sec->size);
> + if (ret) {
> + dev_err(&spi->dev, "rk1608_safe_write err =%d\n", ret);
> + return ret;
> + }
> + }
> +
> + if (sec->flag & BOOT_FLAG_BOOT_REQUEST)
> + ret = rk1608_boot_request(spi, sec);
> + else if (sec->flag & BOOT_FLAG_READ_WAIT)
> + ret = rk1608_read_wait(spi, sec);
> +
> + return ret;
> +}
> +
> +/**
> + * rk1608_download_fw: - rk1608 firmware download through spi
> + *
> + * @spi: spi device
> + * @fw_name: name of firmware file, NULL for default firmware name
> + * Context: can sleep
> + *
> + * It returns zero on success, else a negative error code.
> + **/
> +int rk1608_download_fw(struct spi_device *spi, const char *fw_name)
> +{
> + const struct rk1608_header *head;
> + const struct firmware *fw;
> + int i = 0;
> + int ret = 0;
> +
> + if (!fw_name)
> + fw_name = RK1608_FW_NAME;
> +
> + dev_info(&spi->dev, "before request firmware");
> + ret = request_firmware(&fw, fw_name, &spi->dev);
> + if (ret) {
> + dev_err(&spi->dev, "request firmware %s failed!", fw_name);
> + return ret;
> + }
> +
> + head = (const struct rk1608_header *)fw->data;
> +
> + dev_info(&spi->dev, "request firmware %s (version:%s) success!",
> + fw_name, head->version);
> +
> + for (i = 0; i < head->section_count; i++) {
> + ret = rk1608_download_section(spi, fw->data,
> + &head->sections[i]);
> + if (ret)
> + break;
> + }
> +
> + release_firmware(fw);
> + return ret;
> +}
> +
> +int rk1608_lsb_w32(struct spi_device *spi, s32 addr, s32 data)
> +{
> + s32 write_cmd = RK1608_CMD_WRITE;
> + struct spi_transfer write_cmd_packet = {
> + .tx_buf = &write_cmd,
> + .len = sizeof(write_cmd),
> + };
> + struct spi_transfer addr_packet = {
> + .tx_buf = &addr,
> + .len = sizeof(addr),
> + };
> + struct spi_transfer data_packet = {
> + .tx_buf = &data,
> + .len = sizeof(data),
> + };
> + struct spi_message m;
> +
> + write_cmd = MSB2LSB32(write_cmd);
> + addr = MSB2LSB32(addr);
> + data = MSB2LSB32(data);
> + spi_message_init(&m);
> + spi_message_add_tail(&write_cmd_packet, &m);
> + spi_message_add_tail(&addr_packet, &m);
> + spi_message_add_tail(&data_packet, &m);
> + return spi_sync(spi, &m);
> +}
> +
> +void rk1608_cs_set_value(struct rk1608_state *pdata, int value)
> +{
> + s8 null_cmd = 0;
> + struct spi_transfer null_cmd_packet = {
> + .tx_buf = &null_cmd,
> + .len = sizeof(null_cmd),
> + .cs_change = !value,
> + };
> + struct spi_message m;
> +
> + spi_message_init(&m);
> + spi_message_add_tail(&null_cmd_packet, &m);
> + spi_sync(pdata->spi, &m);
> +}
> +
> +void rk1608_set_spi_speed(struct rk1608_state *pdata, u32 hz)
> +{
> + pdata->spi->max_speed_hz = hz;
> +}
> +
> +static int rk1608_sensor_power(struct v4l2_subdev *sd, int on)
> +{
> + int ret = 0;
> + struct rk1608_state *pdata = to_state(sd);
> + struct spi_device *spi = pdata->spi;
> +
> + mutex_lock(&pdata->lock);
> + /*start Sensor power on/off*/
> + if (pdata->sensor_sd)
> + pdata->sensor_sd->ops->core->s_power(pdata->sensor_sd, on);
> + if (on && !pdata->power_count) {
> + clk_prepare_enable(pdata->mclk);
> + clk_set_rate(pdata->mclk, RK1608_MCLK_RATE);
> + /*request rk1608 enter slave mode*/
> + rk1608_cs_set_value(pdata, 0);
> + if (pdata->powerdown_gpio > 0) {
> + gpio_set_value(pdata->powerdown_gpio,
> + pdata->powerdown_active);
> + }
> + if (pdata->wakeup_gpio > 0) {
> + gpio_set_value(pdata->wakeup_gpio,
> + pdata->wakeup_active);
> + }
> + mdelay(3);
> + if (pdata->reset_gpio > 0)
> + gpio_set_value(pdata->reset_gpio, pdata->reset_active);
> + mdelay(5);
> + rk1608_cs_set_value(pdata, 1);
> + rk1608_set_spi_speed(pdata, pdata->min_speed_hz);
> + rk1608_lsb_w32(spi, SPI_ENR, 0);
> + rk1608_lsb_w32(spi, SPI_CTRL0,
> + OPM_SLAVE_MODE | RSD_SEL_2CYC | DFS_SEL_16BIT);
> + rk1608_hw_init(pdata->spi);
> + rk1608_set_spi_speed(pdata, pdata->max_speed_hz);
> + /*download system firmware*/
> + ret = rk1608_download_fw(pdata->spi, NULL);
> + if (ret)
> + dev_err(pdata->dev, "Download firmware failed!");
> + else
> + dev_info(pdata->dev, "Download firmware success!");
> + enable_irq(pdata->irq);
> + if (pdata->sleepst_irq > 0)
> + enable_irq(pdata->sleepst_irq);
> +
> + } else if (!on && pdata->power_count == 1) {
> + disable_irq(pdata->irq);
> + if (pdata->sleepst_irq > 0)
> + disable_irq(pdata->sleepst_irq);
> + if (pdata->powerdown_gpio > 0)
> + gpio_set_value(pdata->powerdown_gpio,
> + !pdata->powerdown_active);
> +
> + if (pdata->wakeup_gpio > 0)
> + gpio_set_value(pdata->wakeup_gpio,
> + !pdata->wakeup_active);
> +
> + if (pdata->reset_gpio > 0)
> + gpio_set_value(pdata->reset_gpio, !pdata->reset_active);
> +
> + rk1608_cs_set_value(pdata, 0);
> + clk_disable_unprepare(pdata->mclk);
> + }
> + /* Update the power count. */
> + pdata->power_count += on ? 1 : -1;
> + WARN_ON(pdata->power_count < 0);
> + mutex_unlock(&pdata->lock);
> +
> + return ret;
> +}
> +
> +static int rk1608_stream_on(struct rk1608_state *pdata)
> +{
> + int cnt = 0;
> +
> + /*Waiting for the sensor to be ready*/
> + while (pdata->sensor_cnt < pdata->sensor_nums) {
> + /* TIMEOUT 10s break*/
> + if (cnt++ > SENSOR_TIMEOUT) {
> + dev_err(pdata->dev, "Sensor%d is ready to timeout!",
> + pdata->sensor_cnt);
> + break;
> + }
> + mdelay(10);
> + }
> +
> + if (pdata->sensor_nums) {
> + if (pdata->sensor_cnt == pdata->sensor_nums)
> + dev_info(pdata->dev, "Sensor(num %d) is ready!",
> + pdata->sensor_cnt);
> + } else {
> + dev_warn(pdata->dev, "No sensor is found!");
> + }
> +
> + return 0;
> +}
> +
> +static int rk1608_stream_off(struct rk1608_state *pdata)
> +{
> + mutex_lock(&pdata->sensor_lock);
> + pdata->sensor_cnt = 0;
> + mutex_unlock(&pdata->sensor_lock);
> + return 0;
> +}
> +
> +static int rk1608_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> + struct rk1608_state *pdata = to_state(sd);
> +
> + if (enable)
> + return rk1608_stream_on(pdata);
> + else
> + return rk1608_stream_off(pdata);
> +}
> +
> +static int rk1608_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + if (code->index > 0)
> + return -EINVAL;
> +
> + code->code = MEDIA_BUS_FMT_SGRBG8_1X8;
> +
> + return 0;
> +}
> +
> +static int rk1608_get_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_format *fmt)
> +{
> + struct v4l2_mbus_framefmt *mf = &fmt->format;
> +
> + mf->code = MEDIA_BUS_FMT_SGRBG8_1X8;
> + mf->width = RK1608_WINDOW_WIDTH_DEF;
> + mf->height = RK1608_WINDOW_HEIGHT_DEF;
> + mf->field = V4L2_FIELD_NONE;
> + mf->colorspace = V4L2_COLORSPACE_SRGB;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_internal_ops rk1608_subdev_internal_ops = {
> + .open = NULL,
> +};
> +
> +static const struct v4l2_subdev_video_ops rk1608_subdev_video_ops = {
> + .s_stream = rk1608_s_stream,
> +};
> +
> +static const struct v4l2_subdev_pad_ops rk1608_subdev_pad_ops = {
> + .enum_mbus_code = rk1608_enum_mbus_code,
> + .get_fmt = rk1608_get_fmt,
> +};
> +
> +static const struct v4l2_subdev_core_ops rk1608_core_ops = {
> + .s_power = rk1608_sensor_power,
> +};
> +
> +static const struct v4l2_subdev_ops rk1608_subdev_ops = {
> + .core = &rk1608_core_ops,
> + .video = &rk1608_subdev_video_ops,
> + .pad = &rk1608_subdev_pad_ops,
> +};
> +
> +/**
> + * rk1608_msq_read_head - read rk1608 msg queue head
> + *
> + * @spi: spi device
> + * @addr: msg queue head addr
> + * @m: msg queue pointer
> + *
> + * It returns zero on success, else a negative error code.
> + */
> +int rk1608_msq_read_head(struct spi_device *spi,
> + u32 addr, struct rk1608_msg_queue *q)
> +{
> + int err = 0;
> + s32 reg;
> +
> + err = rk1608_safe_read(spi, RK1608_PMU_SYS_REG0, ®, 4);
> +
> + if (err || ((reg & RK1608_MSG_QUEUE_OK_MASK) !=
> + RK1608_MSG_QUEUE_OK_TAG))
> + return -1;
> +
> + err = rk1608_safe_read(spi, addr, (s32 *)q, sizeof(*q));
> +
> + return err;
> +}
> +
> +/**
> + * rk1608_msq_recv_msg - receive a msg from RK1608 -> AP msg queue
> + *
> + * @q: msg queue
> + * @m: a msg pointer buf [out]
> + *
> + * need call rk1608_msq_recv_msg_free to free msg after msg use done
> + *
> + * It returns zero on success, else a negative error code.
> + */
> +int rk1608_msq_recv_msg(struct spi_device *spi, struct msg **m)
> +{
> + struct rk1608_msg_queue queue;
> + struct rk1608_msg_queue *q = &queue;
> + u32 size = 0, msg_size = 0;
> + u32 recv_addr = 0;
> + u32 next_recv_addr = 0;
> + int err = 0;
> +
> + *m = NULL;
> + err = rk1608_msq_read_head(spi, RK1608_S_MSG_QUEUE_ADDR, q);
> + if (err)
> + return err;
> +
> + if (q->cur_send == q->cur_recv)
> + return -1;
> + /*skip to head when size is 0*/
> + err = rk1608_safe_read(spi, (s32)q->cur_recv, (s32 *)&size, 4);
> + if (err)
> + return err;
> + if (size == 0) {
> + err = rk1608_safe_read(spi, (s32)q->buf_head, (s32 *)&size, 4);
> + if (err)
> + return err;
> +
> + msg_size = size * sizeof(u32);
> + recv_addr = q->buf_head;
> + next_recv_addr = q->buf_head + msg_size;
> + } else {
> + msg_size = size * sizeof(u32);
> + recv_addr = q->cur_recv;
> + next_recv_addr = q->cur_recv + msg_size;
> + if (next_recv_addr == q->buf_tail)
> + next_recv_addr = q->buf_head;
> + }
> +
> + if (msg_size > (q->buf_tail - q->buf_head))
> + return -2;
> +
> + *m = kmalloc(msg_size, GFP_KERNEL);
> + err = rk1608_safe_read(spi, recv_addr, (s32 *)*m, msg_size);
> + if (err == 0) {
> + err = rk1608_safe_write(spi, RK1608_S_MSG_QUEUE_ADDR +
> + (u8 *)&q->cur_recv - (u8 *)q,
> + &next_recv_addr, 4);
> + }
> + if (err)
> + kfree(*m);
> +
> + return err;
> +}
> +
> +static void print_rk1608_log(struct rk1608_state *pdata,
> + struct msg_rk1608_log_t *log)
> +{
> + char *str = (char *)(log);
> +
> + str[log->size * sizeof(s32) - 1] = 0;
> + str += sizeof(struct msg_rk1608_log_t);
> + dev_info(pdata->dev, "RK1608%d: %s", log->core_id, str);
> +}
> +
> +void int32_hexdump(const char *prefix, int32_t *data, int len)
> +{
> + pr_err("%s\n", prefix);
> + print_hex_dump(KERN_ERR, "offset ", DUMP_PREFIX_OFFSET,
> + 16, 4, data, len, false);
> + pr_err("\n");
> +}
> +
> +static void dispatch_received_msg(struct rk1608_state *pdata,
> + struct msg *msg)
> +{
> + #if DEBUG_DUMP_ALL_SEND_RECV_MSG == 1
> + int32_hexdump("recv msg:", (s32 *)msg, msg->size * 4);
> + #endif
> +
> + if (msg->type == id_msg_set_stream_out_on_ret_t) {
> + mutex_lock(&pdata->sensor_lock);
> + pdata->sensor_cnt++;
> + mutex_unlock(&pdata->sensor_lock);
> + }
> +
> + if (msg->type == id_msg_rk1608_log_t)
> + print_rk1608_log(pdata, (struct msg_rk1608_log_t *)msg);
> +}
> +
> +static irqreturn_t rk1608_threaded_isr(int irq, void *dev_id)
> +{
> + struct rk1608_state *pdata = dev_id;
> + struct msg *msg;
> +
> + WARN_ON(irq != pdata->irq);
> + while (!rk1608_msq_recv_msg(pdata->spi, &msg) && NULL != msg) {
> + dispatch_received_msg(pdata, msg);
> + /* for kernel msg sync */
> + if (pdata->msg_num != 0 && msg->sync) {
> + dev_info(pdata->dev, "rk1608 kernel sync\n");
> + mutex_lock(&pdata->send_msg_lock);
> + pdata->msg_num--;
> + atomic_set(&pdata->msg_done[pdata->msg_num], 1);
> + mutex_unlock(&pdata->send_msg_lock);
> + wake_up(&pdata->msg_wait);
> + }
> + kfree(msg);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t rk1608_sleep_isr(int irq, void *dev_id)
> +{
> + struct rk1608_state *pdata = dev_id;
> +
> + WARN_ON(irq != pdata->sleepst_irq);
> + if (pdata->powerdown_gpio > 0)
> + gpio_set_value(pdata->powerdown_gpio, !pdata->powerdown_active);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int rk1608_parse_dt_property(struct rk1608_state *pdata)
> +{
> + int ret = 0;
> + int i;
> + struct device *dev = pdata->dev;
> + struct device_node *node = dev->of_node;
> + enum of_gpio_flags flags;
> +
> + if (!node)
> + return 1;
> +
> + ret = of_property_read_u32(node, "spi-max-frequency",
> + &pdata->max_speed_hz);
> + if (ret <= 0) {
> + dev_warn(dev, "can not get spi-max-frequency!");
> + pdata->max_speed_hz = RK1608_MCLK_RATE;
> + }
> +
> + ret = of_property_read_u32(node, "spi-min-frequency",
> + &pdata->min_speed_hz);
> + if (ret <= 0) {
> + dev_warn(dev, "can not get spi-min-frequency!");
> + pdata->min_speed_hz = pdata->max_speed_hz / 2;
> + }
> +
> + pdata->mclk = devm_clk_get(dev, "mclk");
> + if (IS_ERR(pdata->mclk)) {
> + dev_err(dev, "can not get mclk, error %ld\n",
> + PTR_ERR(pdata->mclk));
> + pdata->mclk = NULL;
> + return -1;
> + }
> +
> + ret = of_get_named_gpio_flags(node, "reset-gpio", 0, &flags);
> + if (ret <= 0) {
> + dev_warn(dev, "can not find reset-gpio, error %d\n", ret);
> + return ret;
> + }
> + pdata->reset_gpio = ret;
> + pdata->reset_active = 1;
> + if (flags == OF_GPIO_ACTIVE_LOW)
> + pdata->reset_active = 0;
> +
> + if (pdata->reset_gpio > 0) {
> + ret = devm_gpio_request(dev, pdata->reset_gpio, "rk1608-reset");
> + if (ret) {
> + dev_err(dev, "gpio %d request error %d\n",
> + pdata->reset_gpio, ret);
> + return ret;
> + }
> +
> + ret = gpio_direction_output(pdata->reset_gpio,
> + !pdata->reset_active);
> + if (ret) {
> + dev_err(dev, "gpio %d direction output error %d\n",
> + pdata->reset_gpio, ret);
> + return ret;
> + }
> + }
> +
> + ret = of_get_named_gpio_flags(node, "irq-gpio", 0, NULL);
> + if (ret <= 0) {
> + dev_warn(dev, "can not find irq-gpio, error %d\n", ret);
> + return ret;
> + }
> +
> + pdata->irq_gpio = ret;
> +
> + ret = devm_gpio_request(dev, pdata->irq_gpio, "rk1608-irq");
> + if (ret) {
> + dev_err(dev, "gpio %d request error %d\n", pdata->irq_gpio,
> + ret);
> + return ret;
> + }
> +
> + ret = gpio_direction_input(pdata->irq_gpio);
> + if (ret) {
> + dev_err(dev, "gpio %d direction input error %d\n",
> + pdata->irq_gpio, ret);
> + return ret;
> + }
> +
> + ret = gpio_to_irq(pdata->irq_gpio);
> + if (ret < 0) {
> + dev_err(dev, "Unable to get irq number for GPIO %d, error %d\n",
> + pdata->irq_gpio, ret);
> + return ret;
> + }
> + pdata->irq = ret;
> + ret = request_threaded_irq(pdata->irq, NULL, rk1608_threaded_isr,
> + IRQF_TRIGGER_RISING | IRQF_ONESHOT,
> + "rk1608-irq", pdata);
> + if (ret) {
> + dev_err(dev, "cannot request thread irq: %d\n", ret);
> + return ret;
> + }
> +
> + disable_irq(pdata->irq);
> +
> + ret = of_get_named_gpio_flags(node, "powerdown-gpio", 0, &flags);
> + if (ret <= 0)
> + dev_warn(dev, "can not find powerdown-gpio, error %d\n", ret);
> +
> + pdata->powerdown_gpio = ret;
> + pdata->powerdown_active = 1;
> + if (flags == OF_GPIO_ACTIVE_LOW)
> + pdata->powerdown_active = 0;
> +
> + if (pdata->powerdown_gpio > 0) {
> + ret = devm_gpio_request(dev, pdata->powerdown_gpio,
> + "rk1608-powerdown");
> + if (ret) {
> + dev_err(dev, "gpio %d request error %d\n",
> + pdata->powerdown_gpio, ret);
> + return ret;
> + }
> +
> + ret = gpio_direction_output(pdata->powerdown_gpio,
> + !pdata->powerdown_active);
> + if (ret) {
> + dev_err(dev, "gpio %d direction output error %d\n",
> + pdata->powerdown_gpio, ret);
> + return ret;
> + }
> + }
> +
> + pdata->sleepst_gpio = -1;
> + pdata->sleepst_irq = -1;
> + pdata->wakeup_gpio = -1;
> +
> + ret = of_get_named_gpio_flags(node, "sleepst-gpio", 0, NULL);
> + if (ret <= 0) {
> + dev_warn(dev, "can not find property sleepst-gpio, error %d\n",
> + ret);
> + return ret;
> + }
> +
> + pdata->sleepst_gpio = ret;
> +
> + ret = devm_gpio_request(dev, pdata->sleepst_gpio, "rk1608-sleep-irq");
> + if (ret) {
> + dev_err(dev, "gpio %d request error %d\n",
> + pdata->sleepst_gpio, ret);
> + return ret;
> + }
> +
> + ret = gpio_direction_input(pdata->sleepst_gpio);
> + if (ret) {
> + dev_err(dev, "gpio %d direction input error %d\n",
> + pdata->sleepst_gpio, ret);
> + return ret;
> + }
> +
> + ret = gpio_to_irq(pdata->sleepst_gpio);
> + if (ret < 0) {
> + dev_err(dev, "Unable to get irq number for GPIO %d, error %d\n",
> + pdata->sleepst_gpio, ret);
> + return ret;
> + }
> + pdata->sleepst_irq = ret;
> + ret = request_any_context_irq(pdata->sleepst_irq,
> + rk1608_sleep_isr,
> + IRQF_TRIGGER_RISING,
> + "rk1608-sleepst", pdata);
> + disable_irq(pdata->sleepst_irq);
> +
> + ret = of_get_named_gpio_flags(node, "wakeup-gpio", 0, &flags);
> + if (ret <= 0)
> + dev_warn(dev, "can not find wakeup-gpio error %d\n", ret);
> +
> + pdata->wakeup_gpio = ret;
> + pdata->wakeup_active = 1;
> + if (flags == OF_GPIO_ACTIVE_LOW)
> + pdata->wakeup_active = 0;
> +
> + if (pdata->wakeup_gpio > 0) {
> + ret = devm_gpio_request(dev, pdata->wakeup_gpio,
> + "rk1608-wakeup");
> + if (ret) {
> + dev_err(dev, "gpio %d request error %d\n",
> + pdata->wakeup_gpio, ret);
> + return ret;
> + }
> +
> + ret = gpio_direction_output(pdata->wakeup_gpio,
> + !pdata->wakeup_active);
> + if (ret) {
> + dev_err(dev, "gpio %d direction output error %d\n",
> + pdata->wakeup_gpio, ret);
> + return ret;
> + }
> + }
> + pdata->msg_num = 0;
> + init_waitqueue_head(&pdata->msg_wait);
> + for (i = 0; i < 8; i++)
> + atomic_set(&pdata->msg_done[i], 0);
> +
> + return ret;
> +}
> +
> +static int get_remote_node_dev(struct rk1608_state *pdev)
> +{
> + struct platform_device *sensor_pdev = NULL;
> + struct device *dev = pdev->dev;
> + struct device_node *parent = dev->of_node;
> + struct device_node *node, *pre_node = NULL;
> + struct device_node *remote = NULL;
> + int ret, sensor_nums = 0;
> +
> + node = of_graph_get_next_endpoint(parent, pre_node);
> + if (node) {
> + of_node_put(pre_node);
> + pre_node = node;
> + } else {
> + dev_err(dev, "fieled to get endpoint\n");
> + return -EINVAL;
> + }
> + while ((node = of_graph_get_next_endpoint(parent, pre_node)) != NULL) {
> + of_node_put(pre_node);
> + pre_node = node;
> + remote = of_graph_get_remote_port_parent(node);
> + if (!remote) {
> + dev_err(dev, "%s: no valid device\n", __func__);
> + of_node_put(remote);
> + ret = -EINVAL;
> + }
> +
> + sensor_pdev = of_find_device_by_node(remote);
> + of_node_put(remote);
> +
> + if (!sensor_pdev) {
> + dev_err(dev, "fieled to get Sensor device\n");
> + ret = -EINVAL;
> + } else {
> + pdev->sensor_sd = platform_get_drvdata(sensor_pdev);
> + if (pdev->sensor_sd)
> + sensor_nums++;
> + else
> + dev_err(dev, "fieled to get Sensor drvdata\n");
> + ret = 0;
> + }
> + }
> + pdev->sensor_nums = sensor_nums;
> + if (pdev->sensor_nums)
> + dev_info(dev, "get Sensor (nums=%d) dev is OK!\n",
> + pdev->sensor_nums);
> +
> + return ret;
> +}
> +
> +static int rk1608_probe(struct spi_device *spi)
> +{
> + struct rk1608_state *rk1608;
> + struct v4l2_subdev *sd;
> + struct v4l2_ctrl_handler *handler;
> + int ret;
> +
> + rk1608 = devm_kzalloc(&spi->dev, sizeof(*rk1608), GFP_KERNEL);
> + if (!rk1608)
> + return -ENOMEM;
> + rk1608->dev = &spi->dev;
> + rk1608->spi = spi;
> + spi_set_drvdata(spi, rk1608);
> + ret = rk1608_parse_dt_property(rk1608);
> + if (ret) {
> + dev_err(rk1608->dev, "rk1608 parse dt property err(%x)\n", ret);
> + goto parse_err;
> + }
> + ret = get_remote_node_dev(rk1608);
> + if (ret)
> + dev_warn(rk1608->dev, "get remote node dev err(%x)\n", ret);
> + rk1608->sensor_cnt = 0;
> + mutex_init(&rk1608->sensor_lock);
> + mutex_init(&rk1608->send_msg_lock);
> + mutex_init(&rk1608->lock);
> + sd = &rk1608->sd;
> + v4l2_spi_subdev_init(sd, spi, &rk1608_subdev_ops);
> +
> + handler = &rk1608->ctrl_handler;
> + ret = v4l2_ctrl_handler_init(handler, 1);
> + if (ret)
> + goto handler_init_err;
> +
> + rk1608->link_freq = v4l2_ctrl_new_int_menu(handler, NULL,
> + V4L2_CID_LINK_FREQ,
> + 0, 0, link_freq_menu_items);
> + if (rk1608->link_freq)
> + rk1608->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +
> + if (handler->error)
> + goto handler_err;
> +
> + sd->ctrl_handler = handler;
> + sd->internal_ops = &rk1608_subdev_internal_ops;
> + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +
> + rk1608->pad.flags = MEDIA_PAD_FL_SOURCE;
> + sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR;
> +
> + ret = media_entity_init(&sd->entity, 1, &rk1608->pad, 0);
> + if (ret < 0)
> + goto handler_err;
> +
> + ret = v4l2_async_register_subdev(sd);
> + if (ret < 0)
> + goto register_err;
> + dev_info(rk1608->dev, "DSP rk1608 Driver probe is OK!\n");
> +
> + return 0;
> +register_err:
> + media_entity_cleanup(&sd->entity);
> +handler_err:
> + v4l2_ctrl_handler_free(handler);
> +handler_init_err:
> + v4l2_device_unregister_subdev(&rk1608->sd);
> + mutex_destroy(&rk1608->lock);
> + mutex_destroy(&rk1608->send_msg_lock);
> + mutex_destroy(&rk1608->sensor_lock);
> +parse_err:
> + kfree(rk1608);
> + return ret;
> +}
> +
> +static int rk1608_remove(struct spi_device *spi)
> +{
> + struct rk1608_state *rk1608 = spi_get_drvdata(spi);
> +
> + v4l2_async_unregister_subdev(&rk1608->sd);
> + media_entity_cleanup(&rk1608->sd.entity);
> + v4l2_ctrl_handler_free(&rk1608->ctrl_handler);
> + v4l2_device_unregister_subdev(&rk1608->sd);
> + mutex_destroy(&rk1608->lock);
> + mutex_destroy(&rk1608->send_msg_lock);
> + mutex_destroy(&rk1608->sensor_lock);
> + kfree(rk1608);
> +
> + return 0;
> +}
> +
> +static const struct spi_device_id rk1608_id[] = {
> + { "RK1608", 0 },
> + { }
> +};
> +MODULE_DEVICE_TABLE(spi, rk1608_id);
> +
> +#if IS_ENABLED(CONFIG_OF)
> +static const struct of_device_id rk1608_of_match[] = {
> + { .compatible = "rockchip,rk1608" },
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, rk1608_of_match);
> +#endif
> +
> +static struct spi_driver rk1608_driver = {
> + .driver = {
> + .of_match_table = of_match_ptr(rk1608_of_match),
> + .name = "RK1608",
> + },
> + .probe = rk1608_probe,
> + .remove = rk1608_remove,
> + .id_table = rk1608_id,
> +};
> +
> +module_spi_driver(rk1608_driver);
> +
> +MODULE_AUTHOR("Rockchip Camera/ISP team");
> +MODULE_DESCRIPTION("A DSP driver for rk1608 chip");
> +MODULE_LICENSE("Dual BSD/GPL");
> diff --git a/drivers/media/spi/rk1608.h b/drivers/media/spi/rk1608.h
> new file mode 100644
> index 0000000..bf0c5ec
> --- /dev/null
> +++ b/drivers/media/spi/rk1608.h
> @@ -0,0 +1,366 @@
> +/**
> + * Rockchip rk1608 driver
> + *
> + * Copyright (C) 2017 Rockchip Electronics Co., Ltd.
> + *
> + * This software is available to you under a choice of one of two
> + * licenses. You may choose to be licensed under the terms of the GNU
> + * General Public License (GPL) Version 2, available from the file
> + * COPYING in the main directory of this source tree, or the
> + * OpenIB.org BSD license below:
> + *
> + * Redistribution and use in source and binary forms, with or
> + * without modification, are permitted provided that the following
> + * conditions are met:
> + *
> + * - Redistributions of source code must retain the above
> + * copyright notice, this list of conditions and the following
> + * disclaimer.
> + *
> + * - Redistributions in binary form must reproduce the above
> + * copyright notice, this list of conditions and the following
> + * disclaimer in the documentation and/or other materials
> + * provided with the distribution.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
> + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
> + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
> + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
> + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> + * SOFTWARE.
> + */
> +#ifndef __RK1608_H__
> +#define __RK1608_H__
> +
> +#include <linux/types.h>
> +#include <linux/string.h>
> +#include <linux/spi/spi.h>
> +#include "linux/i2c.h"
> +
> +#define RK1608_OP_TRY_MAX 3
> +#define RK1608_OP_TRY_DELAY 10
> +#define RK1608_CMD_WRITE 0x00000011
> +#define RK1608_CMD_WRITE_REG0 0X00010011
> +#define RK1608_CMD_WRITE_REG1 0X00020011
> +#define RK1608_CMD_READ 0x00000077
> +#define RK1608_CMD_READ_BEGIN 0x000000aa
> +#define RK1608_CMD_QUERY 0x000000ff
> +#define RK1608_CMD_QUERY_REG2 0x000001ff
> +#define RK1608_STATE_ID_MASK (0xffff0000)
> +#define RK1608_STATE_ID (0X16080000)
> +#define RK1608_STATE_MASK (0x0000ffff)
> +
> +#define BOOT_REQUEST_ADDR 0x18000010
> +#define RK1608_HEAD_ADDR 0x60000000
> +#define RK1608_FW_NAME "rk1608.rkl"
> +#define RK1608_S_MSG_QUEUE_ADDR 0x60050010
> +#define RK1608_PMU_SYS_REG0 0x120000f0
> +#define RK1608_MSG_QUEUE_OK_MASK 0xffff0001
> +#define RK1608_MSG_QUEUE_OK_TAG 0x16080001
> +#define RK1608_MAX_OP_BYTES 60000
> +
> +#define RK1608_WINDOW_HEIGHT_DEF 480
> +#define RK1608_WINDOW_WIDTH_DEF 640
> +
> +#define BOOT_FLAG_CRC (0x01 << 0)
> +#define BOOT_FLAG_EXE (0x01 << 1)
> +#define BOOT_FLAG_LOAD_PMEM (0x01 << 2)
> +#define BOOT_FLAG_ACK (0x01 << 3)
> +#define BOOT_FLAG_READ_WAIT (0x01 << 4)
> +#define BOOT_FLAG_BOOT_REQUEST (0x01 << 5)
> +
> +#define DEBUG_DUMP_ALL_SEND_RECV_MSG 0
> +#define RK1608_MCLK_RATE (24 * 1000 * 1000ul)
> +#define SENSOR_TIMEOUT 1000
> +#define GRF_BASE_ADDR 0xff770000
> +#define GRF_GPIO2B_IOMUX 0x0014
> +#define GRF_IO_VSEL 0x0380
> +#define OPM_SLAVE_MODE 0X100000
> +#define RSD_SEL_2CYC 0X008000
> +#define DFS_SEL_16BIT 0X000002
> +#define SPI_CTRL0 0x11060000
> +#define SPI_ENR 0x11060008
> +#define CRUPMU_CLKSEL14_CON 0x12008098
> +#define PMUGRF_GPIO1A_E 0x12030040
> +#define PMUGRF_GPIO1B_E 0x12030044
> +#define BIT7_6_SEL_8MA 0xf000a000
> +#define BIT1_0_SEL_8MA 0x000f000a
> +#define SPI0_PLL_SEL_APLL 0xff004000
> +#define INVALID_ID -1
> +#define RK1608_MAX_SEC_NUM 10
> +
> +#ifndef MIN
> +#define MIN(a, b) ((a) < (b) ? (a) : (b))
> +#endif
> +
> +#ifndef MSB2LSB32
> +#define MSB2LSB32(x) ((((u32)x & 0x80808080) >> 7) | \
> + (((u32)x & 0x40404040) >> 5) | \
> + (((u32)x & 0x20202020) >> 3) | \
> + (((u32)x & 0x10101010) >> 1) | \
> + (((u32)x & 0x08080808) << 1) | \
> + (((u32)x & 0x04040404) << 3) | \
> + (((u32)x & 0x02020202) << 5) | \
> + (((u32)x & 0x01010101) << 7))
> +#endif
> +
> +struct rk1608_section {
> + union {
> + u32 offset;
> + u32 wait_value;
> + };
> + u32 size;
> + union {
> + u32 load_addr;
> + u32 wait_addr;
> + };
> + u16 wait_time;
> + u16 timeout;
> + u16 crc_16;
> + u8 flag;
> + u8 type;
> +};
> +
> +struct rk1608_header {
> + char version[32];
> + u32 header_size;
> + u32 section_count;
> + struct rk1608_section sections[RK1608_MAX_SEC_NUM];
> +};
> +
> +struct rk1608_boot_req {
> + u32 flag;
> + u32 load_addr;
> + u32 boot_len;
> + u8 status;
> + u8 dummy[2];
> + u8 cmd;
> +};
> +
> +struct rk1608_msg_queue {
> + u32 buf_head; /* msg buffer head */
> + u32 buf_tail; /* msg buffer tail */
> + u32 cur_send; /* current msg send postition */
> + u32 cur_recv; /* current msg receive position */
> +};
> +
> +struct msg {
> + u32 size; /* unit 4 bytes */
> + u16 type; /* msg identification */
> + s8 camera_id;
> + s8 sync;
> +};
> +
> +enum {
> + /** AP -> RK1608
> + * 1 msg of sensor
> + */
> + id_msg_init_sensor_t = 0x0001,
> + id_msg_set_input_size_t,
> + id_msg_set_output_size_t,
> + id_msg_set_stream_in_on_t,
> + id_msg_set_stream_in_off_t,
> + id_msg_set_stream_out_on_t,
> + id_msg_set_stream_out_off_t,
> +
> + /** AP -> RK1608
> + * 2 msg of take picture
> + */
> + id_msg_take_picture_t = 0x0021,
> + id_msg_take_picture_done_t,
> +
> + /** AP -> RK1608
> + * 3 msg of realtime parameter
> + */
> + id_msg_rt_args_t = 0x0031,
> +
> + /** AP -> RK1608
> + * 4 msg of power manager
> + */
> + id_msg_set_sys_mode_bypass_t = 0x0200,
> + id_msg_set_sys_mode_standby_t,
> + id_msg_set_sys_mode_idle_enable_t,
> + id_msg_set_sys_mode_idle_disable_t,
> + id_msg_set_sys_mode_slave_rk1608_on_t,
> + id_msg_set_sys_mode_slave_rk1608_off_t,
> +
> + /** AP -> RK1608
> + * 5 msg of debug config
> + */
> + id_msg_set_log_level_t = 0x0250,
> +
> + /** RK1608 -> AP
> + * 6 response of sensor msg
> + */
> + id_msg_init_sensor_ret_t = 0x0301,
> + id_msg_set_input_size_ret_t,
> + id_msg_set_output_size_ret_t,
> + id_msg_set_stream_in_on_ret_t,
> + id_msg_set_stream_in_off_ret_t,
> + id_msg_set_stream_out_on_ret_t,
> + id_msg_set_stream_out_off_ret_t,
> +
> + /** RK1608 -> AP
> + * 7 response of take picture msg
> + */
> + id_msg_take_picture_ret_t = 0x0320,
> + id_msg_take_picture_done_ret_t,
> +
> + /** RK1608 -> AP
> + * 8 response of realtime parameter msg
> + */
> + id_msg_rt_args_ret_t = 0x0330,
> +
> + /*rk1608 -> ap*/
> + id_msg_do_i2c_t = 0x0390,
> + /*ap -> rk1608*/
> + id_msg_do_i2c_ret_t,
> +
> + /** RK1608 -> AP
> + * 9 msg of print log
> + */
> + id_msg_rk1608_log_t = 0x0400,
> +
> + /* dsi2csi dump */
> + id_msg_dsi2sci_rgb_dump_t = 0x6000,
> + id_msg_dsi2sci_nv12_dump_t = 0x6001,
> +
> + /** RK1608 -> AP
> + * 10 msg of xfile
> + */
> + id_msg_xfile_import_t = 0x8000 + 0x0600,
> + id_msg_xfile_export_t,
> + id_msg_xfile_mkdir_t
> +};
> +
> +struct msg_rk1608_log_t {
> + u32 size;
> + u16 type;
> + s8 core_id;
> + s8 log_level;
> +};
> +
> +/**
> + * rk1608_write - RK1608 synchronous write
> + *
> + * @spi: spi device
> + * @addr: resource address
> + * @data: data buffer
> + * @data_len: data buffer size, in bytes
> + * Context: can sleep
> + *
> + * It returns zero on success, else a negative error code.
> + */
> +int rk1608_write(struct spi_device *spi, s32 addr,
> + const s32 *data, size_t data_len);
> +
> +/**
> + * rk1608_safe_write - RK1608 synchronous write with state check
> + *
> + * @spi: spi device
> + * @addr: resource address
> + * @data: data buffer
> + * @data_len: data buffer size, in bytes
> + * Context: can sleep
> + *
> + * It returns zero on success, else operation state code.
> + */
> +int rk1608_safe_write(struct spi_device *spi,
> + s32 addr, const s32 *data, size_t data_len);
> +
> +/**
> + * rk1608_read - RK1608 synchronous read
> + *
> + * @spi: spi device
> + * @addr: resource address
> + * @data: data buffer [out]
> + * @data_len: data buffer size, in bytes
> + * Context: can sleep
> + *
> + * It returns zero on success, else a negative error code.
> + */
> +int rk1608_read(struct spi_device *spi, s32 addr,
> + s32 *data, size_t data_len);
> +
> +/**
> + * rk1608_safe_read - RK1608 synchronous read with state check
> + *
> + * @spi: spi device
> + * @addr: resource address
> + * @data: data buffer [out]
> + * @data_len: data buffer size, in bytes
> + * Context: can sleep
> + *
> + * It returns zero on success, else operation state code.
> + */
> +int rk1608_safe_read(struct spi_device *spi,
> + s32 addr, s32 *data, size_t data_len);
> +
> +/**
> + * rk1608_operation_query - RK1608 last operation state query
> + *
> + * @spi: spi device
> + * @state: last operation state [out]
> + * Context: can sleep
> + *
> + * It returns zero on success, else a negative error code.
> + */
> +int rk1608_operation_query(struct spi_device *spi, s32 *state);
> +
> +/**
> + * rk1608_interrupt_request - RK1608 request a rk1608 interrupt
> + *
> + * @spi: spi device
> + * @interrupt_num: interrupt identification
> + * Context: can sleep
> + *
> + * It returns zero on success, else a negative error code.
> + */
> +int rk1608_interrupt_request(struct spi_device *spi,
> + s32 interrupt_num);
> +
> +static int rk1608_read_wait(struct spi_device *spi,
> + const struct rk1608_section *sec);
> +
> +static int rk1608_boot_request(struct spi_device *spi,
> + const struct rk1608_section *sec);
> +
> +static int rk1608_download_section(struct spi_device *spi, const u8 *data,
> + const struct rk1608_section *sec);
> +/**
> + * rk1608_download_fw: - rk1608 firmware download through spi
> + *
> + * @spi: spi device
> + * @fw_name: name of firmware file, NULL for default firmware name
> + * Context: can sleep
> + *
> + * It returns zero on success, else a negative error code.
> + **/
> +int rk1608_download_fw(struct spi_device *spi, const char *fw_name);
> +
> +/**
> + * rk1608_msq_read_head - read rk1608 msg queue head
> + *
> + * @spi: spi device
> + * @addr: msg queue head addr
> + * @m: msg queue pointer
> + *
> + * It returns zero on success, else a negative error code.
> + */
> +int rk1608_msq_read_head(struct spi_device *spi,
> + u32 addr, struct rk1608_msg_queue *q);
> +
> +/**
> + * rk1608_msq_recv_msg - receive a msg from RK1608 -> AP msg queue
> + *
> + * @q: msg queue
> + * @m: a msg pointer buf [out]
> + *
> + * need call rk1608_msq_free_received_msg to free msg after msg use done
> + *
> + * It returns zero on success, else a negative error code.
> + */
> +int rk1608_msq_recv_msg(struct spi_device *spi, struct msg **m);
> +#endif
> --
> 2.7.4
>
>
Please rebase your kernel tree to linux-next or media_tree, not
rockchip bsp kernel.
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/
https://git.linuxtv.org/
^ permalink raw reply
* [GIT PULL v2 for 4.16] An ordinary pile of atomisp cleanups and fixes
From: Sakari Ailus @ 2017-12-21 22:38 UTC (permalink / raw)
To: linux-media
Hi Mauro,
Here's the regular pile of atomisp cleanups and some fixes, too.
since v1:
- Add Andy's cleanups and fixes.
Please pull.
The following changes since commit ae49432810c5cca2143afc1445edad6582c9f270:
media: ddbridge: improve ddb_ports_attach() failure handling (2017-12-19 07:18:38 -0500)
are available in the git repository at:
ssh://linuxtv.org/git/sailus/media_tree.git atomisp
for you to fetch changes up to e4e1b698c1a67f1d58ca5c232c1b44fa77bd1a7b:
staging: atomisp: Fix DMI matching entry for MRD7 (2017-12-21 23:44:26 +0200)
----------------------------------------------------------------
Aishwarya Pant (1):
staging: atomisp2: replace DEVICE_ATTR with DEVICE_ATTR_RO
Andy Shevchenko (10):
staging: atomisp: Don't leak GPIO resources if clk_get() failed
staging: atomisp: Remove duplicate NULL-check
staging: atomisp: lm3554: Fix control values
staging: atomisp: Disable custom format for now
staging: atomisp: Remove non-ACPI leftovers
staging: atomisp: Switch to use struct device_driver directly
staging: atomisp: Remove redundant PCI code
staging: atomisp: Unexport local function
staging: atomisp: Use standard DMI match table
staging: atomisp: Fix DMI matching entry for MRD7
Arnd Bergmann (1):
staging: atomisp: convert timestamps to ktime_t
Jeremy Sowden (2):
media: staging: atomisp: fix for sparse "using plain integer as NULL pointer" warnings.
media: staging: atomisp: fixes for "symbol was not declared. Should it be static?" sparse warnings.
Riccardo Schirone (4):
staging: add missing blank line after declarations in atomisp-ov5693
staging: improve comments usage in atomisp-ov5693
staging: improves comparisons readability in atomisp-ov5693
staging: fix indentation in atomisp-ov5693
Sergiy Redko (1):
Staging: media: atomisp: made function static
Sinan Kaya (1):
atomisp: deprecate pci_get_bus_and_slot()
drivers/staging/media/atomisp/i2c/atomisp-gc0310.c | 10 +-
drivers/staging/media/atomisp/i2c/atomisp-gc2235.c | 8 +-
drivers/staging/media/atomisp/i2c/atomisp-lm3554.c | 38 +++---
.../staging/media/atomisp/i2c/atomisp-mt9m114.c | 8 +-
drivers/staging/media/atomisp/i2c/atomisp-ov2680.c | 10 +-
drivers/staging/media/atomisp/i2c/atomisp-ov2722.c | 17 +--
drivers/staging/media/atomisp/i2c/ov2680.h | 1 -
.../media/atomisp/i2c/ov5693/atomisp-ov5693.c | 94 ++++++++-------
drivers/staging/media/atomisp/i2c/ov5693/ov5693.h | 2 +-
drivers/staging/media/atomisp/i2c/ov8858.c | 43 ++++---
.../staging/media/atomisp/include/linux/atomisp.h | 2 +
.../atomisp/include/linux/atomisp_gmin_platform.h | 1 -
.../media/atomisp/pci/atomisp2/atomisp_drvfs.c | 17 ++-
.../media/atomisp/pci/atomisp2/atomisp_drvfs.h | 5 +-
.../media/atomisp/pci/atomisp2/atomisp_internal.h | 1 -
.../media/atomisp/pci/atomisp2/atomisp_ioctl.c | 5 +-
.../media/atomisp/pci/atomisp2/atomisp_subdev.c | 2 +
.../media/atomisp/pci/atomisp2/atomisp_v4l2.c | 12 +-
.../isp/kernels/eed1_8/ia_css_eed1_8.host.c | 24 ++--
.../css2400/runtime/debug/src/ia_css_debug.c | 1 +
.../isp_param/interface/ia_css_isp_param_types.h | 2 +-
.../staging/media/atomisp/pci/atomisp2/hmm/hmm.c | 8 +-
.../platform/intel-mid/atomisp_gmin_platform.c | 129 +++++++++++++--------
23 files changed, 221 insertions(+), 219 deletions(-)
--
Kind regards,
Sakari Ailus
e-mail: sakari.ailus@iki.fi
^ permalink raw reply
* Re: [PATCH v1 05/10] staging: atomisp: Remove non-ACPI leftovers
From: Sakari Ailus @ 2017-12-21 22:34 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Dan Carpenter, Andy Shevchenko, Alan Cox,
Linux Media Mailing List, Greg Kroah-Hartman, devel,
Kristian Beilke
In-Reply-To: <CAHp75VcoYnvwrdVQnMmtPfuiRg0AFOSa5WwfhTk3HP0ww7x5VA@mail.gmail.com>
Hi Andy and Dan,
On Wed, Dec 20, 2017 at 12:24:36PM +0200, Andy Shevchenko wrote:
> On Wed, Dec 20, 2017 at 6:54 AM, Dan Carpenter <dan.carpenter@oracle.com> wrote:
> > On Tue, Dec 19, 2017 at 10:59:52PM +0200, Andy Shevchenko wrote:
> >> @@ -1147,10 +1145,8 @@ static int gc2235_probe(struct i2c_client *client)
> >> if (ret)
> >> gc2235_remove(client);
> >
> > This error handling is probably wrong...
> >
>
> Thanks for pointing to this, but I'm not going to fix this by the
> following reasons:
> 1. I admit the driver's code is ugly
> 2. It's staging code
> 3. My patch does not touch those lines
> 4. My purpose is to get it working first.
>
> Feel free to send a followup with a good clean up which I agree with.
Yeah, there's a lot of ugly stuff in this driver... I understand Andy's
patches address problems with functionality, let's make error handling
fixes separately.
So I'm applying these now.
Thanks!
--
Kind regards,
Sakari Ailus
sakari.ailus@linux.intel.com
^ permalink raw reply
* Re: [PATCH v4 00/18] kernel-doc: add supported to document nested structs
From: Mauro Carvalho Chehab @ 2017-12-21 21:35 UTC (permalink / raw)
To: Jonathan Corbet
Cc: Linux Media Mailing List, Mauro Carvalho Chehab,
Linux Doc Mailing List
In-Reply-To: <20171221140843.5e4bcffd@lwn.net>
Em Thu, 21 Dec 2017 14:08:43 -0700
Jonathan Corbet <corbet@lwn.net> escreveu:
> On Mon, 18 Dec 2017 10:30:01 -0200
> Mauro Carvalho Chehab <mchehab@s-opensource.com> wrote:
>
> > This is a rebased version of my patch series that add support for
> > nested structs on kernel-doc. With this version, it won't produce anymore
> > hundreds of identical warnings, as patch 17 removes the warning
> > duplication.
> >
> > Excluding warnings about duplicated Note: section at hash.h, before
> > this series, it reports 166 kernel-doc warnings. After this patch series,
> > it reports 123 kernel-doc warnings, being 51 from DVB. I have already a patch
> > series that will cleanup those new DVB warnings due to nested structs.
> >
> > So, the net result is that the number of warnings is reduced with
> > this version.
>
> This seems like a great set of improvements overall, and I love getting
> rid of all that old kernel-doc code.
> I will note that it makes a full
> htmldocs build take 20-30 seconds longer, which is not entirely
> welcome, but so be it. Someday, I guess, $SOMEBODY should see if there's
> some low-hanging optimization fruit there.
Yeah. Well, I used a recursive algorithm, with can be painfull if there
are mang things to parse.
Anyway, I didn't notice it, because there was a major performance regression
that happened recently that it is affecting all my sphinx builds: trying to
compile stuff in parallel with SPHINXOPTS=-j5 is crashing with:
# Loaded extensions:
# kfigure (1.0.0) from /devel/v4l/patchwork/Documentation/sphinx/kfigure.py
# kernel_include (1.0) from /devel/v4l/patchwork/Documentation/sphinx/kernel_include.py
# rstFlatTable (1.0) from /devel/v4l/patchwork/Documentation/sphinx/rstFlatTable.py
# cdomain (1.0) from /devel/v4l/patchwork/Documentation/sphinx/cdomain.py
# kerneldoc (1.0) from /devel/v4l/patchwork/Documentation/sphinx/kerneldoc.py
# alabaster (0.7.10) from /devel/v4l/docs/sphinx_1.4/lib/python2.7/site-packages/alabaster/__init__.pyc
# sphinx.ext.imgmath (1.4.9) from /devel/v4l/docs/sphinx_1.4/lib/python2.7/site-packages/sphinx/ext/imgmath.pyc
Traceback (most recent call last):
File "/devel/v4l/docs/sphinx_1.4/lib/python2.7/site-packages/sphinx/cmdline.py", line 244, in main
app.build(opts.force_all, filenames)
File "/devel/v4l/docs/sphinx_1.4/lib/python2.7/site-packages/sphinx/application.py", line 297, in build
self.builder.build_update()
File "/devel/v4l/docs/sphinx_1.4/lib/python2.7/site-packages/sphinx/builders/__init__.py", line 251, in build_update
'out of date' % len(to_build))
File "/devel/v4l/docs/sphinx_1.4/lib/python2.7/site-packages/sphinx/builders/__init__.py", line 265, in build
self.doctreedir, self.app))
File "/devel/v4l/docs/sphinx_1.4/lib/python2.7/site-packages/sphinx/environment.py", line 567, in update
self._read_parallel(docnames, app, nproc=app.parallel)
File "/devel/v4l/docs/sphinx_1.4/lib/python2.7/site-packages/sphinx/environment.py", line 625, in _read_parallel
tasks.join()
File "/devel/v4l/docs/sphinx_1.4/lib/python2.7/site-packages/sphinx/util/parallel.py", line 92, in join
self._join_one()
File "/devel/v4l/docs/sphinx_1.4/lib/python2.7/site-packages/sphinx/util/parallel.py", line 97, in _join_one
exc, result = pipe.recv()
EOFError
I had to change my build scripts to remove parallel build, with increased
*a lot* the building time. So, right now, I just go out to take a coffee
or two when building documentation, as, without -j (even without this
patch series), is really slow.
If someone wants to look into it, the breakage happened by the time
I upgraded to Fedora 27 and Kernel 4.14 was released. Yet, I'm using
pip for Sphinx.
So, I dunno what's the culprit. I didn't have time yet to investigate.
> Applied, thanks.
Thank you!
Regards,
Mauro
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox