From: Helmut Grohne <helmut.grohne@intenta.de>
To: Mauro Carvalho Chehab <mchehab@kernel.org>,
Sakari Ailus <sakari.ailus@linux.intel.com>,
Laurent Pinchart <laurent.pinchart@ideasonboard.com>,
<linux-media@vger.kernel.org>
Subject: [RFC PATCH] media: new driver mt9m02x for Onsemi MT9M024 and AR0141CS
Date: Tue, 20 Oct 2020 11:27:33 +0200 [thread overview]
Message-ID: <20201020092732.GA30983@laureti-dev> (raw)
Add basic support for Onsemi (formerly Aptina) image sensors MT9M024 and
AR0141CS.
* Absolute exposure
* Analogue gain as iso sensitivity
* Digital gain
* HDR (MT9M024)
* Temperature sensor (MT9M024)
* Support for monochrome/color models
* Vertical/horziontal flip
* Binning
* Reset via GPIO or I2C.
Signed-off-by: Helmut Grohne <helmut.grohne@intenta.de>
Signed-off-by: Ringo Schulz <ringo.schulz@intenta.de>
Signed-off-by: Peter Will <peter.will@intenta.de>
---
drivers/media/i2c/Kconfig | 10 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/mt9m02x.c | 1596 +++++++++++++++++++++++++++++++++++
3 files changed, 1607 insertions(+)
create mode 100644 drivers/media/i2c/mt9m02x.c
Hi,
I've promised this RFC patch quite a while ago. In the mean time, legal
did catch up and we're entitled to publish it.
This goes as far back as 2018, where I proposed[1] changing the
aptina_pll_calculate function to solve the pix_clock approximately
instead of giving up.
The driver is in practical use. It mostly passes checkpatch.pl. Known
issues:
* It presently defines 3 custom V4L2 CIDs inside the .c file. Those
will need a proper place. Possibly, there is some standard CID that
could replace them. Suggestions welcome.
* There is no streaming functionality yet. In our use, the imager is
triggered externally.
* The driver needs the modified aptina_pll_calculate[1] to work in our
setting. Otherwise probing fails finding a valid pix_clock.
* For similarity with other drivers, I added mt9m02x_platform_data. For
actually using it, it needs to be moved to a header.
Questions:
* What are the big issues that need to be solved before considering the
driver for merging?
* Would it be useful to use the regmap framework?
* The analogue gain is mapped to V4L2_CID_ISO_SENSITIVITY. That's not a
perfect match, but it is a menu with scale. Is there something better
with these properties?
* Should I keep the MT9M024 and AR0141CS drivers fused like this or
separate them? If fused, is the current naming ok? It originated from
a brief period of looking into MT9M021 before adding AR0141CS
support.
* Is mt9m02x_platform_data (i.e. support for non-DT configurations)
worth keeping?
Please save a detailed review for a later iteration. I do expect
requests for structural changes that could easily render detailed
changes irrelevant. Of course, if you spot repeated patterns of details
that I got wrong, that's worth a mention still.
Thank you for looking into this
Helmut
[1] https://lore.kernel.org/linux-media/20180823075208.mqjctv4ax4dakfws@laureti-dev/#t
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index c7ba76fee599..ddcec63ea531 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1245,6 +1245,16 @@ config VIDEO_S5C73M3
This is a V4L2 sensor driver for Samsung S5C73M3
8 Mpixel camera.
+config VIDEO_MT9M02X
+ tristate "Aptina/Onsemi MT9M024 and AR0141CS sensor support"
+ depends on HWMON && I2C && VIDEO_V4L2
+ select V4L2_FWNODE
+ select VIDEO_APTINA_PLL
+ select VIDEO_V4L2_SUBDEV_API
+ help
+ This is a V4L2 sensor driver for Onsemi (formerly Aptina)
+ image sensors MT9M024 and AR0141CS (monochrome/color).
+
endmenu
menu "Lens drivers"
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index f0a77473979d..4023bea35c4f 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -85,6 +85,7 @@ obj-$(CONFIG_VIDEO_OV9640) += ov9640.o
obj-$(CONFIG_VIDEO_OV9650) += ov9650.o
obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o
+obj-$(CONFIG_VIDEO_MT9M02X) += mt9m02x.o
obj-$(CONFIG_VIDEO_MT9M032) += mt9m032.o
obj-$(CONFIG_VIDEO_MT9M111) += mt9m111.o
obj-$(CONFIG_VIDEO_MT9P031) += mt9p031.o
diff --git a/drivers/media/i2c/mt9m02x.c b/drivers/media/i2c/mt9m02x.c
new file mode 100644
index 000000000000..d0eb5e4fc4ea
--- /dev/null
+++ b/drivers/media/i2c/mt9m02x.c
@@ -0,0 +1,1596 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2016-2020 Intenta GmbH
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/math64.h>
+#include <linux/mutex.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+#include "aptina-pll.h"
+
+#define MT9M02X_V4L2_CID_USER_BASE (V4L2_CID_USER_BASE + 0x1000)
+#define MT9M02X_V4L2_CID_HDR_RATIO_T1T2 (MT9M02X_V4L2_CID_USER_BASE + 2)
+#define MT9M02X_V4L2_CID_HDR_RATIO_T2T3 (MT9M02X_V4L2_CID_USER_BASE + 3)
+
+/* registers */
+#define APT_CHIP_VERSION 0x3000
+#define APT_Y_ADDR_START 0x3002
+#define APT_X_ADDR_START 0x3004
+#define APT_Y_ADDR_END 0x3006
+#define APT_X_ADDR_END 0x3008
+#define APT_LINE_LENGTH_PCK 0x300C
+#define APT_LINE_LENGTH_PCK_VALUE 0x0672
+#define APT_FRAME_LENGTH_LINES 0x300A
+#define APT_FRAME_LENGTH_LINES_VALUE 0x08CA
+#define APT_COARSE_INTEGRATION_TIME 0x3012
+#define APT_COARSE_INTEGRATION_TIME_DEFAULT 0x0384
+#define APT_FINE_INTEGRATION_TIME 0x3014
+#define APT_FINE_INTEGRATION_TIME_VALUE 0
+
+#define APT_RESET_REGISTER 0x301A
+#define APT_RESET_DO_RESET BIT(0)
+#define APT_RESET_ENABLE_STREAMING BIT(2)
+/* Can be cleared to make some registers writeable. Not recommended. */
+#define APT_RESET_LOCK_REG BIT(3)
+/* When switching between serial vs. parallel output, a few bits are dependent.
+ * 6 -> drive the parallel pins
+ * 7 -> enable the parallel interface
+ * 12 -> disable the serializer
+ */
+#define APT_RESET_PARALLEL_MODE (BIT(6) | BIT(7) | BIT(12))
+/* Enable a number of input pins including OE_BAR, TRIGGER and STANDBY. */
+#define APT_RESET_GPI_ENABLE BIT(8)
+
+#define APT_ROW_SPEED 0x3028
+#define APT_VT_PIX_CLK_DIV 0x302A
+#define APT_VT_SYS_CLK_DIV 0x302C
+#define APT_PRE_PLL_CLK_DIV 0x302E
+#define APT_PLL_MULTIPLIER 0x3030
+#define APT_DIGITAL_BINNING 0x3032
+
+#define APT_READ_MODE 0x3040
+/* row + column binning: (context B at bits 10:11) */
+#define APT_READ_MODE_BINNING (BIT(12) | BIT(13))
+#define APT_READ_MODE_HFLIP BIT(14)
+#define APT_READ_MODE_VFLIP BIT(15)
+
+#define APT_FLASH 0x3046
+#define APT_FLASH_ENABLED 0x0100
+
+#define APT_GLOBAL_GAIN 0x305E
+#define AR0141CS_ANALOG_GAIN 0x3060
+
+#define APT_EMBEDDED_DATA_CTRL 0x3064
+
+#define APT_OPERATION_MODE_CTRL 0x3082
+#define APT_X_ODD_INC 0x30A2
+#define APT_Y_ODD_INC 0x30A6
+
+#define APT_DIGITAL_TEST 0x30B0
+/* MT9M02X-only: analogue gain at 5:4 (A) and 9:8 (B) */
+#define APT_DIGITAL_TEST_MONOCHROME BIT(7)
+
+#define APT_TEMP_VALUE 0x30B2
+#define APT_TEMP 0x30B4
+#define APT_TEMP_CALIB1 0x30C6
+#define APT_TEMP_CALIB2 0x30C8
+#define APT_HISPI_CONTROL_STATUS 0x31C6
+#define APT_HDR_COMPANDING 0x31D0
+#define APT_COLUMN_CORRECTION 0x30D4
+
+#define APT_DIGITAL_CTRL 0x30BA
+#define APT_DIGITAL_CTRL_DITHERING BIT(5)
+
+#define APT_DIGITAL_LATERAL 0x3190
+
+#define APT_DARK_CONTROL 0x3044
+#define APT_DARK_CONTROL_ROW_NOISE BIT(10)
+
+#define APT_SEQ_CTRL_PORT 0x3088
+/* GENMASK(8, 0): Sequencer RAM address. Number of bits depends on chip. */
+/* BIT(14): defines whether reading APT_SEQ_DATA_PORT advances position */
+#define APT_SEQ_CTRL_PORT_STOPPED BIT(15)
+
+#define APT_SEQ_DATA_PORT 0x3086
+#define APT_ERS_PROG_START_ADDR 0x309E
+#define APT_DATA_PEDESTAL 0x301E
+#define APT_DAC_LD_14_15 0x3EDA
+#define APT_DAC_LD_18_19 0x3EDE
+#define APT_DAC_LD_12_13 0x3ED8
+#define APT_DAC_LD_22_23 0x3EE2
+#define APT_DAC_LD_20_21 0x3EE0
+#define APT_DAC_LD_16_17 0x3EDC
+#define APT_DAC_LD_26_27 0x3EE6
+#define APT_DAC_LD_24_25 0x3EE4
+#define APT_DAC_LD_10_11 0x3ED6
+#define APT_ADC_BITS_6_7 0x30E4
+#define APT_ADC_BITS_4_5 0x30E2
+#define APT_ADC_BITS_2_3 0x30E0
+#define APT_ADC_CONFIG1 0x30E6
+#define APT_ADC_CONFIG2 0x30E8
+
+/* can be supplied via struct i2c_client ->dev.platform_data or device tree.
+ * TODO(mainline): Move this structure to a header such that another driver
+ * can supply it. Though one only needs that when not using DT.
+ */
+struct mt9m02x_platform_data {
+ unsigned int ext_freq; /* in Hz */
+ unsigned int pix_clock; /* in Hz */
+};
+
+struct mt9m02x_state;
+
+/**
+ * struct reg_entry - a configuration option for an imager
+ * @reg: The register id. Must be a valid u16 or the special value MSLEEP.
+ * @value: The value to write into the register or the duration of MSLEEP in ms.
+ */
+struct reg_entry {
+ s32 reg;
+ u16 value;
+};
+
+#define MSLEEP -1
+
+struct chip_info {
+ u16 version;
+
+ const struct aptina_pll_limits *pll_limits;
+ /* Number of ext_clock cycles to wait after lifting reset_gpio. */
+ unsigned int reset_wait_cycles;
+
+ /* Initialization of the sequencer RAM */
+ const u16 *sequencer;
+ size_t sequencer_length;
+ const u16 *linear_sequencer;
+ size_t linear_sequencer_length;
+ /* Static configuration */
+ const struct reg_entry *configuration;
+ size_t configuration_length;
+
+ u16 width, height;
+ u16 xstart, ystart;
+
+ /* Digital gain is a fixed-point number with chip-dependent number of
+ * bits before and after the point. The v4l2 ctrl expects 8 bits after
+ * the point. v4l2value >> digital_gain_shift gives the register value.
+ * digital_gain_min is the minimum v4l2 value.
+ * (1 << digital_gain_bits) - 1 is the maximum register value.
+ */
+ u16 digital_gain_shift, digital_gain_min, digital_gain_bits;
+
+ /* Array of valid V4L2_CID_ISO_SENSITIVITY values sized
+ * again_menu_length.
+ */
+ const s64 *again_menu;
+ size_t again_menu_length;
+ /* Given a valid index into again_menu, set the corresponding gain. */
+ int (*again_set)(struct mt9m02x_state *state, s32 index);
+
+ /* Temperature sensor calibration */
+ long temp_point1, temp_point2; /* milli degree C */
+ u16 temp_mask; /* register mask, 0 -> disable temperature sensor */
+};
+
+struct model_info {
+ const struct chip_info *chip;
+ bool monochrome;
+};
+
+struct mt9m02x_state {
+ struct i2c_client *client;
+ struct gpio_desc *reset_gpio, *standby_gpio;
+
+ /* Only written to while probing: */
+ const struct model_info *model;
+ struct aptina_pll pll;
+ /* register values, equal points -> disabled */
+ u16 temp_value1, temp_value2;
+
+ /* Protected by ctrls.lock: */
+ bool binning;
+
+ struct v4l2_subdev sd;
+ struct media_pad pad;
+ struct v4l2_ctrl_handler ctrls;
+ struct {
+ struct v4l2_ctrl *hflip, *vflip;
+ } mode_cluster;
+ struct {
+ struct v4l2_ctrl *wdr, *ratio_t1t2, *ratio_t2t3;
+ } opmode_cluster;
+ struct v4l2_ctrl *digital_gain_ctrl;
+ struct v4l2_ctrl *exposure_ctrl;
+};
+
+static const u16 ar0141cs_sequencer[] = {
+ 0x4558, 0x6E9B, 0x4A31, 0x4342, 0x8E03, 0x2714, 0x4578, 0x7B3D,
+ 0xFF3D, 0xFF3D, 0xEA27, 0x043D, 0x1027, 0x0527, 0x1535, 0x2705,
+ 0x3D10, 0x4558, 0x2704, 0x2714, 0x3DFF, 0x3DFF, 0x3DEA, 0x2704,
+ 0x6227, 0x288E, 0x0036, 0x2708, 0x3D64, 0x7A3D, 0x0444, 0x2C4B,
+ 0x8F01, 0x4372, 0x719F, 0x4643, 0x166F, 0x9F92, 0x1244, 0x1646,
+ 0x4316, 0x9326, 0x0426, 0x848E, 0x0327, 0xFC5C, 0x0D57, 0x5417,
+ 0x0955, 0x5649, 0x5F53, 0x0553, 0x0728, 0x6C4C, 0x0928, 0x2C72,
+ 0xA37C, 0x9728, 0xA879, 0x6026, 0x9C5C, 0x1B45, 0x4845, 0x0845,
+ 0x8826, 0xBE8E, 0x0127, 0xF817, 0x0227, 0xFA17, 0x095C, 0x0B17,
+ 0x1026, 0xBA5C, 0x0317, 0x1026, 0xB217, 0x065F, 0x2888, 0x9060,
+ 0x27F2, 0x1710, 0x26A2, 0x26A3, 0x5F4D, 0x2808, 0x1A27, 0xFA84,
+ 0x69A0, 0x785D, 0x2888, 0x8710, 0x8C82, 0x8926, 0xB217, 0x036B,
+ 0x9C60, 0x9417, 0x2926, 0x8345, 0xA817, 0x0727, 0xFB17, 0x2945,
+ 0x8820, 0x1708, 0x27FA, 0x5D87, 0x108C, 0x8289, 0x170E, 0x4826,
+ 0x9A28, 0x884C, 0x0B79, 0x1730, 0x2692, 0x1709, 0x9160, 0x27F2,
+ 0x1710, 0x2682, 0x2683, 0x5F4D, 0x2808, 0x1A27, 0xFA84, 0x69A1,
+ 0x785D, 0x2888, 0x8710, 0x8C80, 0x8A26, 0x9217, 0x036B, 0x9D95,
+ 0x2603, 0x5C01, 0x4558, 0x8E00, 0x2798, 0x170A, 0x4A0A, 0x4316,
+ 0x0B43, 0x5B43, 0x1659, 0x4316, 0x8E03, 0x279C, 0x4578, 0x1707,
+ 0x279D, 0x1722, 0x5D87, 0x1028, 0x0853, 0x0D8C, 0x808A, 0x4558,
+ 0x1708, 0x8E01, 0x2798, 0x8E00, 0x76A2, 0x77A2, 0x4644, 0x1616,
+ 0x967A, 0x2644, 0x5C05, 0x1244, 0x4B71, 0x759E, 0x8B86, 0x184A,
+ 0x0343, 0x1606, 0x4316, 0x0743, 0x1604, 0x4316, 0x5843, 0x165A,
+ 0x4316, 0x4558, 0x8E03, 0x279C, 0x4578, 0x7B17, 0x0727, 0x9D17,
+ 0x2245, 0x5822, 0x1710, 0x8E01, 0x2798, 0x8E00, 0x1710, 0x1244,
+ 0x4B8D, 0x602C, 0x2C2C, 0x2C00,
+};
+
+// A-1000 Hidy and linear sequencer load August 2 2011
+static const u16 sequencer_mt9m024[] = {
+ 0x0025, 0x5050, 0x2D26, 0x0828, 0x0D17, 0x0926, 0x0028, 0x0526,
+ 0xA728, 0x0725, 0x8080, 0x2925, 0x0040, 0x2702, 0x1616, 0x2706,
+ 0x1F17, 0x3626, 0xA617, 0x0326, 0xA417, 0x1F28, 0x0526, 0x2028,
+ 0x0425, 0x2020, 0x2700, 0x171D, 0x2500, 0x2017, 0x1219, 0x1703,
+ 0x2706, 0x1728, 0x2805, 0x171A, 0x2660, 0x175A, 0x2317, 0x1122,
+ 0x1741, 0x2500, 0x9027, 0x0026, 0x1828, 0x002E, 0x2A28, 0x081C,
+ 0x1470, 0x7003, 0x1470, 0x7004, 0x1470, 0x7005, 0x1470, 0x7009,
+ 0x170C, 0x0014, 0x0020, 0x0014, 0x0050, 0x0314, 0x0020, 0x0314,
+ 0x0050, 0x0414, 0x0020, 0x0414, 0x0050, 0x0514, 0x0020, 0x2405,
+ 0x1400, 0x5001, 0x2550, 0x502D, 0x2608, 0x280D, 0x1709, 0x2600,
+ 0x2805, 0x26A7, 0x2807, 0x2580, 0x8029, 0x2500, 0x4027, 0x0216,
+ 0x1627, 0x0620, 0x1736, 0x26A6, 0x1703, 0x26A4, 0x171F, 0x2805,
+ 0x2620, 0x2804, 0x2520, 0x2027, 0x0017, 0x1D25, 0x0020, 0x1712,
+ 0x1A17, 0x0327, 0x0617, 0x2828, 0x0517, 0x1A26, 0x6017, 0xAE25,
+ 0x0090, 0x2700, 0x2618, 0x2800, 0x2E2A, 0x2808, 0x1D05, 0x1470,
+ 0x7009, 0x1720, 0x1400, 0x2024, 0x1400, 0x5002, 0x2550, 0x502D,
+ 0x2608, 0x280D, 0x1709, 0x2600, 0x2805, 0x26A7, 0x2807, 0x2580,
+ 0x8029, 0x2500, 0x4027, 0x0216, 0x1627, 0x0617, 0x3626, 0xA617,
+ 0x0326, 0xA417, 0x1F28, 0x0526, 0x2028, 0x0425, 0x2020, 0x2700,
+ 0x171D, 0x2500, 0x2021, 0x1712, 0x1B17, 0x0327, 0x0617, 0x2828,
+ 0x0517, 0x1A26, 0x6017, 0xAE25, 0x0090, 0x2700, 0x2618, 0x2800,
+ 0x2E2A, 0x2808, 0x1E17, 0x0A05, 0x1470, 0x7009, 0x1616, 0x1616,
+ 0x1616, 0x1616, 0x1616, 0x1616, 0x1616, 0x1616, 0x1616, 0x1616,
+ 0x1616, 0x1616, 0x1616, 0x1614, 0x0020, 0x2414, 0x0050, 0x2B2B,
+ 0x2C2C, 0x2C2C, 0x2C00,
+};
+
+static const u16 sequencer_mt9m024_linear[] = {
+ 0x0225, 0x5050, 0x2D26, 0x0828, 0x0D17, 0x0926, 0x0028, 0x0526,
+ 0xA728, 0x0725, 0x8080, 0x2917, 0x0525, 0x0040, 0x2702, 0x1616,
+ 0x2706, 0x1736, 0x26A6, 0x1703, 0x26A4, 0x171F, 0x2805, 0x2620,
+ 0x2804, 0x2520, 0x2027, 0x0017, 0x1E25, 0x0020, 0x2117, 0x1028,
+ 0x051B, 0x1703, 0x2706, 0x1703, 0x1747, 0x2660, 0x17AE, 0x2500,
+ 0x9027, 0x0026, 0x1828, 0x002E, 0x2A28, 0x081E, 0x0831, 0x1440,
+ 0x4014, 0x2020, 0x1410, 0x1034, 0x1400, 0x1014, 0x0020, 0x1400,
+ 0x4013, 0x1802, 0x1470, 0x7004, 0x1470, 0x7003, 0x1470, 0x7017,
+ 0x2002, 0x1400, 0x2002, 0x1400, 0x5004, 0x1400, 0x2004, 0x1400,
+ 0x5022, 0x0314, 0x0020, 0x0314, 0x0050, 0x2C2C, 0x2C2C,
+};
+
+static const struct reg_entry ar0141cs_configuration[] = {
+ /* Optimized and Sequencer settings (referenced by AR0141CS-REV1.ini) */
+ {APT_DARK_CONTROL, APT_DARK_CONTROL_ROW_NOISE},
+ {0x3052, 0xA124},
+ {0x3092, 0x010F},
+ {0x30FE, 0x0080},
+ {0x3ECE, 0x40FF},
+ {0x3ED0, 0xFF40},
+ {0x3ED2, 0xA906},
+ {0x3ED4, 0x001F},
+ {0x3ED6, 0x638F},
+ {0x3ED8, 0xCC99},
+ {0x3EDA, 0x0888},
+ {0x3EDE, 0x8878},
+ {0x3EE0, 0x7744},
+ {0x3EE2, 0x4463},
+ {0x3EE4, 0xAAE0},
+ {0x3EE6, 0x1400},
+ {0x3EEA, 0xA4FF},
+ {0x3EEC, 0x80F0},
+ {0x3EEE, 0x0000},
+ {0x31E0, 0x1701},
+ {0x304C, 0x2000},
+ {0x304A, 0x0210},
+ /* It's not clear why and how long we need to sleep here. If we don't,
+ * i2c writes start to fail.
+ */
+ {MSLEEP, 200},
+ /* imager configuration */
+ {APT_ROW_SPEED, 0x0010},
+ {APT_HDR_COMPANDING, 0x0000},
+ {APT_DIGITAL_CTRL, APT_DIGITAL_CTRL_DITHERING | 0x010C},
+ {0x31AC, 0x0C0C}, /* data_format_bits */
+ /* serial format:
+ * GENMASK(9, 8) must be 3, selects HiSPi if serial interface is enabled
+ * GENMASK(2, 0) line-count, 1 -> parallel
+ */
+ {0x31AE, 0x0301},
+ {APT_FRAME_LENGTH_LINES, APT_FRAME_LENGTH_LINES_VALUE},
+ {APT_LINE_LENGTH_PCK, APT_LINE_LENGTH_PCK_VALUE},
+ {APT_EMBEDDED_DATA_CTRL, 0x1802},
+};
+
+static const struct reg_entry mt9m024_configuration[] = {
+ /* OnSemi A-1000ERS Optimized settings (August 2 2011),
+ * but with a reduced pedestal for better black level
+ */
+ {APT_DATA_PEDESTAL, 0x00A8},
+ {APT_DAC_LD_14_15, 0x0F03},
+ {APT_DAC_LD_18_19, 0xC005},
+ {APT_DAC_LD_12_13, 0x09EF},
+ {APT_DAC_LD_22_23, 0xA46B},
+ {APT_DAC_LD_20_21, 0x067D},
+ {APT_DAC_LD_16_17, 0x0070},
+ {APT_DAC_LD_26_27, 0x8303},
+ {APT_DAC_LD_24_25, 0xD208},
+ {APT_DAC_LD_10_11, 0x00BD},
+ {APT_ADC_BITS_6_7, 0x6372},
+ {APT_ADC_BITS_4_5, 0x7253},
+ {APT_ADC_BITS_2_3, 0x5470},
+ {APT_ADC_CONFIG1, 0xC4CC},
+ {APT_ADC_CONFIG2, 0x8050},
+ /* imager configuration */
+ {APT_ROW_SPEED, 0x0010},
+ {APT_DIGITAL_BINNING, 0x0000},
+ {APT_COLUMN_CORRECTION, 0xE007},
+ {APT_DIGITAL_CTRL, 0x0008},
+ {APT_FRAME_LENGTH_LINES, APT_FRAME_LENGTH_LINES_VALUE},
+ {APT_LINE_LENGTH_PCK, APT_LINE_LENGTH_PCK_VALUE},
+ {APT_FINE_INTEGRATION_TIME, APT_FINE_INTEGRATION_TIME_VALUE},
+ {APT_Y_ODD_INC, 0x0001},
+ {APT_EMBEDDED_DATA_CTRL, 0x1802},
+ {APT_HISPI_CONTROL_STATUS, 0x8008},
+ {APT_DARK_CONTROL, APT_DARK_CONTROL_ROW_NOISE | 4},
+ /* BIT(0): off/on
+ * BIT(1): 12bit/14bit
+ */
+ {APT_HDR_COMPANDING, BIT(0)}, /* 12bit enabled */
+};
+
+static const struct aptina_pll_limits ar0141cs_limits = {
+ .ext_clock_min = 6000000,
+ .ext_clock_max = 50000000,
+ /* The int_clock is not bounded by the data sheet. */
+ .int_clock_min = 1,
+ .int_clock_max = 50000000,
+ .out_clock_min = 384000000,
+ .out_clock_max = 768000000,
+ .pix_clock_max = 74250000,
+
+ .n_min = 1,
+ .n_max = 63,
+ .m_min = 32,
+ /* The data sheet revision 7 page 16 says that the maximum multiplier
+ * is 384. However the register reference revision 3 page 6 say that
+ * the corresponding register has only 8 bits.
+ */
+ .m_max = 255,
+ /* We fix p1 = 1 and control p2 here. */
+ .p1_min = 4,
+ .p1_max = 16,
+};
+
+static const struct aptina_pll_limits mt9m024_limits = {
+ .ext_clock_min = 6000000,
+ .ext_clock_max = 50000000,
+ .int_clock_min = 2000000,
+ .int_clock_max = 24000000,
+ .out_clock_min = 384000000,
+ .out_clock_max = 768000000,
+ .pix_clock_max = 74250000,
+
+ .n_min = 1,
+ .n_max = 63,
+ .m_min = 32,
+ .m_max = 255,
+ /* We fix p1 = 1 and control p2 here. */
+ .p1_min = 4,
+ .p1_max = 16,
+};
+
+static int read_word_from_i2c_device(struct i2c_client *client, u16 register_id,
+ u16 *return_value)
+{
+ __be16 reg_buffer, val_buffer;
+ int ret = 0;
+ struct i2c_msg msg[2];
+
+ if (!client || !return_value)
+ return -EINVAL;
+
+ reg_buffer = cpu_to_be16(register_id);
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].buf = (u8 *)®_buffer;
+ msg[0].len = sizeof(reg_buffer);
+
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].buf = (u8 *)&val_buffer;
+ msg[1].len = sizeof(val_buffer);
+
+ ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+
+ if (ret == 2) {
+ *return_value = be16_to_cpu(val_buffer);
+ return 0;
+ } else if (ret >= 0) {
+ return -EINTR;
+ }
+ return ret;
+}
+
+static int write_word_to_i2c_device(struct i2c_client *client, u16 register_id,
+ u16 value)
+{
+ __be16 buffer[2];
+ int ret;
+
+ if (!client)
+ return -EINVAL;
+
+ buffer[0] = cpu_to_be16(register_id);
+ buffer[1] = cpu_to_be16(value);
+
+ ret = i2c_master_send(client, (const char *)&buffer, sizeof(buffer));
+ if (ret == 4)
+ return 0;
+ else if (ret >= 0)
+ return -EINTR;
+ return ret;
+}
+
+static int write_i2c_word_sequence(struct i2c_client *client,
+ const struct reg_entry *data,
+ size_t count)
+{
+ while (count) {
+ if (data->reg == MSLEEP) {
+ msleep(data->value);
+ } else if (data->reg >= 0 && data->reg <= 0xffff) {
+ int ret = write_word_to_i2c_device(client, data->reg,
+ data->value);
+ if (ret) {
+ dev_err(&client->dev,
+ "write register of image sensor failed (reg=0x%x value=0x%x, err=%d)!\n",
+ data->reg, data->value, ret);
+ return ret;
+ }
+ } else {
+ return -EINVAL;
+ }
+
+ --count;
+ ++data;
+ }
+ return 0;
+}
+
+/* Refer to the AR0141CS data sheet revision 7 page 15 for details on
+ * the gain computation. The following macro is another way of writing:
+ * 100000 * pow(2, coarse) * (1 + fine/16)
+ */
+#define G2ISO(coarse, fine) \
+ ((1 << (coarse)) * (100000 + (100000 / 16) * (fine)))
+static const s64 ar0141cs_again_menu[] = {
+ G2ISO(0, 0), G2ISO(0, 1), G2ISO(0, 2), G2ISO(0, 3),
+ G2ISO(0, 4), G2ISO(0, 5), G2ISO(0, 6), G2ISO(0, 7),
+ G2ISO(0, 8), G2ISO(0, 9), G2ISO(0, 10), G2ISO(0, 11),
+ G2ISO(0, 12), G2ISO(0, 13), G2ISO(0, 14), G2ISO(0, 15),
+ G2ISO(1, 0), G2ISO(1, 1), G2ISO(1, 2), G2ISO(1, 3),
+ G2ISO(1, 4), G2ISO(1, 5), G2ISO(1, 6), G2ISO(1, 7),
+ G2ISO(1, 8), G2ISO(1, 9), G2ISO(1, 10), G2ISO(1, 11),
+ G2ISO(1, 12), G2ISO(1, 13), G2ISO(1, 14), G2ISO(1, 15),
+ G2ISO(2, 0), G2ISO(2, 1), G2ISO(2, 2), G2ISO(2, 3),
+ G2ISO(2, 4), G2ISO(2, 5), G2ISO(2, 6), G2ISO(2, 7),
+ G2ISO(2, 8), G2ISO(2, 9), G2ISO(2, 10), G2ISO(2, 11),
+ G2ISO(2, 12), G2ISO(2, 13), G2ISO(2, 14), G2ISO(2, 15),
+ G2ISO(3, 0), G2ISO(3, 1), G2ISO(3, 2), G2ISO(3, 3),
+ G2ISO(3, 4), G2ISO(3, 5), G2ISO(3, 6), G2ISO(3, 7),
+ G2ISO(3, 8),
+ /* The last gain corresponds to 12x (8x coarse times 1.5x fine). It is
+ * the maximum recommended gain in the AR0141CS data sheet revision 7
+ * page 15.
+ */
+};
+#undef G2I
+
+static int ar0141cs_again_set(struct mt9m02x_state *state, s32 index)
+{
+ /* The 4 low bits of index hold the fine gain. The next 3 bits hold the
+ * coarse gain. That means we're lucky. Context A uses exactly the same
+ * layout. Context B has the same representation shifted by 8.
+ */
+ return write_word_to_i2c_device(state->client, AR0141CS_ANALOG_GAIN,
+ index);
+}
+
+static const s64 mt9m02x_again_menu[] = {
+ 100000, /* 1x */
+ 125000, /* 1.25x */
+ 200000, /* 2x */
+ 250000, /* 2.5x */
+ 400000, /* 4x */
+ 500000, /* 5x */
+ 800000, /* 8x */
+ 1000000, /* 10x */
+};
+
+static int mt9m02x_again_set(struct mt9m02x_state *state, s32 index)
+{
+ int ret;
+ u16 v = BIT(12);
+
+ if (state->model->monochrome)
+ v |= APT_DIGITAL_TEST_MONOCHROME;
+
+ /* Bank A, ignore bank B (shift 8 instead of 4) for now. */
+ v &= ~(3 << 4);
+ /* index >> 1 is the stage1 gain in powers of 2. */
+ v |= (index >> 1) << 4;
+ ret = write_word_to_i2c_device(state->client, APT_DIGITAL_TEST, v);
+ if (ret)
+ return ret;
+
+ /* set stage2, multiplier */
+ ret = read_word_from_i2c_device(state->client, APT_DAC_LD_24_25, &v);
+ if (ret)
+ return ret;
+
+ /* The least significant bit controls the 1.25x amplifier,
+ * which is bank-independent.
+ */
+ if (index & 1)
+ v |= BIT(8);
+ else
+ v &= ~BIT(8);
+ v |= BIT(9);
+
+ return write_word_to_i2c_device(state->client, APT_DAC_LD_24_25, v);
+}
+
+static const struct chip_info chip_ar0141cs = {
+ .version = 0x0151,
+ .pll_limits = &ar0141cs_limits,
+ .reset_wait_cycles = 1800,
+ .sequencer = ar0141cs_sequencer,
+ .sequencer_length = ARRAY_SIZE(ar0141cs_sequencer),
+ .configuration = ar0141cs_configuration,
+ .configuration_length = ARRAY_SIZE(ar0141cs_configuration),
+ .width = 1280,
+ .height = 800,
+ .xstart = 32,
+ .ystart = 24,
+ .digital_gain_shift = 8 - 7,
+ /* When setting a gain below 1.375, we see static noise. */
+ .digital_gain_min = 0x160, /* 1.375 * BIT(8) */
+ .digital_gain_bits = 11,
+ .again_menu = ar0141cs_again_menu,
+ .again_menu_length = ARRAY_SIZE(ar0141cs_again_menu),
+ .again_set = &ar0141cs_again_set,
+};
+
+static const struct model_info model_ar0141cs = {
+ .chip = &chip_ar0141cs,
+ .monochrome = false,
+};
+
+static const struct model_info model_ar0141csm = {
+ .chip = &chip_ar0141cs,
+ .monochrome = true,
+};
+
+static const struct chip_info chip_mt9m024 = {
+ .version = 0x2400,
+ .pll_limits = &mt9m024_limits,
+ .sequencer = sequencer_mt9m024,
+ .sequencer_length = ARRAY_SIZE(sequencer_mt9m024),
+ .linear_sequencer = sequencer_mt9m024_linear,
+ .linear_sequencer_length = ARRAY_SIZE(sequencer_mt9m024_linear),
+ .configuration = mt9m024_configuration,
+ .configuration_length = ARRAY_SIZE(mt9m024_configuration),
+ .width = 1280,
+ .height = 960,
+ .digital_gain_shift = 8 - 5,
+ .digital_gain_bits = 8,
+ .again_menu = mt9m02x_again_menu,
+ .again_menu_length = ARRAY_SIZE(mt9m02x_again_menu),
+ .again_set = &mt9m02x_again_set,
+ .temp_point1 = 70000,
+ .temp_point2 = 55000,
+ .temp_mask = GENMASK(10, 0),
+};
+
+static const struct model_info model_mt9m024 = {
+ .chip = &chip_mt9m024,
+ .monochrome = false,
+};
+
+static const struct model_info model_mt9m024m = {
+ .chip = &chip_mt9m024,
+ .monochrome = true,
+};
+
+static int write_sequencer(struct i2c_client *client, const u16 *data,
+ size_t length)
+{
+ int ret;
+
+ while (length > 0) {
+ ret = write_word_to_i2c_device(client, APT_SEQ_DATA_PORT,
+ *data);
+ if (ret)
+ return ret;
+ ++data;
+ --length;
+ }
+ return 0;
+}
+
+static int initialize_sequencer(struct mt9m02x_state *state)
+{
+ u16 temp_value;
+ int ret;
+ const struct chip_info *chip = state->model->chip;
+
+ if (!chip->sequencer && !chip->linear_sequencer)
+ return 0;
+
+ ret = read_word_from_i2c_device(state->client, APT_SEQ_CTRL_PORT,
+ &temp_value);
+ if (ret)
+ return ret;
+ if ((temp_value & APT_SEQ_CTRL_PORT_STOPPED) == 0) {
+ dev_err(&state->client->dev,
+ "attempt to write sequencer without standby.\n");
+ return -EIO;
+ }
+
+ /* Rewind sequencer RAM address. */
+ if (temp_value != APT_SEQ_CTRL_PORT_STOPPED) {
+ ret = write_word_to_i2c_device(state->client, APT_SEQ_CTRL_PORT,
+ APT_SEQ_CTRL_PORT_STOPPED);
+ if (ret)
+ return ret;
+ }
+
+ if (chip->sequencer) {
+ ret = write_sequencer(state->client, chip->sequencer,
+ chip->sequencer_length);
+ if (ret)
+ return ret;
+ }
+ if (chip->linear_sequencer) {
+ ret = write_sequencer(state->client, chip->linear_sequencer,
+ chip->linear_sequencer_length);
+ if (ret)
+ return ret;
+ /* Write start address of the linear sequencer in bytes. */
+ ret = write_word_to_i2c_device(state->client,
+ APT_ERS_PROG_START_ADDR,
+ 2 * chip->sequencer_length);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int mt9m02x_update_read_mode(struct mt9m02x_state *state)
+{
+ u16 v = 0;
+
+ if (state->binning)
+ v |= APT_READ_MODE_BINNING;
+ if (state->mode_cluster.hflip->val)
+ v |= APT_READ_MODE_HFLIP;
+ if (state->mode_cluster.vflip->val)
+ v |= APT_READ_MODE_VFLIP;
+ return write_word_to_i2c_device(state->client, APT_READ_MODE, v);
+}
+
+static int ar0141cs_update_odd_inc(struct mt9m02x_state *state)
+{
+ u16 inc = state->binning ? 3 : 1;
+ int ret = write_word_to_i2c_device(state->client, APT_X_ODD_INC, inc);
+
+ if (ret)
+ return ret;
+ return write_word_to_i2c_device(state->client, APT_Y_ODD_INC, inc);
+}
+
+static int initialize_temperature_sensor(struct mt9m02x_state *state)
+{
+ int ret;
+ u16 reg;
+
+ if (!state->model->chip->temp_mask)
+ return -EINVAL;
+
+ ret = read_word_from_i2c_device(state->client, APT_TEMP, ®);
+ if (ret) {
+ dev_err(&state->client->dev,
+ "reading from temperature register failed (%d)", ret);
+ return ret;
+ }
+
+ ret = write_word_to_i2c_device(state->client, APT_TEMP, reg | 0x11);
+ if (ret) {
+ dev_err(&state->client->dev,
+ "writing to temperature register failed (%d)", ret);
+ return ret;
+ }
+
+ ret = read_word_from_i2c_device(state->client, APT_TEMP_CALIB1,
+ &state->temp_value1);
+
+ if (!ret)
+ ret = read_word_from_i2c_device(state->client, APT_TEMP_CALIB2,
+ &state->temp_value2);
+
+ if (ret) {
+ dev_err(&state->client->dev,
+ "reading temperature calibration values failed (%d)",
+ ret);
+
+ /* disable */
+ state->temp_value1 = 0;
+ state->temp_value2 = 0;
+ return ret;
+ }
+
+ dev_dbg(&state->client->dev, "temp calib: %ldmC -> %u, %ldmC -> %u",
+ state->model->chip->temp_point1,
+ (unsigned int)state->temp_value1,
+ state->model->chip->temp_point2,
+ (unsigned int)state->temp_value2);
+
+ if (state->temp_value1 == state->temp_value2) {
+ dev_err(&state->client->dev,
+ "bad temperature calibration values");
+ return -EIO;
+ }
+
+ dev_dbg(&state->client->dev, "temperature sensor enabled");
+ return 0;
+}
+
+static void sleep_extcycles(const struct mt9m02x_state *state,
+ unsigned long cycles,
+ unsigned long us)
+{
+ /* We'd want mult_frac(cycles, 1000000, ext_clock), but that can cause
+ * an integer overflow.
+ */
+ WARN_ON(cycles >= ~0UL / 1000);
+ us += mult_frac(cycles, 1000, state->pll.ext_clock / 1000);
+ usleep_range(us, us + 1000);
+}
+
+static void hard_reset(struct mt9m02x_state *state)
+{
+ /* MT9M024 datasheet rev G: see page 59
+ * AR0141CS datasheet rev 7: see page 45
+ */
+ gpiod_set_value_cansleep(state->reset_gpio, 1);
+ /* MT9M024 and AR0141CS need at least 1ms. */
+ usleep_range(1000, 2000);
+ gpiod_set_value_cansleep(state->reset_gpio, 0);
+ /* Give an additional 1ms for the GPIO to settle. */
+ sleep_extcycles(state, state->model->chip->reset_wait_cycles, 1000);
+}
+
+static int init_image_sensor(struct mt9m02x_state *state)
+{
+ int ret = 0;
+ u16 temp_value;
+
+ if (state->standby_gpio)
+ gpiod_set_value_cansleep(state->standby_gpio, 0);
+ if (state->reset_gpio)
+ hard_reset(state);
+
+ // init image sensor
+ ret = read_word_from_i2c_device(state->client, APT_CHIP_VERSION,
+ &temp_value);
+ if (ret != 0) {
+ dev_err(&state->client->dev,
+ "read from chip version register failed\n");
+
+ return ret;
+ }
+
+ /* AR0141CS has a documented value of 0x151, but it sometimes reports
+ * 0x51. It is unclear why, but this is even the case for the reference
+ * design. Assumption: Hardware bug.
+ */
+ if (temp_value == 0x51)
+ temp_value = 0x151;
+
+ if (temp_value != state->model->chip->version) {
+ dev_err(&state->client->dev, "expected chip %x found %x\n",
+ state->model->chip->version, temp_value);
+ return -ENODEV;
+ }
+
+ ret = aptina_pll_calculate(&state->client->dev,
+ state->model->chip->pll_limits,
+ &state->pll);
+ if (ret)
+ return ret;
+
+ if (!state->reset_gpio) {
+ // reset_register_reset (full reset)
+ ret = write_word_to_i2c_device(state->client,
+ APT_RESET_REGISTER,
+ APT_RESET_DO_RESET);
+ if (ret != 0) {
+ dev_err(&state->client->dev,
+ "resetting image sensor failed\n");
+ return ret;
+ }
+
+ // wait for imager go out of reset
+ msleep(200);
+ }
+
+ // configure register_reset
+ ret = write_word_to_i2c_device(state->client, APT_RESET_REGISTER,
+ APT_RESET_LOCK_REG |
+ APT_RESET_PARALLEL_MODE |
+ APT_RESET_GPI_ENABLE);
+ if (ret != 0) {
+ dev_err(&state->client->dev,
+ "configuring of imager \"register_reset\" failed\n");
+ return ret;
+ }
+
+ if (!state->reset_gpio)
+ // wait for imager go out of reset
+ msleep(200);
+
+ ret = initialize_sequencer(state);
+ if (ret)
+ return ret;
+
+ ret = write_i2c_word_sequence(state->client,
+ state->model->chip->configuration,
+ state->model->chip->configuration_length);
+ if (ret)
+ return ret;
+
+ if (state->model->chip == &chip_ar0141cs) {
+ ret = ar0141cs_update_odd_inc(state);
+ if (ret)
+ return ret;
+ ret = write_word_to_i2c_device(state->client, APT_DIGITAL_TEST,
+ state->model->monochrome ?
+ APT_DIGITAL_TEST_MONOCHROME : 0);
+ if (ret)
+ return ret;
+
+ /* AR0141CS datasheet rev 7 page 45:
+ * Wait for OTPM after writing 0x304A.
+ */
+ sleep_extcycles(state, 185135, 0);
+ } else if (state->model->chip == &chip_mt9m024) {
+ ret = read_word_from_i2c_device(state->client,
+ APT_DIGITAL_LATERAL,
+ &temp_value);
+ if (ret)
+ return ret;
+ // Bit 13 -> disable/enable dlo,
+ // Bit 14 -> disable/enable noise filter
+ temp_value |= BIT(13) | BIT(14);
+ ret = write_word_to_i2c_device(state->client,
+ APT_DIGITAL_LATERAL,
+ temp_value);
+ if (ret)
+ return ret;
+ }
+
+ {
+ struct reg_entry values[] = {
+ { APT_X_ADDR_START, state->model->chip->xstart },
+ { APT_Y_ADDR_START, state->model->chip->ystart },
+ { APT_X_ADDR_END,
+ state->model->chip->xstart +
+ state->model->chip->width - 1 },
+ { APT_Y_ADDR_END,
+ state->model->chip->ystart +
+ state->model->chip->height - 1 },
+ };
+ ret = write_i2c_word_sequence(state->client, values,
+ ARRAY_SIZE(values));
+ if (ret)
+ return ret;
+ }
+
+ {
+ struct reg_entry values[] = {
+ { APT_VT_PIX_CLK_DIV, state->pll.p1 },
+ { APT_VT_SYS_CLK_DIV, 1 },
+ { APT_PRE_PLL_CLK_DIV, state->pll.n },
+ { APT_PLL_MULTIPLIER, state->pll.m },
+ };
+ ret = write_i2c_word_sequence(state->client, values,
+ ARRAY_SIZE(values));
+ if (ret)
+ return ret;
+
+ /* AR0141CS datasheet rev 7 page 45
+ * MT9M024 datasheet rev G page 59
+ */
+ usleep_range(1000, 2000);
+ }
+
+ /* Enable flash strobe pin. Actual flash is activated/deactivated
+ * elsewhere.
+ */
+ ret = read_word_from_i2c_device(state->client, APT_FLASH, &temp_value);
+ if (ret)
+ return ret;
+ temp_value |= APT_FLASH_ENABLED;
+ ret = write_word_to_i2c_device(state->client, APT_FLASH, temp_value);
+ if (ret)
+ return ret;
+
+ /* Perform column correction triggering on startup (see datasheet
+ * "AR0132AT-D.pdf" p.34).
+ */
+ ret = read_word_from_i2c_device(state->client, APT_RESET_REGISTER,
+ &temp_value);
+ if (ret == 0) {
+ temp_value |= APT_RESET_ENABLE_STREAMING;
+
+ ret = write_word_to_i2c_device(state->client,
+ APT_RESET_REGISTER, temp_value);
+ if (ret != 0)
+ dev_err(&state->client->dev,
+ "write to reset register failed (value=%u)\n",
+ (u32)temp_value);
+ } else {
+ dev_err(&state->client->dev,
+ "read from reset register failed\n");
+ }
+
+ if (ret != 0)
+ return ret;
+
+ // wait for 500ms -> 10 frames
+ msleep(500);
+
+ ret = read_word_from_i2c_device(state->client, APT_RESET_REGISTER,
+ &temp_value);
+ if (ret == 0) {
+ temp_value &= ~APT_RESET_ENABLE_STREAMING;
+
+ // stop sensor streaming
+ ret = write_word_to_i2c_device(state->client,
+ APT_RESET_REGISTER, temp_value);
+ if (ret != 0)
+ dev_err(&state->client->dev,
+ "write to reset register failed (value=%u)\n",
+ (u32)temp_value);
+ } else {
+ dev_err(&state->client->dev,
+ "read from reset register failed\n");
+ }
+
+ if (ret != 0)
+ return ret;
+
+ /* optional, continue in the presence of errors */
+ initialize_temperature_sensor(state);
+
+ return 0;
+}
+
+static umode_t mt9m02x_hwmon_is_visible(const void *private_data,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ return type == hwmon_temp && attr == hwmon_temp_input ? 0444 : 0;
+}
+
+static int mt9m02x_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct mt9m02x_state *state = dev_get_drvdata(dev);
+ const struct chip_info *chip = state->model->chip;
+ int ret;
+ u16 reg_value;
+
+ if (type != hwmon_temp || attr != hwmon_temp_input)
+ return -EINVAL;
+
+ ret = read_word_from_i2c_device(state->client, APT_TEMP_VALUE,
+ ®_value);
+ if (ret)
+ return ret;
+ reg_value &= chip->temp_mask;
+ /* reg_value is temp_valueX at temp_pointX milli degree C with linear
+ * interpolation.
+ */
+ *val = (((long)reg_value - (long)state->temp_value2) *
+ (chip->temp_point1 - chip->temp_point2)) /
+ ((long)state->temp_value1 - (long)state->temp_value2) +
+ chip->temp_point2;
+ return 0;
+}
+
+static const struct hwmon_ops mt9m02x_hwmon_ops = {
+ .is_visible = &mt9m02x_hwmon_is_visible,
+ .read = &mt9m02x_hwmon_read,
+};
+
+static const u32 mt9m02x_hwmon_temp_config[] = {
+ HWMON_T_INPUT,
+ 0
+};
+
+static const struct hwmon_channel_info mt9m02x_hwmon_temp_channel = {
+ .type = hwmon_temp,
+ .config = mt9m02x_hwmon_temp_config,
+};
+
+static const struct hwmon_channel_info *mt9m02x_hwmon_channel_info[] = {
+ &mt9m02x_hwmon_temp_channel,
+ NULL
+};
+
+static const struct hwmon_chip_info mt9m02x_hwmon_info = {
+ .ops = &mt9m02x_hwmon_ops,
+ .info = mt9m02x_hwmon_channel_info,
+};
+
+static bool mt9m02x_is_wdr(struct mt9m02x_state *state)
+{
+ return state->model->chip == &chip_mt9m024 &&
+ state->opmode_cluster.wdr->val;
+}
+
+static u32 mt9m02x_exposure_100us_to_coarse(struct mt9m02x_state *state, u32 v)
+{
+ u64 t = mul_u32_u32(v, state->pll.pix_clock);
+
+ /* ctrl->val is s/10000 and pix_clock is Hz. */
+ do_div(t, 10000);
+
+ /* MT9M024 datasheet rev. G page 24:
+ * When HDR is enabled, fine integration time is ignored.
+ */
+ if (!mt9m02x_is_wdr(state))
+ t -= APT_FINE_INTEGRATION_TIME_VALUE;
+ do_div(t, APT_LINE_LENGTH_PCK_VALUE);
+ dev_dbg(&state->client->dev,
+ "exposure = %u -> coarse integration = %llu\n", v, t);
+ return t;
+}
+
+static u32 mt9m02x_exposure_coarse_to_100us(struct mt9m02x_state *state, u32 v)
+{
+ u64 t = mul_u32_u32(v, APT_LINE_LENGTH_PCK_VALUE);
+
+ /* MT9M024 datasheet rev. G page 24:
+ * When HDR is enabled, fine integration time is ignored.
+ */
+ if (!mt9m02x_is_wdr(state))
+ t += APT_FINE_INTEGRATION_TIME_VALUE;
+ t = mul_u64_u32_div(t, 10000, state->pll.pix_clock);
+ dev_dbg(&state->client->dev,
+ "coarse integration = %u -> exposure = %llu\n", v, t);
+ return t;
+}
+
+static int mt9m02x_update_exposure_range(struct mt9m02x_state *state)
+{
+ u32 low, high, def;
+
+ if (mt9m02x_is_wdr(state)) {
+ /* MT9M024 datasheet rev. G, page 24:
+ * t1/t2 = 4 << ratio_t1t2
+ * t2/t3 = 4 << ratio_t2t3
+ * low >= (t1/t2) * (t2/t3) * 0.5
+ */
+ low = 8 << (state->opmode_cluster.ratio_t1t2->val +
+ state->opmode_cluster.ratio_t2t3->val);
+ /* high <= 42 * (t1/t2) */
+ high = min(APT_FRAME_LENGTH_LINES_VALUE - 45,
+ 42 * 4 << state->opmode_cluster.ratio_t1t2->val);
+ } else {
+ /* MT9M024 datasheet rev. G, page 21: */
+ low = 2;
+ /* MT9M024 datasheet rev. G, page 24: */
+ high = APT_FRAME_LENGTH_LINES_VALUE - 1;
+ }
+ WARN_ON(low > high);
+
+ low = max_t(u32, 1, mt9m02x_exposure_coarse_to_100us(state, low));
+ high = mt9m02x_exposure_coarse_to_100us(state, high);
+ def = mt9m02x_exposure_coarse_to_100us(
+ state, APT_COARSE_INTEGRATION_TIME_DEFAULT);
+
+ return __v4l2_ctrl_modify_range(state->exposure_ctrl, low, high, 1,
+ clamp(def, low, high));
+}
+
+static int mt9m02x_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct mt9m02x_state *state =
+ container_of(ctrl->handler, struct mt9m02x_state, ctrls);
+ int ret;
+ u16 temp_value;
+
+ switch (ctrl->id) {
+ case V4L2_CID_ISO_SENSITIVITY:
+ return state->model->chip->again_set(state, ctrl->val);
+
+ case V4L2_CID_DIGITAL_GAIN:
+ if (mt9m02x_is_wdr(state))
+ return 0; /* see V4L2_CID_WIDE_DYNAMIC_RANGE */
+
+ return write_word_to_i2c_device(
+ state->client, APT_GLOBAL_GAIN,
+ ctrl->val >> state->model->chip->digital_gain_shift);
+
+ case V4L2_CID_HFLIP:
+ /* case V4L2_CID_VFLIP: cluster */
+ return mt9m02x_update_read_mode(state);
+
+ case V4L2_CID_EXPOSURE_ABSOLUTE:
+ return write_word_to_i2c_device(
+ state->client, APT_COARSE_INTEGRATION_TIME,
+ mt9m02x_exposure_100us_to_coarse(state, ctrl->val));
+
+ case V4L2_CID_WIDE_DYNAMIC_RANGE:
+ /* case MT9M02X_V4L2_CID_HDR_RATIO_T1T2: cluster */
+ /* case MT9M02X_V4L2_CID_HDR_RATIO_T2T3: cluster */
+ if (state->model->chip != &chip_mt9m024)
+ return -EOPNOTSUPP;
+ /* Bits:
+ * 0: 0 = HDR, 1 = linear
+ * 1: fixed 0
+ * 2-3: ratio_t1t2
+ * 4-5: ratio_t2t3
+ * 6-15: fixed 0
+ */
+ temp_value =
+ (state->opmode_cluster.wdr->val ? 0 : 1) |
+ (state->opmode_cluster.ratio_t1t2->val << 2) |
+ (state->opmode_cluster.ratio_t2t3->val << 4);
+ ret = write_word_to_i2c_device(state->client,
+ APT_OPERATION_MODE_CTRL,
+ temp_value);
+ if (ret)
+ return ret;
+
+ ret = mt9m02x_update_exposure_range(state);
+ if (ret)
+ return ret;
+
+ /* Ratios are only active when HDR is enabled. */
+ v4l2_ctrl_activate(state->opmode_cluster.ratio_t1t2,
+ state->opmode_cluster.wdr->val);
+ v4l2_ctrl_activate(state->opmode_cluster.ratio_t2t3,
+ state->opmode_cluster.wdr->val);
+
+ /* When HDR is enabled, digital gain is dependent on ratios
+ * and cannot be controlled otherwise.
+ */
+ v4l2_ctrl_activate(state->digital_gain_ctrl,
+ !state->opmode_cluster.wdr->val);
+ if (state->opmode_cluster.wdr->val)
+ /* MT9M024 datasheet rev. G, table 7 */
+ temp_value = 1 << (1 +
+ state->opmode_cluster.ratio_t1t2->val +
+ state->opmode_cluster.ratio_t2t3->val);
+ else
+ temp_value = state->digital_gain_ctrl->val >>
+ state->model->chip->digital_gain_shift;
+ return write_word_to_i2c_device(state->client, APT_GLOBAL_GAIN,
+ temp_value);
+ }
+ return -EINVAL;
+}
+
+static const struct v4l2_ctrl_ops mt9m02x_ctrl_ops = {
+ .s_ctrl = mt9m02x_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config mt9m02x_hdr_ratio_t1t2_ctrl = {
+ .ops = &mt9m02x_ctrl_ops,
+ .id = MT9M02X_V4L2_CID_HDR_RATIO_T1T2,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "HDR ratio T1T2",
+ .min = 0,
+ .max = 2,
+ .step = 1,
+ .def = 0,
+ .flags = 0,
+};
+
+static const struct v4l2_ctrl_config mt9m02x_hdr_ratio_t2t3_ctrl = {
+ .ops = &mt9m02x_ctrl_ops,
+ .id = MT9M02X_V4L2_CID_HDR_RATIO_T2T3,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "HDR ratio T2T3",
+ .min = 0,
+ .max = 2,
+ .step = 1,
+ .def = 0,
+ .flags = 0,
+};
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int mt9m02x_g_register(struct v4l2_subdev *sd,
+ struct v4l2_dbg_register *reg)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ u16 value;
+ int ret;
+
+ if (reg->reg < 0x3000 || reg->reg > 0x3FFF)
+ return -EINVAL;
+
+ ret = read_word_from_i2c_device(client, reg->reg, &value);
+ if (ret)
+ return ret;
+
+ reg->val = value;
+ reg->size = 2;
+ return 0;
+}
+
+static int mt9m02x_s_register(struct v4l2_subdev *sd,
+ const struct v4l2_dbg_register *reg)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+ if (reg->reg < 0x3000 || reg->reg > 0x3FFF)
+ return -EINVAL;
+
+ return write_word_to_i2c_device(client, reg->reg, reg->val);
+}
+#endif
+
+static const struct v4l2_subdev_core_ops mt9m02x_v4l2_subdev_core_ops = {
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .g_register = &mt9m02x_g_register,
+ .s_register = &mt9m02x_s_register,
+#endif
+};
+
+static inline u32 get_mbus_code(struct mt9m02x_state *state)
+{
+ return state->model->monochrome ? MEDIA_BUS_FMT_Y12_1X12 :
+ MEDIA_BUS_FMT_SGRBG12_1X12;
+}
+
+static int mt9m02x_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct mt9m02x_state *state = container_of(sd, struct mt9m02x_state,
+ sd);
+
+ if (code->pad || code->index)
+ return -EINVAL;
+ code->code = get_mbus_code(state);
+ return 0;
+}
+
+static int mt9m02x_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct mt9m02x_state *state = container_of(sd, struct mt9m02x_state,
+ sd);
+
+ if (fse->pad || fse->code != get_mbus_code(state) || fse->index > 1)
+ return -EINVAL;
+ fse->min_width = state->model->chip->width / (fse->index + 1);
+ fse->max_width = fse->min_width;
+ fse->min_height = state->model->chip->height / (fse->index + 1);
+ fse->max_height = fse->min_height;
+ return 0;
+}
+
+static int mt9m02x_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct mt9m02x_state *state = container_of(sd, struct mt9m02x_state,
+ sd);
+
+ if (format->pad)
+ return -EINVAL;
+
+ format->format.field = V4L2_FIELD_NONE;
+ format->format.code = get_mbus_code(state);
+ format->format.colorspace = V4L2_COLORSPACE_SRGB;
+ mutex_lock(state->ctrls.lock);
+ format->format.width = state->model->chip->width /
+ (state->binning + 1);
+ format->format.height = state->model->chip->height /
+ (state->binning + 1);
+ mutex_unlock(state->ctrls.lock);
+ return 0;
+}
+
+static int mt9m02x_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct mt9m02x_state *state = container_of(sd, struct mt9m02x_state,
+ sd);
+ bool binning = false;
+ int ret;
+
+ if (format->pad)
+ return -EINVAL;
+
+ format->format.field = V4L2_FIELD_NONE;
+ format->format.code = get_mbus_code(state);
+ format->format.colorspace = V4L2_COLORSPACE_SRGB;
+ if (format->format.width * 4 <= state->model->chip->width * 3 &&
+ format->format.height * 4 <= state->model->chip->height * 3)
+ binning = true;
+ format->format.width = state->model->chip->width / (binning + 1);
+ format->format.height = state->model->chip->height / (binning + 1);
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
+ cfg->try_fmt = format->format;
+ return 0;
+ }
+ mutex_lock(state->ctrls.lock);
+ state->binning = binning;
+ ret = mt9m02x_update_read_mode(state);
+ if (state->model->chip == &chip_ar0141cs && !ret)
+ ret = ar0141cs_update_odd_inc(state);
+ mutex_unlock(state->ctrls.lock);
+ return ret;
+}
+
+static const struct v4l2_subdev_pad_ops mt9m02x_v4l2_subdev_pad_ops = {
+ .enum_mbus_code = &mt9m02x_enum_mbus_code,
+ .enum_frame_size = &mt9m02x_enum_frame_size,
+ .get_fmt = &mt9m02x_get_fmt,
+ .set_fmt = &mt9m02x_set_fmt,
+};
+
+static const struct v4l2_subdev_ops mt9m02x_v4l2_subdev_ops = {
+ .core = &mt9m02x_v4l2_subdev_core_ops,
+ .pad = &mt9m02x_v4l2_subdev_pad_ops,
+};
+
+// read out device tree and probe driver
+static int mt9m02x_probe(struct i2c_client *client,
+ const struct i2c_device_id *did)
+{
+ int ret;
+ struct mt9m02x_state *state;
+ struct mt9m02x_platform_data pdatabuffer;
+ struct mt9m02x_platform_data *pdata;
+ struct v4l2_ctrl *ctrl;
+
+ dev_dbg(&client->dev, "start probing i2c device %s!\n", client->name);
+
+ if (client->dev.of_node) {
+ if (of_property_read_u32(client->dev.of_node,
+ "input-clock-frequency",
+ &pdatabuffer.ext_freq)) {
+ dev_err(&client->dev,
+ "input-clock-frequency property missing");
+ return -EINVAL;
+ }
+ if (of_property_read_u32(client->dev.of_node,
+ "pixel-clock-frequency",
+ &pdatabuffer.pix_clock)) {
+ dev_err(&client->dev,
+ "pixel-clock-frequency property missing");
+ return -EINVAL;
+ }
+ pdata = &pdatabuffer;
+ } else {
+ pdata = client->dev.platform_data;
+ if (!pdata) {
+ dev_err(&client->dev, "No platform data found.");
+ return -EINVAL;
+ }
+ }
+
+ state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ state->client = client;
+ state->model = (const void *)did->driver_data;
+ state->pll.ext_clock = pdata->ext_freq;
+ dev_dbg(&client->dev, "ext_clock = %d\n", state->pll.ext_clock);
+ state->pll.pix_clock = pdata->pix_clock;
+ dev_dbg(&client->dev, "pix_clock = %d\n", state->pll.pix_clock);
+
+ state->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(state->reset_gpio))
+ return PTR_ERR(state->reset_gpio);
+ state->standby_gpio = devm_gpiod_get_optional(&client->dev, "standby",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(state->standby_gpio))
+ return PTR_ERR(state->standby_gpio);
+
+ ret = init_image_sensor(state);
+ if (ret) {
+ dev_err(&client->dev, "init image sensor failed!\n");
+ return ret;
+ }
+
+ dev_dbg(&client->dev, "initialization succeeded!\n");
+
+ v4l2_ctrl_handler_init(&state->ctrls,
+ state->model->chip == &chip_mt9m024 ? 9 : 6);
+ /* Monotone mapping of available gains to 0..N. */
+ v4l2_ctrl_new_int_menu(&state->ctrls, &mt9m02x_ctrl_ops,
+ V4L2_CID_ISO_SENSITIVITY,
+ state->model->chip->again_menu_length - 1, 0,
+ state->model->chip->again_menu);
+ state->digital_gain_ctrl = v4l2_ctrl_new_std(
+ &state->ctrls, &mt9m02x_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+ state->model->chip->digital_gain_min,
+ GENMASK(state->model->chip->digital_gain_bits - 1, 0) *
+ BIT(state->model->chip->digital_gain_shift),
+ BIT(state->model->chip->digital_gain_shift),
+ max_t(s64, BIT(8), state->model->chip->digital_gain_min));
+ state->mode_cluster.hflip = v4l2_ctrl_new_std(
+ &state->ctrls, &mt9m02x_ctrl_ops, V4L2_CID_HFLIP,
+ 0, 1, 1, 1);
+ state->mode_cluster.vflip = v4l2_ctrl_new_std(
+ &state->ctrls, &mt9m02x_ctrl_ops, V4L2_CID_VFLIP,
+ 0, 1, 1, 1);
+ ctrl = v4l2_ctrl_new_std(&state->ctrls, &mt9m02x_ctrl_ops,
+ V4L2_CID_PIXEL_RATE, state->pll.pix_clock,
+ state->pll.pix_clock, 1,
+ state->pll.pix_clock);
+ if (ctrl)
+ ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+ state->exposure_ctrl = v4l2_ctrl_new_std(
+ &state->ctrls,
+ &mt9m02x_ctrl_ops,
+ V4L2_CID_EXPOSURE_ABSOLUTE,
+ /* Actual range is set later. */
+ mt9m02x_exposure_coarse_to_100us(
+ state, APT_COARSE_INTEGRATION_TIME_DEFAULT),
+ mt9m02x_exposure_coarse_to_100us(
+ state, APT_COARSE_INTEGRATION_TIME_DEFAULT),
+ 1,
+ mt9m02x_exposure_coarse_to_100us(
+ state, APT_COARSE_INTEGRATION_TIME_DEFAULT));
+
+ if (state->model->chip == &chip_mt9m024) {
+ state->opmode_cluster.wdr = v4l2_ctrl_new_std(
+ &state->ctrls, &mt9m02x_ctrl_ops,
+ V4L2_CID_WIDE_DYNAMIC_RANGE, 0, 1, 1, 0);
+ state->opmode_cluster.ratio_t1t2 = v4l2_ctrl_new_custom(
+ &state->ctrls, &mt9m02x_hdr_ratio_t1t2_ctrl,
+ NULL);
+ state->opmode_cluster.ratio_t2t3 = v4l2_ctrl_new_custom(
+ &state->ctrls, &mt9m02x_hdr_ratio_t2t3_ctrl,
+ NULL);
+ }
+
+ if (state->ctrls.error) {
+ dev_err(&client->dev,
+ "v4l2 control handler registration failed!\n");
+ return state->ctrls.error;
+ }
+ v4l2_ctrl_cluster(2, &state->mode_cluster.hflip);
+ if (state->model->chip == &chip_mt9m024)
+ v4l2_ctrl_cluster(3, &state->opmode_cluster.wdr);
+
+ state->sd.ctrl_handler = &state->ctrls;
+
+ ret = v4l2_ctrl_handler_setup(&state->ctrls);
+ if (ret) {
+ dev_err(&client->dev, "v4l2_ctrl_handler_setup failed!\n");
+ return ret;
+ }
+
+ v4l2_ctrl_lock(state->exposure_ctrl);
+ ret = mt9m02x_update_exposure_range(state);
+ v4l2_ctrl_unlock(state->exposure_ctrl);
+ if (ret)
+ return ret;
+
+ v4l2_i2c_subdev_init(&state->sd, client, &mt9m02x_v4l2_subdev_ops);
+
+ state->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ state->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+ state->pad.flags = MEDIA_PAD_FL_SOURCE;
+ ret = media_entity_pads_init(&state->sd.entity, 1, &state->pad);
+ if (ret)
+ return ret;
+
+ ret = v4l2_async_register_subdev(&state->sd);
+ if (ret) {
+ dev_err(&client->dev, "v4l2_async_register_subdev failed!\n");
+ goto error_entity;
+ }
+
+ if (state->temp_value1 != state->temp_value2) {
+ struct device *hwmon_dev;
+ /* Ignore errors from hwmon device as this functionality is
+ * optional.
+ */
+ hwmon_dev = devm_hwmon_device_register_with_info(
+ &client->dev, client->name, state,
+ &mt9m02x_hwmon_info, NULL);
+ if (IS_ERR(hwmon_dev))
+ dev_warn(&client->dev,
+ "adding hwmon device failed (%ld).",
+ PTR_ERR(hwmon_dev));
+ }
+
+ dev_dbg(&client->dev, "probing i2c device %s successful.\n",
+ client->name);
+ return 0;
+error_entity:
+ media_entity_cleanup(&state->sd.entity);
+ return ret;
+}
+
+static int mt9m02x_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+ struct mt9m02x_state *state =
+ container_of(subdev, struct mt9m02x_state, sd);
+
+ v4l2_device_unregister_subdev(subdev);
+ media_entity_cleanup(&subdev->entity);
+
+ if (state->standby_gpio)
+ gpiod_set_value_cansleep(state->standby_gpio, 1);
+ return 0;
+}
+
+static const struct i2c_device_id mt9m02x_id[] = {
+ { "ar0141cs", (kernel_ulong_t)&model_ar0141cs },
+ { "ar0141csm", (kernel_ulong_t)&model_ar0141csm },
+ { "mt9m024", (kernel_ulong_t)&model_mt9m024 },
+ { "mt9m024m", (kernel_ulong_t)&model_mt9m024m },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, mt9m02x_id);
+
+static struct i2c_driver mt9m02x_driver = {
+ .driver = {
+ .name = "mt9m02x",
+ },
+ .probe = mt9m02x_probe,
+ .remove = mt9m02x_remove,
+ .id_table = mt9m02x_id,
+};
+
+module_i2c_driver(mt9m02x_driver);
+
+MODULE_AUTHOR("Helmut Grohne <helmut.grohne@intenta.de>");
+MODULE_DESCRIPTION("mt9m024/ar0141cs imager driver");
+MODULE_LICENSE("GPL");
--
2.20.1
next reply other threads:[~2020-10-20 9:33 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-10-20 9:27 Helmut Grohne [this message]
2020-10-21 9:50 ` [RFC PATCH] media: new driver mt9m02x for Onsemi MT9M024 and AR0141CS Sakari Ailus
2020-10-21 11:21 ` Helmut Grohne
2020-10-21 12:37 ` Sakari Ailus
2020-10-22 12:17 ` Helmut Grohne
2020-10-22 12:39 ` Sakari Ailus
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20201020092732.GA30983@laureti-dev \
--to=helmut.grohne@intenta.de \
--cc=laurent.pinchart@ideasonboard.com \
--cc=linux-media@vger.kernel.org \
--cc=mchehab@kernel.org \
--cc=sakari.ailus@linux.intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).