* [PATCH v3 9/9] ASoC: es8328: Add DAPM routes from MIC inputs to Mic Bias
From: Binbin Zhou @ 2026-06-26 2:27 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Keguang Zhang
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-sound,
Binbin Zhou
In-Reply-To: <cover.1782439646.git.zhoubinbin@loongson.cn>
The ES8328 codec has differential/single-ended microphone inputs
(LINPUT1/RINPUT1, LINPUT2/RINPUT2) that require connection to the
internal Mic Bias generator for proper operation. Currently, these
routes are missing, which can cause microphone recording to fail.
Add the missing DAPM routes to link the input pins to the Mic Bias
supply, ensuring the microphone bias voltage is correctly applied.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
PS:
I'm not sure if this patch is also required for other platforms using
the ES8328. If not, I'll drop the patch and add this DAPM route to the
`audio-routing` property of the Loongson ASoC machine driver.
sound/soc/codecs/es8328.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c
index 9838fe42cb6f..aaa6646ad4c5 100644
--- a/sound/soc/codecs/es8328.c
+++ b/sound/soc/codecs/es8328.c
@@ -405,6 +405,11 @@ static const struct snd_soc_dapm_route es8328_dapm_routes[] = {
{ "Mic Bias", NULL, "Mic Bias Gen" },
+ { "LINPUT1", NULL, "Mic Bias" },
+ { "RINPUT1", NULL, "Mic Bias" },
+ { "LINPUT2", NULL, "Mic Bias" },
+ { "RINPUT2", NULL, "Mic Bias" },
+
{ "Left Mixer", NULL, "Left DAC" },
{ "Left Mixer", "Left Bypass Switch", "Left Line Mux" },
{ "Left Mixer", "Right Playback Switch", "Right DAC" },
--
2.52.0
^ permalink raw reply related
* [PATCH v3 4/9] ASoC: dt-bindings: loongson,ls-audio-card: Use common sound card
From: Binbin Zhou @ 2026-06-26 2:27 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Keguang Zhang
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-sound,
Binbin Zhou
In-Reply-To: <cover.1782439646.git.zhoubinbin@loongson.cn>
Reference the common sound card properties. This allows removing the
`model` property and directly using the common `audio-routing` property
later on.
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
.../bindings/sound/loongson,ls-audio-card.yaml | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
index 61e8babed402..e1b7445a8b22 100644
--- a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
+++ b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
@@ -8,20 +8,20 @@ title: Loongson 7axxx/2kxxx ASoC audio sound card driver
maintainers:
- Yingkun Meng <mengyingkun@loongson.cn>
+ - Binbin Zhou <zhoubinbin@loongson.cn>
description:
The binding describes the sound card present in loongson
7axxx/2kxxx platform. The sound card is an ASoC component
which uses Loongson I2S controller to transfer the audio data.
+allOf:
+ - $ref: sound-card-common.yaml#
+
properties:
compatible:
const: loongson,ls-audio-card
- model:
- $ref: /schemas/types.yaml#/definitions/string
- description: User specified audio sound card name
-
mclk-fs:
$ref: simple-card.yaml#/definitions/mclk-fs
@@ -47,12 +47,11 @@ properties:
required:
- compatible
- - model
- mclk-fs
- cpu
- codec
-additionalProperties: false
+unevaluatedProperties: false
examples:
- |
--
2.52.0
^ permalink raw reply related
* [PATCH v3 7/9] ASoC: dt-bindings: loongson,ls-audio-card: Add ATK-DL2K0300B compatible
From: Binbin Zhou @ 2026-06-26 2:27 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Keguang Zhang
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-sound,
Binbin Zhou
In-Reply-To: <cover.1782439646.git.zhoubinbin@loongson.cn>
Add new compatible for the ATK-DL2K0300B development board based on
Loongson-2K0300.
Unlike others, this board features GPIO-controlled headphone detection,
headphone control, and speaker enable.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
.../sound/loongson,ls-audio-card.yaml | 38 +++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
index 8c214e5d04b1..dc7f4afbb777 100644
--- a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
+++ b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
@@ -23,6 +23,7 @@ properties:
enum:
- loongson,ls-audio-card # Loongson-2K1000/Loongson-2K2000/LS7A
- loongson,ls2k0300-forever-pi-audio-card # CTCISZ Forever Pi
+ - loongson,ls2k0300-dl2k0300b-audio-card # ATK-DL2K0300B
mclk-fs:
$ref: simple-card.yaml#/definitions/mclk-fs
@@ -47,6 +48,18 @@ properties:
required:
- sound-dai
+ spkr-en-gpios:
+ maxItems: 1
+ description: The GPIO that enables the speakers
+
+ hp-ctl-gpios:
+ maxItems: 1
+ description: The GPIO that control the headphones
+
+ hp-det-gpios:
+ maxItems: 1
+ description: The GPIO that detect headphones are plugged in
+
required:
- compatible
- mclk-fs
@@ -69,3 +82,28 @@ examples:
sound-dai = <&es8323>;
};
};
+
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ sound {
+ compatible = "loongson,ls2k0300-dl2k0300b-audio-card";
+ model = "loongson-audio";
+ mclk-fs = <512>;
+ hp-det-gpios = <&gpio 81 GPIO_ACTIVE_HIGH>;
+ spkr-en-gpios = <&gpio 86 GPIO_ACTIVE_HIGH>;
+ hp-ctl-gpios = <&gpio 87 GPIO_ACTIVE_HIGH>;
+ audio-routing =
+ "Headphone", "LOUT1",
+ "Headphone", "ROUT1",
+ "Speaker", "LOUT2",
+ "Speaker", "ROUT2";
+
+ cpu {
+ sound-dai = <&i2s>;
+ };
+
+ codec {
+ sound-dai = <&es8388>;
+ };
+ };
--
2.52.0
^ permalink raw reply related
* [PATCH v3 6/9] ASoC: loongson: Add Loongson-2K0300 CTCISZ Forever Pi sound card support
From: Binbin Zhou @ 2026-06-26 2:27 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Keguang Zhang
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-sound,
Binbin Zhou
In-Reply-To: <cover.1782439646.git.zhoubinbin@loongson.cn>
The Loongson-2K0300 audio card uses a different DAI format compared to
existing Loongson platforms.
Move the dai_fmt setting from the static DAI link to runtime hw_params
via snd_soc_runtime_set_dai_fmt(), and pass the correct format through
driver match data.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
sound/soc/loongson/loongson_card.c | 34 +++++++++++++++++++++++++-----
1 file changed, 29 insertions(+), 5 deletions(-)
diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c
index ea895fe6b5e9..0e63cbcad57a 100644
--- a/sound/soc/loongson/loongson_card.c
+++ b/sound/soc/loongson/loongson_card.c
@@ -2,8 +2,9 @@
//
// Loongson ASoC Audio Machine driver
//
-// Copyright (C) 2023 Loongson Technology Corporation Limited
+// Copyright (C) 2023-2026 Loongson Technology Corporation Limited
// Author: Yingkun Meng <mengyingkun@loongson.cn>
+// Binbin Zhou <zhoubinbin@loongson.cn>
//
#include <linux/module.h>
@@ -18,6 +19,19 @@ static char codec_name[SND_ACPI_I2C_ID_LEN];
struct loongson_card_data {
struct snd_soc_card snd_card;
unsigned int mclk_fs;
+ const struct loongson_card_config *cfg;
+};
+
+struct loongson_card_config {
+ unsigned int fmt;
+};
+
+static const struct loongson_card_config ls2k1000_card_config = {
+ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBC_CFC,
+};
+
+static const struct loongson_card_config ls2k0300_forever_pi_card_config = {
+ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC,
};
static int loongson_card_hw_params(struct snd_pcm_substream *substream,
@@ -45,7 +59,7 @@ static int loongson_card_hw_params(struct snd_pcm_substream *substream,
return ret;
}
- return 0;
+ return snd_soc_runtime_set_dai_fmt(rtd, ls_card->cfg->fmt);
}
static const struct snd_soc_ops loongson_ops = {
@@ -61,8 +75,6 @@ static struct snd_soc_dai_link loongson_dai_links[] = {
{
.name = "Loongson Audio Port",
.stream_name = "Loongson Audio",
- .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF
- | SND_SOC_DAIFMT_CBC_CFC,
SND_SOC_DAILINK_REG(analog),
.ops = &loongson_ops,
},
@@ -177,6 +189,10 @@ static int loongson_asoc_card_probe(struct platform_device *pdev)
if (!ls_priv)
return -ENOMEM;
+ ls_priv->cfg = (const struct loongson_card_config *)device_get_match_data(dev);
+ if (!ls_priv->cfg)
+ return -EINVAL;
+
card = &ls_priv->snd_card;
card->dev = dev;
@@ -202,7 +218,15 @@ static int loongson_asoc_card_probe(struct platform_device *pdev)
}
static const struct of_device_id loongson_asoc_dt_ids[] = {
- { .compatible = "loongson,ls-audio-card" },
+ /* Loongson-2K1000/Loongson-2K2000/LS7A */
+ {
+ .compatible = "loongson,ls-audio-card",
+ .data = &ls2k1000_card_config
+ },
+ {
+ .compatible = "loongson,ls2k0300-forever-pi-audio-card",
+ .data = &ls2k0300_forever_pi_card_config
+ },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, loongson_asoc_dt_ids);
--
2.52.0
^ permalink raw reply related
* [PATCH v3 5/9] ASoC: dt-bindings: loongson,ls-audio-card: Add ctcisz forever pi compatible
From: Binbin Zhou @ 2026-06-26 2:27 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Keguang Zhang
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-sound,
Binbin Zhou
In-Reply-To: <cover.1782439646.git.zhoubinbin@loongson.cn>
Add a new compatible string `loongson,ls2k0300-forever-pi-audio-card`
for the audio card on Loongson-2K0300 ctcisz forever pi SoC. It uses a
different DAI format compared to existing Loongson platforms.
The existing "loongson,ls-audio-card" remains valid for LS7A,
Loongson-2K1000 and Loongson-2K2000.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
.../devicetree/bindings/sound/loongson,ls-audio-card.yaml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
index e1b7445a8b22..8c214e5d04b1 100644
--- a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
+++ b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
@@ -20,7 +20,9 @@ allOf:
properties:
compatible:
- const: loongson,ls-audio-card
+ enum:
+ - loongson,ls-audio-card # Loongson-2K1000/Loongson-2K2000/LS7A
+ - loongson,ls2k0300-forever-pi-audio-card # CTCISZ Forever Pi
mclk-fs:
$ref: simple-card.yaml#/definitions/mclk-fs
--
2.52.0
^ permalink raw reply related
* [PATCH v3 3/9] ASoC: loongson: Add Loongson-2K0300 I2S controller support
From: Binbin Zhou @ 2026-06-26 2:27 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Keguang Zhang
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-sound,
Binbin Zhou
In-Reply-To: <cover.1782439646.git.zhoubinbin@loongson.cn>
The Loongson-2K0300 I2S interface differs significantly from the
Loongson-2K1000. Although both utilize external DMA controllers, the
Loongson-2K0300 does not require additional registers for routing
configuration.
Due to hardware design flaw, an extra controller reset sequence is
required during probe.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
sound/soc/loongson/loongson_i2s_plat.c | 42 +++++++++++++++++++++-----
1 file changed, 35 insertions(+), 7 deletions(-)
diff --git a/sound/soc/loongson/loongson_i2s_plat.c b/sound/soc/loongson/loongson_i2s_plat.c
index ac054b6ce632..82d95c6644ef 100644
--- a/sound/soc/loongson/loongson_i2s_plat.c
+++ b/sound/soc/loongson/loongson_i2s_plat.c
@@ -2,7 +2,7 @@
//
// Loongson I2S controller master mode dirver(platform device)
//
-// Copyright (C) 2023-2024 Loongson Technology Corporation Limited
+// Copyright (C) 2023-2026 Loongson Technology Corporation Limited
//
// Author: Yingkun Meng <mengyingkun@loongson.cn>
// Binbin Zhou <zhoubinbin@loongson.cn>
@@ -21,6 +21,7 @@
#include "loongson_i2s.h"
#include "loongson_dma.h"
+/* Loongson-2K1000 APBDMA routing */
#define LOONGSON_I2S_RX_DMA_OFFSET 21
#define LOONGSON_I2S_TX_DMA_OFFSET 18
@@ -30,6 +31,11 @@
#define LOONGSON_DMA3_CONF 0x3
#define LOONGSON_DMA4_CONF 0x4
+struct loongson_i2s_plat_config {
+ int rev_id;
+ int (*i2s_dma_config)(struct platform_device *pdev);
+};
+
static int loongson_i2s_apbdma_config(struct platform_device *pdev)
{
int val;
@@ -47,8 +53,18 @@ static int loongson_i2s_apbdma_config(struct platform_device *pdev)
return 0;
}
+static const struct loongson_i2s_plat_config ls2k0300_i2s_plat_config = {
+ .rev_id = 1,
+};
+
+static const struct loongson_i2s_plat_config ls2k1000_i2s_plat_config = {
+ .rev_id = 0,
+ .i2s_dma_config = loongson_i2s_apbdma_config,
+};
+
static int loongson_i2s_plat_probe(struct platform_device *pdev)
{
+ const struct loongson_i2s_plat_config *plat_config;
struct device *dev = &pdev->dev;
struct loongson_i2s *i2s;
struct resource *res;
@@ -59,12 +75,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev)
if (!i2s)
return -ENOMEM;
- ret = loongson_i2s_apbdma_config(pdev);
- if (ret)
- return ret;
+ plat_config = device_get_match_data(dev);
+ if (!plat_config)
+ return -EINVAL;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- i2s->reg_base = devm_ioremap_resource(&pdev->dev, res);
+ if (plat_config->i2s_dma_config) {
+ ret = plat_config->i2s_dma_config(pdev);
+ if (ret)
+ return ret;
+ }
+
+ i2s->reg_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(i2s->reg_base))
return dev_err_probe(dev, PTR_ERR(i2s->reg_base),
"devm_ioremap_resource failed\n");
@@ -87,11 +108,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev)
if (IS_ERR(i2s_clk))
return dev_err_probe(dev, PTR_ERR(i2s_clk), "clock property invalid\n");
i2s->clk_rate = clk_get_rate(i2s_clk);
+ i2s->rev_id = plat_config->rev_id;
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
dev_set_name(dev, LS_I2S_DRVNAME);
dev_set_drvdata(dev, i2s);
+ if (i2s->rev_id == 1) {
+ regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET, I2S_CTRL_RESET);
+ fsleep(200);
+ }
+
ret = devm_snd_soc_register_component(dev, &loongson_i2s_edma_component,
&loongson_i2s_dai, 1);
if (ret)
@@ -102,7 +129,8 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev)
}
static const struct of_device_id loongson_i2s_ids[] = {
- { .compatible = "loongson,ls2k1000-i2s" },
+ { .compatible = "loongson,ls2k0300-i2s", .data = &ls2k0300_i2s_plat_config },
+ { .compatible = "loongson,ls2k1000-i2s", .data = &ls2k1000_i2s_plat_config },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, loongson_i2s_ids);
--
2.52.0
^ permalink raw reply related
* [PATCH v3 2/9] ASoC: dt-bindings: loongson,ls2k1000-i2s: Document Loongson-2K0300 compatible
From: Binbin Zhou @ 2026-06-26 2:27 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Keguang Zhang
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-sound,
Binbin Zhou, Krzysztof Kozlowski
In-Reply-To: <cover.1782439646.git.zhoubinbin@loongson.cn>
Add a new compatible string `loongson,ls2k0300-i2s` for the I2S
controller found on Loongson-2K0300 SoC.
Unlike Loongson-2K1000, Loongson-2K0300 does not require the second
register region for APB DMA configuration, so update the binding to
allow a single reg entry.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
.../bindings/sound/loongson,ls2k1000-i2s.yaml | 22 ++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/sound/loongson,ls2k1000-i2s.yaml b/Documentation/devicetree/bindings/sound/loongson,ls2k1000-i2s.yaml
index da79510bb2d9..51e23c189f7a 100644
--- a/Documentation/devicetree/bindings/sound/loongson,ls2k1000-i2s.yaml
+++ b/Documentation/devicetree/bindings/sound/loongson,ls2k1000-i2s.yaml
@@ -14,9 +14,12 @@ allOf:
properties:
compatible:
- const: loongson,ls2k1000-i2s
+ enum:
+ - loongson,ls2k0300-i2s
+ - loongson,ls2k1000-i2s
reg:
+ minItems: 1
items:
- description: Loongson I2S controller Registers.
- description: APB DMA config register for Loongson I2S controller.
@@ -49,6 +52,23 @@ required:
unevaluatedProperties: false
+if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - loongson,ls2k1000-i2s
+
+then:
+ properties:
+ reg:
+ minItems: 2
+
+else:
+ properties:
+ reg:
+ maxItems: 1
+
examples:
- |
#include <dt-bindings/clock/loongson,ls2k-clk.h>
--
2.52.0
^ permalink raw reply related
* [PATCH v3 1/9] ASoC: loongson: Fix error handling in ACPI property parsing
From: Binbin Zhou @ 2026-06-26 2:27 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Keguang Zhang
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-sound,
Binbin Zhou, stable, Sashiko
In-Reply-To: <cover.1782439646.git.zhoubinbin@loongson.cn>
In loongson_card_parse_acpi(), the return value of
device_property_read_string() for the `codec-dai-name` property was
ignored. If the property is missing or invalid, an uninitialized pointer
would be used later, potentially leading to undefined behavior.
Fix this by checking the return value and propagating the error
appropriately.
Cc: stable@vger.kernel.org
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/cover.1780538113.git.zhoubinbin@loongson.cn?part=5
Fixes: ddb538a3004b ("ASoC: loongson: Factor out loongson_card_acpi_find_device() function")
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
sound/soc/loongson/loongson_card.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c
index 7910d5d9ac4f..ea895fe6b5e9 100644
--- a/sound/soc/loongson/loongson_card.c
+++ b/sound/soc/loongson/loongson_card.c
@@ -91,7 +91,7 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data)
const char *codec_dai_name;
struct acpi_device *adev;
struct device *phy_dev;
- int i;
+ int i, ret;
/* fixup platform name based on reference node */
adev = loongson_card_acpi_find_device(card, "cpu");
@@ -108,7 +108,9 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data)
return -ENOENT;
snprintf(codec_name, sizeof(codec_name), "i2c-%s", acpi_dev_name(adev));
- device_property_read_string(card->dev, "codec-dai-name", &codec_dai_name);
+ ret = device_property_read_string(card->dev, "codec-dai-name", &codec_dai_name);
+ if (ret)
+ return ret;
for (i = 0; i < card->num_links; i++) {
loongson_dai_links[i].platforms->name = dev_name(phy_dev);
--
2.52.0
^ permalink raw reply related
* [PATCH v3 0/9] ASoC: Add Loongson-2K0300 I2S controller and sound card support
From: Binbin Zhou @ 2026-06-26 2:27 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Keguang Zhang
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-sound,
Binbin Zhou
Hi all:
This series adds ASoC support for the Loongson-2K0300 SoC and its
associated development boards, including the CTCISZ Forever Pi and the
ATK-DL2K0300B.
Key changes:
- Extend DT bindings to support ls2k0300-i2s and new audio card
compatibles.
- Refactor the platform I2S driver to handle SoC differences via per-device
configuration (rev_id, optional APB DMA config, reset sequence).
- Refactor the audio machine driver to support board-specific DAI formats,
GPIO-based headphone detection/control, speaker enable, and DAPM routing.
- Add jack detection and automatic switching between headphones and
speakers for the DL2K0300B board.
The patchset also cleans up the existing audio card binding by
referencing the common sound-card properties, and adds new compatibles
for the Loongson-2K0300 variants with proper DAI format handling.
All changes have been tested on Loongson-2K2000 (PCI), Loongson-2K0300
Forever Pi and Loongson-2K0300 DL2K0300B boards.
Thanks.
Binbin
========
V3:
Patch (1/9):
- New patch;
- Error handling reported by AI Sashiko;
Patch (3/9):
- Mark platform configuration structures as `const`;
Patch (4/9):
- Add Acked-by tag from Rob, thanks;
Patch (5/9):
- Correct commit message;
Patch (7/9):
- Drop `loongson` prefix;
- Change `gpiod_hp_mute` to `gpiod_hp_ctl`;
Patch (8/9):
- Add `add_dapm_routes` to loongson_card_config;
- Move gpiod* register ops into loongson_card_parse_of();
- Change `gpiod_hp_mute` to `gpiod_hp_ctl`;
- Add `ls_priv->gpiod_hp_det` judgment in loongson_asoc_machine_init()
to avoid double-free;
Patch (9/9):
- New patch;
- Add DAPM routes from MIC inputs to Mic Bias.
Link to V2:
https://lore.kernel.org/all/cover.1780538113.git.zhoubinbin@loongson.cn/
v2:
- The first four patches for V1 (related to code cleanup) have been
accepted as a separate series. The link is as follows:
https://lore.kernel.org/all/178041371415.93058.4794135670349989571.b4-ty@b4/
Patch (1/7):
- Add Reviewed-by tag from Krzysztof Kozlowski;
Patch (3/7):
- New patch;
- Reference sound-card-common.yaml, drop custom model property;
Patch (4/7)(5/7):
- New patches;
- Support Forever Pi board with different DAI format;
Patch (6/7)(7/7):
- New patches;
- Implement headphone jack detection and DAPM routing for ATK-DL2K0300B
board.
Link to V1:
https://lore.kernel.org/all/cover.1773107475.git.zhoubinbin@loongson.cn/
Binbin Zhou (9):
ASoC: loongson: Fix error handling in ACPI property parsing
ASoC: dt-bindings: loongson,ls2k1000-i2s: Document Loongson-2K0300
compatible
ASoC: loongson: Add Loongson-2K0300 I2S controller support
ASoC: dt-bindings: loongson,ls-audio-card: Use common sound card
ASoC: dt-bindings: loongson,ls-audio-card: Add ctcisz forever pi
compatible
ASoC: loongson: Add Loongson-2K0300 CTCISZ Forever Pi sound card
support
ASoC: dt-bindings: loongson,ls-audio-card: Add ATK-DL2K0300B
compatible
ASoC: loongson: Add headphone jack detection and DAPM routing
ASoC: es8328: Add DAPM routes from MIC inputs to Mic Bias
.../sound/loongson,ls-audio-card.yaml | 53 +++++-
.../bindings/sound/loongson,ls2k1000-i2s.yaml | 22 ++-
sound/soc/codecs/es8328.c | 5 +
sound/soc/loongson/loongson_card.c | 171 ++++++++++++++++--
sound/soc/loongson/loongson_i2s_plat.c | 42 ++++-
5 files changed, 267 insertions(+), 26 deletions(-)
--
2.52.0
^ permalink raw reply
* Re: [PATCH v6 2/2] drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
From: Sunyun Yang @ 2026-06-26 1:55 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: Krzysztof Kozlowski, robh, krzk+dt, conor+dt, andrzej.hajda,
neil.armstrong, maarten.lankhorst, rfoss, mripard,
Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng
In-Reply-To: <tpqutwzasfwhraxzymorkiot7uryef4l2n32fyo3i3fp337sak@fugghdkcqfrj>
Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2026年6月25日周四 22:57写道:
>
> On Thu, Jun 25, 2026 at 09:26:47PM +0800, Sunyun Yang wrote:
> > Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 21:17写道:
> > >
> > > On 25/06/2026 15:14, Sunyun Yang wrote:
> > > > Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 20:54写道:
> > > >>
> > > >> On 08/05/2026 15:40, syyang@lontium.com wrote:
> > > >>> +
> > > >>> +static void lt9611c_reset(struct lt9611c *lt9611c)
> > > >>> +{
> > > >>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> > > >>> + msleep(20);
> > > >>> +
> > > >>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> > > >>> + msleep(20);
> > > >>> +
> > > >>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> > > >>
> > > >> This is just plain wrong. Why do you assert, then de-assert and then
> > > >> finally assert AGAIN the reset leaving the device in powerdown stage?
> > > >>
> > > > I am using software to emulate the hardware RESET button on our EVB.
> > > > When the hardware RESET button is pressed while our chip is running,
> > > > the signal level changes from HIGH to LOW and then back to HIGH.
> > > >
> > > > Of course, we can also use the following:
> > > > static void lt9611c_reset(struct lt9611c *lt9611c)
> > > > {
> > > > gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> > > > msleep(50);
> > > > gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> > > > msleep(20);
> > > > }
> > >
> > > Makes no sense either and you just did not get the point and did not
> > > answer my question. I asked WHY you leave asserted. Answer "we emulate"
> > > is just plain wrong.
> > >
> > > So again please answer:
> > >
> > > Why do you leave device with reset asserted?
> > >
> >
> > devicetree: reset-gpios = <&tlmm 128 GPIO_ACTIVE_HIGH>;
>
> It should be GPIO_ACTIVE_LOW, if the pin as active-low.
>
Yes, I understand that.
I used GPIO_ACTIVE_HIGH intentionally, because I did not want to
modify the existing reset-gpios = <&tlmm 128 GPIO_ACTIVE_HIGH>
property in the lontium-lt9611.yaml device tree file that was
submitted by others. If I used GPIO_ACTIVE_LOW, I would have to change
that line in the device tree binding, and also update the driver files
lontium-lt9611.c and lontium-lt9611uxc.c accordingly. That would add a
significant amount of extra work for me.
Moreover, in much earlier versions, I had added a separate
lontium-lt9611c.yaml file to decouple this new chip from the existing
lontium-lt9611 / lontium-lt9611uxc support. However, Dmitry Baryshkov
considered that unnecessary and asked me to merge lontium-lt9611c
together with the existing drivers. He also told me that the
differences in reset‑pin and VDD/VCC supply did not matter.
The existing device tree and driver code already had issues, and I had
raised them with you before. At that time, you did not seem to care,
and the community reviewers insisted that I must follow your
directions. So I did exactly what you told me to do.
> >
> > GPIO_ACTIVE_HIGH:
> >
> > gpiod_set_value_cansleep(lt9611c->reset_gpio, 0); ------ reset pin
> > is Low level : Clear the register configuration in the chip to stop
> > the chip from working.
> >
> > gpiod_set_value_cansleep(lt9611c->reset_gpio, 1); ------ reset pin
> > is high level: The chip resumes operation.
> >
> >
> >
> > > Best regards,
> > > Krzysztof
>
> --
> With best wishes
> Dmitry
^ permalink raw reply
* Re: [PATCH v6 2/2] drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
From: Sunyun Yang @ 2026-06-26 2:15 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
dmitry.baryshkov, maarten.lankhorst, rfoss, mripard,
Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng
In-Reply-To: <6371d1d8-cdfb-40fa-84c7-ba3ec4e2ac00@kernel.org>
Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 21:51写道:
>
> On 25/06/2026 15:40, Sunyun Yang wrote:
> > Sunyun Yang <syyang@lontium.com> 于2026年6月25日周四 21:26写道:
> >>
> >> Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 21:17写道:
> >>>
> >>> On 25/06/2026 15:14, Sunyun Yang wrote:
> >>>> Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 20:54写道:
> >>>>>
> >>>>> On 08/05/2026 15:40, syyang@lontium.com wrote:
> >>>>>> +
> >>>>>> +static void lt9611c_reset(struct lt9611c *lt9611c)
> >>>>>> +{
> >>>>>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> >>>>>> + msleep(20);
> >>>>>> +
> >>>>>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> >>>>>> + msleep(20);
> >>>>>> +
> >>>>>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> >>>>>
> >>>>> This is just plain wrong. Why do you assert, then de-assert and then
> >>>>> finally assert AGAIN the reset leaving the device in powerdown stage?
> >>>>>
> >>>> I am using software to emulate the hardware RESET button on our EVB.
> >>>> When the hardware RESET button is pressed while our chip is running,
> >>>> the signal level changes from HIGH to LOW and then back to HIGH.
> >>>>
> >>>> Of course, we can also use the following:
> >>>> static void lt9611c_reset(struct lt9611c *lt9611c)
> >>>> {
> >>>> gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> >>>> msleep(50);
> >>>> gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> >>>> msleep(20);
> >>>> }
> >>>
> >>> Makes no sense either and you just did not get the point and did not
> >>> answer my question. I asked WHY you leave asserted. Answer "we emulate"
> >>> is just plain wrong.
> >>>
> >>> So again please answer:
> >>>
> >>> Why do you leave device with reset asserted?
> >>>
> >>
> >> devicetree: reset-gpios = <&tlmm 128 GPIO_ACTIVE_HIGH>;
> >>
> >> GPIO_ACTIVE_HIGH:
> >>
> >> gpiod_set_value_cansleep(lt9611c->reset_gpio, 0); ------ reset pin
> >> is Low level : Clear the register configuration in the chip to stop
> >> the chip from working.
> >>
> >> gpiod_set_value_cansleep(lt9611c->reset_gpio, 1); ------ reset pin
> >> is high level: The chip resumes operation.
> >>
> >>
> >
> > Our purpose is: pull the level low to clear the register configuration
> > in the chip, and then pull it high to allow the MCU inside the chip to
> > re‑initialize the registers.
>
>
> And you do completely opposite... so that confirms your code is just wrong.
>
The lontium-lt9611.yaml uses GPIO_ACTIVE_HIGH. I am just following the
rule of this device tree. If I modify the device tree to use
GPIO_ACTIVE_LOW,
and use the following code in my driver, then my driver would be correct.
However, would the existing kernel drivers lontium-lt9611uxc.c and
lontium-lt9611.c be affected?
static void lt9611c_reset(struct lt9611c *lt9611c)
{
gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
msleep(50);
gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
msleep(20);
}
>
> Best regards,
> Krzysztof
^ permalink raw reply
* Re: [PATCH v2 2/2] arm64: dts: qcom: kaanapali: fix traceNoC probe issue
From: Jie Gan @ 2026-06-26 2:03 UTC (permalink / raw)
To: Leo Yan
Cc: Suzuki K Poulose, Konrad Dybcio, Bjorn Andersson, Konrad Dybcio,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Tingwei Zhang,
Jingyi Wang, Abel Vesa, Mike Leach, James Clark, Yuanfang Zhang,
linux-arm-msm, devicetree, linux-kernel, coresight,
linux-arm-kernel
In-Reply-To: <20260625085643.GD575984@e132581.arm.com>
On 6/25/2026 4:56 PM, Leo Yan wrote:
> On Thu, Jun 25, 2026 at 09:01:18AM +0800, Jie Gan wrote:
>
> [...]
>
>>>> However, I believe it is acceptable to allocate an ATID for the itNoC device
>>>> and the issue can be fixed with this way.
>>>
>>> I think so.
>>
>> Hi Suzuki/Leo
>>
>> Which solution do you prefer to address the issue?
>
> I will leave this to Suzuki.
>
>> The interconnect traceNoC platform driver is intended for the itnoc device,
>> implying that no TPDM devices are connected to it. So, if I modify it to
>> allocate an ATID, I think it would be better to rename the “itnoc” node
>> accordingly? Or it's ok to leave it as-is?
>>
>> BTW, the traceNoC device definitely is an AMBA device with CID/PID
>> registers.
>
> Just to share a bit thoughts on the driver's design.
>
> I think it would be better to keep the probe function generic. The AMBA
> probe should not be specific to TraceNoC, and the platform probe should
> not be only dedicated to the interconnect TraceNoC. The probe function
> should simply handle a device that appears on either the AMBA bus or the
> platform bus.
>
> So the question is: if allocat an ATID for all traceNoC devices, do you
> still need to distinguish TraceNoC types? If no, then the code can be
> unified.
>
Hi Leo,
To be honest, I would prefer not to modify the interconnect platform
driver. On some Qualcomm platforms, multiple itnoc devices reside within
small blocks(one or more than one for each block) and are connected to a
dummy source. In such cases, two ATIDs are allocated for a path (the
dummy source and the itnoc), which is inefficient. This is why the itnoc
platform driver created to avoid this waste.
The TraceNoC (called as AG TraceNoC) is a generic TraceNoC device which
connected to multiple source and link devices, aggregating data from all
source devices into a single output path.
This device is implemented as an AMBA device but lacks proper hardware
configuration. As a result, it must be handled in the driver as a
workaround, which unfortunately breaks the original design intent.
Thanks,
Jie
> Thanks,
> Leo
^ permalink raw reply
* Re: [PATCH 2/2] arm64: tegra: Add Lenovo ThinkEdge SE70 carrier board DTS
From: sashiko-bot @ 2026-06-26 1:23 UTC (permalink / raw)
To: Jiqi Li; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260626010950.459899-3-lijq9@lenovo.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] Peripheral overrides and configuration nodes are incorrectly placed at the root of the device tree, failing to override the actual bus children.
- [High] The `fixed-regulators` container lacks the `compatible = "simple-bus"` property, preventing its children from probing.
- [High] The `cd-inverted` property combined with `GPIO_ACTIVE_LOW` causes a double-inversion of the SD card detect signal.
- [Medium] Use of downstream Linux-for-Tegra (L4T) device tree properties that are invalid in mainline.
- [Low] Device tree node names use underscores instead of hyphens.
- [Low] Mismatch between inline code comment and regulator voltage configuration.
--
commit ce1b543209b572c99dba284d418dec1c8ce39718
Author: Jiqi Li <lijq9@lenovo.com>
arm64: tegra: Add Lenovo ThinkEdge SE70 carrier board DTS
ThinkEdge SE70 is a fanless industrial edge gateway built around
NVIDIA Tegra194 Xavier NX SOM. This patch adds a standalone device
tree file describing core carrier board peripherals:
- Custom 40-pin header pinmux configuration
- External SD card slot with dedicated 3.3V fixed regulator
No fan, PWM, tachometer, extra camera/spi peripherals are present
on this passively cooled platform, so unused nodes are explicitly
disabled following mainline device tree best practices.
Static verification passed: dt_binding_check and dtbs compilation
complete without errors.
[ ... ]
> diff --git a/arch/arm64/boot/dts/nvidia/tegra194-lenovo-thinkedge-se70.dts b/arch/arm64/boot/dts/nvidia/tegra194-lenovo-thinkedge-se70.dts
> new file mode 100644
> index 0000000000000..075e057c90954
> --- /dev/null
> +++ b/arch/arm64/boot/dts/nvidia/tegra194-lenovo-thinkedge-se70.dts
> @@ -0,0 +1,167 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/dts-v1/;
> +
> +#include "tegra194-p3668-0001.dtsi"
> +#include "tegra194-p3509-0000.dtsi"
> +
> +/ {
> + model = "Lenovo ThinkEdge SE70";
> + compatible = "lenovo,thinkedge-se70", "nvidia,p3509-0000+p3668-0001", "nvidia,tegra194";
> +
> + chosen {
> + bootargs = "console=ttyTCU0,115200";
> + };
> +
> + /* Fixed 2.3V regulator for external SD card slot */
[Severity: Low]
Is this comment accurate? It mentions a 2.3V regulator, but the configuration
properties below (regulator-min-microvolt and regulator-max-microvolt) enforce
3.3V.
> + fixed-regulators {
[Severity: High]
Does this fixed-regulators container need a compatible = "simple-bus" property?
Without a recognized bus compatible string, of_platform_populate() won't
recurse into this node at boot. This would prevent the ap2306gn_3v3_sd child
regulator from probing, causing the SDHCI controller requesting it to fail with
-EPROBE_DEFER.
> + ap2306gn_3v3_sd: ap2306gn_3v3_sd {
[Severity: Low]
Should this node name use hyphens instead of underscores (e.g.,
ap2306gn-3v3-sd) to comply with Devicetree specification naming rules and avoid
node_name_chars warnings during compilation?
> + compatible = "regulator-fixed";
> + regulator-name = "ap2306gn-3v3-sd";
> + regulator-min-microvolt = <3300000>;
> + regulator-max-microvolt = <3300000>;
[ ... ]
> + /* Custom pinmux configurations for 40-pin expansion header */
> + pinmux@c302000 {
[Severity: High]
Are these peripheral and pinmux configuration nodes incorrectly placed at the
root level of the device tree (/ {}) rather than being children of the SoC bus?
Placing nodes like pinmux@..., i2c@..., and sdhci@... directly under the root
creates disconnected dummy nodes instead of overriding the actual bus-attached
controllers, causing these intended configurations to be completely ignored.
> + status = "okay";
> + touch_clk_pcc4 {
[ ... ]
> + pinmux@2430028 {
> + status = "okay";
> + soc_gpio41_pq5 {
> + nvidia,pins = "soc_gpio41_pq5";
> + nvidia,function = "rsvd2";
> + nvidia,pull = <TEGRA_PIN_PULL_NONE>;
> + nvidia,tristate = <TEGRA_PIN_DISABLE>;
> + nvidia,enable-input = <TEGRA_PIN_DISABLE>;
> + nvidia,lpdr = <TEGRA_PIN_DISABLE>;
> + nvidia,io-high-voltage = <TEGRA_PIN_DISABLE>;
[Severity: Medium]
Are nvidia,lpdr and nvidia,io-high-voltage downstream Linux-for-Tegra (L4T)
properties? They don't appear to exist in the mainline devicetree bindings,
which means they will be ignored by upstream drivers and fail dt_binding_check
schema validation.
> + };
> + };
[ ... ]
> + /* SDMMC3 for external user SD card slot with dedicated 3.3V power */
> + sdhci@3440000 {
[Severity: High]
Similar to the pinmux nodes above, is this sdhci node placed as a direct child
of the root (/ {}) instead of the SoC bus? This creates a new orphaned node
rather than overriding the actual bus-attached SDHCI controller.
> + uhs-mask = <0x78>;
> + mmc-ocr-mask = <0x0>;
[Severity: Medium]
Are uhs-mask and mmc-ocr-mask valid mainline properties? Like the pinmux
properties above, these appear to be downstream L4T properties that will fail
dt_binding_check schema validation.
> + cd-inverted;
> + cd-gpios = <&gpio_aon TEGRA194_AON_GPIO(CC, 4) GPIO_ACTIVE_LOW>;
[Severity: High]
Does combining the cd-inverted property with GPIO_ACTIVE_LOW in cd-gpios cause
a logical double inversion of the SD card detect signal? The GPIO subsystem
will invert the active-low physical signal, and then the MMC core will invert
that result again due to cd-inverted. This could mean an inserted card is seen
as removed and vice versa, breaking card hotplugging.
> + nvidia,cd-wakeup-capable;
[Severity: Medium]
Is nvidia,cd-wakeup-capable another downstream property unsupported in
mainline?
> + vmmc-supply = <&ap2306gn_3v3_sd>;
> + status = "okay";
> + };
[ ... ]
> + pwm@32f0000 {
> + status = "disabled";
> + };
> + generic_pwm_tachometer {
[Severity: Low]
Should this node name use hyphens instead of underscores (e.g.,
generic-pwm-tachometer) to avoid devicetree specification violations and
node_name_chars warnings?
> + status = "disabled";
> + };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260626010950.459899-1-lijq9@lenovo.com?part=2
^ permalink raw reply
* [PATCH 2/2] arm64: tegra: Add Lenovo ThinkEdge SE70 carrier board DTS
From: Jiqi Li @ 2026-06-26 1:09 UTC (permalink / raw)
To: linux-tegra
Cc: devicetree, robh+dt, krzk+dt, conor+dt, jonathanh, thierry.reding,
mpearson-lenovo, Jiqi Li
In-Reply-To: <20260626010950.459899-1-lijq9@lenovo.com>
ThinkEdge SE70 is a fanless industrial edge gateway built around
NVIDIA Tegra194 Xavier NX SOM. This patch adds a standalone device
tree file describing core carrier board peripherals:
- Custom 40-pin header pinmux configuration
- External SD card slot with dedicated 3.3V fixed regulator
No fan, PWM, tachometer, extra camera/spi peripherals are present
on this passively cooled platform, so unused nodes are explicitly
disabled following mainline device tree best practices.
Static verification passed: dt_binding_check and dtbs compilation
complete without errors.
Signed-off-by: Jiqi Li <lijq9@lenovo.com>
---
arch/arm64/boot/dts/nvidia/Makefile | 1 +
.../nvidia/tegra194-lenovo-thinkedge-se70.dts | 167 ++++++++++++++++++
2 files changed, 168 insertions(+)
create mode 100644 arch/arm64/boot/dts/nvidia/tegra194-lenovo-thinkedge-se70.dts
diff --git a/arch/arm64/boot/dts/nvidia/Makefile b/arch/arm64/boot/dts/nvidia/Makefile
index 72c0cb5efa47..736a3f8a923f 100644
--- a/arch/arm64/boot/dts/nvidia/Makefile
+++ b/arch/arm64/boot/dts/nvidia/Makefile
@@ -29,6 +29,7 @@ dtb-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186-p3509-0000+p3636-0001.dtb
dtb-$(CONFIG_ARCH_TEGRA_194_SOC) += tegra194-p2972-0000.dtb
dtb-$(CONFIG_ARCH_TEGRA_194_SOC) += tegra194-p3509-0000+p3668-0000.dtb
dtb-$(CONFIG_ARCH_TEGRA_194_SOC) += tegra194-p3509-0000+p3668-0001.dtb
+dtb-$(CONFIG_ARCH_TEGRA_194_SOC) += tegra194-lenovo-thinkedge-se70.dtb
dtb-$(CONFIG_ARCH_TEGRA_234_SOC) += tegra234-sim-vdk.dtb
dtb-$(CONFIG_ARCH_TEGRA_234_SOC) += tegra234-p3737-0000+p3701-0000.dtb
dtb-$(CONFIG_ARCH_TEGRA_234_SOC) += tegra234-p3737-0000+p3701-0008.dtb
diff --git a/arch/arm64/boot/dts/nvidia/tegra194-lenovo-thinkedge-se70.dts b/arch/arm64/boot/dts/nvidia/tegra194-lenovo-thinkedge-se70.dts
new file mode 100644
index 000000000000..075e057c9095
--- /dev/null
+++ b/arch/arm64/boot/dts/nvidia/tegra194-lenovo-thinkedge-se70.dts
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+
+#include "tegra194-p3668-0001.dtsi"
+#include "tegra194-p3509-0000.dtsi"
+
+/ {
+ model = "Lenovo ThinkEdge SE70";
+ compatible = "lenovo,thinkedge-se70", "nvidia,p3509-0000+p3668-0001", "nvidia,tegra194";
+
+ chosen {
+ bootargs = "console=ttyTCU0,115200";
+ };
+
+ /* Fixed 2.3V regulator for external SD card slot */
+ fixed-regulators {
+ ap2306gn_3v3_sd: ap2306gn_3v3_sd {
+ compatible = "regulator-fixed";
+ regulator-name = "ap2306gn-3v3-sd";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ gpio = <&gpio TEGRA194_MAIN_GPIO(Q, 5) GPIO_ACTIVE_HIGH>;
+ enable-active-high;
+ status = "okay";
+ };
+ };
+
+ /* Custom pinmux configurations for 40-pin expansion header */
+ pinmux@c302000 {
+ status = "okay";
+ touch_clk_pcc4 {
+ nvidia,pins = "touch_clk_pcc4";
+ nvidia,function = "rsvd2";
+ nvidia,pull = <TEGRA_PIN_PULL_UP>;
+ nvidia,tristate = <TEGRA_PIN_ENABLE>;
+ nvidia,enable-input = <TEGRA_PIN_ENABLE>;
+ nvidia,lpdr = <TEGRA_PIN_DISABLE>;
+ };
+ };
+
+ pinmux@2430028 {
+ status = "okay";
+ soc_gpio41_pq5 {
+ nvidia,pins = "soc_gpio41_pq5";
+ nvidia,function = "rsvd2";
+ nvidia,pull = <TEGRA_PIN_PULL_NONE>;
+ nvidia,tristate = <TEGRA_PIN_DISABLE>;
+ nvidia,enable-input = <TEGRA_PIN_DISABLE>;
+ nvidia,lpdr = <TEGRA_PIN_DISABLE>;
+ nvidia,io-high-voltage = <TEGRA_PIN_DISABLE>;
+ };
+ };
+
+ pinmux@2430000 {
+ status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&hdr40_pinmux>;
+
+ hdr40_pinmux: header-40pin-pinmux {
+ pin7 {
+ nvidia,pins = "aud_mclk_ps4";
+ nvidia,function = "aud";
+ nvidia,pull = <TEGRA_PIN_PULL_NONE>;
+ nvidia,tristate = <TEGRA_PIN_DISABLE>;
+ nvidia,enable-input = <TEGRA_PIN_DISABLE>;
+ };
+ pin11 {
+ nvidia,pins = "uart1_rts_pr4";
+ nvidia,function = "uarta";
+ nvidia,pull = <TEGRA_PIN_PULL_NONE>;
+ nvidia,tristate = <TEGRA_PIN_DISABLE>;
+ nvidia,enable-input = <TEGRA_PIN_DISABLE>;
+ nvidia,lpdr = <0x0>;
+ };
+ pin12 {
+ nvidia,pins = "dap5_sclk_pt5";
+ nvidia,function = "i2s5";
+ nvidia,pull = <TEGRA_PIN_PULL_DOWN>;
+ nvidia,tristate = <TEGRA_PIN_DISABLE>;
+ nvidia,enable-input = <TEGRA_PIN_ENABLE>;
+ };
+ pin35 {
+ nvidia,pins = "dap5_fs_pu0";
+ nvidia,function = "i2s5";
+ nvidia,pull = <TEGRA_PIN_PULL_DOWN>;
+ nvidia,tristate = <TEGRA_PIN_DISABLE>;
+ nvidia,enable-input = <TEGRA_PIN_ENABLE>;
+ };
+ pin36 {
+ nvidia,pins = "uart1_cts_pr5";
+ nvidia,function = "uarta";
+ nvidia,pull = <TEGRA_PIN_PULL_UP>;
+ nvidia,tristate = <TEGRA_PIN_ENABLE>;
+ nvidia,enable-input = <TEGRA_PIN_ENABLE>;
+ nvidia,lpdr = <0x0>;
+ };
+ pin38 {
+ nvidia,pins = "dap5_din_pt7";
+ nvidia,function = "i2s5";
+ nvidia,pull = <TEGRA_PIN_PULL_DOWN>;
+ nvidia,tristate = <TEGRA_PIN_ENABLE>;
+ nvidia,enable-input = <TEGRA_PIN_ENABLE>;
+ };
+ pin40 {
+ nvidia,pins = "dap5_dout_pt6";
+ nvidia,function = "i2s5";
+ nvidia,pull = <TEGRA_PIN_PULL_DOWN>;
+ nvidia,tristate = <TEGRA_PIN_DISABLE>;
+ nvidia,enable-input = <TEGRA_PIN_DISABLE>;
+ };
+ };
+ };
+
+ /* Configure i2c bus clock to 400kHz for carrier board peripherals */
+ i2c@3160000 {
+ clock-frequency = <400000>;
+ status = "okay";
+ };
+
+ /* SDMMC3 for external user SD card slot with dedicated 3.3V power */
+ sdhci@3440000 {
+ uhs-mask = <0x78>;
+ mmc-ocr-mask = <0x0>;
+ cd-inverted;
+ cd-gpios = <&gpio_aon TEGRA194_AON_GPIO(CC, 4) GPIO_ACTIVE_LOW>;
+ nvidia,cd-wakeup-capable;
+ vmmc-supply = <&ap2306gn_3v3_sd>;
+ status = "okay";
+ };
+
+ /* Disable fan and tachometer hardware not populated on SE70 carrier board */
+ pwm-fan {
+ status = "disabled";
+ };
+ thermal-fan-est {
+ status = "disabled";
+ };
+ tachometer@39c0000 {
+ status = "disabled";
+ };
+ pwm@c340000 {
+ status = "disabled";
+ };
+ pwm@3280000 {
+ status = "disabled";
+ };
+ pwm@32c0000 {
+ status = "disabled";
+ };
+ pwm@32d0000 {
+ status = "disabled";
+ };
+ pwm@32f0000 {
+ status = "disabled";
+ };
+ generic_pwm_tachometer {
+ status = "disabled";
+ };
+
+ /* Disable unused SPI interfaces on 40-pin header */
+ spi@3210000 {
+ status = "disabled";
+ };
+ spi@3230000 {
+ status = "disabled";
+ };
+};
--
2.43.0
^ permalink raw reply related
* [PATCH 1/2] dt-bindings: arm: tegra: Add lenovo,thinkedge-se70 compatible string
From: Jiqi Li @ 2026-06-26 1:09 UTC (permalink / raw)
To: linux-tegra
Cc: devicetree, robh+dt, krzk+dt, conor+dt, jonathanh, thierry.reding,
mpearson-lenovo, Jiqi Li
In-Reply-To: <20260626010950.459899-1-lijq9@lenovo.com>
Lenovo ThinkEdge SE70 is a fanless industrial edge gateway carrier
board based on NVIDIA Tegra194 (Xavier NX) SOM.
Add the corresponding compatible string for device tree validation.
Signed-off-by: Jiqi Li <lijq9@lenovo.com>
---
Documentation/devicetree/bindings/arm/tegra.yaml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/tegra.yaml b/Documentation/devicetree/bindings/arm/tegra.yaml
index 033a63f6c068..960c604ef9a3 100644
--- a/Documentation/devicetree/bindings/arm/tegra.yaml
+++ b/Documentation/devicetree/bindings/arm/tegra.yaml
@@ -268,6 +268,10 @@ properties:
items:
- const: nvidia,p3509-0000+p3668-0001
- const: nvidia,tegra194
+ - description: Lenovo ThinkEdge SE70
+ items:
+ - const: lenovo,thinkedge-se70
+ - const: nvidia,tegra194
- items:
- const: nvidia,tegra234-vdk
- const: nvidia,tegra234
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v7 02/13] dt-bindings: display: renesas,rzg2l-du: Add support for RZ/G3E SoC
From: Tommaso Merciai @ 2026-06-26 0:59 UTC (permalink / raw)
To: Rob Herring
Cc: tomm.merciai, geert, laurent.pinchart, linux-renesas-soc,
biju.das.jz, David Airlie, Simona Vetter, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Geert Uytterhoeven, Magnus Damm,
Laurent Pinchart, dri-devel, devicetree, linux-kernel
In-Reply-To: <aiqtSWTTa4ZIThrp@tom-desktop>
Hi Rob,
On Thu, Jun 11, 2026 at 02:42:49PM +0200, Tommaso Merciai wrote:
> Hi Rob,
> Thanks for your review.
>
> On Wed, May 13, 2026 at 05:27:25PM -0500, Rob Herring wrote:
> > On Thu, May 07, 2026 at 11:21:30AM +0200, Tommaso Merciai wrote:
> > > The RZ/G3E SoC integrates two LCD controllers (LCDC0 and LCDC1), each
> > > containing a FCPVD, VSPD, and Display Unit (DU).
> > >
> > > - LCDC0 supports DSI and LVDS (single or dual-channel) outputs.
> > > - LCDC1 supports DSI, LVDS (single-channel), and RGB outputs.
> > >
> > > Add compatible string 'renesas,r9a09g047-du' and extend the binding to
> > > support two DU instances: add reg-names ('du0'/'du1'), extend reg,
> > > interrupts, and resets to maxItems: 2, and extend clocks/clock-names to
> > > six entries (aclk/pclk/vclk per instance, minItems: 3).
> >
> > Don't write what the diff has. I can read the diff for that.
>
> Ouch, thanks.
>
> >
> > >
> > > Drop the "Each port shall have a single endpoint." constraint since
> > > RZ/G3E ports expose multiple endpoints.
> > >
> > > Add a RZ/G3E-specific allOf rule mapping two DU instances to two ports:
> > >
> > > - port@0 (DU0): endpoint@0 DSI, endpoint@2 LVDS ch0, endpoint@3 LVDS ch1
> > > - port@1 (DU1): endpoint@0 DSI, endpoint@1 RGB (DPAD), endpoint@3 LVDS ch1
> > >
> > > Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> > > ---
> > > v6->v7:
> > > - Rebased on top of [1]
> > > [1] https://lore.kernel.org/all/20260429170012.366537-1-prabhakar.mahadev-lad.rj@bp.renesas.com/
> > > - Use single DRM device aggregating both DU instances (1 DU dt node),
> > > modelling single port for each DU0, DU1 and multiple endpoints for
> > > outputs.
> >
> > That seems like the completely wrong thing to do and you've given no
> > reason why you think it is the right choice.
>
>
> We had a discussion with Laurent at [1] about this topic.
> In particular:
>
> DSI ip can select at runtime input data path or DU0 or DU1
> via DSI_LINK_GPO0R VICH register. This can be done by managing the
> 2 DUs as single DRM device aggregating both DU instances that will spawn
> 2 crtcs. In this way at runtime we can select the output for DSI ip
> via the following commands:
>
> modetest -M rzg2l-du -s 58@55:800x600-56.25@XR24 (DU0 -> DSI)
> modetest -M rzg2l-du -s 58@56:800x600-56.25@XR24 (DU1 -> DSI)
>
> This can be done using option [B] (single drm device that spawn 2 crtc).
>
> Using option [A] we will have 2 drm devices 1 for DU0 and 1 for DU1
> that each will spawn a single CRTC and the above feature will be not
> achievable.
>
> In the end we need a way to have single DRM device that spawn 2 CRTCs.
>
> A) Two device tree nodes rapresenting DU0 and DU1 design [v6]:
>
> du0: display@16460000 {
> compatible = "renesas,r9a09g047-du";
> reg = <0 0x16460000 0 0x10000>;
> interrupts = <GIC_SPI 882 IRQ_TYPE_LEVEL_HIGH>;
> clocks = <&cpg CPG_MOD 0xed>,
> <&cpg CPG_MOD 0xee>,
> <&cpg CPG_MOD 0xef>;
> clock-names = "aclk", "pclk", "vclk";
> power-domains = <&cpg>;
> resets = <&cpg 0xdc>;
> renesas,vsps = <&vspd0 0>;
> status = "disabled";
>
> ports {
> #address-cells = <1>;
> #size-cells = <0>;
>
> port@0 {
> reg = <0>;
> du0_out_dsi: endpoint {
> };
> };
>
> port@2 {
> reg = <2>;
> du0_out_lvds0: endpoint {
> };
> };
>
> port@3 {
> reg = <3>;
> du0_out_lvds1: endpoint {
> };
> };
> };
> };
>
> du1: display@16490000 {
> compatible = "renesas,r9a09g047-du";
> reg = <0 0x16490000 0 0x10000>;
> interrupts = <GIC_SPI 922 IRQ_TYPE_LEVEL_HIGH>;
> clocks = <&cpg CPG_MOD 0x1a8>,
> <&cpg CPG_MOD 0x1a9>,
> <&cpg CPG_MOD 0x1aa>;
> clock-names = "aclk", "pclk", "vclk";
> power-domains = <&cpg>;
> resets = <&cpg 0x11e>;
> renesas,vsps = <&vspd1 0>;
> status = "disabled";
>
> ports {
> #address-cells = <1>;
> #size-cells = <0>;
>
> port@0 {
> reg = <0>;
> du1_out_dsi: endpoint {
> };
> };
>
> port@1 {
> reg = <1>;
> du1_out_rgb: endpoint {
> };
> };
>
> port@3 {
> reg = <3>;
> du1_out_lvds1: endpoint {
> };
> };
> };
> };
>
> ---
>
> B) Single device tree node design aggregating both DU instances [v7]:
>
> du: display@16460000 {
> compatible = "renesas,r9a09g047-du";
> reg = <0 0x16460000 0 0x10000>,
> <0 0x16490000 0 0x10000>;
> reg-names = "du0", "du1";
> interrupts = <GIC_SPI 882 IRQ_TYPE_LEVEL_HIGH>,
> <GIC_SPI 922 IRQ_TYPE_LEVEL_HIGH>;
> clocks = <&cpg CPG_MOD 0xed>,
> <&cpg CPG_MOD 0xee>,
> <&cpg CPG_MOD 0xef>,
> <&cpg CPG_MOD 0x1a8>,
> <&cpg CPG_MOD 0x1a9>,
> <&cpg CPG_MOD 0x1aa>;
> clock-names = "aclk", "pclk", "vclk",
> "aclk1", "pclk1", "vclk1";
> power-domains = <&cpg>;
> resets = <&cpg 0xdc>, <&cpg 0x11e>;
> reset-names = "resetn", "resetn1";
> renesas,vsps = <&vspd0 0>, <&vspd1 0>;
> status = "disabled";
>
> ports {
> #address-cells = <1>;
> #size-cells = <0>;
>
> port@0 {
> #address-cells = <1>;
> #size-cells = <0>;
>
> reg = <0>;
>
> du0_out_dsi: endpoint@0 {
> reg = <0>;
> };
>
> du0_out_lvds0: endpoint@2 {
> reg = <2>;
> };
>
> du0_out_lvds1: endpoint@3 {
> reg = <3>;
> };
> };
>
> port@1 {
> #address-cells = <1>;
> #size-cells = <0>;
>
> reg = <1>;
>
> du1_out_dsi: endpoint@0 {
> reg = <0>;
> };
>
> du1_out_rgb: endpoint@1 {
> reg = <1>;
> };
>
> du1_out_lvds1: endpoint@3 {
> reg = <3>;
> };
> };
> };
> };
>
> ---
Gentle ping on how to proceed on this patch?
Kind Regards,
Tommaso
>
> Please Biju, Laurent correct me if I'm missing something.
>
> [1] https://patchwork.kernel.org/project/linux-renesas-soc/patch/8f814f22ff62dcde6153260e2c8c29a5415c9a89.1775636898.git.tommaso.merciai.xr@bp.renesas.com/
> [v6] https://patchwork.kernel.org/project/linux-renesas-soc/patch/8f814f22ff62dcde6153260e2c8c29a5415c9a89.1775636898.git.tommaso.merciai.xr@bp.renesas.com/
> [v7] https://patchwork.kernel.org/project/linux-renesas-soc/patch/ff8e401a0667970a42a55420dcb071e34730a923.1778141145.git.tommaso.merciai.xr@bp.renesas.com/
>
> Kind Regards,
> Tommaso
>
> >
> > Rob
^ permalink raw reply
* Re: [PATCH 2/4] clk: en7523: add support for dedicated PCIe PERSTOUT reset
From: sashiko-bot @ 2026-06-26 0:20 UTC (permalink / raw)
To: Christian Marangi; +Cc: robh, conor+dt, linux-pci, devicetree
In-Reply-To: <20260625215741.3253212-3-ansuelsmth@gmail.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Data race between clock enable/disable and reset assert/deassert on the shared PCIC register (0x88).
- [Medium] The reset status callback does not account for inverted reset logic, reporting incorrect status for PCIC resets.
Pre-existing issues:
- [High] Missing synchronization in the reset controller allows concurrent reset operations to corrupt shared reset registers.
- [High] The `en7581_pci_disable` function calls `usleep_range()`, sleeping in an atomic context.
--
commit 82bdae998b98b49b0cf96f540c945b593f30a899
Author: Christian Marangi <ansuelsmth@gmail.com>
clk: en7523: add support for dedicated PCIe PERSTOUT reset
Add support for resetting the PCIe lines with the PERSTOUT reset. These
special reset are controlled by the PCIC register and are specific to each
of the 3 PCIe lines.
Notice that reset logic is inverted for these bit where 0 is assert and 1
deassert. This is intenrally handled in the reset function.
PCI enable/disable are updated to drop PERSTOUT bits in favor dedicated
reset handling.
> diff --git a/drivers/clk/clk-en7523.c b/drivers/clk/clk-en7523.c
> index 1ab0e2eca5d32..a33cf2e1b76f8 100644
> --- a/drivers/clk/clk-en7523.c
> +++ b/drivers/clk/clk-en7523.c
[ ... ]
> @@ -650,9 +654,7 @@ static void en7581_pci_disable(struct clk_hw *hw)
> void __iomem *np_base = cg->base;
> u32 val, mask;
>
> - mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1 |
> - REG_PCI_CONTROL_PERSTOUT1 | REG_PCI_CONTROL_PERSTOUT2 |
> - REG_PCI_CONTROL_PERSTOUT;
> + mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1;
> val = readl(np_base + REG_PCI_CONTROL);
> writel(val & ~mask, np_base + REG_PCI_CONTROL);
> usleep_range(1000, 2000);
[Severity: High]
This is a pre-existing issue, but the Common Clock Framework calls the
.disable callback with the enable_lock spinlock held and hardware interrupts
disabled.
Does calling usleep_range() here sleep in an atomic context and lead to a bug?
[ ... ]
> @@ -754,14 +756,21 @@ static int en7523_reset_update(struct reset_controller_dev *rcdev,
> unsigned long id, bool assert)
> {
> struct en_rst_data *rst_data = container_of(rcdev, struct en_rst_data, rcdev);
> - void __iomem *addr = rst_data->base + rst_data->bank_ofs[id / RST_NR_PER_BANK];
> + u32 offset = rst_data->bank_ofs[id / RST_NR_PER_BANK];
> + void __iomem *addr = rst_data->base + offset;
> + bool inverted = false;
> u32 val;
>
> + /* For PCIC reset logic is inverted, 0:assert 1:deassert*/
> + if (offset == REG_NP_SCU_PCIC)
> + inverted = true;
[Severity: Medium]
It appears en7523_reset_status() wasn't updated to handle the inverted logic:
drivers/clk/clk-en7523.c:en7523_reset_status() {
...
return !!(readl(addr) & BIT(id % RST_NR_PER_BANK));
}
Will callers of reset_control_status() receive incorrect status for these
resets, returning 0 for asserted and 1 for deasserted when querying PCIC
resets?
> +
> val = readl(addr);
> + val &= ~BIT(id % RST_NR_PER_BANK);
> if (assert)
> - val |= BIT(id % RST_NR_PER_BANK);
> + val |= inverted ? 0 : BIT(id % RST_NR_PER_BANK);
> else
> - val &= ~BIT(id % RST_NR_PER_BANK);
> + val |= inverted ? BIT(id % RST_NR_PER_BANK) : 0;
> writel(val, addr);
[Severity: High]
This is a pre-existing issue, but multiple drivers could concurrently probe or
change power states, causing their reset operations on the same register bank
to interleave.
Could this lockless read-modify-write operation allow concurrent reset
operations to corrupt the shared reset registers?
[Severity: High]
en7581_pci_enable() modifies REG_PCI_CONTROL using a lockless
read-modify-write:
drivers/clk/clk-en7523.c:en7581_pci_enable() {
...
val = readl(np_base + REG_PCI_CONTROL);
writel(val | mask, np_base + REG_PCI_CONTROL);
...
}
If REG_NP_SCU_PCIC and REG_PCI_CONTROL point to the same register, a
concurrent clk_enable() and reset_control_assert() could silently corrupt
each other's bits since they belong to different lock domains.
Will this read-modify-write race with the clock framework?
>
> return 0;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260625215741.3253212-1-ansuelsmth@gmail.com?part=2
^ permalink raw reply
* [PATCH RFC 4/4] arm64: dts: imx8mq-librem5: Add rear camera
From: Vincent Cloutier @ 2026-06-26 0:07 UTC (permalink / raw)
To: linux-media, devicetree, linux-arm-kernel
Cc: linux-kernel, linux-imx, kernel, Vincent Cloutier, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, imx
In-Reply-To: <20260626000715.1111803-1-vincent.cloutier@icloud.com>
From: Vincent Cloutier <vincent@cloutier.co>
Add the Librem 5 rear camera sensor node and enable the second CSI-2
capture path that receives data from it.
Describe the Samsung S5K3L6 sensor with the upstream binding property
names, including reset-gpios and the link frequencies used by the initial
two-lane RAW8/RAW10 driver modes.
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
Assisted-by: OpenCode:gpt-5.5
---
arch/arm64/boot/dts/freescale/imx8mq-librem5.dtsi | 51 +++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/arch/arm64/boot/dts/freescale/imx8mq-librem5.dtsi b/arch/arm64/boot/dts/freescale/imx8mq-librem5.dtsi
index f5d529c5baf3..12d5fb3440c5 100644
--- a/arch/arm64/boot/dts/freescale/imx8mq-librem5.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8mq-librem5.dtsi
@@ -318,6 +318,10 @@ &csi1 {
status = "okay";
};
+&csi2 {
+ status = "okay";
+};
+
&ddrc {
operating-points-v2 = <&ddrc_opp_table>;
status = "okay";
@@ -434,6 +438,13 @@ MX8MQ_IOMUXC_ENET_RXC_GPIO1_IO25 0x83
>;
};
+ pinctrl_csi2: csi2grp {
+ fsl,pins = <
+ /* CSI2_NRST */
+ MX8MQ_IOMUXC_ENET_RD0_GPIO1_IO26 0x83
+ >;
+ };
+
pinctrl_charger_in: chargeringrp {
fsl,pins = <
/* CHRG_INT */
@@ -1175,6 +1186,31 @@ vcm@c {
vcc-supply = <®_csi_1v8>;
};
+ camera_rear: camera@2d {
+ compatible = "samsung,s5k3l6";
+ reg = <0x2d>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_csi2>;
+ clocks = <&clk IMX8MQ_CLK_CLKO2>;
+ assigned-clocks = <&clk IMX8MQ_CLK_CLKO2>;
+ assigned-clock-rates = <25000000>;
+ reset-gpios = <&gpio1 26 GPIO_ACTIVE_LOW>;
+ vdda-supply = <®_vcam_2v8>;
+ vddd-supply = <®_vcam_1v2>;
+ vddio-supply = <®_csi_1v8>;
+ rotation = <270>;
+ orientation = <1>;
+
+ port {
+ camera2_ep: endpoint {
+ data-lanes = <1 2>;
+ link-frequencies = /bits/ 64
+ <537500000 600000000 625000000>;
+ remote-endpoint = <&mipi2_sensor_ep>;
+ };
+ };
+ };
+
bat: fuel-gauge@36 {
compatible = "maxim,max17055";
reg = <0x36>;
@@ -1226,6 +1262,21 @@ mipi1_sensor_ep: endpoint {
};
};
+&mipi_csi2 {
+ status = "okay";
+
+ ports {
+ port@0 {
+ reg = <0>;
+
+ mipi2_sensor_ep: endpoint {
+ remote-endpoint = <&camera2_ep>;
+ data-lanes = <1 2>;
+ };
+ };
+ };
+};
+
&mipi_dsi {
#address-cells = <1>;
#size-cells = <0>;
--
2.53.0
^ permalink raw reply related
* [PATCH RFC 3/4] media: i2c: Add Samsung S5K3L6 image sensor driver
From: Vincent Cloutier @ 2026-06-26 0:06 UTC (permalink / raw)
To: linux-media, devicetree, linux-arm-kernel
Cc: linux-kernel, linux-imx, kernel, Vincent Cloutier,
Mauro Carvalho Chehab, Sakari Ailus, Hans Verkuil, Hans de Goede,
Vladimir Zapolskiy, Mehdi Djait, Laurent Pinchart, Xiaolei Wang,
Walter Werner Schneider, Kate Hsuan, Hardevsinh Palaniya,
Himanshu Bhavani, Svyatoslav Ryhel
In-Reply-To: <20260626000715.1111803-1-vincent.cloutier@icloud.com>
From: Vincent Cloutier <vincent@cloutier.co>
Add a V4L2 sub-device driver for the Samsung S5K3L6 raw Bayer image
sensor.
The initial driver supports the production path used by the Librem 5 rear
camera: a 25 MHz input clock, two MIPI CSI-2 data lanes, RAW8 and RAW10
Bayer modes derived from the full 4208x3120 active array, runtime PM, CCI
register access, basic exposure/gain/blanking controls, test patterns, and
fwnode orientation controls.
This driver is derived from the Librem 5 downstream S5K3L6 carry, initially
authored by Martin Kepplinger and substantially developed by Dorota
Czaplejewicz and Sebastian Krzyszkowiak. The upstream submission rewrites
and collapses that history into a current V4L2 sensor driver shape.
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
Assisted-by: OpenCode:gpt-5.5
---
drivers/media/i2c/Kconfig | 10 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/s5k3l6.c | 1055 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1066 insertions(+)
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 5d173e0ecf42..5ec4db05920a 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -781,6 +781,16 @@ config VIDEO_S5K3M5
To compile this driver as a module, choose M here: the
module will be called s5k3m5.
+config VIDEO_S5K3L6
+ tristate "Samsung S5K3L6 sensor support"
+ select V4L2_CCI_I2C
+ help
+ This is a V4L2 sensor driver for Samsung S5K3L6 13MP raw
+ camera sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called s5k3l6.
+
config VIDEO_S5K5BAF
tristate "Samsung S5K5BAF sensor support"
help
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index e45359efe0e4..f3360e97aa38 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -128,6 +128,7 @@ obj-$(CONFIG_VIDEO_RDACM21) += rdacm21.o
obj-$(CONFIG_VIDEO_RJ54N1) += rj54n1cb0c.o
obj-$(CONFIG_VIDEO_S5C73M3) += s5c73m3/
obj-$(CONFIG_VIDEO_S5K3M5) += s5k3m5.o
+obj-$(CONFIG_VIDEO_S5K3L6) += s5k3l6.o
obj-$(CONFIG_VIDEO_S5K5BAF) += s5k5baf.o
obj-$(CONFIG_VIDEO_S5K6A3) += s5k6a3.o
obj-$(CONFIG_VIDEO_S5KJN1) += s5kjn1.o
diff --git a/drivers/media/i2c/s5k3l6.c b/drivers/media/i2c/s5k3l6.c
new file mode 100644
index 000000000000..f70f83e9de17
--- /dev/null
+++ b/drivers/media/i2c/s5k3l6.c
@@ -0,0 +1,1055 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2020-2021 Purism SPC
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/units.h>
+
+#include <media/v4l2-cci.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+
+#define S5K3L6_MCLK_FREQ (25 * HZ_PER_MHZ)
+#define S5K3L6_DATA_LANES 2
+
+#define S5K3L6_LINK_FREQ_537P5MHZ (537500ULL * HZ_PER_KHZ)
+#define S5K3L6_LINK_FREQ_600MHZ (600ULL * HZ_PER_MHZ)
+#define S5K3L6_LINK_FREQ_625MHZ (625ULL * HZ_PER_MHZ)
+
+/* Downstream mode tables use a fixed 480 MHz VT pixel clock. */
+#define S5K3L6_PIXEL_RATE (480ULL * HZ_PER_MHZ)
+
+#define S5K3L6_PIXEL_ARRAY_WIDTH 4224
+#define S5K3L6_PIXEL_ARRAY_HEIGHT 3136
+#define S5K3L6_ACTIVE_LEFT 8
+#define S5K3L6_ACTIVE_TOP 8
+#define S5K3L6_ACTIVE_WIDTH 4208
+#define S5K3L6_ACTIVE_HEIGHT 3120
+
+#define S5K3L6_CHIP_ID 0x30c6
+#define S5K3L6_REVISION 0xb0
+
+#define S5K3L6_REG_CHIP_ID CCI_REG16(0x0000)
+#define S5K3L6_REG_REVISION CCI_REG8(0x0002)
+#define S5K3L6_REG_MODE_SELECT CCI_REG8(0x0100)
+#define S5K3L6_MODE_STANDBY 0x00
+#define S5K3L6_MODE_STREAMING 0x01
+
+#define S5K3L6_REG_LANE_MODE CCI_REG8(0x0114)
+#define S5K3L6_REG_DATA_FORMAT CCI_REG16(0x0112)
+#define S5K3L6_DATA_FORMAT_RAW8 0x0808
+#define S5K3L6_DATA_FORMAT_RAW10 0x0a0a
+
+#define S5K3L6_REG_OP_PLL_MULTIPLIER CCI_REG16(0x030e)
+#define S5K3L6_REG_REQUESTED_LINK_RATE CCI_REG16(0x0820)
+#define S5K3L6_REG_FRAME_LENGTH CCI_REG16(0x0340)
+#define S5K3L6_REG_LINE_LENGTH CCI_REG16(0x0342)
+#define S5K3L6_REG_X_ADDR_START CCI_REG16(0x0344)
+#define S5K3L6_REG_Y_ADDR_START CCI_REG16(0x0346)
+#define S5K3L6_REG_X_ADDR_END CCI_REG16(0x0348)
+#define S5K3L6_REG_Y_ADDR_END CCI_REG16(0x034a)
+#define S5K3L6_REG_X_OUTPUT_SIZE CCI_REG16(0x034c)
+#define S5K3L6_REG_Y_OUTPUT_SIZE CCI_REG16(0x034e)
+
+#define S5K3L6_REG_EXPOSURE CCI_REG16(0x0202)
+#define S5K3L6_EXPOSURE_MIN 2
+#define S5K3L6_EXPOSURE_MARGIN 2
+
+#define S5K3L6_REG_ANALOG_GAIN CCI_REG16(0x0204)
+#define S5K3L6_ANALOG_GAIN_MIN 0x20
+#define S5K3L6_ANALOG_GAIN_MAX 0x200
+#define S5K3L6_ANALOG_GAIN_DEFAULT 0x20
+
+#define S5K3L6_REG_DIGITAL_GAIN CCI_REG16(0x020e)
+#define S5K3L6_DIGITAL_GAIN_MIN 0x100
+#define S5K3L6_DIGITAL_GAIN_MAX 0x300
+#define S5K3L6_DIGITAL_GAIN_DEFAULT 0x100
+
+#define S5K3L6_REG_TEST_PATTERN CCI_REG8(0x0601)
+
+#define S5K3L6_REG_BPC CCI_REG8(0x3403)
+#define S5K3L6_BPC_FILTER BIT(0)
+#define S5K3L6_BPC_AF_FILTER BIT(2)
+
+#define S5K3L6_REG_PLL_PD CCI_REG8(0x3c1e)
+#define S5K3L6_REG_MIPI_CONTINUOUS CCI_REG8(0x38a1)
+
+#define to_s5k3l6(_sd) container_of(_sd, struct s5k3l6, sd)
+
+static const s64 s5k3l6_link_freq_menu[] = {
+ S5K3L6_LINK_FREQ_537P5MHZ,
+ S5K3L6_LINK_FREQ_600MHZ,
+ S5K3L6_LINK_FREQ_625MHZ,
+};
+
+static const char *const s5k3l6_supply_names[] = {
+ "vddio",
+ "vdda",
+ "vddd",
+};
+
+#define S5K3L6_NUM_SUPPLIES ARRAY_SIZE(s5k3l6_supply_names)
+
+struct s5k3l6_reg_list {
+ const struct cci_reg_sequence *regs;
+ unsigned int num_regs;
+};
+
+struct s5k3l6_mode {
+ u32 width;
+ u32 height;
+ u32 code;
+ u32 hts;
+ u32 vts;
+ u32 data_format;
+ u16 op_pll_multiplier;
+ u8 binning;
+ u8 link_freq_index;
+
+ const struct s5k3l6_reg_list reg_list;
+};
+
+struct s5k3l6 {
+ struct device *dev;
+ struct regmap *regmap;
+ struct clk *mclk;
+ struct gpio_desc *reset_gpio;
+ struct regulator_bulk_data supplies[S5K3L6_NUM_SUPPLIES];
+
+ struct v4l2_subdev sd;
+ struct media_pad pad;
+
+ struct v4l2_ctrl_handler ctrl_handler;
+ struct v4l2_ctrl *link_freq;
+ struct v4l2_ctrl *pixel_rate;
+ struct v4l2_ctrl *hblank;
+ struct v4l2_ctrl *vblank;
+ struct v4l2_ctrl *exposure;
+
+ const struct s5k3l6_mode *mode;
+};
+
+static const struct cci_reg_sequence s5k3l6_binning_4x4[] = {
+ { CCI_REG8(0x0900), 0x01 },
+ { CCI_REG8(0x0901), 0x44 },
+ { CCI_REG8(0x0387), 0x07 },
+ { CCI_REG16(0x3090), 0x8000 },
+ { CCI_REG16(0x314a), 0x5f02 },
+ { CCI_REG16(0x32b2), 0x0006 },
+ { CCI_REG16(0x32b4), 0x0006 },
+ { CCI_REG16(0x32b6), 0x0006 },
+ { CCI_REG16(0x32b8), 0x0006 },
+ { CCI_REG16(0x3238), 0x000a },
+ { CCI_REG16(0x380c), 0x003b },
+};
+
+static const struct cci_reg_sequence s5k3l6_binning_2x2[] = {
+ { CCI_REG8(0x0900), 0x01 },
+ { CCI_REG8(0x0901), 0x22 },
+ { CCI_REG8(0x0387), 0x03 },
+ { CCI_REG16(0x3090), 0x8000 },
+ { CCI_REG16(0x314a), 0x5f02 },
+ { CCI_REG16(0x32b2), 0x0003 },
+ { CCI_REG16(0x32b4), 0x0003 },
+ { CCI_REG16(0x32b6), 0x0003 },
+ { CCI_REG16(0x32b8), 0x0003 },
+ { CCI_REG16(0x3238), 0x000b },
+ { CCI_REG16(0x380c), 0x0049 },
+};
+
+static const struct cci_reg_sequence s5k3l6_no_binning[] = {
+ { CCI_REG8(0x0900), 0x00 },
+ { CCI_REG8(0x0387), 0x01 },
+ { CCI_REG16(0x3090), 0x8800 },
+ { CCI_REG16(0x314a), 0x5f00 },
+ { CCI_REG16(0x32b2), 0x0000 },
+ { CCI_REG16(0x32b4), 0x0000 },
+ { CCI_REG16(0x32b6), 0x0000 },
+ { CCI_REG16(0x32b8), 0x0000 },
+ { CCI_REG16(0x3238), 0x000c },
+ { CCI_REG16(0x380c), 0x0090 },
+};
+
+static const struct cci_reg_sequence s5k3l6_common_regs[] = {
+ { CCI_REG16(0x0136), 0x1900 },
+ { CCI_REG16(0x0300), 0x0005 },
+ { CCI_REG16(0x0304), 0x0004 },
+ { CCI_REG16(0x0306), 0x0060 },
+ { CCI_REG16(0x030c), 0x0004 },
+ { CCI_REG16(0x3002), 0x0e00 },
+ { CCI_REG16(0x3006), 0x1000 },
+ { CCI_REG16(0x300a), 0x0c00 },
+ { CCI_REG16(0x3018), 0xc500 },
+ { CCI_REG16(0x3024), 0x0016 },
+ { CCI_REG16(0x306a), 0x2f4c },
+ { CCI_REG16(0x3070), 0x3d00 },
+ { CCI_REG16(0x3072), 0x0013 },
+ { CCI_REG16(0x3074), 0x0977 },
+ { CCI_REG16(0x3076), 0x9411 },
+ { CCI_REG16(0x307a), 0x0d20 },
+ { CCI_REG16(0x3084), 0x1314 },
+ { CCI_REG16(0x309c), 0x0640 },
+ { CCI_REG16(0x309e), 0x002d },
+ { CCI_REG16(0x3266), 0x0001 },
+ { CCI_REG16(0x3452), 0x0000 },
+ { CCI_REG16(0x345a), 0x0000 },
+ { CCI_REG16(0x345c), 0x0000 },
+ { CCI_REG16(0x345e), 0x0000 },
+ { CCI_REG16(0x3460), 0x0000 },
+ { CCI_REG16(0x38da), 0x000a },
+ { CCI_REG16(0x38dc), 0x000b },
+ { CCI_REG16(0x38d6), 0x000a },
+ { CCI_REG16(0x3932), 0x1000 },
+ { CCI_REG16(0x393e), 0x4000 },
+ { CCI_REG16(0x3c36), 0x2800 },
+ { CCI_REG16(0x3c38), 0x0028 },
+ { S5K3L6_REG_MIPI_CONTINUOUS, 0x7e },
+ { S5K3L6_REG_BPC,
+ 0x42 | S5K3L6_BPC_FILTER | S5K3L6_BPC_AF_FILTER },
+};
+
+static const struct s5k3l6_mode s5k3l6_supported_modes[] = {
+ {
+ .width = 1052,
+ .height = 780,
+ .code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .hts = 0x1320,
+ .vts = 0x0331,
+ .data_format = S5K3L6_DATA_FORMAT_RAW8,
+ .op_pll_multiplier = 0x56,
+ .binning = 4,
+ .link_freq_index = 0,
+ .reg_list = {
+ .regs = s5k3l6_binning_4x4,
+ .num_regs = ARRAY_SIZE(s5k3l6_binning_4x4),
+ },
+ }, {
+ .width = 2104,
+ .height = 1560,
+ .code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .hts = 0x1320,
+ .vts = 0x0662,
+ .data_format = S5K3L6_DATA_FORMAT_RAW8,
+ .op_pll_multiplier = 0x56,
+ .binning = 2,
+ .link_freq_index = 0,
+ .reg_list = {
+ .regs = s5k3l6_binning_2x2,
+ .num_regs = ARRAY_SIZE(s5k3l6_binning_2x2),
+ },
+ }, {
+ .width = 4208,
+ .height = 3120,
+ .code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .hts = 0x1ce0,
+ .vts = 0x0cbc,
+ .data_format = S5K3L6_DATA_FORMAT_RAW8,
+ .op_pll_multiplier = 0x64,
+ .binning = 1,
+ .link_freq_index = 2,
+ .reg_list = {
+ .regs = s5k3l6_no_binning,
+ .num_regs = ARRAY_SIZE(s5k3l6_no_binning),
+ },
+ }, {
+ .width = 1052,
+ .height = 780,
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .hts = 0x1320,
+ .vts = 0x0331,
+ .data_format = S5K3L6_DATA_FORMAT_RAW10,
+ .op_pll_multiplier = 0x56,
+ .binning = 4,
+ .link_freq_index = 0,
+ .reg_list = {
+ .regs = s5k3l6_binning_4x4,
+ .num_regs = ARRAY_SIZE(s5k3l6_binning_4x4),
+ },
+ }, {
+ .width = 2104,
+ .height = 1560,
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .hts = 0x1320,
+ .vts = 0x0662,
+ .data_format = S5K3L6_DATA_FORMAT_RAW10,
+ .op_pll_multiplier = 0x60,
+ .binning = 2,
+ .link_freq_index = 1,
+ .reg_list = {
+ .regs = s5k3l6_binning_2x2,
+ .num_regs = ARRAY_SIZE(s5k3l6_binning_2x2),
+ },
+ }, {
+ .width = 4208,
+ .height = 3120,
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .hts = 0x2650,
+ .vts = 0x0cbc,
+ .data_format = S5K3L6_DATA_FORMAT_RAW10,
+ .op_pll_multiplier = 0x64,
+ .binning = 1,
+ .link_freq_index = 2,
+ .reg_list = {
+ .regs = s5k3l6_no_binning,
+ .num_regs = ARRAY_SIZE(s5k3l6_no_binning),
+ },
+ },
+};
+
+static const char *const s5k3l6_test_pattern_menu[] = {
+ "Disabled",
+ "Solid color",
+ "Color bars",
+};
+
+static bool s5k3l6_code_supported(u32 code)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(s5k3l6_supported_modes); i++) {
+ if (s5k3l6_supported_modes[i].code == code)
+ return true;
+ }
+
+ return false;
+}
+
+static const struct s5k3l6_mode *s5k3l6_find_mode(u32 code, u32 width,
+ u32 height)
+{
+ const struct s5k3l6_mode *best = NULL;
+ u32 best_delta = U32_MAX;
+ unsigned int i;
+
+ if (!s5k3l6_code_supported(code))
+ code = s5k3l6_supported_modes[0].code;
+
+ for (i = 0; i < ARRAY_SIZE(s5k3l6_supported_modes); i++) {
+ const struct s5k3l6_mode *mode = &s5k3l6_supported_modes[i];
+ u32 delta;
+
+ if (mode->code != code)
+ continue;
+
+ delta = abs((int)mode->width - (int)width) +
+ abs((int)mode->height - (int)height);
+ if (!best || delta < best_delta) {
+ best = mode;
+ best_delta = delta;
+ }
+ }
+
+ return best;
+}
+
+static void s5k3l6_update_pad_format(const struct s5k3l6_mode *mode,
+ struct v4l2_mbus_framefmt *fmt)
+{
+ fmt->code = mode->code;
+ fmt->width = mode->width;
+ fmt->height = mode->height;
+ fmt->field = V4L2_FIELD_NONE;
+ fmt->colorspace = V4L2_COLORSPACE_RAW;
+ fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+ fmt->xfer_func = V4L2_XFER_FUNC_NONE;
+}
+
+static void s5k3l6_update_controls(struct s5k3l6 *s5k3l6,
+ const struct s5k3l6_mode *mode)
+{
+ u32 hblank = mode->hts - mode->width;
+ u32 vblank = mode->vts - mode->height;
+ u32 exposure_max = mode->vts - S5K3L6_EXPOSURE_MARGIN;
+ u32 exposure_default = min_t(u32, 0x03de, exposure_max);
+
+ __v4l2_ctrl_s_ctrl(s5k3l6->link_freq, mode->link_freq_index);
+ __v4l2_ctrl_modify_range(s5k3l6->hblank, hblank, hblank, 1, hblank);
+ __v4l2_ctrl_modify_range(s5k3l6->vblank, vblank,
+ 0xffff - mode->height, 1, vblank);
+ __v4l2_ctrl_s_ctrl(s5k3l6->vblank, vblank);
+ __v4l2_ctrl_modify_range(s5k3l6->exposure, S5K3L6_EXPOSURE_MIN,
+ exposure_max, 1, exposure_default);
+ __v4l2_ctrl_s_ctrl(s5k3l6->exposure, exposure_default);
+}
+
+static int s5k3l6_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct s5k3l6 *s5k3l6 =
+ container_of(ctrl->handler, struct s5k3l6, ctrl_handler);
+ const struct s5k3l6_mode *mode = s5k3l6->mode;
+ u32 frame_length;
+ u32 exposure_max;
+ int active;
+ int ret;
+
+ if (ctrl->id == V4L2_CID_VBLANK) {
+ exposure_max =
+ mode->height + ctrl->val - S5K3L6_EXPOSURE_MARGIN;
+ __v4l2_ctrl_modify_range(s5k3l6->exposure,
+ S5K3L6_EXPOSURE_MIN, exposure_max, 1,
+ s5k3l6->exposure->default_value);
+ }
+
+ active = pm_runtime_get_if_active(s5k3l6->dev);
+ if (!active)
+ return 0;
+ if (active < 0)
+ return active;
+
+ switch (ctrl->id) {
+ case V4L2_CID_ANALOGUE_GAIN:
+ ret = cci_write(s5k3l6->regmap, S5K3L6_REG_ANALOG_GAIN,
+ ctrl->val, NULL);
+ break;
+ case V4L2_CID_DIGITAL_GAIN:
+ ret = cci_write(s5k3l6->regmap, S5K3L6_REG_DIGITAL_GAIN,
+ ctrl->val, NULL);
+ break;
+ case V4L2_CID_EXPOSURE:
+ ret = cci_write(s5k3l6->regmap, S5K3L6_REG_EXPOSURE,
+ ctrl->val, NULL);
+ break;
+ case V4L2_CID_VBLANK:
+ frame_length = mode->height + ctrl->val;
+ ret = cci_write(s5k3l6->regmap, S5K3L6_REG_FRAME_LENGTH,
+ frame_length, NULL);
+ break;
+ case V4L2_CID_TEST_PATTERN:
+ ret = cci_write(s5k3l6->regmap, S5K3L6_REG_TEST_PATTERN,
+ ctrl->val, NULL);
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+
+ pm_runtime_put(s5k3l6->dev);
+
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops s5k3l6_ctrl_ops = {
+ .s_ctrl = s5k3l6_set_ctrl,
+};
+
+static int s5k3l6_init_controls(struct s5k3l6 *s5k3l6)
+{
+ struct v4l2_ctrl_handler *ctrl_hdlr = &s5k3l6->ctrl_handler;
+ const struct v4l2_ctrl_ops *ops = &s5k3l6_ctrl_ops;
+ const struct s5k3l6_mode *mode = s5k3l6->mode;
+ struct v4l2_fwnode_device_properties props;
+ u32 hblank = mode->hts - mode->width;
+ u32 vblank = mode->vts - mode->height;
+ u32 exposure_max = mode->vts - S5K3L6_EXPOSURE_MARGIN;
+ u32 exposure_default = min_t(u32, 0x03de, exposure_max);
+ int ret;
+
+ v4l2_ctrl_handler_init(ctrl_hdlr, 10);
+
+ s5k3l6->link_freq =
+ v4l2_ctrl_new_int_menu(ctrl_hdlr, &s5k3l6_ctrl_ops,
+ V4L2_CID_LINK_FREQ,
+ ARRAY_SIZE(s5k3l6_link_freq_menu) - 1,
+ mode->link_freq_index,
+ s5k3l6_link_freq_menu);
+ if (s5k3l6->link_freq)
+ s5k3l6->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ s5k3l6->pixel_rate =
+ v4l2_ctrl_new_std(ctrl_hdlr, &s5k3l6_ctrl_ops,
+ V4L2_CID_PIXEL_RATE, S5K3L6_PIXEL_RATE,
+ S5K3L6_PIXEL_RATE, 1, S5K3L6_PIXEL_RATE);
+ if (s5k3l6->pixel_rate)
+ s5k3l6->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ s5k3l6->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &s5k3l6_ctrl_ops,
+ V4L2_CID_HBLANK, hblank, hblank, 1,
+ hblank);
+ if (s5k3l6->hblank)
+ s5k3l6->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ s5k3l6->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &s5k3l6_ctrl_ops,
+ V4L2_CID_VBLANK, vblank,
+ 0xffff - mode->height, 1, vblank);
+
+ v4l2_ctrl_new_std(ctrl_hdlr, &s5k3l6_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+ S5K3L6_ANALOG_GAIN_MIN, S5K3L6_ANALOG_GAIN_MAX, 1,
+ S5K3L6_ANALOG_GAIN_DEFAULT);
+
+ v4l2_ctrl_new_std(ctrl_hdlr, &s5k3l6_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+ S5K3L6_DIGITAL_GAIN_MIN, S5K3L6_DIGITAL_GAIN_MAX,
+ 1, S5K3L6_DIGITAL_GAIN_DEFAULT);
+
+ s5k3l6->exposure =
+ v4l2_ctrl_new_std(ctrl_hdlr, ops, V4L2_CID_EXPOSURE,
+ S5K3L6_EXPOSURE_MIN, exposure_max, 1,
+ exposure_default);
+
+ v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &s5k3l6_ctrl_ops,
+ V4L2_CID_TEST_PATTERN,
+ ARRAY_SIZE(s5k3l6_test_pattern_menu) - 1,
+ 0, 0, s5k3l6_test_pattern_menu);
+
+ if (ctrl_hdlr->error) {
+ ret = ctrl_hdlr->error;
+ goto error_free_hdlr;
+ }
+
+ ret = v4l2_fwnode_device_parse(s5k3l6->dev, &props);
+ if (ret)
+ goto error_free_hdlr;
+
+ ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &s5k3l6_ctrl_ops,
+ &props);
+ if (ret)
+ goto error_free_hdlr;
+
+ s5k3l6->sd.ctrl_handler = ctrl_hdlr;
+
+ return 0;
+
+error_free_hdlr:
+ v4l2_ctrl_handler_free(ctrl_hdlr);
+
+ return ret;
+}
+
+static int s5k3l6_write_mode(struct s5k3l6 *s5k3l6)
+{
+ const struct s5k3l6_mode *mode = s5k3l6->mode;
+ u64 link_freq = s5k3l6_link_freq_menu[mode->link_freq_index];
+ /*
+ * Match downstream integer-MHz programming. The 537.5 MHz mode uses
+ * 1074 here, not the rounded 1075 Mb/s DDR lane rate.
+ */
+ u16 link_rate = div_u64(link_freq, HZ_PER_MHZ) * 2;
+ u32 crop_width = mode->width * mode->binning;
+ u32 crop_height = mode->height * mode->binning;
+ u32 x_start = S5K3L6_ACTIVE_LEFT;
+ u32 y_start = S5K3L6_ACTIVE_TOP;
+ u32 x_end = x_start + crop_width - 1;
+ u32 y_end = y_start + crop_height - 1;
+ struct cci_reg_sequence crop_regs[] = {
+ { S5K3L6_REG_X_OUTPUT_SIZE, mode->width },
+ { S5K3L6_REG_Y_OUTPUT_SIZE, mode->height },
+ { S5K3L6_REG_Y_ADDR_START, y_start },
+ { S5K3L6_REG_Y_ADDR_END, y_end },
+ { S5K3L6_REG_X_ADDR_START, x_start },
+ { S5K3L6_REG_X_ADDR_END, x_end },
+ };
+ struct cci_reg_sequence format_regs[] = {
+ { S5K3L6_REG_DATA_FORMAT, mode->data_format },
+ { S5K3L6_REG_LANE_MODE, S5K3L6_DATA_LANES - 1 },
+ { S5K3L6_REG_OP_PLL_MULTIPLIER, mode->op_pll_multiplier },
+ { S5K3L6_REG_REQUESTED_LINK_RATE, link_rate },
+ { S5K3L6_REG_LINE_LENGTH, mode->hts },
+ };
+ int ret = 0;
+
+ cci_multi_reg_write(s5k3l6->regmap, crop_regs, ARRAY_SIZE(crop_regs),
+ &ret);
+ cci_multi_reg_write(s5k3l6->regmap, mode->reg_list.regs,
+ mode->reg_list.num_regs, &ret);
+ cci_multi_reg_write(s5k3l6->regmap, s5k3l6_common_regs,
+ ARRAY_SIZE(s5k3l6_common_regs), &ret);
+ cci_multi_reg_write(s5k3l6->regmap, format_regs,
+ ARRAY_SIZE(format_regs), &ret);
+
+ return ret;
+}
+
+static int s5k3l6_enable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct s5k3l6 *s5k3l6 = to_s5k3l6(sd);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(s5k3l6->dev);
+ if (ret)
+ return ret;
+
+ cci_write(s5k3l6->regmap, S5K3L6_REG_PLL_PD, 0x01, &ret);
+ if (ret)
+ goto error;
+
+ ret = s5k3l6_write_mode(s5k3l6);
+ if (ret)
+ goto error;
+
+ ret = __v4l2_ctrl_handler_setup(s5k3l6->sd.ctrl_handler);
+ if (ret)
+ goto error;
+
+ cci_write(s5k3l6->regmap, S5K3L6_REG_MODE_SELECT,
+ S5K3L6_MODE_STREAMING, &ret);
+ cci_write(s5k3l6->regmap, S5K3L6_REG_PLL_PD, 0x00, &ret);
+ if (ret)
+ goto error;
+
+ return 0;
+
+error:
+ dev_err(s5k3l6->dev, "failed to start streaming: %d\n", ret);
+ pm_runtime_put_autosuspend(s5k3l6->dev);
+
+ return ret;
+}
+
+static int s5k3l6_disable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct s5k3l6 *s5k3l6 = to_s5k3l6(sd);
+ int ret;
+
+ ret = cci_write(s5k3l6->regmap, S5K3L6_REG_MODE_SELECT,
+ S5K3L6_MODE_STANDBY, NULL);
+ if (ret)
+ dev_err(s5k3l6->dev, "failed to stop streaming: %d\n", ret);
+
+ pm_runtime_put_autosuspend(s5k3l6->dev);
+
+ return ret;
+}
+
+static int s5k3l6_set_pad_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct s5k3l6 *s5k3l6 = to_s5k3l6(sd);
+ const struct s5k3l6_mode *mode;
+
+ mode = s5k3l6_find_mode(fmt->format.code, fmt->format.width,
+ fmt->format.height);
+ s5k3l6_update_pad_format(mode, &fmt->format);
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE && s5k3l6->mode != mode) {
+ s5k3l6->mode = mode;
+ s5k3l6_update_controls(s5k3l6, mode);
+ }
+
+ *v4l2_subdev_state_get_format(state, 0) = fmt->format;
+
+ return 0;
+}
+
+static int s5k3l6_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ static const u32 codes[] = {
+ MEDIA_BUS_FMT_SGRBG8_1X8,
+ MEDIA_BUS_FMT_SGRBG10_1X10,
+ };
+
+ if (code->index >= ARRAY_SIZE(codes))
+ return -EINVAL;
+
+ code->code = codes[code->index];
+
+ return 0;
+}
+
+static int s5k3l6_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ unsigned int index = 0;
+ unsigned int i;
+
+ if (!s5k3l6_code_supported(fse->code))
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(s5k3l6_supported_modes); i++) {
+ const struct s5k3l6_mode *mode = &s5k3l6_supported_modes[i];
+
+ if (mode->code != fse->code)
+ continue;
+
+ if (index++ != fse->index)
+ continue;
+
+ fse->min_width = mode->width;
+ fse->max_width = mode->width;
+ fse->min_height = mode->height;
+ fse->max_height = mode->height;
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int s5k3l6_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_selection *sel)
+{
+ switch (sel->target) {
+ case V4L2_SEL_TGT_NATIVE_SIZE:
+ sel->r.left = 0;
+ sel->r.top = 0;
+ sel->r.width = S5K3L6_PIXEL_ARRAY_WIDTH;
+ sel->r.height = S5K3L6_PIXEL_ARRAY_HEIGHT;
+ return 0;
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP:
+ sel->r.left = S5K3L6_ACTIVE_LEFT;
+ sel->r.top = S5K3L6_ACTIVE_TOP;
+ sel->r.width = S5K3L6_ACTIVE_WIDTH;
+ sel->r.height = S5K3L6_ACTIVE_HEIGHT;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int s5k3l6_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad,
+ struct v4l2_mbus_config *config)
+{
+ struct s5k3l6 *s5k3l6 = to_s5k3l6(sd);
+ const struct s5k3l6_mode *mode = s5k3l6->mode;
+
+ if (pad)
+ return -EINVAL;
+
+ *config = (struct v4l2_mbus_config) {
+ .type = V4L2_MBUS_CSI2_DPHY,
+ .link_freq = s5k3l6_link_freq_menu[mode->link_freq_index],
+ };
+ config->bus.mipi_csi2.num_data_lanes = S5K3L6_DATA_LANES;
+
+ return 0;
+}
+
+static int s5k3l6_init_state(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
+{
+ struct s5k3l6 *s5k3l6 = to_s5k3l6(sd);
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_TRY,
+ .pad = 0,
+ .format = {
+ .code = s5k3l6->mode->code,
+ .width = s5k3l6->mode->width,
+ .height = s5k3l6->mode->height,
+ },
+ };
+
+ s5k3l6_set_pad_format(sd, state, &fmt);
+
+ return 0;
+}
+
+static const struct v4l2_subdev_video_ops s5k3l6_video_ops = {
+ .s_stream = v4l2_subdev_s_stream_helper,
+};
+
+static const struct v4l2_subdev_pad_ops s5k3l6_pad_ops = {
+ .set_fmt = s5k3l6_set_pad_format,
+ .get_fmt = v4l2_subdev_get_fmt,
+ .get_selection = s5k3l6_get_selection,
+ .get_mbus_config = s5k3l6_get_mbus_config,
+ .enum_mbus_code = s5k3l6_enum_mbus_code,
+ .enum_frame_size = s5k3l6_enum_frame_size,
+ .enable_streams = s5k3l6_enable_streams,
+ .disable_streams = s5k3l6_disable_streams,
+};
+
+static const struct v4l2_subdev_ops s5k3l6_subdev_ops = {
+ .video = &s5k3l6_video_ops,
+ .pad = &s5k3l6_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops s5k3l6_internal_ops = {
+ .init_state = s5k3l6_init_state,
+};
+
+static const struct media_entity_operations s5k3l6_entity_ops = {
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+static int s5k3l6_identify_sensor(struct s5k3l6 *s5k3l6)
+{
+ u64 val;
+ int ret;
+
+ ret = cci_read(s5k3l6->regmap, S5K3L6_REG_CHIP_ID, &val, NULL);
+ if (ret)
+ return dev_err_probe(s5k3l6->dev, ret,
+ "failed to read chip id\n");
+
+ if (val != S5K3L6_CHIP_ID)
+ return dev_err_probe(s5k3l6->dev, -ENODEV,
+ "chip id mismatch: %x != %llx\n",
+ S5K3L6_CHIP_ID, val);
+
+ ret = cci_read(s5k3l6->regmap, S5K3L6_REG_REVISION, &val, NULL);
+ if (ret)
+ return dev_err_probe(s5k3l6->dev, ret,
+ "failed to read revision\n");
+
+ if (val != S5K3L6_REVISION)
+ return dev_err_probe(s5k3l6->dev, -ENODEV,
+ "revision mismatch: %x != %llx\n",
+ S5K3L6_REVISION, val);
+
+ return 0;
+}
+
+static int s5k3l6_check_hwcfg(struct s5k3l6 *s5k3l6)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(s5k3l6->dev);
+ struct v4l2_fwnode_endpoint bus_cfg = {
+ .bus = {
+ .mipi_csi2 = {
+ .num_data_lanes = S5K3L6_DATA_LANES,
+ },
+ },
+ .bus_type = V4L2_MBUS_CSI2_DPHY,
+ };
+ struct fwnode_handle *ep;
+ unsigned long freq_bitmap;
+ unsigned long expected_bitmap;
+ int ret;
+
+ if (!fwnode)
+ return -ENODEV;
+
+ ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
+ if (!ep)
+ return -EINVAL;
+
+ ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
+ fwnode_handle_put(ep);
+ if (ret)
+ return ret;
+
+ if (bus_cfg.bus.mipi_csi2.num_data_lanes != S5K3L6_DATA_LANES) {
+ dev_err(s5k3l6->dev, "only %u data lanes are supported\n",
+ S5K3L6_DATA_LANES);
+ ret = -EINVAL;
+ goto endpoint_free;
+ }
+
+ ret = v4l2_link_freq_to_bitmap(s5k3l6->dev, bus_cfg.link_frequencies,
+ bus_cfg.nr_of_link_frequencies,
+ s5k3l6_link_freq_menu,
+ ARRAY_SIZE(s5k3l6_link_freq_menu),
+ &freq_bitmap);
+ if (ret)
+ goto endpoint_free;
+
+ expected_bitmap = GENMASK(ARRAY_SIZE(s5k3l6_link_freq_menu) - 1, 0);
+ if (freq_bitmap != expected_bitmap) {
+ dev_err(s5k3l6->dev, "not all link frequencies are listed\n");
+ ret = -EINVAL;
+ }
+
+endpoint_free:
+ v4l2_fwnode_endpoint_free(&bus_cfg);
+
+ return ret;
+}
+
+static int s5k3l6_power_on(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct s5k3l6 *s5k3l6 = to_s5k3l6(sd);
+ int ret;
+
+ ret = regulator_bulk_enable(S5K3L6_NUM_SUPPLIES, s5k3l6->supplies);
+ if (ret)
+ return ret;
+
+ usleep_range(10, 20);
+
+ ret = clk_prepare_enable(s5k3l6->mclk);
+ if (ret)
+ goto disable_regulators;
+
+ gpiod_set_value_cansleep(s5k3l6->reset_gpio, 0);
+ usleep_range(USEC_PER_MSEC, 1200);
+
+ gpiod_set_value_cansleep(s5k3l6->reset_gpio, 1);
+ usleep_range(400, 800);
+ gpiod_set_value_cansleep(s5k3l6->reset_gpio, 0);
+
+ usleep_range(10 * USEC_PER_MSEC, 11 * USEC_PER_MSEC);
+
+ return 0;
+
+disable_regulators:
+ regulator_bulk_disable(S5K3L6_NUM_SUPPLIES, s5k3l6->supplies);
+
+ return ret;
+}
+
+static int s5k3l6_power_off(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct s5k3l6 *s5k3l6 = to_s5k3l6(sd);
+
+ gpiod_set_value_cansleep(s5k3l6->reset_gpio, 1);
+ clk_disable_unprepare(s5k3l6->mclk);
+
+ return regulator_bulk_disable(S5K3L6_NUM_SUPPLIES,
+ s5k3l6->supplies);
+}
+
+static int s5k3l6_probe(struct i2c_client *client)
+{
+ struct s5k3l6 *s5k3l6;
+ unsigned long freq;
+ unsigned int i;
+ int ret;
+
+ s5k3l6 = devm_kzalloc(&client->dev, sizeof(*s5k3l6), GFP_KERNEL);
+ if (!s5k3l6)
+ return -ENOMEM;
+
+ s5k3l6->dev = &client->dev;
+ v4l2_i2c_subdev_init(&s5k3l6->sd, client, &s5k3l6_subdev_ops);
+
+ s5k3l6->regmap = devm_cci_regmap_init_i2c(client, 16);
+ if (IS_ERR(s5k3l6->regmap))
+ return dev_err_probe(s5k3l6->dev, PTR_ERR(s5k3l6->regmap),
+ "failed to init CCI\n");
+
+ s5k3l6->mclk = devm_v4l2_sensor_clk_get(s5k3l6->dev, NULL);
+ if (IS_ERR(s5k3l6->mclk))
+ return dev_err_probe(s5k3l6->dev, PTR_ERR(s5k3l6->mclk),
+ "failed to get clock\n");
+
+ freq = clk_get_rate(s5k3l6->mclk);
+ if (freq != S5K3L6_MCLK_FREQ)
+ return dev_err_probe(s5k3l6->dev, -EINVAL,
+ "clock frequency %lu is not supported\n",
+ freq);
+
+ ret = s5k3l6_check_hwcfg(s5k3l6);
+ if (ret)
+ return dev_err_probe(s5k3l6->dev, ret,
+ "failed to check HW configuration\n");
+
+ s5k3l6->reset_gpio =
+ devm_gpiod_get_optional(s5k3l6->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(s5k3l6->reset_gpio))
+ return dev_err_probe(s5k3l6->dev,
+ PTR_ERR(s5k3l6->reset_gpio),
+ "failed to get reset GPIO\n");
+
+ for (i = 0; i < S5K3L6_NUM_SUPPLIES; i++)
+ s5k3l6->supplies[i].supply = s5k3l6_supply_names[i];
+
+ ret = devm_regulator_bulk_get(s5k3l6->dev, S5K3L6_NUM_SUPPLIES,
+ s5k3l6->supplies);
+ if (ret)
+ return dev_err_probe(s5k3l6->dev, ret,
+ "failed to get supplies\n");
+
+ ret = s5k3l6_power_on(s5k3l6->dev);
+ if (ret)
+ return ret;
+
+ ret = s5k3l6_identify_sensor(s5k3l6);
+ if (ret)
+ goto power_off;
+
+ s5k3l6->mode = &s5k3l6_supported_modes[0];
+ ret = s5k3l6_init_controls(s5k3l6);
+ if (ret)
+ goto power_off;
+
+ s5k3l6->sd.state_lock = s5k3l6->ctrl_handler.lock;
+ s5k3l6->sd.internal_ops = &s5k3l6_internal_ops;
+ s5k3l6->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ s5k3l6->sd.entity.ops = &s5k3l6_entity_ops;
+ s5k3l6->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+ s5k3l6->pad.flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&s5k3l6->sd.entity, 1, &s5k3l6->pad);
+ if (ret)
+ goto free_ctrls;
+
+ ret = v4l2_subdev_init_finalize(&s5k3l6->sd);
+ if (ret)
+ goto cleanup_entity;
+
+ pm_runtime_set_active(s5k3l6->dev);
+ pm_runtime_enable(s5k3l6->dev);
+
+ ret = v4l2_async_register_subdev_sensor(&s5k3l6->sd);
+ if (ret)
+ goto cleanup_subdev;
+
+ pm_runtime_set_autosuspend_delay(s5k3l6->dev, 1000);
+ pm_runtime_use_autosuspend(s5k3l6->dev);
+ pm_runtime_idle(s5k3l6->dev);
+
+ return 0;
+
+cleanup_subdev:
+ v4l2_subdev_cleanup(&s5k3l6->sd);
+ pm_runtime_disable(s5k3l6->dev);
+ pm_runtime_set_suspended(s5k3l6->dev);
+
+cleanup_entity:
+ media_entity_cleanup(&s5k3l6->sd.entity);
+
+free_ctrls:
+ v4l2_ctrl_handler_free(s5k3l6->sd.ctrl_handler);
+
+power_off:
+ s5k3l6_power_off(s5k3l6->dev);
+
+ return ret;
+}
+
+static void s5k3l6_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
+ struct s5k3l6 *s5k3l6 = to_s5k3l6(sd);
+
+ v4l2_async_unregister_subdev(sd);
+ v4l2_subdev_cleanup(sd);
+ media_entity_cleanup(&sd->entity);
+ v4l2_ctrl_handler_free(sd->ctrl_handler);
+ pm_runtime_disable(s5k3l6->dev);
+
+ if (!pm_runtime_status_suspended(s5k3l6->dev)) {
+ s5k3l6_power_off(s5k3l6->dev);
+ pm_runtime_set_suspended(s5k3l6->dev);
+ }
+}
+
+static const struct dev_pm_ops s5k3l6_pm_ops = {
+ SET_RUNTIME_PM_OPS(s5k3l6_power_off, s5k3l6_power_on, NULL)
+};
+
+static const struct of_device_id s5k3l6_of_match[] = {
+ { .compatible = "samsung,s5k3l6" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, s5k3l6_of_match);
+
+static struct i2c_driver s5k3l6_i2c_driver = {
+ .driver = {
+ .name = "s5k3l6",
+ .pm = &s5k3l6_pm_ops,
+ .of_match_table = s5k3l6_of_match,
+ },
+ .probe = s5k3l6_probe,
+ .remove = s5k3l6_remove,
+};
+
+module_i2c_driver(s5k3l6_i2c_driver);
+
+MODULE_DESCRIPTION("Samsung S5K3L6 image sensor driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH RFC 2/4] dt-bindings: media: i2c: Add Samsung S5K3L6 image sensor
From: Vincent Cloutier @ 2026-06-26 0:06 UTC (permalink / raw)
To: linux-media, devicetree, linux-arm-kernel
Cc: linux-kernel, linux-imx, kernel, Vincent Cloutier,
Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
In-Reply-To: <20260626000715.1111803-1-vincent.cloutier@icloud.com>
From: Vincent Cloutier <vincent@cloutier.co>
Add a devicetree binding for the Samsung S5K3L6 13MP raw Bayer image
sensor.
This starts the upstreaming work for the Librem 5 rear camera path. The
binding describes the validated two-lane MIPI CSI-2 configuration.
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
Assisted-by: OpenCode:gpt-5.5
---
.../bindings/media/i2c/samsung,s5k3l6.yaml | 117 +++++++++++++++++++++
1 file changed, 117 insertions(+)
diff --git a/Documentation/devicetree/bindings/media/i2c/samsung,s5k3l6.yaml b/Documentation/devicetree/bindings/media/i2c/samsung,s5k3l6.yaml
new file mode 100644
index 000000000000..96150764b341
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/samsung,s5k3l6.yaml
@@ -0,0 +1,117 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/samsung,s5k3l6.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Samsung S5K3L6 13MP MIPI CSI-2 image sensor
+
+maintainers:
+ - Vincent Cloutier <vincent@cloutier.co>
+ - Purism Kernel Team <kernel@puri.sm>
+
+description: |-
+ The Samsung S5K3L6 is a raw Bayer image sensor with a MIPI CSI-2 image
+ data interface and an I2C-compatible control bus.
+
+allOf:
+ - $ref: /schemas/media/video-interface-devices.yaml#
+
+properties:
+ compatible:
+ const: samsung,s5k3l6
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ description: Reference to the sensor input clock.
+ maxItems: 1
+
+ reset-gpios:
+ description: Active-low reset GPIO.
+ maxItems: 1
+
+ vdda-supply:
+ description: Analog power supply.
+
+ vddd-supply:
+ description: Digital core power supply.
+
+ vddio-supply:
+ description: Digital I/O power supply.
+
+ port:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ data-lanes:
+ items:
+ - const: 1
+ - const: 2
+
+ link-frequencies:
+ minItems: 3
+ maxItems: 3
+ items:
+ - const: 537500000
+ - const: 600000000
+ - const: 625000000
+
+ required:
+ - data-lanes
+ - link-frequencies
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - vdda-supply
+ - vddd-supply
+ - vddio-supply
+ - port
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/imx8mq-clock.h>
+ #include <dt-bindings/gpio/gpio.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ camera@2d {
+ compatible = "samsung,s5k3l6";
+ reg = <0x2d>;
+
+ clocks = <&clk IMX8MQ_CLK_CLKO2>;
+ assigned-clocks = <&clk IMX8MQ_CLK_CLKO2>;
+ assigned-clock-rates = <25000000>;
+
+ reset-gpios = <&gpio1 26 GPIO_ACTIVE_LOW>;
+ vdda-supply = <®_camera_vdda>;
+ vddd-supply = <®_camera_vddd>;
+ vddio-supply = <®_camera_vddio>;
+
+ orientation = <1>;
+ rotation = <270>;
+
+ port {
+ camera_out: endpoint {
+ data-lanes = <1 2>;
+ link-frequencies = /bits/ 64
+ <537500000 600000000 625000000>;
+ remote-endpoint = <&mipi_csi2_in>;
+ };
+ };
+ };
+ };
+...
--
2.53.0
^ permalink raw reply related
* [PATCH RFC 1/4] media: imx8mq-mipi-csi2: Make reset release SoC-specific
From: Vincent Cloutier @ 2026-06-26 0:06 UTC (permalink / raw)
To: linux-media, devicetree, linux-arm-kernel
Cc: linux-kernel, linux-imx, kernel, Vincent Cloutier,
Laurent Pinchart, Frank Li, Martin Kepplinger-Novakovic,
Rui Miguel Silva, Mauro Carvalho Chehab, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Philipp Zabel, imx
In-Reply-To: <20260626000715.1111803-1-vincent.cloutier@icloud.com>
From: Vincent Cloutier <vincent@cloutier.co>
The CSI-2 software reset helper currently asserts the reset control and
then releases it again unconditionally.
That release step is required by the i.MX8QXP path, but it changes the
reset sequence used by i.MX8MQ. On Librem 5r4, which is i.MX8MQ-based,
the unconditional release step prevents the camera pipeline from producing
frames after reset; captures time out waiting for EOF from the CSI bridge.
This series enables the Librem 5 rear camera on the second i.MX8MQ CSI-2
receiver. Keep the i.MX8MQ path on the known-working assert-only software
reset sequence while preserving the explicit release step for i.MX8QXP.
Make reset release opt-in through platform data.
Tested on Librem 5r4 with the existing HI846 front camera and the S5K3L6
rear camera added by this series.
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
Assisted-by: OpenCode:gpt-5.5
---
drivers/media/platform/nxp/imx8mq-mipi-csi2.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c
index 950793297496..xxxxxxxxxxxx 100644
--- a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c
+++ b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c
@@ -76,6 +76,7 @@ struct imx8mq_plat_data {
int (*enable)(struct csi_state *state, u32 hs_settle);
void (*disable)(struct csi_state *state);
bool use_reg_csr;
+ bool needs_reset_deassert;
};
/*
@@ -244,6 +245,7 @@ static const struct imx8mq_plat_data imx8qxp_data = {
.enable = imx8qxp_gpr_enable,
.disable = imx8qxp_gpr_disable,
.use_reg_csr = true,
+ .needs_reset_deassert = true,
};
static const struct csi2_pix_format imx8mq_mipi_csi_formats[] = {
@@ -363,8 +365,12 @@ static int imx8mq_mipi_csi_sw_reset(struct csi_state *state)
return ret;
}
- /* Explicitly release reset to make sure reset bits are cleared. */
- return reset_control_deassert(state->rst);
+ /*
+ * Some SoC integrations require an explicit release after reset
+ * assertion. Keep this SoC-specific so i.MX8MQ retains its
+ * known-working assert-only sequence.
+ */
+ if (!state->pdata->needs_reset_deassert)
+ return 0;
+
+ return reset_control_deassert(state->rst);
}
static void imx8mq_mipi_csi_set_params(struct csi_state *state)
--
2.53.0
^ permalink raw reply
* [PATCH RFC 0/4] media: i2c: Add Samsung S5K3L6 and Librem 5 rear camera
From: Vincent Cloutier @ 2026-06-26 0:06 UTC (permalink / raw)
To: linux-media, devicetree, linux-arm-kernel
Cc: linux-kernel, linux-imx, kernel, Vincent Cloutier
From: Vincent Cloutier <vincent@cloutier.co>
This RFC fixes the i.MX8MQ CSI-2 reset path needed by Librem 5 camera
capture, adds initial upstream support for the Samsung S5K3L6 image
sensor, and wires it up as the rear camera on the Purism Librem 5.
This is intentionally a rewrite, not a direct forwarding of the Librem 5
downstream S5K3L6 carry. The downstream history is a long bring-up
series with debugfs register overrides, debug-frame plumbing, commented-out
old subdev code, FIXME/TODO scaffolding, and several API-era migrations.
This RFC collapses the usable production path into a current V4L2 sensor
driver using CCI regmap, runtime PM, fwnode endpoint validation, and the
current subdev stream API.
Many thanks to Martin Kepplinger, Dorota Czaplejewicz, and Sebastian
Krzyszkowiak for the original Librem 5 S5K3L6 driver work and rear-camera
bring-up. Martin authored the initial downstream driver, and Dorota and
Sebastian substantially developed the mode tables, controls, power-up
sequence, and sensor tuning that this rewrite is based on.
This series covers:
- 25 MHz input clock, matching the Librem 5 downstream configuration
- two MIPI CSI-2 data lanes
- RAW8 and RAW10 SGRBG modes at 1052x780, 2104x1560, and 4208x3120
- exposure, analogue gain, digital gain, blanking, pixel-rate,
link-frequency, test-pattern, orientation, and rotation controls
- Librem 5 DTS integration for the rear sensor and second CSI-2 path
The rewritten driver and DTS path have now been tested on Librem 5r4
hardware in a v7.1.1 carry build, with the i.MX8MQ CSI-2 reset fix from this
series applied.
Patch 1 keeps the i.MX8MQ CSI-2 software reset sequence compatible with
the Librem 5 camera pipeline by making the post-assert reset release
SoC-specific. This is included as an RFC prerequisite for the Librem 5
rear-camera enablement; if preferred, it can be split out and handled as
a separate media/platform fix.
Vincent Cloutier (4):
media: imx8mq-mipi-csi2: Keep i.MX8MQ reset assert-only
dt-bindings: media: i2c: Add Samsung S5K3L6 image sensor
media: i2c: Add Samsung S5K3L6 image sensor driver
arm64: dts: imx8mq-librem5: Add rear camera
.../bindings/media/i2c/samsung,s5k3l6.yaml | 117 ++
arch/arm64/boot/dts/freescale/imx8mq-librem5.dtsi | 51 +
drivers/media/i2c/Kconfig | 10 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/s5k3l6.c | 1055 +++++++++++++++++
drivers/media/platform/nxp/imx8mq-mipi-csi2.c | 9 +-
6 files changed, 1241 insertions(+), 2 deletions(-)
--
2.53.0
^ permalink raw reply
* Re: [PATCH v6 2/2] Input: isa1200 - new driver for Imagis ISA1200
From: Dmitry Torokhov @ 2026-06-25 23:45 UTC (permalink / raw)
To: Svyatoslav Ryhel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij,
linux-input, devicetree, linux-kernel
In-Reply-To: <20260617070528.35006-3-clamor95@gmail.com>
Hi Svyatoslav,
On Wed, Jun 17, 2026 at 10:05:27AM +0300, Svyatoslav Ryhel wrote:
> From: Linus Walleij <linusw@kernel.org>
>
> The ISA1200 is a haptic feedback unit from Imagis Technology using two
> motors for haptic feedback in mobile phones. Used in many mobile devices
> c. 2012 including Samsung Galxy S Advance GT-I9070 (Janice), Samsung Beam
> GT-I8350 (Gavini), LG Optimus 4X P880 and LG Optimus Vu P895.
>
> The exact datasheet for the ISA1200 is not available; all data was modeled
> based on available downstream kernel sources for various devices and
> fragments of information scattered across the internet.
>
> Tested-by: Linus Walleij <linusw@kernel.org> # GT-I9070 Janice
> Signed-off-by: Linus Walleij <linusw@kernel.org>
> Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
> Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Sashiko convinced me that using mutex_trylock() in the playback work
handler will result in dropping requests and that we are safe not taking
the lock there at all. Can you please try the following modification?
diff --git a/drivers/input/misc/isa1200.c b/drivers/input/misc/isa1200.c
index c61adc4b605c..fb7f68fa0a2b 100644
--- a/drivers/input/misc/isa1200.c
+++ b/drivers/input/misc/isa1200.c
@@ -253,15 +253,12 @@ static void isa1200_stop(struct isa1200 *isa)
static void isa1200_play_work(struct work_struct *work)
{
struct isa1200 *isa = container_of(work, struct isa1200, play_work);
- struct input_dev *input = isa->input;
-
- scoped_guard(mutex_try, &input->mutex) {
- if (!isa->suspended) {
- if (isa->level)
- isa1200_start(isa);
- else
- isa1200_stop(isa);
- }
+
+ if (!READ_ONCE(isa->suspended)) {
+ if (isa->level)
+ isa1200_start(isa);
+ else
+ isa1200_stop(isa);
}
}
If this works no need to resend, I'll fold on my side.
Thanks.
--
Dmitry
^ permalink raw reply related
* Re: [PATCH v14 4/5] gpio: rpmsg: add generic rpmsg GPIO driver
From: Julian Braha @ 2026-06-25 22:17 UTC (permalink / raw)
To: Shenwei Wang, Linus Walleij, Bartosz Golaszewski, Jonathan Corbet,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Mathieu Poirier, Frank Li, Sascha Hauer
Cc: Shuah Khan, linux-gpio, linux-doc, linux-kernel,
Pengutronix Kernel Team, Fabio Estevam, Shenwei Wang, Peng Fan,
devicetree, linux-remoteproc, imx, linux-arm-kernel, linux-imx,
Arnaud POULIQUEN, b-padhi, Andrew Lunn, Bartosz Golaszewski
In-Reply-To: <20260625155432.815185-5-shenwei.wang@oss.nxp.com>
Hi Shenwei,
On 6/25/26 16:54, Shenwei Wang wrote:
> +config GPIO_RPMSG
> + tristate "Generic RPMSG GPIO support"
> + depends on OF && REMOTEPROC
> + select GPIOLIB_IRQCHIP
> + default REMOTEPROC
> + help
> + Say yes here to support the generic GPIO functions over the RPMSG
> + bus. Currently supported devices: i.MX7ULP, i.MX8ULP, i.MX8x, and
> + i.MX9x.
> +
> + If unsure, say N.
You've got GPIO_RPMSG defaulting to REMOTEPROC, which is also a
dependency, so it's effectively the same as using 'default y'.
But then you've got "If unsure, say N" in the help text. Usually this is
used when the default is N.
- Julian Braha
^ permalink raw reply
* Re: [PATCH v2 5/8] dt-bindings: iio: adc: add ti,ads122c14
From: sashiko-bot @ 2026-06-25 22:12 UTC (permalink / raw)
Cc: robh, conor+dt, devicetree
In-Reply-To: <20260625-iio-adc-ti-ads122c14-v2-5-ceb9b0b561cb@baylibre.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] The pattern property regex for channel nodes `^channel@[0-7a-f]$` omits digits 8 and 9, rejecting valid node addresses.
- [High] The `burn-out-current-nanoamp` property applies an `enum` constraint directly to an array, which will fail DT validation.
- [Medium] The commit message explicitly claims to reuse the custom 'ti,vref-source' property, but the code actually implements the standard 'reference-sources' property instead.
- [Medium] The JSON schema reference `$ref: adc.yaml` is missing the `#` fragment or the absolute path.
- [Medium] The `reference-sources` property lacks a `maxItems` constraint and leaves subsequent elements unvalidated.
- [Low] Strict index ordering in `interrupts` clashes with unordered `interrupt-names`, causing invalid semantics if only one interrupt is used.
--
commit de052c7080b8c89f1a3c4b730b190d119eacb414
Author: David Lechner (TI) <dlechner@baylibre.com>
dt-bindings: iio: adc: add ti,ads122c14
Add new bindings for ti,ads122c14 and similar devices primarily intended for
use with temperature sensors. These bindings introduce channel-specific
properties to handle per-measurement reference voltage and current output
requirements.
> ti,vref-source is reused from ti,tlv320adcx140.yaml (otherwise might
> have preferred an enum of strings).
[Severity: Medium]
The commit message explicitly states it reuses the 'ti,vref-source' property,
but the schema actually implements the standard 'reference-sources' property
instead. Should the commit message be updated to match the implementation?
> diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
> new file mode 100644
> index 0000000000000..59ee4d652a087
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
[ ... ]
> + interrupts:
> + minItems: 1
> + items:
> + - description: FAULT interrupt (GPIO2 pin)
> + - description: DRDY interrupt (GPIO3 pin)
> +
> + interrupt-names:
> + minItems: 1
> + maxItems: 2
> + items:
> + enum: [fault, drdy]
[Severity: Low]
If a device tree specifies only the DRDY interrupt at index 0, does the strict
ordering in the interrupts list (which expects FAULT at index 0) clash with
the unordered interrupt-names definition?
[ ... ]
> +patternProperties:
> + ^channel@[0-7a-f]$:
> + $ref: adc.yaml
[Severity: Medium]
Does the JSON schema reference here need a '#' fragment or an absolute path
to ensure it resolves properly across all tooling versions during validation?
[Severity: High]
Does the regex pattern ^channel@[0-7a-f]$ omit digits 8 and 9? The reg
property below has a maximum of 15, so a device tree legitimately using
channel@8 or channel@9 would fail schema validation.
> +
> + unevaluatedProperties: false
> +
> + properties:
> + reg:
> + items:
> + - maximum: 15 # arbitrary limit, channel@ can be any combination of AIN0-AIN7
[ ... ]
> + burn-out-current-nanoamp:
> + enum: [200, 1000, 10000]
[Severity: High]
Because device tree properties are parsed as arrays, will applying an enum
constraint directly to the property without using the 'items' keyword cause
dt_binding_check to always fail?
> +
> + reference-sources:
> + items:
> + - enum: [internal-2.5v, internal-1.25v, external, avdd]
> + default: internal-2.5v
[Severity: Medium]
Is a maxItems constraint needed for this array? Without it, JSON Schema only
applies the list validation to the first element, meaning additional items
in the array might silently pass without being checked.
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260625-iio-adc-ti-ads122c14-v2-0-ceb9b0b561cb@baylibre.com?part=5
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox