* [PATCH] staging: media: atomisp: remove unused macros
From: Rhys Tumelty @ 2026-06-06 9:14 UTC (permalink / raw)
To: hansg, mchehab, gregkh
Cc: sakari.ailus, andy, linux-media, linux-staging, linux-kernel,
Rhys Tumelty
removed unused macros across the atomisp driver that are defined
in .c files, but never used, which was flagged as errors in a
W=2 build, due to -Werror=unused-macros.
Signed-off-by: Rhys Tumelty <rhys@tumelty.co.uk>
---
.../media/atomisp/pci/atomisp_compat_css20.c | 3 ---
.../media/atomisp/pci/atomisp_gmin_platform.c | 3 ---
drivers/staging/media/atomisp/pci/atomisp_v4l2.c | 7 -------
.../atomisp/pci/hive_isp_css_common/host/debug.c | 1 -
drivers/staging/media/atomisp/pci/mmu/isp_mmu.c | 14 --------------
.../atomisp/pci/runtime/inputfifo/src/inputfifo.c | 3 ---
drivers/staging/media/atomisp/pci/sh_css.c | 3 ---
drivers/staging/media/atomisp/pci/sh_css_hrt.c | 2 --
drivers/staging/media/atomisp/pci/sh_css_metrics.c | 6 ------
9 files changed, 42 deletions(-)
diff --git a/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c b/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c
index be5f37f4a6fd..95edc98137cc 100644
--- a/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c
+++ b/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c
@@ -28,9 +28,6 @@
#include <linux/io.h>
#include <linux/pm_runtime.h>
-/* Assume max number of ACC stages */
-#define MAX_ACC_STAGES 20
-
/* Ideally, this should come from CSS headers */
#define NO_LINK -1
diff --git a/drivers/staging/media/atomisp/pci/atomisp_gmin_platform.c b/drivers/staging/media/atomisp/pci/atomisp_gmin_platform.c
index 4026e98c5845..0eafd81c44cd 100644
--- a/drivers/staging/media/atomisp/pci/atomisp_gmin_platform.c
+++ b/drivers/staging/media/atomisp/pci/atomisp_gmin_platform.c
@@ -48,7 +48,6 @@ enum clock_rate {
/* TI SND9039 PMIC register set */
#define LDO9_REG 0x49
#define LDO10_REG 0x4a
-#define LDO11_REG 0x4b
#define LDO_2P8V_ON 0x2f /* 0x2e selects 2.85V ... */
#define LDO_2P8V_OFF 0x2e /* ... bottom bit is "enabled" */
@@ -99,8 +98,6 @@ static struct gmin_subdev gmin_subdevs[MAX_SUBDEVS];
#define PMIC_ACPI_TI "INT33F5" /* Dollar Cove TI PMIC */
#define PMIC_ACPI_CRYSTALCOVE "INT33FD" /* Crystal Cove PMIC */
-#define PMIC_PLATFORM_TI "intel_soc_pmic_chtdc_ti"
-
static enum {
PMIC_UNSET = 0,
PMIC_REGULATOR,
diff --git a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c
index 900a67552d6a..eaaa3753abf9 100644
--- a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c
+++ b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c
@@ -36,13 +36,6 @@
#include "device_access.h"
-/* Timeouts to wait for all subdevs to be registered */
-#define SUBDEV_WAIT_TIMEOUT 50 /* ms */
-#define SUBDEV_WAIT_TIMEOUT_MAX_COUNT 40 /* up to 2 seconds */
-
-/* G-Min addition: pull this in from intel_mid_pm.h */
-#define CSTATE_EXIT_LATENCY_C1 1
-
/* cross component debug message flag */
int dbg_level;
module_param(dbg_level, int, 0644);
diff --git a/drivers/staging/media/atomisp/pci/hive_isp_css_common/host/debug.c b/drivers/staging/media/atomisp/pci/hive_isp_css_common/host/debug.c
index 8513e78856b2..d05832e7f337 100644
--- a/drivers/staging/media/atomisp/pci/hive_isp_css_common/host/debug.c
+++ b/drivers/staging/media/atomisp/pci/hive_isp_css_common/host/debug.c
@@ -12,7 +12,6 @@
#include "debug_private.h"
#endif /* __INLINE_DEBUG__ */
-#define __INLINE_SP__
#include "sp.h"
#include "assert_support.h"
diff --git a/drivers/staging/media/atomisp/pci/mmu/isp_mmu.c b/drivers/staging/media/atomisp/pci/mmu/isp_mmu.c
index 5193a7eb7d9f..6a8c5ba27b02 100644
--- a/drivers/staging/media/atomisp/pci/mmu/isp_mmu.c
+++ b/drivers/staging/media/atomisp/pci/mmu/isp_mmu.c
@@ -29,20 +29,6 @@
#include "atomisp_internal.h"
#include "mmu/isp_mmu.h"
-/*
- * 64-bit x86 processor physical address layout:
- * 0 - 0x7fffffff DDR RAM (2GB)
- * 0x80000000 - 0xffffffff MMIO (2GB)
- * 0x100000000 - 0x3fffffffffff DDR RAM (64TB)
- * So if the system has more than 2GB DDR memory, the lower 2GB occupies the
- * physical address 0 - 0x7fffffff and the rest will start from 0x100000000.
- * We have to make sure memory is allocated from the lower 2GB for devices
- * that are only 32-bit capable(e.g. the ISP MMU).
- *
- * For any confusion, contact bin.gao@intel.com.
- */
-#define NR_PAGES_2GB (SZ_2G / PAGE_SIZE)
-
static void free_mmu_map(struct isp_mmu *mmu, unsigned int start_isp_virt,
unsigned int end_isp_virt);
diff --git a/drivers/staging/media/atomisp/pci/runtime/inputfifo/src/inputfifo.c b/drivers/staging/media/atomisp/pci/runtime/inputfifo/src/inputfifo.c
index 8e1efeb6372c..b084f9edb8a7 100644
--- a/drivers/staging/media/atomisp/pci/runtime/inputfifo/src/inputfifo.c
+++ b/drivers/staging/media/atomisp/pci/runtime/inputfifo/src/inputfifo.c
@@ -10,9 +10,7 @@
#include "device_access.h"
-#define __INLINE_SP__
#include "sp.h"
-#define __INLINE_ISP__
#include "isp.h"
#define __INLINE_IRQ__
#include "irq.h"
@@ -21,7 +19,6 @@
#define __INLINE_EVENT__
#include "event_fifo.h"
-#define __INLINE_SP__
#include "input_system.h" /* MIPI_PREDICTOR_NONE,... */
diff --git a/drivers/staging/media/atomisp/pci/sh_css.c b/drivers/staging/media/atomisp/pci/sh_css.c
index 6cda5925fa45..ec3ff31f76e0 100644
--- a/drivers/staging/media/atomisp/pci/sh_css.c
+++ b/drivers/staging/media/atomisp/pci/sh_css.c
@@ -61,9 +61,6 @@
#include <gpio_private.h>
#include "timed_ctrl.h"
#include "ia_css_inputfifo.h"
-#define WITH_PC_MONITORING 0
-
-#define SH_CSS_VIDEO_BUFFER_ALIGNMENT 0
#include "ia_css_spctrl.h"
diff --git a/drivers/staging/media/atomisp/pci/sh_css_hrt.c b/drivers/staging/media/atomisp/pci/sh_css_hrt.c
index d4633572f8f3..1ef95308bd48 100644
--- a/drivers/staging/media/atomisp/pci/sh_css_hrt.c
+++ b/drivers/staging/media/atomisp/pci/sh_css_hrt.c
@@ -13,9 +13,7 @@
#define __INLINE_EVENT__
#include "event_fifo.h"
-#define __INLINE_SP__
#include "sp.h"
-#define __INLINE_ISP__
#include "isp.h"
#define __INLINE_IRQ__
#include "irq.h"
diff --git a/drivers/staging/media/atomisp/pci/sh_css_metrics.c b/drivers/staging/media/atomisp/pci/sh_css_metrics.c
index edf473dd86ca..24cdd52283ba 100644
--- a/drivers/staging/media/atomisp/pci/sh_css_metrics.c
+++ b/drivers/staging/media/atomisp/pci/sh_css_metrics.c
@@ -12,12 +12,6 @@
#include "sh_css_internal.h"
-#define MULTIPLE_PCS 0
-#define SUSPEND 0
-#define NOF_PCS 1
-#define RESUME_MASK 0x8
-#define STOP_MASK 0x0
-
static bool pc_histogram_enabled;
static struct sh_css_pc_histogram *isp_histogram;
static struct sh_css_pc_histogram *sp_histogram;
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] staging: greybus: audio: fix snprintf truncation errors
From: Dan Carpenter @ 2026-06-06 7:08 UTC (permalink / raw)
To: Rhys Tumelty
Cc: gregkh, Vaibhav Agarwal, Mark Greer, Johan Hovold, Alex Elder,
greybus-dev, linux-staging, linux-kernel
In-Reply-To: <20260605192857.78944-1-rhys@tumelty.co.uk>
On Fri, Jun 05, 2026 at 08:28:56PM +0100, Rhys Tumelty wrote:
> change snprintf() to scnprintf() in both gbaudio_tplg_create_widget()
> and gbaudio_tplg_process_kcontrols() to prevent potential string
> truncation warnings when prefixing the device id to the control name.
>
This commit message is unclear. My understanding is that snprintf()
is complaining that the array size of w->name is less than the array
size of "GB %d %s" plus the array size of temp_name. This is a W=1
complaint.
I hate this warning. We use snprintf() to deliberately truncate
the string. Now it's complaining that the string might be truncated.
Oh no! What we want to happen might happen! This is the same argument
that people used to block safer alternatives to strcpy() into glibc
because "it's still going to truncate the string and that's equally
bad as a root exploit!"
First of all, the string is not going to be truncated. (I haven't
looked). Second of all, this warning makes no sense in the kernel.
I have never once had a bug which I failed to debug because the
last two bytes in a string were truncated. There has never been a
scenario where I was looking through dmesg and snprintf() truncated
some bytes so I couldn't guess what I was looking at.
So your solution is to change it to scnprintf() which is kernel only
and GCC doesn't know about it... I bet GCC eventually learns about
scnprintf() and it eventually becomes a warning again.
A better solution is to disable that annoying check.
regards,
dan carpenter
^ permalink raw reply
* Re: [linux-6.1.y 1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
From: Sasha Levin @ 2026-06-05 19:37 UTC (permalink / raw)
To: lee, Jiri Kosina, Benjamin Tissoires, Filipe Laíns,
Bastien Nocera, Ping Cheng, Jason Gerecke, Viresh Kumar,
Johan Hovold, Alex Elder, Greg Kroah-Hartman, linux-input,
linux-kernel, greybus-dev, linux-staging
Cc: Sasha Levin, stable, Vicki Pfau, Jiri Kosina
In-Reply-To: <20260603163022.3301081-1-lee@kernel.org>
> [linux-6.1.y 1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
Whole series queued for 6.1.y, thanks.
--
Thanks,
Sasha
^ permalink raw reply
* Re: [linux-5.15.y 1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
From: Sasha Levin @ 2026-06-05 19:37 UTC (permalink / raw)
To: lee, Jiri Kosina, Benjamin Tissoires, Viresh Kumar, Johan Hovold,
Alex Elder, Greg Kroah-Hartman, linux-input, linux-kernel,
greybus-dev, linux-staging
Cc: Sasha Levin, stable, Vicki Pfau, Jiri Kosina
In-Reply-To: <20260604092659.3953067-1-lee@kernel.org>
> [linux-5.15.y 1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
Whole series queued for 5.15.y, thanks.
--
Thanks,
Sasha
^ permalink raw reply
* [PATCH] staging: greybus: audio: fix snprintf truncation errors
From: Rhys Tumelty @ 2026-06-05 19:28 UTC (permalink / raw)
To: gregkh
Cc: Rhys Tumelty, Vaibhav Agarwal, Mark Greer, Johan Hovold,
Alex Elder, greybus-dev, linux-staging, linux-kernel
change snprintf() to scnprintf() in both gbaudio_tplg_create_widget()
and gbaudio_tplg_process_kcontrols() to prevent potential string
truncation warnings when prefixing the device id to the control name.
Signed-off-by: Rhys Tumelty <rhys@tumelty.co.uk>
---
drivers/staging/greybus/audio_topology.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/staging/greybus/audio_topology.c b/drivers/staging/greybus/audio_topology.c
index 76146f91c..b19febabb 100644
--- a/drivers/staging/greybus/audio_topology.c
+++ b/drivers/staging/greybus/audio_topology.c
@@ -1087,7 +1087,7 @@ static int gbaudio_tplg_create_widget(struct gbaudio_module_info *module,
/* Prefix dev_id to widget control_name */
strscpy(temp_name, w->name, sizeof(temp_name));
- snprintf(w->name, sizeof(w->name), "GB %d %s", module->dev_id, temp_name);
+ scnprintf(w->name, sizeof(w->name), "GB %d %s", module->dev_id, temp_name);
switch (w->type) {
case snd_soc_dapm_spk:
@@ -1169,8 +1169,8 @@ static int gbaudio_tplg_process_kcontrols(struct gbaudio_module_info *module,
control->id = curr->id;
/* Prefix dev_id to widget_name */
strscpy(temp_name, curr->name, sizeof(temp_name));
- snprintf(curr->name, sizeof(curr->name), "GB %d %s", module->dev_id,
- temp_name);
+ scnprintf(curr->name, sizeof(curr->name), "GB %d %s", module->dev_id,
+ temp_name);
control->name = curr->name;
if (curr->info.type == GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED) {
struct gb_audio_enumerated *gbenum =
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v2 1/1] MAINTAINERS: Camera sensor and Intel IPU driver changes
From: Frank Li @ 2026-06-05 18:49 UTC (permalink / raw)
To: Sakari Ailus
Cc: linux-media, Yong Zhi, Mauro Carvalho Chehab, Greg Kroah-Hartman,
Lixu Zhang, linux-kernel, linux-staging, Bingbu Cao,
Dave Stevenson, David Heidelberg, Richard Acayan
In-Reply-To: <20260605074944.666654-1-sakari.ailus@linux.intel.com>
On Fri, Jun 05, 2026 at 10:49:44AM +0300, Sakari Ailus wrote:
> From: Dave Hansen <dave.hansen@linux.intel.com>
>
> Tian Shu Qiu and Bingbu Cao are maintainers and reviewers of a bunch of
> media drivers (7 and 9 respectively). Bingbu's e-mail address has changed
> and Tian Shu's is bouncing.
>
> Update Bingbu's e-mail address, remove Bingbu as a maintainer from Intel
> specific drivers and and remove Tian Shu as maintainer. Also add Dave
> Stevenson as a maintainer and David Heidelberg as a reviewer for the
> imx355 driver.
>
> Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
> Cc: Yong Zhi <yong.zhi@intel.com>
> Cc: Dan Scally <dan.scally@ideasonboard.com>
> Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: Lixu Zhang <lixu.zhang@intel.com>
> Cc: linux-media@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-staging@lists.linux.dev
> Co-developed-by: Sakari Ailus <sakari.ailus@linux.intel.com>
> Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
> ---
> MAINTAINERS | 29 ++++++++++++-----------------
> 1 file changed, 12 insertions(+), 17 deletions(-)
>
...
>
> INTEL IPU6 INPUT SYSTEM DRIVER
> M: Sakari Ailus <sakari.ailus@linux.intel.com>
> -M: Bingbu Cao <bingbu.cao@intel.com>
> -R: Tianshu Qiu <tian.shu.qiu@intel.com>
Need add both to CREDITS files
Frank
^ permalink raw reply
* Re: [linux-6.6.y 1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
From: patchwork-bot+netdevbpf @ 2026-06-05 10:05 UTC (permalink / raw)
To: Lee Jones
Cc: jikos, benjamin.tissoires, lains, hadess, ping.cheng,
jason.gerecke, vireshk, johan, elder, gregkh, sashal, linux-input,
linux-kernel, greybus-dev, linux-staging, bpf, stable, vi,
jkosina
In-Reply-To: <20260603133140.3069226-1-lee@kernel.org>
Hello:
This series was applied to bpf/bpf-next.git (net)
by Linus Torvalds <torvalds@linux-foundation.org>:
On Wed, 3 Jun 2026 14:31:25 +0100 you wrote:
> From: Vicki Pfau <vi@endrift.com>
>
> hid_warn_ratelimited() is needed. Add the others as part of the block.
>
> Signed-off-by: Vicki Pfau <vi@endrift.com>
> Signed-off-by: Jiri Kosina <jkosina@suse.com>
> (cherry picked from commit 1d64624243af8329b4b219d8c39e28ea448f9929)
> Signed-off-by: Lee Jones <lee@kernel.org>
>
> [...]
Here is the summary with links:
- [linux-6.6.y,1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
(no matching commit)
- [linux-6.6.y,2/3] HID: pass the buffer size to hid_report_raw_event
(no matching commit)
- [linux-6.6.y,3/3] HID: core: Fix size_t specifier in hid_report_raw_event()
https://git.kernel.org/bpf/bpf-next/c/4d3a2a466b8d
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* Re: [PATCH v2 1/1] staging: media: tegra-video: vi: Improve media graph building logic
From: Luca Ceresoli @ 2026-06-05 8:29 UTC (permalink / raw)
To: Svyatoslav Ryhel, Thierry Reding, Jonathan Hunter,
Sowjanya Komatineni, Luca Ceresoli, Mauro Carvalho Chehab,
Hans Verkuil, Greg Kroah-Hartman
Cc: linux-media, linux-tegra, linux-staging, linux-kernel
In-Reply-To: <20260523064750.35553-2-clamor95@gmail.com>
On Sat May 23, 2026 at 8:47 AM CEST, Svyatoslav Ryhel wrote:
> The existing tegra_vi_graph_build function relies heavily on a one-to-one
> match between Device Tree nodes and media pad links. While this works for
> simpler configurations, it causes issues when Device Tree nodes do not
> match media pad link logic (e.g., mt9m114). Switch to the
> media_entity_get_fwnode_pad helper to verify and retrieve the correct pad
> linked to an endpoint, rather than assuming the endpoint ID matches the
> pad ID.
>
> Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
I still haven't looked at the code, but:
Tested-by: Luca Ceresoli <luca.ceresoli@bootlin.com> # tegra20, parallel camera
Luca
--
Luca Ceresoli, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* [PATCH v2 1/1] MAINTAINERS: Camera sensor and Intel IPU driver changes
From: Sakari Ailus @ 2026-06-05 7:49 UTC (permalink / raw)
To: linux-media
Cc: Yong Zhi, Mauro Carvalho Chehab, Greg Kroah-Hartman, Lixu Zhang,
linux-kernel, linux-staging, Bingbu Cao, Dave Stevenson,
David Heidelberg, Richard Acayan
From: Dave Hansen <dave.hansen@linux.intel.com>
Tian Shu Qiu and Bingbu Cao are maintainers and reviewers of a bunch of
media drivers (7 and 9 respectively). Bingbu's e-mail address has changed
and Tian Shu's is bouncing.
Update Bingbu's e-mail address, remove Bingbu as a maintainer from Intel
specific drivers and and remove Tian Shu as maintainer. Also add Dave
Stevenson as a maintainer and David Heidelberg as a reviewer for the
imx355 driver.
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Yong Zhi <yong.zhi@intel.com>
Cc: Dan Scally <dan.scally@ideasonboard.com>
Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Lixu Zhang <lixu.zhang@intel.com>
Cc: linux-media@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-staging@lists.linux.dev
Co-developed-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
---
MAINTAINERS | 29 ++++++++++++-----------------
1 file changed, 12 insertions(+), 17 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index efbf808063e5..64478875cd78 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3882,8 +3882,8 @@ F: Documentation/devicetree/bindings/leds/ams,as3668.yaml
F: drivers/leds/leds-as3668.c
ASAHI KASEI AK7375 LENS VOICE COIL DRIVER
-M: Tianshu Qiu <tian.shu.qiu@intel.com>
L: linux-media@vger.kernel.org
+M: Sakari Ailus <sakari.ailus@linux.intel.com>
S: Maintained
T: git git://linuxtv.org/media.git
F: Documentation/devicetree/bindings/media/i2c/asahi-kasei,ak7375.yaml
@@ -13042,9 +13042,7 @@ F: drivers/iommu/intel/
INTEL IPU3 CSI-2 CIO2 DRIVER
M: Yong Zhi <yong.zhi@intel.com>
M: Sakari Ailus <sakari.ailus@linux.intel.com>
-M: Bingbu Cao <bingbu.cao@intel.com>
M: Dan Scally <dan.scally@ideasonboard.com>
-R: Tianshu Qiu <tian.shu.qiu@intel.com>
L: linux-media@vger.kernel.org
S: Maintained
T: git git://linuxtv.org/media.git
@@ -13053,8 +13051,6 @@ F: drivers/media/pci/intel/ipu3/
INTEL IPU3 CSI-2 IMGU DRIVER
M: Sakari Ailus <sakari.ailus@linux.intel.com>
-R: Bingbu Cao <bingbu.cao@intel.com>
-R: Tianshu Qiu <tian.shu.qiu@intel.com>
L: linux-media@vger.kernel.org
S: Maintained
F: Documentation/admin-guide/media/ipu3.rst
@@ -13064,8 +13060,6 @@ F: drivers/staging/media/ipu3/
INTEL IPU6 INPUT SYSTEM DRIVER
M: Sakari Ailus <sakari.ailus@linux.intel.com>
-M: Bingbu Cao <bingbu.cao@intel.com>
-R: Tianshu Qiu <tian.shu.qiu@intel.com>
L: linux-media@vger.kernel.org
S: Maintained
T: git git://linuxtv.org/media.git
@@ -13074,7 +13068,6 @@ F: drivers/media/pci/intel/ipu6/
INTEL IPU7 INPUT SYSTEM DRIVER
M: Sakari Ailus <sakari.ailus@linux.intel.com>
-R: Bingbu Cao <bingbu.cao@intel.com>
L: linux-media@vger.kernel.org
S: Maintained
T: git git://linuxtv.org/media.git
@@ -13376,7 +13369,6 @@ F: drivers/net/wireless/intel/iwlwifi/
INTEL VISION SENSING CONTROLLER DRIVER
M: Sakari Ailus <sakari.ailus@linux.intel.com>
-R: Bingbu Cao <bingbu.cao@intel.com>
R: Lixu Zhang <lixu.zhang@intel.com>
L: linux-media@vger.kernel.org
S: Maintained
@@ -19657,7 +19649,8 @@ F: Documentation/devicetree/bindings/media/i2c/ovti,os05b10.yaml
F: drivers/media/i2c/os05b10.c
OMNIVISION OV01A10 SENSOR DRIVER
-M: Bingbu Cao <bingbu.cao@intel.com>
+M: Bingbu Cao <bingbu.cao@amd.com>
+M: Sakari Ailus <sakari.ailus@linux.intel.com>
L: linux-media@vger.kernel.org
S: Maintained
T: git git://linuxtv.org/media.git
@@ -19750,9 +19743,8 @@ F: Documentation/devicetree/bindings/media/i2c/ovti,ov2735.yaml
F: drivers/media/i2c/ov2735.c
OMNIVISION OV2740 SENSOR DRIVER
-M: Tianshu Qiu <tian.shu.qiu@intel.com>
-R: Sakari Ailus <sakari.ailus@linux.intel.com>
-R: Bingbu Cao <bingbu.cao@intel.com>
+M: Sakari Ailus <sakari.ailus@linux.intel.com>
+R: Bingbu Cao <bingbu.cao@amd.com>
L: linux-media@vger.kernel.org
S: Maintained
T: git git://linuxtv.org/media.git
@@ -19894,9 +19886,9 @@ F: Documentation/devicetree/bindings/media/i2c/ovti,ov9650.txt
F: drivers/media/i2c/ov9650.c
OMNIVISION OV9734 SENSOR DRIVER
-M: Tianshu Qiu <tian.shu.qiu@intel.com>
-R: Bingbu Cao <bingbu.cao@intel.com>
L: linux-media@vger.kernel.org
+M: Sakari Ailus <sakari.ailus@linux.intel.com>
+R: Bingbu Cao <bingbu.cao@amd.com>
S: Maintained
T: git git://linuxtv.org/media.git
F: drivers/media/i2c/ov9734.c
@@ -24912,7 +24904,8 @@ F: Documentation/devicetree/bindings/media/i2c/sony,imx296.yaml
F: drivers/media/i2c/imx296.c
SONY IMX319 SENSOR DRIVER
-M: Bingbu Cao <bingbu.cao@intel.com>
+M: Bingbu Cao <bingbu.cao@amd.com>
+M: Sakari Ailus <sakari.ailus@linux.intel.com>
L: linux-media@vger.kernel.org
S: Maintained
T: git git://linuxtv.org/media.git
@@ -24934,7 +24927,9 @@ F: Documentation/devicetree/bindings/media/i2c/sony,imx335.yaml
F: drivers/media/i2c/imx335.c
SONY IMX355 SENSOR DRIVER
-M: Tianshu Qiu <tian.shu.qiu@intel.com>
+M: Sakari Ailus <sakari.ailus@linux.intel.com>
+M: Dave Stevenson <dave.stevenson@raspberrypi.com>
+R: David Heidelberg <david@ixit.cz>
L: linux-media@vger.kernel.org
S: Maintained
T: git git://linuxtv.org/media.git
--
2.47.3
^ permalink raw reply related
* Re: [PATCH v8 0/2] staging: rtl8723bs: Fix error handling in _rtw_pktfile_read()
From: Andy Shevchenko @ 2026-06-05 5:53 UTC (permalink / raw)
To: Bitterblue Smith
Cc: Minu Jin, gregkh, dan.carpenter, abrahamadekunle50,
zxcv2569763104, milospuric856, karanja99erick, weibu,
linux-staging, linux-kernel
In-Reply-To: <853404cd-cd0d-4562-82bf-2272240df785@gmail.com>
On Fri, Jun 05, 2026 at 12:47:59AM +0300, Bitterblue Smith wrote:
> On 03/06/2026 17:06, Andy Shevchenko wrote:
> > On Wed, Jun 03, 2026 at 04:40:45PM +0300, Bitterblue Smith wrote:
> >> On 03/06/2026 03:48, Andy Shevchenko wrote:
> >>> On Sun, May 24, 2026 at 09:30:01PM +0300, Bitterblue Smith wrote:
> >>>> On 27/01/2026 17:38, Minu Jin wrote:
> >>>>> This series improves error handling in _rtw_pktfile_read() and cleans up
> >>>>> the code style to comply with kernel standards.
> >>>>>
> >>>>> 1. The first patch combines the logic change and caller updates.
> >>>>> The function change and the caller updates must be in the same
> >>>>> patch. If they are separated, the code will not work correctly
> >>>>> or will cause errors at that specific point in the history.
> >>>>>
> >>>>> 2. The second patch focuses purely on code style cleanup (changing uint
> >>>>> to unsigned int) as requested by Andy Shevchenko.
> >>>>>
> >>>>> Regarding the logic change in _rtw_pktfile_read():
> >>>>>
> >>>>> The original code used a ternary operator to read whatever data was
> >>>>> available, even if it was less than requested. This could lead to
> >>>>> callers processing incomplete data without knowing it.
> >>>>>
> >>>>> I have changed this to return -EINVAL when the remaining data is insufficient.
> >>>>> This is safer because most callers expect the exact amount of data and
> >>>>> should not proceed with a partial read.
> >>>>>
> >>>>> Testing and Verification:
> >>>>>
> >>>>> I do not have access to the physical RTL8723BS hardware. However, I have
> >>>>> performed a rigorous manual audit of the data path and verified the
> >>>>> changes using Smatch static analysis. The analysis confirmed that no
> >>>>> new warnings or logical regressions were introduced in the modified files.
> >>>>
> >>>> I have some bad news:
> >>>>
> >>>> https://bbs.archlinux.org/viewtopic.php?id=313401
> >>>
> >>> It's unclear that this patch made it happen. See below.
> >>
> >> It looks pretty clear to me, but okay. I forwarded your request for bisection.
> >
> > I don't know how it does. There is no information about from which kernel one
> > upgrades to which. I assumed someone bumps from v7.0 to v7.0.3, but there no
> > such patch. If we take v6.19..v7.0.3 range, there are dozens of patches.
> >
> > I.o.w. it is semi-poorly written bug report: some of useful information and
> > some crucial that is missing...
> >
>
> The new message "coalesce failed with error -22" is printed when
> rtw_xmitframe_coalesce() returns -EINVAL. This is the only recent patch
> which touched rtw_xmitframe_coalesce().
Fair enough.
The quick fix is to replace
return -EINVAL;
with
rlen = rtw_remainder_len(pfile);
in drivers/staging/rtl8723bs/os_dep/xmit_linux.c::_rtw_pktfile_read().
There might be a better fix to understand why the requested length in that case
is bigger than remainder in the device.
> >>>> [quote]
> >>>>
> >>>> I recently upgraded my system, and now I’m having a strange issue with my
> >>>> WiFi (Realtek RTL8723BS).
> >>>>
> >>>> now I can no longer connect to any WiFi networks as a client. however,
> >>>> creating an Access Point (AP) still works fine, but whenever I try to connect
> >>>> to an existing network, the connection fails.
> >>>>
> >>>> dmesg logs:
> >>>> Every time I attempt a connection, dmesg gets spammed with this error:
> >>>>
> >>>> [ 1083.925640] rtl8723bs mmc0:0001:1 wlan0: xmit_xmitframes: coalesce failed with error -22
> >>>> [ 1084.921217] rtl8723bs mmc0:0001:1 wlan0: xmit_xmitframes: coalesce failed with error -22
> >>>> [ 1085.921434] rtl8723bs mmc0:0001:1 wlan0: xmit_xmitframes: coalesce failed with error -22
> >>>> [ 1086.922320] rtl8723bs mmc0:0001:1 wlan0: xmit_xmitframes: coalesce failed with error -22
> >>>> ....
> >>>>
> >>>> Has anyone encountered this specific "coalesce" error with the rtl8723bs
> >>>> driver? I'm looking for advice on whether this is a known bug in recent
> >>>> kernels or if there's a specific module parameter I should try to bypass
> >>>> this.
> >>>>
> >>>> System Info:
> >>>>
> >>>> Chipset: RTL8723BS (SDIO)
> >>>>
> >>>> Kernel version: 7.0.3-zen1-2-zen
> >>>>
> >>>> update: I fixed the issue after kernel downgrade
> >>>>
> >>>> [/quote]
> >>>
> >>> That guy should bisect and find the culprit. It will help a lot to understand
> >>> what's going on.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* [PATCH RESEND] staging: rtl8723bs: rename CamelCase variable RxMgmtFrameSeqNum
From: Gianmaria Biselli @ 2026-06-05 5:01 UTC (permalink / raw)
To: gregkh; +Cc: linux-staging, linux-kernel
Rename RxMgmtFrameSeqNum to rx_mgmt_frame_seq_num to comply with
kernel coding style.
Found by checkpatch.pl
Signed-off-by: Gianmaria Biselli <gianmariabiselli@gmail.com>
---
drivers/staging/rtl8723bs/core/rtw_mlme_ext.c | 4 ++--
drivers/staging/rtl8723bs/core/rtw_sta_mgt.c | 2 +-
drivers/staging/rtl8723bs/include/sta_info.h | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c
index a86d6f97cf02..89f253cc1da6 100644
--- a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c
+++ b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c
@@ -454,12 +454,12 @@ void mgt_dispatcher(struct adapter *padapter, union recv_frame *precv_frame)
if (psta) {
if (GetRetry(pframe)) {
- if (precv_frame->u.hdr.attrib.seq_num == psta->RxMgmtFrameSeqNum) {
+ if (precv_frame->u.hdr.attrib.seq_num == psta->rx_mgmt_frame_seq_num) {
/* drop the duplicate management frame */
return;
}
}
- psta->RxMgmtFrameSeqNum = precv_frame->u.hdr.attrib.seq_num;
+ psta->rx_mgmt_frame_seq_num = precv_frame->u.hdr.attrib.seq_num;
}
switch (GetFrameSubType(pframe)) {
diff --git a/drivers/staging/rtl8723bs/core/rtw_sta_mgt.c b/drivers/staging/rtl8723bs/core/rtw_sta_mgt.c
index a1b7fe843979..ce878c49e2e1 100644
--- a/drivers/staging/rtl8723bs/core/rtw_sta_mgt.c
+++ b/drivers/staging/rtl8723bs/core/rtw_sta_mgt.c
@@ -256,7 +256,7 @@ struct sta_info *rtw_alloc_stainfo(struct sta_priv *pstapriv, u8 *hwaddr)
psta->rssi_stat.UndecoratedSmoothedCCK = (-1);
/* init for the sequence number of received management frame */
- psta->RxMgmtFrameSeqNum = 0xffff;
+ psta->rx_mgmt_frame_seq_num = 0xffff;
spin_unlock_bh(&pstapriv->sta_hash_lock);
/* alloc mac id for non-bc/mc station, */
rtw_alloc_macid(pstapriv->padapter, psta);
diff --git a/drivers/staging/rtl8723bs/include/sta_info.h b/drivers/staging/rtl8723bs/include/sta_info.h
index 63343998266a..0f53e77c110c 100644
--- a/drivers/staging/rtl8723bs/include/sta_info.h
+++ b/drivers/staging/rtl8723bs/include/sta_info.h
@@ -214,7 +214,7 @@ struct sta_info {
/* */
/* To store the sequence number of received management frame */
- u16 RxMgmtFrameSeqNum;
+ u16 rx_mgmt_frame_seq_num;
};
#define sta_rx_pkts(sta) \
--
2.51.0
^ permalink raw reply related
* Re: [PATCH v8 0/2] staging: rtl8723bs: Fix error handling in _rtw_pktfile_read()
From: Bitterblue Smith @ 2026-06-04 21:47 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Minu Jin, gregkh, dan.carpenter, abrahamadekunle50,
zxcv2569763104, milospuric856, karanja99erick, weibu,
linux-staging, linux-kernel
In-Reply-To: <aiA0zDHc494rnc55@ashevche-desk.local>
On 03/06/2026 17:06, Andy Shevchenko wrote:
> On Wed, Jun 03, 2026 at 04:40:45PM +0300, Bitterblue Smith wrote:
>> On 03/06/2026 03:48, Andy Shevchenko wrote:
>>> On Sun, May 24, 2026 at 09:30:01PM +0300, Bitterblue Smith wrote:
>>>> On 27/01/2026 17:38, Minu Jin wrote:
>>>>> This series improves error handling in _rtw_pktfile_read() and cleans up
>>>>> the code style to comply with kernel standards.
>>>>>
>>>>> 1. The first patch combines the logic change and caller updates.
>>>>> The function change and the caller updates must be in the same
>>>>> patch. If they are separated, the code will not work correctly
>>>>> or will cause errors at that specific point in the history.
>>>>>
>>>>> 2. The second patch focuses purely on code style cleanup (changing uint
>>>>> to unsigned int) as requested by Andy Shevchenko.
>>>>>
>>>>> Regarding the logic change in _rtw_pktfile_read():
>>>>>
>>>>> The original code used a ternary operator to read whatever data was
>>>>> available, even if it was less than requested. This could lead to
>>>>> callers processing incomplete data without knowing it.
>>>>>
>>>>> I have changed this to return -EINVAL when the remaining data is insufficient.
>>>>> This is safer because most callers expect the exact amount of data and
>>>>> should not proceed with a partial read.
>>>>>
>>>>> Testing and Verification:
>>>>>
>>>>> I do not have access to the physical RTL8723BS hardware. However, I have
>>>>> performed a rigorous manual audit of the data path and verified the
>>>>> changes using Smatch static analysis. The analysis confirmed that no
>>>>> new warnings or logical regressions were introduced in the modified files.
>>>>
>>>> I have some bad news:
>>>>
>>>> https://bbs.archlinux.org/viewtopic.php?id=313401
>>>
>>> It's unclear that this patch made it happen. See below.
>>
>> It looks pretty clear to me, but okay. I forwarded your request for bisection.
>
> I don't know how it does. There is no information about from which kernel one
> upgrades to which. I assumed someone bumps from v7.0 to v7.0.3, but there no
> such patch. If we take v6.19..v7.0.3 range, there are dozens of patches.
>
> I.o.w. it is semi-poorly written bug report: some of useful information and
> some crucial that is missing...
>
The new message "coalesce failed with error -22" is printed when
rtw_xmitframe_coalesce() returns -EINVAL. This is the only recent patch
which touched rtw_xmitframe_coalesce().
>>>> [quote]
>>>>
>>>> Hey everyone,
>>>>
>>>> I recently upgraded my system, and now I’m having a strange issue with my
>>>> WiFi (Realtek RTL8723BS).
>>>>
>>>> now I can no longer connect to any WiFi networks as a client. however,
>>>> creating an Access Point (AP) still works fine, but whenever I try to connect
>>>> to an existing network, the connection fails.
>>>>
>>>> dmesg logs:
>>>> Every time I attempt a connection, dmesg gets spammed with this error:
>>>>
>>>> [ 1083.925640] rtl8723bs mmc0:0001:1 wlan0: xmit_xmitframes: coalesce failed with error -22
>>>> [ 1084.921217] rtl8723bs mmc0:0001:1 wlan0: xmit_xmitframes: coalesce failed with error -22
>>>> [ 1085.921434] rtl8723bs mmc0:0001:1 wlan0: xmit_xmitframes: coalesce failed with error -22
>>>> [ 1086.922320] rtl8723bs mmc0:0001:1 wlan0: xmit_xmitframes: coalesce failed with error -22
>>>> ....
>>>>
>>>> Has anyone encountered this specific "coalesce" error with the rtl8723bs
>>>> driver? I'm looking for advice on whether this is a known bug in recent
>>>> kernels or if there's a specific module parameter I should try to bypass
>>>> this.
>>>>
>>>> System Info:
>>>>
>>>> Chipset: RTL8723BS (SDIO)
>>>>
>>>> Kernel version: 7.0.3-zen1-2-zen
>>>>
>>>> update: I fixed the issue after kernel downgrade
>>>>
>>>> [/quote]
>>>
>>> That guy should bisect and find the culprit. It will help a lot to understand
>>> what's going on.
>
^ permalink raw reply
* Re: [PATCH] staging: rtl8723bs: add braces to resolve checkpatch
From: Ethan Tidmore @ 2026-06-04 21:34 UTC (permalink / raw)
To: Dalvin-Ehinoma Noah Aiguobas, gregkh; +Cc: linux-staging, linux-kernel
In-Reply-To: <aiG4aDh2czwJ2-bf@koolguy>
On Thu Jun 4, 2026 at 12:39 PM CDT, Dalvin-Ehinoma Noah Aiguobas wrote:
> Resolved checkpatch finding by adding braces as shown in styleguide.
>
> Signed-off-by: Dalvin-Ehinoma Noah Aiguobas <fliegbert2@gmail.com>
> ---
The patch notes should describe what the patch does, not the tool used
to find the issue.
Thanks,
ET
^ permalink raw reply
* [PATCH v3] staging: rtl8723bs: remove comparisons to boolean literals
From: William Elder @ 2026-06-04 19:05 UTC (permalink / raw)
To: Greg Kroah-Hartman, open list:STAGING SUBSYSTEM, open list; +Cc: williamxelder
Remove comparisons to true and false as they are error prone.
Use the idiomatic C forms instead
v3: Fix inverted logic for ht_option checks
Signed-off-by: William Elder <williamxelder@gmail.com>
---
drivers/staging/rtl8723bs/core/rtw_wlan_util.c | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/drivers/staging/rtl8723bs/core/rtw_wlan_util.c b/drivers/staging/rtl8723bs/core/rtw_wlan_util.c
index 6a7c09db4cd9..1f7f24dc8f7c 100644
--- a/drivers/staging/rtl8723bs/core/rtw_wlan_util.c
+++ b/drivers/staging/rtl8723bs/core/rtw_wlan_util.c
@@ -236,7 +236,7 @@ void Restore_DM_Func_Flag(struct adapter *padapter)
void Switch_DM_Func(struct adapter *padapter, u32 mode, u8 enable)
{
- if (enable == true)
+ if (enable)
rtw_hal_set_hwreg(padapter, HW_VAR_DM_FUNC_SET, (u8 *)(&mode));
else
rtw_hal_set_hwreg(padapter, HW_VAR_DM_FUNC_CLR, (u8 *)(&mode));
@@ -340,7 +340,6 @@ void set_channel_bwmode(struct adapter *padapter, unsigned char channel, unsigne
center_ch = rtw_get_center_ch(channel, bwmode, channel_offset);
-
/* set Channel */
if (mutex_lock_interruptible(&(adapter_to_dvobj(padapter)->setch_mutex)))
return;
@@ -847,7 +846,7 @@ static void bwmode_update_check(struct adapter *padapter, struct ndis_80211_var_
if (!pIE)
return;
- if (phtpriv->ht_option == false)
+ if (!phtpriv->ht_option)
return;
if (pIE->length > sizeof(struct HT_info_element))
@@ -892,7 +891,7 @@ static void bwmode_update_check(struct adapter *padapter, struct ndis_80211_var_
pmlmeinfo->bwmode_updated = false;
}
- if (true == pmlmeinfo->bwmode_updated) {
+ if (pmlmeinfo->bwmode_updated) {
struct sta_info *psta;
struct wlan_bssid_ex *cur_network = &(pmlmeinfo->network);
struct sta_priv *pstapriv = &padapter->stapriv;
@@ -931,7 +930,7 @@ void HT_caps_handler(struct adapter *padapter, struct ndis_80211_var_ie *pIE)
if (!pIE)
return;
- if (phtpriv->ht_option == false)
+ if (!phtpriv->ht_option)
return;
pmlmeinfo->HT_caps_enable = 1;
@@ -994,7 +993,7 @@ void HT_info_handler(struct adapter *padapter, struct ndis_80211_var_ie *pIE)
if (!pIE)
return;
- if (phtpriv->ht_option == false)
+ if (!phtpriv->ht_option)
return;
if (pIE->length > sizeof(struct HT_info_element))
@@ -1689,7 +1688,7 @@ void adaptive_early_32k(struct mlme_ext_priv *pmlmeext, u8 *pframe, uint len)
pmlmeext->bcn_delay_cnt[delay_ms]++;
/* pmlmeext->bcn_delay_ratio[delay_ms] = (pmlmeext->bcn_delay_cnt[delay_ms] * 100) /pmlmeext->bcn_cnt; */
/* dump for adaptive_early_32k */
- if (pmlmeext->bcn_cnt > 100 && (pmlmeext->adaptive_tsf_done == true)) {
+ if (pmlmeext->bcn_cnt > 100 && (pmlmeext->adaptive_tsf_done)) {
u8 ratio_20_delay, ratio_80_delay;
u8 DrvBcnEarly, DrvBcnTimeOut;
@@ -1737,7 +1736,7 @@ void rtw_alloc_macid(struct adapter *padapter, struct sta_info *psta)
spin_lock_bh(&pdvobj->lock);
for (i = 0; i < NUM_STA; i++) {
- if (pdvobj->macid[i] == false) {
+ if (!pdvobj->macid[i]) {
pdvobj->macid[i] = true;
break;
}
@@ -1762,7 +1761,7 @@ void rtw_release_macid(struct adapter *padapter, struct sta_info *psta)
spin_lock_bh(&pdvobj->lock);
if (psta->mac_id < NUM_STA && psta->mac_id != 1) {
- if (pdvobj->macid[psta->mac_id] == true) {
+ if (pdvobj->macid[psta->mac_id]) {
pdvobj->macid[psta->mac_id] = false;
psta->mac_id = NUM_STA;
}
--
2.54.0
^ permalink raw reply related
* [PATCH] staging: rtl8723bs: add braces to resolve checkpatch
From: Dalvin-Ehinoma Noah Aiguobas @ 2026-06-04 17:39 UTC (permalink / raw)
To: gregkh; +Cc: linux-staging, linux-kernel
Resolved checkpatch finding by adding braces as shown in styleguide.
Signed-off-by: Dalvin-Ehinoma Noah Aiguobas <fliegbert2@gmail.com>
---
drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
index d0bbe1bb979c..8d4bcf7ebeaf 100644
--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
+++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
@@ -472,8 +472,9 @@ int rtw_parse_wpa_ie(u8 *wpa_ie, int wpa_ie_len, int *group_cipher, int *pairwis
pos += WPA_SELECTOR_LEN;
left -= WPA_SELECTOR_LEN;
- } else if (left > 0)
+ } else if (left > 0) {
return _FAIL;
+ }
/* pairwise_cipher */
if (left >= 2) {
@@ -492,8 +493,9 @@ int rtw_parse_wpa_ie(u8 *wpa_ie, int wpa_ie_len, int *group_cipher, int *pairwis
left -= WPA_SELECTOR_LEN;
}
- } else if (left == 1)
+ } else if (left == 1) {
return _FAIL;
+ }
if (is_8021x) {
if (left >= 6) {
@@ -532,8 +534,9 @@ int rtw_parse_wpa2_ie(u8 *rsn_ie, int rsn_ie_len, int *group_cipher, int *pairwi
pos += RSN_SELECTOR_LEN;
left -= RSN_SELECTOR_LEN;
- } else if (left > 0)
+ } else if (left > 0) {
return _FAIL;
+ }
/* pairwise_cipher */
if (left >= 2) {
@@ -552,8 +555,9 @@ int rtw_parse_wpa2_ie(u8 *rsn_ie, int rsn_ie_len, int *group_cipher, int *pairwi
left -= RSN_SELECTOR_LEN;
}
- } else if (left == 1)
+ } else if (left == 1) {
return _FAIL;
+ }
if (is_8021x) {
if (left >= 6) {
--
2.54.0
^ permalink raw reply related
* [PATCH] staging: rtl8723bs: replace beacon timing magic numbers with named constants
From: Jad Keskes @ 2026-06-04 15:18 UTC (permalink / raw)
To: linux-staging; +Cc: gregkh, linux-kernel, Jad Keskes
In-Reply-To: <aiGMXBNQ0TbIGbpP@stanley.mountain>
Decompose the beacon timing magic numbers in
rtl8723b_InitBeaconParameters() into named constants with bitfield
expressions to document the register layout.
REG_TBTT_PROHIBIT (0x0540): The value 0x6404 splits into hold time
(0x64) in the upper byte and prohibit setup time (0x04) in the lower
byte, in 32us units — matching the layout used in the rtw88 driver
(WLAN_TBTT_HOLD_TIME << 8 | WLAN_TBTT_PROHIBIT).
REG_BCNTCFG (0x0510): The value 0x660F follows the same bitfield
layout as the adjacent EDCA AC parameter registers:
AIFS = bits[7:0] = 0x0F (disables contention before beacon Tx);
CWmin = bits[11:8] = 0x06; CWmax = bits[15:12] = 0x06.
This layout is confirmed by rtl8192cu which writes 0x66FF (test chips)
and 0x660F (normal chips) — only AIFS varies, the CW byte stays 0x66.
Drop the misleading "ms" unit comment on REG_TBTT_PROHIBIT and the
outstanding TODO marker since this was the last item it referenced.
Signed-off-by: Jad Keskes <inasj268@gmail.com>
---
drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c | 5 ++---
drivers/staging/rtl8723bs/include/rtl8723b_hal.h | 13 +++++++++++++
2 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c b/drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c
index e794fe3ca..4afba1252 100644
--- a/drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c
+++ b/drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c
@@ -878,8 +878,7 @@ void rtl8723b_InitBeaconParameters(struct adapter *padapter)
rtw_write16(padapter, REG_BCN_CTRL, val16);
- /* TODO: Remove these magic number */
- rtw_write16(padapter, REG_TBTT_PROHIBIT, 0x6404);/* ms */
+ rtw_write16(padapter, REG_TBTT_PROHIBIT, TBTT_PROHIBIT_TIME_8723B);
/* Firmware will control REG_DRVERLYINT when power saving is enable, */
/* so don't set this register on STA mode. */
if (check_fwstate(&padapter->mlmepriv, WIFI_STATION_STATE) == false)
@@ -888,7 +887,7 @@ void rtl8723b_InitBeaconParameters(struct adapter *padapter)
/* Suggested by designer timchen. Change beacon AIFS to the largest number */
/* because test chip does not contension before sending beacon. by tynli. 2009.11.03 */
- rtw_write16(padapter, REG_BCNTCFG, 0x660F);
+ rtw_write16(padapter, REG_BCNTCFG, BCNTCFG_8723B);
pHalData->RegBcnCtrlVal = rtw_read8(padapter, REG_BCN_CTRL);
pHalData->RegTxPause = rtw_read8(padapter, REG_TXPAUSE);
diff --git a/drivers/staging/rtl8723bs/include/rtl8723b_hal.h b/drivers/staging/rtl8723bs/include/rtl8723b_hal.h
index ffd039278..81105c55a 100644
--- a/drivers/staging/rtl8723bs/include/rtl8723b_hal.h
+++ b/drivers/staging/rtl8723bs/include/rtl8723b_hal.h
@@ -72,6 +72,19 @@ struct rt_firmware_hdr {
#define DRIVER_EARLY_INT_TIME_8723B 0x05
#define BCN_DMA_ATIME_INT_TIME_8723B 0x02
+/* REG_TBTT_PROHIBIT (0x0540) - TBTT prohibit hold/setup in 32us units */
+#define TBTT_PROHIBIT_SETUP_8723B 0x04
+#define TBTT_PROHIBIT_HOLD_8723B 0x64
+#define TBTT_PROHIBIT_TIME_8723B \
+ ((TBTT_PROHIBIT_HOLD_8723B << 8) | TBTT_PROHIBIT_SETUP_8723B)
+
+/* REG_BCNTCFG (0x0510) - beacon AIFS, CWmin, CWmax (EDCA-like layout) */
+#define BCN_AIFS_8723B 0x0F
+#define BCN_CW_MIN_8723B 0x06
+#define BCN_CW_MAX_8723B 0x06
+#define BCNTCFG_8723B \
+ ((BCN_CW_MAX_8723B << 12) | (BCN_CW_MIN_8723B << 8) | BCN_AIFS_8723B)
+
/* for 8723B */
/* TX 32K, RX 16K, Page size 128B for TX, 8B for RX */
#define PAGE_SIZE_TX_8723B 128
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] staging: rtl8723bs: replace magic numbers with named constants
From: Dan Carpenter @ 2026-06-04 14:34 UTC (permalink / raw)
To: Jad Keskes; +Cc: linux-staging, Greg Kroah-Hartman, linux-kernel
In-Reply-To: <aiGMXBNQ0TbIGbpP@stanley.mountain>
On Thu, Jun 04, 2026 at 05:31:56PM +0300, Dan Carpenter wrote:
> On Wed, Jun 03, 2026 at 03:31:27PM +0100, Jad Keskes wrote:
> > Remove the magic numbers in rtl8723b_InitBeaconParameters() as requested
> > by the outstanding TODO comment. Replace 0x6404 and 0x660F with named
> > constants defined in rtl8723b_hal.h alongside the existing beacon timing
> > constants.
> >
> > Signed-off-by: Jad Keskes <inasj268@gmail.com>
>
> Just having the same define but as a goto doesn't add any information
> or value.
Ugh... Sorry, I meant to say "just having the same value but as a define
doesn't add any information."
regards,
dan carpenter
^ permalink raw reply
* Re: [PATCH] staging: rtl8723bs: replace magic numbers with named constants
From: Dan Carpenter @ 2026-06-04 14:31 UTC (permalink / raw)
To: Jad Keskes; +Cc: linux-staging, Greg Kroah-Hartman, linux-kernel
In-Reply-To: <20260603143127.343103-1-inasj268@gmail.com>
On Wed, Jun 03, 2026 at 03:31:27PM +0100, Jad Keskes wrote:
> Remove the magic numbers in rtl8723b_InitBeaconParameters() as requested
> by the outstanding TODO comment. Replace 0x6404 and 0x660F with named
> constants defined in rtl8723b_hal.h alongside the existing beacon timing
> constants.
>
> Signed-off-by: Jad Keskes <inasj268@gmail.com>
Just having the same define but as a goto doesn't add any information
or value.
This is actually the third attempt to do this. The first two were AI
patches. On the first time, I asked how the author came up with the
definition and the had the AI generate a halucinated fake spec. On the
second patch, the AI found the first fake spec and assumed it was
correct. :P
Your patch at least doesn't try to pass off any wrong information so
that's good.
I was able to find this explanation from an out of tree driver.
https://gitlab.elettra.eu/intel_socfpga/linux-socfpga/-/blob/socfpga-5.15.60-lts/drivers/net/wireless/realtek/rtw88/rtw8822b.c?ref_type=heads
#define WLAN_TBTT_PROHIBIT 0x04 /* unit : 32us */
#define WLAN_TBTT_HOLD_TIME 0x064 /* unit : 32us */
But it's weird that the comment here says ms. I don't know what
is correct. That driver doesn't have any explanation for the 0x660f
value.
regards,
dan carpenter
^ permalink raw reply
* [PATCH v13 14/22] media: i2c: add Maxim GMSL2/3 serializer framework
From: Dumitru Ceclan via B4 Relay @ 2026-06-04 14:14 UTC (permalink / raw)
To: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
Laurent Pinchart, Julien Massot, Rob Herring,
Niklas Söderlund, Greg Kroah-Hartman
Cc: mitrutzceclan, linux-media, linux-kernel, devicetree,
linux-staging, linux-gpio, Niklas Söderlund, Martin Hecht,
Cosmin Tanislav
In-Reply-To: <20260604-gmsl2-3_serdes-v13-0-9d8a4919983b@analog.com>
From: Cosmin Tanislav <demonsingur@gmail.com>
These drivers are meant to be used as a common framework for Maxim
GMSL2/3 serializers.
This framework enables support for the following new features across
all the chips:
* Full Streams API support
* .get_frame_desc()
* I2C ATR
* automatic GMSL link version negotiation
* automatic stream id selection
* automatic VC remapping
* automatic pixel mode / tunnel mode selection
* automatic double mode selection / data padding
* logging of internal state and chip status registers via .log_status()
* PHY modes
* serializer pinctrl
* TPG
Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
---
drivers/media/i2c/maxim-serdes/Makefile | 2 +-
drivers/media/i2c/maxim-serdes/max_ser.c | 2184 ++++++++++++++++++++++++++++++
drivers/media/i2c/maxim-serdes/max_ser.h | 147 ++
3 files changed, 2332 insertions(+), 1 deletion(-)
diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
index 630fbb486bab..17511cb03369 100644
--- a/drivers/media/i2c/maxim-serdes/Makefile
+++ b/drivers/media/i2c/maxim-serdes/Makefile
@@ -1,3 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
-max-serdes-objs := max_serdes.o
+max-serdes-objs := max_serdes.o max_ser.o
obj-$(CONFIG_VIDEO_MAXIM_SERDES) += max-serdes.o
diff --git a/drivers/media/i2c/maxim-serdes/max_ser.c b/drivers/media/i2c/maxim-serdes/max_ser.c
new file mode 100644
index 000000000000..a193381435e6
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max_ser.c
@@ -0,0 +1,2184 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Maxim GMSL2 Serializer Driver
+ *
+ * Copyright (C) 2025 Analog Devices Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c-atr.h>
+#include <linux/i2c-mux.h>
+#include <linux/module.h>
+
+#include <media/mipi-csi2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+#include "max_ser.h"
+#include "max_serdes.h"
+
+#define MAX_SER_NUM_LINKS 1
+#define MAX_SER_NUM_PHYS 1
+
+struct max_ser_priv {
+ struct max_ser *ser;
+ struct device *dev;
+ struct i2c_client *client;
+
+ struct i2c_atr *atr;
+ struct i2c_mux_core *mux;
+
+ struct media_pad *pads;
+ struct max_serdes_source *sources;
+ u64 *streams_masks;
+ u32 double_bpps;
+
+ struct v4l2_subdev sd;
+ struct v4l2_async_notifier nf;
+ struct v4l2_ctrl_handler ctrl_handler;
+};
+
+struct max_ser_route_hw {
+ struct max_serdes_source *source;
+ struct max_ser_pipe *pipe;
+ struct v4l2_mbus_frame_desc_entry entry;
+ bool is_tpg;
+};
+
+static inline struct max_ser_priv *sd_to_priv(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct max_ser_priv, sd);
+}
+
+static inline struct max_ser_priv *nf_to_priv(struct v4l2_async_notifier *nf)
+{
+ return container_of(nf, struct max_ser_priv, nf);
+}
+
+static inline struct max_ser_priv *ctrl_to_priv(struct v4l2_ctrl_handler *handler)
+{
+ return container_of(handler, struct max_ser_priv, ctrl_handler);
+}
+
+static inline bool max_ser_pad_is_sink(struct max_ser *ser, u32 pad)
+{
+ return pad < ser->ops->num_phys;
+}
+
+static inline bool max_ser_pad_is_source(struct max_ser *ser, u32 pad)
+{
+ return pad >= ser->ops->num_phys &&
+ pad < ser->ops->num_phys + MAX_SER_NUM_LINKS;
+}
+
+static inline u32 max_ser_source_pad(struct max_ser *ser)
+{
+ return ser->ops->num_phys;
+}
+
+static inline bool max_ser_pad_is_tpg(struct max_ser *ser, u32 pad)
+{
+ return pad >= ser->ops->num_phys + MAX_SER_NUM_LINKS;
+}
+
+static inline unsigned int max_ser_phy_to_pad(struct max_ser *ser,
+ struct max_ser_phy *phy)
+{
+ return phy->index;
+}
+
+static inline unsigned int max_ser_num_pads(struct max_ser *ser)
+{
+ return ser->ops->num_phys + MAX_SER_NUM_LINKS +
+ (ser->ops->set_tpg ? 1 : 0);
+}
+
+static struct max_ser_phy *max_ser_pad_to_phy(struct max_ser *ser, u32 pad)
+{
+ if (!max_ser_pad_is_sink(ser, pad))
+ return NULL;
+
+ return &ser->phys[pad];
+}
+
+static struct max_ser_pipe *
+max_ser_find_phy_pipe(struct max_ser *ser, struct max_ser_phy *phy)
+{
+ unsigned int i;
+
+ for (i = 0; i < ser->ops->num_pipes; i++) {
+ struct max_ser_pipe *pipe = &ser->pipes[i];
+
+ if (pipe->phy_id == phy->index)
+ return pipe;
+ }
+
+ return NULL;
+}
+
+static struct max_serdes_source *
+max_ser_get_phy_source(struct max_ser_priv *priv, struct max_ser_phy *phy)
+{
+ return &priv->sources[phy->index];
+}
+
+static const struct max_serdes_tpg_entry *
+max_ser_find_tpg_entry(struct max_ser *ser, u32 target_index,
+ u32 width, u32 height, u32 code,
+ u32 numerator, u32 denominator)
+{
+ const struct max_serdes_tpg_entry *entry;
+ unsigned int index = 0;
+ unsigned int i;
+
+ for (i = 0; i < ser->ops->tpg_entries.num_entries; i++) {
+ entry = &ser->ops->tpg_entries.entries[i];
+
+ if ((width != 0 && width != entry->width) ||
+ (height != 0 && height != entry->height) ||
+ (code != 0 && code != entry->code) ||
+ (numerator != 0 && numerator != entry->interval.numerator) ||
+ (denominator != 0 && denominator != entry->interval.denominator))
+ continue;
+
+ if (index == target_index)
+ break;
+
+ index++;
+ }
+
+ if (i == ser->ops->tpg_entries.num_entries)
+ return NULL;
+
+ return &ser->ops->tpg_entries.entries[i];
+}
+
+static const struct max_serdes_tpg_entry *
+max_ser_find_state_tpg_entry(struct max_ser *ser, struct v4l2_subdev_state *state,
+ unsigned int pad)
+{
+ struct v4l2_mbus_framefmt *fmt;
+ struct v4l2_fract *in;
+
+ fmt = v4l2_subdev_state_get_format(state, pad, MAX_SERDES_TPG_STREAM);
+ if (!fmt)
+ return NULL;
+
+ in = v4l2_subdev_state_get_interval(state, pad, MAX_SERDES_TPG_STREAM);
+ if (!in)
+ return NULL;
+
+ return max_ser_find_tpg_entry(ser, 0, fmt->width, fmt->height, fmt->code,
+ in->numerator, in->denominator);
+}
+
+static int max_ser_get_tpg_fd_entry_state(struct max_ser *ser,
+ struct v4l2_subdev_state *state,
+ struct v4l2_mbus_frame_desc_entry *fd_entry,
+ unsigned int pad)
+{
+ const struct max_serdes_tpg_entry *entry;
+
+ entry = max_ser_find_state_tpg_entry(ser, state, pad);
+ if (!entry)
+ return -EINVAL;
+
+ fd_entry->stream = MAX_SERDES_TPG_STREAM;
+ fd_entry->flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX;
+ fd_entry->length = entry->width * entry->height * entry->bpp / 8;
+ fd_entry->pixelcode = entry->code;
+ fd_entry->bus.csi2.vc = 0;
+ fd_entry->bus.csi2.dt = entry->dt;
+
+ return 0;
+}
+
+static int max_ser_tpg_route_to_hw(struct max_ser_priv *priv,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_route *route,
+ struct max_ser_route_hw *hw)
+{
+ struct max_ser *ser = priv->ser;
+
+ hw->pipe = &ser->pipes[0];
+
+ return max_ser_get_tpg_fd_entry_state(ser, state, &hw->entry,
+ route->sink_pad);
+}
+
+static int max_ser_route_to_hw(struct max_ser_priv *priv,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_route *route,
+ struct max_ser_route_hw *hw)
+{
+ struct max_ser *ser = priv->ser;
+ struct v4l2_mbus_frame_desc fd = {};
+ struct max_ser_phy *phy;
+ unsigned int i;
+ int ret;
+
+ memset(hw, 0, sizeof(*hw));
+
+ hw->is_tpg = max_ser_pad_is_tpg(ser, route->sink_pad);
+ if (hw->is_tpg)
+ return max_ser_tpg_route_to_hw(priv, state, route, hw);
+
+ phy = max_ser_pad_to_phy(ser, route->sink_pad);
+ if (!phy)
+ return -ENOENT;
+
+ hw->pipe = max_ser_find_phy_pipe(ser, phy);
+ if (!hw->pipe)
+ return -ENOENT;
+
+ hw->source = max_ser_get_phy_source(priv, phy);
+ if (!hw->source->sd)
+ return 0;
+
+ ret = v4l2_subdev_call(hw->source->sd, pad, get_frame_desc,
+ hw->source->pad, &fd);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < fd.num_entries; i++)
+ if (fd.entry[i].stream == route->sink_stream)
+ break;
+
+ if (i == fd.num_entries)
+ return -ENOENT;
+
+ hw->entry = fd.entry[i];
+
+ return 0;
+}
+
+static int max_ser_phy_set_active(struct max_ser *ser, struct max_ser_phy *phy,
+ bool active)
+{
+ int ret;
+
+ if (ser->ops->set_phy_active) {
+ ret = ser->ops->set_phy_active(ser, phy, active);
+ if (ret)
+ return ret;
+ }
+
+ phy->active = active;
+
+ return 0;
+}
+
+static int max_ser_set_pipe_dts(struct max_ser_priv *priv, struct max_ser_pipe *pipe,
+ unsigned int *dts, unsigned int num_dts)
+{
+ struct max_ser *ser = priv->ser;
+ unsigned int i;
+ int ret;
+
+ if (!ser->ops->set_pipe_dt || !ser->ops->set_pipe_dt_en)
+ return 0;
+
+ for (i = 0; i < num_dts; i++) {
+ ret = ser->ops->set_pipe_dt(ser, pipe, i, dts[i]);
+ if (ret)
+ return ret;
+
+ ret = ser->ops->set_pipe_dt_en(ser, pipe, i, true);
+ if (ret)
+ return ret;
+ }
+
+ if (num_dts == pipe->num_dts)
+ return 0;
+
+ for (i = num_dts; i < ser->ops->num_dts_per_pipe; i++) {
+ ret = ser->ops->set_pipe_dt_en(ser, pipe, i, false);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int max_ser_set_pipe_mode(struct max_ser_priv *priv, struct max_ser_pipe *pipe,
+ struct max_ser_pipe_mode *mode)
+{
+ struct max_ser *ser = priv->ser;
+
+ if (mode->bpp == pipe->mode.bpp &&
+ mode->soft_bpp == pipe->mode.soft_bpp &&
+ mode->dbl8 == pipe->mode.dbl8 &&
+ mode->dbl10 == pipe->mode.dbl10 &&
+ mode->dbl12 == pipe->mode.dbl12)
+ return 0;
+
+ if (!ser->ops->set_pipe_mode)
+ return 0;
+
+ return ser->ops->set_pipe_mode(ser, pipe, mode);
+}
+
+static int max_ser_i2c_atr_attach_addr(struct i2c_atr *atr, u32 chan_id,
+ u16 addr, u16 alias)
+{
+ struct max_serdes_i2c_xlate xlate = {
+ .src = alias,
+ .dst = addr,
+ .en = true,
+ };
+ struct max_ser_priv *priv = i2c_atr_get_driver_data(atr);
+ struct max_ser *ser = priv->ser;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ser->ops->num_i2c_xlates; i++)
+ if (!ser->i2c_xlates[i].en)
+ break;
+
+ if (i == ser->ops->num_i2c_xlates) {
+ dev_err(priv->dev,
+ "Reached maximum number of I2C translations\n");
+ return -EINVAL;
+ }
+
+ ret = ser->ops->set_i2c_xlate(ser, i, &xlate);
+ if (ret)
+ return ret;
+
+ ser->i2c_xlates[i] = xlate;
+
+ return 0;
+}
+
+static void max_ser_i2c_atr_detach_addr(struct i2c_atr *atr, u32 chan_id, u16 addr)
+{
+ struct max_ser_priv *priv = i2c_atr_get_driver_data(atr);
+ struct max_ser *ser = priv->ser;
+ struct max_serdes_i2c_xlate xlate = { 0 };
+ unsigned int i;
+
+ /* Find index of matching I2C translation. */
+ for (i = 0; i < ser->ops->num_i2c_xlates; i++)
+ if (ser->i2c_xlates[i].dst == addr)
+ break;
+
+ if (WARN_ON(i == ser->ops->num_i2c_xlates))
+ return;
+
+ ser->ops->set_i2c_xlate(ser, i, &xlate);
+ ser->i2c_xlates[i] = xlate;
+}
+
+static const struct i2c_atr_ops max_ser_i2c_atr_ops = {
+ .attach_addr = max_ser_i2c_atr_attach_addr,
+ .detach_addr = max_ser_i2c_atr_detach_addr,
+};
+
+static void max_ser_i2c_atr_deinit(struct max_ser_priv *priv)
+{
+ /* Deleting adapters that haven't been added does no harm. */
+ i2c_atr_del_adapter(priv->atr, 0);
+
+ i2c_atr_delete(priv->atr);
+}
+
+static int max_ser_i2c_atr_init(struct max_ser_priv *priv)
+{
+ struct i2c_atr_adap_desc desc = {
+ .chan_id = 0,
+ };
+ int ret;
+
+ if (!i2c_check_functionality(priv->client->adapter,
+ I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
+ return -ENODEV;
+
+ priv->atr = i2c_atr_new(priv->client->adapter, priv->dev,
+ &max_ser_i2c_atr_ops, 1, 0);
+ if (IS_ERR(priv->atr))
+ return PTR_ERR(priv->atr);
+
+ i2c_atr_set_driver_data(priv->atr, priv);
+
+ ret = i2c_atr_add_adapter(priv->atr, &desc);
+ if (ret) {
+ i2c_atr_delete(priv->atr);
+ priv->atr = NULL;
+ }
+
+ return ret;
+}
+
+static int max_ser_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
+{
+ return 0;
+}
+
+static void max_ser_i2c_mux_deinit(struct max_ser_priv *priv)
+{
+ i2c_mux_del_adapters(priv->mux);
+}
+
+static int max_ser_i2c_mux_init(struct max_ser_priv *priv)
+{
+ priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
+ 1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
+ max_ser_i2c_mux_select, NULL);
+ if (!priv->mux)
+ return -ENOMEM;
+
+ return i2c_mux_add_adapter(priv->mux, 0, 0);
+}
+
+static int max_ser_i2c_adapter_init(struct max_ser_priv *priv)
+{
+ struct fwnode_handle *fwnode;
+
+ fwnode = device_get_named_child_node(priv->dev, "i2c-gate");
+ if (fwnode) {
+ fwnode_handle_put(fwnode);
+ return max_ser_i2c_mux_init(priv);
+ }
+
+ return max_ser_i2c_atr_init(priv);
+}
+
+static void max_ser_i2c_adapter_deinit(struct max_ser_priv *priv)
+{
+ if (device_get_named_child_node(priv->dev, "i2c-gate"))
+ max_ser_i2c_mux_deinit(priv);
+ else
+ max_ser_i2c_atr_deinit(priv);
+}
+
+static int max_ser_set_tpg_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *format)
+{
+ struct v4l2_mbus_framefmt *fmt = &format->format;
+ struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
+ struct max_ser *ser = priv->ser;
+ const struct max_serdes_tpg_entry *entry;
+ struct v4l2_fract *in;
+
+ if (format->stream != MAX_SERDES_TPG_STREAM)
+ return -EINVAL;
+
+ entry = max_ser_find_tpg_entry(ser, 0, fmt->width, fmt->height,
+ fmt->code, 0, 0);
+ if (!entry)
+ return -EINVAL;
+
+ in = v4l2_subdev_state_get_interval(state, format->pad, format->stream);
+ if (!in)
+ return -EINVAL;
+
+ in->numerator = entry->interval.numerator;
+ in->denominator = entry->interval.denominator;
+
+ return 0;
+}
+
+static int max_ser_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *format)
+{
+ struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
+ struct max_ser *ser = priv->ser;
+ struct v4l2_mbus_framefmt *fmt;
+ int ret;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && ser->active)
+ return -EBUSY;
+
+ /* No transcoding, source and sink formats must match. */
+ if (max_ser_pad_is_source(ser, format->pad))
+ return v4l2_subdev_get_fmt(sd, state, format);
+
+ if (max_ser_pad_is_tpg(ser, format->pad)) {
+ ret = max_ser_set_tpg_fmt(sd, state, format);
+ if (ret)
+ return ret;
+ }
+
+ if (format->format.code == ~0U)
+ format->format.code = MEDIA_BUS_FMT_UYVY8_1X16;
+
+ fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
+ if (!fmt)
+ return -EINVAL;
+
+ *fmt = format->format;
+
+ fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
+ format->stream);
+ if (!fmt)
+ return -EINVAL;
+
+ *fmt = format->format;
+
+ return 0;
+}
+
+static int max_ser_log_status(struct v4l2_subdev *sd)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct max_ser *ser = priv->ser;
+ struct v4l2_subdev_state *state;
+ unsigned int i, j;
+ int ret;
+
+ state = v4l2_subdev_lock_and_get_active_state(&priv->sd);
+
+ v4l2_info(sd, "mode: %s\n", max_serdes_gmsl_mode_str(ser->mode));
+ if (ser->ops->set_tpg) {
+ const struct max_serdes_tpg_entry *entry = ser->tpg_entry;
+
+ if (entry) {
+ v4l2_info(sd, "tpg: %ux%u@%u/%u, code: %u, dt: %u, bpp: %u\n",
+ entry->width, entry->height,
+ entry->interval.numerator,
+ entry->interval.denominator,
+ entry->code, entry->dt, entry->bpp);
+ } else {
+ v4l2_info(sd, "tpg: disabled\n");
+ }
+ }
+ if (ser->ops->log_status) {
+ ret = ser->ops->log_status(ser);
+ if (ret)
+ return ret;
+ }
+ v4l2_info(sd, "i2c_xlates:\n");
+ for (i = 0; i < ser->ops->num_i2c_xlates; i++) {
+ v4l2_info(sd, "\ten: %u, src: 0x%02x dst: 0x%02x\n",
+ ser->i2c_xlates[i].en, ser->i2c_xlates[i].src,
+ ser->i2c_xlates[i].dst);
+ if (!ser->i2c_xlates[i].en)
+ break;
+ }
+ v4l2_info(sd, "\n");
+ if (ser->ops->set_vc_remap) {
+ v4l2_info(sd, "vc_remaps: %u\n", ser->num_vc_remaps);
+ for (j = 0; j < ser->num_vc_remaps; j++) {
+ v4l2_info(sd, "\tvc_remap: src: %u, dst: %u\n",
+ ser->vc_remaps[j].src, ser->vc_remaps[j].dst);
+ }
+ }
+ v4l2_info(sd, "\n");
+
+ for (i = 0; i < ser->ops->num_pipes; i++) {
+ struct max_ser_pipe *pipe = &ser->pipes[i];
+
+ v4l2_info(sd, "pipe: %u\n", pipe->index);
+ v4l2_info(sd, "\tenabled: %u\n", pipe->enabled);
+
+ if (!pipe->enabled) {
+ v4l2_info(sd, "\n");
+ continue;
+ }
+
+ v4l2_info(sd, "\tphy_id: %u\n", pipe->phy_id);
+ v4l2_info(sd, "\tstream_id: %u\n", pipe->stream_id);
+ if (ser->ops->set_pipe_phy)
+ v4l2_info(sd, "\tphy_id: %u\n", pipe->phy_id);
+ if (ser->ops->set_pipe_dt) {
+ v4l2_info(sd, "\tdts: %u\n", pipe->num_dts);
+ for (j = 0; j < pipe->num_dts; j++)
+ v4l2_info(sd, "\t\tdt: 0x%02x\n", pipe->dts[j]);
+ }
+ if (ser->ops->set_pipe_vcs)
+ v4l2_info(sd, "\tvcs: 0x%08x\n", pipe->vcs);
+ if (ser->ops->set_pipe_mode) {
+ v4l2_info(sd, "\tdbl8: %u\n", pipe->mode.dbl8);
+ v4l2_info(sd, "\tdbl10: %u\n", pipe->mode.dbl10);
+ v4l2_info(sd, "\tdbl12: %u\n", pipe->mode.dbl12);
+ v4l2_info(sd, "\tsoft_bpp: %u\n", pipe->mode.soft_bpp);
+ v4l2_info(sd, "\tbpp: %u\n", pipe->mode.bpp);
+ }
+ if (ser->ops->log_pipe_status) {
+ ret = ser->ops->log_pipe_status(ser, pipe);
+ if (ret)
+ goto out_unlock;
+ }
+ v4l2_info(sd, "\n");
+ }
+
+ for (i = 0; i < ser->ops->num_phys; i++) {
+ struct max_ser_phy *phy = &ser->phys[i];
+
+ v4l2_info(sd, "phy: %u\n", phy->index);
+ v4l2_info(sd, "\tenabled: %u\n", phy->enabled);
+
+ if (!phy->enabled) {
+ v4l2_info(sd, "\n");
+ continue;
+ }
+
+ v4l2_info(sd, "\tactive: %u\n", phy->active);
+ v4l2_info(sd, "\tnum_data_lanes: %u\n", phy->mipi.num_data_lanes);
+ v4l2_info(sd, "\tclock_lane: %u\n", phy->mipi.clock_lane);
+ v4l2_info(sd, "\tnoncontinuous_clock: %u\n",
+ !!(phy->mipi.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK));
+ if (ser->ops->log_phy_status) {
+ ret = ser->ops->log_phy_status(ser, phy);
+ if (ret)
+ goto out_unlock;
+ }
+ v4l2_info(sd, "\n");
+ }
+
+ ret = 0;
+
+out_unlock:
+ v4l2_subdev_unlock_state(state);
+
+ return ret;
+}
+
+static int max_ser_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct max_ser_priv *priv = ctrl_to_priv(ctrl->handler);
+ struct max_ser *ser = priv->ser;
+
+ switch (ctrl->id) {
+ case V4L2_CID_TEST_PATTERN:
+ ser->tpg_pattern = ctrl->val;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int max_ser_enum_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_frame_interval_enum *fie)
+{
+ struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
+ struct max_ser *ser = priv->ser;
+ const struct max_serdes_tpg_entry *entry;
+
+ if (!max_ser_pad_is_tpg(ser, fie->pad) ||
+ fie->stream != MAX_SERDES_TPG_STREAM)
+ return -ENOTTY;
+
+ entry = max_ser_find_tpg_entry(ser, fie->index, fie->width, fie->height,
+ fie->code, fie->interval.denominator,
+ fie->interval.numerator);
+ if (!entry)
+ return -EINVAL;
+
+ fie->interval.numerator = entry->interval.numerator;
+ fie->interval.denominator = entry->interval.denominator;
+
+ return 0;
+}
+
+static int max_ser_set_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_frame_interval *fi)
+{
+ struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
+ struct max_ser *ser = priv->ser;
+ const struct max_serdes_tpg_entry *entry;
+ struct v4l2_mbus_framefmt *fmt;
+ struct v4l2_fract *in;
+
+ if (!max_ser_pad_is_tpg(ser, fi->pad) ||
+ fi->stream != MAX_SERDES_TPG_STREAM)
+ return -ENOTTY;
+
+ if (fi->which == V4L2_SUBDEV_FORMAT_ACTIVE && ser->active)
+ return -EBUSY;
+
+ fmt = v4l2_subdev_state_get_format(state, fi->pad, fi->stream);
+ if (!fmt)
+ return -EINVAL;
+
+ entry = max_ser_find_tpg_entry(ser, 0, fmt->width, fmt->height,
+ fmt->code, fi->interval.denominator,
+ fi->interval.numerator);
+ if (!entry)
+ return -EINVAL;
+
+ in = v4l2_subdev_state_get_interval(state, fi->pad, fi->stream);
+ if (!in)
+ return -EINVAL;
+
+ in->numerator = fi->interval.numerator;
+ in->denominator = fi->interval.denominator;
+
+ return 0;
+}
+
+static int max_ser_get_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_frame_interval *fi)
+{
+ struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
+ struct max_ser *ser = priv->ser;
+
+ if (!max_ser_pad_is_tpg(ser, fi->pad) ||
+ fi->stream != MAX_SERDES_TPG_STREAM)
+ return -ENOTTY;
+
+ return v4l2_subdev_get_frame_interval(sd, state, fi);
+}
+
+static int max_ser_get_frame_desc_state(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_mbus_frame_desc *fd,
+ unsigned int pad)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct max_ser *ser = priv->ser;
+ struct v4l2_subdev_route *route;
+ int ret;
+
+ if (!max_ser_pad_is_source(ser, pad))
+ return -ENOENT;
+
+ fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
+
+ for_each_active_route(&state->routing, route) {
+ struct max_ser_route_hw hw;
+
+ if (pad != route->source_pad)
+ continue;
+
+ ret = max_ser_route_to_hw(priv, state, route, &hw);
+ if (ret)
+ return ret;
+
+ hw.entry.stream = route->source_stream;
+
+ fd->entry[fd->num_entries++] = hw.entry;
+ }
+
+ return 0;
+}
+
+static int max_ser_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
+ struct v4l2_mbus_frame_desc *fd)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct v4l2_subdev_state *state;
+ int ret;
+
+ state = v4l2_subdev_lock_and_get_active_state(&priv->sd);
+
+ ret = max_ser_get_frame_desc_state(sd, state, fd, pad);
+
+ v4l2_subdev_unlock_state(state);
+
+ return ret;
+}
+
+static int max_ser_set_tpg_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_krouting *routing)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct max_ser *ser = priv->ser;
+ const struct max_serdes_tpg_entry *entry;
+ struct v4l2_mbus_framefmt fmt = { 0 };
+ int ret;
+
+ ret = max_serdes_validate_tpg_routing(routing);
+ if (ret)
+ return ret;
+
+ entry = &ser->ops->tpg_entries.entries[0];
+
+ fmt.width = entry->width;
+ fmt.height = entry->height;
+ fmt.code = entry->code;
+
+ return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &fmt);
+}
+
+static int __max_ser_set_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_krouting *routing)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct max_ser *ser = priv->ser;
+ struct v4l2_subdev_route *route;
+ bool is_tpg = false;
+ int ret;
+
+ ret = v4l2_subdev_routing_validate(sd, routing,
+ V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 |
+ V4L2_SUBDEV_ROUTING_NO_SINK_STREAM_MIX);
+ if (ret)
+ return ret;
+
+ for_each_active_route(routing, route) {
+ if (max_ser_pad_is_tpg(ser, route->sink_pad)) {
+ is_tpg = true;
+ break;
+ }
+ }
+
+ if (is_tpg)
+ return max_ser_set_tpg_routing(sd, state, routing);
+
+ static const struct v4l2_mbus_framefmt format = {
+ .code = MEDIA_BUS_FMT_Y8_1X8,
+ .field = V4L2_FIELD_NONE,
+ .width = 640,
+ .height = 480,
+ };
+
+ return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
+}
+
+static int max_ser_set_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ enum v4l2_subdev_format_whence which,
+ struct v4l2_subdev_krouting *routing)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct max_ser *ser = priv->ser;
+
+ if (which == V4L2_SUBDEV_FORMAT_ACTIVE && ser->active)
+ return -EBUSY;
+
+ return __max_ser_set_routing(sd, state, routing);
+}
+
+static int max_ser_get_pipe_vcs_dts(struct max_ser_priv *priv,
+ struct v4l2_subdev_state *state,
+ struct max_ser_pipe *pipe,
+ unsigned int *vcs,
+ unsigned int *dts, unsigned int *num_dts,
+ u64 *streams_masks)
+{
+ struct v4l2_subdev_route *route;
+ struct max_ser *ser = priv->ser;
+ unsigned int i;
+ int ret;
+
+ *vcs = 0;
+ *num_dts = 0;
+
+ if (ser->mode != MAX_SERDES_GMSL_PIXEL_MODE)
+ return 0;
+
+ for_each_active_route(&state->routing, route) {
+ struct max_ser_route_hw hw;
+ unsigned int vc, dt;
+
+ if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
+ continue;
+
+ ret = max_ser_route_to_hw(priv, state, route, &hw);
+ if (ret)
+ return ret;
+
+ if (hw.pipe != pipe)
+ continue;
+
+ vc = hw.entry.bus.csi2.vc;
+ dt = hw.entry.bus.csi2.dt;
+
+ if (vc >= MAX_SERDES_VC_ID_NUM)
+ return -E2BIG;
+
+ *vcs |= BIT(vc);
+
+ /* Skip already added DT. */
+ for (i = 0; i < *num_dts; i++)
+ if (dts[i] == dt)
+ break;
+
+ if (i < *num_dts)
+ continue;
+
+ if (*num_dts >= ser->ops->num_dts_per_pipe)
+ return -EINVAL;
+
+ dts[*num_dts] = dt;
+ (*num_dts)++;
+ }
+
+ /*
+ * Hardware cannot distinguish between different pairs of VC and DT,
+ * issue a warning.
+ */
+ for_each_active_route(&state->routing, route) {
+ struct max_ser_route_hw hw;
+ unsigned int vc, dt;
+
+ /*
+ * Skip enabled streams, we only want to check for leaks
+ * among the disabled streams.
+ */
+ if ((BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
+ continue;
+
+ ret = max_ser_route_to_hw(priv, state, route, &hw);
+ if (ret)
+ return ret;
+
+ if (hw.pipe != pipe)
+ continue;
+
+ vc = hw.entry.bus.csi2.vc;
+ dt = hw.entry.bus.csi2.dt;
+
+ if (vc >= MAX_SERDES_VC_ID_NUM)
+ return -E2BIG;
+
+ if (!(*vcs & BIT(vc)))
+ continue;
+
+ for (i = 0; i < *num_dts; i++)
+ if (dts[i] == dt)
+ break;
+
+ if (i == *num_dts)
+ continue;
+
+ dev_warn(priv->dev, "Leaked disabled stream %u:%u with VC: %u, DT: %u",
+ route->source_pad, route->source_stream, vc, dt);
+ }
+
+ return 0;
+}
+
+static int max_ser_get_pipe_mode(struct max_ser_priv *priv,
+ struct v4l2_subdev_state *state,
+ struct max_ser_pipe *pipe,
+ struct max_ser_pipe_mode *mode)
+{
+ struct v4l2_subdev_route *route;
+ struct max_ser *ser = priv->ser;
+ bool force_set_bpp = false;
+ unsigned int doubled_bpp = 0;
+ unsigned int min_bpp;
+ unsigned int max_bpp;
+ u32 bpps = 0;
+ int ret;
+
+ if (ser->mode != MAX_SERDES_GMSL_PIXEL_MODE)
+ return 0;
+
+ for_each_active_route(&state->routing, route) {
+ struct max_ser_route_hw hw;
+ unsigned int bpp;
+
+ ret = max_ser_route_to_hw(priv, state, route, &hw);
+ if (ret)
+ return ret;
+
+ if (hw.pipe != pipe)
+ continue;
+
+ if (hw.is_tpg)
+ force_set_bpp = true;
+
+ ret = max_serdes_get_fd_bpp(&hw.entry, &bpp);
+ if (ret)
+ return ret;
+
+ bpps |= BIT(bpp);
+ }
+
+ ret = max_serdes_process_bpps(priv->dev, bpps, priv->double_bpps, &doubled_bpp);
+ if (ret)
+ return ret;
+
+ if (doubled_bpp == 8)
+ mode->dbl8 = true;
+ else if (doubled_bpp == 10)
+ mode->dbl10 = true;
+ else if (doubled_bpp == 12)
+ mode->dbl12 = true;
+
+ if (doubled_bpp) {
+ bpps &= ~BIT(doubled_bpp);
+ bpps |= BIT(doubled_bpp * 2);
+ }
+
+ if (!bpps)
+ return 0;
+
+ min_bpp = __ffs(bpps);
+ max_bpp = __fls(bpps);
+
+ if (doubled_bpp)
+ mode->soft_bpp = min_bpp;
+
+ if (min_bpp != max_bpp || force_set_bpp)
+ mode->bpp = max_bpp;
+
+ return 0;
+}
+
+static int max_ser_update_pipe_enable(struct max_ser_priv *priv,
+ struct max_ser_pipe *pipe,
+ struct v4l2_subdev_state *state,
+ u64 *streams_masks)
+{
+ struct max_ser *ser = priv->ser;
+ struct v4l2_subdev_route *route;
+ bool enable = false;
+ int ret;
+
+ for_each_active_route(&state->routing, route) {
+ struct max_ser_route_hw hw;
+
+ if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
+ continue;
+
+ ret = max_ser_route_to_hw(priv, state, route, &hw);
+ if (ret)
+ return ret;
+
+ if (hw.pipe != pipe)
+ continue;
+
+ enable = true;
+ break;
+ }
+
+ if (enable == pipe->enabled)
+ return 0;
+
+ ret = ser->ops->set_pipe_enable(ser, pipe, enable);
+ if (ret)
+ return ret;
+
+ pipe->enabled = enable;
+
+ return 0;
+}
+
+static int max_ser_update_pipe(struct max_ser_priv *priv,
+ struct max_ser_pipe *pipe,
+ struct v4l2_subdev_state *state,
+ u64 *streams_masks)
+{
+ struct max_ser *ser = priv->ser;
+ struct max_ser_pipe_mode mode = { 0 };
+ unsigned int num_dts;
+ unsigned int *dts;
+ unsigned int vcs;
+ int ret;
+
+ if (!ser->ops->num_dts_per_pipe)
+ return 0;
+
+ dts = devm_kcalloc(priv->dev, ser->ops->num_dts_per_pipe, sizeof(*dts),
+ GFP_KERNEL);
+ if (!dts)
+ return -ENOMEM;
+
+ ret = max_ser_get_pipe_vcs_dts(priv, state, pipe, &vcs, dts, &num_dts,
+ streams_masks);
+ if (ret)
+ goto err_free_dts;
+
+ ret = max_ser_get_pipe_mode(priv, state, pipe, &mode);
+ if (ret)
+ goto err_free_dts;
+
+ if (ser->ops->set_pipe_vcs) {
+ ret = ser->ops->set_pipe_vcs(ser, pipe, vcs);
+ if (ret)
+ goto err_free_dts;
+ }
+
+ ret = max_ser_set_pipe_mode(priv, pipe, &mode);
+ if (ret)
+ goto err_revert_vcs;
+
+ ret = max_ser_set_pipe_dts(priv, pipe, dts, num_dts);
+ if (ret)
+ goto err_revert_mode;
+
+ pipe->vcs = vcs;
+ pipe->mode = mode;
+
+ if (pipe->dts)
+ devm_kfree(priv->dev, pipe->dts);
+
+ pipe->dts = dts;
+ pipe->num_dts = num_dts;
+
+ return 0;
+
+err_revert_mode:
+ max_ser_set_pipe_mode(priv, pipe, &pipe->mode);
+
+err_revert_vcs:
+ if (ser->ops->set_pipe_vcs)
+ ser->ops->set_pipe_vcs(ser, pipe, pipe->vcs);
+
+err_free_dts:
+ devm_kfree(priv->dev, dts);
+
+ return ret;
+}
+
+static int max_ser_update_phy(struct max_ser_priv *priv,
+ struct v4l2_subdev_state *state,
+ struct max_ser_phy *phy, u64 *streams_masks)
+{
+ struct max_ser *ser = priv->ser;
+ u32 pad = max_ser_phy_to_pad(ser, phy);
+ bool enable_changed = !streams_masks[pad] != !priv->streams_masks[pad];
+ bool enable = !!streams_masks[pad];
+ struct max_ser_pipe *pipe;
+ int ret;
+
+ pipe = max_ser_find_phy_pipe(ser, phy);
+ if (!pipe)
+ return -ENOENT;
+
+ if (!enable && enable_changed) {
+ ret = max_ser_phy_set_active(ser, phy, enable);
+ if (ret)
+ return ret;
+ }
+
+ ret = max_ser_update_pipe(priv, pipe, state, streams_masks);
+ if (ret)
+ goto err_revert_phy_disable;
+
+ ret = max_ser_update_pipe_enable(priv, pipe, state, streams_masks);
+ if (ret)
+ goto err_revert_pipe_update;
+
+ if (enable && enable_changed) {
+ ret = max_ser_phy_set_active(ser, phy, enable);
+ if (ret)
+ goto err_revert_update_pipe_enable;
+ }
+
+ return 0;
+
+err_revert_update_pipe_enable:
+ max_ser_update_pipe_enable(priv, pipe, state, priv->streams_masks);
+
+err_revert_pipe_update:
+ max_ser_update_pipe(priv, pipe, state, priv->streams_masks);
+
+err_revert_phy_disable:
+ if (!enable && enable_changed)
+ max_ser_phy_set_active(ser, phy, !enable);
+
+ return ret;
+}
+
+static int max_ser_update_phys(struct max_ser_priv *priv,
+ struct v4l2_subdev_state *state,
+ u64 *streams_masks)
+{
+ struct max_ser *ser = priv->ser;
+ unsigned int failed_update_phy_id = ser->ops->num_phys;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ser->ops->num_phys; i++) {
+ struct max_ser_phy *phy = &ser->phys[i];
+
+ ret = max_ser_update_phy(priv, state, phy, streams_masks);
+ if (ret) {
+ failed_update_phy_id = i;
+ goto err;
+ }
+ }
+
+ return 0;
+
+err:
+ for (i = 0; i < failed_update_phy_id; i++) {
+ struct max_ser_phy *phy = &ser->phys[i];
+
+ max_ser_update_phy(priv, state, phy, priv->streams_masks);
+ }
+
+ return ret;
+}
+
+static int max_ser_enable_disable_streams(struct max_ser_priv *priv,
+ struct v4l2_subdev_state *state,
+ u32 pad, u64 updated_streams_mask,
+ bool enable)
+{
+ struct max_ser *ser = priv->ser;
+
+ return max_serdes_xlate_enable_disable_streams(priv->sources, 0, state,
+ pad, updated_streams_mask, 0,
+ ser->ops->num_phys, enable);
+}
+
+static bool max_ser_is_tpg_routed(struct max_ser_priv *priv,
+ struct v4l2_subdev_state *state)
+{
+ struct v4l2_subdev_route *route;
+ int ret;
+
+ for_each_active_route(&state->routing, route) {
+ struct max_ser_route_hw hw;
+
+ ret = max_ser_route_to_hw(priv, state, route, &hw);
+ if (ret)
+ return false;
+
+ if (hw.is_tpg)
+ return true;
+ }
+
+ return false;
+}
+
+static int max_ser_update_tpg(struct max_ser_priv *priv,
+ struct v4l2_subdev_state *state,
+ u64 *streams_masks)
+{
+ const struct max_serdes_tpg_entry *entry = NULL;
+ struct max_ser *ser = priv->ser;
+ struct v4l2_subdev_route *route;
+ int ret;
+
+ for_each_active_route(&state->routing, route) {
+ struct max_ser_route_hw hw;
+
+ if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
+ continue;
+
+ ret = max_ser_route_to_hw(priv, state, route, &hw);
+ if (ret)
+ return ret;
+
+ if (!hw.is_tpg)
+ continue;
+
+ entry = max_ser_find_state_tpg_entry(ser, state, route->sink_pad);
+ break;
+ }
+
+ if (entry == ser->tpg_entry)
+ return 0;
+
+ ret = ser->ops->set_tpg(ser, entry);
+ if (ret)
+ return ret;
+
+ ser->tpg_entry = entry;
+
+ return 0;
+}
+
+static int max_ser_update_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ u32 pad, u64 updated_streams_mask, bool enable)
+{
+ struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
+ struct max_ser *ser = priv->ser;
+ unsigned int num_pads = max_ser_num_pads(ser);
+ u64 *streams_masks;
+ int ret;
+
+ ret = max_serdes_get_streams_masks(priv->dev, state, pad, updated_streams_mask,
+ num_pads, priv->streams_masks, &streams_masks,
+ enable);
+ if (ret)
+ return ret;
+
+ if (!enable) {
+ ret = max_ser_enable_disable_streams(priv, state, pad,
+ updated_streams_mask, enable);
+ if (ret)
+ goto err_free_streams_masks;
+ }
+
+ ret = max_ser_update_tpg(priv, state, streams_masks);
+ if (ret)
+ goto err_revert_streams_disable;
+
+ ret = max_ser_update_phys(priv, state, streams_masks);
+ if (ret)
+ goto err_revert_update_tpg;
+
+ if (enable) {
+ ret = max_ser_enable_disable_streams(priv, state, pad,
+ updated_streams_mask, enable);
+ if (ret)
+ goto err_revert_phys_update;
+ }
+
+ devm_kfree(priv->dev, priv->streams_masks);
+ priv->streams_masks = streams_masks;
+ ser->active = !!streams_masks[pad];
+
+ return 0;
+
+err_revert_phys_update:
+ max_ser_update_phys(priv, state, priv->streams_masks);
+
+err_revert_update_tpg:
+ max_ser_update_tpg(priv, state, priv->streams_masks);
+
+err_revert_streams_disable:
+ if (!enable)
+ max_ser_enable_disable_streams(priv, state, pad,
+ updated_streams_mask, !enable);
+
+err_free_streams_masks:
+ devm_kfree(priv->dev, streams_masks);
+
+ return ret;
+}
+
+static int max_ser_enable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ u32 pad, u64 streams_mask)
+{
+ return max_ser_update_streams(sd, state, pad, streams_mask, true);
+}
+
+static int max_ser_disable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ u32 pad, u64 streams_mask)
+{
+ return max_ser_update_streams(sd, state, pad, streams_mask, false);
+}
+
+static int max_ser_init_state(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
+{
+ struct v4l2_subdev_route routes[MAX_SER_NUM_PHYS] = { 0 };
+ struct v4l2_subdev_krouting routing = {
+ .routes = routes,
+ };
+ struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
+ struct max_ser *ser = priv->ser;
+ unsigned int stream = 0;
+ unsigned int i;
+
+ for (i = 0; i < ser->ops->num_phys; i++) {
+ struct max_ser_phy *phy = &ser->phys[i];
+
+ if (!phy->enabled)
+ continue;
+
+ routing.routes[routing.num_routes++] = (struct v4l2_subdev_route) {
+ .sink_pad = max_ser_phy_to_pad(ser, phy),
+ .sink_stream = 0,
+ .source_pad = max_ser_source_pad(ser),
+ .source_stream = stream,
+ .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+ };
+ stream++;
+
+ /*
+ * The Streams API is an experimental feature.
+ * If multiple routes are provided here, userspace will not be
+ * able to configure them unless the Streams API is enabled.
+ * Provide a single route until it is enabled.
+ */
+ break;
+ }
+
+ return __max_ser_set_routing(sd, state, &routing);
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int max_ser_g_register(struct v4l2_subdev *sd,
+ struct v4l2_dbg_register *reg)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct max_ser *ser = priv->ser;
+ unsigned int val;
+ int ret;
+
+ ret = ser->ops->reg_read(ser, reg->reg, &val);
+ if (ret)
+ return ret;
+
+ reg->val = val;
+ reg->size = 1;
+
+ return 0;
+}
+
+static int max_ser_s_register(struct v4l2_subdev *sd,
+ const struct v4l2_dbg_register *reg)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct max_ser *ser = priv->ser;
+
+ return ser->ops->reg_write(ser, reg->reg, reg->val);
+}
+#endif
+
+static const struct v4l2_subdev_core_ops max_ser_core_ops = {
+ .log_status = max_ser_log_status,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .g_register = max_ser_g_register,
+ .s_register = max_ser_s_register,
+#endif
+};
+
+static const struct v4l2_ctrl_ops max_ser_ctrl_ops = {
+ .s_ctrl = max_ser_s_ctrl,
+};
+
+static const struct v4l2_subdev_pad_ops max_ser_pad_ops = {
+ .enable_streams = max_ser_enable_streams,
+ .disable_streams = max_ser_disable_streams,
+
+ .set_routing = max_ser_set_routing,
+ .get_frame_desc = max_ser_get_frame_desc,
+
+ .get_fmt = v4l2_subdev_get_fmt,
+ .set_fmt = max_ser_set_fmt,
+
+ .enum_frame_interval = max_ser_enum_frame_interval,
+ .get_frame_interval = max_ser_get_frame_interval,
+ .set_frame_interval = max_ser_set_frame_interval,
+};
+
+static const struct v4l2_subdev_ops max_ser_subdev_ops = {
+ .core = &max_ser_core_ops,
+ .pad = &max_ser_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops max_ser_internal_ops = {
+ .init_state = &max_ser_init_state,
+};
+
+static const struct media_entity_operations max_ser_media_ops = {
+ .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
+ .has_pad_interdep = v4l2_subdev_has_pad_interdep,
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+static int max_ser_init(struct max_ser_priv *priv)
+{
+ struct max_ser *ser = priv->ser;
+ unsigned int i;
+ int ret;
+
+ if (ser->ops->init) {
+ ret = ser->ops->init(ser);
+ if (ret)
+ return ret;
+ }
+
+ if (ser->ops->set_tunnel_enable) {
+ ret = ser->ops->set_tunnel_enable(ser, false);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < ser->ops->num_phys; i++) {
+ struct max_ser_phy *phy = &ser->phys[i];
+
+ if (phy->enabled) {
+ ret = ser->ops->init_phy(ser, phy);
+ if (ret)
+ return ret;
+ }
+
+ if (ser->ops->set_phy_active) {
+ ret = ser->ops->set_phy_active(ser, phy, false);
+ if (ret)
+ return ret;
+ }
+ }
+
+ for (i = 0; i < ser->ops->num_pipes; i++) {
+ struct max_ser_pipe *pipe = &ser->pipes[i];
+ struct max_ser_phy *phy = &ser->phys[pipe->phy_id];
+
+ ret = ser->ops->set_pipe_enable(ser, pipe, false);
+ if (ret)
+ return ret;
+
+ if (ser->ops->set_pipe_stream_id) {
+ ret = ser->ops->set_pipe_stream_id(ser, pipe, pipe->stream_id);
+ if (ret)
+ return ret;
+ }
+
+ if (ser->ops->set_pipe_phy) {
+ ret = ser->ops->set_pipe_phy(ser, pipe, phy);
+ if (ret)
+ return ret;
+ }
+
+ if (ser->ops->set_pipe_vcs) {
+ ret = ser->ops->set_pipe_vcs(ser, pipe, 0);
+ if (ret)
+ return ret;
+ }
+
+ if (ser->ops->set_pipe_mode) {
+ ret = ser->ops->set_pipe_mode(ser, pipe, &pipe->mode);
+ if (ret)
+ return ret;
+ }
+
+ ret = max_ser_set_pipe_dts(priv, pipe, NULL, 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int max_ser_notify_bound(struct v4l2_async_notifier *nf,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_connection *base_asc)
+{
+ struct max_ser_priv *priv = nf_to_priv(nf);
+ struct max_serdes_asc *asc = asc_to_max(base_asc);
+ struct max_serdes_source *source = asc->source;
+ u32 pad = source->index;
+ int ret;
+
+ ret = media_entity_get_fwnode_pad(&subdev->entity,
+ source->ep_fwnode,
+ MEDIA_PAD_FL_SOURCE);
+ if (ret < 0) {
+ dev_err(priv->dev, "Failed to find pad for %s\n", subdev->name);
+ return ret;
+ }
+
+ source->sd = subdev;
+ source->pad = ret;
+
+ ret = media_create_pad_link(&source->sd->entity, source->pad,
+ &priv->sd.entity, pad,
+ MEDIA_LNK_FL_ENABLED |
+ MEDIA_LNK_FL_IMMUTABLE);
+ if (ret) {
+ dev_err(priv->dev, "Unable to link %s:%u -> %s:%u\n",
+ source->sd->name, source->pad, priv->sd.name, pad);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void max_ser_notify_unbind(struct v4l2_async_notifier *nf,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_connection *base_asc)
+{
+ struct max_serdes_asc *asc = asc_to_max(base_asc);
+ struct max_serdes_source *source = asc->source;
+
+ source->sd = NULL;
+}
+
+static const struct v4l2_async_notifier_operations max_ser_notify_ops = {
+ .bound = max_ser_notify_bound,
+ .unbind = max_ser_notify_unbind,
+};
+
+static int max_ser_v4l2_notifier_register(struct max_ser_priv *priv)
+{
+ struct max_ser *ser = priv->ser;
+ unsigned int i;
+ int ret;
+
+ v4l2_async_subdev_nf_init(&priv->nf, &priv->sd);
+
+ for (i = 0; i < ser->ops->num_phys; i++) {
+ struct max_ser_phy *phy = &ser->phys[i];
+ struct max_serdes_source *source;
+ struct max_serdes_asc *asc;
+
+ source = max_ser_get_phy_source(priv, phy);
+ if (!source->ep_fwnode)
+ continue;
+
+ asc = v4l2_async_nf_add_fwnode(&priv->nf, source->ep_fwnode,
+ struct max_serdes_asc);
+ if (IS_ERR(asc)) {
+ dev_err(priv->dev,
+ "Failed to add subdev for source %u: %pe", i,
+ asc);
+
+ v4l2_async_nf_cleanup(&priv->nf);
+
+ return PTR_ERR(asc);
+ }
+
+ asc->source = source;
+ }
+
+ priv->nf.ops = &max_ser_notify_ops;
+
+ ret = v4l2_async_nf_register(&priv->nf);
+ if (ret) {
+ dev_err(priv->dev, "Failed to register subdev notifier");
+ v4l2_async_nf_cleanup(&priv->nf);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void max_ser_v4l2_notifier_unregister(struct max_ser_priv *priv)
+{
+ v4l2_async_nf_unregister(&priv->nf);
+ v4l2_async_nf_cleanup(&priv->nf);
+}
+
+static int max_ser_v4l2_register(struct max_ser_priv *priv)
+{
+ struct v4l2_subdev *sd = &priv->sd;
+ struct max_ser *ser = priv->ser;
+ void *data = i2c_get_clientdata(priv->client);
+ unsigned int num_pads = max_ser_num_pads(ser);
+ unsigned int i;
+ int ret;
+
+ v4l2_i2c_subdev_init(sd, priv->client, &max_ser_subdev_ops);
+ i2c_set_clientdata(priv->client, data);
+ sd->internal_ops = &max_ser_internal_ops;
+ sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+ sd->entity.ops = &max_ser_media_ops;
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
+
+ priv->pads = devm_kcalloc(priv->dev, num_pads, sizeof(*priv->pads), GFP_KERNEL);
+ if (!priv->pads)
+ return -ENOMEM;
+
+ for (i = 0; i < num_pads; i++) {
+ if (max_ser_pad_is_sink(ser, i))
+ priv->pads[i].flags = MEDIA_PAD_FL_SINK;
+ else if (max_ser_pad_is_source(ser, i))
+ priv->pads[i].flags = MEDIA_PAD_FL_SOURCE;
+ else if (max_ser_pad_is_tpg(ser, i))
+ priv->pads[i].flags = MEDIA_PAD_FL_SINK |
+ MEDIA_PAD_FL_INTERNAL;
+ else
+ return -EINVAL;
+ }
+
+ v4l2_set_subdevdata(sd, priv);
+
+ if (ser->ops->tpg_patterns) {
+ v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
+ priv->sd.ctrl_handler = &priv->ctrl_handler;
+
+ v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
+ &max_ser_ctrl_ops,
+ V4L2_CID_TEST_PATTERN,
+ MAX_SERDES_TPG_PATTERN_MAX,
+ ~ser->ops->tpg_patterns,
+ __ffs(ser->ops->tpg_patterns),
+ max_serdes_tpg_patterns);
+ if (priv->ctrl_handler.error) {
+ ret = priv->ctrl_handler.error;
+ goto err_free_ctrl;
+ }
+ }
+
+ ret = media_entity_pads_init(&sd->entity, num_pads, priv->pads);
+ if (ret)
+ goto err_free_ctrl;
+
+ ret = max_ser_v4l2_notifier_register(priv);
+ if (ret)
+ goto err_media_entity_cleanup;
+
+ ret = v4l2_subdev_init_finalize(sd);
+ if (ret)
+ goto err_nf_cleanup;
+
+ ret = v4l2_async_register_subdev(sd);
+ if (ret)
+ goto err_sd_cleanup;
+
+ return 0;
+
+err_sd_cleanup:
+ v4l2_subdev_cleanup(sd);
+err_nf_cleanup:
+ max_ser_v4l2_notifier_unregister(priv);
+err_media_entity_cleanup:
+ media_entity_cleanup(&sd->entity);
+err_free_ctrl:
+ v4l2_ctrl_handler_free(&priv->ctrl_handler);
+
+ return ret;
+}
+
+static void max_ser_v4l2_unregister(struct max_ser_priv *priv)
+{
+ struct v4l2_subdev *sd = &priv->sd;
+
+ max_ser_v4l2_notifier_unregister(priv);
+ v4l2_async_unregister_subdev(sd);
+ v4l2_subdev_cleanup(sd);
+ media_entity_cleanup(&sd->entity);
+ v4l2_ctrl_handler_free(&priv->ctrl_handler);
+}
+
+static int max_ser_parse_sink_dt_endpoint(struct max_ser_priv *priv,
+ struct max_ser_phy *phy,
+ struct max_serdes_source *source,
+ struct fwnode_handle *fwnode)
+{
+ struct max_ser *ser = priv->ser;
+ u32 pad = max_ser_phy_to_pad(ser, phy);
+ struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
+ struct fwnode_handle *ep;
+ int ret;
+
+ ep = fwnode_graph_get_endpoint_by_id(fwnode, pad, 0, 0);
+ if (!ep)
+ return 0;
+
+ source->ep_fwnode = fwnode_graph_get_remote_endpoint(ep);
+ if (!source->ep_fwnode) {
+ fwnode_handle_put(ep);
+ dev_err(priv->dev,
+ "Failed to get remote endpoint on port %u\n", pad);
+ return -EINVAL;
+ }
+
+ ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep);
+ fwnode_handle_put(ep);
+ if (ret) {
+ dev_err(priv->dev, "Could not parse endpoint on port %u\n", pad);
+ return ret;
+ }
+
+ phy->mipi = v4l2_ep.bus.mipi_csi2;
+ phy->enabled = true;
+
+ return 0;
+}
+
+static int max_ser_find_phys_config(struct max_ser_priv *priv)
+{
+ struct max_ser *ser = priv->ser;
+ const struct max_serdes_phys_configs *configs = &ser->ops->phys_configs;
+ struct max_ser_phy *phy;
+ unsigned int i, j;
+
+ if (!configs->num_configs)
+ return 0;
+
+ for (i = 0; i < configs->num_configs; i++) {
+ const struct max_serdes_phys_config *config = &configs->configs[i];
+ bool matching = true;
+
+ for (j = 0; j < ser->ops->num_phys; j++) {
+ phy = &ser->phys[j];
+
+ if (!phy->enabled)
+ continue;
+
+ if (phy->mipi.num_data_lanes <= config->lanes[j])
+ continue;
+
+ matching = false;
+
+ break;
+ }
+
+ if (matching)
+ break;
+ }
+
+ if (i == configs->num_configs) {
+ dev_err(priv->dev, "Invalid lane configuration\n");
+ return -EINVAL;
+ }
+
+ ser->phys_config = i;
+
+ return 0;
+}
+
+static int max_ser_parse_dt(struct max_ser_priv *priv)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(priv->dev);
+ struct max_ser *ser = priv->ser;
+ struct max_ser_pipe *pipe;
+ struct max_ser_phy *phy;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ser->ops->num_phys; i++) {
+ phy = &ser->phys[i];
+ phy->index = i;
+ }
+
+ for (i = 0; i < ser->ops->num_pipes; i++) {
+ pipe = &ser->pipes[i];
+ pipe->index = i;
+ pipe->phy_id = i % ser->ops->num_phys;
+ pipe->stream_id = i % MAX_SERDES_STREAMS_NUM;
+ }
+
+ for (i = 0; i < ser->ops->num_phys; i++) {
+ struct max_ser_phy *phy = &ser->phys[i];
+ struct max_serdes_source *source;
+
+ source = max_ser_get_phy_source(priv, phy);
+ source->index = i;
+
+ ret = max_ser_parse_sink_dt_endpoint(priv, phy, source, fwnode);
+ if (ret)
+ return ret;
+ }
+
+ return max_ser_find_phys_config(priv);
+}
+
+static int max_ser_allocate(struct max_ser_priv *priv)
+{
+ struct max_ser *ser = priv->ser;
+ unsigned int num_pads = max_ser_num_pads(ser);
+
+ ser->phys = devm_kcalloc(priv->dev, ser->ops->num_phys,
+ sizeof(*ser->phys), GFP_KERNEL);
+ if (!ser->phys)
+ return -ENOMEM;
+
+ ser->pipes = devm_kcalloc(priv->dev, ser->ops->num_pipes,
+ sizeof(*ser->pipes), GFP_KERNEL);
+ if (!ser->pipes)
+ return -ENOMEM;
+
+ ser->vc_remaps = devm_kcalloc(priv->dev, ser->ops->num_vc_remaps,
+ sizeof(*ser->vc_remaps), GFP_KERNEL);
+ if (!ser->vc_remaps)
+ return -ENOMEM;
+
+ ser->i2c_xlates = devm_kcalloc(priv->dev, ser->ops->num_i2c_xlates,
+ sizeof(*ser->i2c_xlates), GFP_KERNEL);
+ if (!ser->i2c_xlates)
+ return -ENOMEM;
+
+ priv->sources = devm_kcalloc(priv->dev, ser->ops->num_phys,
+ sizeof(*priv->sources), GFP_KERNEL);
+ if (!priv->sources)
+ return -ENOMEM;
+
+ priv->streams_masks = devm_kcalloc(priv->dev, num_pads,
+ sizeof(*priv->streams_masks),
+ GFP_KERNEL);
+ if (!priv->streams_masks)
+ return -ENOMEM;
+
+ return 0;
+}
+
+int max_ser_probe(struct i2c_client *client, struct max_ser *ser)
+{
+ struct device *dev = &client->dev;
+ struct max_ser_priv *priv;
+ int ret;
+
+ if (ser->ops->num_phys > MAX_SER_NUM_PHYS)
+ return -E2BIG;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->client = client;
+ priv->dev = dev;
+ priv->ser = ser;
+ ser->priv = priv;
+ ser->mode = __ffs(ser->ops->modes);
+
+ ret = max_ser_allocate(priv);
+ if (ret)
+ return ret;
+
+ ret = max_ser_parse_dt(priv);
+ if (ret)
+ return ret;
+
+ ret = max_ser_init(priv);
+ if (ret)
+ return ret;
+
+ ret = max_ser_i2c_adapter_init(priv);
+ if (ret)
+ return ret;
+
+ ret = max_ser_v4l2_register(priv);
+ if (ret)
+ goto err_i2c_adapter_deinit;
+
+ return 0;
+
+err_i2c_adapter_deinit:
+ max_ser_i2c_adapter_deinit(priv);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(max_ser_probe, "MAX_SERDES");
+
+int max_ser_remove(struct max_ser *ser)
+{
+ struct max_ser_priv *priv = ser->priv;
+
+ max_ser_v4l2_unregister(priv);
+
+ max_ser_i2c_adapter_deinit(priv);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(max_ser_remove, "MAX_SERDES");
+
+int max_ser_set_double_bpps(struct v4l2_subdev *sd, u32 double_bpps)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+
+ priv->double_bpps = double_bpps;
+
+ return 0;
+}
+
+int max_ser_set_stream_id(struct v4l2_subdev *sd, unsigned int stream_id)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct max_ser *ser = priv->ser;
+ struct max_ser_pipe *pipe = &ser->pipes[0];
+
+ if (!ser->ops->set_pipe_stream_id)
+ return -EOPNOTSUPP;
+
+ return ser->ops->set_pipe_stream_id(ser, pipe, stream_id);
+}
+
+int max_ser_get_stream_id(struct v4l2_subdev *sd, unsigned int *stream_id)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct max_ser *ser = priv->ser;
+ struct max_ser_pipe *pipe = &ser->pipes[0];
+
+ if (!ser->ops->get_pipe_stream_id)
+ return -EOPNOTSUPP;
+
+ *stream_id = ser->ops->get_pipe_stream_id(ser, pipe);
+
+ return 0;
+}
+
+unsigned int max_ser_get_supported_modes(struct v4l2_subdev *sd)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct max_ser *ser = priv->ser;
+ struct v4l2_subdev_state *state;
+ unsigned int modes = ser->ops->modes;
+
+ state = v4l2_subdev_lock_and_get_active_state(&priv->sd);
+
+ if (max_ser_is_tpg_routed(priv, state))
+ modes = BIT(ser->ops->tpg_mode);
+
+ v4l2_subdev_unlock_state(state);
+
+ return modes;
+}
+
+bool max_ser_supports_vc_remap(struct v4l2_subdev *sd)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct max_ser *ser = priv->ser;
+
+ return !!ser->ops->set_vc_remap;
+}
+
+int max_ser_set_mode(struct v4l2_subdev *sd, enum max_serdes_gmsl_mode mode)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct max_ser *ser = priv->ser;
+ int ret;
+
+ if (!(ser->ops->modes & BIT(mode)))
+ return -EINVAL;
+
+ if (ser->mode == mode)
+ return 0;
+
+ if (ser->ops->set_tunnel_enable) {
+ bool tunnel_enable = mode == MAX_SERDES_GMSL_TUNNEL_MODE;
+
+ ret = ser->ops->set_tunnel_enable(ser, tunnel_enable);
+ if (ret)
+ return ret;
+ }
+
+ ser->mode = mode;
+
+ return 0;
+}
+
+int max_ser_set_vc_remaps(struct v4l2_subdev *sd,
+ struct max_serdes_vc_remap *vc_remaps,
+ int num_vc_remaps)
+{
+ struct max_ser_priv *priv = sd_to_priv(sd);
+ struct max_ser *ser = priv->ser;
+ unsigned int mask = 0;
+ unsigned int i;
+ int ret;
+
+ if (!ser->ops->set_vc_remap)
+ return -EOPNOTSUPP;
+
+ if (num_vc_remaps > ser->ops->num_vc_remaps)
+ return -E2BIG;
+
+ for (i = 0; i < num_vc_remaps; i++) {
+ ret = ser->ops->set_vc_remap(ser, i, &vc_remaps[i]);
+ if (ret)
+ return ret;
+
+ mask |= BIT(i);
+ }
+
+ ret = ser->ops->set_vc_remaps_enable(ser, mask);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < num_vc_remaps; i++)
+ ser->vc_remaps[i] = vc_remaps[i];
+
+ ser->num_vc_remaps = num_vc_remaps;
+
+ return 0;
+}
+
+static int max_ser_read_reg(struct i2c_adapter *adapter, u8 addr,
+ u16 reg, u8 *val)
+{
+ u8 buf[2] = { reg >> 8, reg & 0xff };
+ struct i2c_msg msg[2] = {
+ {
+ .addr = addr,
+ .flags = 0,
+ .buf = buf,
+ .len = sizeof(buf),
+ },
+ {
+ .addr = addr,
+ .flags = I2C_M_RD,
+ .buf = buf,
+ .len = 1,
+ },
+ };
+ int ret;
+
+ ret = i2c_transfer(adapter, msg, ARRAY_SIZE(msg));
+ if (ret < 0)
+ return ret;
+
+ *val = buf[0];
+
+ return 0;
+}
+
+static int max_ser_write_reg(struct i2c_adapter *adapter, u8 addr,
+ u16 reg, u8 val)
+{
+ u8 buf[3] = { reg >> 8, reg & 0xff, val };
+ struct i2c_msg msg[1] = {
+ {
+ .addr = addr,
+ .flags = 0,
+ .buf = buf,
+ .len = sizeof(buf),
+ },
+ };
+ int ret;
+
+ ret = i2c_transfer(adapter, msg, ARRAY_SIZE(msg));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+int max_ser_reset(struct i2c_adapter *adapter, u8 addr)
+{
+ int ret;
+ u8 val;
+
+ ret = max_ser_read_reg(adapter, addr, MAX_SER_CTRL0, &val);
+ if (ret)
+ return ret;
+
+ val |= MAX_SER_CTRL0_RESET_ALL;
+
+ return max_ser_write_reg(adapter, addr, MAX_SER_CTRL0, val);
+}
+
+int max_ser_wait_for_multiple(struct i2c_adapter *adapter, u8 *addrs,
+ unsigned int num_addrs, u8 *current_addr)
+{
+ unsigned int i, j;
+ int ret = 0;
+ u8 val;
+
+ for (i = 0; i < 10; i++) {
+ for (j = 0; j < num_addrs; j++) {
+ ret = max_ser_read_reg(adapter, addrs[j], MAX_SER_REG0, &val);
+ if (!ret && val) {
+ *current_addr = addrs[j];
+ return 0;
+ }
+
+ msleep(100);
+ }
+ }
+
+ return ret;
+}
+
+int max_ser_wait(struct i2c_adapter *adapter, u8 addr)
+{
+ u8 current_addr;
+
+ return max_ser_wait_for_multiple(adapter, &addr, 1, ¤t_addr);
+}
+
+int max_ser_fix_tx_ids(struct i2c_adapter *adapter, u8 addr)
+{
+ unsigned int addr_regs[] = {
+ MAX_SER_CFGI_INFOFR_TR3,
+ MAX_SER_CFGL_SPI_TR3,
+ MAX_SER_CFGC_CC_TR3,
+ MAX_SER_CFGC_GPIO_TR3,
+ MAX_SER_CFGL_IIC_X_TR3,
+ MAX_SER_CFGL_IIC_Y_TR3,
+ };
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(addr_regs); i++) {
+ ret = max_ser_write_reg(adapter, addr, addr_regs[i], addr);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int max_ser_change_address(struct i2c_adapter *adapter, u8 addr, u8 new_addr)
+{
+ u8 val = FIELD_PREP(MAX_SER_REG0_DEV_ADDR, new_addr);
+
+ return max_ser_write_reg(adapter, addr, MAX_SER_REG0, val);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("I2C_ATR");
diff --git a/drivers/media/i2c/maxim-serdes/max_ser.h b/drivers/media/i2c/maxim-serdes/max_ser.h
new file mode 100644
index 000000000000..a9365be5e62d
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max_ser.h
@@ -0,0 +1,147 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025 Analog Devices Inc.
+ */
+
+#ifndef MAX_SER_H
+#define MAX_SER_H
+
+#include <linux/i2c.h>
+
+#include <media/v4l2-mediabus.h>
+
+#include "max_serdes.h"
+
+#define MAX_SER_REG0 0x0
+#define MAX_SER_REG0_DEV_ADDR GENMASK(7, 1)
+
+#define MAX_SER_CTRL0 0x10
+#define MAX_SER_CTRL0_RESET_ALL BIT(7)
+
+#define MAX_SER_CFGI_INFOFR_TR3 0x7b
+#define MAX_SER_CFGL_SPI_TR3 0x83
+#define MAX_SER_CFGC_CC_TR3 0x8b
+#define MAX_SER_CFGC_GPIO_TR3 0x93
+#define MAX_SER_CFGL_IIC_X_TR3 0xa3
+#define MAX_SER_CFGL_IIC_Y_TR3 0xab
+
+struct max_ser_phy {
+ unsigned int index;
+ struct v4l2_mbus_config_mipi_csi2 mipi;
+ bool enabled;
+ bool active;
+};
+
+struct max_ser_pipe_mode {
+ unsigned int soft_bpp;
+ unsigned int bpp;
+ bool dbl8;
+ bool dbl10;
+ bool dbl12;
+};
+
+struct max_ser_pipe {
+ unsigned int index;
+ unsigned int phy_id;
+ unsigned int stream_id;
+ unsigned int *dts;
+ unsigned int num_dts;
+ unsigned int vcs;
+ struct max_ser_pipe_mode mode;
+ bool enabled;
+};
+
+struct max_ser;
+
+struct max_ser_ops {
+ unsigned int modes;
+ unsigned int num_pipes;
+ unsigned int num_dts_per_pipe;
+ unsigned int num_phys;
+ unsigned int num_i2c_xlates;
+ unsigned int num_vc_remaps;
+
+ struct max_serdes_phys_configs phys_configs;
+ struct max_serdes_tpg_entries tpg_entries;
+ enum max_serdes_gmsl_mode tpg_mode;
+ unsigned int tpg_patterns;
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ int (*reg_read)(struct max_ser *ser, unsigned int reg, unsigned int *val);
+ int (*reg_write)(struct max_ser *ser, unsigned int reg, unsigned int val);
+#endif
+ int (*log_status)(struct max_ser *ser);
+ int (*log_pipe_status)(struct max_ser *ser, struct max_ser_pipe *pipe);
+ int (*log_phy_status)(struct max_ser *ser, struct max_ser_phy *phy);
+ int (*init)(struct max_ser *ser);
+ int (*set_i2c_xlate)(struct max_ser *ser, unsigned int i,
+ struct max_serdes_i2c_xlate *i2c_xlate);
+ int (*set_tunnel_enable)(struct max_ser *ser, bool enable);
+ int (*set_tpg)(struct max_ser *ser, const struct max_serdes_tpg_entry *entry);
+ int (*init_phy)(struct max_ser *ser, struct max_ser_phy *phy);
+ int (*set_phy_active)(struct max_ser *ser, struct max_ser_phy *phy,
+ bool enable);
+ int (*set_pipe_enable)(struct max_ser *ser, struct max_ser_pipe *pipe,
+ bool enable);
+ int (*set_pipe_dt)(struct max_ser *ser, struct max_ser_pipe *pipe,
+ unsigned int i, unsigned int dt);
+ int (*set_pipe_dt_en)(struct max_ser *ser, struct max_ser_pipe *pipe,
+ unsigned int i, bool enable);
+ int (*set_pipe_vcs)(struct max_ser *ser, struct max_ser_pipe *pipe,
+ unsigned int vcs);
+ int (*set_pipe_mode)(struct max_ser *ser, struct max_ser_pipe *pipe,
+ struct max_ser_pipe_mode *mode);
+ int (*set_vc_remap)(struct max_ser *ser, unsigned int i,
+ struct max_serdes_vc_remap *vc_remap);
+ int (*set_vc_remaps_enable)(struct max_ser *ser, unsigned int mask);
+ int (*set_pipe_stream_id)(struct max_ser *ser, struct max_ser_pipe *pipe,
+ unsigned int stream_id);
+ unsigned int (*get_pipe_stream_id)(struct max_ser *ser, struct max_ser_pipe *pipe);
+ int (*set_pipe_phy)(struct max_ser *ser, struct max_ser_pipe *pipe,
+ struct max_ser_phy *phy);
+};
+
+struct max_ser_priv;
+
+struct max_ser {
+ struct max_ser_priv *priv;
+
+ const struct max_ser_ops *ops;
+
+ struct max_serdes_i2c_xlate *i2c_xlates;
+
+ struct max_ser_phy *phys;
+ struct max_ser_pipe *pipes;
+ const struct max_serdes_tpg_entry *tpg_entry;
+ enum max_serdes_tpg_pattern tpg_pattern;
+
+ struct max_serdes_vc_remap *vc_remaps;
+ unsigned int num_vc_remaps;
+
+ unsigned int phys_config;
+ unsigned int active;
+ enum max_serdes_gmsl_mode mode;
+};
+
+int max_ser_probe(struct i2c_client *client, struct max_ser *ser);
+
+int max_ser_remove(struct max_ser *ser);
+
+int max_ser_set_double_bpps(struct v4l2_subdev *sd, u32 double_bpps);
+unsigned int max_ser_get_supported_modes(struct v4l2_subdev *sd);
+int max_ser_set_mode(struct v4l2_subdev *sd, enum max_serdes_gmsl_mode mode);
+bool max_ser_supports_vc_remap(struct v4l2_subdev *sd);
+int max_ser_set_stream_id(struct v4l2_subdev *sd, unsigned int stream_id);
+int max_ser_get_stream_id(struct v4l2_subdev *sd, unsigned int *stream_id);
+int max_ser_set_vc_remaps(struct v4l2_subdev *sd, struct max_serdes_vc_remap *vc_remaps,
+ int num_vc_remaps);
+
+int max_ser_reset(struct i2c_adapter *adapter, u8 addr);
+int max_ser_wait(struct i2c_adapter *adapter, u8 addr);
+int max_ser_wait_for_multiple(struct i2c_adapter *adapter, u8 *addrs,
+ unsigned int num_addrs, u8 *current_addr);
+
+int max_ser_change_address(struct i2c_adapter *adapter, u8 addr, u8 new_addr);
+int max_ser_fix_tx_ids(struct i2c_adapter *adapter, u8 addr);
+
+#endif // MAX_SER_H
--
2.53.0
^ permalink raw reply related
* [PATCH v13 20/22] staging: media: remove MAX96712 driver
From: Dumitru Ceclan via B4 Relay @ 2026-06-04 14:14 UTC (permalink / raw)
To: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
Laurent Pinchart, Julien Massot, Rob Herring,
Niklas Söderlund, Greg Kroah-Hartman
Cc: mitrutzceclan, linux-media, linux-kernel, devicetree,
linux-staging, linux-gpio, Niklas Söderlund, Martin Hecht,
Cosmin Tanislav
In-Reply-To: <20260604-gmsl2-3_serdes-v13-0-9d8a4919983b@analog.com>
From: Cosmin Tanislav <demonsingur@gmail.com>
Remove the staging MAX96712 driver.
Its functionality has been moved to the MAX96724 driver which makes use
of the Maxim GMSL2/3 deserializer framework.
Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
MAINTAINERS | 1 -
drivers/staging/media/Kconfig | 2 -
drivers/staging/media/Makefile | 1 -
drivers/staging/media/max96712/Kconfig | 14 -
drivers/staging/media/max96712/Makefile | 2 -
drivers/staging/media/max96712/max96712.c | 487 ------------------------------
6 files changed, 507 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 75ee49bc612b..9577aa8dc4ea 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15733,7 +15733,6 @@ M: Niklas Söderlund <niklas.soderlund@ragnatech.se>
L: linux-media@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
-F: drivers/staging/media/max96712/max96712.c
MAX96714 GMSL2 DESERIALIZER DRIVER
M: Julien Massot <julien.massot@collabora.com>
diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig
index 1aa31bddf970..5f532ef4f6ce 100644
--- a/drivers/staging/media/Kconfig
+++ b/drivers/staging/media/Kconfig
@@ -30,8 +30,6 @@ source "drivers/staging/media/ipu3/Kconfig"
source "drivers/staging/media/ipu7/Kconfig"
-source "drivers/staging/media/max96712/Kconfig"
-
source "drivers/staging/media/meson/vdec/Kconfig"
source "drivers/staging/media/sunxi/Kconfig"
diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile
index 6f78b0edde1e..f517390f5d43 100644
--- a/drivers/staging/media/Makefile
+++ b/drivers/staging/media/Makefile
@@ -2,7 +2,6 @@
obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += deprecated/atmel/
obj-$(CONFIG_INTEL_ATOMISP) += atomisp/
obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx/
-obj-$(CONFIG_VIDEO_MAX96712) += max96712/
obj-$(CONFIG_VIDEO_MESON_VDEC) += meson/vdec/
obj-$(CONFIG_VIDEO_SUNXI) += sunxi/
obj-$(CONFIG_VIDEO_TEGRA) += tegra-video/
diff --git a/drivers/staging/media/max96712/Kconfig b/drivers/staging/media/max96712/Kconfig
deleted file mode 100644
index 117fadf81bd0..000000000000
--- a/drivers/staging/media/max96712/Kconfig
+++ /dev/null
@@ -1,14 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-config VIDEO_MAX96712
- tristate "Maxim MAX96712 Quad GMSL2 Deserializer support"
- depends on I2C
- depends on OF_GPIO
- depends on VIDEO_DEV
- select V4L2_FWNODE
- select VIDEO_V4L2_SUBDEV_API
- select MEDIA_CONTROLLER
- help
- This driver supports the Maxim MAX96712 Quad GMSL2 Deserializer.
-
- To compile this driver as a module, choose M here: the
- module will be called max96712.
diff --git a/drivers/staging/media/max96712/Makefile b/drivers/staging/media/max96712/Makefile
deleted file mode 100644
index 70c1974ce3f0..000000000000
--- a/drivers/staging/media/max96712/Makefile
+++ /dev/null
@@ -1,2 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-obj-$(CONFIG_VIDEO_MAX96712) += max96712.o
diff --git a/drivers/staging/media/max96712/max96712.c b/drivers/staging/media/max96712/max96712.c
deleted file mode 100644
index 0751b2e04895..000000000000
--- a/drivers/staging/media/max96712/max96712.c
+++ /dev/null
@@ -1,487 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Maxim MAX96712 Quad GMSL2 Deserializer Driver
- *
- * Copyright (C) 2021 Renesas Electronics Corporation
- * Copyright (C) 2021 Niklas Söderlund
- */
-
-#include <linux/delay.h>
-#include <linux/i2c.h>
-#include <linux/module.h>
-#include <linux/of_graph.h>
-#include <linux/regmap.h>
-
-#include <media/v4l2-ctrls.h>
-#include <media/v4l2-fwnode.h>
-#include <media/v4l2-subdev.h>
-
-#define DEBUG_EXTRA_REG 0x09
-#define DEBUG_EXTRA_PCLK_25MHZ 0x00
-#define DEBUG_EXTRA_PCLK_75MHZ 0x01
-
-enum max96712_pattern {
- MAX96712_PATTERN_CHECKERBOARD = 0,
- MAX96712_PATTERN_GRADIENT,
-};
-
-struct max96712_info {
- unsigned int dpllfreq;
- bool have_debug_extra;
-};
-
-struct max96712_priv {
- struct i2c_client *client;
- struct regmap *regmap;
- struct gpio_desc *gpiod_pwdn;
-
- const struct max96712_info *info;
-
- bool cphy;
- struct v4l2_mbus_config_mipi_csi2 mipi;
-
- struct v4l2_subdev sd;
- struct v4l2_ctrl_handler ctrl_handler;
- struct media_pad pads[1];
-
- enum max96712_pattern pattern;
-};
-
-static int max96712_write(struct max96712_priv *priv, unsigned int reg, u8 val)
-{
- int ret;
-
- ret = regmap_write(priv->regmap, reg, val);
- if (ret)
- dev_err(&priv->client->dev, "write 0x%04x failed\n", reg);
-
- return ret;
-}
-
-static int max96712_update_bits(struct max96712_priv *priv, unsigned int reg,
- u8 mask, u8 val)
-{
- int ret;
-
- ret = regmap_update_bits(priv->regmap, reg, mask, val);
- if (ret)
- dev_err(&priv->client->dev, "update 0x%04x failed\n", reg);
-
- return ret;
-}
-
-static int max96712_write_bulk(struct max96712_priv *priv, unsigned int reg,
- const void *val, size_t val_count)
-{
- int ret;
-
- ret = regmap_bulk_write(priv->regmap, reg, val, val_count);
- if (ret)
- dev_err(&priv->client->dev, "bulk write 0x%04x failed\n", reg);
-
- return ret;
-}
-
-static int max96712_write_bulk_value(struct max96712_priv *priv,
- unsigned int reg, unsigned int val,
- size_t val_count)
-{
- unsigned int i;
- u8 values[4];
-
- for (i = 1; i <= val_count; i++)
- values[i - 1] = (val >> ((val_count - i) * 8)) & 0xff;
-
- return max96712_write_bulk(priv, reg, &values, val_count);
-}
-
-static void max96712_reset(struct max96712_priv *priv)
-{
- max96712_update_bits(priv, 0x13, 0x40, 0x40);
- msleep(20);
-}
-
-static void max96712_mipi_enable(struct max96712_priv *priv, bool enable)
-{
- if (enable) {
- max96712_update_bits(priv, 0x40b, 0x02, 0x02);
- max96712_update_bits(priv, 0x8a0, 0x80, 0x80);
- } else {
- max96712_update_bits(priv, 0x8a0, 0x80, 0x00);
- max96712_update_bits(priv, 0x40b, 0x02, 0x00);
- }
-}
-
-static void max96712_mipi_configure(struct max96712_priv *priv)
-{
- unsigned int i;
- u8 phy5 = 0;
-
- max96712_mipi_enable(priv, false);
-
- /* Select 2x4 mode. */
- max96712_write(priv, 0x8a0, 0x04);
-
- /* TODO: Add support for 2-lane and 1-lane configurations. */
- if (priv->cphy) {
- /* Configure a 3-lane C-PHY using PHY0 and PHY1. */
- max96712_write(priv, 0x94a, 0xa0);
-
- /* Configure C-PHY timings. */
- max96712_write(priv, 0x8ad, 0x3f);
- max96712_write(priv, 0x8ae, 0x7d);
- } else {
- /* Configure a 4-lane D-PHY using PHY0 and PHY1. */
- max96712_write(priv, 0x94a, 0xc0);
- }
-
- /* Configure lane mapping for PHY0 and PHY1. */
- /* TODO: Add support for lane swapping. */
- max96712_write(priv, 0x8a3, 0xe4);
-
- /* Configure lane polarity for PHY0 and PHY1. */
- for (i = 0; i < priv->mipi.num_data_lanes + 1; i++)
- if (priv->mipi.lane_polarities[i])
- phy5 |= BIT(i == 0 ? 5 : i < 3 ? i - 1 : i);
- max96712_write(priv, 0x8a5, phy5);
-
- /* Set link frequency for PHY0 and PHY1. */
- max96712_update_bits(priv, 0x415, 0x3f,
- ((priv->info->dpllfreq / 100) & 0x1f) | BIT(5));
- max96712_update_bits(priv, 0x418, 0x3f,
- ((priv->info->dpllfreq / 100) & 0x1f) | BIT(5));
-
- /* Enable PHY0 and PHY1 */
- max96712_update_bits(priv, 0x8a2, 0xf0, 0x30);
-}
-
-static void max96712_pattern_enable(struct max96712_priv *priv, bool enable)
-{
- const u32 h_active = 1920;
- const u32 h_fp = 88;
- const u32 h_sw = 44;
- const u32 h_bp = 148;
- const u32 h_tot = h_active + h_fp + h_sw + h_bp;
-
- const u32 v_active = 1080;
- const u32 v_fp = 4;
- const u32 v_sw = 5;
- const u32 v_bp = 36;
- const u32 v_tot = v_active + v_fp + v_sw + v_bp;
-
- if (!enable) {
- max96712_write(priv, 0x1051, 0x00);
- return;
- }
-
- /* Set PCLK to 75MHz if device have DEBUG_EXTRA register. */
- if (priv->info->have_debug_extra)
- max96712_write(priv, DEBUG_EXTRA_REG, DEBUG_EXTRA_PCLK_75MHZ);
-
- /* Configure Video Timing Generator for 1920x1080 @ 30 fps. */
- max96712_write_bulk_value(priv, 0x1052, 0, 3);
- max96712_write_bulk_value(priv, 0x1055, v_sw * h_tot, 3);
- max96712_write_bulk_value(priv, 0x1058,
- (v_active + v_fp + + v_bp) * h_tot, 3);
- max96712_write_bulk_value(priv, 0x105b, 0, 3);
- max96712_write_bulk_value(priv, 0x105e, h_sw, 2);
- max96712_write_bulk_value(priv, 0x1060, h_active + h_fp + h_bp, 2);
- max96712_write_bulk_value(priv, 0x1062, v_tot, 2);
- max96712_write_bulk_value(priv, 0x1064,
- h_tot * (v_sw + v_bp) + (h_sw + h_bp), 3);
- max96712_write_bulk_value(priv, 0x1067, h_active, 2);
- max96712_write_bulk_value(priv, 0x1069, h_fp + h_sw + h_bp, 2);
- max96712_write_bulk_value(priv, 0x106b, v_active, 2);
-
- /* Generate VS, HS and DE in free-running mode. */
- max96712_write(priv, 0x1050, 0xfb);
-
- /* Configure Video Pattern Generator. */
- if (priv->pattern == MAX96712_PATTERN_CHECKERBOARD) {
- /* Set checkerboard pattern size. */
- max96712_write(priv, 0x1074, 0x3c);
- max96712_write(priv, 0x1075, 0x3c);
- max96712_write(priv, 0x1076, 0x3c);
-
- /* Set checkerboard pattern colors. */
- max96712_write_bulk_value(priv, 0x106e, 0xfecc00, 3);
- max96712_write_bulk_value(priv, 0x1071, 0x006aa7, 3);
-
- /* Generate checkerboard pattern. */
- max96712_write(priv, 0x1051, 0x10);
- } else {
- /* Set gradient increment. */
- max96712_write(priv, 0x106d, 0x10);
-
- /* Generate gradient pattern. */
- max96712_write(priv, 0x1051, 0x20);
- }
-}
-
-static int max96712_s_stream(struct v4l2_subdev *sd, int enable)
-{
- struct max96712_priv *priv = v4l2_get_subdevdata(sd);
-
- if (enable) {
- max96712_pattern_enable(priv, true);
- max96712_mipi_enable(priv, true);
- } else {
- max96712_mipi_enable(priv, false);
- max96712_pattern_enable(priv, false);
- }
-
- return 0;
-}
-
-static const struct v4l2_subdev_video_ops max96712_video_ops = {
- .s_stream = max96712_s_stream,
-};
-
-static int max96712_init_state(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state)
-{
- static const struct v4l2_mbus_framefmt default_fmt = {
- .width = 1920,
- .height = 1080,
- .code = MEDIA_BUS_FMT_RGB888_1X24,
- .colorspace = V4L2_COLORSPACE_SRGB,
- .field = V4L2_FIELD_NONE,
- .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT,
- .quantization = V4L2_QUANTIZATION_DEFAULT,
- .xfer_func = V4L2_XFER_FUNC_DEFAULT,
- };
- struct v4l2_mbus_framefmt *fmt;
-
- fmt = v4l2_subdev_state_get_format(state, 0);
- *fmt = default_fmt;
-
- return 0;
-}
-
-static const struct v4l2_subdev_internal_ops max96712_internal_ops = {
- .init_state = max96712_init_state,
-};
-
-static const struct v4l2_subdev_pad_ops max96712_pad_ops = {
- .get_fmt = v4l2_subdev_get_fmt,
- .set_fmt = v4l2_subdev_get_fmt,
-};
-
-static const struct v4l2_subdev_ops max96712_subdev_ops = {
- .video = &max96712_video_ops,
- .pad = &max96712_pad_ops,
-};
-
-static const char * const max96712_test_pattern[] = {
- "Checkerboard",
- "Gradient",
-};
-
-static int max96712_s_ctrl(struct v4l2_ctrl *ctrl)
-{
- struct max96712_priv *priv =
- container_of(ctrl->handler, struct max96712_priv, ctrl_handler);
-
- switch (ctrl->id) {
- case V4L2_CID_TEST_PATTERN:
- priv->pattern = ctrl->val ?
- MAX96712_PATTERN_GRADIENT :
- MAX96712_PATTERN_CHECKERBOARD;
- break;
- }
- return 0;
-}
-
-static const struct v4l2_ctrl_ops max96712_ctrl_ops = {
- .s_ctrl = max96712_s_ctrl,
-};
-
-static int max96712_v4l2_register(struct max96712_priv *priv)
-{
- long pixel_rate;
- int ret;
-
- priv->sd.internal_ops = &max96712_internal_ops;
- v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96712_subdev_ops);
- priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
- priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
-
- v4l2_ctrl_handler_init(&priv->ctrl_handler, 2);
-
- /*
- * TODO: Once V4L2_CID_LINK_FREQ is changed from a menu control to an
- * INT64 control it should be used here instead of V4L2_CID_PIXEL_RATE.
- */
- pixel_rate = priv->info->dpllfreq / priv->mipi.num_data_lanes * 1000000;
- v4l2_ctrl_new_std(&priv->ctrl_handler, NULL, V4L2_CID_PIXEL_RATE,
- pixel_rate, pixel_rate, 1, pixel_rate);
-
- v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, &max96712_ctrl_ops,
- V4L2_CID_TEST_PATTERN,
- ARRAY_SIZE(max96712_test_pattern) - 1,
- 0, 0, max96712_test_pattern);
-
- priv->sd.ctrl_handler = &priv->ctrl_handler;
- ret = priv->ctrl_handler.error;
- if (ret)
- goto error;
-
- priv->pads[0].flags = MEDIA_PAD_FL_SOURCE;
- ret = media_entity_pads_init(&priv->sd.entity, 1, priv->pads);
- if (ret)
- goto error;
-
- v4l2_set_subdevdata(&priv->sd, priv);
-
- priv->sd.state_lock = priv->ctrl_handler.lock;
- ret = v4l2_subdev_init_finalize(&priv->sd);
- if (ret)
- goto error;
-
- ret = v4l2_async_register_subdev(&priv->sd);
- if (ret < 0) {
- dev_err(&priv->client->dev, "Unable to register subdevice\n");
- goto error;
- }
-
- return 0;
-error:
- v4l2_ctrl_handler_free(&priv->ctrl_handler);
-
- return ret;
-}
-
-static int max96712_parse_dt(struct max96712_priv *priv)
-{
- struct fwnode_handle *ep;
- struct v4l2_fwnode_endpoint v4l2_ep = {
- .bus_type = V4L2_MBUS_UNKNOWN,
- };
- unsigned int supported_lanes;
- int ret;
-
- ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(&priv->client->dev), 4,
- 0, 0);
- if (!ep) {
- dev_err(&priv->client->dev, "Not connected to subdevice\n");
- return -EINVAL;
- }
-
- ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep);
- fwnode_handle_put(ep);
- if (ret) {
- dev_err(&priv->client->dev, "Could not parse v4l2 endpoint\n");
- return -EINVAL;
- }
-
- switch (v4l2_ep.bus_type) {
- case V4L2_MBUS_CSI2_DPHY:
- supported_lanes = 4;
- priv->cphy = false;
- break;
- case V4L2_MBUS_CSI2_CPHY:
- supported_lanes = 3;
- priv->cphy = true;
- break;
- default:
- dev_err(&priv->client->dev, "Unsupported bus-type %u\n",
- v4l2_ep.bus_type);
- return -EINVAL;
- }
-
- if (v4l2_ep.bus.mipi_csi2.num_data_lanes != supported_lanes) {
- dev_err(&priv->client->dev, "Only %u data lanes supported\n",
- supported_lanes);
- return -EINVAL;
- }
-
- priv->mipi = v4l2_ep.bus.mipi_csi2;
-
- return 0;
-}
-
-static const struct regmap_config max96712_i2c_regmap = {
- .reg_bits = 16,
- .val_bits = 8,
- .max_register = 0x1f00,
-};
-
-static int max96712_probe(struct i2c_client *client)
-{
- struct max96712_priv *priv;
- int ret;
-
- priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- priv->info = of_device_get_match_data(&client->dev);
-
- priv->client = client;
-
- priv->regmap = devm_regmap_init_i2c(client, &max96712_i2c_regmap);
- if (IS_ERR(priv->regmap))
- return PTR_ERR(priv->regmap);
-
- priv->gpiod_pwdn = devm_gpiod_get_optional(&client->dev, "enable",
- GPIOD_OUT_HIGH);
- if (IS_ERR(priv->gpiod_pwdn))
- return PTR_ERR(priv->gpiod_pwdn);
-
- gpiod_set_consumer_name(priv->gpiod_pwdn, "max96712-pwdn");
- gpiod_set_value_cansleep(priv->gpiod_pwdn, 1);
-
- if (priv->gpiod_pwdn)
- usleep_range(4000, 5000);
-
- max96712_reset(priv);
-
- ret = max96712_parse_dt(priv);
- if (ret)
- return ret;
-
- max96712_mipi_configure(priv);
-
- return max96712_v4l2_register(priv);
-}
-
-static void max96712_remove(struct i2c_client *client)
-{
- struct v4l2_subdev *sd = i2c_get_clientdata(client);
- struct max96712_priv *priv = container_of(sd, struct max96712_priv, sd);
-
- v4l2_async_unregister_subdev(&priv->sd);
-
- gpiod_set_value_cansleep(priv->gpiod_pwdn, 0);
-}
-
-static const struct max96712_info max96712_info_max96712 = {
- .dpllfreq = 1000,
- .have_debug_extra = true,
-};
-
-static const struct max96712_info max96712_info_max96724 = {
- .dpllfreq = 1200,
-};
-
-static const struct of_device_id max96712_of_table[] = {
- { .compatible = "maxim,max96712", .data = &max96712_info_max96712 },
- { .compatible = "maxim,max96724", .data = &max96712_info_max96724 },
- { /* sentinel */ }
-};
-MODULE_DEVICE_TABLE(of, max96712_of_table);
-
-static struct i2c_driver max96712_i2c_driver = {
- .driver = {
- .name = "max96712",
- .of_match_table = of_match_ptr(max96712_of_table),
- },
- .probe = max96712_probe,
- .remove = max96712_remove,
-};
-
-module_i2c_driver(max96712_i2c_driver);
-
-MODULE_DESCRIPTION("Maxim MAX96712 Quad GMSL2 Deserializer Driver");
-MODULE_AUTHOR("Niklas Söderlund <niklas.soderlund@ragnatech.se>");
-MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH v13 22/22] media: i2c: remove MAX96714 driver
From: Dumitru Ceclan via B4 Relay @ 2026-06-04 14:14 UTC (permalink / raw)
To: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
Laurent Pinchart, Julien Massot, Rob Herring,
Niklas Söderlund, Greg Kroah-Hartman
Cc: mitrutzceclan, linux-media, linux-kernel, devicetree,
linux-staging, linux-gpio, Niklas Söderlund, Martin Hecht,
Cosmin Tanislav
In-Reply-To: <20260604-gmsl2-3_serdes-v13-0-9d8a4919983b@analog.com>
From: Cosmin Tanislav <demonsingur@gmail.com>
Remove the MAX96714 driver. Its functionality has been moved to the
MAX9296A driver which makes use of the Maxim GMSL2/3 serializer
framework.
Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Reviewed-by: Julien Massot <julien.massot@collabora.com>
---
MAINTAINERS | 1 -
drivers/media/i2c/Kconfig | 17 -
drivers/media/i2c/Makefile | 1 -
drivers/media/i2c/max96714.c | 1017 ------------------------------------------
4 files changed, 1036 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index e16cb6f33c85..2afdeee5a946 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15739,7 +15739,6 @@ M: Julien Massot <julien.massot@collabora.com>
L: linux-media@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml
-F: drivers/media/i2c/max96714.c
MAX96717 GMSL2 SERIALIZER DRIVER
M: Julien Massot <julien.massot@collabora.com>
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 8108978fbfa2..c777e34db064 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1752,23 +1752,6 @@ config VIDEO_DS90UB960
Device driver for the Texas Instruments DS90UB954, DS90UB960
FPD-Link III Deserializers and DS90UB9702 FPD-Link IV Deserializer.
-config VIDEO_MAX96714
- tristate "Maxim MAX96714 GMSL2 deserializer"
- depends on OF && I2C && VIDEO_DEV
- select I2C_MUX
- select MEDIA_CONTROLLER
- select GPIOLIB
- select V4L2_CCI_I2C
- select V4L2_FWNODE
- select VIDEO_V4L2_SUBDEV_API
- help
- Device driver for the Maxim MAX96714 GMSL2 Deserializer.
- MAX96714 deserializers convert a GMSL2 input to MIPI CSI-2
- output.
-
- To compile this driver as a module, choose M here: the
- module will be called max96714.
-
source "drivers/media/i2c/maxim-serdes/Kconfig"
endmenu
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index b49832e61f39..3b0c180077be 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -70,7 +70,6 @@ obj-$(CONFIG_VIDEO_LT6911UXE) += lt6911uxe.o
obj-$(CONFIG_VIDEO_M52790) += m52790.o
obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
-obj-$(CONFIG_VIDEO_MAX96714) += max96714.o
obj-$(CONFIG_VIDEO_MAXIM_SERDES) += maxim-serdes/
obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o
obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
diff --git a/drivers/media/i2c/max96714.c b/drivers/media/i2c/max96714.c
deleted file mode 100644
index e3e625e6f11a..000000000000
--- a/drivers/media/i2c/max96714.c
+++ /dev/null
@@ -1,1017 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Maxim GMSL2 Deserializer Driver
- *
- * Copyright (C) 2024 Collabora Ltd.
- */
-
-#include <linux/bitfield.h>
-#include <linux/bitops.h>
-#include <linux/gpio/consumer.h>
-#include <linux/i2c.h>
-#include <linux/i2c-mux.h>
-#include <linux/module.h>
-#include <linux/property.h>
-#include <linux/regmap.h>
-#include <linux/regulator/consumer.h>
-
-#include <media/v4l2-cci.h>
-#include <media/v4l2-ctrls.h>
-#include <media/v4l2-fwnode.h>
-#include <media/v4l2-subdev.h>
-
-#define MAX96714_DEVICE_ID 0xc9
-#define MAX96714F_DEVICE_ID 0xca
-#define MAX96714_NPORTS 2
-#define MAX96714_PAD_SINK 0
-#define MAX96714_PAD_SOURCE 1
-#define MAX96714_CSI_NLANES 4
-
-/* DEV */
-#define MAX96714_REG13 CCI_REG8(0x0d)
-#define MAX96714_DEV_REV CCI_REG8(0x0e)
-#define MAX96714_DEV_REV_MASK GENMASK(3, 0)
-#define MAX96714_LINK_LOCK CCI_REG8(0x13)
-#define MAX96714_LINK_LOCK_BIT BIT(3)
-#define MAX96714_IO_CHK0 CCI_REG8(0x38)
-#define MAX96714_PATTERN_CLK_FREQ GENMASK(1, 0)
-/* VID_RX */
-#define MAX96714_VIDEO_RX8 CCI_REG8(0x11a)
-#define MAX96714_VID_LOCK BIT(6)
-
-/* VRX_PATGEN_0 */
-#define MAX96714_PATGEN_0 CCI_REG8(0x240)
-#define MAX96714_PATGEN_1 CCI_REG8(0x241)
-#define MAX96714_PATGEN_MODE GENMASK(5, 4)
-#define MAX96714_PATGEN_VS_DLY CCI_REG24(0x242)
-#define MAX96714_PATGEN_VS_HIGH CCI_REG24(0x245)
-#define MAX96714_PATGEN_VS_LOW CCI_REG24(0x248)
-#define MAX96714_PATGEN_V2H CCI_REG24(0x24b)
-#define MAX96714_PATGEN_HS_HIGH CCI_REG16(0x24e)
-#define MAX96714_PATGEN_HS_LOW CCI_REG16(0x250)
-#define MAX96714_PATGEN_HS_CNT CCI_REG16(0x252)
-#define MAX96714_PATGEN_V2D CCI_REG24(0x254)
-#define MAX96714_PATGEN_DE_HIGH CCI_REG16(0x257)
-#define MAX96714_PATGEN_DE_LOW CCI_REG16(0x259)
-#define MAX96714_PATGEN_DE_CNT CCI_REG16(0x25b)
-#define MAX96714_PATGEN_GRAD_INC CCI_REG8(0x25d)
-#define MAX96714_PATGEN_CHKB_COLOR_A CCI_REG24(0x25e)
-#define MAX96714_PATGEN_CHKB_COLOR_B CCI_REG24(0x261)
-#define MAX96714_PATGEN_CHKB_RPT_CNT_A CCI_REG8(0x264)
-#define MAX96714_PATGEN_CHKB_RPT_CNT_B CCI_REG8(0x265)
-#define MAX96714_PATGEN_CHKB_ALT CCI_REG8(0x266)
-/* BACKTOP */
-#define MAX96714_BACKTOP25 CCI_REG8(0x320)
-#define CSI_DPLL_FREQ_MASK GENMASK(4, 0)
-
-/* MIPI_PHY */
-#define MAX96714_MIPI_PHY0 CCI_REG8(0x330)
-#define MAX96714_FORCE_CSI_OUT BIT(7)
-#define MAX96714_MIPI_STDBY_N CCI_REG8(0x332)
-#define MAX96714_MIPI_STDBY_MASK GENMASK(5, 4)
-#define MAX96714_MIPI_LANE_MAP CCI_REG8(0x333)
-#define MAX96714_MIPI_POLARITY CCI_REG8(0x335)
-#define MAX96714_MIPI_POLARITY_MASK GENMASK(5, 0)
-
-/* MIPI_TX */
-#define MAX96714_MIPI_LANE_CNT CCI_REG8(0x44a)
-#define MAX96714_CSI2_LANE_CNT_MASK GENMASK(7, 6)
-#define MAX96714_MIPI_TX52 CCI_REG8(0x474)
-#define MAX96714_TUN_EN BIT(0)
-
-#define MHZ(v) ((u32)((v) * 1000000U))
-
-enum max96714_vpg_mode {
- MAX96714_VPG_DISABLED = 0,
- MAX96714_VPG_CHECKERBOARD = 1,
- MAX96714_VPG_GRADIENT = 2,
-};
-
-struct max96714_rxport {
- struct {
- struct v4l2_subdev *sd;
- u16 pad;
- struct fwnode_handle *ep_fwnode;
- } source;
- struct regulator *poc;
-};
-
-struct max96714_txport {
- struct v4l2_fwnode_endpoint vep;
-};
-
-struct max96714_priv {
- struct i2c_client *client;
- struct regmap *regmap;
- struct gpio_desc *pd_gpio;
- struct max96714_rxport rxport;
- struct i2c_mux_core *mux;
- u64 enabled_source_streams;
- struct v4l2_subdev sd;
- struct media_pad pads[MAX96714_NPORTS];
- struct v4l2_mbus_config_mipi_csi2 mipi_csi2;
- struct v4l2_ctrl_handler ctrl_handler;
- struct v4l2_async_notifier notifier;
- s64 tx_link_freq;
- enum max96714_vpg_mode pattern;
-};
-
-static inline struct max96714_priv *sd_to_max96714(struct v4l2_subdev *sd)
-{
- return container_of(sd, struct max96714_priv, sd);
-}
-
-static int max96714_enable_tx_port(struct max96714_priv *priv)
-{
- return cci_update_bits(priv->regmap, MAX96714_MIPI_STDBY_N,
- MAX96714_MIPI_STDBY_MASK,
- MAX96714_MIPI_STDBY_MASK, NULL);
-}
-
-static int max96714_disable_tx_port(struct max96714_priv *priv)
-{
- return cci_update_bits(priv->regmap, MAX96714_MIPI_STDBY_N,
- MAX96714_MIPI_STDBY_MASK, 0, NULL);
-}
-
-static bool max96714_tx_port_enabled(struct max96714_priv *priv)
-{
- u64 val;
-
- cci_read(priv->regmap, MAX96714_MIPI_STDBY_N, &val, NULL);
-
- return val & MAX96714_MIPI_STDBY_MASK;
-}
-
-static int max96714_apply_patgen_timing(struct max96714_priv *priv,
- struct v4l2_subdev_state *state)
-{
- struct v4l2_mbus_framefmt *fmt =
- v4l2_subdev_state_get_format(state, MAX96714_PAD_SOURCE);
- const u32 h_active = fmt->width;
- const u32 h_fp = 88;
- const u32 h_sw = 44;
- const u32 h_bp = 148;
- u32 h_tot;
- const u32 v_active = fmt->height;
- const u32 v_fp = 4;
- const u32 v_sw = 5;
- const u32 v_bp = 36;
- u32 v_tot;
- int ret = 0;
-
- h_tot = h_active + h_fp + h_sw + h_bp;
- v_tot = v_active + v_fp + v_sw + v_bp;
-
- /* 75 Mhz pixel clock */
- cci_update_bits(priv->regmap, MAX96714_IO_CHK0,
- MAX96714_PATTERN_CLK_FREQ, 1, &ret);
-
- dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height,
- fmt->width);
-
- cci_write(priv->regmap, MAX96714_PATGEN_VS_DLY, 0, &ret);
- cci_write(priv->regmap, MAX96714_PATGEN_VS_HIGH, v_sw * h_tot, &ret);
- cci_write(priv->regmap, MAX96714_PATGEN_VS_LOW,
- (v_active + v_fp + v_bp) * h_tot, &ret);
- cci_write(priv->regmap, MAX96714_PATGEN_HS_HIGH, h_sw, &ret);
- cci_write(priv->regmap, MAX96714_PATGEN_HS_LOW, h_active + h_fp + h_bp,
- &ret);
- cci_write(priv->regmap, MAX96714_PATGEN_V2D,
- h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret);
- cci_write(priv->regmap, MAX96714_PATGEN_HS_CNT, v_tot, &ret);
- cci_write(priv->regmap, MAX96714_PATGEN_DE_HIGH, h_active, &ret);
- cci_write(priv->regmap, MAX96714_PATGEN_DE_LOW, h_fp + h_sw + h_bp,
- &ret);
- cci_write(priv->regmap, MAX96714_PATGEN_DE_CNT, v_active, &ret);
- /* B G R */
- cci_write(priv->regmap, MAX96714_PATGEN_CHKB_COLOR_A, 0xfecc00, &ret);
- /* B G R */
- cci_write(priv->regmap, MAX96714_PATGEN_CHKB_COLOR_B, 0x006aa7, &ret);
- cci_write(priv->regmap, MAX96714_PATGEN_CHKB_RPT_CNT_A, 0x3c, &ret);
- cci_write(priv->regmap, MAX96714_PATGEN_CHKB_RPT_CNT_B, 0x3c, &ret);
- cci_write(priv->regmap, MAX96714_PATGEN_CHKB_ALT, 0x3c, &ret);
- cci_write(priv->regmap, MAX96714_PATGEN_GRAD_INC, 0x10, &ret);
-
- return ret;
-}
-
-static int max96714_apply_patgen(struct max96714_priv *priv,
- struct v4l2_subdev_state *state)
-{
- unsigned int val;
- int ret = 0;
-
- if (priv->pattern)
- ret = max96714_apply_patgen_timing(priv, state);
-
- cci_write(priv->regmap, MAX96714_PATGEN_0, priv->pattern ? 0xfb : 0,
- &ret);
-
- val = FIELD_PREP(MAX96714_PATGEN_MODE, priv->pattern);
- cci_update_bits(priv->regmap, MAX96714_PATGEN_1, MAX96714_PATGEN_MODE,
- val, &ret);
- return ret;
-}
-
-static int max96714_s_ctrl(struct v4l2_ctrl *ctrl)
-{
- struct max96714_priv *priv =
- container_of(ctrl->handler, struct max96714_priv, ctrl_handler);
- int ret;
-
- switch (ctrl->id) {
- case V4L2_CID_TEST_PATTERN:
- if (priv->enabled_source_streams)
- return -EBUSY;
- priv->pattern = ctrl->val;
- break;
- default:
- return -EINVAL;
- }
-
- ret = cci_update_bits(priv->regmap, MAX96714_MIPI_PHY0,
- MAX96714_FORCE_CSI_OUT,
- priv->pattern ? MAX96714_FORCE_CSI_OUT : 0, NULL);
-
- /* Pattern generator doesn't work with tunnel mode */
- return cci_update_bits(priv->regmap, MAX96714_MIPI_TX52,
- MAX96714_TUN_EN,
- priv->pattern ? 0 : MAX96714_TUN_EN, &ret);
-}
-
-static const char * const max96714_test_pattern[] = {
- "Disabled",
- "Checkerboard",
- "Gradient"
-};
-
-static const struct v4l2_ctrl_ops max96714_ctrl_ops = {
- .s_ctrl = max96714_s_ctrl,
-};
-
-static int max96714_enable_streams(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state,
- u32 source_pad, u64 streams_mask)
-{
- struct max96714_priv *priv = sd_to_max96714(sd);
- u64 sink_streams;
- int ret;
-
- if (!priv->enabled_source_streams)
- max96714_enable_tx_port(priv);
-
- ret = max96714_apply_patgen(priv, state);
- if (ret)
- goto err;
-
- if (!priv->pattern) {
- if (!priv->rxport.source.sd) {
- ret = -ENODEV;
- goto err;
- }
-
- sink_streams =
- v4l2_subdev_state_xlate_streams(state,
- MAX96714_PAD_SOURCE,
- MAX96714_PAD_SINK,
- &streams_mask);
-
- ret = v4l2_subdev_enable_streams(priv->rxport.source.sd,
- priv->rxport.source.pad,
- sink_streams);
- if (ret)
- goto err;
- }
-
- priv->enabled_source_streams |= streams_mask;
-
- return 0;
-
-err:
- if (!priv->enabled_source_streams)
- max96714_disable_tx_port(priv);
-
- return ret;
-}
-
-static int max96714_disable_streams(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state,
- u32 source_pad, u64 streams_mask)
-{
- struct max96714_priv *priv = sd_to_max96714(sd);
- u64 sink_streams;
-
- if (!priv->pattern) {
- int ret;
-
- sink_streams =
- v4l2_subdev_state_xlate_streams(state,
- MAX96714_PAD_SOURCE,
- MAX96714_PAD_SINK,
- &streams_mask);
-
- ret = v4l2_subdev_disable_streams(priv->rxport.source.sd,
- priv->rxport.source.pad,
- sink_streams);
- if (ret)
- return ret;
- }
-
- priv->enabled_source_streams &= ~streams_mask;
-
- if (!priv->enabled_source_streams)
- max96714_disable_tx_port(priv);
-
- return 0;
-}
-
-static int max96714_set_fmt(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state,
- struct v4l2_subdev_format *format)
-{
- struct max96714_priv *priv = sd_to_max96714(sd);
- struct v4l2_mbus_framefmt *fmt;
-
- if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
- priv->enabled_source_streams)
- return -EBUSY;
-
- /* No transcoding, source and sink formats must match. */
- if (format->pad == MAX96714_PAD_SOURCE)
- return v4l2_subdev_get_fmt(sd, state, format);
-
- fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
- if (!fmt)
- return -EINVAL;
-
- *fmt = format->format;
-
- fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
- format->stream);
- if (!fmt)
- return -EINVAL;
-
- *fmt = format->format;
-
- return 0;
-}
-
-static int _max96714_set_routing(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state,
- enum v4l2_subdev_format_whence which,
- struct v4l2_subdev_krouting *routing)
-{
- static const struct v4l2_mbus_framefmt format = {
- .width = 1280,
- .height = 1080,
- .code = MEDIA_BUS_FMT_Y8_1X8,
- .field = V4L2_FIELD_NONE,
- };
- int ret;
-
- ret = v4l2_subdev_routing_validate(sd, routing,
- V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
- if (ret)
- return ret;
-
- return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
-}
-
-static int max96714_set_routing(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state,
- enum v4l2_subdev_format_whence which,
- struct v4l2_subdev_krouting *routing)
-{
- struct max96714_priv *priv = sd_to_max96714(sd);
-
- if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams)
- return -EBUSY;
-
- return _max96714_set_routing(sd, state, which, routing);
-}
-
-static int max96714_init_state(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state)
-{
- struct v4l2_subdev_route routes[] = {
- {
- .sink_pad = MAX96714_PAD_SINK,
- .sink_stream = 0,
- .source_pad = MAX96714_PAD_SOURCE,
- .source_stream = 0,
- .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
- }
- };
- struct v4l2_subdev_krouting routing = {
- .num_routes = ARRAY_SIZE(routes),
- .routes = routes,
- };
-
- return _max96714_set_routing(sd, state, V4L2_SUBDEV_FORMAT_ACTIVE,
- &routing);
-}
-
-static const struct v4l2_subdev_pad_ops max96714_pad_ops = {
- .enable_streams = max96714_enable_streams,
- .disable_streams = max96714_disable_streams,
-
- .set_routing = max96714_set_routing,
- .get_fmt = v4l2_subdev_get_fmt,
- .set_fmt = max96714_set_fmt,
-};
-
-static bool max96714_link_locked(struct max96714_priv *priv)
-{
- u64 val = 0;
-
- cci_read(priv->regmap, MAX96714_LINK_LOCK, &val, NULL);
-
- return val & MAX96714_LINK_LOCK_BIT;
-}
-
-static void max96714_link_status(struct max96714_priv *priv)
-{
- struct device *dev = &priv->client->dev;
-
- dev_info(dev, "Link locked:%d\n", max96714_link_locked(priv));
-}
-
-static bool max96714_pipe_locked(struct max96714_priv *priv)
-{
- u64 val;
-
- cci_read(priv->regmap, MAX96714_VIDEO_RX8, &val, NULL);
-
- return val & MAX96714_VID_LOCK;
-}
-
-static void max96714_pipe_status(struct max96714_priv *priv)
-{
- struct device *dev = &priv->client->dev;
-
- dev_info(dev, "Pipe vidlock:%d\n", max96714_pipe_locked(priv));
-}
-
-static void max96714_csi_status(struct max96714_priv *priv)
-{
- struct device *dev = &priv->client->dev;
- u64 freq = 0;
-
- cci_read(priv->regmap, MAX96714_BACKTOP25, &freq, NULL);
- freq = FIELD_GET(CSI_DPLL_FREQ_MASK, freq);
-
- dev_info(dev, "CSI controller DPLL freq:%u00MHz CSIPHY enabled:%d\n",
- (u8)freq, max96714_tx_port_enabled(priv));
-}
-
-static int max96714_log_status(struct v4l2_subdev *sd)
-{
- struct max96714_priv *priv = sd_to_max96714(sd);
- struct device *dev = &priv->client->dev;
-
- dev_info(dev, "Deserializer: max96714\n");
-
- max96714_link_status(priv);
- max96714_pipe_status(priv);
- max96714_csi_status(priv);
-
- return 0;
-}
-
-static const struct v4l2_subdev_core_ops max96714_subdev_core_ops = {
- .log_status = max96714_log_status,
-};
-
-static const struct v4l2_subdev_video_ops max96714_video_ops = {
- .s_stream = v4l2_subdev_s_stream_helper,
-};
-
-static const struct v4l2_subdev_internal_ops max96714_internal_ops = {
- .init_state = max96714_init_state,
-};
-
-static const struct v4l2_subdev_ops max96714_subdev_ops = {
- .video = &max96714_video_ops,
- .core = &max96714_subdev_core_ops,
- .pad = &max96714_pad_ops,
-};
-
-static const struct media_entity_operations max96714_entity_ops = {
- .link_validate = v4l2_subdev_link_validate,
-};
-
-static int max96714_notify_bound(struct v4l2_async_notifier *notifier,
- struct v4l2_subdev *subdev,
- struct v4l2_async_connection *asd)
-{
- struct max96714_priv *priv = sd_to_max96714(notifier->sd);
- struct device *dev = &priv->client->dev;
- int ret;
-
- ret = media_entity_get_fwnode_pad(&subdev->entity,
- priv->rxport.source.ep_fwnode,
- MEDIA_PAD_FL_SOURCE);
- if (ret < 0) {
- dev_err(dev, "Failed to find pad for %s\n", subdev->name);
- return ret;
- }
-
- priv->rxport.source.sd = subdev;
- priv->rxport.source.pad = ret;
-
- ret = media_create_pad_link(&priv->rxport.source.sd->entity,
- priv->rxport.source.pad, &priv->sd.entity,
- MAX96714_PAD_SINK,
- MEDIA_LNK_FL_ENABLED |
- MEDIA_LNK_FL_IMMUTABLE);
- if (ret) {
- dev_err(dev, "Unable to link %s:%u -> %s:%u\n",
- priv->rxport.source.sd->name, priv->rxport.source.pad,
- priv->sd.name, MAX96714_PAD_SINK);
- return ret;
- }
-
- return 0;
-}
-
-static const struct v4l2_async_notifier_operations max96714_notify_ops = {
- .bound = max96714_notify_bound,
-};
-
-static int max96714_v4l2_notifier_register(struct max96714_priv *priv)
-{
- struct device *dev = &priv->client->dev;
- struct max96714_rxport *rxport = &priv->rxport;
- struct v4l2_async_connection *asd;
- int ret;
-
- if (!rxport->source.ep_fwnode)
- return 0;
-
- v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd);
-
- asd = v4l2_async_nf_add_fwnode(&priv->notifier,
- rxport->source.ep_fwnode,
- struct v4l2_async_connection);
- if (IS_ERR(asd)) {
- dev_err(dev, "Failed to add subdev: %pe", asd);
- v4l2_async_nf_cleanup(&priv->notifier);
- return PTR_ERR(asd);
- }
-
- priv->notifier.ops = &max96714_notify_ops;
-
- ret = v4l2_async_nf_register(&priv->notifier);
- if (ret) {
- dev_err(dev, "Failed to register subdev_notifier");
- v4l2_async_nf_cleanup(&priv->notifier);
- return ret;
- }
-
- return 0;
-}
-
-static int max96714_create_subdev(struct max96714_priv *priv)
-{
- struct device *dev = &priv->client->dev;
- int ret;
-
- v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96714_subdev_ops);
- priv->sd.internal_ops = &max96714_internal_ops;
-
- v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
- priv->sd.ctrl_handler = &priv->ctrl_handler;
-
- v4l2_ctrl_new_int_menu(&priv->ctrl_handler, NULL, V4L2_CID_LINK_FREQ,
- 0, 0, &priv->tx_link_freq);
- v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
- &max96714_ctrl_ops,
- V4L2_CID_TEST_PATTERN,
- ARRAY_SIZE(max96714_test_pattern) - 1,
- 0, 0, max96714_test_pattern);
- if (priv->ctrl_handler.error) {
- ret = priv->ctrl_handler.error;
- goto err_free_ctrl;
- }
-
- priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
- priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
- priv->sd.entity.ops = &max96714_entity_ops;
-
- priv->pads[MAX96714_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
- priv->pads[MAX96714_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
-
- ret = media_entity_pads_init(&priv->sd.entity,
- MAX96714_NPORTS,
- priv->pads);
- if (ret)
- goto err_free_ctrl;
-
- priv->sd.state_lock = priv->sd.ctrl_handler->lock;
-
- ret = v4l2_subdev_init_finalize(&priv->sd);
- if (ret)
- goto err_entity_cleanup;
-
- ret = max96714_v4l2_notifier_register(priv);
- if (ret) {
- dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret);
- goto err_subdev_cleanup;
- }
-
- ret = v4l2_async_register_subdev(&priv->sd);
- if (ret) {
- dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret);
- goto err_unreg_notif;
- }
-
- return 0;
-
-err_unreg_notif:
- v4l2_async_nf_unregister(&priv->notifier);
- v4l2_async_nf_cleanup(&priv->notifier);
-err_subdev_cleanup:
- v4l2_subdev_cleanup(&priv->sd);
-err_entity_cleanup:
- media_entity_cleanup(&priv->sd.entity);
-err_free_ctrl:
- v4l2_ctrl_handler_free(&priv->ctrl_handler);
-
- return ret;
-};
-
-static void max96714_destroy_subdev(struct max96714_priv *priv)
-{
- v4l2_async_nf_unregister(&priv->notifier);
- v4l2_async_nf_cleanup(&priv->notifier);
- v4l2_async_unregister_subdev(&priv->sd);
-
- v4l2_subdev_cleanup(&priv->sd);
-
- media_entity_cleanup(&priv->sd.entity);
- v4l2_ctrl_handler_free(&priv->ctrl_handler);
-}
-
-static int max96714_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
-{
- return 0;
-}
-
-static int max96714_i2c_mux_init(struct max96714_priv *priv)
-{
- priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
- 1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
- max96714_i2c_mux_select, NULL);
- if (!priv->mux)
- return -ENOMEM;
-
- return i2c_mux_add_adapter(priv->mux, 0, 0);
-}
-
-static int max96714_init_tx_port(struct max96714_priv *priv)
-{
- struct v4l2_mbus_config_mipi_csi2 *mipi;
- unsigned long lanes_used = 0;
- unsigned int val, lane;
- int ret;
-
- ret = max96714_disable_tx_port(priv);
-
- mipi = &priv->mipi_csi2;
- val = div_u64(priv->tx_link_freq * 2, MHZ(100));
-
- cci_update_bits(priv->regmap, MAX96714_BACKTOP25,
- CSI_DPLL_FREQ_MASK, val, &ret);
-
- val = FIELD_PREP(MAX96714_CSI2_LANE_CNT_MASK, mipi->num_data_lanes - 1);
- cci_update_bits(priv->regmap, MAX96714_MIPI_LANE_CNT,
- MAX96714_CSI2_LANE_CNT_MASK, val, &ret);
-
- /* lanes polarity */
- val = 0;
- for (lane = 0; lane < mipi->num_data_lanes + 1; lane++) {
- if (!mipi->lane_polarities[lane])
- continue;
- if (lane == 0)
- /* clock lane */
- val |= BIT(5);
- else if (lane < 3)
- /* Lane D0 and D1 */
- val |= BIT(lane - 1);
- else
- /* D2 and D3 */
- val |= BIT(lane);
- }
-
- cci_update_bits(priv->regmap, MAX96714_MIPI_POLARITY,
- MAX96714_MIPI_POLARITY_MASK, val, &ret);
-
- /* lanes mapping */
- val = 0;
- for (lane = 0; lane < mipi->num_data_lanes; lane++) {
- val |= (mipi->data_lanes[lane] - 1) << (lane * 2);
- lanes_used |= BIT(mipi->data_lanes[lane] - 1);
- }
-
- /*
- * Unused lanes need to be mapped as well to not have
- * the same lanes mapped twice.
- */
- for (; lane < MAX96714_CSI_NLANES; lane++) {
- unsigned int idx = find_first_zero_bit(&lanes_used,
- MAX96714_CSI_NLANES);
-
- val |= idx << (lane * 2);
- lanes_used |= BIT(idx);
- }
-
- return cci_write(priv->regmap, MAX96714_MIPI_LANE_MAP, val, &ret);
-}
-
-static int max96714_rxport_enable_poc(struct max96714_priv *priv)
-{
- struct max96714_rxport *rxport = &priv->rxport;
-
- if (!rxport->poc)
- return 0;
-
- return regulator_enable(rxport->poc);
-}
-
-static int max96714_rxport_disable_poc(struct max96714_priv *priv)
-{
- struct max96714_rxport *rxport = &priv->rxport;
-
- if (!rxport->poc)
- return 0;
-
- return regulator_disable(rxport->poc);
-}
-
-static int max96714_parse_dt_txport(struct max96714_priv *priv)
-{
- struct device *dev = &priv->client->dev;
- struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
- struct fwnode_handle *ep_fwnode;
- u32 num_data_lanes;
- int ret;
-
- ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
- MAX96714_PAD_SOURCE, 0, 0);
- if (!ep_fwnode)
- return -EINVAL;
-
- ret = v4l2_fwnode_endpoint_alloc_parse(ep_fwnode, &vep);
- fwnode_handle_put(ep_fwnode);
- if (ret) {
- dev_err(dev, "tx: failed to parse endpoint data\n");
- return -EINVAL;
- }
-
- if (vep.nr_of_link_frequencies != 1) {
- ret = -EINVAL;
- goto err_free_vep;
- }
-
- priv->tx_link_freq = vep.link_frequencies[0];
- /* Min 50MHz, Max 1250MHz, 50MHz step */
- if (priv->tx_link_freq < MHZ(50) || priv->tx_link_freq > MHZ(1250) ||
- (u32)priv->tx_link_freq % MHZ(50)) {
- dev_err(dev, "tx: invalid link frequency\n");
- ret = -EINVAL;
- goto err_free_vep;
- }
-
- num_data_lanes = vep.bus.mipi_csi2.num_data_lanes;
- if (num_data_lanes < 1 || num_data_lanes > MAX96714_CSI_NLANES) {
- dev_err(dev,
- "tx: invalid number of data lanes must be 1 to 4\n");
- ret = -EINVAL;
- goto err_free_vep;
- }
-
- priv->mipi_csi2 = vep.bus.mipi_csi2;
-
-err_free_vep:
- v4l2_fwnode_endpoint_free(&vep);
-
- return ret;
-}
-
-static int max96714_parse_dt_rxport(struct max96714_priv *priv)
-{
- static const char *poc_name = "port0-poc";
- struct max96714_rxport *rxport = &priv->rxport;
- struct device *dev = &priv->client->dev;
- struct fwnode_handle *ep_fwnode;
- int ret;
-
- ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
- MAX96714_PAD_SINK, 0, 0);
- if (!ep_fwnode)
- return -ENOENT;
-
- rxport->source.ep_fwnode = fwnode_graph_get_remote_endpoint(ep_fwnode);
- fwnode_handle_put(ep_fwnode);
-
- if (!rxport->source.ep_fwnode) {
- dev_err(dev, "rx: no remote endpoint\n");
- return -EINVAL;
- }
-
- rxport->poc = devm_regulator_get_optional(dev, poc_name);
- if (IS_ERR(rxport->poc)) {
- ret = PTR_ERR(rxport->poc);
- if (ret == -ENODEV) {
- rxport->poc = NULL;
- } else {
- dev_err(dev, "rx: failed to get POC supply: %d\n", ret);
- goto err_put_source_ep_fwnode;
- }
- }
-
- return 0;
-
-err_put_source_ep_fwnode:
- fwnode_handle_put(rxport->source.ep_fwnode);
- return ret;
-}
-
-static int max96714_parse_dt(struct max96714_priv *priv)
-{
- int ret;
-
- ret = max96714_parse_dt_txport(priv);
- if (ret)
- return ret;
-
- ret = max96714_parse_dt_rxport(priv);
- /*
- * The deserializer can create a test pattern even if the
- * rx port is not connected to a serializer.
- */
- if (ret && ret == -ENOENT)
- ret = 0;
-
- return ret;
-}
-
-static int max96714_enable_core_hw(struct max96714_priv *priv)
-{
- struct device *dev = &priv->client->dev;
- u64 val;
- int ret;
-
- if (priv->pd_gpio) {
- /* wait min 2 ms for reset to complete */
- gpiod_set_value_cansleep(priv->pd_gpio, 1);
- fsleep(2000);
- gpiod_set_value_cansleep(priv->pd_gpio, 0);
- /* wait min 2 ms for power up to finish */
- fsleep(2000);
- }
-
- ret = cci_read(priv->regmap, MAX96714_REG13, &val, NULL);
- if (ret) {
- dev_err_probe(dev, ret, "Cannot read first register, abort\n");
- goto err_pd_gpio;
- }
-
- if (val != MAX96714_DEVICE_ID && val != MAX96714F_DEVICE_ID) {
- dev_err(dev, "Unsupported device id expected %x got %x\n",
- MAX96714F_DEVICE_ID, (u8)val);
- ret = -EOPNOTSUPP;
- goto err_pd_gpio;
- }
-
- ret = cci_read(priv->regmap, MAX96714_DEV_REV, &val, NULL);
- if (ret)
- goto err_pd_gpio;
-
- dev_dbg(dev, "Found %x (rev %lx)\n", MAX96714F_DEVICE_ID,
- (u8)val & MAX96714_DEV_REV_MASK);
-
- ret = cci_read(priv->regmap, MAX96714_MIPI_TX52, &val, NULL);
- if (ret)
- goto err_pd_gpio;
-
- if (!(val & MAX96714_TUN_EN)) {
- dev_err(dev, "Only supporting tunnel mode");
- ret = -EOPNOTSUPP;
- goto err_pd_gpio;
- }
-
- return 0;
-
-err_pd_gpio:
- gpiod_set_value_cansleep(priv->pd_gpio, 1);
- return ret;
-}
-
-static void max96714_disable_core_hw(struct max96714_priv *priv)
-{
- gpiod_set_value_cansleep(priv->pd_gpio, 1);
-}
-
-static int max96714_get_hw_resources(struct max96714_priv *priv)
-{
- struct device *dev = &priv->client->dev;
-
- priv->regmap = devm_cci_regmap_init_i2c(priv->client, 16);
- if (IS_ERR(priv->regmap))
- return PTR_ERR(priv->regmap);
-
- priv->pd_gpio =
- devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH);
- if (IS_ERR(priv->pd_gpio))
- return dev_err_probe(dev, PTR_ERR(priv->pd_gpio),
- "Cannot get powerdown GPIO\n");
- return 0;
-}
-
-static int max96714_probe(struct i2c_client *client)
-{
- struct device *dev = &client->dev;
- struct max96714_priv *priv;
- int ret;
-
- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- priv->client = client;
-
- ret = max96714_get_hw_resources(priv);
- if (ret)
- return ret;
-
- ret = max96714_enable_core_hw(priv);
- if (ret)
- return ret;
-
- ret = max96714_parse_dt(priv);
- if (ret)
- goto err_disable_core_hw;
-
- max96714_init_tx_port(priv);
-
- ret = max96714_rxport_enable_poc(priv);
- if (ret)
- goto err_free_ports;
-
- ret = max96714_i2c_mux_init(priv);
- if (ret)
- goto err_disable_poc;
-
- ret = max96714_create_subdev(priv);
- if (ret)
- goto err_del_mux;
-
- return 0;
-
-err_del_mux:
- i2c_mux_del_adapters(priv->mux);
-err_disable_poc:
- max96714_rxport_disable_poc(priv);
-err_free_ports:
- fwnode_handle_put(priv->rxport.source.ep_fwnode);
-err_disable_core_hw:
- max96714_disable_core_hw(priv);
-
- return ret;
-}
-
-static void max96714_remove(struct i2c_client *client)
-{
- struct v4l2_subdev *sd = i2c_get_clientdata(client);
- struct max96714_priv *priv = sd_to_max96714(sd);
-
- max96714_destroy_subdev(priv);
- i2c_mux_del_adapters(priv->mux);
- max96714_rxport_disable_poc(priv);
- fwnode_handle_put(priv->rxport.source.ep_fwnode);
- max96714_disable_core_hw(priv);
- gpiod_set_value_cansleep(priv->pd_gpio, 1);
-}
-
-static const struct of_device_id max96714_of_ids[] = {
- { .compatible = "maxim,max96714f" },
- { }
-};
-MODULE_DEVICE_TABLE(of, max96714_of_ids);
-
-static struct i2c_driver max96714_i2c_driver = {
- .driver = {
- .name = "max96714",
- .of_match_table = max96714_of_ids,
- },
- .probe = max96714_probe,
- .remove = max96714_remove,
-};
-
-module_i2c_driver(max96714_i2c_driver);
-
-MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("Maxim Integrated GMSL2 Deserializers Driver");
-MODULE_AUTHOR("Julien Massot <julien.massot@collabora.com>");
--
2.53.0
^ permalink raw reply related
* [PATCH v13 19/22] arm64: defconfig: disable deprecated MAX96712 driver
From: Dumitru Ceclan via B4 Relay @ 2026-06-04 14:14 UTC (permalink / raw)
To: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
Laurent Pinchart, Julien Massot, Rob Herring,
Niklas Söderlund, Greg Kroah-Hartman
Cc: mitrutzceclan, linux-media, linux-kernel, devicetree,
linux-staging, linux-gpio, Niklas Söderlund, Martin Hecht,
Cosmin Tanislav
In-Reply-To: <20260604-gmsl2-3_serdes-v13-0-9d8a4919983b@analog.com>
From: Cosmin Tanislav <demonsingur@gmail.com>
The staging MAX96712 driver will be removed as its functionality has
been moved to the MAX96724 driver which makes use of the Maxim
GMSL2/3 deserializer framework.
Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
---
arch/arm64/configs/defconfig | 1 -
1 file changed, 1 deletion(-)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 4c81b721a03a..9d3b284ed9b1 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -1426,7 +1426,6 @@ CONFIG_GREYBUS=m
CONFIG_GREYBUS_BEAGLEPLAY=m
CONFIG_STAGING=y
CONFIG_STAGING_MEDIA=y
-CONFIG_VIDEO_MAX96712=m
CONFIG_VIDEO_MESON_VDEC=m
CONFIG_SND_BCM2835=m
CONFIG_CHROME_PLATFORMS=y
--
2.53.0
^ permalink raw reply related
* [PATCH v13 12/22] dt-bindings: media: i2c: add MAX9296A, MAX96716A, MAX96792A
From: Dumitru Ceclan via B4 Relay @ 2026-06-04 14:13 UTC (permalink / raw)
To: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
Laurent Pinchart, Julien Massot, Rob Herring,
Niklas Söderlund, Greg Kroah-Hartman
Cc: mitrutzceclan, linux-media, linux-kernel, devicetree,
linux-staging, linux-gpio, Niklas Söderlund, Martin Hecht,
Cosmin Tanislav
In-Reply-To: <20260604-gmsl2-3_serdes-v13-0-9d8a4919983b@analog.com>
From: Cosmin Tanislav <demonsingur@gmail.com>
The MAX9296A deserializer converts single or dual serial inputs to MIPI
CSI-2 outputs. The GMSL2 links operate at a fixed rate of 3Gbps or 6Gbps
in the forward direction and 187.5Mbps in the reverse direction.
In GMSL1 mode, each serial link can be paired with 3.12Gbps or 1.5Gbps
GMSL1 serializers or operate up to 4.5Gbps with GMSL2 serializers with
GMSL1 backward compatibility. The MAX9296A supports mixed GMSL2 and
GMSL1 links. The serial inputs operate independently, allowing videos
with different timings and resolutions to be received on each input.
MAX96716A supports both tunnel and pixel mode.
MAX96792A supports both tunnel and pixel mode, and has two GMSL3 links.
Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
.../bindings/media/i2c/maxim,max9296a.yaml | 242 +++++++++++++++++++++
MAINTAINERS | 6 +
2 files changed, 248 insertions(+)
diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml
new file mode 100644
index 000000000000..de6bfcec5dc6
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml
@@ -0,0 +1,242 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2024 Collabora Ltd.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/maxim,max9296a.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim MAX9296A GMSL2 to CSI-2 Deserializer
+
+maintainers:
+ - Cosmin Tanislav <cosmin.tanislav@analog.com>
+
+description: >
+ The MAX9296A deserializer converts single or dual serial inputs to
+ MIPI CSI-2 outputs. The GMSL2 links operate at a fixed rate of 3Gbps
+ or 6Gbps in the forward direction and 187.5Mbps in the reverse
+ direction. In GMSL1 mode, each serial link can be paired with 3.12Gbps
+ or 1.5Gbps GMSL1 serializers or operate up to 4.5Gbps with GMSL2
+ serializers with GMSL1 backward compatibility. The MAX9296A supports
+ mixed GMSL2 and GMSL1 links. The serial inputs operate independently,
+ allowing videos with different timings and resolutions to be received
+ on each input.
+
+ MAX96716A supports both tunnel and pixel mode.
+
+ MAX96792A supports both tunnel and pixel mode, and has two GMSL3 links.
+
+properties:
+ compatible:
+ enum:
+ - maxim,max9296a
+ - maxim,max96716a
+ - maxim,max96792a
+
+ reg:
+ maxItems: 1
+
+ powerdown-gpios:
+ maxItems: 1
+ description: Specifier for the GPIO connected to the PWDNB pin.
+
+ port0-poc-supply:
+ description: Regulator providing Power over Coax for GMSL port 0
+
+ port1-poc-supply:
+ description: Regulator providing Power over Coax for GMSL port 1
+
+ i2c-alias-pool:
+ maxItems: 2
+
+ i2c-atr:
+ type: object
+ additionalProperties: false
+
+ properties:
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ patternProperties:
+ '^i2c@[0-1]$':
+ $ref: /schemas/i2c/i2c-controller.yaml#
+ unevaluatedProperties: false
+ properties:
+ reg:
+ items:
+ minimum: 0
+ maximum: 1
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ patternProperties:
+ '^port@[0-1]$':
+ $ref: /schemas/graph.yaml#/properties/port
+ description: GMSL Input ports 0-1
+
+ '^port@[2-3]$':
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+ description: CSI-2 Output ports 0-1
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ data-lanes:
+ minItems: 1
+ maxItems: 4
+
+ lane-polarities:
+ minItems: 2
+ maxItems: 5
+
+ link-frequencies:
+ maxItems: 1
+
+ required:
+ - data-lanes
+
+ anyOf:
+ - required:
+ - port@2
+ - required:
+ - port@3
+
+required:
+ - compatible
+ - reg
+ - ports
+
+additionalProperties: false
+
+allOf:
+ - $ref: /schemas/i2c/i2c-atr.yaml#
+
+dependentRequired:
+ i2c-atr: [i2c-alias-pool]
+ i2c-alias-pool: [i2c-atr]
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/media/video-interfaces.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ deserializer@28 {
+ compatible = "maxim,max9296a";
+ reg = <0x28>;
+ powerdown-gpios = <&main_gpio0 37 GPIO_ACTIVE_LOW>;
+
+ i2c-alias-pool = <0x40 0x41>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ des_gmsl_in_0: endpoint {
+ remote-endpoint = <&ser_0_gmsl_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ des_gmsl_in_1: endpoint {
+ remote-endpoint = <&ser_1_gmsl_out>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+ des_csi_out: endpoint {
+ data-lanes = <1 2 3 4>;
+ link-frequencies = /bits/ 64 <400000000>;
+ remote-endpoint = <&csi_in>;
+ };
+ };
+ };
+
+ i2c-atr {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ i2c@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0>;
+
+ serializer@40 {
+ compatible = "maxim,max96717", "maxim,max96717f";
+ reg = <0x40>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ #clock-cells = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ ser_0_csi_in: endpoint {
+ data-lanes = <1 2>;
+ remote-endpoint = <&sensor_0_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ ser_0_gmsl_out: endpoint {
+ remote-endpoint = <&des_gmsl_in_0>;
+ };
+ };
+ };
+ };
+ };
+
+ i2c@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <1>;
+
+ serializer@40 {
+ compatible = "maxim,max96717", "maxim,max96717f";
+ reg = <0x40>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ #clock-cells = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ ser_1_csi_in: endpoint {
+ data-lanes = <1 2>;
+ remote-endpoint = <&sensor_1_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ ser_1_gmsl_out: endpoint {
+ remote-endpoint = <&des_gmsl_in_1>;
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 63389fea5d15..f9bdc133b52d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15763,6 +15763,12 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/proximity/maxbotix,mb1232.yaml
F: drivers/iio/proximity/mb1232.c
+MAXIM GMSL2/3 SERIALIZERS AND DESERIALIZERS
+M: Cosmin Tanislav <cosmin.tanislav@analog.com>
+L: linux-media@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml
+
MAXIM MAX11205 DRIVER
M: Ramona Bolboaca <ramona.bolboaca@analog.com>
L: linux-iio@vger.kernel.org
--
2.53.0
^ permalink raw reply related
* [PATCH v13 21/22] media: i2c: remove MAX96717 driver
From: Dumitru Ceclan via B4 Relay @ 2026-06-04 14:14 UTC (permalink / raw)
To: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
Laurent Pinchart, Julien Massot, Rob Herring,
Niklas Söderlund, Greg Kroah-Hartman
Cc: mitrutzceclan, linux-media, linux-kernel, devicetree,
linux-staging, linux-gpio, Niklas Söderlund, Martin Hecht,
Cosmin Tanislav
In-Reply-To: <20260604-gmsl2-3_serdes-v13-0-9d8a4919983b@analog.com>
From: Cosmin Tanislav <demonsingur@gmail.com>
Remove the MAX96717 driver. Its functionality has been moved to a new
MAX96717 driver which makes use of the Maxim GMSL2/3 serializer
framework.
Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Reviewed-by: Julien Massot <julien.massot@collabora.com>
---
MAINTAINERS | 1 -
drivers/media/i2c/Kconfig | 17 -
drivers/media/i2c/Makefile | 1 -
drivers/media/i2c/max96717.c | 1104 ------------------------------------------
4 files changed, 1123 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 9577aa8dc4ea..e16cb6f33c85 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15746,7 +15746,6 @@ M: Julien Massot <julien.massot@collabora.com>
L: linux-media@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
-F: drivers/media/i2c/max96717.c
MAX9860 MONO AUDIO VOICE CODEC DRIVER
M: Peter Rosin <peda@axentia.se>
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 42cb53c5395d..8108978fbfa2 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1769,23 +1769,6 @@ config VIDEO_MAX96714
To compile this driver as a module, choose M here: the
module will be called max96714.
-config VIDEO_MAX96717
- tristate "Maxim MAX96717 GMSL2 Serializer support"
- depends on I2C && VIDEO_DEV && COMMON_CLK
- select I2C_MUX
- select MEDIA_CONTROLLER
- select GPIOLIB
- select V4L2_CCI_I2C
- select V4L2_FWNODE
- select VIDEO_V4L2_SUBDEV_API
- help
- Device driver for the Maxim MAX96717 GMSL2 Serializer.
- MAX96717 serializers convert video on a MIPI CSI-2
- input to a GMSL2 output.
-
- To compile this driver as a module, choose M here: the
- module will be called max96717.
-
source "drivers/media/i2c/maxim-serdes/Kconfig"
endmenu
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 9af890f8c298..b49832e61f39 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -71,7 +71,6 @@ obj-$(CONFIG_VIDEO_M52790) += m52790.o
obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
obj-$(CONFIG_VIDEO_MAX96714) += max96714.o
-obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
obj-$(CONFIG_VIDEO_MAXIM_SERDES) += maxim-serdes/
obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o
obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c
deleted file mode 100644
index 72f021b1a7b9..000000000000
--- a/drivers/media/i2c/max96717.c
+++ /dev/null
@@ -1,1104 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Maxim GMSL2 Serializer Driver
- *
- * Copyright (C) 2024 Collabora Ltd.
- */
-
-#include <linux/bitfield.h>
-#include <linux/clk.h>
-#include <linux/clk-provider.h>
-#include <linux/delay.h>
-#include <linux/gpio/driver.h>
-#include <linux/i2c-mux.h>
-#include <linux/i2c.h>
-#include <linux/property.h>
-#include <linux/regmap.h>
-
-#include <media/v4l2-cci.h>
-#include <media/v4l2-ctrls.h>
-#include <media/v4l2-fwnode.h>
-#include <media/v4l2-subdev.h>
-
-#define MAX96717_DEVICE_ID 0xbf
-#define MAX96717F_DEVICE_ID 0xc8
-#define MAX96717_PORTS 2
-#define MAX96717_PAD_SINK 0
-#define MAX96717_PAD_SOURCE 1
-#define MAX96717_CSI_NLANES 4
-
-#define MAX96717_DEFAULT_CLKOUT_RATE 24000000UL
-
-/* DEV */
-#define MAX96717_REG3 CCI_REG8(0x3)
-#define MAX96717_RCLKSEL GENMASK(1, 0)
-#define RCLKSEL_REF_PLL CCI_REG8(0x3)
-#define MAX96717_REG6 CCI_REG8(0x6)
-#define RCLKEN BIT(5)
-#define MAX96717_DEV_ID CCI_REG8(0xd)
-#define MAX96717_DEV_REV CCI_REG8(0xe)
-#define MAX96717_DEV_REV_MASK GENMASK(3, 0)
-
-/* VID_TX Z */
-#define MAX96717_VIDEO_TX0 CCI_REG8(0x110)
-#define MAX96717_VIDEO_AUTO_BPP BIT(3)
-#define MAX96717_VIDEO_TX2 CCI_REG8(0x112)
-#define MAX96717_VIDEO_PCLKDET BIT(7)
-
-/* VTX_Z */
-#define MAX96717_VTX0 CCI_REG8(0x24e)
-#define MAX96717_VTX1 CCI_REG8(0x24f)
-#define MAX96717_PATTERN_CLK_FREQ GENMASK(3, 1)
-#define MAX96717_VTX_VS_DLY CCI_REG24(0x250)
-#define MAX96717_VTX_VS_HIGH CCI_REG24(0x253)
-#define MAX96717_VTX_VS_LOW CCI_REG24(0x256)
-#define MAX96717_VTX_V2H CCI_REG24(0x259)
-#define MAX96717_VTX_HS_HIGH CCI_REG16(0x25c)
-#define MAX96717_VTX_HS_LOW CCI_REG16(0x25e)
-#define MAX96717_VTX_HS_CNT CCI_REG16(0x260)
-#define MAX96717_VTX_V2D CCI_REG24(0x262)
-#define MAX96717_VTX_DE_HIGH CCI_REG16(0x265)
-#define MAX96717_VTX_DE_LOW CCI_REG16(0x267)
-#define MAX96717_VTX_DE_CNT CCI_REG16(0x269)
-#define MAX96717_VTX29 CCI_REG8(0x26b)
-#define MAX96717_VTX_MODE GENMASK(1, 0)
-#define MAX96717_VTX_GRAD_INC CCI_REG8(0x26c)
-#define MAX96717_VTX_CHKB_COLOR_A CCI_REG24(0x26d)
-#define MAX96717_VTX_CHKB_COLOR_B CCI_REG24(0x270)
-#define MAX96717_VTX_CHKB_RPT_CNT_A CCI_REG8(0x273)
-#define MAX96717_VTX_CHKB_RPT_CNT_B CCI_REG8(0x274)
-#define MAX96717_VTX_CHKB_ALT CCI_REG8(0x275)
-
-/* GPIO */
-#define MAX96717_NUM_GPIO 11
-#define MAX96717_GPIO_REG_A(gpio) CCI_REG8(0x2be + (gpio) * 3)
-#define MAX96717_GPIO_OUT BIT(4)
-#define MAX96717_GPIO_IN BIT(3)
-#define MAX96717_GPIO_RX_EN BIT(2)
-#define MAX96717_GPIO_TX_EN BIT(1)
-#define MAX96717_GPIO_OUT_DIS BIT(0)
-
-/* FRONTTOP */
-/* MAX96717 only have CSI port 'B' */
-#define MAX96717_FRONTOP0 CCI_REG8(0x308)
-#define MAX96717_START_PORT_B BIT(5)
-
-/* MIPI_RX */
-#define MAX96717_MIPI_RX1 CCI_REG8(0x331)
-#define MAX96717_MIPI_LANES_CNT GENMASK(5, 4)
-#define MAX96717_MIPI_RX2 CCI_REG8(0x332) /* phy1 Lanes map */
-#define MAX96717_PHY2_LANES_MAP GENMASK(7, 4)
-#define MAX96717_MIPI_RX3 CCI_REG8(0x333) /* phy2 Lanes map */
-#define MAX96717_PHY1_LANES_MAP GENMASK(3, 0)
-#define MAX96717_MIPI_RX4 CCI_REG8(0x334) /* phy1 lane polarities */
-#define MAX96717_PHY1_LANES_POL GENMASK(6, 4)
-#define MAX96717_MIPI_RX5 CCI_REG8(0x335) /* phy2 lane polarities */
-#define MAX96717_PHY2_LANES_POL GENMASK(2, 0)
-
-/* MIPI_RX_EXT */
-#define MAX96717_MIPI_RX_EXT11 CCI_REG8(0x383)
-#define MAX96717_TUN_MODE BIT(7)
-
-/* REF_VTG */
-#define REF_VTG0 CCI_REG8(0x3f0)
-#define REFGEN_PREDEF_EN BIT(6)
-#define REFGEN_PREDEF_FREQ_MASK GENMASK(5, 4)
-#define REFGEN_PREDEF_FREQ_ALT BIT(3)
-#define REFGEN_RST BIT(1)
-#define REFGEN_EN BIT(0)
-
-/* MISC */
-#define PIO_SLEW_1 CCI_REG8(0x570)
-
-enum max96717_vpg_mode {
- MAX96717_VPG_DISABLED = 0,
- MAX96717_VPG_CHECKERBOARD = 1,
- MAX96717_VPG_GRADIENT = 2,
-};
-
-struct max96717_priv {
- struct i2c_client *client;
- struct regmap *regmap;
- struct i2c_mux_core *mux;
- struct v4l2_mbus_config_mipi_csi2 mipi_csi2;
- struct v4l2_subdev sd;
- struct media_pad pads[MAX96717_PORTS];
- struct v4l2_ctrl_handler ctrl_handler;
- struct v4l2_async_notifier notifier;
- struct v4l2_subdev *source_sd;
- u16 source_sd_pad;
- u64 enabled_source_streams;
- u8 pll_predef_index;
- struct clk_hw clk_hw;
- struct gpio_chip gpio_chip;
- enum max96717_vpg_mode pattern;
-};
-
-static inline struct max96717_priv *sd_to_max96717(struct v4l2_subdev *sd)
-{
- return container_of(sd, struct max96717_priv, sd);
-}
-
-static inline struct max96717_priv *clk_hw_to_max96717(struct clk_hw *hw)
-{
- return container_of(hw, struct max96717_priv, clk_hw);
-}
-
-static int max96717_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
-{
- return 0;
-}
-
-static int max96717_i2c_mux_init(struct max96717_priv *priv)
-{
- priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
- 1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
- max96717_i2c_mux_select, NULL);
- if (!priv->mux)
- return -ENOMEM;
-
- return i2c_mux_add_adapter(priv->mux, 0, 0);
-}
-
-static inline int max96717_start_csi(struct max96717_priv *priv, bool start)
-{
- return cci_update_bits(priv->regmap, MAX96717_FRONTOP0,
- MAX96717_START_PORT_B,
- start ? MAX96717_START_PORT_B : 0, NULL);
-}
-
-static int max96717_apply_patgen_timing(struct max96717_priv *priv,
- struct v4l2_subdev_state *state)
-{
- struct v4l2_mbus_framefmt *fmt =
- v4l2_subdev_state_get_format(state, MAX96717_PAD_SOURCE);
- const u32 h_active = fmt->width;
- const u32 h_fp = 88;
- const u32 h_sw = 44;
- const u32 h_bp = 148;
- u32 h_tot;
- const u32 v_active = fmt->height;
- const u32 v_fp = 4;
- const u32 v_sw = 5;
- const u32 v_bp = 36;
- u32 v_tot;
- int ret = 0;
-
- h_tot = h_active + h_fp + h_sw + h_bp;
- v_tot = v_active + v_fp + v_sw + v_bp;
-
- /* 75 Mhz pixel clock */
- cci_update_bits(priv->regmap, MAX96717_VTX1,
- MAX96717_PATTERN_CLK_FREQ, 0xa, &ret);
-
- dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height,
- fmt->width);
-
- cci_write(priv->regmap, MAX96717_VTX_VS_DLY, 0, &ret);
- cci_write(priv->regmap, MAX96717_VTX_VS_HIGH, v_sw * h_tot, &ret);
- cci_write(priv->regmap, MAX96717_VTX_VS_LOW,
- (v_active + v_fp + v_bp) * h_tot, &ret);
- cci_write(priv->regmap, MAX96717_VTX_HS_HIGH, h_sw, &ret);
- cci_write(priv->regmap, MAX96717_VTX_HS_LOW, h_active + h_fp + h_bp,
- &ret);
- cci_write(priv->regmap, MAX96717_VTX_V2D,
- h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret);
- cci_write(priv->regmap, MAX96717_VTX_HS_CNT, v_tot, &ret);
- cci_write(priv->regmap, MAX96717_VTX_DE_HIGH, h_active, &ret);
- cci_write(priv->regmap, MAX96717_VTX_DE_LOW, h_fp + h_sw + h_bp,
- &ret);
- cci_write(priv->regmap, MAX96717_VTX_DE_CNT, v_active, &ret);
- /* B G R */
- cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_A, 0xfecc00, &ret);
- /* B G R */
- cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_B, 0x006aa7, &ret);
- cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_A, 0x3c, &ret);
- cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_B, 0x3c, &ret);
- cci_write(priv->regmap, MAX96717_VTX_CHKB_ALT, 0x3c, &ret);
- cci_write(priv->regmap, MAX96717_VTX_GRAD_INC, 0x10, &ret);
-
- return ret;
-}
-
-static int max96717_apply_patgen(struct max96717_priv *priv,
- struct v4l2_subdev_state *state)
-{
- unsigned int val;
- int ret = 0;
-
- if (priv->pattern)
- ret = max96717_apply_patgen_timing(priv, state);
-
- cci_write(priv->regmap, MAX96717_VTX0, priv->pattern ? 0xfb : 0,
- &ret);
-
- val = FIELD_PREP(MAX96717_VTX_MODE, priv->pattern);
- cci_update_bits(priv->regmap, MAX96717_VTX29, MAX96717_VTX_MODE,
- val, &ret);
- return ret;
-}
-
-static int max96717_s_ctrl(struct v4l2_ctrl *ctrl)
-{
- struct max96717_priv *priv =
- container_of(ctrl->handler, struct max96717_priv, ctrl_handler);
- int ret;
-
- switch (ctrl->id) {
- case V4L2_CID_TEST_PATTERN:
- if (priv->enabled_source_streams)
- return -EBUSY;
- priv->pattern = ctrl->val;
- break;
- default:
- return -EINVAL;
- }
-
- /* Use bpp from bpp register */
- ret = cci_update_bits(priv->regmap, MAX96717_VIDEO_TX0,
- MAX96717_VIDEO_AUTO_BPP,
- priv->pattern ? 0 : MAX96717_VIDEO_AUTO_BPP,
- NULL);
-
- /*
- * Pattern generator doesn't work with tunnel mode.
- * Needs RGB color format and deserializer tunnel mode must be disabled.
- */
- return cci_update_bits(priv->regmap, MAX96717_MIPI_RX_EXT11,
- MAX96717_TUN_MODE,
- priv->pattern ? 0 : MAX96717_TUN_MODE, &ret);
-}
-
-static const char * const max96717_test_pattern[] = {
- "Disabled",
- "Checkerboard",
- "Gradient"
-};
-
-static const struct v4l2_ctrl_ops max96717_ctrl_ops = {
- .s_ctrl = max96717_s_ctrl,
-};
-
-static int max96717_gpiochip_get(struct gpio_chip *gpiochip,
- unsigned int offset)
-{
- struct max96717_priv *priv = gpiochip_get_data(gpiochip);
- u64 val;
- int ret;
-
- ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset),
- &val, NULL);
- if (ret)
- return ret;
-
- if (val & MAX96717_GPIO_OUT_DIS)
- return !!(val & MAX96717_GPIO_IN);
- else
- return !!(val & MAX96717_GPIO_OUT);
-}
-
-static int max96717_gpiochip_set(struct gpio_chip *gpiochip,
- unsigned int offset, int value)
-{
- struct max96717_priv *priv = gpiochip_get_data(gpiochip);
-
- return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
- MAX96717_GPIO_OUT, MAX96717_GPIO_OUT, NULL);
-}
-
-static int max96717_gpio_get_direction(struct gpio_chip *gpiochip,
- unsigned int offset)
-{
- struct max96717_priv *priv = gpiochip_get_data(gpiochip);
- u64 val;
- int ret;
-
- ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset), &val, NULL);
- if (ret < 0)
- return ret;
-
- return !!(val & MAX96717_GPIO_OUT_DIS);
-}
-
-static int max96717_gpio_direction_out(struct gpio_chip *gpiochip,
- unsigned int offset, int value)
-{
- struct max96717_priv *priv = gpiochip_get_data(gpiochip);
-
- return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
- MAX96717_GPIO_OUT_DIS | MAX96717_GPIO_OUT,
- value ? MAX96717_GPIO_OUT : 0, NULL);
-}
-
-static int max96717_gpio_direction_in(struct gpio_chip *gpiochip,
- unsigned int offset)
-{
- struct max96717_priv *priv = gpiochip_get_data(gpiochip);
-
- return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
- MAX96717_GPIO_OUT_DIS, MAX96717_GPIO_OUT_DIS,
- NULL);
-}
-
-static int max96717_gpiochip_probe(struct max96717_priv *priv)
-{
- struct device *dev = &priv->client->dev;
- struct gpio_chip *gc = &priv->gpio_chip;
- int i, ret = 0;
-
- gc->label = dev_name(dev);
- gc->parent = dev;
- gc->owner = THIS_MODULE;
- gc->ngpio = MAX96717_NUM_GPIO;
- gc->base = -1;
- gc->can_sleep = true;
- gc->get_direction = max96717_gpio_get_direction;
- gc->direction_input = max96717_gpio_direction_in;
- gc->direction_output = max96717_gpio_direction_out;
- gc->set = max96717_gpiochip_set;
- gc->get = max96717_gpiochip_get;
-
- /* Disable GPIO forwarding */
- for (i = 0; i < gc->ngpio; i++)
- cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(i),
- MAX96717_GPIO_RX_EN | MAX96717_GPIO_TX_EN,
- 0, &ret);
-
- if (ret)
- return ret;
-
- ret = devm_gpiochip_add_data(dev, gc, priv);
- if (ret) {
- dev_err(dev, "Unable to create gpio_chip\n");
- return ret;
- }
-
- return 0;
-}
-
-static int _max96717_set_routing(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state,
- struct v4l2_subdev_krouting *routing)
-{
- static const struct v4l2_mbus_framefmt format = {
- .width = 1280,
- .height = 1080,
- .code = MEDIA_BUS_FMT_Y8_1X8,
- .field = V4L2_FIELD_NONE,
- };
- int ret;
-
- ret = v4l2_subdev_routing_validate(sd, routing,
- V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
- if (ret)
- return ret;
-
- ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
- if (ret)
- return ret;
-
- return 0;
-}
-
-static int max96717_set_routing(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state,
- enum v4l2_subdev_format_whence which,
- struct v4l2_subdev_krouting *routing)
-{
- struct max96717_priv *priv = sd_to_max96717(sd);
-
- if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams)
- return -EBUSY;
-
- return _max96717_set_routing(sd, state, routing);
-}
-
-static int max96717_set_fmt(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state,
- struct v4l2_subdev_format *format)
-{
- struct max96717_priv *priv = sd_to_max96717(sd);
- struct v4l2_mbus_framefmt *fmt;
- u64 stream_source_mask;
-
- if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
- priv->enabled_source_streams)
- return -EBUSY;
-
- /* No transcoding, source and sink formats must match. */
- if (format->pad == MAX96717_PAD_SOURCE)
- return v4l2_subdev_get_fmt(sd, state, format);
-
- /* Set sink format */
- fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
- if (!fmt)
- return -EINVAL;
-
- *fmt = format->format;
-
- /* Propagate to source format */
- fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
- format->stream);
- if (!fmt)
- return -EINVAL;
- *fmt = format->format;
-
- stream_source_mask = BIT(format->stream);
-
- return v4l2_subdev_state_xlate_streams(state, MAX96717_PAD_SOURCE,
- MAX96717_PAD_SINK,
- &stream_source_mask);
-}
-
-static int max96717_init_state(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state)
-{
- struct v4l2_subdev_route routes[] = {
- {
- .sink_pad = MAX96717_PAD_SINK,
- .sink_stream = 0,
- .source_pad = MAX96717_PAD_SOURCE,
- .source_stream = 0,
- .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
- },
- };
- struct v4l2_subdev_krouting routing = {
- .num_routes = ARRAY_SIZE(routes),
- .routes = routes,
- };
-
- return _max96717_set_routing(sd, state, &routing);
-}
-
-static bool max96717_pipe_pclkdet(struct max96717_priv *priv)
-{
- u64 val = 0;
-
- cci_read(priv->regmap, MAX96717_VIDEO_TX2, &val, NULL);
-
- return val & MAX96717_VIDEO_PCLKDET;
-}
-
-static int max96717_log_status(struct v4l2_subdev *sd)
-{
- struct max96717_priv *priv = sd_to_max96717(sd);
- struct device *dev = &priv->client->dev;
-
- dev_info(dev, "Serializer: max96717\n");
- dev_info(dev, "Pipe: pclkdet:%d\n", max96717_pipe_pclkdet(priv));
-
- return 0;
-}
-
-static int max96717_enable_streams(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state, u32 pad,
- u64 streams_mask)
-{
- struct max96717_priv *priv = sd_to_max96717(sd);
- u64 sink_streams;
- int ret;
-
- if (!priv->enabled_source_streams)
- max96717_start_csi(priv, true);
-
- ret = max96717_apply_patgen(priv, state);
- if (ret)
- goto stop_csi;
-
- if (!priv->pattern) {
- sink_streams =
- v4l2_subdev_state_xlate_streams(state,
- MAX96717_PAD_SOURCE,
- MAX96717_PAD_SINK,
- &streams_mask);
-
- ret = v4l2_subdev_enable_streams(priv->source_sd,
- priv->source_sd_pad,
- sink_streams);
- if (ret)
- goto stop_csi;
- }
-
- priv->enabled_source_streams |= streams_mask;
-
- return 0;
-
-stop_csi:
- if (!priv->enabled_source_streams)
- max96717_start_csi(priv, false);
-
- return ret;
-}
-
-static int max96717_disable_streams(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state, u32 pad,
- u64 streams_mask)
-{
- struct max96717_priv *priv = sd_to_max96717(sd);
- u64 sink_streams;
-
- /*
- * Stop the CSI receiver first then the source,
- * otherwise the device may become unresponsive
- * while holding the I2C bus low.
- */
- priv->enabled_source_streams &= ~streams_mask;
- if (!priv->enabled_source_streams)
- max96717_start_csi(priv, false);
-
- if (!priv->pattern) {
- int ret;
-
- sink_streams =
- v4l2_subdev_state_xlate_streams(state,
- MAX96717_PAD_SOURCE,
- MAX96717_PAD_SINK,
- &streams_mask);
-
- ret = v4l2_subdev_disable_streams(priv->source_sd,
- priv->source_sd_pad,
- sink_streams);
- if (ret)
- return ret;
- }
-
- return 0;
-}
-
-static const struct v4l2_subdev_pad_ops max96717_pad_ops = {
- .enable_streams = max96717_enable_streams,
- .disable_streams = max96717_disable_streams,
- .set_routing = max96717_set_routing,
- .get_fmt = v4l2_subdev_get_fmt,
- .set_fmt = max96717_set_fmt,
-};
-
-static const struct v4l2_subdev_core_ops max96717_subdev_core_ops = {
- .log_status = max96717_log_status,
-};
-
-static const struct v4l2_subdev_internal_ops max96717_internal_ops = {
- .init_state = max96717_init_state,
-};
-
-static const struct v4l2_subdev_ops max96717_subdev_ops = {
- .core = &max96717_subdev_core_ops,
- .pad = &max96717_pad_ops,
-};
-
-static const struct media_entity_operations max96717_entity_ops = {
- .link_validate = v4l2_subdev_link_validate,
-};
-
-static int max96717_notify_bound(struct v4l2_async_notifier *notifier,
- struct v4l2_subdev *source_subdev,
- struct v4l2_async_connection *asd)
-{
- struct max96717_priv *priv = sd_to_max96717(notifier->sd);
- struct device *dev = &priv->client->dev;
- int ret;
-
- ret = media_entity_get_fwnode_pad(&source_subdev->entity,
- source_subdev->fwnode,
- MEDIA_PAD_FL_SOURCE);
- if (ret < 0) {
- dev_err(dev, "Failed to find pad for %s\n",
- source_subdev->name);
- return ret;
- }
-
- priv->source_sd = source_subdev;
- priv->source_sd_pad = ret;
-
- ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad,
- &priv->sd.entity, 0,
- MEDIA_LNK_FL_ENABLED |
- MEDIA_LNK_FL_IMMUTABLE);
- if (ret) {
- dev_err(dev, "Unable to link %s:%u -> %s:0\n",
- source_subdev->name, priv->source_sd_pad,
- priv->sd.name);
- return ret;
- }
-
- return 0;
-}
-
-static const struct v4l2_async_notifier_operations max96717_notify_ops = {
- .bound = max96717_notify_bound,
-};
-
-static int max96717_v4l2_notifier_register(struct max96717_priv *priv)
-{
- struct device *dev = &priv->client->dev;
- struct v4l2_async_connection *asd;
- struct fwnode_handle *ep_fwnode;
- int ret;
-
- ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
- MAX96717_PAD_SINK, 0, 0);
- if (!ep_fwnode) {
- dev_err(dev, "No graph endpoint\n");
- return -ENODEV;
- }
-
- v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd);
-
- asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode,
- struct v4l2_async_connection);
-
- fwnode_handle_put(ep_fwnode);
-
- if (IS_ERR(asd)) {
- dev_err(dev, "Failed to add subdev: %pe", asd);
- v4l2_async_nf_cleanup(&priv->notifier);
- return PTR_ERR(asd);
- }
-
- priv->notifier.ops = &max96717_notify_ops;
-
- ret = v4l2_async_nf_register(&priv->notifier);
- if (ret) {
- dev_err(dev, "Failed to register subdev_notifier");
- v4l2_async_nf_cleanup(&priv->notifier);
- return ret;
- }
-
- return 0;
-}
-
-static int max96717_subdev_init(struct max96717_priv *priv)
-{
- struct device *dev = &priv->client->dev;
- int ret;
-
- v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96717_subdev_ops);
- priv->sd.internal_ops = &max96717_internal_ops;
-
- v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
- priv->sd.ctrl_handler = &priv->ctrl_handler;
-
- v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
- &max96717_ctrl_ops,
- V4L2_CID_TEST_PATTERN,
- ARRAY_SIZE(max96717_test_pattern) - 1,
- 0, 0, max96717_test_pattern);
- if (priv->ctrl_handler.error) {
- ret = priv->ctrl_handler.error;
- goto err_free_ctrl;
- }
-
- priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
- priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
- priv->sd.entity.ops = &max96717_entity_ops;
-
- priv->pads[MAX96717_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
- priv->pads[MAX96717_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
-
- ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads);
- if (ret) {
- dev_err_probe(dev, ret, "Failed to init pads\n");
- goto err_free_ctrl;
- }
-
- ret = v4l2_subdev_init_finalize(&priv->sd);
- if (ret) {
- dev_err_probe(dev, ret,
- "v4l2 subdev init finalized failed\n");
- goto err_entity_cleanup;
- }
- ret = max96717_v4l2_notifier_register(priv);
- if (ret) {
- dev_err_probe(dev, ret,
- "v4l2 subdev notifier register failed\n");
- goto err_free_state;
- }
-
- ret = v4l2_async_register_subdev(&priv->sd);
- if (ret) {
- dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n");
- goto err_unreg_notif;
- }
-
- return 0;
-
-err_unreg_notif:
- v4l2_async_nf_unregister(&priv->notifier);
- v4l2_async_nf_cleanup(&priv->notifier);
-err_free_state:
- v4l2_subdev_cleanup(&priv->sd);
-err_entity_cleanup:
- media_entity_cleanup(&priv->sd.entity);
-err_free_ctrl:
- v4l2_ctrl_handler_free(&priv->ctrl_handler);
-
- return ret;
-}
-
-static void max96717_subdev_uninit(struct max96717_priv *priv)
-{
- v4l2_async_unregister_subdev(&priv->sd);
- v4l2_async_nf_unregister(&priv->notifier);
- v4l2_async_nf_cleanup(&priv->notifier);
- v4l2_subdev_cleanup(&priv->sd);
- media_entity_cleanup(&priv->sd.entity);
- v4l2_ctrl_handler_free(&priv->ctrl_handler);
-}
-
-struct max96717_pll_predef_freq {
- unsigned long freq;
- bool is_alt;
- u8 val;
-};
-
-static const struct max96717_pll_predef_freq max96717_predef_freqs[] = {
- { 13500000, true, 0 }, { 19200000, false, 0 },
- { 24000000, true, 1 }, { 27000000, false, 1 },
- { 37125000, false, 2 }, { 74250000, false, 3 },
-};
-
-static unsigned long
-max96717_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
-{
- struct max96717_priv *priv = clk_hw_to_max96717(hw);
-
- return max96717_predef_freqs[priv->pll_predef_index].freq;
-}
-
-static unsigned int max96717_clk_find_best_index(struct max96717_priv *priv,
- unsigned long rate)
-{
- unsigned int i, idx = 0;
- unsigned long diff_new, diff_old = U32_MAX;
-
- for (i = 0; i < ARRAY_SIZE(max96717_predef_freqs); i++) {
- diff_new = abs(rate - max96717_predef_freqs[i].freq);
- if (diff_new < diff_old) {
- diff_old = diff_new;
- idx = i;
- }
- }
-
- return idx;
-}
-
-static int max96717_clk_determine_rate(struct clk_hw *hw,
- struct clk_rate_request *req)
-{
- struct max96717_priv *priv = clk_hw_to_max96717(hw);
- struct device *dev = &priv->client->dev;
- unsigned int idx;
-
- idx = max96717_clk_find_best_index(priv, req->rate);
-
- if (req->rate != max96717_predef_freqs[idx].freq) {
- dev_warn(dev, "Request CLK freq:%lu, found CLK freq:%lu\n",
- req->rate, max96717_predef_freqs[idx].freq);
- }
-
- req->rate = max96717_predef_freqs[idx].freq;
-
- return 0;
-}
-
-static int max96717_clk_set_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long parent_rate)
-{
- struct max96717_priv *priv = clk_hw_to_max96717(hw);
- unsigned int val, idx;
- int ret = 0;
-
- idx = max96717_clk_find_best_index(priv, rate);
-
- val = FIELD_PREP(REFGEN_PREDEF_FREQ_MASK,
- max96717_predef_freqs[idx].val);
-
- if (max96717_predef_freqs[idx].is_alt)
- val |= REFGEN_PREDEF_FREQ_ALT;
-
- val |= REFGEN_RST | REFGEN_PREDEF_EN;
-
- cci_write(priv->regmap, REF_VTG0, val, &ret);
- cci_update_bits(priv->regmap, REF_VTG0, REFGEN_RST | REFGEN_EN,
- REFGEN_EN, &ret);
- if (ret)
- return ret;
-
- priv->pll_predef_index = idx;
-
- return 0;
-}
-
-static int max96717_clk_prepare(struct clk_hw *hw)
-{
- struct max96717_priv *priv = clk_hw_to_max96717(hw);
-
- return cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN,
- RCLKEN, NULL);
-}
-
-static void max96717_clk_unprepare(struct clk_hw *hw)
-{
- struct max96717_priv *priv = clk_hw_to_max96717(hw);
-
- cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN, 0, NULL);
-}
-
-static const struct clk_ops max96717_clk_ops = {
- .prepare = max96717_clk_prepare,
- .unprepare = max96717_clk_unprepare,
- .set_rate = max96717_clk_set_rate,
- .recalc_rate = max96717_clk_recalc_rate,
- .determine_rate = max96717_clk_determine_rate,
-};
-
-static int max96717_register_clkout(struct max96717_priv *priv)
-{
- struct device *dev = &priv->client->dev;
- struct clk_init_data init = { .ops = &max96717_clk_ops };
- int ret;
-
- init.name = kasprintf(GFP_KERNEL, "max96717.%s.clk_out", dev_name(dev));
- if (!init.name)
- return -ENOMEM;
-
- /* RCLKSEL Reference PLL output */
- ret = cci_update_bits(priv->regmap, MAX96717_REG3, MAX96717_RCLKSEL,
- MAX96717_RCLKSEL, NULL);
- /* MFP4 fastest slew rate */
- cci_update_bits(priv->regmap, PIO_SLEW_1, BIT(5) | BIT(4), 0, &ret);
- if (ret)
- goto free_init_name;
-
- priv->clk_hw.init = &init;
-
- /* Initialize to 24 MHz */
- ret = max96717_clk_set_rate(&priv->clk_hw,
- MAX96717_DEFAULT_CLKOUT_RATE, 0);
- if (ret < 0)
- goto free_init_name;
-
- ret = devm_clk_hw_register(dev, &priv->clk_hw);
- kfree(init.name);
- if (ret)
- return dev_err_probe(dev, ret, "Cannot register clock HW\n");
-
- ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
- &priv->clk_hw);
- if (ret)
- return dev_err_probe(dev, ret,
- "Cannot add OF clock provider\n");
-
- return 0;
-
-free_init_name:
- kfree(init.name);
- return ret;
-}
-
-static int max96717_init_csi_lanes(struct max96717_priv *priv)
-{
- struct v4l2_mbus_config_mipi_csi2 *mipi = &priv->mipi_csi2;
- unsigned long lanes_used = 0;
- unsigned int nlanes, lane, val = 0;
- int ret;
-
- nlanes = mipi->num_data_lanes;
-
- ret = cci_update_bits(priv->regmap, MAX96717_MIPI_RX1,
- MAX96717_MIPI_LANES_CNT,
- FIELD_PREP(MAX96717_MIPI_LANES_CNT,
- nlanes - 1), NULL);
-
- /* lanes polarity */
- for (lane = 0; lane < nlanes + 1; lane++) {
- if (!mipi->lane_polarities[lane])
- continue;
- /* Clock lane */
- if (lane == 0)
- val |= BIT(2);
- else if (lane < 3)
- val |= BIT(lane - 1);
- else
- val |= BIT(lane);
- }
-
- cci_update_bits(priv->regmap, MAX96717_MIPI_RX5,
- MAX96717_PHY2_LANES_POL,
- FIELD_PREP(MAX96717_PHY2_LANES_POL, val), &ret);
-
- cci_update_bits(priv->regmap, MAX96717_MIPI_RX4,
- MAX96717_PHY1_LANES_POL,
- FIELD_PREP(MAX96717_PHY1_LANES_POL,
- val >> 3), &ret);
- /* lanes mapping */
- for (lane = 0, val = 0; lane < nlanes; lane++) {
- val |= (mipi->data_lanes[lane] - 1) << (lane * 2);
- lanes_used |= BIT(mipi->data_lanes[lane] - 1);
- }
-
- /*
- * Unused lanes need to be mapped as well to not have
- * the same lanes mapped twice.
- */
- for (; lane < MAX96717_CSI_NLANES; lane++) {
- unsigned int idx = find_first_zero_bit(&lanes_used,
- MAX96717_CSI_NLANES);
-
- val |= idx << (lane * 2);
- lanes_used |= BIT(idx);
- }
-
- cci_update_bits(priv->regmap, MAX96717_MIPI_RX3,
- MAX96717_PHY1_LANES_MAP,
- FIELD_PREP(MAX96717_PHY1_LANES_MAP, val), &ret);
-
- return cci_update_bits(priv->regmap, MAX96717_MIPI_RX2,
- MAX96717_PHY2_LANES_MAP,
- FIELD_PREP(MAX96717_PHY2_LANES_MAP, val >> 4),
- &ret);
-}
-
-static int max96717_hw_init(struct max96717_priv *priv)
-{
- struct device *dev = &priv->client->dev;
- u64 dev_id, val;
- int ret;
-
- ret = cci_read(priv->regmap, MAX96717_DEV_ID, &dev_id, NULL);
- if (ret)
- return dev_err_probe(dev, ret,
- "Fail to read the device id\n");
-
- if (dev_id != MAX96717_DEVICE_ID && dev_id != MAX96717F_DEVICE_ID)
- return dev_err_probe(dev, -EOPNOTSUPP,
- "Unsupported device id got %x\n", (u8)dev_id);
-
- ret = cci_read(priv->regmap, MAX96717_DEV_REV, &val, NULL);
- if (ret)
- return dev_err_probe(dev, ret,
- "Fail to read device revision");
-
- dev_dbg(dev, "Found %x (rev %lx)\n", (u8)dev_id,
- (u8)val & MAX96717_DEV_REV_MASK);
-
- ret = cci_read(priv->regmap, MAX96717_MIPI_RX_EXT11, &val, NULL);
- if (ret)
- return dev_err_probe(dev, ret,
- "Fail to read mipi rx extension");
-
- if (!(val & MAX96717_TUN_MODE))
- return dev_err_probe(dev, -EOPNOTSUPP,
- "Only supporting tunnel mode");
-
- return max96717_init_csi_lanes(priv);
-}
-
-static int max96717_parse_dt(struct max96717_priv *priv)
-{
- struct device *dev = &priv->client->dev;
- struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
- struct fwnode_handle *ep_fwnode;
- unsigned char num_data_lanes;
- int ret;
-
- ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
- MAX96717_PAD_SINK, 0, 0);
- if (!ep_fwnode)
- return dev_err_probe(dev, -ENOENT, "no endpoint found\n");
-
- ret = v4l2_fwnode_endpoint_parse(ep_fwnode, &vep);
-
- fwnode_handle_put(ep_fwnode);
-
- if (ret < 0)
- return dev_err_probe(dev, ret, "Failed to parse sink endpoint");
-
- num_data_lanes = vep.bus.mipi_csi2.num_data_lanes;
- if (num_data_lanes < 1 || num_data_lanes > MAX96717_CSI_NLANES)
- return dev_err_probe(dev, -EINVAL,
- "Invalid data lanes must be 1 to 4\n");
-
- priv->mipi_csi2 = vep.bus.mipi_csi2;
-
- return 0;
-}
-
-static int max96717_probe(struct i2c_client *client)
-{
- struct device *dev = &client->dev;
- struct max96717_priv *priv;
- int ret;
-
- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- priv->client = client;
- priv->regmap = devm_cci_regmap_init_i2c(client, 16);
- if (IS_ERR(priv->regmap)) {
- ret = PTR_ERR(priv->regmap);
- return dev_err_probe(dev, ret, "Failed to init regmap\n");
- }
-
- ret = max96717_parse_dt(priv);
- if (ret)
- return dev_err_probe(dev, ret, "Failed to parse the dt\n");
-
- ret = max96717_hw_init(priv);
- if (ret)
- return dev_err_probe(dev, ret,
- "Failed to initialize the hardware\n");
-
- ret = max96717_gpiochip_probe(priv);
- if (ret)
- return dev_err_probe(&client->dev, ret,
- "Failed to init gpiochip\n");
-
- ret = max96717_register_clkout(priv);
- if (ret)
- return dev_err_probe(dev, ret, "Failed to register clkout\n");
-
- ret = max96717_subdev_init(priv);
- if (ret)
- return dev_err_probe(dev, ret,
- "Failed to initialize v4l2 subdev\n");
-
- ret = max96717_i2c_mux_init(priv);
- if (ret) {
- dev_err_probe(dev, ret, "failed to add remote i2c adapter\n");
- max96717_subdev_uninit(priv);
- }
-
- return ret;
-}
-
-static void max96717_remove(struct i2c_client *client)
-{
- struct v4l2_subdev *sd = i2c_get_clientdata(client);
- struct max96717_priv *priv = sd_to_max96717(sd);
-
- max96717_subdev_uninit(priv);
- i2c_mux_del_adapters(priv->mux);
-}
-
-static const struct of_device_id max96717_of_ids[] = {
- { .compatible = "maxim,max96717f" },
- { }
-};
-MODULE_DEVICE_TABLE(of, max96717_of_ids);
-
-static struct i2c_driver max96717_i2c_driver = {
- .driver = {
- .name = "max96717",
- .of_match_table = max96717_of_ids,
- },
- .probe = max96717_probe,
- .remove = max96717_remove,
-};
-
-module_i2c_driver(max96717_i2c_driver);
-
-MODULE_DESCRIPTION("Maxim GMSL2 MAX96717 Serializer Driver");
-MODULE_AUTHOR("Julien Massot <julien.massot@collabora.com>");
-MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH v13 18/22] media: i2c: maxim-serdes: add MAX9296A driver
From: Dumitru Ceclan via B4 Relay @ 2026-06-04 14:14 UTC (permalink / raw)
To: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
Laurent Pinchart, Julien Massot, Rob Herring,
Niklas Söderlund, Greg Kroah-Hartman
Cc: mitrutzceclan, linux-media, linux-kernel, devicetree,
linux-staging, linux-gpio, Niklas Söderlund, Martin Hecht,
Cosmin Tanislav
In-Reply-To: <20260604-gmsl2-3_serdes-v13-0-9d8a4919983b@analog.com>
From: Cosmin Tanislav <demonsingur@gmail.com>
Add a new MAX9296A driver that also supports MAX96714, MAX96714F,
MAX96714R, MAX96716A and MAX96792A.
Integrate it with the common Deserializer framework, while keeping
compatibility with existing usecases, avoiding code duplication, and
also enabling more features across all chips.
Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
---
drivers/media/i2c/maxim-serdes/Kconfig | 14 +
drivers/media/i2c/maxim-serdes/Makefile | 1 +
drivers/media/i2c/maxim-serdes/max9296a.c | 1369 +++++++++++++++++++++++++++++
3 files changed, 1384 insertions(+)
diff --git a/drivers/media/i2c/maxim-serdes/Kconfig b/drivers/media/i2c/maxim-serdes/Kconfig
index 9d3621ae8d90..d0c70d678699 100644
--- a/drivers/media/i2c/maxim-serdes/Kconfig
+++ b/drivers/media/i2c/maxim-serdes/Kconfig
@@ -47,3 +47,17 @@ config VIDEO_MAX96724
To compile this driver as a module, choose M here: the module
will be called max96724.
+
+config VIDEO_MAX9296A
+ tristate "Maxim MAX9296A Dual Deserializer support"
+ depends on I2C
+ depends on VIDEO_DEV
+ select VIDEO_MAXIM_SERDES
+ help
+ This driver supports the Maxim MAX9296A, MAX96716A, MAX96792A
+ Dual Deserializers, and the MAX96714, MAX96714F, MAX96714R
+ Single Deserializers, which convert from up to two GMSL2/3
+ links to up to two MIPI D-PHY outputs.
+
+ To compile this driver as a module, choose M here: the module
+ will be called max9296a.
diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
index b6d5aebfaee1..ae306bc33bfb 100644
--- a/drivers/media/i2c/maxim-serdes/Makefile
+++ b/drivers/media/i2c/maxim-serdes/Makefile
@@ -3,3 +3,4 @@ max-serdes-objs := max_serdes.o max_ser.o max_des.o
obj-$(CONFIG_VIDEO_MAXIM_SERDES) += max-serdes.o
obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
obj-$(CONFIG_VIDEO_MAX96724) += max96724.o
+obj-$(CONFIG_VIDEO_MAX9296A) += max9296a.o
diff --git a/drivers/media/i2c/maxim-serdes/max9296a.c b/drivers/media/i2c/maxim-serdes/max9296a.c
new file mode 100644
index 000000000000..3dcdb63dbb9e
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max9296a.c
@@ -0,0 +1,1369 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Maxim MAX9296A Quad GMSL2 Deserializer Driver
+ *
+ * Copyright (C) 2025 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/regmap.h>
+
+#include <media/mipi-csi2.h>
+
+#include "max_des.h"
+
+#define MAX9296A_REG0 0x0
+
+#define MAX9296A_REG1 0x1
+#define MAX9296A_REG1_RX_RATE_A GENMASK(1, 0)
+#define MAX9296A_REG1_RX_RATE_3GBPS 0b01
+#define MAX9296A_REG1_RX_RATE_6GBPS 0b10
+#define MAX9296A_REG1_RX_RATE_12GBPS 0b11
+
+#define MAX9296A_REG2 0x2
+#define MAX9296A_REG2_VID_EN(p) BIT((p) + 4)
+
+#define MAX9296A_REG4 0x4
+#define MAX9296A_REG4_GMSL3_X(x) BIT((x) + 6)
+#define MAX9296A_REG4_RX_RATE_B GENMASK(1, 0)
+
+#define MAX9296A_REG6 0x6
+#define MAX9296A_REG6_GMSL2_X(x) BIT((x) + 6)
+
+#define MAX9296A_CTRL0 0x10
+#define MAX9296A_CTRL0_LINK_CFG GENMASK(1, 0)
+#define MAX9296A_CTRL0_AUTO_LINK BIT(4)
+#define MAX9296A_CTRL0_RESET_ONESHOT BIT(5)
+#define MAX9296A_CTRL0_RESET_ALL BIT(7)
+
+#define MAX9296A_CTRL2 0x12
+#define MAX9296A_CTRL2_RESET_ONESHOT_B BIT(5)
+
+#define MAX9296A_MIPI_TX0(x) (0x28 + (x) * 0x5000)
+#define MAX9296A_MIPI_TX0_RX_FEC_EN BIT(1)
+
+#define MAX9296A_IO_CHK0 0x38
+#define MAX9296A_IO_CHK0_PIN_DRV_EN_0 GENMASK(1, 0)
+#define MAX9296A_IO_CHK0_PIN_DRV_EN_0_25MHZ 0b00
+#define MAX9296A_IO_CHK0_PIN_DRV_EN_0_75MHZ 0b01
+#define MAX9296A_IO_CHK0_PIN_DRV_EN_0_USE_PIPE 0b10
+
+#define MAX9296A_RX50(p) (0x50 + (p))
+#define MAX9296A_RX50_STR_SEL GENMASK(1, 0)
+
+#define MAX9296A_VIDEO_PIPE_EN 0x160
+#define MAX9296A_VIDEO_PIPE_EN_MASK(p) BIT(p)
+
+#define MAX9296A_VIDEO_PIPE_SEL 0x161
+#define MAX9296A_VIDEO_PIPE_SEL_STREAM(p) (GENMASK(1, 0) << ((p) * 3))
+#define MAX9296A_VIDEO_PIPE_SEL_LINK(p) BIT(2 + (p) * 3)
+
+#define MAX9296A_VPRBS(p) (0x1fc + (p) * 0x20)
+#define MAX9296A_VPRBS_VIDEO_LOCK BIT(0)
+#define MAX9296A_VPRBS_PATGEN_CLK_SRC BIT(7)
+#define MAX9296A_VPRBS_PATGEN_CLK_SRC_150MHZ 0b0
+#define MAX9296A_VPRBS_PATGEN_CLK_SRC_600MHZ 0b1
+
+#define MAX9296A_PATGEN_0 0x240
+#define MAX9296A_PATGEN_0_VTG_MODE GENMASK(1, 0)
+#define MAX9296A_PATGEN_0_VTG_MODE_FREE_RUNNING 0b11
+#define MAX9296A_PATGEN_0_DE_INV BIT(2)
+#define MAX9296A_PATGEN_0_HS_INV BIT(3)
+#define MAX9296A_PATGEN_0_VS_INV BIT(4)
+#define MAX9296A_PATGEN_0_GEN_DE BIT(5)
+#define MAX9296A_PATGEN_0_GEN_HS BIT(6)
+#define MAX9296A_PATGEN_0_GEN_VS BIT(7)
+
+#define MAX9296A_PATGEN_1 0x241
+#define MAX9296A_PATGEN_1_PATGEN_MODE GENMASK(5, 4)
+#define MAX9296A_PATGEN_1_PATGEN_MODE_DISABLED 0b00
+#define MAX9296A_PATGEN_1_PATGEN_MODE_CHECKER 0b11
+#define MAX9296A_PATGEN_1_PATGEN_MODE_GRADIENT 0b10
+
+#define MAX9296A_VS_DLY_2 0x242
+#define MAX9296A_VS_HIGH_2 0x245
+#define MAX9296A_VS_LOW_2 0x248
+#define MAX9296A_V2H_2 0x24b
+#define MAX9296A_HS_HIGH_1 0x24e
+#define MAX9296A_HS_LOW_1 0x250
+#define MAX9296A_HS_CNT_1 0x252
+#define MAX9296A_V2D_2 0x254
+#define MAX9296A_DE_HIGH_1 0x257
+#define MAX9296A_DE_LOW_1 0x259
+#define MAX9296A_DE_CNT_1 0x25b
+#define MAX9296A_GRAD_INCR 0x25d
+#define MAX9296A_CHKR_COLOR_A_L 0x25e
+#define MAX9296A_CHKR_COLOR_B_L 0x261
+#define MAX9296A_CHKR_RPT_A 0x264
+#define MAX9296A_CHKR_RPT_B 0x265
+#define MAX9296A_CHKR_ALT 0x266
+
+#define MAX9296A_BACKTOP12 0x313
+#define MAX9296A_BACKTOP12_CSI_OUT_EN BIT(1)
+
+#define MAX9296A_BACKTOP21 0x31c
+#define MAX9296A_BACKTOP21_BPP8DBL(p) BIT(4 + (p))
+
+#define MAX9296A_BACKTOP22(x) (0x31d + (x) * 0x3)
+#define MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL GENMASK(4, 0)
+#define MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL_EN BIT(5)
+
+#define MAX9296A_BACKTOP24 0x31f
+#define MAX9296A_BACKTOP24_BPP8DBL_MODE(p) BIT(4 + (p))
+
+#define MAX9296A_BACKTOP32 0x327
+#define MAX9296A_BACKTOP32_BPP10DBL(p) BIT(p)
+#define MAX9296A_BACKTOP32_BPP10DBL_MODE(p) BIT(4 + (p))
+
+#define MAX9296A_BACKTOP33 0x328
+#define MAX9296A_BACKTOP32_BPP12DBL(p) BIT(p)
+
+#define MAX9296A_MIPI_PHY0 0x330
+#define MAX9296A_MIPI_PHY0_FORCE_CSI_OUT_EN BIT(7)
+
+#define MAX9296A_MIPI_PHY2 0x332
+#define MAX9296A_MIPI_PHY2_PHY_STDBY_N(x) (GENMASK(5, 4) << ((x) * 2))
+
+#define MAX9296A_MIPI_PHY3(x) (0x333 + (x))
+#define MAX9296A_MIPI_PHY3_PHY_LANE_MAP_4 GENMASK(7, 0)
+
+#define MAX9296A_MIPI_PHY5(x) (0x335 + (x))
+#define MAX9296A_MIPI_PHY5_PHY_POL_MAP_0_1 GENMASK(1, 0)
+#define MAX9296A_MIPI_PHY5_PHY_POL_MAP_2_3 GENMASK(4, 3)
+#define MAX9296A_MIPI_PHY5_PHY_POL_MAP_CLK(x) BIT((x) == 0 ? 5 : 2)
+
+#define MAX9296A_MIPI_PHY18 0x342
+#define MAX9296A_MIPI_PHY18_CSI2_TX_PKT_CNT(x) (GENMASK(3, 0) << (4 * (x)))
+
+#define MAX9296A_MIPI_PHY20(x) (0x344 + (x))
+
+#define MAX9296A_MIPI_TX3(x) (0x403 + (x) * 0x40)
+#define MAX9296A_MIPI_TX3_DESKEW_INIT_8X32K FIELD_PREP(GENMASK(2, 0), 0b001)
+#define MAX9296A_MIPI_TX3_DESKEW_INIT_AUTO BIT(7)
+
+#define MAX9296A_MIPI_TX4(x) (0x404 + (x) * 0x40)
+#define MAX9296A_MIPI_TX4_DESKEW_PER_2K FIELD_PREP(GENMASK(2, 0), 0b001)
+#define MAX9296A_MIPI_TX4_DESKEW_PER_AUTO BIT(7)
+
+#define MAX9296A_MIPI_TX10(x) (0x40a + (x) * 0x40)
+#define MAX9296A_MIPI_TX10_CSI2_LANE_CNT GENMASK(7, 6)
+#define MAX9296A_MIPI_TX10_CSI2_CPHY_EN BIT(5)
+
+#define MAX9296A_MIPI_TX11(p) (0x40b + (p) * 0x40)
+#define MAX9296A_MIPI_TX12(p) (0x40c + (p) * 0x40)
+
+#define MAX9296A_MIPI_TX13(p, x) (0x40d + (p) * 0x40 + (x) * 0x2)
+#define MAX9296A_MIPI_TX13_MAP_SRC_DT GENMASK(5, 0)
+#define MAX9296A_MIPI_TX13_MAP_SRC_VC GENMASK(7, 6)
+
+#define MAX9296A_MIPI_TX14(p, x) (0x40e + (p) * 0x40 + (x) * 0x2)
+#define MAX9296A_MIPI_TX14_MAP_DST_DT GENMASK(5, 0)
+#define MAX9296A_MIPI_TX14_MAP_DST_VC GENMASK(7, 6)
+
+#define MAX9296A_MIPI_TX45(p, x) (0x42d + (p) * 0x40 + (x) / 4)
+#define MAX9296A_MIPI_TX45_MAP_DPHY_DEST(x) (GENMASK(1, 0) << (2 * ((x) % 4)))
+
+#define MAX9296A_MIPI_TX51(x) (0x433 + (x) * 0x40)
+#define MAX9296A_MIPI_TX51_ALT_MEM_MAP_12 BIT(0)
+#define MAX9296A_MIPI_TX51_ALT_MEM_MAP_8 BIT(1)
+#define MAX9296A_MIPI_TX51_ALT_MEM_MAP_10 BIT(2)
+#define MAX9296A_MIPI_TX51_ALT2_MEM_MAP_8 BIT(4)
+
+#define MAX9296A_MIPI_TX52(x) (0x434 + (x) * 0x40)
+#define MAX9296A_MIPI_TX52_TUN_DEST BIT(1)
+#define MAX9296A_MIPI_TX52_TUN_EN BIT(0)
+
+#define MAX9296A_GMSL1_EN 0xf00
+#define MAX9296A_GMSL1_EN_LINK_EN GENMASK(1, 0)
+
+#define MAX9296A_RLMS3E(x) (0x143e + (x) * 0x100)
+#define MAX9296A_RLMS3F(x) (0x143f + (x) * 0x100)
+#define MAX9296A_RLMS49(x) (0x1449 + (x) * 0x100)
+#define MAX9296A_RLMS7E(x) (0x147e + (x) * 0x100)
+#define MAX9296A_RLMS7F(x) (0x147f + (x) * 0x100)
+#define MAX9296A_RLMSA3(x) (0x14a3 + (x) * 0x100)
+#define MAX9296A_RLMSA5(x) (0x14a5 + (x) * 0x100)
+#define MAX9296A_RLMSD8(x) (0x14d8 + (x) * 0x100)
+
+#define MAX9296A_DPLL_0(x) (0x1c00 + (x) * 0x100)
+#define MAX9296A_DPLL_0_CONFIG_SOFT_RST_N BIT(0)
+
+#define MAX9296A_PIPES_NUM 4
+#define MAX9296A_PHYS_NUM 2
+
+static const struct regmap_config max9296a_i2c_regmap = {
+ .reg_bits = 16,
+ .val_bits = 8,
+};
+
+struct max9296a_priv {
+ struct max_des des;
+ const struct max9296a_chip_info *info;
+
+ struct device *dev;
+ struct i2c_client *client;
+ struct regmap *regmap;
+
+ struct gpio_desc *gpiod_pwdn;
+};
+
+struct max9296a_chip_info {
+ const struct max_des_info *info;
+ const struct max_des_ops *ops;
+ const struct reg_sequence *rlms_adjust_sequence;
+ unsigned int rlms_adjust_sequence_len;
+ unsigned int max_register;
+ unsigned int pipe_hw_ids[MAX9296A_PIPES_NUM];
+ unsigned int phy_hw_ids[MAX9296A_PHYS_NUM];
+ bool has_per_link_reset;
+ bool phy0_lanes_0_1_on_second_phy;
+ bool polarity_on_physical_lanes;
+ bool supports_cphy;
+ bool supports_phy_log;
+};
+
+#define des_to_priv(_des) \
+ container_of(_des, struct max9296a_priv, des)
+
+static int max9296a_wait_for_device(struct max9296a_priv *priv)
+{
+ unsigned int val;
+ int ret, err;
+
+ err = read_poll_timeout(regmap_read, ret,
+ !ret && val,
+ 100 * USEC_PER_MSEC,
+ 1 * USEC_PER_SEC, false,
+ priv->regmap, MAX9296A_REG0, &val);
+ if (err)
+ dev_err(priv->dev, "Timeout waiting for deserializer: %d\n", ret);
+
+ return err;
+}
+
+static int max9296a_reset(struct max9296a_priv *priv)
+{
+ int ret;
+
+ ret = max9296a_wait_for_device(priv);
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(priv->regmap, MAX9296A_CTRL0,
+ MAX9296A_CTRL0_RESET_ALL);
+ if (ret)
+ return ret;
+
+ msleep(100);
+
+ return max9296a_wait_for_device(priv);
+}
+
+static unsigned int max9296a_pipe_id(struct max9296a_priv *priv,
+ struct max_des_pipe *pipe)
+{
+ return priv->info->pipe_hw_ids[pipe->index];
+}
+
+static unsigned int max9296a_phy_id(struct max9296a_priv *priv,
+ struct max_des_phy *phy)
+{
+ return priv->info->phy_hw_ids[phy->index];
+}
+
+static int __maybe_unused max9296a_reg_read(struct max_des *des, unsigned int reg,
+ unsigned int *val)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+
+ return regmap_read(priv->regmap, reg, val);
+}
+
+static int __maybe_unused max9296a_reg_write(struct max_des *des, unsigned int reg,
+ unsigned int val)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+
+ return regmap_write(priv->regmap, reg, val);
+}
+
+static int max9626a_log_pipe_status(struct max_des *des,
+ struct max_des_pipe *pipe)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ unsigned int index = max9296a_pipe_id(priv, pipe);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(priv->regmap, MAX9296A_VPRBS(index), &val);
+ if (ret)
+ return ret;
+
+ dev_info(priv->dev, "\tvideo_lock: %u\n",
+ !!(val & MAX9296A_VPRBS_VIDEO_LOCK));
+
+ return 0;
+}
+
+static int max9296a_log_phy_status(struct max_des *des,
+ struct max_des_phy *phy)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ unsigned int index = phy->index;
+ unsigned int val;
+ int ret;
+
+ if (!priv->info->supports_phy_log)
+ return 0;
+
+ ret = regmap_read(priv->regmap, MAX9296A_MIPI_PHY18, &val);
+ if (ret)
+ return ret;
+
+ dev_info(priv->dev, "\tcsi2_pkt_cnt: %lu\n",
+ field_get(MAX9296A_MIPI_PHY18_CSI2_TX_PKT_CNT(index), val));
+
+ ret = regmap_read(priv->regmap, MAX9296A_MIPI_PHY20(index), &val);
+ if (ret)
+ return ret;
+
+ dev_info(priv->dev, "\tphy_pkt_cnt: %u\n", val);
+
+ return 0;
+}
+
+static int max9296a_set_enable(struct max_des *des, bool enable)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+
+ return regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP12,
+ MAX9296A_BACKTOP12_CSI_OUT_EN, enable);
+}
+
+static int max9296a_init_tpg(struct max_des *des)
+{
+ const struct reg_sequence regs[] = {
+ { MAX9296A_GRAD_INCR, MAX_SERDES_GRAD_INCR },
+ REG_SEQUENCE_3_LE(MAX9296A_CHKR_COLOR_A_L,
+ MAX_SERDES_CHECKER_COLOR_A),
+ REG_SEQUENCE_3_LE(MAX9296A_CHKR_COLOR_B_L,
+ MAX_SERDES_CHECKER_COLOR_B),
+ { MAX9296A_CHKR_RPT_A, MAX_SERDES_CHECKER_SIZE },
+ { MAX9296A_CHKR_RPT_B, MAX_SERDES_CHECKER_SIZE },
+ { MAX9296A_CHKR_ALT, MAX_SERDES_CHECKER_SIZE },
+ };
+ struct max9296a_priv *priv = des_to_priv(des);
+
+ return regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
+}
+
+static int max9296a_init(struct max_des *des)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ int ret;
+
+ if (priv->info->rlms_adjust_sequence) {
+ ret = regmap_multi_reg_write(priv->regmap,
+ priv->info->rlms_adjust_sequence,
+ priv->info->rlms_adjust_sequence_len);
+ if (ret)
+ return ret;
+ }
+
+ return max9296a_init_tpg(des);
+}
+
+static int max9296a_init_phy(struct max_des *des, struct max_des_phy *phy)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ bool is_cphy = phy->bus_type == V4L2_MBUS_CSI2_CPHY;
+ unsigned int num_data_lanes = phy->mipi.num_data_lanes;
+ unsigned int dpll_freq = is_cphy ? phy->link_frequency
+ : phy->link_frequency * 2;
+ unsigned int num_hw_data_lanes;
+ unsigned int hw_index = max9296a_phy_id(priv, phy);
+ unsigned int index = phy->index;
+ unsigned int used_data_lanes = 0;
+ unsigned int val;
+ unsigned int i;
+ int ret;
+
+ if (is_cphy && !priv->info->supports_cphy) {
+ dev_err(priv->dev, "CPHY not supported\n");
+ return -EINVAL;
+ }
+
+ num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy);
+
+ /*
+ * MAX9296A has four PHYs, but does not support single-PHY configurations,
+ * only double-PHY configurations, even when only using two lanes.
+ * For PHY 0 + PHY 1, PHY 1 is the master PHY.
+ * For PHY 2 + PHY 3, PHY 2 is the master PHY.
+ * Clock is always on the master PHY.
+ * For first pair of PHYs, first lanes are on the master PHY.
+ * For second pair of PHYs, first lanes are on the master PHY too.
+ *
+ * PHY 0 + 1
+ * CLK = PHY 1
+ * PHY1 Lane 0 = D0
+ * PHY1 Lane 1 = D1
+ * PHY0 Lane 0 = D2
+ * PHY0 Lane 1 = D3
+ *
+ * PHY 2 + 3
+ * CLK = PHY 2
+ * PHY2 Lane 0 = D0
+ * PHY2 Lane 1 = D1
+ * PHY3 Lane 0 = D2
+ * PHY3 Lane 1 = D3
+ *
+ * MAX96714 only has two PHYs which cannot support single-PHY configurations.
+ * Clock is always on the master PHY, first lanes are on PHY 0, even if
+ * PHY 1 is the master PHY.
+ *
+ * PHY 0 + 1
+ * CLK = PHY 1
+ * PHY0 Lane 0 = D0
+ * PHY0 Lane 1 = D1
+ * PHY1 Lane 0 = D2
+ * PHY1 Lane 1 = D3
+ */
+
+ /* Configure a lane count. */
+ ret = regmap_update_bits(priv->regmap, MAX9296A_MIPI_TX10(hw_index),
+ MAX9296A_MIPI_TX10_CSI2_LANE_CNT,
+ FIELD_PREP(MAX9296A_MIPI_TX10_CSI2_LANE_CNT,
+ num_data_lanes - 1));
+ if (ret)
+ return ret;
+
+ ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX10(hw_index),
+ MAX9296A_MIPI_TX10_CSI2_CPHY_EN, is_cphy);
+ if (ret)
+ return ret;
+
+ /* Configure lane mapping. */
+ /*
+ * The lane of each PHY can be mapped to physical lanes 0, 1, 2, and 3.
+ * This mapping is exclusive, multiple lanes, even if unused cannot be
+ * mapped to the same physical lane.
+ * Each lane mapping is represented as two bits.
+ */
+ val = 0;
+ for (i = 0; i < num_hw_data_lanes ; i++) {
+ unsigned int map;
+
+ if (i < num_data_lanes)
+ map = phy->mipi.data_lanes[i] - 1;
+ else
+ map = ffz(used_data_lanes);
+
+ val |= map << (i * 2);
+ used_data_lanes |= BIT(map);
+ }
+
+ if (phy->index == 0 && priv->info->phy0_lanes_0_1_on_second_phy)
+ val = ((val & 0xf) << 4) | ((val >> 4) & 0xf);
+
+ ret = regmap_update_bits(priv->regmap, MAX9296A_MIPI_PHY3(index),
+ MAX9296A_MIPI_PHY3_PHY_LANE_MAP_4,
+ FIELD_PREP(MAX9296A_MIPI_PHY3_PHY_LANE_MAP_4, val));
+ if (ret)
+ return ret;
+
+ /*
+ * Configure lane polarity.
+ *
+ * PHY 0 and 1 are on register 0x335.
+ * PHY 2 and 3 are on register 0x336.
+ *
+ * Each PHY has 3 bits of polarity configuration.
+ *
+ * On MAX9296A, each bit represents the lane polarity of logical lanes.
+ * Each of these lanes can be mapped to any physical lane.
+ * 0th bit is for lane 0.
+ * 1st bit is for lane 1.
+ * 2nd bit is for clock lane.
+ *
+ * On MAX96714, each bit represents the lane polarity of physical lanes.
+ * 0th bit for physical lane 0.
+ * 1st bit for physical lane 1.
+ * 2nd bit for clock lane of PHY 0, the slave PHY, which is unused.
+ *
+ * 3rd bit for physical lane 2.
+ * 4th bit for physical lane 3.
+ * 5th bit for clock lane of PHY 1, the master PHY.
+ */
+
+ for (i = 0, val = 0; i < num_data_lanes; i++) {
+ unsigned int map;
+
+ if (!phy->mipi.lane_polarities[i + 1])
+ continue;
+
+ /*
+ * The numbers inside the data_lanes array specify the hardware
+ * lane each logical lane maps to.
+ * If polarity is set for the physical lanes, retrieve the
+ * physical lane matching the logical lane from data_lanes.
+ * Otherwise, when polarity is set for the logical lanes
+ * the index of the polarity can be used.
+ */
+
+ if (priv->info->polarity_on_physical_lanes)
+ map = phy->mipi.data_lanes[i] - 1;
+ else
+ map = i;
+
+ val |= BIT(map);
+ }
+
+ if (phy->index == 0 && priv->info->phy0_lanes_0_1_on_second_phy)
+ val = ((val & 0x3) << 2) | ((val >> 2) & 0x3);
+
+ ret = regmap_update_bits(priv->regmap, MAX9296A_MIPI_PHY5(index),
+ MAX9296A_MIPI_PHY5_PHY_POL_MAP_0_1 |
+ MAX9296A_MIPI_PHY5_PHY_POL_MAP_2_3,
+ FIELD_PREP(MAX9296A_MIPI_PHY5_PHY_POL_MAP_0_1, val) |
+ FIELD_PREP(MAX9296A_MIPI_PHY5_PHY_POL_MAP_2_3, val >> 2));
+ if (ret)
+ return ret;
+
+ ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_PHY5(index),
+ MAX9296A_MIPI_PHY5_PHY_POL_MAP_CLK(index),
+ phy->mipi.lane_polarities[0]);
+ if (ret)
+ return ret;
+
+ /* Put DPLL block into reset. */
+ ret = regmap_clear_bits(priv->regmap, MAX9296A_DPLL_0(hw_index),
+ MAX9296A_DPLL_0_CONFIG_SOFT_RST_N);
+ if (ret)
+ return ret;
+
+ /* Set DPLL frequency. */
+ ret = regmap_update_bits(priv->regmap, MAX9296A_BACKTOP22(hw_index),
+ MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL,
+ FIELD_PREP(MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL,
+ div_u64(dpll_freq, 100000000)));
+ if (ret)
+ return ret;
+
+ /* Enable DPLL frequency. */
+ ret = regmap_set_bits(priv->regmap, MAX9296A_BACKTOP22(hw_index),
+ MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL_EN);
+ if (ret)
+ return ret;
+
+ /* Pull DPLL block out of reset. */
+ ret = regmap_set_bits(priv->regmap, MAX9296A_DPLL_0(hw_index),
+ MAX9296A_DPLL_0_CONFIG_SOFT_RST_N);
+ if (ret)
+ return ret;
+
+ if (dpll_freq > 1500000000ull) {
+ /* Enable initial deskew with 2 x 32k UI. */
+ ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX3(hw_index),
+ MAX9296A_MIPI_TX3_DESKEW_INIT_AUTO |
+ MAX9296A_MIPI_TX3_DESKEW_INIT_8X32K);
+ if (ret)
+ return ret;
+
+ /* Enable periodic deskew with 2 x 1k UI.. */
+ ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX4(hw_index),
+ MAX9296A_MIPI_TX4_DESKEW_PER_AUTO |
+ MAX9296A_MIPI_TX4_DESKEW_PER_2K);
+ if (ret)
+ return ret;
+ } else {
+ /* Disable initial deskew. */
+ ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX3(hw_index), 0x0);
+ if (ret)
+ return ret;
+
+ /* Disable periodic deskew. */
+ ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX4(hw_index), 0x0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int max9296a_set_phy_mode(struct max_des *des, struct max_des_phy *phy,
+ struct max_des_phy_mode *mode)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ unsigned int phy_id = max9296a_phy_id(priv, phy);
+ int ret;
+
+ /* Set alternate memory map modes. */
+ ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX51(phy_id),
+ MAX9296A_MIPI_TX51_ALT_MEM_MAP_12,
+ mode->alt_mem_map12);
+ if (ret)
+ return ret;
+
+ ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX51(phy_id),
+ MAX9296A_MIPI_TX51_ALT_MEM_MAP_8,
+ mode->alt_mem_map8);
+ if (ret)
+ return ret;
+
+ ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX51(phy_id),
+ MAX9296A_MIPI_TX51_ALT_MEM_MAP_10,
+ mode->alt_mem_map10);
+ if (ret)
+ return ret;
+
+ return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX51(phy_id),
+ MAX9296A_MIPI_TX51_ALT2_MEM_MAP_8,
+ mode->alt2_mem_map8);
+}
+
+static int max9296a_set_phy_enable(struct max_des *des, struct max_des_phy *phy,
+ bool enable)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+
+ return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_PHY2,
+ MAX9296A_MIPI_PHY2_PHY_STDBY_N(phy->index), enable);
+}
+
+static int max9296a_set_pipe_remap(struct max_des *des,
+ struct max_des_pipe *pipe,
+ unsigned int i,
+ struct max_des_remap *remap)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ struct max_des_phy *phy = &des->phys[remap->phy];
+ unsigned int phy_id = max9296a_phy_id(priv, phy);
+ unsigned int index = max9296a_pipe_id(priv, pipe);
+ int ret;
+
+ /* Set source Data Type and Virtual Channel. */
+ /* TODO: implement extended Virtual Channel. */
+ ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX13(index, i),
+ FIELD_PREP(MAX9296A_MIPI_TX13_MAP_SRC_DT,
+ remap->from_dt) |
+ FIELD_PREP(MAX9296A_MIPI_TX13_MAP_SRC_VC,
+ remap->from_vc));
+ if (ret)
+ return ret;
+
+ /* Set destination Data Type and Virtual Channel. */
+ /* TODO: implement extended Virtual Channel. */
+ ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX14(index, i),
+ FIELD_PREP(MAX9296A_MIPI_TX14_MAP_DST_DT,
+ remap->to_dt) |
+ FIELD_PREP(MAX9296A_MIPI_TX14_MAP_DST_VC,
+ remap->to_vc));
+ if (ret)
+ return ret;
+
+ /* Set destination PHY. */
+ return regmap_update_bits(priv->regmap, MAX9296A_MIPI_TX45(index, i),
+ MAX9296A_MIPI_TX45_MAP_DPHY_DEST(i),
+ field_prep(MAX9296A_MIPI_TX45_MAP_DPHY_DEST(i),
+ phy_id));
+}
+
+static int max9296a_set_pipe_remaps_enable(struct max_des *des,
+ struct max_des_pipe *pipe,
+ unsigned int mask)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ unsigned int index = max9296a_pipe_id(priv, pipe);
+ int ret;
+
+ ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX11(index), mask);
+ if (ret)
+ return ret;
+
+ return regmap_write(priv->regmap, MAX9296A_MIPI_TX12(index), mask >> 8);
+}
+
+static int max9296a_set_pipe_enable(struct max_des *des, struct max_des_pipe *pipe,
+ bool enable)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ unsigned int index = max9296a_pipe_id(priv, pipe);
+
+ return regmap_assign_bits(priv->regmap, MAX9296A_REG2,
+ MAX9296A_REG2_VID_EN(index), enable);
+}
+
+static int max96714_set_pipe_enable(struct max_des *des, struct max_des_pipe *pipe,
+ bool enable)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ unsigned int index = max9296a_pipe_id(priv, pipe);
+
+ return regmap_assign_bits(priv->regmap, MAX9296A_VIDEO_PIPE_EN,
+ MAX9296A_VIDEO_PIPE_EN_MASK(index - 1), enable);
+}
+
+static int max96714_set_pipe_tunnel_enable(struct max_des *des,
+ struct max_des_pipe *pipe, bool enable)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ unsigned int index = max9296a_pipe_id(priv, pipe);
+
+ return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX52(index),
+ MAX9296A_MIPI_TX52_TUN_EN, enable);
+}
+
+static int max9296a_set_pipe_stream_id(struct max_des *des, struct max_des_pipe *pipe,
+ unsigned int stream_id)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ unsigned int index = max9296a_pipe_id(priv, pipe);
+
+ return regmap_update_bits(priv->regmap, MAX9296A_RX50(index), MAX9296A_RX50_STR_SEL,
+ FIELD_PREP(MAX9296A_RX50_STR_SEL, stream_id));
+}
+
+static int max96714_set_pipe_stream_id(struct max_des *des, struct max_des_pipe *pipe,
+ unsigned int stream_id)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ unsigned int index = pipe->index;
+
+ return regmap_update_bits(priv->regmap, MAX9296A_VIDEO_PIPE_SEL,
+ MAX9296A_VIDEO_PIPE_SEL_STREAM(index),
+ field_prep(MAX9296A_VIDEO_PIPE_SEL_STREAM(index),
+ stream_id));
+}
+
+static int max96716a_set_pipe_link(struct max_des *des, struct max_des_pipe *pipe,
+ struct max_des_link *link)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ unsigned int index = pipe->index;
+
+ return regmap_update_bits(priv->regmap, MAX9296A_VIDEO_PIPE_SEL,
+ MAX9296A_VIDEO_PIPE_SEL_LINK(index),
+ field_prep(MAX9296A_VIDEO_PIPE_SEL_LINK(index),
+ link->index));
+}
+
+static int max96716a_set_pipe_tunnel_phy(struct max_des *des,
+ struct max_des_pipe *pipe,
+ struct max_des_phy *phy)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ unsigned int index = max9296a_pipe_id(priv, pipe);
+
+ return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX52(index),
+ MAX9296A_MIPI_TX52_TUN_DEST, phy->index);
+}
+
+static int max9296a_set_pipe_mode(struct max_des *des,
+ struct max_des_pipe *pipe,
+ struct max_des_pipe_mode *mode)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ unsigned int index = max9296a_pipe_id(priv, pipe);
+ int ret;
+
+ /* Set 8bit double mode. */
+ ret = regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP21,
+ MAX9296A_BACKTOP21_BPP8DBL(index), mode->dbl8);
+ if (ret)
+ return ret;
+
+ ret = regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP24,
+ MAX9296A_BACKTOP24_BPP8DBL_MODE(index),
+ mode->dbl8mode);
+ if (ret)
+ return ret;
+
+ /* Set 10bit double mode. */
+ ret = regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP32,
+ MAX9296A_BACKTOP32_BPP10DBL(index), mode->dbl10);
+ if (ret)
+ return ret;
+
+ ret = regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP32,
+ MAX9296A_BACKTOP32_BPP10DBL_MODE(index),
+ mode->dbl10mode);
+ if (ret)
+ return ret;
+
+ /* Set 12bit double mode. */
+ /* TODO: check support for double mode on MAX96714. */
+ return regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP33,
+ MAX9296A_BACKTOP32_BPP12DBL(index), mode->dbl12);
+}
+
+static int max9296a_reset_link(struct max9296a_priv *priv, unsigned int index)
+{
+ unsigned int reg, mask;
+
+ if (index == 0) {
+ reg = MAX9296A_CTRL0;
+ mask = MAX9296A_CTRL0_RESET_ONESHOT;
+ } else {
+ reg = MAX9296A_CTRL2;
+ mask = MAX9296A_CTRL2_RESET_ONESHOT_B;
+ }
+
+ return regmap_set_bits(priv->regmap, reg, mask);
+}
+
+static int max9296a_select_links(struct max_des *des, unsigned int mask)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ int ret;
+
+ if (des->info->num_links == 1)
+ return 0;
+
+ if (!mask) {
+ dev_err(priv->dev, "Disable all links unsupported\n");
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(priv->regmap, MAX9296A_GMSL1_EN,
+ MAX9296A_GMSL1_EN_LINK_EN,
+ FIELD_PREP(MAX9296A_GMSL1_EN_LINK_EN, mask));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(priv->regmap, MAX9296A_CTRL0,
+ MAX9296A_CTRL0_AUTO_LINK |
+ MAX9296A_CTRL0_LINK_CFG |
+ MAX9296A_CTRL0_RESET_ONESHOT,
+ FIELD_PREP(MAX9296A_CTRL0_LINK_CFG, mask) |
+ FIELD_PREP(MAX9296A_CTRL0_RESET_ONESHOT, 1));
+ if (ret)
+ return ret;
+
+ if (priv->info->has_per_link_reset) {
+ ret = max9296a_reset_link(priv, 1);
+ if (ret)
+ return ret;
+ }
+
+ msleep(200);
+
+ return 0;
+}
+
+static int max9296a_set_link_version(struct max_des *des,
+ struct max_des_link *link,
+ enum max_serdes_gmsl_version version)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ unsigned int index = link->index;
+ bool gmsl3_en = version == MAX_SERDES_GMSL_3_12GBPS;
+ unsigned int reg, mask, val;
+ int ret;
+
+ if (des->info->needs_single_link_version)
+ index = 0;
+
+ if (index == 0) {
+ reg = MAX9296A_REG1;
+ mask = MAX9296A_REG1_RX_RATE_A;
+ } else {
+ reg = MAX9296A_REG4;
+ mask = MAX9296A_REG4_RX_RATE_B;
+ }
+
+ if (version == MAX_SERDES_GMSL_3_12GBPS)
+ val = MAX9296A_REG1_RX_RATE_12GBPS;
+ else if (version == MAX_SERDES_GMSL_2_6GBPS)
+ val = MAX9296A_REG1_RX_RATE_6GBPS;
+ else
+ val = MAX9296A_REG1_RX_RATE_3GBPS;
+
+ ret = regmap_update_bits(priv->regmap, reg, mask, field_prep(mask, val));
+ if (ret)
+ return ret;
+
+ if (!(des->info->versions & BIT(MAX_SERDES_GMSL_3_12GBPS)))
+ return 0;
+
+ ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX0(index),
+ MAX9296A_MIPI_TX0_RX_FEC_EN, gmsl3_en);
+ if (ret)
+ return ret;
+
+ ret = regmap_assign_bits(priv->regmap, MAX9296A_REG6,
+ MAX9296A_REG6_GMSL2_X(index), !gmsl3_en);
+ if (ret)
+ return ret;
+
+ return regmap_assign_bits(priv->regmap, MAX9296A_REG4,
+ MAX9296A_REG4_GMSL3_X(index), gmsl3_en);
+}
+
+static int max9296a_set_tpg_timings(struct max9296a_priv *priv,
+ const struct max_serdes_tpg_timings *tm)
+{
+ const struct reg_sequence regs[] = {
+ REG_SEQUENCE_3(MAX9296A_VS_DLY_2, tm->vs_dly),
+ REG_SEQUENCE_3(MAX9296A_VS_HIGH_2, tm->vs_high),
+ REG_SEQUENCE_3(MAX9296A_VS_LOW_2, tm->vs_low),
+ REG_SEQUENCE_3(MAX9296A_V2H_2, tm->v2h),
+ REG_SEQUENCE_2(MAX9296A_HS_HIGH_1, tm->hs_high),
+ REG_SEQUENCE_2(MAX9296A_HS_LOW_1, tm->hs_low),
+ REG_SEQUENCE_2(MAX9296A_HS_CNT_1, tm->hs_cnt),
+ REG_SEQUENCE_3(MAX9296A_V2D_2, tm->v2d),
+ REG_SEQUENCE_2(MAX9296A_DE_HIGH_1, tm->de_high),
+ REG_SEQUENCE_2(MAX9296A_DE_LOW_1, tm->de_low),
+ REG_SEQUENCE_2(MAX9296A_DE_CNT_1, tm->de_cnt),
+ };
+ int ret;
+
+ ret = regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
+ if (ret)
+ return ret;
+
+ return regmap_write(priv->regmap, MAX9296A_PATGEN_0,
+ FIELD_PREP(MAX9296A_PATGEN_0_VTG_MODE,
+ MAX9296A_PATGEN_0_VTG_MODE_FREE_RUNNING) |
+ FIELD_PREP(MAX9296A_PATGEN_0_DE_INV, tm->de_inv) |
+ FIELD_PREP(MAX9296A_PATGEN_0_HS_INV, tm->hs_inv) |
+ FIELD_PREP(MAX9296A_PATGEN_0_VS_INV, tm->vs_inv) |
+ FIELD_PREP(MAX9296A_PATGEN_0_GEN_DE, tm->gen_de) |
+ FIELD_PREP(MAX9296A_PATGEN_0_GEN_HS, tm->gen_hs) |
+ FIELD_PREP(MAX9296A_PATGEN_0_GEN_VS, tm->gen_vs));
+}
+
+static int max9296a_set_tpg_clk(struct max9296a_priv *priv, u32 clock)
+{
+ bool patgen_clk_src = 0;
+ u8 pin_drv_en;
+ int ret;
+
+ switch (clock) {
+ case 25000000:
+ pin_drv_en = MAX9296A_IO_CHK0_PIN_DRV_EN_0_25MHZ;
+ break;
+ case 75000000:
+ pin_drv_en = MAX9296A_IO_CHK0_PIN_DRV_EN_0_75MHZ;
+ break;
+ case 150000000:
+ pin_drv_en = MAX9296A_IO_CHK0_PIN_DRV_EN_0_USE_PIPE;
+ patgen_clk_src = MAX9296A_VPRBS_PATGEN_CLK_SRC_150MHZ;
+ break;
+ case 600000000:
+ pin_drv_en = MAX9296A_IO_CHK0_PIN_DRV_EN_0_USE_PIPE;
+ patgen_clk_src = MAX9296A_VPRBS_PATGEN_CLK_SRC_600MHZ;
+ break;
+ case 0:
+ return 0;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * TPG data is always injected on link 0, which is always routed to
+ * pipe 0.
+ */
+ ret = regmap_update_bits(priv->regmap, MAX9296A_VPRBS(0),
+ MAX9296A_VPRBS_PATGEN_CLK_SRC,
+ FIELD_PREP(MAX9296A_VPRBS_PATGEN_CLK_SRC,
+ patgen_clk_src));
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(priv->regmap, MAX9296A_IO_CHK0,
+ MAX9296A_IO_CHK0_PIN_DRV_EN_0,
+ FIELD_PREP(MAX9296A_IO_CHK0_PIN_DRV_EN_0,
+ pin_drv_en));
+}
+
+static int max9296a_set_tpg_mode(struct max9296a_priv *priv, bool enable)
+{
+ unsigned int patgen_mode;
+
+ switch (priv->des.tpg_pattern) {
+ case MAX_SERDES_TPG_PATTERN_GRADIENT:
+ patgen_mode = MAX9296A_PATGEN_1_PATGEN_MODE_GRADIENT;
+ break;
+ case MAX_SERDES_TPG_PATTERN_CHECKERBOARD:
+ patgen_mode = MAX9296A_PATGEN_1_PATGEN_MODE_CHECKER;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(priv->regmap, MAX9296A_PATGEN_1,
+ MAX9296A_PATGEN_1_PATGEN_MODE,
+ FIELD_PREP(MAX9296A_PATGEN_1_PATGEN_MODE,
+ enable ? patgen_mode
+ : MAX9296A_PATGEN_1_PATGEN_MODE_DISABLED));
+}
+
+static int max9296a_set_tpg(struct max_des *des,
+ const struct max_serdes_tpg_entry *entry)
+{
+ struct max9296a_priv *priv = des_to_priv(des);
+ struct max_serdes_tpg_timings timings = { 0 };
+ int ret;
+
+ ret = max_serdes_get_tpg_timings(entry, &timings);
+ if (ret)
+ return ret;
+
+ ret = max9296a_set_tpg_timings(priv, &timings);
+ if (ret)
+ return ret;
+
+ ret = max9296a_set_tpg_clk(priv, timings.clock);
+ if (ret)
+ return ret;
+
+ ret = max9296a_set_tpg_mode(priv, entry);
+ if (ret)
+ return ret;
+
+ return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_PHY0,
+ MAX9296A_MIPI_PHY0_FORCE_CSI_OUT_EN, !!entry);
+}
+
+static const struct max_serdes_tpg_entry max9296a_tpg_entries[] = {
+ MAX_TPG_ENTRY_640X480P60_RGB888,
+ MAX_TPG_ENTRY_1920X1080P30_RGB888,
+ MAX_TPG_ENTRY_1920X1080P60_RGB888,
+};
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+#define MAX9296A_DEBUG_OPS \
+ .reg_read = max9296a_reg_read, \
+ .reg_write = max9296a_reg_write,
+#else
+#define MAX9296A_DEBUG_OPS
+#endif
+
+#define MAX9296A_COMMON_INFO \
+ .num_remaps_per_pipe = 16, \
+ .tpg_entries = { \
+ .num_entries = ARRAY_SIZE(max9296a_tpg_entries), \
+ .entries = max9296a_tpg_entries, \
+ }, \
+ .tpg_patterns = BIT(MAX_SERDES_TPG_PATTERN_CHECKERBOARD) | \
+ BIT(MAX_SERDES_TPG_PATTERN_GRADIENT)
+
+#define MAX9296A_COMMON_OPS \
+ MAX9296A_DEBUG_OPS \
+ .log_pipe_status = max9626a_log_pipe_status, \
+ .log_phy_status = max9296a_log_phy_status, \
+ .set_enable = max9296a_set_enable, \
+ .init = max9296a_init, \
+ .init_phy = max9296a_init_phy, \
+ .set_phy_mode = max9296a_set_phy_mode, \
+ .set_phy_enable = max9296a_set_phy_enable, \
+ .set_pipe_remap = max9296a_set_pipe_remap, \
+ .set_pipe_remaps_enable = max9296a_set_pipe_remaps_enable, \
+ .set_pipe_mode = max9296a_set_pipe_mode, \
+ .set_tpg = max9296a_set_tpg, \
+ .select_links = max9296a_select_links, \
+ .set_link_version = max9296a_set_link_version
+
+static void max9296a_power_off(void *data)
+{
+ struct max9296a_priv *priv = data;
+
+ gpiod_set_value_cansleep(priv->gpiod_pwdn, 1);
+}
+
+static int max9296a_probe(struct i2c_client *client)
+{
+ struct regmap_config i2c_regmap = max9296a_i2c_regmap;
+ struct device *dev = &client->dev;
+ struct max9296a_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->info = device_get_match_data(dev);
+ if (!priv->info) {
+ dev_err(dev, "Failed to get match data\n");
+ return -ENODEV;
+ }
+
+ priv->dev = dev;
+ priv->client = client;
+ i2c_set_clientdata(client, priv);
+
+ i2c_regmap.max_register = priv->info->max_register;
+ priv->regmap = devm_regmap_init_i2c(client, &i2c_regmap);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->gpiod_pwdn = devm_gpiod_get_optional(&client->dev, "powerdown",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->gpiod_pwdn))
+ return PTR_ERR(priv->gpiod_pwdn);
+
+ if (priv->gpiod_pwdn) {
+ /* PWDN must be held for 1us for reset */
+ udelay(1);
+
+ gpiod_set_value_cansleep(priv->gpiod_pwdn, 0);
+ /* Maximum power-up time (tLOCK) 4ms */
+ usleep_range(4000, 5000);
+
+ ret = devm_add_action_or_reset(dev, max9296a_power_off,
+ priv);
+ if (ret)
+ return ret;
+ }
+
+ priv->des.info = priv->info->info;
+ priv->des.ops = priv->info->ops;
+
+ ret = max9296a_reset(priv);
+ if (ret)
+ return ret;
+
+ return max_des_probe(client, &priv->des);
+}
+
+static void max9296a_remove(struct i2c_client *client)
+{
+ struct max9296a_priv *priv = i2c_get_clientdata(client);
+
+ max_des_remove(&priv->des);
+}
+
+static const struct max_serdes_phys_config max9296a_phys_configs[] = {
+ { { 4, 4 } },
+};
+
+static const struct max_serdes_phys_config max96714_phys_configs[] = {
+ { { 4 } },
+};
+
+static const struct max_des_info max9296a_des_info = {
+ MAX9296A_COMMON_INFO,
+ .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) |
+ BIT(MAX_SERDES_GMSL_2_6GBPS),
+ .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE),
+ .needs_single_link_version = true,
+ .needs_unique_stream_id = true,
+ .phys_configs = {
+ .num_configs = ARRAY_SIZE(max9296a_phys_configs),
+ .configs = max9296a_phys_configs,
+ },
+ .fix_tx_ids = true,
+ .use_atr = true,
+ .num_pipes = 4,
+ .num_phys = 2,
+ .num_links = 2,
+};
+
+static const struct max_des_ops max9296a_ops = {
+ MAX9296A_COMMON_OPS,
+ .set_pipe_stream_id = max9296a_set_pipe_stream_id,
+ .set_pipe_enable = max9296a_set_pipe_enable,
+};
+
+static const struct max9296a_chip_info max9296a_info = {
+ .info = &max9296a_des_info,
+ .ops = &max9296a_ops,
+ .max_register = 0x1f00,
+ .phy0_lanes_0_1_on_second_phy = true,
+ .pipe_hw_ids = { 0, 1, 2, 3 },
+ .phy_hw_ids = { 1, 2 },
+};
+
+static const struct max_des_info max96714_des_info = {
+ MAX9296A_COMMON_INFO,
+ .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) |
+ BIT(MAX_SERDES_GMSL_2_6GBPS),
+ .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
+ BIT(MAX_SERDES_GMSL_TUNNEL_MODE),
+ .phys_configs = {
+ .num_configs = ARRAY_SIZE(max96714_phys_configs),
+ .configs = max96714_phys_configs,
+ },
+ .tpg_mode = MAX_SERDES_GMSL_PIXEL_MODE,
+ .num_pipes = 1,
+ .num_phys = 1,
+ .num_links = 1,
+};
+
+static const struct max_des_ops max96714_ops = {
+ MAX9296A_COMMON_OPS,
+ .set_pipe_stream_id = max96714_set_pipe_stream_id,
+ .set_pipe_enable = max96714_set_pipe_enable,
+ .set_pipe_tunnel_enable = max96714_set_pipe_tunnel_enable,
+};
+
+/*
+ * These register writes are described as required in MAX96714 datasheet
+ * Page 53, Section Register Map, to optimize link performance in 6Gbps
+ * and 3Gbps links for all cable lengths.
+ */
+static const struct reg_sequence max96714_rlms_reg_sequence[] = {
+ { MAX9296A_RLMS3E(0), 0xfd },
+ { MAX9296A_RLMS3F(0), 0x3d },
+ { MAX9296A_RLMS49(0), 0xf5 },
+ { MAX9296A_RLMS7E(0), 0xa8 },
+ { MAX9296A_RLMS7F(0), 0x68 },
+ { MAX9296A_RLMSA3(0), 0x30 },
+ { MAX9296A_RLMSA5(0), 0x70 },
+ { MAX9296A_RLMSD8(0), 0x07 },
+};
+
+static const struct max9296a_chip_info max96714_info = {
+ .info = &max96714_des_info,
+ .ops = &max96714_ops,
+ .max_register = 0x5011,
+ .polarity_on_physical_lanes = true,
+ .supports_phy_log = true,
+ .rlms_adjust_sequence = max96714_rlms_reg_sequence,
+ .rlms_adjust_sequence_len = ARRAY_SIZE(max96714_rlms_reg_sequence),
+ .pipe_hw_ids = { 1 },
+ .phy_hw_ids = { 1 },
+};
+
+static const struct max_des_info max96714f_des_info = {
+ MAX9296A_COMMON_INFO,
+ .versions = BIT(MAX_SERDES_GMSL_2_3GBPS),
+ .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
+ BIT(MAX_SERDES_GMSL_TUNNEL_MODE),
+ .phys_configs = {
+ .num_configs = ARRAY_SIZE(max96714_phys_configs),
+ .configs = max96714_phys_configs,
+ },
+ .tpg_mode = MAX_SERDES_GMSL_PIXEL_MODE,
+ .num_pipes = 1,
+ .num_phys = 1,
+ .num_links = 1,
+};
+
+static const struct max_des_ops max96714f_ops = {
+ MAX9296A_COMMON_OPS,
+ .set_pipe_stream_id = max96714_set_pipe_stream_id,
+ .set_pipe_enable = max96714_set_pipe_enable,
+ .set_pipe_tunnel_enable = max96714_set_pipe_tunnel_enable,
+};
+
+static const struct max9296a_chip_info max96714f_info = {
+ .info = &max96714f_des_info,
+ .ops = &max96714f_ops,
+ .max_register = 0x5011,
+ .polarity_on_physical_lanes = true,
+ .supports_phy_log = true,
+ .rlms_adjust_sequence = max96714_rlms_reg_sequence,
+ .rlms_adjust_sequence_len = ARRAY_SIZE(max96714_rlms_reg_sequence),
+ .pipe_hw_ids = { 1 },
+ .phy_hw_ids = { 1 },
+};
+
+static const struct max_des_info max96716a_des_info = {
+ MAX9296A_COMMON_INFO,
+ .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) |
+ BIT(MAX_SERDES_GMSL_2_6GBPS),
+ .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
+ BIT(MAX_SERDES_GMSL_TUNNEL_MODE),
+ .use_atr = true,
+ .phys_configs = {
+ .num_configs = ARRAY_SIZE(max9296a_phys_configs),
+ .configs = max9296a_phys_configs,
+ },
+ .tpg_mode = MAX_SERDES_GMSL_PIXEL_MODE,
+ .num_pipes = 2,
+ .num_phys = 2,
+ .num_links = 2,
+};
+
+static const struct max_des_ops max96716a_ops = {
+ MAX9296A_COMMON_OPS,
+ .set_pipe_stream_id = max96714_set_pipe_stream_id,
+ .set_pipe_link = max96716a_set_pipe_link,
+ .set_pipe_enable = max96714_set_pipe_enable,
+ .set_pipe_tunnel_phy = max96716a_set_pipe_tunnel_phy,
+ .set_pipe_tunnel_enable = max96714_set_pipe_tunnel_enable,
+};
+
+static const struct max9296a_chip_info max96716a_info = {
+ .info = &max96716a_des_info,
+ .ops = &max96716a_ops,
+ .max_register = 0x52d6,
+ .has_per_link_reset = true,
+ .phy0_lanes_0_1_on_second_phy = true,
+ .supports_cphy = true,
+ .supports_phy_log = true,
+ .pipe_hw_ids = { 1, 2 },
+ .phy_hw_ids = { 1, 2 },
+};
+
+static const struct max_des_info max96792a_des_info = {
+ MAX9296A_COMMON_INFO,
+ .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) |
+ BIT(MAX_SERDES_GMSL_2_6GBPS) |
+ BIT(MAX_SERDES_GMSL_3_12GBPS),
+ .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
+ BIT(MAX_SERDES_GMSL_TUNNEL_MODE),
+ .use_atr = true,
+ .phys_configs = {
+ .num_configs = ARRAY_SIZE(max9296a_phys_configs),
+ .configs = max9296a_phys_configs,
+ },
+ .tpg_mode = MAX_SERDES_GMSL_PIXEL_MODE,
+ .num_pipes = 2,
+ .num_phys = 2,
+ .num_links = 2,
+};
+
+static const struct max_des_ops max96792a_ops = {
+ MAX9296A_COMMON_OPS,
+ .set_pipe_stream_id = max96714_set_pipe_stream_id,
+ .set_pipe_link = max96716a_set_pipe_link,
+ .set_pipe_enable = max96714_set_pipe_enable,
+ .set_pipe_tunnel_phy = max96716a_set_pipe_tunnel_phy,
+ .set_pipe_tunnel_enable = max96714_set_pipe_tunnel_enable,
+};
+
+static const struct max9296a_chip_info max96792a_info = {
+ .info = &max96792a_des_info,
+ .ops = &max96792a_ops,
+ .max_register = 0x52d6,
+ .has_per_link_reset = true,
+ .phy0_lanes_0_1_on_second_phy = true,
+ .supports_cphy = true,
+ .supports_phy_log = true,
+ .pipe_hw_ids = { 1, 2 },
+ .phy_hw_ids = { 1, 2 },
+};
+
+static const struct of_device_id max9296a_of_table[] = {
+ { .compatible = "maxim,max9296a", .data = &max9296a_info },
+ { .compatible = "maxim,max96714", .data = &max96714_info },
+ { .compatible = "maxim,max96714f", .data = &max96714f_info },
+ { .compatible = "maxim,max96714r", .data = &max96714f_info },
+ { .compatible = "maxim,max96716a", .data = &max96716a_info },
+ { .compatible = "maxim,max96792a", .data = &max96792a_info },
+ { },
+};
+MODULE_DEVICE_TABLE(of, max9296a_of_table);
+
+static struct i2c_driver max9296a_i2c_driver = {
+ .driver = {
+ .name = "max9296a",
+ .of_match_table = max9296a_of_table,
+ },
+ .probe = max9296a_probe,
+ .remove = max9296a_remove,
+};
+
+module_i2c_driver(max9296a_i2c_driver);
+
+MODULE_IMPORT_NS("MAX_SERDES");
+MODULE_DESCRIPTION("Maxim MAX9296A Quad GMSL2 Deserializer Driver");
+MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
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