9044 lines
263 KiB
Diff
9044 lines
263 KiB
Diff
From 34da0da73b83e12132610e47ee7638ad5e429ba4 Mon Sep 17 00:00:00 2001
|
|
From: Christophe Priouzeau <christophe.priouzeau@foss.st.com>
|
|
Date: Tue, 31 May 2022 11:59:07 +0200
|
|
Subject: [PATCH 10/22] ARM-5.15.24-stm32mp1-r1-MISC-MEDIA-SOC-THERMAL
|
|
|
|
Signed-off-by: Christophe Priouzeau <christophe.priouzeau@foss.st.com>
|
|
---
|
|
.../bindings/media/i2c/galaxycore,gc2145.yaml | 115 ++
|
|
.../bindings/soc/stm32/st,stm32mp1-hslv.yaml | 44 +
|
|
.../bindings/soc/stm32/stm32_hdp.txt | 39 +
|
|
MAINTAINERS | 8 +
|
|
drivers/char/hw_random/stm32-rng.c | 182 +-
|
|
drivers/firmware/Kconfig | 2 +-
|
|
drivers/input/touchscreen/edt-ft5x06.c | 20 +
|
|
drivers/input/touchscreen/goodix.c | 39 +-
|
|
drivers/media/i2c/Kconfig | 12 +
|
|
drivers/media/i2c/Makefile | 1 +
|
|
drivers/media/i2c/gc2145.c | 1754 +++++++++++++++++
|
|
drivers/media/i2c/ov5640.c | 459 +++--
|
|
drivers/media/i2c/st-mipid02.c | 10 +-
|
|
drivers/media/platform/Kconfig | 13 +
|
|
drivers/media/platform/stm32/Makefile | 1 +
|
|
drivers/media/platform/stm32/stm32-dcmi.c | 129 +-
|
|
.../platform/stm32/stm32-dcmipp/Makefile | 5 +
|
|
.../stm32/stm32-dcmipp/dcmipp-bytecap.c | 1109 +++++++++++
|
|
.../stm32/stm32-dcmipp/dcmipp-byteproc.c | 737 +++++++
|
|
.../stm32/stm32-dcmipp/dcmipp-common.c | 116 ++
|
|
.../stm32/stm32-dcmipp/dcmipp-common.h | 235 +++
|
|
.../platform/stm32/stm32-dcmipp/dcmipp-core.c | 688 +++++++
|
|
.../stm32/stm32-dcmipp/dcmipp-parallel.c | 494 +++++
|
|
drivers/media/v4l2-core/v4l2-fwnode.c | 3 +
|
|
drivers/mtd/mtdcore.c | 2 +
|
|
drivers/mtd/nand/raw/stm32_fmc2_nand.c | 40 +-
|
|
drivers/nvmem/core.c | 2 +-
|
|
drivers/nvmem/stm32-romem.c | 498 ++++-
|
|
drivers/of/platform.c | 4 +
|
|
drivers/pwm/pwm-stm32-lp.c | 4 +-
|
|
drivers/pwm/pwm-stm32.c | 4 +
|
|
drivers/soc/Kconfig | 1 +
|
|
drivers/soc/Makefile | 1 +
|
|
drivers/soc/st/Kconfig | 25 +
|
|
drivers/soc/st/Makefile | 3 +
|
|
drivers/soc/st/stm32-hslv.c | 157 ++
|
|
drivers/soc/st/stm32_hdp.c | 242 +++
|
|
drivers/soc/st/stm32_pm_domain.c | 212 ++
|
|
drivers/thermal/st/stm_thermal.c | 30 +-
|
|
include/dt-bindings/soc/stm32-hdp.h | 108 +
|
|
include/dt-bindings/soc/stm32mp13-hdp.h | 133 ++
|
|
include/linux/nvmem-provider.h | 4 +-
|
|
include/media/mipi-csi2.h | 45 +
|
|
include/media/v4l2-fwnode.h | 2 +
|
|
44 files changed, 7483 insertions(+), 249 deletions(-)
|
|
create mode 100644 Documentation/devicetree/bindings/media/i2c/galaxycore,gc2145.yaml
|
|
create mode 100644 Documentation/devicetree/bindings/soc/stm32/st,stm32mp1-hslv.yaml
|
|
create mode 100644 Documentation/devicetree/bindings/soc/stm32/stm32_hdp.txt
|
|
create mode 100644 drivers/media/i2c/gc2145.c
|
|
create mode 100644 drivers/media/platform/stm32/stm32-dcmipp/Makefile
|
|
create mode 100644 drivers/media/platform/stm32/stm32-dcmipp/dcmipp-bytecap.c
|
|
create mode 100644 drivers/media/platform/stm32/stm32-dcmipp/dcmipp-byteproc.c
|
|
create mode 100644 drivers/media/platform/stm32/stm32-dcmipp/dcmipp-common.c
|
|
create mode 100644 drivers/media/platform/stm32/stm32-dcmipp/dcmipp-common.h
|
|
create mode 100644 drivers/media/platform/stm32/stm32-dcmipp/dcmipp-core.c
|
|
create mode 100644 drivers/media/platform/stm32/stm32-dcmipp/dcmipp-parallel.c
|
|
create mode 100644 drivers/soc/st/Kconfig
|
|
create mode 100644 drivers/soc/st/Makefile
|
|
create mode 100644 drivers/soc/st/stm32-hslv.c
|
|
create mode 100644 drivers/soc/st/stm32_hdp.c
|
|
create mode 100644 drivers/soc/st/stm32_pm_domain.c
|
|
create mode 100644 include/dt-bindings/soc/stm32-hdp.h
|
|
create mode 100644 include/dt-bindings/soc/stm32mp13-hdp.h
|
|
create mode 100644 include/media/mipi-csi2.h
|
|
|
|
diff --git a/Documentation/devicetree/bindings/media/i2c/galaxycore,gc2145.yaml b/Documentation/devicetree/bindings/media/i2c/galaxycore,gc2145.yaml
|
|
new file mode 100644
|
|
index 000000000..af07250ce
|
|
--- /dev/null
|
|
+++ b/Documentation/devicetree/bindings/media/i2c/galaxycore,gc2145.yaml
|
|
@@ -0,0 +1,115 @@
|
|
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
|
+%YAML 1.2
|
|
+---
|
|
+$id: http://devicetree.org/schemas/media/i2c/galaxycore,gc2145.yaml#
|
|
+$schema: http://devicetree.org/meta-schemas/core.yaml#
|
|
+
|
|
+title: Galaxy Core 1/5'' UXGA CMOS Image Sensor
|
|
+
|
|
+maintainers:
|
|
+ - Alain Volmat <alain.volmat@foss.st.com>
|
|
+
|
|
+description: |
|
|
+ The Galaxy Core GC2145 is a high quality 2 Mega CMOS image sensor, for mobile phone camera
|
|
+ applications and digital camera products. GC2145 incorporates a 1616V x 1232H active pixel
|
|
+ array, on-chip 10-bit ADC, and image signal processor. It is programmable through an I2C
|
|
+ interface. Image data is sent either through a parallel interface or through MIPI CSI-2.
|
|
+
|
|
+allOf:
|
|
+ - $ref: ../video-interface-devices.yaml#
|
|
+
|
|
+properties:
|
|
+ compatible:
|
|
+ const: galaxycore,gc2145
|
|
+
|
|
+ reg:
|
|
+ enum:
|
|
+ - 0x3c
|
|
+
|
|
+ clocks:
|
|
+ description: Reference to the xclk clock.
|
|
+ maxItems: 1
|
|
+
|
|
+ powerdown-gpios:
|
|
+ description: GPIO descriptor for the powerdown pin.
|
|
+ maxItems: 1
|
|
+
|
|
+ reset-gpios:
|
|
+ description: GPIO descriptor for the reset pin.
|
|
+ maxItems: 1
|
|
+
|
|
+ IOVDD-supply:
|
|
+ description: Power Supply for I/O circuits (1.7 - 3V).
|
|
+
|
|
+ AVDD-supply:
|
|
+ description: Power for analog circuit/sensor array (2.7 - 3V).
|
|
+
|
|
+ DVDD-supply:
|
|
+ description: Power for digital core (1.7 - 1.9V).
|
|
+
|
|
+ port:
|
|
+ $ref: /schemas/graph.yaml#/$defs/port-base
|
|
+ description: |
|
|
+ Video output port.
|
|
+
|
|
+ properties:
|
|
+ endpoint:
|
|
+ $ref: /schemas/media/video-interfaces.yaml#
|
|
+ unevaluatedProperties: false
|
|
+
|
|
+ properties:
|
|
+ data-lanes:
|
|
+ anyOf:
|
|
+ - items:
|
|
+ - const: 1
|
|
+ - const: 2
|
|
+ - items:
|
|
+ - const: 1
|
|
+ - const: 2
|
|
+
|
|
+ required:
|
|
+ - data-lanes
|
|
+
|
|
+ additionalProperties: false
|
|
+
|
|
+required:
|
|
+ - compatible
|
|
+ - reg
|
|
+ - clocks
|
|
+ - powerdown-gpios
|
|
+ - reset-gpios
|
|
+ - IOVDD-supply
|
|
+ - AVDD-supply
|
|
+ - DVDD-supply
|
|
+ - port
|
|
+
|
|
+additionalProperties: false
|
|
+
|
|
+examples:
|
|
+ - |
|
|
+ #include <dt-bindings/gpio/gpio.h>
|
|
+
|
|
+ i2c5 {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ gc2145@3c {
|
|
+ compatible = "galaxycore,gc2145";
|
|
+ reg = <0x3c>;
|
|
+ clocks = <&clk_ext_camera>;
|
|
+ IOVDD-supply = <&scmi_v3v3_sw>;
|
|
+ AVDD-supply = <&scmi_v3v3_sw>;
|
|
+ DVDD-supply = <&scmi_v3v3_sw>;
|
|
+ powerdown-gpios = <&mcp23017 3 (GPIO_ACTIVE_LOW | GPIO_PUSH_PULL)>;
|
|
+ reset-gpios = <&mcp23017 4 (GPIO_ACTIVE_LOW | GPIO_PUSH_PULL)>;
|
|
+
|
|
+ port {
|
|
+ gc2145_ep: endpoint {
|
|
+ remote-endpoint = <&mipid02_0>;
|
|
+ data-lanes = <1 2>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+...
|
|
diff --git a/Documentation/devicetree/bindings/soc/stm32/st,stm32mp1-hslv.yaml b/Documentation/devicetree/bindings/soc/stm32/st,stm32mp1-hslv.yaml
|
|
new file mode 100644
|
|
index 000000000..a228a5b26
|
|
--- /dev/null
|
|
+++ b/Documentation/devicetree/bindings/soc/stm32/st,stm32mp1-hslv.yaml
|
|
@@ -0,0 +1,44 @@
|
|
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
|
+%YAML 1.2
|
|
+---
|
|
+$id: http://devicetree.org/schemas/regulator/st,stm32mp1-hslv.yaml#
|
|
+$schema: http://devicetree.org/meta-schemas/core.yaml#
|
|
+
|
|
+title: STM32MP1 HSLV IO Speed config assistant
|
|
+
|
|
+maintainers:
|
|
+ - Pascal Paillet <p.paillet@st.com>
|
|
+
|
|
+description: |
|
|
+ Some of the STMicroelectronics's STM32 MP13 IOs can be set to high speed mode
|
|
+ if their supply is lower than a 2.7V. The goal of this driver is to
|
|
+ automatically set the IOs to high speed mode depending on their supply's
|
|
+ regulator voltage value.
|
|
+
|
|
+properties:
|
|
+ compatible:
|
|
+ const: st,stm32mp13,hslv
|
|
+
|
|
+ hslv-supply:
|
|
+ description: Input supply phandle(s) for hslv input
|
|
+
|
|
+ st,syscon:
|
|
+ description: hslv sysconf register offset and mask
|
|
+
|
|
+required:
|
|
+ - compatible
|
|
+ - hslv-supply
|
|
+ - st,syscon
|
|
+
|
|
+additionalProperties: false
|
|
+
|
|
+examples:
|
|
+ - |
|
|
+ hslv@1 {
|
|
+ compatible = "st,stm32mp13,hslv";
|
|
+
|
|
+ /* sdmmc1 hslv = 0x50 + 4 * 4 = 0x60 */
|
|
+ st,syscon = <&syscfg 0x60 0x1018>;
|
|
+ hslv-supply = <&sdmmc1_regu>;
|
|
+ };
|
|
+...
|
|
diff --git a/Documentation/devicetree/bindings/soc/stm32/stm32_hdp.txt b/Documentation/devicetree/bindings/soc/stm32/stm32_hdp.txt
|
|
new file mode 100644
|
|
index 000000000..e2bd82f49
|
|
--- /dev/null
|
|
+++ b/Documentation/devicetree/bindings/soc/stm32/stm32_hdp.txt
|
|
@@ -0,0 +1,39 @@
|
|
+STM32 - STM32MP1- HDP Pin configuration for STM32MP1
|
|
+=======================================================
|
|
+
|
|
+The Hardware Debug Port (HDP) allows the observation of internal signals. By using multiplexers,
|
|
+up to 16 signals for each of 8-bit output can be observed.
|
|
+
|
|
+Required Properties:
|
|
+
|
|
+ - compatible: Must be "st,stm32mp1-hdp"
|
|
+ - muxing-hdp: Indicates for each HDP pins selected which HDP output among the 16 available signals you want
|
|
+
|
|
+For each HDP pins you can select one of 16 signals which will be described in file : include/dt-bindings/soc/stm32-hdp.h
|
|
+
|
|
+Example
|
|
+-------
|
|
+
|
|
+In common dtsi file:
|
|
+
|
|
+hdp: hdp@5002a000 {
|
|
+ compatible = "st,stm32mp1-hdp";
|
|
+ reg = <0x5002a000 0x400>;
|
|
+ clocks = <&rcc HDP>;
|
|
+ clock-names = "hdp";
|
|
+};
|
|
+
|
|
+In board-specific file:
|
|
+
|
|
+In this example I've selected HDP0, HDP6 and HDP7, and for HDP0 the output signal is HDP0_GPOVAL_0,
|
|
+for HDP6 is HDP6_GPOVAL_6, and for HDP7 is HDP7_GPOVAL_7.
|
|
+
|
|
+&hdp {
|
|
+ pinctrl-names = "default", "sleep";
|
|
+ pinctrl-0 = <&hdp0_pins_a &hdp6_pins_a &hdp7_pins_a>;
|
|
+ pinctrl-1 = <&hdp0_pins_sleep_a &hdp6_pins_sleep_a &hdp7_pins_sleep_a>;
|
|
+
|
|
+ muxing-hdp = <(STM32_HDP(0, HDP0_GPOVAL_0) |
|
|
+ STM32_HDP(6, HDP6_GPOVAL_6) |
|
|
+ STM32_HDP(7, HDP7_GPOVAL_7))>;
|
|
+};
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
index 3b79fd441..d95abb753 100644
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -7748,6 +7748,14 @@ F: kernel/futex.c
|
|
F: tools/perf/bench/futex*
|
|
F: tools/testing/selftests/futex/
|
|
|
|
+GALAXYCORE GC2145 SENSOR DRIVER
|
|
+M: Alain Volmat <alain.volmat@foss.st.com>
|
|
+L: linux-media@vger.kernel.org
|
|
+S: Maintained
|
|
+T: git git://linuxtv.org/media_tree.git
|
|
+F: Documentation/devicetree/bindings/media/i2c/galaxycore,gc2145.yaml
|
|
+F: drivers/media/i2c/gc2145.c
|
|
+
|
|
GATEWORKS SYSTEM CONTROLLER (GSC) DRIVER
|
|
M: Tim Harvey <tharvey@gateworks.com>
|
|
M: Robert Jones <rjones@gateworks.com>
|
|
diff --git a/drivers/char/hw_random/stm32-rng.c b/drivers/char/hw_random/stm32-rng.c
|
|
index bc22178f8..f88f9ce2f 100644
|
|
--- a/drivers/char/hw_random/stm32-rng.c
|
|
+++ b/drivers/char/hw_random/stm32-rng.c
|
|
@@ -16,22 +16,33 @@
|
|
#include <linux/reset.h>
|
|
#include <linux/slab.h>
|
|
|
|
-#define RNG_CR 0x00
|
|
-#define RNG_CR_RNGEN BIT(2)
|
|
-#define RNG_CR_CED BIT(5)
|
|
+#define RNG_CR 0x00
|
|
+#define RNG_CR_RNGEN BIT(2)
|
|
+#define RNG_CR_CED BIT(5)
|
|
+#define RNG_CR_CONDRST BIT(30)
|
|
+#define RNG_CR_CONFLOCK BIT(31)
|
|
|
|
-#define RNG_SR 0x04
|
|
-#define RNG_SR_SEIS BIT(6)
|
|
-#define RNG_SR_CEIS BIT(5)
|
|
-#define RNG_SR_DRDY BIT(0)
|
|
+#define RNG_SR 0x04
|
|
+#define RNG_SR_SEIS BIT(6)
|
|
+#define RNG_SR_CEIS BIT(5)
|
|
+#define RNG_SR_DRDY BIT(0)
|
|
|
|
-#define RNG_DR 0x08
|
|
+#define RNG_DR 0x08
|
|
+
|
|
+#define RNG_NIST_CONFIG_A 0x0F00D00
|
|
+#define RNG_NIST_CONFIG_B 0x1801000
|
|
+#define RNG_NIST_CONFIG_MASK GENMASK(25, 8)
|
|
+
|
|
+struct stm32_rng_data {
|
|
+ bool has_cond_reset;
|
|
+};
|
|
|
|
struct stm32_rng_private {
|
|
struct hwrng rng;
|
|
void __iomem *base;
|
|
struct clk *clk;
|
|
struct reset_control *rst;
|
|
+ const struct stm32_rng_data *data;
|
|
bool ced;
|
|
};
|
|
|
|
@@ -84,34 +95,119 @@ static int stm32_rng_init(struct hwrng *rng)
|
|
struct stm32_rng_private *priv =
|
|
container_of(rng, struct stm32_rng_private, rng);
|
|
int err;
|
|
+ u32 reg;
|
|
|
|
err = clk_prepare_enable(priv->clk);
|
|
if (err)
|
|
return err;
|
|
|
|
- if (priv->ced)
|
|
- writel_relaxed(RNG_CR_RNGEN, priv->base + RNG_CR);
|
|
- else
|
|
- writel_relaxed(RNG_CR_RNGEN | RNG_CR_CED,
|
|
- priv->base + RNG_CR);
|
|
+ reg = readl_relaxed(priv->base + RNG_CR);
|
|
+
|
|
+ if (!priv->ced) {
|
|
+ reg |= RNG_CR_CED;
|
|
+ if (priv->data->has_cond_reset) {
|
|
+ reg &= ~RNG_NIST_CONFIG_MASK;
|
|
+ reg |= RNG_CR_CONDRST | RNG_NIST_CONFIG_B;
|
|
+ writel_relaxed(reg, priv->base + RNG_CR);
|
|
+ reg &= ~RNG_CR_CONDRST;
|
|
+ reg |= RNG_CR_CONFLOCK;
|
|
+ writel_relaxed(reg, priv->base + RNG_CR);
|
|
+ err = readl_relaxed_poll_timeout_atomic(priv->base + RNG_CR,
|
|
+ reg, (!(reg & RNG_CR_CONDRST)),
|
|
+ 10, 50000);
|
|
+ if (err) {
|
|
+ dev_err((struct device *)priv->rng.priv,
|
|
+ "%s: timeout %x!\n", __func__, reg);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
|
|
/* clear error indicators */
|
|
writel_relaxed(0, priv->base + RNG_SR);
|
|
|
|
+ reg |= RNG_CR_RNGEN;
|
|
+ writel_relaxed(reg, priv->base + RNG_CR);
|
|
+
|
|
+ err = readl_relaxed_poll_timeout_atomic(priv->base + RNG_SR, reg,
|
|
+ reg & RNG_SR_DRDY,
|
|
+ 10, 100000);
|
|
+ if (err | (reg & ~RNG_SR_DRDY)) {
|
|
+ clk_disable_unprepare(priv->clk);
|
|
+ dev_err((struct device *)priv->rng.priv,
|
|
+ "%s: timeout:%x SR: %x!\n", __func__, err, reg);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
return 0;
|
|
}
|
|
|
|
-static void stm32_rng_cleanup(struct hwrng *rng)
|
|
+static int stm32_rng_remove(struct platform_device *ofdev)
|
|
{
|
|
- struct stm32_rng_private *priv =
|
|
- container_of(rng, struct stm32_rng_private, rng);
|
|
+ pm_runtime_disable(&ofdev->dev);
|
|
|
|
- writel_relaxed(0, priv->base + RNG_CR);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+static int stm32_rng_runtime_suspend(struct device *dev)
|
|
+{
|
|
+ u32 reg;
|
|
+ struct stm32_rng_private *priv = dev_get_drvdata(dev);
|
|
+
|
|
+ reg = readl_relaxed(priv->base + RNG_CR);
|
|
+ reg &= ~RNG_CR_RNGEN;
|
|
+ writel_relaxed(reg, priv->base + RNG_CR);
|
|
clk_disable_unprepare(priv->clk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_rng_runtime_resume(struct device *dev)
|
|
+{
|
|
+ u32 reg;
|
|
+ struct stm32_rng_private *priv = dev_get_drvdata(dev);
|
|
+
|
|
+ clk_prepare_enable(priv->clk);
|
|
+ reg = readl_relaxed(priv->base + RNG_CR);
|
|
+ reg |= RNG_CR_RNGEN;
|
|
+ writel_relaxed(reg, priv->base + RNG_CR);
|
|
+
|
|
+ return 0;
|
|
}
|
|
+#endif
|
|
+
|
|
+static const struct dev_pm_ops stm32_rng_pm_ops = {
|
|
+ SET_RUNTIME_PM_OPS(stm32_rng_runtime_suspend,
|
|
+ stm32_rng_runtime_resume, NULL)
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
|
+ pm_runtime_force_resume)
|
|
+};
|
|
+
|
|
+static const struct stm32_rng_data stm32mp13_rng_data = {
|
|
+ .has_cond_reset = true,
|
|
+};
|
|
+
|
|
+static const struct stm32_rng_data stm32_rng_data = {
|
|
+ .has_cond_reset = false,
|
|
+};
|
|
+
|
|
+static const struct of_device_id stm32_rng_match[] = {
|
|
+ {
|
|
+ .compatible = "st,stm32mp13-rng",
|
|
+ .data = &stm32mp13_rng_data,
|
|
+ },
|
|
+ {
|
|
+ .compatible = "st,stm32-rng",
|
|
+ .data = &stm32_rng_data,
|
|
+ },
|
|
+ {},
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, stm32_rng_match);
|
|
|
|
static int stm32_rng_probe(struct platform_device *ofdev)
|
|
{
|
|
+ const struct of_device_id *match;
|
|
struct device *dev = &ofdev->dev;
|
|
struct device_node *np = ofdev->dev.of_node;
|
|
struct stm32_rng_private *priv;
|
|
@@ -143,13 +239,18 @@ static int stm32_rng_probe(struct platform_device *ofdev)
|
|
|
|
priv->ced = of_property_read_bool(np, "clock-error-detect");
|
|
|
|
+ match = of_match_device(of_match_ptr(stm32_rng_match), dev);
|
|
+ if (!match) {
|
|
+ dev_err(dev, "no compatible OF match\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ priv->data = match->data;
|
|
+
|
|
dev_set_drvdata(dev, priv);
|
|
|
|
priv->rng.name = dev_driver_string(dev);
|
|
-#ifndef CONFIG_PM
|
|
priv->rng.init = stm32_rng_init;
|
|
- priv->rng.cleanup = stm32_rng_cleanup;
|
|
-#endif
|
|
priv->rng.read = stm32_rng_read;
|
|
priv->rng.priv = (unsigned long) dev;
|
|
priv->rng.quality = 900;
|
|
@@ -161,47 +262,6 @@ static int stm32_rng_probe(struct platform_device *ofdev)
|
|
return devm_hwrng_register(dev, &priv->rng);
|
|
}
|
|
|
|
-static int stm32_rng_remove(struct platform_device *ofdev)
|
|
-{
|
|
- pm_runtime_disable(&ofdev->dev);
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-#ifdef CONFIG_PM
|
|
-static int stm32_rng_runtime_suspend(struct device *dev)
|
|
-{
|
|
- struct stm32_rng_private *priv = dev_get_drvdata(dev);
|
|
-
|
|
- stm32_rng_cleanup(&priv->rng);
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int stm32_rng_runtime_resume(struct device *dev)
|
|
-{
|
|
- struct stm32_rng_private *priv = dev_get_drvdata(dev);
|
|
-
|
|
- return stm32_rng_init(&priv->rng);
|
|
-}
|
|
-#endif
|
|
-
|
|
-static const struct dev_pm_ops stm32_rng_pm_ops = {
|
|
- SET_RUNTIME_PM_OPS(stm32_rng_runtime_suspend,
|
|
- stm32_rng_runtime_resume, NULL)
|
|
- SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
|
- pm_runtime_force_resume)
|
|
-};
|
|
-
|
|
-
|
|
-static const struct of_device_id stm32_rng_match[] = {
|
|
- {
|
|
- .compatible = "st,stm32-rng",
|
|
- },
|
|
- {},
|
|
-};
|
|
-MODULE_DEVICE_TABLE(of, stm32_rng_match);
|
|
-
|
|
static struct platform_driver stm32_rng_driver = {
|
|
.driver = {
|
|
.name = "stm32-rng",
|
|
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
|
|
index cda7d7162..dd44afd6a 100644
|
|
--- a/drivers/firmware/Kconfig
|
|
+++ b/drivers/firmware/Kconfig
|
|
@@ -11,7 +11,7 @@ source "drivers/firmware/arm_scmi/Kconfig"
|
|
config ARM_SCPI_PROTOCOL
|
|
tristate "ARM System Control and Power Interface (SCPI) Message Protocol"
|
|
depends on ARM || ARM64 || COMPILE_TEST
|
|
- depends on MAILBOX
|
|
+ depends on MAILBOX || HAVE_ARM_SMCCC || OPTEE
|
|
help
|
|
System Control and Power Interface (SCPI) Message Protocol is
|
|
defined for the purpose of communication between the Application
|
|
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
|
|
index bb2e1cbff..918d81e6e 100644
|
|
--- a/drivers/input/touchscreen/edt-ft5x06.c
|
|
+++ b/drivers/input/touchscreen/edt-ft5x06.c
|
|
@@ -30,6 +30,8 @@
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <asm/unaligned.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <drm/drm_mipi_dsi.h>
|
|
|
|
#define WORK_REGISTER_THRESHOLD 0x00
|
|
#define WORK_REGISTER_REPORT_RATE 0x08
|
|
@@ -914,6 +916,10 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client,
|
|
snprintf(model_name, EDT_NAME_LEN, "EP%i%i0ML00",
|
|
rdbuf[0] >> 4, rdbuf[0] & 0x0F);
|
|
break;
|
|
+ case 0x51: /* Rocktech Rk043fn48h Display */
|
|
+ tsdata->version = EV_FT;
|
|
+ snprintf(model_name, EDT_NAME_LEN, "FT5336GQQ");
|
|
+ break;
|
|
case 0x5a: /* Solomon Goldentek Display */
|
|
snprintf(model_name, EDT_NAME_LEN, "GKTW50SCED1R0");
|
|
break;
|
|
@@ -1078,6 +1084,8 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client,
|
|
const struct edt_i2c_chip_data *chip_data;
|
|
struct edt_ft5x06_ts_data *tsdata;
|
|
u8 buf[2] = { 0xfc, 0x00 };
|
|
+ struct mipi_dsi_device *panel;
|
|
+ struct device_node *np;
|
|
struct input_dev *input;
|
|
unsigned long irq_flags;
|
|
int error;
|
|
@@ -1255,6 +1263,18 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client,
|
|
if (error)
|
|
return error;
|
|
|
|
+ np = of_parse_phandle(client->dev.of_node, "panel", 0);
|
|
+ if (np) {
|
|
+ panel = of_find_mipi_dsi_device_by_node(np);
|
|
+ of_node_put(np);
|
|
+ if (!panel)
|
|
+ return -EPROBE_DEFER;
|
|
+
|
|
+ device_link_add(&client->dev, &panel->dev, DL_FLAG_STATELESS |
|
|
+ DL_FLAG_AUTOREMOVE_SUPPLIER);
|
|
+ put_device(&panel->dev);
|
|
+ }
|
|
+
|
|
edt_ft5x06_ts_prepare_debugfs(tsdata, dev_driver_string(&client->dev));
|
|
|
|
dev_dbg(&client->dev,
|
|
diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c
|
|
index 5051a1766..f96979543 100644
|
|
--- a/drivers/input/touchscreen/goodix.c
|
|
+++ b/drivers/input/touchscreen/goodix.c
|
|
@@ -21,6 +21,7 @@
|
|
#include <linux/input/touchscreen.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
+#include <drm/drm_mipi_dsi.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/regulator/consumer.h>
|
|
@@ -310,7 +311,7 @@ static int goodix_ts_read_input_report(struct goodix_ts_data *ts, u8 *data)
|
|
error = goodix_i2c_read(ts->client, addr, data,
|
|
header_contact_keycode_size);
|
|
if (error) {
|
|
- dev_err(&ts->client->dev, "I2C transfer error: %d\n",
|
|
+ dev_dbg(&ts->client->dev, "I2C transfer error: %d\n",
|
|
error);
|
|
return error;
|
|
}
|
|
@@ -438,7 +439,7 @@ static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id)
|
|
goodix_process_events(ts);
|
|
|
|
if (goodix_i2c_write_u8(ts->client, GOODIX_READ_COOR_ADDR, 0) < 0)
|
|
- dev_err(&ts->client->dev, "I2C write end_cmd error\n");
|
|
+ dev_dbg(&ts->client->dev, "I2C write end_cmd error\n");
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
@@ -1157,6 +1158,8 @@ static int goodix_ts_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct goodix_ts_data *ts;
|
|
+ struct mipi_dsi_device *panel;
|
|
+ struct device_node *np;
|
|
int error;
|
|
|
|
dev_dbg(&client->dev, "I2C Address: 0x%02x\n", client->addr);
|
|
@@ -1210,6 +1213,26 @@ static int goodix_ts_probe(struct i2c_client *client,
|
|
dev_err(&client->dev, "Controller reset failed.\n");
|
|
return error;
|
|
}
|
|
+ } else {
|
|
+ /* reset the controller */
|
|
+ if (ts->gpiod_rst) {
|
|
+ error = gpiod_direction_output(ts->gpiod_rst, 1);
|
|
+ if (error) {
|
|
+ dev_err(&client->dev, "Gpio reset failed.\n");
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ msleep(20);
|
|
+
|
|
+ error = gpiod_direction_output(ts->gpiod_rst, 0);
|
|
+ if (error) {
|
|
+ dev_err(&client->dev, "Gpio unreset failed.\n");
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ /* need a delay after reset to test I2C */
|
|
+ msleep(100);
|
|
+ }
|
|
}
|
|
|
|
error = goodix_i2c_test(client);
|
|
@@ -1256,6 +1279,17 @@ static int goodix_ts_probe(struct i2c_client *client,
|
|
return error;
|
|
}
|
|
|
|
+ np = of_parse_phandle(client->dev.of_node, "panel", 0);
|
|
+ if (np) {
|
|
+ panel = of_find_mipi_dsi_device_by_node(np);
|
|
+ of_node_put(np);
|
|
+ if (!panel)
|
|
+ return -EPROBE_DEFER;
|
|
+ device_link_add(&client->dev, &panel->dev, DL_FLAG_STATELESS |
|
|
+ DL_FLAG_AUTOREMOVE_SUPPLIER);
|
|
+ put_device(&panel->dev);
|
|
+ }
|
|
+
|
|
return 0;
|
|
}
|
|
|
|
@@ -1311,6 +1345,7 @@ static int __maybe_unused goodix_suspend(struct device *dev)
|
|
* sooner, delay 58ms here.
|
|
*/
|
|
msleep(58);
|
|
+
|
|
return 0;
|
|
}
|
|
|
|
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
|
|
index 6157e73ee..9343c680b 100644
|
|
--- a/drivers/media/i2c/Kconfig
|
|
+++ b/drivers/media/i2c/Kconfig
|
|
@@ -730,6 +730,18 @@ config VIDEO_APTINA_PLL
|
|
config VIDEO_CCS_PLL
|
|
tristate
|
|
|
|
+config VIDEO_GC2145
|
|
+ tristate "GalaxyCore GC2145 sensor support"
|
|
+ depends on I2C && VIDEO_V4L2
|
|
+ select VIDEO_V4L2_SUBDEV_API
|
|
+ select V4L2_FWNODE
|
|
+ help
|
|
+ This is a V4L2 sensor-level driver for GalaxyCore GC2145
|
|
+ 2 Mpixel camera.
|
|
+
|
|
+ To compile this driver as a module, choose M here: the
|
|
+ module will be called gc2145.
|
|
+
|
|
config VIDEO_HI556
|
|
tristate "Hynix Hi-556 sensor support"
|
|
depends on I2C && VIDEO_V4L2
|
|
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
|
|
index 83268f20a..3916c2818 100644
|
|
--- a/drivers/media/i2c/Makefile
|
|
+++ b/drivers/media/i2c/Makefile
|
|
@@ -133,4 +133,5 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
|
|
obj-$(CONFIG_VIDEO_RDACM20) += rdacm20.o
|
|
obj-$(CONFIG_VIDEO_RDACM21) += rdacm21.o
|
|
obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
|
|
+obj-$(CONFIG_VIDEO_GC2145) += gc2145.o
|
|
obj-$(CONFIG_SDR_MAX2175) += max2175.o
|
|
diff --git a/drivers/media/i2c/gc2145.c b/drivers/media/i2c/gc2145.c
|
|
new file mode 100644
|
|
index 000000000..f0c7a8ad7
|
|
--- /dev/null
|
|
+++ b/drivers/media/i2c/gc2145.c
|
|
@@ -0,0 +1,1754 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * A V4L2 driver for Galaxycore GC2145 camera.
|
|
+ * Copyright (C) 2022, STMicroelectronics SA
|
|
+ *
|
|
+ * Inspired from the imx219.c driver
|
|
+ */
|
|
+
|
|
+#include <linux/clk.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
+#include <linux/units.h>
|
|
+#include <media/mipi-csi2.h>
|
|
+#include <media/v4l2-ctrls.h>
|
|
+#include <media/v4l2-device.h>
|
|
+#include <media/v4l2-event.h>
|
|
+#include <media/v4l2-fwnode.h>
|
|
+#include <media/v4l2-mediabus.h>
|
|
+
|
|
+/* Chip ID */
|
|
+/* Page 0 */
|
|
+#define GC2145_REG_OUTPUT_FMT 0x84
|
|
+#define GC2145_REG_CHIP_ID 0xf0
|
|
+#define GC2145_REG_PAGE_SELECT 0xfe
|
|
+/* Page 3 */
|
|
+#define GC2145_REG_FIFO_FULL_LVL_LOW 0x04
|
|
+#define GC2145_REG_FIFO_FULL_LVL_HIGH 0x05
|
|
+#define GC2145_REG_MIPI_DT 0x11
|
|
+#define GC2145_REG_LWC_LOW 0x12
|
|
+#define GC2145_REG_LWC_HIGH 0x13
|
|
+#define GC2145_REG_FIFO_GATE_MODE 0x17
|
|
+
|
|
+#define GC2145_CHIP_ID 0x2145
|
|
+
|
|
+/* External clock frequency is 24.0MHz */
|
|
+#define GC2145_XCLK_FREQ (24 * HZ_PER_MHZ)
|
|
+
|
|
+struct gc2145_reg {
|
|
+ unsigned char address;
|
|
+ unsigned char val;
|
|
+};
|
|
+
|
|
+struct gc2145_reg_list {
|
|
+ unsigned int num_of_regs;
|
|
+ const struct gc2145_reg *regs;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * struct gc2145_mode - GC2145 mode description
|
|
+ * @width: frame width (in pixel)
|
|
+ * @height: frame height (in pixel)
|
|
+ * @frame_interval: interval (fractionnal) between 2 frames
|
|
+ * @reg_list: registers config sequence to enter into the mode
|
|
+ * @pixel_rate: pixel_rate associated with the mode
|
|
+ */
|
|
+struct gc2145_mode {
|
|
+ unsigned int width;
|
|
+ unsigned int height;
|
|
+ struct v4l2_fract frame_interval;
|
|
+ struct gc2145_reg_list reg_list;
|
|
+ unsigned long pixel_rate;
|
|
+};
|
|
+
|
|
+#define GC2145_640_480_PIXELRATE (60 * HZ_PER_MHZ)
|
|
+static const struct gc2145_reg mode_640_480_regs[] = {
|
|
+ {0xfe, 0xf0}, {0xfe, 0xf0}, {0xfe, 0xf0}, {0xfc, 0x06},
|
|
+ {0xf6, 0x00}, {0xf7, 0x1d}, {0xf8, 0x86}, {0xfa, 0x00},
|
|
+ {0xf9, 0x8e}, {0xf2, 0x00},
|
|
+ /* ISP registers */
|
|
+ {0xfe, 0x00},
|
|
+ /* Exposure */
|
|
+ {0x03, 0x04}, {0x04, 0xe2},
|
|
+ /* Offset Y */
|
|
+ {0x09, 0x00}, {0x0a, 0x00},
|
|
+ /* Offset X */
|
|
+ {0x0b, 0x00}, {0x0c, 0x00},
|
|
+ /* Windows Height */
|
|
+ {0x0d, 0x04}, {0x0e, 0xc0},
|
|
+ /* Windows Width */
|
|
+ {0x0f, 0x06}, {0x10, 0x52},
|
|
+ /* SH Delay */
|
|
+ {0x12, 0x2e},
|
|
+ /* Flip */
|
|
+ {0x17, 0x14},
|
|
+ /* Analog Mode 2 (row skip ??) */
|
|
+ {0x18, 0x22}, {0x19, 0x0e}, {0x1a, 0x01}, {0x1b, 0x4b},
|
|
+ {0x1c, 0x07}, {0x1d, 0x10}, {0x1e, 0x88}, {0x1f, 0x78},
|
|
+ {0x20, 0x03}, {0x21, 0x40}, {0x22, 0xa0}, {0x24, 0x16},
|
|
+ {0x25, 0x01}, {0x26, 0x10}, {0x2d, 0x60}, {0x30, 0x01},
|
|
+ {0x31, 0x90}, {0x33, 0x06}, {0x34, 0x01},
|
|
+ {0xfe, 0x00},
|
|
+ {0x80, 0x7f}, {0x81, 0x26}, {0x82, 0xfa}, {0x83, 0x00},
|
|
+ {0x84, 0x02}, {0x86, 0x02}, {0x88, 0x03}, {0x89, 0x03},
|
|
+ {0x85, 0x08}, {0x8a, 0x00}, {0x8b, 0x00}, {0xb0, 0x55},
|
|
+ {0xc3, 0x00}, {0xc4, 0x80}, {0xc5, 0x90}, {0xc6, 0x3b},
|
|
+ {0xc7, 0x46}, {0xec, 0x06}, {0xed, 0x04}, {0xee, 0x60},
|
|
+ {0xef, 0x90}, {0xb6, 0x01},
|
|
+ /* Crop */
|
|
+ {0x90, 0x01},
|
|
+ {0x91, 0x00}, {0x92, 0x00},
|
|
+ {0x93, 0x00}, {0x94, 0x00},
|
|
+ {0x95, 0x04}, {0x96, 0xb0},
|
|
+ {0x97, 0x06}, {0x98, 0x40},
|
|
+ /* BLK */
|
|
+ {0xfe, 0x00},
|
|
+ {0x40, 0x42}, {0x41, 0x00}, {0x43, 0x5b}, {0x5e, 0x00},
|
|
+ {0x5f, 0x00}, {0x60, 0x00}, {0x61, 0x00}, {0x62, 0x00},
|
|
+ {0x63, 0x00}, {0x64, 0x00}, {0x65, 0x00}, {0x66, 0x20},
|
|
+ {0x67, 0x20}, {0x68, 0x20}, {0x69, 0x20}, {0x76, 0x00},
|
|
+ {0x6a, 0x08}, {0x6b, 0x08}, {0x6c, 0x08}, {0x6d, 0x08},
|
|
+ {0x6e, 0x08}, {0x6f, 0x08}, {0x70, 0x08}, {0x71, 0x08},
|
|
+ {0x76, 0x00}, {0x72, 0xf0}, {0x7e, 0x3c}, {0x7f, 0x00},
|
|
+ {0xfe, 0x02},
|
|
+ {0x48, 0x15}, {0x49, 0x00}, {0x4b, 0x0b},
|
|
+ {0xfe, 0x00},
|
|
+ /* AEC */
|
|
+ {0xfe, 0x01},
|
|
+ {0x01, 0x04}, {0x02, 0xc0}, {0x03, 0x04}, {0x04, 0x90},
|
|
+ {0x05, 0x30}, {0x06, 0x90}, {0x07, 0x30}, {0x08, 0x80},
|
|
+ {0x09, 0x00}, {0x0a, 0x82}, {0x0b, 0x11}, {0x0c, 0x10},
|
|
+ {0x11, 0x10}, {0x13, 0x7b}, {0x17, 0x00}, {0x1c, 0x11},
|
|
+ {0x1e, 0x61}, {0x1f, 0x35}, {0x20, 0x40}, {0x22, 0x40},
|
|
+ {0x23, 0x20},
|
|
+ {0xfe, 0x02},
|
|
+ {0x0f, 0x04},
|
|
+ {0xfe, 0x01},
|
|
+ {0x12, 0x35}, {0x15, 0xb0}, {0x10, 0x31}, {0x3e, 0x28},
|
|
+ {0x3f, 0xb0}, {0x40, 0x90}, {0x41, 0x0f},
|
|
+ /* INTPEE */
|
|
+ {0xfe, 0x02},
|
|
+ {0x90, 0x6c}, {0x91, 0x03}, {0x92, 0xcb}, {0x94, 0x33},
|
|
+ {0x95, 0x84}, {0x97, 0x65}, {0xa2, 0x11},
|
|
+ {0xfe, 0x00},
|
|
+ /* DNDD */
|
|
+ {0xfe, 0x02},
|
|
+ {0x80, 0xc1}, {0x81, 0x08}, {0x82, 0x05}, {0x83, 0x08},
|
|
+ {0x84, 0x0a}, {0x86, 0xf0}, {0x87, 0x50}, {0x88, 0x15},
|
|
+ {0x89, 0xb0}, {0x8a, 0x30}, {0x8b, 0x10},
|
|
+ /* ASDE */
|
|
+ {0xfe, 0x01},
|
|
+ {0x21, 0x04},
|
|
+ {0xfe, 0x02},
|
|
+ {0xa3, 0x50}, {0xa4, 0x20}, {0xa5, 0x40}, {0xa6, 0x80},
|
|
+ {0xab, 0x40}, {0xae, 0x0c}, {0xb3, 0x46}, {0xb4, 0x64},
|
|
+ {0xb6, 0x38}, {0xb7, 0x01}, {0xb9, 0x2b}, {0x3c, 0x04},
|
|
+ {0x3d, 0x15}, {0x4b, 0x06}, {0x4c, 0x20},
|
|
+ {0xfe, 0x00},
|
|
+ /* Gamma */
|
|
+ {0xfe, 0x02},
|
|
+ {0x10, 0x09}, {0x11, 0x0d}, {0x12, 0x13}, {0x13, 0x19},
|
|
+ {0x14, 0x27}, {0x15, 0x37}, {0x16, 0x45}, {0x17, 0x53},
|
|
+ {0x18, 0x69}, {0x19, 0x7d}, {0x1a, 0x8f}, {0x1b, 0x9d},
|
|
+ {0x1c, 0xa9}, {0x1d, 0xbd}, {0x1e, 0xcd}, {0x1f, 0xd9},
|
|
+ {0x20, 0xe3}, {0x21, 0xea}, {0x22, 0xef}, {0x23, 0xf5},
|
|
+ {0x24, 0xf9}, {0x25, 0xff},
|
|
+ {0xfe, 0x00},
|
|
+ {0xc6, 0x20}, {0xc7, 0x2b},
|
|
+ /* Gamma 2 */
|
|
+ {0xfe, 0x02},
|
|
+ {0x26, 0x0f}, {0x27, 0x14}, {0x28, 0x19}, {0x29, 0x1e},
|
|
+ {0x2a, 0x27}, {0x2b, 0x33}, {0x2c, 0x3b}, {0x2d, 0x45},
|
|
+ {0x2e, 0x59}, {0x2f, 0x69}, {0x30, 0x7c}, {0x31, 0x89},
|
|
+ {0x32, 0x98}, {0x33, 0xae}, {0x34, 0xc0}, {0x35, 0xcf},
|
|
+ {0x36, 0xda}, {0x37, 0xe2}, {0x38, 0xe9}, {0x39, 0xf3},
|
|
+ {0x3a, 0xf9}, {0x3b, 0xff},
|
|
+ /* YCP */
|
|
+ {0xfe, 0x02},
|
|
+ {0xd1, 0x32}, {0xd2, 0x32}, {0xd3, 0x40}, {0xd6, 0xf0},
|
|
+ {0xd7, 0x10}, {0xd8, 0xda}, {0xdd, 0x14}, {0xde, 0x86},
|
|
+ {0xed, 0x80}, {0xee, 0x00}, {0xef, 0x3f}, {0xd8, 0xd8},
|
|
+ /* ABS */
|
|
+ {0xfe, 0x01},
|
|
+ {0x9f, 0x40},
|
|
+ /* LSC */
|
|
+ {0xfe, 0x01},
|
|
+ {0xc2, 0x14}, {0xc3, 0x0d}, {0xc4, 0x0c}, {0xc8, 0x15},
|
|
+ {0xc9, 0x0d}, {0xca, 0x0a}, {0xbc, 0x24}, {0xbd, 0x10},
|
|
+ {0xbe, 0x0b}, {0xb6, 0x25}, {0xb7, 0x16}, {0xb8, 0x15},
|
|
+ {0xc5, 0x00}, {0xc6, 0x00}, {0xc7, 0x00}, {0xcb, 0x00},
|
|
+ {0xcc, 0x00}, {0xcd, 0x00}, {0xbf, 0x07}, {0xc0, 0x00},
|
|
+ {0xc1, 0x00}, {0xb9, 0x00}, {0xba, 0x00}, {0xbb, 0x00},
|
|
+ {0xaa, 0x01}, {0xab, 0x01}, {0xac, 0x00}, {0xad, 0x05},
|
|
+ {0xae, 0x06}, {0xaf, 0x0e}, {0xb0, 0x0b}, {0xb1, 0x07},
|
|
+ {0xb2, 0x06}, {0xb3, 0x17}, {0xb4, 0x0e}, {0xb5, 0x0e},
|
|
+ {0xd0, 0x09}, {0xd1, 0x00}, {0xd2, 0x00}, {0xd6, 0x08},
|
|
+ {0xd7, 0x00}, {0xd8, 0x00}, {0xd9, 0x00}, {0xda, 0x00},
|
|
+ {0xdb, 0x00}, {0xd3, 0x0a}, {0xd4, 0x00}, {0xd5, 0x00},
|
|
+ {0xa4, 0x00}, {0xa5, 0x00}, {0xa6, 0x77}, {0xa7, 0x77},
|
|
+ {0xa8, 0x77}, {0xa9, 0x77}, {0xa1, 0x80}, {0xa2, 0x80},
|
|
+ {0xfe, 0x01},
|
|
+ {0xdf, 0x0d}, {0xdc, 0x25}, {0xdd, 0x30}, {0xe0, 0x77},
|
|
+ {0xe1, 0x80}, {0xe2, 0x77}, {0xe3, 0x90}, {0xe6, 0x90},
|
|
+ {0xe7, 0xa0}, {0xe8, 0x90}, {0xe9, 0xa0},
|
|
+ {0xfe, 0x00},
|
|
+ /* AWB */
|
|
+ {0xfe, 0x01},
|
|
+ {0x4f, 0x00}, {0x4f, 0x00}, {0x4b, 0x01}, {0x4f, 0x00},
|
|
+ {0x4c, 0x01}, {0x4d, 0x71}, {0x4e, 0x01},
|
|
+ {0x4c, 0x01}, {0x4d, 0x91}, {0x4e, 0x01},
|
|
+ {0x4c, 0x01}, {0x4d, 0x70}, {0x4e, 0x01},
|
|
+ {0x4c, 0x01}, {0x4d, 0x90}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xb0}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8f}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6f}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xaf}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xd0}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xf0}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xcf}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xef}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6e}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8e}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xae}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xce}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x4d}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6d}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8d}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xad}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xcd}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x4c}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6c}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8c}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xac}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xcc}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xcb}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x4b}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6b}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8b}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xab}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8a}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xaa}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xca}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xca}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xc9}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8a}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0x89}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xa9}, {0x4e, 0x04},
|
|
+ {0x4c, 0x02}, {0x4d, 0x0b}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x0a}, {0x4e, 0x05},
|
|
+ {0x4c, 0x01}, {0x4d, 0xeb}, {0x4e, 0x05},
|
|
+ {0x4c, 0x01}, {0x4d, 0xea}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x09}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x29}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x2a}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x4a}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x8a}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x49}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x69}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x89}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0xa9}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x48}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x68}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x69}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0xca}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xc9}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xe9}, {0x4e, 0x07},
|
|
+ {0x4c, 0x03}, {0x4d, 0x09}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xc8}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xe8}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xa7}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xc7}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xe7}, {0x4e, 0x07},
|
|
+ {0x4c, 0x03}, {0x4d, 0x07}, {0x4e, 0x07},
|
|
+ {0x4f, 0x01},
|
|
+ {0x50, 0x80}, {0x51, 0xa8}, {0x52, 0x47}, {0x53, 0x38},
|
|
+ {0x54, 0xc7}, {0x56, 0x0e}, {0x58, 0x08}, {0x5b, 0x00},
|
|
+ {0x5c, 0x74}, {0x5d, 0x8b}, {0x61, 0xdb}, {0x62, 0xb8},
|
|
+ {0x63, 0x86}, {0x64, 0xc0}, {0x65, 0x04}, {0x67, 0xa8},
|
|
+ {0x68, 0xb0}, {0x69, 0x00}, {0x6a, 0xa8}, {0x6b, 0xb0},
|
|
+ {0x6c, 0xaf}, {0x6d, 0x8b}, {0x6e, 0x50}, {0x6f, 0x18},
|
|
+ {0x73, 0xf0}, {0x70, 0x0d}, {0x71, 0x60}, {0x72, 0x80},
|
|
+ {0x74, 0x01}, {0x75, 0x01}, {0x7f, 0x0c}, {0x76, 0x70},
|
|
+ {0x77, 0x58}, {0x78, 0xa0}, {0x79, 0x5e}, {0x7a, 0x54},
|
|
+ {0x7b, 0x58},
|
|
+ {0xfe, 0x00},
|
|
+ /* CC */
|
|
+ {0xfe, 0x02},
|
|
+ {0xc0, 0x01}, {0xc1, 0x44}, {0xc2, 0xfd}, {0xc3, 0x04},
|
|
+ {0xc4, 0xf0}, {0xc5, 0x48}, {0xc6, 0xfd}, {0xc7, 0x46},
|
|
+ {0xc8, 0xfd}, {0xc9, 0x02}, {0xca, 0xe0}, {0xcb, 0x45},
|
|
+ {0xcc, 0xec}, {0xcd, 0x48}, {0xce, 0xf0}, {0xcf, 0xf0},
|
|
+ {0xe3, 0x0c}, {0xe4, 0x4b}, {0xe5, 0xe0},
|
|
+ /* ABS */
|
|
+ {0xfe, 0x01},
|
|
+ {0x9f, 0x40},
|
|
+ {0xfe, 0x00},
|
|
+ /* Dark sun */
|
|
+ {0xfe, 0x02},
|
|
+ {0x40, 0xbf}, {0x46, 0xcf},
|
|
+ {0xfe, 0x00},
|
|
+ /* Framerate 50Hz */
|
|
+ {0xfe, 0x00},
|
|
+ {0x05, 0x01}, {0x06, 0x30}, {0x07, 0x00}, {0x08, 0x0c},
|
|
+ {0xfe, 0x01},
|
|
+ {0x25, 0x01}, {0x26, 0x75}, {0x27, 0x04}, {0x28, 0x5f},
|
|
+ {0x29, 0x04}, {0x2a, 0x5f}, {0x2b, 0x04}, {0x2c, 0x5f},
|
|
+ {0x2d, 0x04}, {0x2e, 0x5f},
|
|
+ {0xfe, 0x00},
|
|
+ /* Output */
|
|
+ {0xfe, 0x00},
|
|
+ {0xf2, 0x00},
|
|
+ /* Mipi */
|
|
+ {0xfe, 0x03},
|
|
+ {0x02, 0x22}, {0x03, 0x10},
|
|
+ {0x06, 0x88}, {0x01, 0x87}, {0x10, 0x95}, {0x11, 0x1e},
|
|
+ {0x15, 0x10}, {0x22, 0x04},
|
|
+ {0x23, 0x10}, {0x24, 0x10}, {0x25, 0x10}, {0x26, 0x05},
|
|
+ {0x21, 0x10}, {0x29, 0x03}, {0x2a, 0x0a}, {0x2b, 0x06},
|
|
+ {0xfe, 0x00},
|
|
+ {0xfe, 0x00},
|
|
+ {0xfd, 0x01}, {0xfa, 0x00},
|
|
+ /* Crop window */
|
|
+ {0xfe, 0x00},
|
|
+ {0x90, 0x01}, {0x91, 0x00}, {0x92, 0x00}, {0x93, 0x00},
|
|
+ {0x94, 0x00}, {0x95, 0x01}, {0x96, 0xe0}, {0x97, 0x02},
|
|
+ {0x98, 0x80}, {0x99, 0x55}, {0x9a, 0x06}, {0x9b, 0x01},
|
|
+ {0x9c, 0x23}, {0x9d, 0x00}, {0x9e, 0x00}, {0x9f, 0x01},
|
|
+ {0xa0, 0x23}, {0xa1, 0x00}, {0xa2, 0x00},
|
|
+ /* AWB */
|
|
+ {0xfe, 0x00},
|
|
+ {0xec, 0x06}, {0xed, 0x04}, {0xee, 0x60}, {0xef, 0x90},
|
|
+ {0xfe, 0x01},
|
|
+ {0x74, 0x01},
|
|
+ /* AEC */
|
|
+ {0xfe, 0x01},
|
|
+ {0x01, 0x04}, {0x02, 0xc0}, {0x03, 0x04}, {0x04, 0x90},
|
|
+ {0x05, 0x30}, {0x06, 0x90}, {0x07, 0x30}, {0x08, 0x80},
|
|
+ {0x0a, 0x82},
|
|
+ {0xfe, 0x01},
|
|
+ {0x21, 0x04},
|
|
+};
|
|
+
|
|
+#define GC2145_1280_720_PIXELRATE (96 * HZ_PER_MHZ)
|
|
+static const struct gc2145_reg mode_1280_720_regs[] = {
|
|
+ {0xfe, 0xf0}, {0xfe, 0xf0}, {0xfe, 0xf0}, {0xfc, 0x06},
|
|
+ {0xf6, 0x00}, {0xf7, 0x1d}, {0xf8, 0x83}, {0xfa, 0x00},
|
|
+ {0xf9, 0x8e}, {0xf2, 0x00},
|
|
+ /* ISP registers */
|
|
+ {0xfe, 0x00},
|
|
+ /* Exposure */
|
|
+ {0x03, 0x04}, {0x04, 0xe2},
|
|
+ /* Offset Y */
|
|
+ {0x09, 0x00}, {0x0a, 0xf0},
|
|
+ /* Offset X */
|
|
+ {0x0b, 0x00}, {0x0c, 0xa0},
|
|
+ /* Windows Height */
|
|
+ {0x0d, 0x02}, {0x0e, 0xe0},
|
|
+ /* Windows Width */
|
|
+ {0x0f, 0x05}, {0x10, 0x10},
|
|
+ /* SH Delay */
|
|
+ {0x12, 0x2e},
|
|
+ /* Flip */
|
|
+ {0x17, 0x14},
|
|
+ /* Analog Mode 2 (row skip ??) */
|
|
+ {0x18, 0x22}, {0x19, 0x0e}, {0x1a, 0x01}, {0x1b, 0x4b},
|
|
+ {0x1c, 0x07}, {0x1d, 0x10}, {0x1e, 0x88}, {0x1f, 0x78},
|
|
+ {0x20, 0x03}, {0x21, 0x40}, {0x22, 0xa0}, {0x24, 0x3f},
|
|
+ {0x25, 0x01}, {0x26, 0x10}, {0x2d, 0x60}, {0x30, 0x01},
|
|
+ {0x31, 0x90}, {0x33, 0x06}, {0x34, 0x01},
|
|
+ {0xfe, 0x00},
|
|
+ {0x80, 0x7f}, {0x81, 0x26}, {0x82, 0xfa}, {0x83, 0x00},
|
|
+ {0x84, 0x02}, {0x86, 0x06}, {0x88, 0x03}, {0x89, 0x03},
|
|
+ {0x85, 0x08}, {0x8a, 0x00}, {0x8b, 0x00}, {0xb0, 0x55},
|
|
+ {0xc3, 0x00}, {0xc4, 0x80}, {0xc5, 0x90}, {0xc6, 0x3b},
|
|
+ {0xc7, 0x46}, {0xec, 0x06}, {0xed, 0x04}, {0xee, 0x60},
|
|
+ {0xef, 0x90}, {0xb6, 0x01},
|
|
+ /* Crop */
|
|
+ {0x90, 0x01},
|
|
+ {0x91, 0x00}, {0x92, 0x00},
|
|
+ {0x93, 0x00}, {0x94, 0x00},
|
|
+ {0x95, 0x02}, {0x96, 0xd0},
|
|
+ {0x97, 0x05}, {0x98, 0x00},
|
|
+ /* BLK */
|
|
+ {0xfe, 0x00},
|
|
+ {0x40, 0x42}, {0x41, 0x00}, {0x43, 0x5b}, {0x5e, 0x00},
|
|
+ {0x5f, 0x00}, {0x60, 0x00}, {0x61, 0x00}, {0x62, 0x00},
|
|
+ {0x63, 0x00}, {0x64, 0x00}, {0x65, 0x00}, {0x66, 0x20},
|
|
+ {0x67, 0x20}, {0x68, 0x20}, {0x69, 0x20}, {0x76, 0x00},
|
|
+ {0x6a, 0x08}, {0x6b, 0x08}, {0x6c, 0x08}, {0x6d, 0x08},
|
|
+ {0x6e, 0x08}, {0x6f, 0x08}, {0x70, 0x08}, {0x71, 0x08},
|
|
+ {0x76, 0x00}, {0x72, 0xf0}, {0x7e, 0x3c}, {0x7f, 0x00},
|
|
+ {0xfe, 0x02},
|
|
+ {0x48, 0x15}, {0x49, 0x00}, {0x4b, 0x0b},
|
|
+ {0xfe, 0x00},
|
|
+ /* AEC */
|
|
+ {0xfe, 0x01},
|
|
+ {0x01, 0x04}, {0x02, 0xc0}, {0x03, 0x04}, {0x04, 0x90},
|
|
+ {0x05, 0x30}, {0x06, 0x90}, {0x07, 0x30}, {0x08, 0x80},
|
|
+ {0x09, 0x00}, {0x0a, 0x82}, {0x0b, 0x11}, {0x0c, 0x10},
|
|
+ {0x11, 0x10}, {0x13, 0x7b}, {0x17, 0x00}, {0x1c, 0x11},
|
|
+ {0x1e, 0x61}, {0x1f, 0x35}, {0x20, 0x40}, {0x22, 0x40},
|
|
+ {0x23, 0x20},
|
|
+ {0xfe, 0x02},
|
|
+ {0x0f, 0x04},
|
|
+ {0xfe, 0x01},
|
|
+ {0x12, 0x35}, {0x15, 0xb0}, {0x10, 0x31}, {0x3e, 0x28},
|
|
+ {0x3f, 0xb0}, {0x40, 0x90}, {0x41, 0x0f},
|
|
+ /* INTPEE */
|
|
+ {0xfe, 0x02},
|
|
+ {0x90, 0x6c}, {0x91, 0x03}, {0x92, 0xcb}, {0x94, 0x33},
|
|
+ {0x95, 0x84}, {0x97, 0x65}, {0xa2, 0x11},
|
|
+ {0xfe, 0x00},
|
|
+ /* DNDD */
|
|
+ {0xfe, 0x02},
|
|
+ {0x80, 0xc1}, {0x81, 0x08}, {0x82, 0x05}, {0x83, 0x08},
|
|
+ {0x84, 0x0a}, {0x86, 0xf0}, {0x87, 0x50}, {0x88, 0x15},
|
|
+ {0x89, 0xb0}, {0x8a, 0x30}, {0x8b, 0x10},
|
|
+ /* ASDE */
|
|
+ {0xfe, 0x01},
|
|
+ {0x21, 0x04},
|
|
+ {0xfe, 0x02},
|
|
+ {0xa3, 0x50}, {0xa4, 0x20}, {0xa5, 0x40}, {0xa6, 0x80},
|
|
+ {0xab, 0x40}, {0xae, 0x0c}, {0xb3, 0x46}, {0xb4, 0x64},
|
|
+ {0xb6, 0x38}, {0xb7, 0x01}, {0xb9, 0x2b}, {0x3c, 0x04},
|
|
+ {0x3d, 0x15}, {0x4b, 0x06}, {0x4c, 0x20},
|
|
+ {0xfe, 0x00},
|
|
+ /* Gamma */
|
|
+ {0xfe, 0x02},
|
|
+ {0x10, 0x09}, {0x11, 0x0d}, {0x12, 0x13}, {0x13, 0x19},
|
|
+ {0x14, 0x27}, {0x15, 0x37}, {0x16, 0x45}, {0x17, 0x53},
|
|
+ {0x18, 0x69}, {0x19, 0x7d}, {0x1a, 0x8f}, {0x1b, 0x9d},
|
|
+ {0x1c, 0xa9}, {0x1d, 0xbd}, {0x1e, 0xcd}, {0x1f, 0xd9},
|
|
+ {0x20, 0xe3}, {0x21, 0xea}, {0x22, 0xef}, {0x23, 0xf5},
|
|
+ {0x24, 0xf9}, {0x25, 0xff},
|
|
+ {0xfe, 0x00},
|
|
+ {0xc6, 0x20}, {0xc7, 0x2b},
|
|
+ /* Gamma 2 */
|
|
+ {0xfe, 0x02},
|
|
+ {0x26, 0x0f}, {0x27, 0x14}, {0x28, 0x19}, {0x29, 0x1e},
|
|
+ {0x2a, 0x27}, {0x2b, 0x33}, {0x2c, 0x3b}, {0x2d, 0x45},
|
|
+ {0x2e, 0x59}, {0x2f, 0x69}, {0x30, 0x7c}, {0x31, 0x89},
|
|
+ {0x32, 0x98}, {0x33, 0xae}, {0x34, 0xc0}, {0x35, 0xcf},
|
|
+ {0x36, 0xda}, {0x37, 0xe2}, {0x38, 0xe9}, {0x39, 0xf3},
|
|
+ {0x3a, 0xf9}, {0x3b, 0xff},
|
|
+ /* YCP */
|
|
+ {0xfe, 0x02},
|
|
+ {0xd1, 0x32}, {0xd2, 0x32}, {0xd3, 0x40}, {0xd6, 0xf0},
|
|
+ {0xd7, 0x10}, {0xd8, 0xda}, {0xdd, 0x14}, {0xde, 0x86},
|
|
+ {0xed, 0x80}, {0xee, 0x00}, {0xef, 0x3f}, {0xd8, 0xd8},
|
|
+ /* ABS */
|
|
+ {0xfe, 0x01},
|
|
+ {0x9f, 0x40},
|
|
+ /* LSC */
|
|
+ {0xfe, 0x01},
|
|
+ {0xc2, 0x14}, {0xc3, 0x0d}, {0xc4, 0x0c}, {0xc8, 0x15},
|
|
+ {0xc9, 0x0d}, {0xca, 0x0a}, {0xbc, 0x24}, {0xbd, 0x10},
|
|
+ {0xbe, 0x0b}, {0xb6, 0x25}, {0xb7, 0x16}, {0xb8, 0x15},
|
|
+ {0xc5, 0x00}, {0xc6, 0x00}, {0xc7, 0x00}, {0xcb, 0x00},
|
|
+ {0xcc, 0x00}, {0xcd, 0x00}, {0xbf, 0x07}, {0xc0, 0x00},
|
|
+ {0xc1, 0x00}, {0xb9, 0x00}, {0xba, 0x00}, {0xbb, 0x00},
|
|
+ {0xaa, 0x01}, {0xab, 0x01}, {0xac, 0x00}, {0xad, 0x05},
|
|
+ {0xae, 0x06}, {0xaf, 0x0e}, {0xb0, 0x0b}, {0xb1, 0x07},
|
|
+ {0xb2, 0x06}, {0xb3, 0x17}, {0xb4, 0x0e}, {0xb5, 0x0e},
|
|
+ {0xd0, 0x09}, {0xd1, 0x00}, {0xd2, 0x00}, {0xd6, 0x08},
|
|
+ {0xd7, 0x00}, {0xd8, 0x00}, {0xd9, 0x00}, {0xda, 0x00},
|
|
+ {0xdb, 0x00}, {0xd3, 0x0a}, {0xd4, 0x00}, {0xd5, 0x00},
|
|
+ {0xa4, 0x00}, {0xa5, 0x00}, {0xa6, 0x77}, {0xa7, 0x77},
|
|
+ {0xa8, 0x77}, {0xa9, 0x77}, {0xa1, 0x80}, {0xa2, 0x80},
|
|
+ {0xfe, 0x01},
|
|
+ {0xdf, 0x0d}, {0xdc, 0x25}, {0xdd, 0x30}, {0xe0, 0x77},
|
|
+ {0xe1, 0x80}, {0xe2, 0x77}, {0xe3, 0x90}, {0xe6, 0x90},
|
|
+ {0xe7, 0xa0}, {0xe8, 0x90}, {0xe9, 0xa0},
|
|
+ {0xfe, 0x00},
|
|
+ /* AWB */
|
|
+ {0xfe, 0x01},
|
|
+ {0x4f, 0x00}, {0x4f, 0x00}, {0x4b, 0x01}, {0x4f, 0x00},
|
|
+ {0x4c, 0x01}, {0x4d, 0x71}, {0x4e, 0x01},
|
|
+ {0x4c, 0x01}, {0x4d, 0x91}, {0x4e, 0x01},
|
|
+ {0x4c, 0x01}, {0x4d, 0x70}, {0x4e, 0x01},
|
|
+ {0x4c, 0x01}, {0x4d, 0x90}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xb0}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8f}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6f}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xaf}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xd0}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xf0}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xcf}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xef}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6e}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8e}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xae}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xce}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x4d}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6d}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8d}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xad}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xcd}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x4c}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6c}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8c}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xac}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xcc}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xcb}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x4b}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6b}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8b}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xab}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8a}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xaa}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xca}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xca}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xc9}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8a}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0x89}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xa9}, {0x4e, 0x04},
|
|
+ {0x4c, 0x02}, {0x4d, 0x0b}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x0a}, {0x4e, 0x05},
|
|
+ {0x4c, 0x01}, {0x4d, 0xeb}, {0x4e, 0x05},
|
|
+ {0x4c, 0x01}, {0x4d, 0xea}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x09}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x29}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x2a}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x4a}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x8a}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x49}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x69}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x89}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0xa9}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x48}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x68}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x69}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0xca}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xc9}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xe9}, {0x4e, 0x07},
|
|
+ {0x4c, 0x03}, {0x4d, 0x09}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xc8}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xe8}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xa7}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xc7}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xe7}, {0x4e, 0x07},
|
|
+ {0x4c, 0x03}, {0x4d, 0x07}, {0x4e, 0x07},
|
|
+ {0x4f, 0x01},
|
|
+ {0x50, 0x80}, {0x51, 0xa8}, {0x52, 0x47}, {0x53, 0x38},
|
|
+ {0x54, 0xc7}, {0x56, 0x0e}, {0x58, 0x08}, {0x5b, 0x00},
|
|
+ {0x5c, 0x74}, {0x5d, 0x8b}, {0x61, 0xdb}, {0x62, 0xb8},
|
|
+ {0x63, 0x86}, {0x64, 0xc0}, {0x65, 0x04}, {0x67, 0xa8},
|
|
+ {0x68, 0xb0}, {0x69, 0x00}, {0x6a, 0xa8}, {0x6b, 0xb0},
|
|
+ {0x6c, 0xaf}, {0x6d, 0x8b}, {0x6e, 0x50}, {0x6f, 0x18},
|
|
+ {0x73, 0xf0}, {0x70, 0x0d}, {0x71, 0x60}, {0x72, 0x80},
|
|
+ {0x74, 0x01}, {0x75, 0x01}, {0x7f, 0x0c}, {0x76, 0x70},
|
|
+ {0x77, 0x58}, {0x78, 0xa0}, {0x79, 0x5e}, {0x7a, 0x54},
|
|
+ {0x7b, 0x58},
|
|
+ {0xfe, 0x00},
|
|
+ /* CC */
|
|
+ {0xfe, 0x02},
|
|
+ {0xc0, 0x01}, {0xc1, 0x44}, {0xc2, 0xfd}, {0xc3, 0x04},
|
|
+ {0xc4, 0xf0}, {0xc5, 0x48}, {0xc6, 0xfd}, {0xc7, 0x46},
|
|
+ {0xc8, 0xfd}, {0xc9, 0x02}, {0xca, 0xe0}, {0xcb, 0x45},
|
|
+ {0xcc, 0xec}, {0xcd, 0x48}, {0xce, 0xf0}, {0xcf, 0xf0},
|
|
+ {0xe3, 0x0c}, {0xe4, 0x4b}, {0xe5, 0xe0},
|
|
+ /* ABS */
|
|
+ {0xfe, 0x01},
|
|
+ {0x9f, 0x40},
|
|
+ {0xfe, 0x00},
|
|
+ /* Dark sun */
|
|
+ {0xfe, 0x02},
|
|
+ {0x40, 0xbf}, {0x46, 0xcf},
|
|
+ {0xfe, 0x00},
|
|
+ /* Framerate 50Hz */
|
|
+ {0xfe, 0x00},
|
|
+ {0x05, 0x01}, {0x06, 0x56}, {0x07, 0x00}, {0x08, 0x12},
|
|
+ {0xfe, 0x01}, {0x25, 0x00}, {0x26, 0xe6}, {0x27, 0x02},
|
|
+ {0x28, 0xb2}, {0x29, 0x02}, {0x2a, 0xb2}, {0x2b, 0x02},
|
|
+ {0x2c, 0xb2}, {0x2d, 0x02}, {0x2e, 0xb2},
|
|
+ {0xfe, 0x00},
|
|
+ /* Output */
|
|
+ {0xfe, 0x00},
|
|
+ {0xf2, 0x00},
|
|
+ /* Mipi */
|
|
+ {0xfe, 0x03},
|
|
+ {0x01, 0x87}, {0x02, 0x22}, {0x03, 0x10},
|
|
+ {0x06, 0x88}, {0x10, 0x95}, {0x11, 0x1e},
|
|
+ {0x15, 0x12}, {0x22, 0x04},
|
|
+ {0x23, 0x10}, {0x24, 0x10}, {0x25, 0x10}, {0x26, 0x05},
|
|
+ {0x21, 0x10}, {0x29, 0x03}, {0x2a, 0x0a}, {0x2b, 0x06},
|
|
+ {0x42, 0x00}, {0x43, 0x05},
|
|
+ {0xfe, 0x00},
|
|
+};
|
|
+
|
|
+#define GC2145_1600_1200_PIXELRATE (72 * HZ_PER_MHZ)
|
|
+static const struct gc2145_reg mode_1600_1200_regs[] = {
|
|
+ {0xfe, 0xf0}, {0xfe, 0xf0}, {0xfe, 0xf0}, {0xfc, 0x06},
|
|
+ {0xf6, 0x00}, {0xf7, 0x1d}, {0xf8, 0x84}, {0xfa, 0x00},
|
|
+ {0xf9, 0x8e}, {0xf2, 0x00},
|
|
+ /* ISP registers */
|
|
+ {0xfe, 0x00},
|
|
+ /* Exposure */
|
|
+ {0x03, 0x04}, {0x04, 0xe2},
|
|
+ /* Offset Y */
|
|
+ {0x09, 0x00}, {0x0a, 0x00},
|
|
+ /* Offset X */
|
|
+ {0x0b, 0x00}, {0x0c, 0x00},
|
|
+ /* Windows Height */
|
|
+ {0x0d, 0x04}, {0x0e, 0xc0},
|
|
+ /* Windows Width */
|
|
+ {0x0f, 0x06}, {0x10, 0x52},
|
|
+ /* SH Delay */
|
|
+ {0x12, 0x2e},
|
|
+ /* Flip */
|
|
+ {0x17, 0x14},
|
|
+ /* Analog Mode 2 (row skip ??) */
|
|
+ {0x18, 0x22}, {0x19, 0x0e}, {0x1a, 0x01}, {0x1b, 0x4b},
|
|
+ {0x1c, 0x07}, {0x1d, 0x10}, {0x1e, 0x88}, {0x1f, 0x78},
|
|
+ {0x20, 0x03}, {0x21, 0x40}, {0x22, 0xa0}, {0x24, 0x16},
|
|
+ {0x25, 0x01}, {0x26, 0x10}, {0x2d, 0x60}, {0x30, 0x01},
|
|
+ {0x31, 0x90}, {0x33, 0x06}, {0x34, 0x01},
|
|
+ {0xfe, 0x00},
|
|
+ {0x80, 0x7f}, {0x81, 0x26}, {0x82, 0xfa}, {0x83, 0x00},
|
|
+ {0x84, 0x02}, {0x86, 0x02}, {0x88, 0x03}, {0x89, 0x03},
|
|
+ {0x85, 0x08}, {0x8a, 0x00}, {0x8b, 0x00}, {0xb0, 0x55},
|
|
+ {0xc3, 0x00}, {0xc4, 0x80}, {0xc5, 0x90}, {0xc6, 0x3b},
|
|
+ {0xc7, 0x46}, {0xec, 0x06}, {0xed, 0x04}, {0xee, 0x60},
|
|
+ {0xef, 0x90}, {0xb6, 0x01},
|
|
+ /* Crop */
|
|
+ {0x90, 0x01},
|
|
+ {0x91, 0x00}, {0x92, 0x00},
|
|
+ {0x93, 0x00}, {0x94, 0x00},
|
|
+ {0x95, 0x04}, {0x96, 0xb0},
|
|
+ {0x97, 0x06}, {0x98, 0x40},
|
|
+ /* BLK */
|
|
+ {0xfe, 0x00},
|
|
+ {0x40, 0x42}, {0x41, 0x00}, {0x43, 0x5b}, {0x5e, 0x00},
|
|
+ {0x5f, 0x00}, {0x60, 0x00}, {0x61, 0x00}, {0x62, 0x00},
|
|
+ {0x63, 0x00}, {0x64, 0x00}, {0x65, 0x00}, {0x66, 0x20},
|
|
+ {0x67, 0x20}, {0x68, 0x20}, {0x69, 0x20}, {0x76, 0x00},
|
|
+ {0x6a, 0x08}, {0x6b, 0x08}, {0x6c, 0x08}, {0x6d, 0x08},
|
|
+ {0x6e, 0x08}, {0x6f, 0x08}, {0x70, 0x08}, {0x71, 0x08},
|
|
+ {0x76, 0x00}, {0x72, 0xf0}, {0x7e, 0x3c}, {0x7f, 0x00},
|
|
+ {0xfe, 0x02},
|
|
+ {0x48, 0x15}, {0x49, 0x00}, {0x4b, 0x0b},
|
|
+ {0xfe, 0x00},
|
|
+ /* AEC */
|
|
+ {0xfe, 0x01},
|
|
+ {0x01, 0x04}, {0x02, 0xc0}, {0x03, 0x04}, {0x04, 0x90},
|
|
+ {0x05, 0x30}, {0x06, 0x90}, {0x07, 0x30}, {0x08, 0x80},
|
|
+ {0x09, 0x00}, {0x0a, 0x82}, {0x0b, 0x11}, {0x0c, 0x10},
|
|
+ {0x11, 0x10}, {0x13, 0x7b}, {0x17, 0x00}, {0x1c, 0x11},
|
|
+ {0x1e, 0x61}, {0x1f, 0x35}, {0x20, 0x40}, {0x22, 0x40},
|
|
+ {0x23, 0x20},
|
|
+ {0xfe, 0x02},
|
|
+ {0x0f, 0x04},
|
|
+ {0xfe, 0x01},
|
|
+ {0x12, 0x35}, {0x15, 0xb0}, {0x10, 0x31}, {0x3e, 0x28},
|
|
+ {0x3f, 0xb0}, {0x40, 0x90}, {0x41, 0x0f},
|
|
+ /* INTPEE */
|
|
+ {0xfe, 0x02},
|
|
+ {0x90, 0x6c}, {0x91, 0x03}, {0x92, 0xcb}, {0x94, 0x33},
|
|
+ {0x95, 0x84}, {0x97, 0x65}, {0xa2, 0x11},
|
|
+ {0xfe, 0x00},
|
|
+ /* DNDD */
|
|
+ {0xfe, 0x02},
|
|
+ {0x80, 0xc1}, {0x81, 0x08}, {0x82, 0x05}, {0x83, 0x08},
|
|
+ {0x84, 0x0a}, {0x86, 0xf0}, {0x87, 0x50}, {0x88, 0x15},
|
|
+ {0x89, 0xb0}, {0x8a, 0x30}, {0x8b, 0x10},
|
|
+ /* ASDE */
|
|
+ {0xfe, 0x01},
|
|
+ {0x21, 0x04},
|
|
+ {0xfe, 0x02},
|
|
+ {0xa3, 0x50}, {0xa4, 0x20}, {0xa5, 0x40}, {0xa6, 0x80},
|
|
+ {0xab, 0x40}, {0xae, 0x0c}, {0xb3, 0x46}, {0xb4, 0x64},
|
|
+ {0xb6, 0x38}, {0xb7, 0x01}, {0xb9, 0x2b}, {0x3c, 0x04},
|
|
+ {0x3d, 0x15}, {0x4b, 0x06}, {0x4c, 0x20},
|
|
+ {0xfe, 0x00},
|
|
+ /* Gamma */
|
|
+ {0xfe, 0x02},
|
|
+ {0x10, 0x09}, {0x11, 0x0d}, {0x12, 0x13}, {0x13, 0x19},
|
|
+ {0x14, 0x27}, {0x15, 0x37}, {0x16, 0x45}, {0x17, 0x53},
|
|
+ {0x18, 0x69}, {0x19, 0x7d}, {0x1a, 0x8f}, {0x1b, 0x9d},
|
|
+ {0x1c, 0xa9}, {0x1d, 0xbd}, {0x1e, 0xcd}, {0x1f, 0xd9},
|
|
+ {0x20, 0xe3}, {0x21, 0xea}, {0x22, 0xef}, {0x23, 0xf5},
|
|
+ {0x24, 0xf9}, {0x25, 0xff},
|
|
+ {0xfe, 0x00},
|
|
+ {0xc6, 0x20}, {0xc7, 0x2b},
|
|
+ /* Gamma 2 */
|
|
+ {0xfe, 0x02},
|
|
+ {0x26, 0x0f}, {0x27, 0x14}, {0x28, 0x19}, {0x29, 0x1e},
|
|
+ {0x2a, 0x27}, {0x2b, 0x33}, {0x2c, 0x3b}, {0x2d, 0x45},
|
|
+ {0x2e, 0x59}, {0x2f, 0x69}, {0x30, 0x7c}, {0x31, 0x89},
|
|
+ {0x32, 0x98}, {0x33, 0xae}, {0x34, 0xc0}, {0x35, 0xcf},
|
|
+ {0x36, 0xda}, {0x37, 0xe2}, {0x38, 0xe9}, {0x39, 0xf3},
|
|
+ {0x3a, 0xf9}, {0x3b, 0xff},
|
|
+ /* YCP */
|
|
+ {0xfe, 0x02},
|
|
+ {0xd1, 0x32}, {0xd2, 0x32}, {0xd3, 0x40}, {0xd6, 0xf0},
|
|
+ {0xd7, 0x10}, {0xd8, 0xda}, {0xdd, 0x14}, {0xde, 0x86},
|
|
+ {0xed, 0x80}, {0xee, 0x00}, {0xef, 0x3f}, {0xd8, 0xd8},
|
|
+ /* ABS */
|
|
+ {0xfe, 0x01},
|
|
+ {0x9f, 0x40},
|
|
+ /* LSC */
|
|
+ {0xfe, 0x01},
|
|
+ {0xc2, 0x14}, {0xc3, 0x0d}, {0xc4, 0x0c}, {0xc8, 0x15},
|
|
+ {0xc9, 0x0d}, {0xca, 0x0a}, {0xbc, 0x24}, {0xbd, 0x10},
|
|
+ {0xbe, 0x0b}, {0xb6, 0x25}, {0xb7, 0x16}, {0xb8, 0x15},
|
|
+ {0xc5, 0x00}, {0xc6, 0x00}, {0xc7, 0x00}, {0xcb, 0x00},
|
|
+ {0xcc, 0x00}, {0xcd, 0x00}, {0xbf, 0x07}, {0xc0, 0x00},
|
|
+ {0xc1, 0x00}, {0xb9, 0x00}, {0xba, 0x00}, {0xbb, 0x00},
|
|
+ {0xaa, 0x01}, {0xab, 0x01}, {0xac, 0x00}, {0xad, 0x05},
|
|
+ {0xae, 0x06}, {0xaf, 0x0e}, {0xb0, 0x0b}, {0xb1, 0x07},
|
|
+ {0xb2, 0x06}, {0xb3, 0x17}, {0xb4, 0x0e}, {0xb5, 0x0e},
|
|
+ {0xd0, 0x09}, {0xd1, 0x00}, {0xd2, 0x00}, {0xd6, 0x08},
|
|
+ {0xd7, 0x00}, {0xd8, 0x00}, {0xd9, 0x00}, {0xda, 0x00},
|
|
+ {0xdb, 0x00}, {0xd3, 0x0a}, {0xd4, 0x00}, {0xd5, 0x00},
|
|
+ {0xa4, 0x00}, {0xa5, 0x00}, {0xa6, 0x77}, {0xa7, 0x77},
|
|
+ {0xa8, 0x77}, {0xa9, 0x77}, {0xa1, 0x80}, {0xa2, 0x80},
|
|
+ {0xfe, 0x01},
|
|
+ {0xdf, 0x0d}, {0xdc, 0x25}, {0xdd, 0x30}, {0xe0, 0x77},
|
|
+ {0xe1, 0x80}, {0xe2, 0x77}, {0xe3, 0x90}, {0xe6, 0x90},
|
|
+ {0xe7, 0xa0}, {0xe8, 0x90}, {0xe9, 0xa0},
|
|
+ {0xfe, 0x00},
|
|
+ /* AWB */
|
|
+ {0xfe, 0x01},
|
|
+ {0x4f, 0x00}, {0x4f, 0x00}, {0x4b, 0x01}, {0x4f, 0x00},
|
|
+ {0x4c, 0x01}, {0x4d, 0x71}, {0x4e, 0x01},
|
|
+ {0x4c, 0x01}, {0x4d, 0x91}, {0x4e, 0x01},
|
|
+ {0x4c, 0x01}, {0x4d, 0x70}, {0x4e, 0x01},
|
|
+ {0x4c, 0x01}, {0x4d, 0x90}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xb0}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8f}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6f}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xaf}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xd0}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xf0}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xcf}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0xef}, {0x4e, 0x02},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6e}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8e}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xae}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xce}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x4d}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6d}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8d}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xad}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xcd}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x4c}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6c}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8c}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xac}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xcc}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xcb}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x4b}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x6b}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8b}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0xab}, {0x4e, 0x03},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8a}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xaa}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xca}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xca}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xc9}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0x8a}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0x89}, {0x4e, 0x04},
|
|
+ {0x4c, 0x01}, {0x4d, 0xa9}, {0x4e, 0x04},
|
|
+ {0x4c, 0x02}, {0x4d, 0x0b}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x0a}, {0x4e, 0x05},
|
|
+ {0x4c, 0x01}, {0x4d, 0xeb}, {0x4e, 0x05},
|
|
+ {0x4c, 0x01}, {0x4d, 0xea}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x09}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x29}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x2a}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x4a}, {0x4e, 0x05},
|
|
+ {0x4c, 0x02}, {0x4d, 0x8a}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x49}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x69}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x89}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0xa9}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x48}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x68}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0x69}, {0x4e, 0x06},
|
|
+ {0x4c, 0x02}, {0x4d, 0xca}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xc9}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xe9}, {0x4e, 0x07},
|
|
+ {0x4c, 0x03}, {0x4d, 0x09}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xc8}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xe8}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xa7}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xc7}, {0x4e, 0x07},
|
|
+ {0x4c, 0x02}, {0x4d, 0xe7}, {0x4e, 0x07},
|
|
+ {0x4c, 0x03}, {0x4d, 0x07}, {0x4e, 0x07},
|
|
+ {0x4f, 0x01},
|
|
+ {0x50, 0x80}, {0x51, 0xa8}, {0x52, 0x47}, {0x53, 0x38},
|
|
+ {0x54, 0xc7}, {0x56, 0x0e}, {0x58, 0x08}, {0x5b, 0x00},
|
|
+ {0x5c, 0x74}, {0x5d, 0x8b}, {0x61, 0xdb}, {0x62, 0xb8},
|
|
+ {0x63, 0x86}, {0x64, 0xc0}, {0x65, 0x04}, {0x67, 0xa8},
|
|
+ {0x68, 0xb0}, {0x69, 0x00}, {0x6a, 0xa8}, {0x6b, 0xb0},
|
|
+ {0x6c, 0xaf}, {0x6d, 0x8b}, {0x6e, 0x50}, {0x6f, 0x18},
|
|
+ {0x73, 0xf0}, {0x70, 0x0d}, {0x71, 0x60}, {0x72, 0x80},
|
|
+ {0x74, 0x01}, {0x75, 0x01}, {0x7f, 0x0c}, {0x76, 0x70},
|
|
+ {0x77, 0x58}, {0x78, 0xa0}, {0x79, 0x5e}, {0x7a, 0x54},
|
|
+ {0x7b, 0x58},
|
|
+ {0xfe, 0x00},
|
|
+ /* CC */
|
|
+ {0xfe, 0x02},
|
|
+ {0xc0, 0x01}, {0xc1, 0x44}, {0xc2, 0xfd}, {0xc3, 0x04},
|
|
+ {0xc4, 0xf0}, {0xc5, 0x48}, {0xc6, 0xfd}, {0xc7, 0x46},
|
|
+ {0xc8, 0xfd}, {0xc9, 0x02}, {0xca, 0xe0}, {0xcb, 0x45},
|
|
+ {0xcc, 0xec}, {0xcd, 0x48}, {0xce, 0xf0}, {0xcf, 0xf0},
|
|
+ {0xe3, 0x0c}, {0xe4, 0x4b}, {0xe5, 0xe0},
|
|
+ /* ABS */
|
|
+ {0xfe, 0x01},
|
|
+ {0x9f, 0x40},
|
|
+ {0xfe, 0x00},
|
|
+ /* Dark sun */
|
|
+ {0xfe, 0x02},
|
|
+ {0x40, 0xbf}, {0x46, 0xcf},
|
|
+ {0xfe, 0x00},
|
|
+ /* Framerate 50Hz */
|
|
+ {0xfe, 0x00},
|
|
+ {0x05, 0x01}, {0x06, 0x56}, {0x07, 0x00}, {0x08, 0x32},
|
|
+ {0xfe, 0x01},
|
|
+ {0x25, 0x00}, {0x26, 0xfa}, {0x27, 0x04}, {0x28, 0xe2},
|
|
+ {0x29, 0x04}, {0x2a, 0xe2}, {0x2b, 0x04}, {0x2c, 0xe2},
|
|
+ {0x2d, 0x04}, {0x2e, 0xe2},
|
|
+ {0xfe, 0x00},
|
|
+ /* Output */
|
|
+ {0xfe, 0x00},
|
|
+ {0xf2, 0x00},
|
|
+ /* Mipi */
|
|
+ {0xfe, 0x03},
|
|
+ {0x01, 0x87}, {0x02, 0x22}, {0x03, 0x10},
|
|
+ {0x06, 0x88}, {0x10, 0x95}, {0x11, 0x1e},
|
|
+ {0x15, 0x10}, {0x22, 0x04},
|
|
+ {0x23, 0x10}, {0x24, 0x10}, {0x25, 0x10}, {0x26, 0x05},
|
|
+ {0x21, 0x10}, {0x29, 0x03}, {0x2a, 0x0a}, {0x2b, 0x06},
|
|
+ {0xfe, 0x00},
|
|
+};
|
|
+
|
|
+/* Regulators supplies */
|
|
+static const char * const gc2145_supply_name[] = {
|
|
+ "IOVDD", /* Digital I/O (1.7-3V) suppply */
|
|
+ "AVDD", /* Analog (2.7-3V) supply */
|
|
+ "DVDD", /* Digital Core (1.7-1.9V) supply */
|
|
+};
|
|
+
|
|
+#define GC2145_NUM_SUPPLIES ARRAY_SIZE(gc2145_supply_name)
|
|
+
|
|
+/* Mode configs */
|
|
+#define GC2145_MODE_640X480 0
|
|
+#define GC2145_MODE_1280X720 1
|
|
+#define GC2145_MODE_1600X1200 2
|
|
+static const struct gc2145_mode supported_modes[] = {
|
|
+ {
|
|
+ /* 640x480 30fps mode */
|
|
+ .width = 640,
|
|
+ .height = 480,
|
|
+ .frame_interval = {
|
|
+ .numerator = 1,
|
|
+ .denominator = 30,
|
|
+ },
|
|
+ .reg_list = {
|
|
+ .num_of_regs = ARRAY_SIZE(mode_640_480_regs),
|
|
+ .regs = mode_640_480_regs,
|
|
+ },
|
|
+ .pixel_rate = GC2145_640_480_PIXELRATE,
|
|
+ },
|
|
+ {
|
|
+ /* 1280x720 30fps mode */
|
|
+ .width = 1280,
|
|
+ .height = 720,
|
|
+ .frame_interval = {
|
|
+ .numerator = 1,
|
|
+ .denominator = 30,
|
|
+ },
|
|
+ .reg_list = {
|
|
+ .num_of_regs = ARRAY_SIZE(mode_1280_720_regs),
|
|
+ .regs = mode_1280_720_regs,
|
|
+ },
|
|
+ .pixel_rate = GC2145_1280_720_PIXELRATE,
|
|
+ },
|
|
+ {
|
|
+ /* 1600x1200 20fps mode */
|
|
+ .width = 1600,
|
|
+ .height = 1200,
|
|
+ .frame_interval = {
|
|
+ .numerator = 1,
|
|
+ .denominator = 20,
|
|
+ },
|
|
+ .reg_list = {
|
|
+ .num_of_regs = ARRAY_SIZE(mode_1600_1200_regs),
|
|
+ .regs = mode_1600_1200_regs,
|
|
+ },
|
|
+ .pixel_rate = GC2145_1600_1200_PIXELRATE,
|
|
+ },
|
|
+};
|
|
+
|
|
+/**
|
|
+ * struct gc2145_format - GC2145 pixel format description
|
|
+ * @code: media bus (MBUS) associated code
|
|
+ * @colorspace: V4L2 colospace
|
|
+ * @datatype: MIPI CSI2 data type
|
|
+ * @output_fmt: GC2145 output format
|
|
+ */
|
|
+struct gc2145_format {
|
|
+ unsigned int code;
|
|
+ unsigned int colorspace;
|
|
+ unsigned char datatype;
|
|
+ unsigned char output_fmt;
|
|
+};
|
|
+
|
|
+/* All supported formats */
|
|
+#define GC2145_DEFAULT_MBUS_FORMAT MEDIA_BUS_FMT_YUYV8_2X8
|
|
+static const struct gc2145_format supported_formats[] = {
|
|
+ {
|
|
+ .code = MEDIA_BUS_FMT_UYVY8_2X8,
|
|
+ .colorspace = V4L2_COLORSPACE_SRGB,
|
|
+ .datatype = MIPI_CSI2_DT_YUV422_8B,
|
|
+ .output_fmt = 0x00,
|
|
+ },
|
|
+ {
|
|
+ .code = MEDIA_BUS_FMT_VYUY8_2X8,
|
|
+ .colorspace = V4L2_COLORSPACE_SRGB,
|
|
+ .datatype = MIPI_CSI2_DT_YUV422_8B,
|
|
+ .output_fmt = 0x01,
|
|
+ },
|
|
+ {
|
|
+ .code = MEDIA_BUS_FMT_YUYV8_2X8,
|
|
+ .colorspace = V4L2_COLORSPACE_SRGB,
|
|
+ .datatype = MIPI_CSI2_DT_YUV422_8B,
|
|
+ .output_fmt = 0x02,
|
|
+ },
|
|
+ {
|
|
+ .code = MEDIA_BUS_FMT_YVYU8_2X8,
|
|
+ .colorspace = V4L2_COLORSPACE_SRGB,
|
|
+ .datatype = MIPI_CSI2_DT_YUV422_8B,
|
|
+ .output_fmt = 0x03,
|
|
+ },
|
|
+ {
|
|
+ .code = MEDIA_BUS_FMT_RGB565_2X8_BE,
|
|
+ .colorspace = V4L2_COLORSPACE_SRGB,
|
|
+ .datatype = MIPI_CSI2_DT_RGB565,
|
|
+ .output_fmt = 0x06,
|
|
+ },
|
|
+ {
|
|
+ .code = MEDIA_BUS_FMT_SRGGB8_1X8,
|
|
+ .colorspace = V4L2_COLORSPACE_RAW,
|
|
+ .datatype = MIPI_CSI2_DT_RAW8,
|
|
+ .output_fmt = 0x19, /* Image is taken out of the Lens correction */
|
|
+ },
|
|
+};
|
|
+
|
|
+struct gc2145 {
|
|
+ struct v4l2_subdev sd;
|
|
+ struct media_pad pad;
|
|
+
|
|
+ struct v4l2_mbus_framefmt fmt;
|
|
+
|
|
+ struct clk *xclk;
|
|
+
|
|
+ struct gpio_desc *reset_gpio;
|
|
+ struct gpio_desc *powerdown_gpio;
|
|
+ struct regulator_bulk_data supplies[GC2145_NUM_SUPPLIES];
|
|
+
|
|
+ struct v4l2_ctrl_handler ctrl_handler;
|
|
+ /* V4L2 Controls */
|
|
+ struct v4l2_ctrl *pixel_rate;
|
|
+
|
|
+ /* Current mode */
|
|
+ const struct gc2145_mode *mode;
|
|
+
|
|
+ /*
|
|
+ * Mutex for serialized access:
|
|
+ * Protect sensor module set pad format and start/stop streaming safely.
|
|
+ */
|
|
+ struct mutex mutex;
|
|
+
|
|
+ /* Streaming on/off */
|
|
+ bool streaming;
|
|
+};
|
|
+
|
|
+static inline struct gc2145 *to_gc2145(struct v4l2_subdev *_sd)
|
|
+{
|
|
+ return container_of(_sd, struct gc2145, sd);
|
|
+}
|
|
+
|
|
+static int gc2145_read_reg(struct gc2145 *gc2145, u8 addr, u8 *data, int data_size)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&gc2145->sd);
|
|
+ struct i2c_msg msg[2];
|
|
+ int ret;
|
|
+
|
|
+ msg[0].addr = client->addr;
|
|
+ msg[0].flags = client->flags;
|
|
+ msg[0].buf = &addr;
|
|
+ msg[0].len = 1;
|
|
+
|
|
+ msg[1].addr = client->addr;
|
|
+ msg[1].flags = client->flags | I2C_M_RD;
|
|
+ msg[1].buf = data;
|
|
+ msg[1].len = data_size;
|
|
+
|
|
+ ret = i2c_transfer(client->adapter, msg, 2);
|
|
+ if (ret < 0) {
|
|
+ dev_err(&client->dev, "%s: error %d: start_index=%x, data_size=%d\n",
|
|
+ __func__, ret, (u32)addr, data_size);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ dev_dbg(&client->dev, "[rd %02x] => %*ph\n", (u32)addr, data_size, data);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int gc2145_write_reg(struct gc2145 *gc2145, u8 addr, u8 data)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&gc2145->sd);
|
|
+ struct i2c_msg msg;
|
|
+ u8 buf[] = { addr, data };
|
|
+ int ret;
|
|
+
|
|
+ msg.addr = client->addr;
|
|
+ msg.flags = client->flags;
|
|
+ msg.buf = buf;
|
|
+ msg.len = sizeof(buf);
|
|
+
|
|
+ dev_dbg(&client->dev, "[wr %02x] <= %02xh\n", (u32)addr, data);
|
|
+
|
|
+ ret = i2c_transfer(client->adapter, &msg, 1);
|
|
+ if (ret < 0) {
|
|
+ dev_err(&client->dev, "%s: error %d: addr=%x, data=%02xh\n",
|
|
+ __func__, ret, (u32)addr, data);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Write a list of registers */
|
|
+static int gc2145_write_regs(struct gc2145 *gc2145, const struct gc2145_reg *regs, u32 len)
|
|
+{
|
|
+ unsigned int i;
|
|
+ int ret;
|
|
+
|
|
+ for (i = 0; i < len; i++) {
|
|
+ ret = gc2145_write_reg(gc2145, regs[i].address, regs[i].val);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct gc2145_format *gc2145_get_format_code(struct gc2145 *gc2145, u32 code)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(supported_formats); i++)
|
|
+ if (supported_formats[i].code == code)
|
|
+ break;
|
|
+
|
|
+ if (i >= ARRAY_SIZE(supported_formats))
|
|
+ i = 0;
|
|
+
|
|
+ return &supported_formats[i];
|
|
+}
|
|
+
|
|
+static void gc2145_set_default_format(struct gc2145 *gc2145)
|
|
+{
|
|
+ struct v4l2_mbus_framefmt *fmt = &gc2145->fmt;
|
|
+
|
|
+ fmt->code = supported_formats[0].code;
|
|
+ fmt->colorspace = supported_formats[0].colorspace;
|
|
+ fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
|
|
+ fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, fmt->colorspace, fmt->ycbcr_enc);
|
|
+ fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
|
|
+ fmt->width = supported_modes[GC2145_MODE_640X480].width;
|
|
+ fmt->height = supported_modes[GC2145_MODE_640X480].height;
|
|
+ fmt->field = V4L2_FIELD_NONE;
|
|
+}
|
|
+
|
|
+static int gc2145_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
|
|
+{
|
|
+ struct gc2145 *gc2145 = to_gc2145(sd);
|
|
+ struct v4l2_mbus_framefmt *try_fmt = v4l2_subdev_get_try_format(sd, fh->state, 0);
|
|
+
|
|
+ mutex_lock(&gc2145->mutex);
|
|
+
|
|
+ /* Initialize try_fmt */
|
|
+ try_fmt->width = supported_modes[GC2145_MODE_640X480].width;
|
|
+ try_fmt->height = supported_modes[GC2145_MODE_640X480].height;
|
|
+ try_fmt->code = GC2145_DEFAULT_MBUS_FORMAT;
|
|
+ try_fmt->field = V4L2_FIELD_NONE;
|
|
+
|
|
+ mutex_unlock(&gc2145->mutex);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int gc2145_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_mbus_code_enum *code)
|
|
+{
|
|
+ struct gc2145 *gc2145 = to_gc2145(sd);
|
|
+
|
|
+ if (code->index >= ARRAY_SIZE(supported_formats))
|
|
+ return -EINVAL;
|
|
+
|
|
+ mutex_lock(&gc2145->mutex);
|
|
+ code->code = supported_formats[code->index].code;
|
|
+ mutex_unlock(&gc2145->mutex);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int gc2145_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_frame_size_enum *fse)
|
|
+{
|
|
+ struct gc2145 *gc2145 = to_gc2145(sd);
|
|
+ const struct gc2145_format *gc2145_format;
|
|
+ u32 code;
|
|
+
|
|
+ if (fse->index >= ARRAY_SIZE(supported_modes))
|
|
+ return -EINVAL;
|
|
+
|
|
+ mutex_lock(&gc2145->mutex);
|
|
+
|
|
+ gc2145_format = gc2145_get_format_code(gc2145, fse->code);
|
|
+ code = gc2145_format->code;
|
|
+
|
|
+ mutex_unlock(&gc2145->mutex);
|
|
+ if (fse->code != code)
|
|
+ return -EINVAL;
|
|
+
|
|
+ fse->min_width = supported_modes[fse->index].width;
|
|
+ fse->max_width = fse->min_width;
|
|
+ fse->min_height = supported_modes[fse->index].height;
|
|
+ fse->max_height = fse->min_height;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void gc2145_reset_colorspace(struct v4l2_mbus_framefmt *fmt)
|
|
+{
|
|
+ fmt->colorspace = V4L2_COLORSPACE_SRGB;
|
|
+ fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
|
|
+ fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, fmt->colorspace,
|
|
+ fmt->ycbcr_enc);
|
|
+ fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
|
|
+}
|
|
+
|
|
+static void gc2145_update_pad_format(struct gc2145 *gc2145, const struct gc2145_mode *mode,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ fmt->format.width = mode->width;
|
|
+ fmt->format.height = mode->height;
|
|
+ fmt->format.field = V4L2_FIELD_NONE;
|
|
+ gc2145_reset_colorspace(&fmt->format);
|
|
+}
|
|
+
|
|
+static int __gc2145_get_pad_format(struct gc2145 *gc2145, struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
|
|
+ struct v4l2_mbus_framefmt *try_fmt =
|
|
+ v4l2_subdev_get_try_format(&gc2145->sd, sd_state, fmt->pad);
|
|
+ fmt->format = *try_fmt;
|
|
+ } else {
|
|
+ gc2145_update_pad_format(gc2145, gc2145->mode, fmt);
|
|
+ fmt->format.code = gc2145->fmt.code;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int gc2145_get_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ struct gc2145 *gc2145 = to_gc2145(sd);
|
|
+ int ret;
|
|
+
|
|
+ mutex_lock(&gc2145->mutex);
|
|
+ ret = __gc2145_get_pad_format(gc2145, sd_state, fmt);
|
|
+ mutex_unlock(&gc2145->mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int gc2145_set_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ struct gc2145 *gc2145 = to_gc2145(sd);
|
|
+ const struct gc2145_mode *mode;
|
|
+ const struct gc2145_format *gc2145_fmt;
|
|
+ struct v4l2_mbus_framefmt *framefmt;
|
|
+
|
|
+ mutex_lock(&gc2145->mutex);
|
|
+
|
|
+ gc2145_fmt = gc2145_get_format_code(gc2145, fmt->format.code);
|
|
+ fmt->format.code = gc2145_fmt->code;
|
|
+
|
|
+ mode = v4l2_find_nearest_size(supported_modes, ARRAY_SIZE(supported_modes), width, height,
|
|
+ fmt->format.width, fmt->format.height);
|
|
+
|
|
+ /* In RAW mode, VGA is not possible so use 720p instead */
|
|
+ if ((gc2145_fmt->colorspace == V4L2_COLORSPACE_RAW) &&
|
|
+ (mode == &supported_modes[GC2145_MODE_640X480]))
|
|
+ mode = &supported_modes[GC2145_MODE_1280X720];
|
|
+
|
|
+ gc2145_update_pad_format(gc2145, mode, fmt);
|
|
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
|
|
+ framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
|
|
+ *framefmt = fmt->format;
|
|
+ } else if (gc2145->mode != mode || gc2145->fmt.code != fmt->format.code) {
|
|
+ gc2145->fmt = fmt->format;
|
|
+ gc2145->mode = mode;
|
|
+ /* Update pixel_rate based on the mode */
|
|
+ __v4l2_ctrl_s_ctrl_int64(gc2145->pixel_rate, mode->pixel_rate);
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&gc2145->mutex);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int gc2145_start_streaming(struct gc2145 *gc2145)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&gc2145->sd);
|
|
+ const struct gc2145_reg_list *reg_list;
|
|
+ const struct gc2145_format *gc2145_format;
|
|
+ uint16_t lwc, fifo_full_lvl, fifo_gate_mode;
|
|
+ int ret;
|
|
+
|
|
+ ret = pm_runtime_resume_and_get(&client->dev);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /*
|
|
+ * TODO - configuration of the sensor will have to be moved into
|
|
+ * gc2145_set_pad_format once we have a way to configure without
|
|
+ * starting the sensor. In such case, only streaming start will
|
|
+ * be done here.
|
|
+ */
|
|
+
|
|
+ /* Apply default values of current mode */
|
|
+ reg_list = &gc2145->mode->reg_list;
|
|
+ ret = gc2145_write_regs(gc2145, reg_list->regs, reg_list->num_of_regs);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "%s failed to set mode\n", __func__);
|
|
+ goto err_rpm_put;
|
|
+ }
|
|
+
|
|
+ gc2145_format = gc2145_get_format_code(gc2145, gc2145->fmt.code);
|
|
+
|
|
+ /* Set the output format */
|
|
+ ret = gc2145_write_reg(gc2145, GC2145_REG_PAGE_SELECT, 0x00);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = gc2145_write_reg(gc2145, GC2145_REG_OUTPUT_FMT, gc2145_format->output_fmt);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Set 3rd page access */
|
|
+ ret = gc2145_write_reg(gc2145, GC2145_REG_PAGE_SELECT, 0x03);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /*
|
|
+ * Adjust the MIPI buffer settings.
|
|
+ * For YUV/RGB, LWC = image width * 2
|
|
+ * For RAW8, LWC = image width
|
|
+ * For RAW10, LWC = image width * 1.25
|
|
+ */
|
|
+ if (gc2145_format->colorspace != V4L2_COLORSPACE_RAW)
|
|
+ lwc = gc2145->mode->width * 2;
|
|
+ else if (gc2145_format->code == MEDIA_BUS_FMT_SRGGB8_1X8)
|
|
+ lwc = gc2145->mode->width * 1;
|
|
+ else
|
|
+ lwc = gc2145->mode->width + (gc2145->mode->width / 4);
|
|
+
|
|
+ ret = gc2145_write_reg(gc2145, GC2145_REG_LWC_HIGH, lwc >> 8);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ ret = gc2145_write_reg(gc2145, GC2145_REG_LWC_LOW, (lwc & 0xff));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /*
|
|
+ * Adjust the MIPI Fifo Full Level
|
|
+ * TODO - would need to understand better the constraints
|
|
+ * 640x480 RGB: 0x0190
|
|
+ * 1280x720 / 1600x1200 (aka no scaler) non RAW: 0x0010
|
|
+ * 1600x1200 RAW: 0x0190
|
|
+ */
|
|
+ if (gc2145_format->colorspace != V4L2_COLORSPACE_RAW) {
|
|
+ if ((gc2145->mode->width == 720) || (gc2145->mode->width == 1200))
|
|
+ fifo_full_lvl = 0x0010;
|
|
+ else
|
|
+ fifo_full_lvl = 0x0190;
|
|
+ } else
|
|
+ fifo_full_lvl = 0x0190;
|
|
+
|
|
+ ret = gc2145_write_reg(gc2145, GC2145_REG_FIFO_FULL_LVL_HIGH,
|
|
+ fifo_full_lvl >> 8);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ ret = gc2145_write_reg(gc2145, GC2145_REG_FIFO_FULL_LVL_LOW,
|
|
+ fifo_full_lvl & 0xff);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Set the fifo gate mode / MIPI wdiv set */
|
|
+ if (gc2145_format->colorspace == V4L2_COLORSPACE_RAW)
|
|
+ fifo_gate_mode = 0xf1;
|
|
+ else
|
|
+ fifo_gate_mode = 0xf0;
|
|
+ ret = gc2145_write_reg(gc2145, GC2145_REG_FIFO_GATE_MODE,
|
|
+ fifo_gate_mode);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Set the MIPI data type */
|
|
+ ret = gc2145_write_reg(gc2145, GC2145_REG_MIPI_DT, gc2145_format->datatype);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Apply customized values from user */
|
|
+ ret = __v4l2_ctrl_handler_setup(gc2145->sd.ctrl_handler);
|
|
+ if (ret)
|
|
+ goto err_rpm_put;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_rpm_put:
|
|
+ pm_runtime_put(&client->dev);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void gc2145_stop_streaming(struct gc2145 *gc2145)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&gc2145->sd);
|
|
+
|
|
+ /*
|
|
+ * TODO - once we have a way to turn off only streaming of the
|
|
+ * sensor, we will have to do it here.
|
|
+ */
|
|
+
|
|
+ pm_runtime_put(&client->dev);
|
|
+}
|
|
+
|
|
+static int gc2145_g_frame_interval(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_frame_interval *fi)
|
|
+{
|
|
+ struct gc2145 *gc2145 = to_gc2145(sd);
|
|
+
|
|
+ mutex_lock(&gc2145->mutex);
|
|
+ fi->interval = gc2145->mode->frame_interval;
|
|
+ mutex_unlock(&gc2145->mutex);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int gc2145_set_stream(struct v4l2_subdev *sd, int enable)
|
|
+{
|
|
+ struct gc2145 *gc2145 = to_gc2145(sd);
|
|
+ int ret = 0;
|
|
+
|
|
+ mutex_lock(&gc2145->mutex);
|
|
+ if (gc2145->streaming == enable) {
|
|
+ mutex_unlock(&gc2145->mutex);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ if (enable) {
|
|
+ /*
|
|
+ * Apply default & customized values
|
|
+ * and then start streaming.
|
|
+ */
|
|
+ ret = gc2145_start_streaming(gc2145);
|
|
+ if (ret)
|
|
+ goto err_unlock;
|
|
+ } else {
|
|
+ gc2145_stop_streaming(gc2145);
|
|
+ }
|
|
+
|
|
+ gc2145->streaming = enable;
|
|
+
|
|
+ mutex_unlock(&gc2145->mutex);
|
|
+
|
|
+ return ret;
|
|
+
|
|
+err_unlock:
|
|
+ mutex_unlock(&gc2145->mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* Power/clock management functions */
|
|
+static int gc2145_power_on(struct device *dev)
|
|
+{
|
|
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
|
|
+ struct gc2145 *gc2145 = to_gc2145(sd);
|
|
+ int ret;
|
|
+
|
|
+ ret = regulator_bulk_enable(GC2145_NUM_SUPPLIES, gc2145->supplies);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "failed to enable regulators\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(gc2145->xclk);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "failed to enable clock\n");
|
|
+ goto reg_off;
|
|
+ }
|
|
+
|
|
+ /* TODO - Following delays should be tuned */
|
|
+ usleep_range(10000, 12000);
|
|
+ gpiod_set_value_cansleep(gc2145->powerdown_gpio, 0);
|
|
+ usleep_range(10000, 12000);
|
|
+ gpiod_set_value_cansleep(gc2145->reset_gpio, 0);
|
|
+ usleep_range(40000, 50000);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+reg_off:
|
|
+ regulator_bulk_disable(GC2145_NUM_SUPPLIES, gc2145->supplies);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int gc2145_power_off(struct device *dev)
|
|
+{
|
|
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
|
|
+ struct gc2145 *gc2145 = to_gc2145(sd);
|
|
+
|
|
+ gpiod_set_value_cansleep(gc2145->powerdown_gpio, 1);
|
|
+ gpiod_set_value_cansleep(gc2145->reset_gpio, 1);
|
|
+ regulator_bulk_disable(GC2145_NUM_SUPPLIES, gc2145->supplies);
|
|
+ clk_disable_unprepare(gc2145->xclk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __maybe_unused gc2145_suspend(struct device *dev)
|
|
+{
|
|
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
|
|
+ struct gc2145 *gc2145 = to_gc2145(sd);
|
|
+
|
|
+ if (gc2145->streaming)
|
|
+ gc2145_stop_streaming(gc2145);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __maybe_unused gc2145_resume(struct device *dev)
|
|
+{
|
|
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
|
|
+ struct gc2145 *gc2145 = to_gc2145(sd);
|
|
+ int ret;
|
|
+
|
|
+ if (gc2145->streaming) {
|
|
+ ret = gc2145_start_streaming(gc2145);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+error:
|
|
+ gc2145_stop_streaming(gc2145);
|
|
+ gc2145->streaming = false;
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int gc2145_get_regulators(struct gc2145 *gc2145)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&gc2145->sd);
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < GC2145_NUM_SUPPLIES; i++)
|
|
+ gc2145->supplies[i].supply = gc2145_supply_name[i];
|
|
+
|
|
+ return devm_regulator_bulk_get(&client->dev, GC2145_NUM_SUPPLIES, gc2145->supplies);
|
|
+}
|
|
+
|
|
+/* Verify chip ID */
|
|
+static int gc2145_identify_module(struct gc2145 *gc2145)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&gc2145->sd);
|
|
+ int ret;
|
|
+ u16 chip_id;
|
|
+
|
|
+ ret = gc2145_read_reg(gc2145, GC2145_REG_CHIP_ID, (u8 *)&chip_id, 2);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "failed to read chip id %x\n", GC2145_CHIP_ID);
|
|
+ return ret;
|
|
+ }
|
|
+ chip_id = be16_to_cpu(chip_id);
|
|
+
|
|
+ if (chip_id != GC2145_CHIP_ID) {
|
|
+ dev_err(&client->dev, "chip id mismatch: %x!=%x\n", GC2145_CHIP_ID, chip_id);
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct v4l2_subdev_core_ops gc2145_core_ops = {
|
|
+ .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
|
|
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_video_ops gc2145_video_ops = {
|
|
+ .g_frame_interval = gc2145_g_frame_interval,
|
|
+ /*
|
|
+ * Currently frame_interval can't be changed hence s_frame_interval
|
|
+ * does same as g_frame_interval
|
|
+ */
|
|
+ .s_frame_interval = gc2145_g_frame_interval,
|
|
+ .s_stream = gc2145_set_stream,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_pad_ops gc2145_pad_ops = {
|
|
+ .enum_mbus_code = gc2145_enum_mbus_code,
|
|
+ .get_fmt = gc2145_get_pad_format,
|
|
+ .set_fmt = gc2145_set_pad_format,
|
|
+ .enum_frame_size = gc2145_enum_frame_size,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_ops gc2145_subdev_ops = {
|
|
+ .core = &gc2145_core_ops,
|
|
+ .video = &gc2145_video_ops,
|
|
+ .pad = &gc2145_pad_ops,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_internal_ops gc2145_internal_ops = {
|
|
+ .open = gc2145_open,
|
|
+};
|
|
+
|
|
+/* Initialize control handlers */
|
|
+static int gc2145_init_controls(struct gc2145 *gc2145)
|
|
+{
|
|
+ struct i2c_client *client = v4l2_get_subdevdata(&gc2145->sd);
|
|
+ struct v4l2_ctrl_handler *ctrl_hdlr;
|
|
+ int ret;
|
|
+
|
|
+ ctrl_hdlr = &gc2145->ctrl_handler;
|
|
+ ret = v4l2_ctrl_handler_init(ctrl_hdlr, 12);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ mutex_init(&gc2145->mutex);
|
|
+ ctrl_hdlr->lock = &gc2145->mutex;
|
|
+
|
|
+ /* By default, PIXEL_RATE is read only */
|
|
+ gc2145->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_PIXEL_RATE, 0, INT_MAX, 1,
|
|
+ GC2145_640_480_PIXELRATE);
|
|
+ gc2145->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
|
|
+
|
|
+ if (ctrl_hdlr->error) {
|
|
+ ret = ctrl_hdlr->error;
|
|
+ dev_err(&client->dev, "control init failed (%d)\n", ret);
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ gc2145->sd.ctrl_handler = ctrl_hdlr;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+error:
|
|
+ v4l2_ctrl_handler_free(ctrl_hdlr);
|
|
+ mutex_destroy(&gc2145->mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void gc2145_free_controls(struct gc2145 *gc2145)
|
|
+{
|
|
+ v4l2_ctrl_handler_free(gc2145->sd.ctrl_handler);
|
|
+ mutex_destroy(&gc2145->mutex);
|
|
+}
|
|
+
|
|
+static int gc2145_check_hwcfg(struct device *dev)
|
|
+{
|
|
+ struct fwnode_handle *endpoint;
|
|
+ struct v4l2_fwnode_endpoint ep_cfg = {
|
|
+ .bus_type = V4L2_MBUS_CSI2_DPHY
|
|
+ };
|
|
+ int ret = -EINVAL;
|
|
+
|
|
+ endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
|
|
+ if (!endpoint) {
|
|
+ dev_err(dev, "endpoint node not found\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) {
|
|
+ dev_err(dev, "could not parse endpoint\n");
|
|
+ goto error_out;
|
|
+ }
|
|
+
|
|
+ /* Check the number of MIPI CSI2 data lanes */
|
|
+ if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) {
|
|
+ dev_err(dev, "only 2 data lanes are currently supported\n");
|
|
+ goto error_out;
|
|
+ }
|
|
+
|
|
+ ret = 0;
|
|
+
|
|
+error_out:
|
|
+ v4l2_fwnode_endpoint_free(&ep_cfg);
|
|
+ fwnode_handle_put(endpoint);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int gc2145_probe(struct i2c_client *client)
|
|
+{
|
|
+ struct device *dev = &client->dev;
|
|
+ struct gc2145 *gc2145;
|
|
+ int ret;
|
|
+ unsigned int xclk_freq;
|
|
+
|
|
+ gc2145 = devm_kzalloc(&client->dev, sizeof(*gc2145), GFP_KERNEL);
|
|
+ if (!gc2145)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ v4l2_i2c_subdev_init(&gc2145->sd, client, &gc2145_subdev_ops);
|
|
+
|
|
+ /* Check the hardware configuration in device tree */
|
|
+ if (gc2145_check_hwcfg(dev))
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* Get system clock (xclk) */
|
|
+ gc2145->xclk = devm_clk_get(dev, NULL);
|
|
+ if (IS_ERR(gc2145->xclk)) {
|
|
+ dev_err(dev, "failed to get xclk\n");
|
|
+ return PTR_ERR(gc2145->xclk);
|
|
+ }
|
|
+
|
|
+ xclk_freq = clk_get_rate(gc2145->xclk);
|
|
+ if (xclk_freq != GC2145_XCLK_FREQ) {
|
|
+ dev_err(dev, "xclk frequency not supported: %d Hz\n", xclk_freq);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = gc2145_get_regulators(gc2145);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "failed to get regulators\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Request optional enable pin */
|
|
+ gc2145->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
|
|
+
|
|
+ /* Request optional enable pin */
|
|
+ gc2145->powerdown_gpio = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH);
|
|
+
|
|
+ /*
|
|
+ * The sensor must be powered for gc2145_identify_module()
|
|
+ * to be able to read the CHIP_ID register
|
|
+ */
|
|
+ ret = gc2145_power_on(dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = gc2145_identify_module(gc2145);
|
|
+ if (ret)
|
|
+ goto error_power_off;
|
|
+
|
|
+ /* Set default mode to max resolution */
|
|
+ gc2145->mode = &supported_modes[GC2145_MODE_1600X1200];
|
|
+
|
|
+ ret = gc2145_init_controls(gc2145);
|
|
+ if (ret)
|
|
+ goto error_power_off;
|
|
+
|
|
+ /* Initialize subdev */
|
|
+ gc2145->sd.internal_ops = &gc2145_internal_ops;
|
|
+ gc2145->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
|
|
+ gc2145->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
|
|
+
|
|
+ /* Initialize source pad */
|
|
+ gc2145->pad.flags = MEDIA_PAD_FL_SOURCE;
|
|
+
|
|
+ /* Initialize default format */
|
|
+ gc2145_set_default_format(gc2145);
|
|
+
|
|
+ ret = media_entity_pads_init(&gc2145->sd.entity, 1, &gc2145->pad);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "failed to init entity pads: %d\n", ret);
|
|
+ goto error_handler_free;
|
|
+ }
|
|
+
|
|
+ ret = v4l2_async_register_subdev_sensor(&gc2145->sd);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to register sensor sub-device: %d\n", ret);
|
|
+ goto error_media_entity;
|
|
+ }
|
|
+
|
|
+ /* Enable runtime PM and turn off the device */
|
|
+ pm_runtime_set_active(dev);
|
|
+ pm_runtime_enable(dev);
|
|
+ pm_runtime_idle(dev);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+error_media_entity:
|
|
+ media_entity_cleanup(&gc2145->sd.entity);
|
|
+
|
|
+error_handler_free:
|
|
+ gc2145_free_controls(gc2145);
|
|
+
|
|
+error_power_off:
|
|
+ gc2145_power_off(dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int gc2145_remove(struct i2c_client *client)
|
|
+{
|
|
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
|
|
+ struct gc2145 *gc2145 = to_gc2145(sd);
|
|
+
|
|
+ v4l2_async_unregister_subdev(sd);
|
|
+ media_entity_cleanup(&sd->entity);
|
|
+ gc2145_free_controls(gc2145);
|
|
+
|
|
+ pm_runtime_disable(&client->dev);
|
|
+ if (!pm_runtime_status_suspended(&client->dev))
|
|
+ gc2145_power_off(&client->dev);
|
|
+ pm_runtime_set_suspended(&client->dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id gc2145_dt_ids[] = {
|
|
+ { .compatible = "galaxycore,gc2145" },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, gc2145_dt_ids);
|
|
+
|
|
+static const struct dev_pm_ops gc2145_pm_ops = {
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(gc2145_suspend, gc2145_resume)
|
|
+ SET_RUNTIME_PM_OPS(gc2145_power_off, gc2145_power_on, NULL)
|
|
+};
|
|
+
|
|
+static struct i2c_driver gc2145_i2c_driver = {
|
|
+ .driver = {
|
|
+ .name = "gc2145",
|
|
+ .of_match_table = gc2145_dt_ids,
|
|
+ .pm = &gc2145_pm_ops,
|
|
+ },
|
|
+ .probe_new = gc2145_probe,
|
|
+ .remove = gc2145_remove,
|
|
+};
|
|
+
|
|
+module_i2c_driver(gc2145_i2c_driver);
|
|
+
|
|
+MODULE_AUTHOR("Alain Volmat <alain.volmat@foss.st.com");
|
|
+MODULE_DESCRIPTION("GalaxyCore GC2145 sensor driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c
|
|
index ddbd71394..551075a64 100644
|
|
--- a/drivers/media/i2c/ov5640.c
|
|
+++ b/drivers/media/i2c/ov5640.c
|
|
@@ -65,6 +65,7 @@
|
|
#define OV5640_REG_TIMING_VTS 0x380e
|
|
#define OV5640_REG_TIMING_TC_REG20 0x3820
|
|
#define OV5640_REG_TIMING_TC_REG21 0x3821
|
|
+#define OV5640_REG_DVP_PCLK_DIVIDER 0x3824
|
|
#define OV5640_REG_AEC_CTRL00 0x3a00
|
|
#define OV5640_REG_AEC_B50_STEP 0x3a08
|
|
#define OV5640_REG_AEC_B60_STEP 0x3a0a
|
|
@@ -88,6 +89,7 @@
|
|
#define OV5640_REG_POLARITY_CTRL00 0x4740
|
|
#define OV5640_REG_MIPI_CTRL00 0x4800
|
|
#define OV5640_REG_DEBUG_MODE 0x4814
|
|
+#define OV5640_REG_PCLK_PERIOD 0x4837
|
|
#define OV5640_REG_ISP_FORMAT_MUX_CTRL 0x501f
|
|
#define OV5640_REG_PRE_ISP_TEST_SET1 0x503d
|
|
#define OV5640_REG_SDE_CTRL0 0x5580
|
|
@@ -111,10 +113,11 @@ enum ov5640_mode_id {
|
|
OV5640_NUM_MODES,
|
|
};
|
|
|
|
+#define OV5640_DEFAULT_MODE OV5640_MODE_VGA_640_480
|
|
+
|
|
enum ov5640_frame_rate {
|
|
OV5640_15_FPS = 0,
|
|
OV5640_30_FPS,
|
|
- OV5640_60_FPS,
|
|
OV5640_NUM_FRAMERATES,
|
|
};
|
|
|
|
@@ -146,6 +149,19 @@ static const struct ov5640_pixfmt ov5640_formats[] = {
|
|
{ MEDIA_BUS_FMT_SRGGB8_1X8, V4L2_COLORSPACE_SRGB, },
|
|
};
|
|
|
|
+static const s64 ov5640_link_freqs[] = {
|
|
+ 63136800, /* 1280x720@15 */
|
|
+ 83954880, /* 176x144@15, 320x240@15, 720x480@15 */
|
|
+ 92145600, /* 640x480@15, 1024x768@15 */
|
|
+ 126273600, /* 1280x720@30 */
|
|
+ 167909760, /* 176x144@30, 320x240@30, 720x480@30 */
|
|
+ 184291200, /* 640x480@30, 1024x768@30 */
|
|
+ 191116800, /* 1920x1080@15 */
|
|
+ 335819520, /* 2592x1944@15 */
|
|
+ 382233600, /* 1920x1080@30 */
|
|
+};
|
|
+#define OV5640_LINK_FREQS_NUM ARRAY_SIZE(ov5640_link_freqs)
|
|
+
|
|
/*
|
|
* FIXME: remove this when a subdev API becomes available
|
|
* to set the MIPI CSI-2 virtual channel.
|
|
@@ -158,7 +174,6 @@ MODULE_PARM_DESC(virtual_channel,
|
|
static const int ov5640_framerates[] = {
|
|
[OV5640_15_FPS] = 15,
|
|
[OV5640_30_FPS] = 30,
|
|
- [OV5640_60_FPS] = 60,
|
|
};
|
|
|
|
/* regulator supplies */
|
|
@@ -222,6 +237,7 @@ struct ov5640_ctrls {
|
|
struct v4l2_ctrl *test_pattern;
|
|
struct v4l2_ctrl *hflip;
|
|
struct v4l2_ctrl *vflip;
|
|
+ struct v4l2_ctrl *link_freq;
|
|
};
|
|
|
|
struct ov5640_dev {
|
|
@@ -377,8 +393,8 @@ static const struct reg_value ov5640_setting_VGA_640_480[] = {
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
{0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0},
|
|
+ {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
static const struct reg_value ov5640_setting_XGA_1024_768[] = {
|
|
@@ -396,8 +412,7 @@ static const struct reg_value ov5640_setting_XGA_1024_768[] = {
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
{0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
static const struct reg_value ov5640_setting_QVGA_320_240[] = {
|
|
@@ -415,8 +430,7 @@ static const struct reg_value ov5640_setting_QVGA_320_240[] = {
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
{0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
static const struct reg_value ov5640_setting_QQVGA_160_120[] = {
|
|
@@ -452,8 +466,7 @@ static const struct reg_value ov5640_setting_QCIF_176_144[] = {
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
{0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
static const struct reg_value ov5640_setting_NTSC_720_480[] = {
|
|
@@ -471,8 +484,7 @@ static const struct reg_value ov5640_setting_NTSC_720_480[] = {
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
{0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
static const struct reg_value ov5640_setting_PAL_720_576[] = {
|
|
@@ -490,8 +502,7 @@ static const struct reg_value ov5640_setting_PAL_720_576[] = {
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
{0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
static const struct reg_value ov5640_setting_720P_1280_720[] = {
|
|
@@ -509,8 +520,7 @@ static const struct reg_value ov5640_setting_720P_1280_720[] = {
|
|
{0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x72, 0, 0}, {0x3a0e, 0x01, 0, 0},
|
|
{0x3a0d, 0x02, 0, 0}, {0x3a14, 0x02, 0, 0}, {0x3a15, 0xe4, 0, 0},
|
|
{0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0},
|
|
- {0x3824, 0x04, 0, 0}, {0x5001, 0x83, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
static const struct reg_value ov5640_setting_1080P_1920_1080[] = {
|
|
@@ -528,8 +538,8 @@ static const struct reg_value ov5640_setting_1080P_1920_1080[] = {
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
{0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0x83, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0},
|
|
+ {0x5001, 0x83, 0, 0},
|
|
{0x3c07, 0x07, 0, 0}, {0x3c08, 0x00, 0, 0},
|
|
{0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
{0x3800, 0x01, 0, 0}, {0x3801, 0x50, 0, 0}, {0x3802, 0x01, 0, 0},
|
|
@@ -540,7 +550,6 @@ static const struct reg_value ov5640_setting_1080P_1920_1080[] = {
|
|
{0x3a09, 0x50, 0, 0}, {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x18, 0, 0},
|
|
{0x3a0e, 0x03, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x04, 0, 0},
|
|
{0x3a15, 0x60, 0, 0}, {0x4407, 0x04, 0, 0},
|
|
- {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, {0x3824, 0x04, 0, 0},
|
|
{0x4005, 0x1a, 0, 0},
|
|
};
|
|
|
|
@@ -559,20 +568,31 @@ static const struct reg_value ov5640_setting_QSXGA_2592_1944[] = {
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
{0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0x83, 0, 70},
|
|
+ {0x4407, 0x04, 0, 0},
|
|
+ {0x5001, 0x83, 0, 70},
|
|
};
|
|
|
|
/* power-on sensor init reg table */
|
|
-static const struct ov5640_mode_info ov5640_mode_init_data = {
|
|
+static const struct ov5640_mode_info *ov5640_mode_init_data;
|
|
+
|
|
+static const struct ov5640_mode_info ov5640_mode_init_data_dvp = {
|
|
0, SUBSAMPLING, 640, 1896, 480, 984,
|
|
ov5640_init_setting_30fps_VGA,
|
|
ARRAY_SIZE(ov5640_init_setting_30fps_VGA),
|
|
OV5640_30_FPS,
|
|
};
|
|
|
|
+static const struct ov5640_mode_info ov5640_mode_init_data_csi2 = {
|
|
+ 0, SUBSAMPLING, 640, 2844, 480, 984,
|
|
+ ov5640_init_setting_30fps_VGA,
|
|
+ ARRAY_SIZE(ov5640_init_setting_30fps_VGA),
|
|
+ OV5640_30_FPS,
|
|
+};
|
|
+
|
|
+static const struct ov5640_mode_info *ov5640_mode_data;
|
|
+
|
|
static const struct ov5640_mode_info
|
|
-ov5640_mode_data[OV5640_NUM_MODES] = {
|
|
+ov5640_mode_data_dvp[OV5640_NUM_MODES] = {
|
|
{OV5640_MODE_QQVGA_160_120, SUBSAMPLING,
|
|
160, 1896, 120, 984,
|
|
ov5640_setting_QQVGA_160_120,
|
|
@@ -592,7 +612,7 @@ ov5640_mode_data[OV5640_NUM_MODES] = {
|
|
640, 1896, 480, 1080,
|
|
ov5640_setting_VGA_640_480,
|
|
ARRAY_SIZE(ov5640_setting_VGA_640_480),
|
|
- OV5640_60_FPS},
|
|
+ OV5640_30_FPS},
|
|
{OV5640_MODE_NTSC_720_480, SUBSAMPLING,
|
|
720, 1896, 480, 984,
|
|
ov5640_setting_NTSC_720_480,
|
|
@@ -625,6 +645,65 @@ ov5640_mode_data[OV5640_NUM_MODES] = {
|
|
OV5640_15_FPS},
|
|
};
|
|
|
|
+/*
|
|
+ * When using CSI-2 interface, adjusting htot to 2844
|
|
+ * gives better result in term of framerate, 720p support
|
|
+ * and overall stability with CSI-2 receiver
|
|
+ */
|
|
+static const struct ov5640_mode_info
|
|
+ov5640_mode_data_csi2[OV5640_NUM_MODES] = {
|
|
+ {OV5640_MODE_QQVGA_160_120, SUBSAMPLING,
|
|
+ 160, 2844, 120, 984,
|
|
+ ov5640_setting_QQVGA_160_120,
|
|
+ ARRAY_SIZE(ov5640_setting_QQVGA_160_120),
|
|
+ OV5640_30_FPS},
|
|
+ {OV5640_MODE_QCIF_176_144, SUBSAMPLING,
|
|
+ 176, 2844, 144, 984,
|
|
+ ov5640_setting_QCIF_176_144,
|
|
+ ARRAY_SIZE(ov5640_setting_QCIF_176_144),
|
|
+ OV5640_30_FPS},
|
|
+ {OV5640_MODE_QVGA_320_240, SUBSAMPLING,
|
|
+ 320, 2844, 240, 984,
|
|
+ ov5640_setting_QVGA_320_240,
|
|
+ ARRAY_SIZE(ov5640_setting_QVGA_320_240),
|
|
+ OV5640_30_FPS},
|
|
+ {OV5640_MODE_VGA_640_480, SUBSAMPLING,
|
|
+ 640, 2844, 480, 1080,
|
|
+ ov5640_setting_VGA_640_480,
|
|
+ ARRAY_SIZE(ov5640_setting_VGA_640_480),
|
|
+ OV5640_30_FPS},
|
|
+ {OV5640_MODE_NTSC_720_480, SUBSAMPLING,
|
|
+ 720, 2844, 480, 984,
|
|
+ ov5640_setting_NTSC_720_480,
|
|
+ ARRAY_SIZE(ov5640_setting_NTSC_720_480),
|
|
+ OV5640_30_FPS},
|
|
+ {OV5640_MODE_PAL_720_576, SUBSAMPLING,
|
|
+ 720, 2844, 576, 984,
|
|
+ ov5640_setting_PAL_720_576,
|
|
+ ARRAY_SIZE(ov5640_setting_PAL_720_576),
|
|
+ OV5640_30_FPS},
|
|
+ {OV5640_MODE_XGA_1024_768, SUBSAMPLING,
|
|
+ 1024, 2844, 768, 1080,
|
|
+ ov5640_setting_XGA_1024_768,
|
|
+ ARRAY_SIZE(ov5640_setting_XGA_1024_768),
|
|
+ OV5640_30_FPS},
|
|
+ {OV5640_MODE_720P_1280_720, SUBSAMPLING,
|
|
+ 1280, 2844, 720, 740,
|
|
+ ov5640_setting_720P_1280_720,
|
|
+ ARRAY_SIZE(ov5640_setting_720P_1280_720),
|
|
+ OV5640_30_FPS},
|
|
+ {OV5640_MODE_1080P_1920_1080, SCALING,
|
|
+ 1920, 2844, 1080, 1120,
|
|
+ ov5640_setting_1080P_1920_1080,
|
|
+ ARRAY_SIZE(ov5640_setting_1080P_1920_1080),
|
|
+ OV5640_30_FPS},
|
|
+ {OV5640_MODE_QSXGA_2592_1944, SCALING,
|
|
+ 2592, 2844, 1944, 1968,
|
|
+ ov5640_setting_QSXGA_2592_1944,
|
|
+ ARRAY_SIZE(ov5640_setting_QSXGA_2592_1944),
|
|
+ OV5640_15_FPS},
|
|
+};
|
|
+
|
|
static int ov5640_init_slave_id(struct ov5640_dev *sensor)
|
|
{
|
|
struct i2c_client *client = sensor->i2c_client;
|
|
@@ -946,66 +1025,81 @@ static unsigned long ov5640_calc_sys_clk(struct ov5640_dev *sensor,
|
|
* ov5640_set_mipi_pclk() - Calculate the clock tree configuration values
|
|
* for the MIPI CSI-2 output.
|
|
*
|
|
- * @rate: The requested bandwidth per lane in bytes per second.
|
|
- * 'Bandwidth Per Lane' is calculated as:
|
|
- * bpl = HTOT * VTOT * FPS * bpp / num_lanes;
|
|
- *
|
|
- * This function use the requested bandwidth to calculate:
|
|
- * - sample_rate = bpl / (bpp / num_lanes);
|
|
- * = bpl / (PLL_RDIV * BIT_DIV * PCLK_DIV * MIPI_DIV / num_lanes);
|
|
- *
|
|
- * - mipi_sclk = bpl / MIPI_DIV / 2; ( / 2 is for CSI-2 DDR)
|
|
- *
|
|
- * with these fixed parameters:
|
|
- * PLL_RDIV = 2;
|
|
- * BIT_DIVIDER = 2; (MIPI_BIT_MODE == 8 ? 2 : 2,5);
|
|
- * PCLK_DIV = 1;
|
|
- *
|
|
- * The MIPI clock generation differs for modes that use the scaler and modes
|
|
- * that do not. In case the scaler is in use, the MIPI_SCLK generates the MIPI
|
|
- * BIT CLk, and thus:
|
|
- *
|
|
- * - mipi_sclk = bpl / MIPI_DIV / 2;
|
|
- * MIPI_DIV = 1;
|
|
- *
|
|
- * For modes that do not go through the scaler, the MIPI BIT CLOCK is generated
|
|
- * from the pixel clock, and thus:
|
|
- *
|
|
- * - sample_rate = bpl / (bpp / num_lanes);
|
|
- * = bpl / (2 * 2 * 1 * MIPI_DIV / num_lanes);
|
|
- * = bpl / (4 * MIPI_DIV / num_lanes);
|
|
- * - MIPI_DIV = bpp / (4 * num_lanes);
|
|
- *
|
|
* FIXME: this have been tested with 16bpp and 2 lanes setup only.
|
|
- * MIPI_DIV is fixed to value 2, but it -might- be changed according to the
|
|
* above formula for setups with 1 lane or image formats with different bpp.
|
|
- *
|
|
- * FIXME: this deviates from the sensor manual documentation which is quite
|
|
- * thin on the MIPI clock tree generation part.
|
|
*/
|
|
-static int ov5640_set_mipi_pclk(struct ov5640_dev *sensor,
|
|
- unsigned long rate)
|
|
+static int ov5640_set_mipi_pclk(struct ov5640_dev *sensor)
|
|
{
|
|
- const struct ov5640_mode_info *mode = sensor->current_mode;
|
|
+ u8 bit_div, mipi_div, pclk_div, sclk_div, sclk2x_div, root_div;
|
|
u8 prediv, mult, sysdiv;
|
|
- u8 mipi_div;
|
|
+ unsigned long link_freq;
|
|
+ unsigned long sysclk;
|
|
+ u8 pclk_period;
|
|
int ret;
|
|
|
|
+ /* Get current link frequency */
|
|
+ link_freq = ov5640_link_freqs[sensor->ctrls.link_freq->val];
|
|
+
|
|
/*
|
|
- * 1280x720 is reported to use 'SUBSAMPLING' only,
|
|
- * but according to the sensor manual it goes through the
|
|
- * scaler before subsampling.
|
|
+ * - mipi_div - Assumptions not supported by documentation
|
|
+ *
|
|
+ * The MIPI clock generation differs for modes that use the scaler and
|
|
+ * modes that do not.
|
|
*/
|
|
- if (mode->dn_mode == SCALING ||
|
|
- (mode->id == OV5640_MODE_720P_1280_720))
|
|
- mipi_div = OV5640_MIPI_DIV_SCLK;
|
|
+ if (sensor->current_mode->dn_mode == SCALING)
|
|
+ mipi_div = 1;
|
|
else
|
|
- mipi_div = OV5640_MIPI_DIV_PCLK;
|
|
+ mipi_div = 2;
|
|
+
|
|
+ sysclk = link_freq * 2 * mipi_div;
|
|
+ ov5640_calc_sys_clk(sensor, sysclk, &prediv, &mult, &sysdiv);
|
|
+
|
|
+ /*
|
|
+ * Adjust PLL parameters to maintain the MIPI_SCLK-to-PCLK ratio;
|
|
+ *
|
|
+ * - root_div = 2 (fixed)
|
|
+ * - bit_div : MIPI 8-bit = 2
|
|
+ * MIPI 10-bit = 2,5
|
|
+ * - pclk_div = 1 (fixed)
|
|
+ * - pll_div = (2 lanes ? mipi_div : 2 * mipi_div)
|
|
+ * 2 lanes: MIPI_SCLK = (4 or 5) * PCLK
|
|
+ * 1 lanes: MIPI_SCLK = (8 or 10) * PCLK
|
|
+ *
|
|
+ * TODO: support 10-bit formats
|
|
+ * TODO: support 1 lane
|
|
+ */
|
|
+ root_div = OV5640_PLL_CTRL3_PLL_ROOT_DIV_2;
|
|
+ bit_div = OV5640_PLL_CTRL0_MIPI_MODE_8BIT;
|
|
+ pclk_div = OV5640_PLL_SYS_ROOT_DIVIDER_BYPASS;
|
|
+
|
|
+ /*
|
|
+ * Scaler clock:
|
|
+ * - YUV: PCLK >= 2 * SCLK
|
|
+ * - RAW or JPEG: PCLK >= SCLK
|
|
+ * - sclk2x_div = sclk_div / 2
|
|
+ *
|
|
+ * TODO: add support for RAW and JPEG modes. To maintain the
|
|
+ * SCLK to PCLK ratio, the sclk_div should probably be
|
|
+ * adjusted.
|
|
+ */
|
|
+ sclk_div = ilog2(OV5640_SCLK_ROOT_DIV);
|
|
+ sclk2x_div = ilog2(OV5640_SCLK2X_ROOT_DIV);
|
|
|
|
- ov5640_calc_sys_clk(sensor, rate, &prediv, &mult, &sysdiv);
|
|
+ /*
|
|
+ * This is called pclk period, but it actually represents the
|
|
+ * sample period expressed in ns with 1-bit decimal (0x01=0.5ns).
|
|
+ *
|
|
+ * - pclk = link_freq * 2 * lanes / bpp
|
|
+ *
|
|
+ * TODO: support 1 data lane; support modes with bpp != 16.
|
|
+ */
|
|
+ pclk_period = 2000000000UL / (link_freq / 2);
|
|
|
|
+ /* Program the clock tree registers. */
|
|
ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL0,
|
|
- 0x0f, OV5640_PLL_CTRL0_MIPI_MODE_8BIT);
|
|
+ 0x0f, bit_div);
|
|
+ if (ret)
|
|
+ return ret;
|
|
|
|
ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL1,
|
|
0xff, sysdiv << 4 | mipi_div);
|
|
@@ -1017,12 +1111,16 @@ static int ov5640_set_mipi_pclk(struct ov5640_dev *sensor,
|
|
return ret;
|
|
|
|
ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL3,
|
|
- 0x1f, OV5640_PLL_CTRL3_PLL_ROOT_DIV_2 | prediv);
|
|
+ 0x1f, root_div | prediv);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = ov5640_mod_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, 0x3f,
|
|
+ (pclk_div << 4) | (sclk2x_div << 2) | sclk_div);
|
|
if (ret)
|
|
return ret;
|
|
|
|
- return ov5640_mod_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER,
|
|
- 0x30, OV5640_PLL_SYS_ROOT_DIVIDER_BYPASS);
|
|
+ return ov5640_write_reg(sensor, OV5640_REG_PCLK_PERIOD, pclk_period);
|
|
}
|
|
|
|
static unsigned long ov5640_calc_pclk(struct ov5640_dev *sensor,
|
|
@@ -1042,11 +1140,68 @@ static unsigned long ov5640_calc_pclk(struct ov5640_dev *sensor,
|
|
return _rate / *pll_rdiv / *bit_div / *pclk_div;
|
|
}
|
|
|
|
-static int ov5640_set_dvp_pclk(struct ov5640_dev *sensor, unsigned long rate)
|
|
+static u64 ov5640_calc_pixel_rate(struct ov5640_dev *sensor)
|
|
+{
|
|
+ u64 rate;
|
|
+
|
|
+ rate = sensor->current_mode->vtot * sensor->current_mode->htot;
|
|
+ rate *= ov5640_framerates[sensor->current_fr];
|
|
+
|
|
+ return rate;
|
|
+}
|
|
+
|
|
+static int ov5640_set_dvp_pclk(struct ov5640_dev *sensor)
|
|
{
|
|
+ const struct ov5640_mode_info *mode = sensor->current_mode;
|
|
u8 prediv, mult, sysdiv, pll_rdiv, bit_div, pclk_div;
|
|
+ struct i2c_client *client = sensor->i2c_client;
|
|
+ unsigned int pclk_freq, max_pclk_freq;
|
|
+ u8 dvp_pclk_divider;
|
|
+ unsigned long rate;
|
|
int ret;
|
|
|
|
+ /* Get pixel rate */
|
|
+ rate = ov5640_calc_pixel_rate(sensor);
|
|
+
|
|
+ dev_dbg(&client->dev, "pixel_rate=%lu selected (%dx%d@%d)(%dx%d)\n",
|
|
+ rate,
|
|
+ sensor->current_mode->hact,
|
|
+ sensor->current_mode->vact,
|
|
+ ov5640_framerates[sensor->current_fr],
|
|
+ sensor->current_mode->htot,
|
|
+ sensor->current_mode->vtot);
|
|
+
|
|
+ /*
|
|
+ * All the formats we support have 16 bits per pixel, seems to require
|
|
+ * the same rate than YUV, so we can just use 16 bpp all the time.
|
|
+ */
|
|
+ rate = rate * 16 / sensor->ep.bus.parallel.bus_width;
|
|
+
|
|
+ /*
|
|
+ * 1280x720 and 1024x768 are reported to use 'SUBSAMPLING' only,
|
|
+ * but they seems to go through the scaler before subsampling.
|
|
+ */
|
|
+ if (mode->dn_mode == SCALING ||
|
|
+ (mode->id == OV5640_MODE_720P_1280_720) ||
|
|
+ (mode->id == OV5640_MODE_XGA_1024_768))
|
|
+ dvp_pclk_divider = 1;
|
|
+ else
|
|
+ dvp_pclk_divider = 2;
|
|
+
|
|
+ ret = ov5640_write_reg(sensor, OV5640_REG_DVP_PCLK_DIVIDER,
|
|
+ dvp_pclk_divider);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ pclk_freq = rate / dvp_pclk_divider;
|
|
+ max_pclk_freq = sensor->ep.bus.parallel.pclk_max_frequency;
|
|
+
|
|
+ /* clip rate according to optional maximum pixel clock limit */
|
|
+ if (max_pclk_freq && (pclk_freq > max_pclk_freq)) {
|
|
+ rate = max_pclk_freq * dvp_pclk_divider;
|
|
+ dev_dbg(&client->dev, "DVP pixel clock too high (%d > %d Hz), reducing rate...\n",
|
|
+ pclk_freq, max_pclk_freq);
|
|
+ }
|
|
+
|
|
ov5640_calc_pclk(sensor, rate, &prediv, &mult, &sysdiv, &pll_rdiv,
|
|
&bit_div, &pclk_div);
|
|
|
|
@@ -1081,6 +1236,7 @@ static int ov5640_set_dvp_pclk(struct ov5640_dev *sensor, unsigned long rate)
|
|
(ilog2(pclk_div) << 4));
|
|
}
|
|
|
|
+#if 0
|
|
/* set JPEG framing sizes */
|
|
static int ov5640_set_jpeg_timings(struct ov5640_dev *sensor,
|
|
const struct ov5640_mode_info *mode)
|
|
@@ -1104,19 +1260,20 @@ static int ov5640_set_jpeg_timings(struct ov5640_dev *sensor,
|
|
|
|
return ov5640_write_reg16(sensor, OV5640_REG_VFIFO_VSIZE, mode->vact);
|
|
}
|
|
+#endif
|
|
|
|
/* download ov5640 settings to sensor through i2c */
|
|
static int ov5640_set_timings(struct ov5640_dev *sensor,
|
|
const struct ov5640_mode_info *mode)
|
|
{
|
|
int ret;
|
|
-
|
|
+#if 0
|
|
if (sensor->fmt.code == MEDIA_BUS_FMT_JPEG_1X8) {
|
|
ret = ov5640_set_jpeg_timings(sensor, mode);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
-
|
|
+#endif
|
|
ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_DVPHO, mode->hact);
|
|
if (ret < 0)
|
|
return ret;
|
|
@@ -1556,7 +1713,7 @@ ov5640_find_mode(struct ov5640_dev *sensor, enum ov5640_frame_rate fr,
|
|
const struct ov5640_mode_info *mode;
|
|
|
|
mode = v4l2_find_nearest_size(ov5640_mode_data,
|
|
- ARRAY_SIZE(ov5640_mode_data),
|
|
+ OV5640_NUM_MODES,
|
|
hact, vact,
|
|
width, height);
|
|
|
|
@@ -1571,16 +1728,6 @@ ov5640_find_mode(struct ov5640_dev *sensor, enum ov5640_frame_rate fr,
|
|
return mode;
|
|
}
|
|
|
|
-static u64 ov5640_calc_pixel_rate(struct ov5640_dev *sensor)
|
|
-{
|
|
- u64 rate;
|
|
-
|
|
- rate = sensor->current_mode->vtot * sensor->current_mode->htot;
|
|
- rate *= ov5640_framerates[sensor->current_fr];
|
|
-
|
|
- return rate;
|
|
-}
|
|
-
|
|
/*
|
|
* sensor changes between scaling and subsampling, go through
|
|
* exposure calculation
|
|
@@ -1762,7 +1909,6 @@ static int ov5640_set_mode(struct ov5640_dev *sensor)
|
|
enum ov5640_downsize_mode dn_mode, orig_dn_mode;
|
|
bool auto_gain = sensor->ctrls.auto_gain->val == 1;
|
|
bool auto_exp = sensor->ctrls.auto_exp->val == V4L2_EXPOSURE_AUTO;
|
|
- unsigned long rate;
|
|
int ret;
|
|
|
|
dn_mode = mode->dn_mode;
|
|
@@ -1781,17 +1927,10 @@ static int ov5640_set_mode(struct ov5640_dev *sensor)
|
|
goto restore_auto_gain;
|
|
}
|
|
|
|
- /*
|
|
- * All the formats we support have 16 bits per pixel, seems to require
|
|
- * the same rate than YUV, so we can just use 16 bpp all the time.
|
|
- */
|
|
- rate = ov5640_calc_pixel_rate(sensor) * 16;
|
|
if (sensor->ep.bus_type == V4L2_MBUS_CSI2_DPHY) {
|
|
- rate = rate / sensor->ep.bus.mipi_csi2.num_data_lanes;
|
|
- ret = ov5640_set_mipi_pclk(sensor, rate);
|
|
+ ret = ov5640_set_mipi_pclk(sensor);
|
|
} else {
|
|
- rate = rate / sensor->ep.bus.parallel.bus_width;
|
|
- ret = ov5640_set_dvp_pclk(sensor, rate);
|
|
+ ret = ov5640_set_dvp_pclk(sensor);
|
|
}
|
|
|
|
if (ret < 0)
|
|
@@ -1860,10 +1999,10 @@ static int ov5640_restore_mode(struct ov5640_dev *sensor)
|
|
int ret;
|
|
|
|
/* first load the initial register values */
|
|
- ret = ov5640_load_regs(sensor, &ov5640_mode_init_data);
|
|
+ ret = ov5640_load_regs(sensor, ov5640_mode_init_data);
|
|
if (ret < 0)
|
|
return ret;
|
|
- sensor->last_mode = &ov5640_mode_init_data;
|
|
+ sensor->last_mode = ov5640_mode_init_data;
|
|
|
|
ret = ov5640_mod_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, 0x3f,
|
|
(ilog2(OV5640_SCLK2X_ROOT_DIV) << 2) |
|
|
@@ -2198,12 +2337,12 @@ static int ov5640_try_frame_interval(struct ov5640_dev *sensor,
|
|
int i;
|
|
|
|
minfps = ov5640_framerates[OV5640_15_FPS];
|
|
- maxfps = ov5640_framerates[OV5640_60_FPS];
|
|
+ maxfps = ov5640_framerates[OV5640_30_FPS];
|
|
|
|
if (fi->numerator == 0) {
|
|
fi->denominator = maxfps;
|
|
fi->numerator = 1;
|
|
- rate = OV5640_60_FPS;
|
|
+ rate = OV5640_30_FPS;
|
|
goto find_mode;
|
|
}
|
|
|
|
@@ -2286,6 +2425,70 @@ static int ov5640_try_fmt_internal(struct v4l2_subdev *sd,
|
|
return 0;
|
|
}
|
|
|
|
+static int ov5640_set_link_freq_ctrl(struct ov5640_dev *sensor,
|
|
+ unsigned long rate)
|
|
+{
|
|
+ u8 mipi_div;
|
|
+ unsigned int i;
|
|
+ unsigned int ret;
|
|
+ unsigned long link_freq;
|
|
+ unsigned int freq_index;
|
|
+ struct i2c_client *client = sensor->i2c_client;
|
|
+
|
|
+ if (sensor->ep.bus_type != V4L2_MBUS_CSI2_DPHY)
|
|
+ return 0;
|
|
+
|
|
+ /*
|
|
+ * - mipi_div - Assumptions not supported by documentation
|
|
+ *
|
|
+ * The MIPI clock generation differs for modes that use the scaler and
|
|
+ * modes that do not.
|
|
+ */
|
|
+ if (sensor->current_mode->dn_mode == SCALING)
|
|
+ mipi_div = 1;
|
|
+ else
|
|
+ mipi_div = 2;
|
|
+
|
|
+ /*
|
|
+ * All the formats we support have 16 bits per pixel, seems to require
|
|
+ * the same rate than YUV, so we can just use 16 bpp all the time.
|
|
+ */
|
|
+ rate = rate * 16;
|
|
+
|
|
+ /*
|
|
+ * The 'rate' parameter is the bitrate = VTOT * HTOT * FPS * BPP
|
|
+ *
|
|
+ * Adjust it to represent the CSI-2 link frequency and use it to
|
|
+ * update the associated control.
|
|
+ */
|
|
+ link_freq = rate / sensor->ep.bus.mipi_csi2.num_data_lanes /
|
|
+ 2 / mipi_div;
|
|
+
|
|
+ freq_index = OV5640_LINK_FREQS_NUM - 1;
|
|
+ for (i = 0; i < OV5640_LINK_FREQS_NUM; ++i) {
|
|
+ if (ov5640_link_freqs[i] == link_freq) {
|
|
+ freq_index = i;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ WARN_ONCE(i == OV5640_LINK_FREQS_NUM,
|
|
+ "Link frequency %ld not supported", link_freq);
|
|
+
|
|
+ ret = __v4l2_ctrl_s_ctrl(sensor->ctrls.link_freq, freq_index);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ dev_dbg(&client->dev, "link_freq[%d]=%lld selected (%dx%d@%d)(%dx%d)\n",
|
|
+ freq_index, ov5640_link_freqs[freq_index],
|
|
+ sensor->current_mode->hact,
|
|
+ sensor->current_mode->vact,
|
|
+ ov5640_framerates[sensor->current_fr],
|
|
+ sensor->current_mode->htot,
|
|
+ sensor->current_mode->vtot);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
static int ov5640_set_fmt(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *sd_state,
|
|
struct v4l2_subdev_format *format)
|
|
@@ -2325,8 +2528,14 @@ static int ov5640_set_fmt(struct v4l2_subdev *sd,
|
|
if (mbus_fmt->code != sensor->fmt.code)
|
|
sensor->pending_fmt_change = true;
|
|
|
|
- __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate,
|
|
- ov5640_calc_pixel_rate(sensor));
|
|
+ if (sensor->pending_mode_change || sensor->pending_fmt_change) {
|
|
+ unsigned long rate = ov5640_calc_pixel_rate(sensor);
|
|
+
|
|
+ __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, rate);
|
|
+
|
|
+ ret = ov5640_set_link_freq_ctrl(sensor, rate);
|
|
+ }
|
|
+
|
|
out:
|
|
mutex_unlock(&sensor->lock);
|
|
return ret;
|
|
@@ -2746,6 +2955,7 @@ static int ov5640_init_controls(struct ov5640_dev *sensor)
|
|
const struct v4l2_ctrl_ops *ops = &ov5640_ctrl_ops;
|
|
struct ov5640_ctrls *ctrls = &sensor->ctrls;
|
|
struct v4l2_ctrl_handler *hdl = &ctrls->handler;
|
|
+ unsigned long rate;
|
|
int ret;
|
|
|
|
v4l2_ctrl_handler_init(hdl, 32);
|
|
@@ -2754,9 +2964,22 @@ static int ov5640_init_controls(struct ov5640_dev *sensor)
|
|
hdl->lock = &sensor->lock;
|
|
|
|
/* Clock related controls */
|
|
+ rate = ov5640_calc_pixel_rate(sensor);
|
|
ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE,
|
|
0, INT_MAX, 1,
|
|
- ov5640_calc_pixel_rate(sensor));
|
|
+ rate);
|
|
+
|
|
+ ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
|
|
+ OV5640_LINK_FREQS_NUM - 1,
|
|
+ 0, ov5640_link_freqs);
|
|
+ if (ctrls->link_freq)
|
|
+ ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
|
|
+
|
|
+ mutex_lock(&sensor->lock);
|
|
+ ret = ov5640_set_link_freq_ctrl(sensor, rate);
|
|
+ mutex_unlock(&sensor->lock);
|
|
+ if (ret)
|
|
+ goto free_ctrls;
|
|
|
|
/* Auto/manual white balance */
|
|
ctrls->auto_wb = v4l2_ctrl_new_std(hdl, ops,
|
|
@@ -2914,13 +3137,16 @@ static int ov5640_s_frame_interval(struct v4l2_subdev *sd,
|
|
|
|
if (mode != sensor->current_mode ||
|
|
frame_rate != sensor->current_fr) {
|
|
+ unsigned long rate;
|
|
+
|
|
sensor->current_fr = frame_rate;
|
|
sensor->frame_interval = fi->interval;
|
|
sensor->current_mode = mode;
|
|
sensor->pending_mode_change = true;
|
|
|
|
- __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate,
|
|
- ov5640_calc_pixel_rate(sensor));
|
|
+ rate = ov5640_calc_pixel_rate(sensor);
|
|
+ __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, rate);
|
|
+ ret = ov5640_set_link_freq_ctrl(sensor, rate);
|
|
}
|
|
out:
|
|
mutex_unlock(&sensor->lock);
|
|
@@ -3066,15 +3292,10 @@ static int ov5640_probe(struct i2c_client *client)
|
|
fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
|
|
fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
|
|
fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
|
|
- fmt->width = 640;
|
|
- fmt->height = 480;
|
|
fmt->field = V4L2_FIELD_NONE;
|
|
sensor->frame_interval.numerator = 1;
|
|
sensor->frame_interval.denominator = ov5640_framerates[OV5640_30_FPS];
|
|
sensor->current_fr = OV5640_30_FPS;
|
|
- sensor->current_mode =
|
|
- &ov5640_mode_data[OV5640_MODE_VGA_640_480];
|
|
- sensor->last_mode = sensor->current_mode;
|
|
|
|
sensor->ae_target = 52;
|
|
|
|
@@ -3115,6 +3336,20 @@ static int ov5640_probe(struct i2c_client *client)
|
|
return -EINVAL;
|
|
}
|
|
|
|
+ if (sensor->ep.bus_type == V4L2_MBUS_CSI2_DPHY) {
|
|
+ ov5640_mode_data = ov5640_mode_data_csi2;
|
|
+ ov5640_mode_init_data = &ov5640_mode_init_data_csi2;
|
|
+ } else {
|
|
+ ov5640_mode_data = ov5640_mode_data_dvp;
|
|
+ ov5640_mode_init_data = &ov5640_mode_init_data_dvp;
|
|
+ }
|
|
+
|
|
+ fmt->width = ov5640_mode_data[OV5640_DEFAULT_MODE].hact;
|
|
+ fmt->height = ov5640_mode_data[OV5640_DEFAULT_MODE].vact;
|
|
+ sensor->current_mode =
|
|
+ &ov5640_mode_data[OV5640_DEFAULT_MODE];
|
|
+ sensor->last_mode = sensor->current_mode;
|
|
+
|
|
/* get system clock (xclk) */
|
|
sensor->xclk = devm_clk_get(dev, "xclk");
|
|
if (IS_ERR(sensor->xclk)) {
|
|
diff --git a/drivers/media/i2c/st-mipid02.c b/drivers/media/i2c/st-mipid02.c
|
|
index f630b88cb..b69e5baaf 100644
|
|
--- a/drivers/media/i2c/st-mipid02.c
|
|
+++ b/drivers/media/i2c/st-mipid02.c
|
|
@@ -50,6 +50,7 @@
|
|
/* Bits definition for MIPID02_MODE_REG2 */
|
|
#define MODE_HSYNC_ACTIVE_HIGH BIT(1)
|
|
#define MODE_VSYNC_ACTIVE_HIGH BIT(2)
|
|
+#define MODE_PCLK_SAMPLE_RISING BIT(3)
|
|
/* Bits definition for MIPID02_DATA_SELECTION_CTRL */
|
|
#define SELECTION_MANUAL_DATA BIT(2)
|
|
#define SELECTION_MANUAL_WIDTH BIT(3)
|
|
@@ -63,7 +64,8 @@ static const u32 mipid02_supported_fmt_codes[] = {
|
|
MEDIA_BUS_FMT_SGRBG12_1X12, MEDIA_BUS_FMT_SRGGB12_1X12,
|
|
MEDIA_BUS_FMT_UYVY8_1X16, MEDIA_BUS_FMT_BGR888_1X24,
|
|
MEDIA_BUS_FMT_RGB565_2X8_LE, MEDIA_BUS_FMT_RGB565_2X8_BE,
|
|
- MEDIA_BUS_FMT_YUYV8_2X8, MEDIA_BUS_FMT_UYVY8_2X8,
|
|
+ MEDIA_BUS_FMT_YUYV8_2X8, MEDIA_BUS_FMT_YVYU8_2X8,
|
|
+ MEDIA_BUS_FMT_UYVY8_2X8, MEDIA_BUS_FMT_VYUY8_2X8,
|
|
MEDIA_BUS_FMT_JPEG_1X8
|
|
};
|
|
|
|
@@ -132,7 +134,9 @@ static int bpp_from_code(__u32 code)
|
|
return 12;
|
|
case MEDIA_BUS_FMT_UYVY8_1X16:
|
|
case MEDIA_BUS_FMT_YUYV8_2X8:
|
|
+ case MEDIA_BUS_FMT_YVYU8_2X8:
|
|
case MEDIA_BUS_FMT_UYVY8_2X8:
|
|
+ case MEDIA_BUS_FMT_VYUY8_2X8:
|
|
case MEDIA_BUS_FMT_RGB565_2X8_LE:
|
|
case MEDIA_BUS_FMT_RGB565_2X8_BE:
|
|
return 16;
|
|
@@ -163,7 +167,9 @@ static u8 data_type_from_code(__u32 code)
|
|
return 0x2c;
|
|
case MEDIA_BUS_FMT_UYVY8_1X16:
|
|
case MEDIA_BUS_FMT_YUYV8_2X8:
|
|
+ case MEDIA_BUS_FMT_YVYU8_2X8:
|
|
case MEDIA_BUS_FMT_UYVY8_2X8:
|
|
+ case MEDIA_BUS_FMT_VYUY8_2X8:
|
|
return 0x1e;
|
|
case MEDIA_BUS_FMT_BGR888_1X24:
|
|
return 0x24;
|
|
@@ -494,6 +500,8 @@ static int mipid02_configure_from_tx(struct mipid02_dev *bridge)
|
|
bridge->r.mode_reg2 |= MODE_HSYNC_ACTIVE_HIGH;
|
|
if (ep->bus.parallel.flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
|
|
bridge->r.mode_reg2 |= MODE_VSYNC_ACTIVE_HIGH;
|
|
+ if (ep->bus.parallel.flags & V4L2_MBUS_PCLK_SAMPLE_RISING)
|
|
+ bridge->r.mode_reg2 |= MODE_PCLK_SAMPLE_RISING;
|
|
|
|
return 0;
|
|
}
|
|
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
|
|
index 80321e038..94a4ba771 100644
|
|
--- a/drivers/media/platform/Kconfig
|
|
+++ b/drivers/media/platform/Kconfig
|
|
@@ -138,6 +138,19 @@ config VIDEO_STM32_DCMI
|
|
To compile this driver as a module, choose M here: the module
|
|
will be called stm32-dcmi.
|
|
|
|
+config VIDEO_STM32_DCMIPP
|
|
+ tristate "STM32 Digital Camera Memory Interface Pixel Processor (DCMIPP) support"
|
|
+ depends on VIDEO_V4L2 && OF && MEDIA_CONTROLLER
|
|
+ depends on ARCH_STM32 || COMPILE_TEST
|
|
+ select VIDEOBUF2_DMA_CONTIG
|
|
+ select V4L2_FWNODE
|
|
+ help
|
|
+ This module makes the STM32 Digital Camera Memory Interface
|
|
+ Pixel Processor (DCMIPP) available as a v4l2 device.
|
|
+
|
|
+ To compile this driver as a module, choose M here: the module
|
|
+ will be called stm32-dcmipp.
|
|
+
|
|
config VIDEO_RENESAS_CEU
|
|
tristate "Renesas Capture Engine Unit (CEU) driver"
|
|
depends on VIDEO_DEV && VIDEO_V4L2
|
|
diff --git a/drivers/media/platform/stm32/Makefile b/drivers/media/platform/stm32/Makefile
|
|
index 48b36db2c..a37eee4bf 100644
|
|
--- a/drivers/media/platform/stm32/Makefile
|
|
+++ b/drivers/media/platform/stm32/Makefile
|
|
@@ -1,2 +1,3 @@
|
|
# SPDX-License-Identifier: GPL-2.0-only
|
|
obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32-dcmi.o
|
|
+obj-$(CONFIG_VIDEO_STM32_DCMIPP) += stm32-dcmipp/
|
|
diff --git a/drivers/media/platform/stm32/stm32-dcmi.c b/drivers/media/platform/stm32/stm32-dcmi.c
|
|
index 611071864..c6d58e3ec 100644
|
|
--- a/drivers/media/platform/stm32/stm32-dcmi.c
|
|
+++ b/drivers/media/platform/stm32/stm32-dcmi.c
|
|
@@ -95,6 +95,9 @@ enum state {
|
|
#define MIN_HEIGHT 16U
|
|
#define MAX_HEIGHT 2592U
|
|
|
|
+/* DMA can sustain YUV 720p@15fps max */
|
|
+#define MAX_DMA_BANDWIDTH (1280 * 720 * 2 * 15)
|
|
+
|
|
#define TIMEOUT_MS 1000
|
|
|
|
#define OVERRUN_ERROR_THRESHOLD 3
|
|
@@ -113,7 +116,7 @@ struct dcmi_framesize {
|
|
struct dcmi_buf {
|
|
struct vb2_v4l2_buffer vb;
|
|
bool prepared;
|
|
- dma_addr_t paddr;
|
|
+ struct sg_table sgt;
|
|
size_t size;
|
|
struct list_head list;
|
|
};
|
|
@@ -157,6 +160,7 @@ struct stm32_dcmi {
|
|
enum state state;
|
|
struct dma_chan *dma_chan;
|
|
dma_cookie_t dma_cookie;
|
|
+ u32 dma_max_burst;
|
|
u32 misr;
|
|
int errors_count;
|
|
int overrun_count;
|
|
@@ -326,13 +330,12 @@ static int dcmi_start_dma(struct stm32_dcmi *dcmi,
|
|
mutex_lock(&dcmi->dma_lock);
|
|
|
|
/* Prepare a DMA transaction */
|
|
- desc = dmaengine_prep_slave_single(dcmi->dma_chan, buf->paddr,
|
|
- buf->size,
|
|
+ desc = dmaengine_prep_slave_sg(dcmi->dma_chan, buf->sgt.sgl,
|
|
+ buf->sgt.nents,
|
|
DMA_DEV_TO_MEM,
|
|
DMA_PREP_INTERRUPT);
|
|
if (!desc) {
|
|
- dev_err(dcmi->dev, "%s: DMA dmaengine_prep_slave_single failed for buffer phy=%pad size=%zu\n",
|
|
- __func__, &buf->paddr, buf->size);
|
|
+ dev_err(dcmi->dev, "%s: DMA dmaengine_prep_slave_sg failed\n", __func__);
|
|
mutex_unlock(&dcmi->dma_lock);
|
|
return -EINVAL;
|
|
}
|
|
@@ -470,17 +473,12 @@ static irqreturn_t dcmi_irq_thread(int irq, void *arg)
|
|
static irqreturn_t dcmi_irq_callback(int irq, void *arg)
|
|
{
|
|
struct stm32_dcmi *dcmi = arg;
|
|
- unsigned long flags;
|
|
-
|
|
- spin_lock_irqsave(&dcmi->irqlock, flags);
|
|
|
|
dcmi->misr = reg_read(dcmi->regs, DCMI_MIS);
|
|
|
|
/* Clear interrupt */
|
|
reg_set(dcmi->regs, DCMI_ICR, IT_FRAME | IT_OVR | IT_ERR);
|
|
|
|
- spin_unlock_irqrestore(&dcmi->irqlock, flags);
|
|
-
|
|
return IRQ_WAKE_THREAD;
|
|
}
|
|
|
|
@@ -524,6 +522,10 @@ static int dcmi_buf_prepare(struct vb2_buffer *vb)
|
|
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
|
struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb);
|
|
unsigned long size;
|
|
+ unsigned int num_sgs;
|
|
+ dma_addr_t dma_buf;
|
|
+ struct scatterlist *sg;
|
|
+ int i, ret;
|
|
|
|
size = dcmi->fmt.fmt.pix.sizeimage;
|
|
|
|
@@ -537,15 +539,35 @@ static int dcmi_buf_prepare(struct vb2_buffer *vb)
|
|
|
|
if (!buf->prepared) {
|
|
/* Get memory addresses */
|
|
- buf->paddr =
|
|
- vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
|
|
buf->size = vb2_plane_size(&buf->vb.vb2_buf, 0);
|
|
- buf->prepared = true;
|
|
+ if (buf->size <= dcmi->dma_max_burst)
|
|
+ num_sgs = 1;
|
|
+ else
|
|
+ num_sgs = DIV_ROUND_UP(buf->size, dcmi->dma_max_burst);
|
|
+
|
|
+ ret = sg_alloc_table(&buf->sgt, num_sgs, GFP_ATOMIC);
|
|
+ if (ret) {
|
|
+ dev_err(dcmi->dev, "sg table alloc failed\n");
|
|
+ return ret;
|
|
+ }
|
|
|
|
- vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size);
|
|
+ dma_buf = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
|
|
|
|
dev_dbg(dcmi->dev, "buffer[%d] phy=%pad size=%zu\n",
|
|
- vb->index, &buf->paddr, buf->size);
|
|
+ vb->index, &dma_buf, buf->size);
|
|
+
|
|
+ for_each_sg(buf->sgt.sgl, sg, num_sgs, i) {
|
|
+ size_t bytes = min_t(size_t, size, dcmi->dma_max_burst);
|
|
+
|
|
+ sg_dma_address(sg) = dma_buf;
|
|
+ sg_dma_len(sg) = bytes;
|
|
+ dma_buf += bytes;
|
|
+ size -= bytes;
|
|
+ }
|
|
+
|
|
+ buf->prepared = true;
|
|
+
|
|
+ vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size);
|
|
}
|
|
|
|
return 0;
|
|
@@ -579,6 +601,15 @@ static void dcmi_buf_queue(struct vb2_buffer *vb)
|
|
spin_unlock_irq(&dcmi->irqlock);
|
|
}
|
|
|
|
+static void dcmi_buf_cleanup(struct vb2_buffer *vb)
|
|
+{
|
|
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
|
+ struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb);
|
|
+
|
|
+ if (buf->prepared)
|
|
+ sg_free_table(&buf->sgt);
|
|
+}
|
|
+
|
|
static struct media_entity *dcmi_find_source(struct stm32_dcmi *dcmi)
|
|
{
|
|
struct media_entity *entity = &dcmi->vdev->entity;
|
|
@@ -742,6 +773,33 @@ static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count)
|
|
if (ret)
|
|
goto err_media_pipeline_stop;
|
|
|
|
+ /* Check if snapshop mode is necessary for jpeg capture */
|
|
+ if (dcmi->sd_format->fourcc == V4L2_PIX_FMT_JPEG) {
|
|
+ unsigned int rate;
|
|
+ struct v4l2_streamparm p = {
|
|
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE
|
|
+ };
|
|
+ struct v4l2_fract frame_interval = {1, 30};
|
|
+
|
|
+ ret = v4l2_g_parm_cap(dcmi->vdev, dcmi->source, &p);
|
|
+ if (!ret)
|
|
+ frame_interval = p.parm.capture.timeperframe;
|
|
+
|
|
+ rate = dcmi->fmt.fmt.pix.sizeimage *
|
|
+ frame_interval.denominator / frame_interval.numerator;
|
|
+
|
|
+ /*
|
|
+ * If rate exceed DMA capabilities, switch to snapshot mode
|
|
+ * to ensure that current DMA transfer is elapsed before
|
|
+ * capturing a new JPEG.
|
|
+ */
|
|
+ if (rate > MAX_DMA_BANDWIDTH) {
|
|
+ val |= CR_CM;
|
|
+ dev_dbg(dcmi->dev, "Capture rate is too high for continuous mode (%d > %d bytes/s), switch to snapshot mode\n",
|
|
+ rate, MAX_DMA_BANDWIDTH);
|
|
+ }
|
|
+ }
|
|
+
|
|
spin_lock_irq(&dcmi->irqlock);
|
|
|
|
/* Set bus width */
|
|
@@ -795,10 +853,6 @@ static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count)
|
|
if (dcmi->do_crop)
|
|
dcmi_set_crop(dcmi);
|
|
|
|
- /* Enable jpeg capture */
|
|
- if (dcmi->sd_format->fourcc == V4L2_PIX_FMT_JPEG)
|
|
- reg_set(dcmi->regs, DCMI_CR, CR_CM);/* Snapshot mode */
|
|
-
|
|
/* Enable dcmi */
|
|
reg_set(dcmi->regs, DCMI_CR, CR_ENABLE);
|
|
|
|
@@ -914,6 +968,7 @@ static const struct vb2_ops dcmi_video_qops = {
|
|
.buf_init = dcmi_buf_init,
|
|
.buf_prepare = dcmi_buf_prepare,
|
|
.buf_queue = dcmi_buf_queue,
|
|
+ .buf_cleanup = dcmi_buf_cleanup,
|
|
.start_streaming = dcmi_start_streaming,
|
|
.stop_streaming = dcmi_stop_streaming,
|
|
.wait_prepare = vb2_ops_wait_prepare,
|
|
@@ -1793,6 +1848,15 @@ static int dcmi_graph_notify_bound(struct v4l2_async_notifier *notifier,
|
|
|
|
dev_dbg(dcmi->dev, "Subdev \"%s\" bound\n", subdev->name);
|
|
|
|
+ ret = video_register_device(dcmi->vdev, VFL_TYPE_VIDEO, -1);
|
|
+ if (ret) {
|
|
+ dev_err(dcmi->dev, "Failed to register video device\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ dev_dbg(dcmi->dev, "Device registered as %s\n",
|
|
+ video_device_node_name(dcmi->vdev));
|
|
+
|
|
/*
|
|
* Link this sub-device to DCMI, it could be
|
|
* a parallel camera sensor or a bridge
|
|
@@ -1805,10 +1869,11 @@ static int dcmi_graph_notify_bound(struct v4l2_async_notifier *notifier,
|
|
&dcmi->vdev->entity, 0,
|
|
MEDIA_LNK_FL_IMMUTABLE |
|
|
MEDIA_LNK_FL_ENABLED);
|
|
- if (ret)
|
|
+ if (ret) {
|
|
dev_err(dcmi->dev, "Failed to create media pad link with subdev \"%s\"\n",
|
|
subdev->name);
|
|
- else
|
|
+ video_unregister_device(dcmi->vdev);
|
|
+ } else
|
|
dev_dbg(dcmi->dev, "DCMI is now linked to \"%s\"\n",
|
|
subdev->name);
|
|
|
|
@@ -1866,6 +1931,7 @@ static int dcmi_probe(struct platform_device *pdev)
|
|
struct stm32_dcmi *dcmi;
|
|
struct vb2_queue *q;
|
|
struct dma_chan *chan;
|
|
+ struct dma_slave_caps caps;
|
|
struct clk *mclk;
|
|
int irq;
|
|
int ret = 0;
|
|
@@ -1953,6 +2019,12 @@ static int dcmi_probe(struct platform_device *pdev)
|
|
return ret;
|
|
}
|
|
|
|
+ dcmi->dma_max_burst = UINT_MAX;
|
|
+ ret = dma_get_slave_caps(chan, &caps);
|
|
+ if (!ret && caps.max_sg_burst)
|
|
+ dcmi->dma_max_burst = caps.max_sg_burst *
|
|
+ DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
+
|
|
spin_lock_init(&dcmi->irqlock);
|
|
mutex_init(&dcmi->lock);
|
|
mutex_init(&dcmi->dma_lock);
|
|
@@ -2008,15 +2080,6 @@ static int dcmi_probe(struct platform_device *pdev)
|
|
}
|
|
dcmi->vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT;
|
|
|
|
- ret = video_register_device(dcmi->vdev, VFL_TYPE_VIDEO, -1);
|
|
- if (ret) {
|
|
- dev_err(dcmi->dev, "Failed to register video device\n");
|
|
- goto err_media_entity_cleanup;
|
|
- }
|
|
-
|
|
- dev_dbg(dcmi->dev, "Device registered as %s\n",
|
|
- video_device_node_name(dcmi->vdev));
|
|
-
|
|
/* Buffer queue */
|
|
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF;
|
|
@@ -2037,7 +2100,7 @@ static int dcmi_probe(struct platform_device *pdev)
|
|
|
|
ret = dcmi_graph_init(dcmi);
|
|
if (ret < 0)
|
|
- goto err_media_entity_cleanup;
|
|
+ goto err_vb2_queue_release;
|
|
|
|
/* Reset device */
|
|
ret = reset_control_assert(dcmi->rstc);
|
|
@@ -2063,7 +2126,10 @@ static int dcmi_probe(struct platform_device *pdev)
|
|
return 0;
|
|
|
|
err_cleanup:
|
|
+ v4l2_async_notifier_unregister(&dcmi->notifier);
|
|
v4l2_async_notifier_cleanup(&dcmi->notifier);
|
|
+err_vb2_queue_release:
|
|
+ vb2_queue_release(q);
|
|
err_media_entity_cleanup:
|
|
media_entity_cleanup(&dcmi->vdev->entity);
|
|
err_device_release:
|
|
@@ -2085,6 +2151,7 @@ static int dcmi_remove(struct platform_device *pdev)
|
|
|
|
v4l2_async_notifier_unregister(&dcmi->notifier);
|
|
v4l2_async_notifier_cleanup(&dcmi->notifier);
|
|
+ vb2_queue_release(&dcmi->queue);
|
|
media_entity_cleanup(&dcmi->vdev->entity);
|
|
v4l2_device_unregister(&dcmi->v4l2_dev);
|
|
media_device_cleanup(&dcmi->mdev);
|
|
diff --git a/drivers/media/platform/stm32/stm32-dcmipp/Makefile b/drivers/media/platform/stm32/stm32-dcmipp/Makefile
|
|
new file mode 100644
|
|
index 000000000..cbddc9814
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/stm32/stm32-dcmipp/Makefile
|
|
@@ -0,0 +1,5 @@
|
|
+# SPDX-License-Identifier: GPL-2.0
|
|
+stm32-dcmipp-y := dcmipp-core.o dcmipp-common.o
|
|
+
|
|
+obj-$(CONFIG_VIDEO_STM32_DCMIPP) += stm32-dcmipp.o
|
|
+obj-$(CONFIG_VIDEO_STM32_DCMIPP) += dcmipp-parallel.o dcmipp-byteproc.o dcmipp-bytecap.o
|
|
diff --git a/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-bytecap.c b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-bytecap.c
|
|
new file mode 100644
|
|
index 000000000..415766c08
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-bytecap.c
|
|
@@ -0,0 +1,1109 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Driver for STM32 Digital Camera Memory Interface Pixel Processor
|
|
+ *
|
|
+ * Copyright (C) STMicroelectronics SA 2021
|
|
+ * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
|
|
+ * Alain Volmat <alain.volmat@foss.st.com>
|
|
+ * for STMicroelectronics.
|
|
+ */
|
|
+
|
|
+#include <linux/component.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/iopoll.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mod_devicetable.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/reset.h>
|
|
+#include <media/v4l2-ioctl.h>
|
|
+#include <media/v4l2-mc.h>
|
|
+#include <media/videobuf2-core.h>
|
|
+#include <media/videobuf2-dma-contig.h>
|
|
+
|
|
+#include "dcmipp-common.h"
|
|
+
|
|
+#define DCMIPP_BYTECAP_DRV_NAME "dcmipp-bytecap"
|
|
+
|
|
+#define DCMIPP_PRSR (0x1F8)
|
|
+#define DCMIPP_CMIER (0x3F0)
|
|
+#define DCMIPP_CMIER_P0FRAMEIE BIT(9)
|
|
+#define DCMIPP_CMIER_P0VSYNCIE BIT(10)
|
|
+#define DCMIPP_CMIER_P0OVRIE BIT(15)
|
|
+#define DCMIPP_CMIER_P0ALL (DCMIPP_CMIER_P0VSYNCIE |\
|
|
+ DCMIPP_CMIER_P0FRAMEIE |\
|
|
+ DCMIPP_CMIER_P0OVRIE)
|
|
+#define DCMIPP_CMSR1 (0x3F4)
|
|
+#define DCMIPP_CMSR2 (0x3F8)
|
|
+#define DCMIPP_CMSR2_P0FRAMEF BIT(9)
|
|
+#define DCMIPP_CMSR2_P0VSYNCF BIT(10)
|
|
+#define DCMIPP_CMSR2_P0OVRF BIT(15)
|
|
+#define DCMIPP_CMFCR (0x3FC)
|
|
+#define DCMIPP_P0FSCR (0x404)
|
|
+#define DCMIPP_P0FSCR_PIPEN BIT(31)
|
|
+#define DCMIPP_P0FCTCR (0x500)
|
|
+#define DCMIPP_P0FCTCR_CPTREQ BIT(3)
|
|
+#define DCMIPP_P0DCCNTR (0x5B0)
|
|
+#define DCMIPP_P0DCLMTR (0x5B4)
|
|
+#define DCMIPP_P0DCLMTR_ENABLE BIT(31)
|
|
+#define DCMIPP_P0DCLMTR_LIMIT_MASK GENMASK(23, 0)
|
|
+#define DCMIPP_P0PPM0AR1 (0x5C4)
|
|
+#define DCMIPP_P0SR (0x5F8)
|
|
+#define DCMIPP_P0SR_CPTACT BIT(23)
|
|
+
|
|
+struct dcmipp_bytecap_pix_map {
|
|
+ unsigned int code;
|
|
+ u32 pixelformat;
|
|
+};
|
|
+
|
|
+#define PIXMAP_MBUS_PFMT(mbus, fmt) \
|
|
+ { \
|
|
+ .code = MEDIA_BUS_FMT_##mbus, \
|
|
+ .pixelformat = V4L2_PIX_FMT_##fmt \
|
|
+ }
|
|
+
|
|
+static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_pix_map_list[] = {
|
|
+ PIXMAP_MBUS_PFMT(RGB565_2X8_LE, RGB565),
|
|
+ PIXMAP_MBUS_PFMT(YUYV8_2X8, YUYV),
|
|
+ PIXMAP_MBUS_PFMT(YVYU8_2X8, YVYU),
|
|
+ PIXMAP_MBUS_PFMT(UYVY8_2X8, UYVY),
|
|
+ PIXMAP_MBUS_PFMT(VYUY8_2X8, VYUY),
|
|
+ PIXMAP_MBUS_PFMT(Y8_1X8, GREY),
|
|
+ PIXMAP_MBUS_PFMT(SBGGR8_1X8, SBGGR8),
|
|
+ PIXMAP_MBUS_PFMT(SGBRG8_1X8, SGBRG8),
|
|
+ PIXMAP_MBUS_PFMT(SGRBG8_1X8, SGRBG8),
|
|
+ PIXMAP_MBUS_PFMT(SRGGB8_1X8, SRGGB8),
|
|
+ PIXMAP_MBUS_PFMT(JPEG_1X8, JPEG),
|
|
+};
|
|
+
|
|
+static const struct dcmipp_bytecap_pix_map *dcmipp_bytecap_pix_map_by_pixelformat
|
|
+ (u32 pixelformat)
|
|
+{
|
|
+ const struct dcmipp_bytecap_pix_map *l = dcmipp_bytecap_pix_map_list;
|
|
+ unsigned int size = ARRAY_SIZE(dcmipp_bytecap_pix_map_list);
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < size; i++) {
|
|
+ if (l[i].pixelformat == pixelformat)
|
|
+ return &l[i];
|
|
+ }
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static const struct dcmipp_bytecap_pix_map *dcmipp_bytecap_pix_map_by_index(unsigned int i)
|
|
+{
|
|
+ const struct dcmipp_bytecap_pix_map *l = dcmipp_bytecap_pix_map_list;
|
|
+ unsigned int size = ARRAY_SIZE(dcmipp_bytecap_pix_map_list);
|
|
+
|
|
+ if (i >= size)
|
|
+ return NULL;
|
|
+
|
|
+ return &l[i];
|
|
+}
|
|
+
|
|
+struct dcmipp_buf {
|
|
+ struct vb2_v4l2_buffer vb;
|
|
+ bool prepared;
|
|
+ dma_addr_t paddr;
|
|
+ size_t size;
|
|
+ struct list_head list;
|
|
+};
|
|
+
|
|
+enum state {
|
|
+ STOPPED = 0,
|
|
+ WAIT_FOR_BUFFER,
|
|
+ RUNNING,
|
|
+};
|
|
+
|
|
+struct dcmipp_bytecap_device {
|
|
+ struct dcmipp_ent_device ved;
|
|
+ struct video_device vdev;
|
|
+ struct device *dev;
|
|
+ struct device *cdev;
|
|
+ struct v4l2_pix_format format;
|
|
+ struct vb2_queue queue;
|
|
+ struct list_head buffers;
|
|
+ /* Protects the access of variables shared within the interrupt */
|
|
+ spinlock_t irqlock;
|
|
+ /* Protect this data structure */
|
|
+ struct mutex lock;
|
|
+ u32 sequence;
|
|
+ struct media_pipeline pipe;
|
|
+
|
|
+ enum state state;
|
|
+
|
|
+ /*
|
|
+ * DCMIPP driver is handling 2 buffers
|
|
+ * active: buffer into which DCMIPP is currently writing into
|
|
+ * next: buffer given to the DCMIPP and which will become
|
|
+ * automatically active on next VSYNC
|
|
+ */
|
|
+ struct dcmipp_buf *active, *next;
|
|
+
|
|
+ void __iomem *regs;
|
|
+ struct reset_control *rstc;
|
|
+
|
|
+ u32 cmier;
|
|
+ u32 cmsr2;
|
|
+
|
|
+ int errors_count;
|
|
+ int limit_count;
|
|
+ int overrun_count;
|
|
+ int buffers_count;
|
|
+ int vsync_count;
|
|
+ int frame_count;
|
|
+ int it_count;
|
|
+ int underrun_count;
|
|
+ int nactive_count;
|
|
+};
|
|
+
|
|
+static const struct v4l2_pix_format fmt_default = {
|
|
+ .width = DCMIPP_FMT_WIDTH_DEFAULT,
|
|
+ .height = DCMIPP_FMT_HEIGHT_DEFAULT,
|
|
+ .pixelformat = V4L2_PIX_FMT_RGB565,
|
|
+ .field = V4L2_FIELD_NONE,
|
|
+ .colorspace = V4L2_COLORSPACE_DEFAULT,
|
|
+};
|
|
+
|
|
+static inline int frame_size(u32 width, u32 height, u32 format)
|
|
+{
|
|
+ switch (format) {
|
|
+ case V4L2_PIX_FMT_SBGGR8:
|
|
+ case V4L2_PIX_FMT_SGBRG8:
|
|
+ case V4L2_PIX_FMT_SGRBG8:
|
|
+ case V4L2_PIX_FMT_SRGGB8:
|
|
+ case V4L2_PIX_FMT_GREY:
|
|
+ return (width * height);
|
|
+ case V4L2_PIX_FMT_RGB565:
|
|
+ case V4L2_PIX_FMT_YUYV:
|
|
+ case V4L2_PIX_FMT_YVYU:
|
|
+ case V4L2_PIX_FMT_UYVY:
|
|
+ case V4L2_PIX_FMT_VYUY:
|
|
+ return (width * height * 2);
|
|
+ case V4L2_PIX_FMT_JPEG:
|
|
+ return (width * height);
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline int frame_stride(u32 width, u32 format)
|
|
+{
|
|
+ switch (format) {
|
|
+ case V4L2_PIX_FMT_SBGGR8:
|
|
+ case V4L2_PIX_FMT_SGBRG8:
|
|
+ case V4L2_PIX_FMT_SGRBG8:
|
|
+ case V4L2_PIX_FMT_SRGGB8:
|
|
+ case V4L2_PIX_FMT_GREY:
|
|
+ case V4L2_PIX_FMT_JPEG:
|
|
+ return width;
|
|
+ case V4L2_PIX_FMT_RGB565:
|
|
+ case V4L2_PIX_FMT_YUYV:
|
|
+ case V4L2_PIX_FMT_YVYU:
|
|
+ case V4L2_PIX_FMT_UYVY:
|
|
+ case V4L2_PIX_FMT_VYUY:
|
|
+ return (width * 2);
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline int hdw_pixel_alignment(u32 format)
|
|
+{
|
|
+ /* 16 bytes alignment required by hardware */
|
|
+ switch (format) {
|
|
+ case V4L2_PIX_FMT_SBGGR8:
|
|
+ case V4L2_PIX_FMT_SGBRG8:
|
|
+ case V4L2_PIX_FMT_SGRBG8:
|
|
+ case V4L2_PIX_FMT_SRGGB8:
|
|
+ case V4L2_PIX_FMT_GREY:
|
|
+ case V4L2_PIX_FMT_JPEG:
|
|
+ return 4;/* 2^4 = 16 pixels = 16 bytes */
|
|
+ case V4L2_PIX_FMT_RGB565:
|
|
+ case V4L2_PIX_FMT_YUYV:
|
|
+ case V4L2_PIX_FMT_YVYU:
|
|
+ case V4L2_PIX_FMT_UYVY:
|
|
+ case V4L2_PIX_FMT_VYUY:
|
|
+ return 3;/* 2^3 = 8 pixels = 16 bytes */
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int dcmipp_bytecap_querycap(struct file *file, void *priv,
|
|
+ struct v4l2_capability *cap)
|
|
+{
|
|
+ strscpy(cap->driver, DCMIPP_PDEV_NAME, sizeof(cap->driver));
|
|
+ strscpy(cap->card, KBUILD_MODNAME, sizeof(cap->card));
|
|
+ snprintf(cap->bus_info, sizeof(cap->bus_info),
|
|
+ "platform:%s", DCMIPP_PDEV_NAME);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dcmipp_bytecap_get_format(struct dcmipp_ent_device *ved,
|
|
+ struct v4l2_pix_format *fmt)
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap = container_of(ved, struct dcmipp_bytecap_device,
|
|
+ ved);
|
|
+
|
|
+ *fmt = vcap->format;
|
|
+}
|
|
+
|
|
+static int dcmipp_bytecap_g_fmt_vid_cap(struct file *file, void *priv,
|
|
+ struct v4l2_format *f)
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap = video_drvdata(file);
|
|
+
|
|
+ f->fmt.pix = vcap->format;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_bytecap_try_fmt_vid_cap(struct file *file, void *priv,
|
|
+ struct v4l2_format *f)
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap = video_drvdata(file);
|
|
+ struct v4l2_pix_format *format = &f->fmt.pix;
|
|
+ const struct dcmipp_bytecap_pix_map *vpix;
|
|
+ u32 in_w, in_h;
|
|
+
|
|
+ /* Don't accept a pixelformat that is not on the table */
|
|
+ vpix = dcmipp_bytecap_pix_map_by_pixelformat(format->pixelformat);
|
|
+ if (!vpix)
|
|
+ format->pixelformat = fmt_default.pixelformat;
|
|
+
|
|
+ /* Adjust width & height */
|
|
+ in_w = format->width;
|
|
+ in_h = format->height;
|
|
+ v4l_bound_align_image(&format->width,
|
|
+ DCMIPP_FRAME_MIN_WIDTH, DCMIPP_FRAME_MAX_WIDTH,
|
|
+ hdw_pixel_alignment(format->pixelformat),
|
|
+ &format->height,
|
|
+ DCMIPP_FRAME_MIN_HEIGHT, DCMIPP_FRAME_MAX_HEIGHT,
|
|
+ hdw_pixel_alignment(format->pixelformat),
|
|
+ 0);
|
|
+ if (format->width != in_w || format->height != in_h)
|
|
+ dev_dbg(vcap->dev,
|
|
+ "resolution updated: %dx%d -> %dx%d\n",
|
|
+ in_w, in_h, format->width, format->height);
|
|
+
|
|
+ format->bytesperline = frame_stride(format->width, format->pixelformat);
|
|
+ format->sizeimage = frame_size(format->width, format->height, format->pixelformat);
|
|
+
|
|
+ if (format->field == V4L2_FIELD_ANY)
|
|
+ format->field = fmt_default.field;
|
|
+
|
|
+ dcmipp_colorimetry_clamp(format);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_bytecap_s_fmt_vid_cap(struct file *file, void *priv,
|
|
+ struct v4l2_format *f)
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap = video_drvdata(file);
|
|
+ int ret;
|
|
+
|
|
+ /* Do not change the format while stream is on */
|
|
+ if (vb2_is_busy(&vcap->queue))
|
|
+ return -EBUSY;
|
|
+
|
|
+ ret = dcmipp_bytecap_try_fmt_vid_cap(file, priv, f);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ dev_dbg(vcap->dev, "%s: format update: old:%dx%d (0x%x, %d, %d, %d, %d) new:%dx%d (0x%x, %d, %d, %d, %d)\n",
|
|
+ vcap->vdev.name,
|
|
+ /* old */
|
|
+ vcap->format.width, vcap->format.height,
|
|
+ vcap->format.pixelformat, vcap->format.colorspace,
|
|
+ vcap->format.quantization, vcap->format.xfer_func,
|
|
+ vcap->format.ycbcr_enc,
|
|
+ /* new */
|
|
+ f->fmt.pix.width, f->fmt.pix.height,
|
|
+ f->fmt.pix.pixelformat, f->fmt.pix.colorspace,
|
|
+ f->fmt.pix.quantization, f->fmt.pix.xfer_func,
|
|
+ f->fmt.pix.ycbcr_enc);
|
|
+
|
|
+ vcap->format = f->fmt.pix;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_bytecap_enum_fmt_vid_cap(struct file *file, void *priv,
|
|
+ struct v4l2_fmtdesc *f)
|
|
+{
|
|
+ const struct dcmipp_bytecap_pix_map *vpix = dcmipp_bytecap_pix_map_by_index(f->index);
|
|
+
|
|
+ if (!vpix)
|
|
+ return -EINVAL;
|
|
+
|
|
+ f->pixelformat = vpix->pixelformat;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_bytecap_enum_framesizes(struct file *file, void *fh,
|
|
+ struct v4l2_frmsizeenum *fsize)
|
|
+{
|
|
+ const struct dcmipp_bytecap_pix_map *vpix;
|
|
+
|
|
+ if (fsize->index)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* Only accept code in the pix map table */
|
|
+ vpix = dcmipp_bytecap_pix_map_by_pixelformat(fsize->pixel_format);
|
|
+ if (!vpix)
|
|
+ return -EINVAL;
|
|
+
|
|
+ fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
|
|
+ fsize->stepwise.min_width = DCMIPP_FRAME_MIN_WIDTH;
|
|
+ fsize->stepwise.max_width = DCMIPP_FRAME_MAX_WIDTH;
|
|
+ fsize->stepwise.min_height = DCMIPP_FRAME_MIN_HEIGHT;
|
|
+ fsize->stepwise.max_height = DCMIPP_FRAME_MAX_HEIGHT;
|
|
+ fsize->stepwise.step_width = 1;
|
|
+ fsize->stepwise.step_height = 1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* TODO - based on the explanation text, should also use v4l2_pipeline_link_notify */
|
|
+static int dcmipp_bytecap_open(struct file *file)
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap = video_drvdata(file);
|
|
+ int ret;
|
|
+
|
|
+ ret = mutex_lock_interruptible(&vcap->lock);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = v4l2_fh_open(file);
|
|
+ if (ret)
|
|
+ goto err_unlock;
|
|
+
|
|
+ ret = v4l2_pipeline_pm_get(&vcap->vdev.entity);
|
|
+ if (ret)
|
|
+ goto err_close;
|
|
+
|
|
+ mutex_unlock(&vcap->lock);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_close:
|
|
+ v4l2_fh_release(file);
|
|
+err_unlock:
|
|
+ mutex_unlock(&vcap->lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int dcmipp_bytecap_close(struct file *file)
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap = video_drvdata(file);
|
|
+
|
|
+ vb2_fop_release(file);
|
|
+
|
|
+ v4l2_pipeline_pm_put(&vcap->vdev.entity);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct v4l2_file_operations dcmipp_bytecap_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = dcmipp_bytecap_open,
|
|
+ .release = dcmipp_bytecap_close,
|
|
+ .read = vb2_fop_read,
|
|
+ .poll = vb2_fop_poll,
|
|
+ .unlocked_ioctl = video_ioctl2,
|
|
+ .mmap = vb2_fop_mmap,
|
|
+};
|
|
+
|
|
+static const struct v4l2_ioctl_ops dcmipp_bytecap_ioctl_ops = {
|
|
+ .vidioc_querycap = dcmipp_bytecap_querycap,
|
|
+
|
|
+ .vidioc_g_fmt_vid_cap = dcmipp_bytecap_g_fmt_vid_cap,
|
|
+ .vidioc_s_fmt_vid_cap = dcmipp_bytecap_s_fmt_vid_cap,
|
|
+ .vidioc_try_fmt_vid_cap = dcmipp_bytecap_try_fmt_vid_cap,
|
|
+ .vidioc_enum_fmt_vid_cap = dcmipp_bytecap_enum_fmt_vid_cap,
|
|
+ .vidioc_enum_framesizes = dcmipp_bytecap_enum_framesizes,
|
|
+
|
|
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
|
|
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
|
|
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
|
|
+ .vidioc_querybuf = vb2_ioctl_querybuf,
|
|
+ .vidioc_qbuf = vb2_ioctl_qbuf,
|
|
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
|
|
+ .vidioc_expbuf = vb2_ioctl_expbuf,
|
|
+ .vidioc_streamon = vb2_ioctl_streamon,
|
|
+ .vidioc_streamoff = vb2_ioctl_streamoff,
|
|
+};
|
|
+
|
|
+static int dcmipp_pipeline_s_stream(struct dcmipp_bytecap_device *vcap,
|
|
+ int state)
|
|
+{
|
|
+ struct media_entity *entity = &vcap->vdev.entity;
|
|
+ struct v4l2_subdev *subdev;
|
|
+ struct media_pad *pad;
|
|
+ int ret;
|
|
+
|
|
+ /* Start/stop all entities within pipeline */
|
|
+ while (1) {
|
|
+ pad = &entity->pads[0];
|
|
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
|
|
+ break;
|
|
+
|
|
+ pad = media_entity_remote_pad(pad);
|
|
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
|
|
+ break;
|
|
+
|
|
+ entity = pad->entity;
|
|
+ subdev = media_entity_to_v4l2_subdev(entity);
|
|
+
|
|
+ ret = v4l2_subdev_call(subdev, video, s_stream, state);
|
|
+ if (ret < 0 && ret != -ENOIOCTLCMD) {
|
|
+ dev_err(vcap->dev, "%s: \"%s\" failed to %s streaming (%d)\n",
|
|
+ __func__, subdev->name,
|
|
+ state ? "start" : "stop", ret);
|
|
+
|
|
+ if (!state)
|
|
+ v4l2_subdev_call(subdev, core, s_power, state);
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ dev_dbg(vcap->dev, "\"%s\" is %s\n",
|
|
+ subdev->name, state ? "started" : "stopped");
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dcmipp_start_capture(struct dcmipp_bytecap_device *vcap,
|
|
+ struct dcmipp_buf *buf)
|
|
+{
|
|
+ /* Set buffer address */
|
|
+ reg_write(vcap, DCMIPP_P0PPM0AR1, buf->paddr);
|
|
+ dev_dbg(vcap->dev, "Write [%d] %p phy=%pad\n", buf->vb.vb2_buf.index, buf, &buf->paddr);
|
|
+
|
|
+ /* Set buffer size */
|
|
+ reg_write(vcap, DCMIPP_P0DCLMTR, DCMIPP_P0DCLMTR_ENABLE |
|
|
+ ((buf->size / 4) & DCMIPP_P0DCLMTR_LIMIT_MASK));
|
|
+
|
|
+ /* Capture request */
|
|
+ reg_set(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ);
|
|
+}
|
|
+
|
|
+static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq,
|
|
+ unsigned int count)
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq);
|
|
+ struct media_entity *entity = &vcap->vdev.entity;
|
|
+ struct dcmipp_buf *buf, *node;
|
|
+ int ret;
|
|
+
|
|
+ vcap->sequence = 0;
|
|
+ vcap->errors_count = 0;
|
|
+ vcap->limit_count = 0;
|
|
+ vcap->overrun_count = 0;
|
|
+ vcap->buffers_count = 0;
|
|
+ vcap->vsync_count = 0;
|
|
+ vcap->frame_count = 0;
|
|
+ vcap->it_count = 0;
|
|
+ vcap->underrun_count = 0;
|
|
+ vcap->nactive_count = 0;
|
|
+
|
|
+ ret = pm_runtime_get_sync(vcap->cdev);
|
|
+ if (ret < 0) {
|
|
+ dev_err(vcap->dev, "%s: Failed to start streaming, cannot get sync (%d)\n",
|
|
+ __func__, ret);
|
|
+ goto err_pm_put;
|
|
+ }
|
|
+
|
|
+ /* Start the media pipeline */
|
|
+ ret = media_pipeline_start(entity, &vcap->pipe);
|
|
+ if (ret) {
|
|
+ dev_err(vcap->dev, "%s: Failed to start streaming, media pipeline start error (%d)\n",
|
|
+ __func__, ret);
|
|
+ goto err_pm_put;
|
|
+ }
|
|
+
|
|
+ /* Start all the elements within pipeline */
|
|
+ ret = dcmipp_pipeline_s_stream(vcap, 1);
|
|
+ if (ret)
|
|
+ goto err_media_pipeline_stop;
|
|
+
|
|
+ spin_lock_irq(&vcap->irqlock);
|
|
+
|
|
+ /* Enable pipe at the end of programming */
|
|
+ reg_set(vcap, DCMIPP_P0FSCR, DCMIPP_P0FSCR_PIPEN);
|
|
+
|
|
+ /*
|
|
+ * Start capture if at least one buffer has been queued,
|
|
+ * otherwise start is deferred at next buffer queueing
|
|
+ */
|
|
+ buf = list_first_entry_or_null(&vcap->buffers, typeof(*buf), list);
|
|
+ if (!buf) {
|
|
+ dev_dbg(vcap->dev, "Start streaming is deferred to next buffer queueing\n");
|
|
+ vcap->next = NULL;
|
|
+ vcap->state = WAIT_FOR_BUFFER;
|
|
+ spin_unlock_irq(&vcap->irqlock);
|
|
+ return 0;
|
|
+ }
|
|
+ vcap->next = buf;
|
|
+ dev_dbg(vcap->dev, "Start with next [%d] %p phy=%pad\n",
|
|
+ buf->vb.vb2_buf.index, buf, &buf->paddr);
|
|
+
|
|
+ /* Start capture */
|
|
+ dcmipp_start_capture(vcap, buf);
|
|
+
|
|
+ /* Enable interruptions */
|
|
+ vcap->cmier |= DCMIPP_CMIER_P0ALL;
|
|
+ reg_set(vcap, DCMIPP_CMIER, vcap->cmier);
|
|
+
|
|
+ vcap->state = RUNNING;
|
|
+
|
|
+ spin_unlock_irq(&vcap->irqlock);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_media_pipeline_stop:
|
|
+ media_pipeline_stop(entity);
|
|
+err_pm_put:
|
|
+ pm_runtime_put(vcap->cdev);
|
|
+ spin_lock_irq(&vcap->irqlock);
|
|
+ /*
|
|
+ * Return all buffers to vb2 in QUEUED state.
|
|
+ * This will give ownership back to userspace
|
|
+ */
|
|
+ list_for_each_entry_safe(buf, node, &vcap->buffers, list) {
|
|
+ list_del_init(&buf->list);
|
|
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
|
|
+ }
|
|
+ vcap->active = NULL;
|
|
+ spin_unlock_irq(&vcap->irqlock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void dcmipp_dump_status(struct dcmipp_bytecap_device *vcap)
|
|
+{
|
|
+ struct device *dev = vcap->dev;
|
|
+
|
|
+ dev_dbg(dev, "[DCMIPP_PRSR] =%#10.8x\n", reg_read(vcap, DCMIPP_PRSR));
|
|
+ dev_dbg(dev, "[DCMIPP_P0SR] =%#10.8x\n", reg_read(vcap, DCMIPP_P0SR));
|
|
+ dev_dbg(dev, "[DCMIPP_P0DCCNTR]=%#10.8x\n",
|
|
+ reg_read(vcap, DCMIPP_P0DCCNTR));
|
|
+ dev_dbg(dev, "[DCMIPP_CMSR1] =%#10.8x\n", reg_read(vcap, DCMIPP_CMSR1));
|
|
+ dev_dbg(dev, "[DCMIPP_CMSR2] =%#10.8x\n", reg_read(vcap, DCMIPP_CMSR2));
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Stop the stream engine. Any remaining buffers in the stream queue are
|
|
+ * dequeued and passed on to the vb2 framework marked as STATE_ERROR.
|
|
+ */
|
|
+static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq)
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq);
|
|
+ struct dcmipp_buf *buf, *node;
|
|
+ int ret;
|
|
+ u32 status;
|
|
+
|
|
+ dcmipp_pipeline_s_stream(vcap, 0);
|
|
+
|
|
+ /* Stop the media pipeline */
|
|
+ media_pipeline_stop(&vcap->vdev.entity);
|
|
+
|
|
+ /* Disable interruptions */
|
|
+ reg_clear(vcap, DCMIPP_CMIER, vcap->cmier);
|
|
+
|
|
+ /* Stop capture */
|
|
+ reg_clear(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ);
|
|
+
|
|
+ /* Wait until CPTACT become 0 */
|
|
+ ret = readl_relaxed_poll_timeout(vcap->regs + DCMIPP_P0SR,
|
|
+ status,
|
|
+ !(status & DCMIPP_P0SR_CPTACT),
|
|
+ 20, 1000);
|
|
+ if (ret)
|
|
+ dev_warn(vcap->dev, "Timeout when stopping\n");
|
|
+
|
|
+ /* Disable pipe */
|
|
+ reg_clear(vcap, DCMIPP_P0FSCR, DCMIPP_P0FSCR_PIPEN);
|
|
+
|
|
+ spin_lock_irq(&vcap->irqlock);
|
|
+
|
|
+ /* Return all queued buffers to vb2 in ERROR state */
|
|
+ list_for_each_entry_safe(buf, node, &vcap->buffers, list) {
|
|
+ list_del_init(&buf->list);
|
|
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
|
|
+ }
|
|
+ INIT_LIST_HEAD(&vcap->buffers);
|
|
+
|
|
+ vcap->active = NULL;
|
|
+ vcap->state = STOPPED;
|
|
+
|
|
+ dcmipp_dump_status(vcap);
|
|
+
|
|
+ spin_unlock_irq(&vcap->irqlock);
|
|
+
|
|
+ pm_runtime_put(vcap->cdev);
|
|
+
|
|
+ if (ret) {
|
|
+ /* Reset IP on timeout */
|
|
+ if (reset_control_assert(vcap->rstc))
|
|
+ dev_warn(vcap->dev, "Failed to assert the reset line\n");
|
|
+
|
|
+ usleep_range(3000, 5000);
|
|
+
|
|
+ if (reset_control_deassert(vcap->rstc))
|
|
+ dev_warn(vcap->dev, "Failed to deassert the reset line\n");
|
|
+ }
|
|
+
|
|
+ if (vcap->errors_count)
|
|
+ dev_warn(vcap->dev, "Some errors found while streaming: errors=%d (overrun=%d, limit=%d, nactive=%d), underrun=%d, buffers=%d\n",
|
|
+ vcap->errors_count, vcap->overrun_count, vcap->limit_count,
|
|
+ vcap->nactive_count, vcap->underrun_count, vcap->buffers_count);
|
|
+ dev_dbg(vcap->dev, "Stop streaming, errors=%d (overrun=%d, limit=%d, nactive=%d), underrun=%d, vsync=%d, frame=%d, buffers=%d, it=%d\n",
|
|
+ vcap->errors_count, vcap->overrun_count, vcap->limit_count,
|
|
+ vcap->nactive_count, vcap->underrun_count, vcap->vsync_count,
|
|
+ vcap->frame_count, vcap->buffers_count, vcap->it_count);
|
|
+}
|
|
+
|
|
+static int dcmipp_bytecap_buf_prepare(struct vb2_buffer *vb)
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vb->vb2_queue);
|
|
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
|
+ struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb);
|
|
+ unsigned long size;
|
|
+
|
|
+ size = vcap->format.sizeimage;
|
|
+
|
|
+ if (vb2_plane_size(vb, 0) < size) {
|
|
+ dev_err(vcap->dev, "%s data will not fit into plane (%lu < %lu)\n",
|
|
+ __func__, vb2_plane_size(vb, 0), size);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ vb2_set_plane_payload(vb, 0, size);
|
|
+
|
|
+ if (!buf->prepared) {
|
|
+ /* Get memory addresses */
|
|
+ buf->paddr =
|
|
+ vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
|
|
+ buf->size = vb2_plane_size(&buf->vb.vb2_buf, 0);
|
|
+ buf->prepared = true;
|
|
+
|
|
+ vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size);
|
|
+
|
|
+ dev_dbg(vcap->dev, "Setup [%d] phy=%pad size=%zu\n",
|
|
+ vb->index, &buf->paddr, buf->size);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dcmipp_bytecap_buf_queue(struct vb2_buffer *vb2_buf)
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap =
|
|
+ vb2_get_drv_priv(vb2_buf->vb2_queue);
|
|
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2_buf);
|
|
+ struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb);
|
|
+
|
|
+ spin_lock_irq(&vcap->irqlock);
|
|
+ list_add_tail(&buf->list, &vcap->buffers);
|
|
+
|
|
+ dev_dbg(vcap->dev, "Queue [%d] %p phy=%pad\n", buf->vb.vb2_buf.index, buf, &buf->paddr);
|
|
+
|
|
+ if (vcap->state == WAIT_FOR_BUFFER) {
|
|
+ vcap->next = buf;
|
|
+ dev_dbg(vcap->dev, "Restart with next [%d] %p phy=%pad\n",
|
|
+ buf->vb.vb2_buf.index, buf, &buf->paddr);
|
|
+
|
|
+ dcmipp_start_capture(vcap, buf);
|
|
+
|
|
+ vcap->state = RUNNING;
|
|
+
|
|
+ spin_unlock_irq(&vcap->irqlock);
|
|
+
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ spin_unlock_irq(&vcap->irqlock);
|
|
+}
|
|
+
|
|
+static int dcmipp_bytecap_queue_setup(struct vb2_queue *vq,
|
|
+ unsigned int *nbuffers,
|
|
+ unsigned int *nplanes,
|
|
+ unsigned int sizes[],
|
|
+ struct device *alloc_devs[])
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq);
|
|
+ unsigned int size;
|
|
+
|
|
+ size = vcap->format.sizeimage;
|
|
+
|
|
+ /* Make sure the image size is large enough */
|
|
+ if (*nplanes)
|
|
+ return sizes[0] < vcap->format.sizeimage ? -EINVAL : 0;
|
|
+
|
|
+ *nplanes = 1;
|
|
+ sizes[0] = vcap->format.sizeimage;
|
|
+
|
|
+ dev_dbg(vcap->dev, "Setup queue, count=%d, size=%d\n",
|
|
+ *nbuffers, size);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_bytecap_buf_init(struct vb2_buffer *vb)
|
|
+{
|
|
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
|
+ struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb);
|
|
+
|
|
+ INIT_LIST_HEAD(&buf->list);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct vb2_ops dcmipp_bytecap_qops = {
|
|
+ .start_streaming = dcmipp_bytecap_start_streaming,
|
|
+ .stop_streaming = dcmipp_bytecap_stop_streaming,
|
|
+ .buf_init = dcmipp_bytecap_buf_init,
|
|
+ .buf_prepare = dcmipp_bytecap_buf_prepare,
|
|
+ .buf_queue = dcmipp_bytecap_buf_queue,
|
|
+ .queue_setup = dcmipp_bytecap_queue_setup,
|
|
+ /*
|
|
+ * Since q->lock is set we can use the standard
|
|
+ * vb2_ops_wait_prepare/finish helper functions.
|
|
+ */
|
|
+ .wait_prepare = vb2_ops_wait_prepare,
|
|
+ .wait_finish = vb2_ops_wait_finish,
|
|
+};
|
|
+
|
|
+static const struct media_entity_operations dcmipp_bytecap_mops = {
|
|
+ .link_validate = dcmipp_link_validate,
|
|
+};
|
|
+
|
|
+static void dcmipp_bytecap_release(struct video_device *vdev)
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap =
|
|
+ container_of(vdev, struct dcmipp_bytecap_device, vdev);
|
|
+
|
|
+ dcmipp_pads_cleanup(vcap->ved.pads);
|
|
+ kfree(vcap);
|
|
+}
|
|
+
|
|
+static void dcmipp_bytecap_comp_unbind(struct device *comp,
|
|
+ struct device *master,
|
|
+ void *master_data)
|
|
+{
|
|
+ struct dcmipp_ent_device *ved = dev_get_drvdata(comp);
|
|
+ struct dcmipp_bytecap_device *vcap =
|
|
+ container_of(ved, struct dcmipp_bytecap_device, ved);
|
|
+
|
|
+ media_entity_cleanup(ved->ent);
|
|
+ vb2_video_unregister_device(&vcap->vdev);
|
|
+}
|
|
+
|
|
+static void dcmipp_buffer_done(struct dcmipp_bytecap_device *vcap,
|
|
+ struct dcmipp_buf *buf,
|
|
+ size_t bytesused,
|
|
+ int err)
|
|
+{
|
|
+ struct vb2_v4l2_buffer *vbuf;
|
|
+
|
|
+ list_del_init(&buf->list);
|
|
+
|
|
+ vbuf = &buf->vb;
|
|
+
|
|
+ vbuf->sequence = vcap->sequence++;
|
|
+ vbuf->field = V4L2_FIELD_NONE;
|
|
+ vbuf->vb2_buf.timestamp = ktime_get_ns();
|
|
+ vb2_set_plane_payload(&vbuf->vb2_buf, 0, bytesused);
|
|
+ vb2_buffer_done(&vbuf->vb2_buf,
|
|
+ err ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
|
|
+ dev_dbg(vcap->dev, "Done [%d] %p phy=%pad\n", buf->vb.vb2_buf.index, buf, &buf->paddr);
|
|
+ vcap->buffers_count++;
|
|
+}
|
|
+
|
|
+/* irqlock must be held */
|
|
+static void dcmipp_bytecap_set_next_frame_or_stop(struct dcmipp_bytecap_device *vcap)
|
|
+{
|
|
+ if (!vcap->next && list_is_singular(&vcap->buffers)) {
|
|
+ /*
|
|
+ * If there is no available buffer (none or a single one in the list while two
|
|
+ * are expected), stop the capture (effective for next frame). On-going frame
|
|
+ * capture will continue till FRAME END but no further capture will be done.
|
|
+ */
|
|
+ reg_clear(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ);
|
|
+
|
|
+ dev_dbg(vcap->dev, "Capture restart is deferred to next buffer queueing\n");
|
|
+ vcap->next = NULL;
|
|
+ vcap->state = WAIT_FOR_BUFFER;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* If we don't have buffer yet, pick the one after active */
|
|
+ if (!vcap->next)
|
|
+ vcap->next = list_next_entry(vcap->active, list);
|
|
+
|
|
+ /*
|
|
+ * Set buffer address
|
|
+ * This register is shadowed and will be taken into
|
|
+ * account on next VSYNC (start of next frame)
|
|
+ */
|
|
+ reg_write(vcap, DCMIPP_P0PPM0AR1, vcap->next->paddr);
|
|
+ dev_dbg(vcap->dev, "Write [%d] %p phy=%pad\n",
|
|
+ vcap->next->vb.vb2_buf.index, vcap->next, &vcap->next->paddr);
|
|
+}
|
|
+
|
|
+/* irqlock must be held */
|
|
+static void dcmipp_bytecap_process_frame(struct dcmipp_bytecap_device *vcap,
|
|
+ size_t bytesused)
|
|
+{
|
|
+ int err = 0;
|
|
+ struct dcmipp_buf *buf = vcap->active;
|
|
+
|
|
+ if (!buf) {
|
|
+ vcap->nactive_count++;
|
|
+ vcap->errors_count++;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (bytesused > buf->size) {
|
|
+ dev_dbg(vcap->dev, "frame larger than expected (%zu > %zu)\n",
|
|
+ bytesused, buf->size);
|
|
+ /* Clip to buffer size and return buffer to V4L2 in error */
|
|
+ bytesused = buf->size;
|
|
+ vcap->limit_count++;
|
|
+ vcap->errors_count++;
|
|
+ err = -EOVERFLOW;
|
|
+ }
|
|
+
|
|
+ dcmipp_buffer_done(vcap, buf, bytesused, err);
|
|
+ vcap->active = NULL;
|
|
+}
|
|
+
|
|
+static irqreturn_t dcmipp_bytecap_irq_thread(int irq, void *arg)
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap =
|
|
+ container_of(arg, struct dcmipp_bytecap_device, ved);
|
|
+ size_t bytesused = 0;
|
|
+ u32 cmsr2;
|
|
+
|
|
+ spin_lock_irq(&vcap->irqlock);
|
|
+
|
|
+ cmsr2 = vcap->cmsr2 & vcap->cmier;
|
|
+
|
|
+ /*
|
|
+ * If we have an overrun, a frame-end will probably not be generated, in that
|
|
+ * case the active buffer will be recycled as next buffer by the VSYNC handler
|
|
+ */
|
|
+ if (cmsr2 & DCMIPP_CMSR2_P0OVRF) {
|
|
+ vcap->errors_count++;
|
|
+ vcap->overrun_count++;
|
|
+ }
|
|
+
|
|
+ if (cmsr2 & DCMIPP_CMSR2_P0FRAMEF) {
|
|
+ vcap->frame_count++;
|
|
+
|
|
+ /* Read captured buffer size */
|
|
+ bytesused = reg_read(vcap, DCMIPP_P0DCCNTR);
|
|
+ dcmipp_bytecap_process_frame(vcap, bytesused);
|
|
+ }
|
|
+
|
|
+ if (cmsr2 & DCMIPP_CMSR2_P0VSYNCF) {
|
|
+ vcap->vsync_count++;
|
|
+ if (vcap->state == WAIT_FOR_BUFFER) {
|
|
+ vcap->underrun_count++;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * On VSYNC, the previously set next buffer is going to become active thanks to
|
|
+ * the shadowing mechanism of the DCMIPP. In most of the cases, since a FRAMEEND
|
|
+ * has already come, pointer next is NULL since active is reset during the
|
|
+ * FRAMEEND handling. However, in case of framerate adjustment, there are more
|
|
+ * VSYNC than FRAMEEND. Thus we recycle the active (but not used) buffer and put it
|
|
+ * back into next.
|
|
+ */
|
|
+ swap(vcap->active, vcap->next);
|
|
+ dcmipp_bytecap_set_next_frame_or_stop(vcap);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ spin_unlock_irq(&vcap->irqlock);
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static irqreturn_t dcmipp_bytecap_irq_callback(int irq, void *arg)
|
|
+{
|
|
+ struct dcmipp_bytecap_device *vcap =
|
|
+ container_of(arg, struct dcmipp_bytecap_device, ved);
|
|
+
|
|
+ /* Store interrupt status register */
|
|
+ vcap->cmsr2 = reg_read(vcap, DCMIPP_CMSR2);
|
|
+ vcap->it_count++;
|
|
+
|
|
+ /* Clear interrupt */
|
|
+ reg_write(vcap, DCMIPP_CMFCR, vcap->cmsr2);
|
|
+
|
|
+ return IRQ_WAKE_THREAD;
|
|
+}
|
|
+
|
|
+static int dcmipp_bytecap_comp_bind(struct device *comp, struct device *master,
|
|
+ void *master_data)
|
|
+{
|
|
+ struct dcmipp_bind_data *bind_data = master_data;
|
|
+ struct dcmipp_platform_data *pdata = comp->platform_data;
|
|
+ struct dcmipp_bytecap_device *vcap;
|
|
+ struct v4l2_pix_format *format;
|
|
+ struct video_device *vdev;
|
|
+ struct vb2_queue *q;
|
|
+ int ret = 0;
|
|
+
|
|
+ /* Allocate the dcmipp_bytecap_device struct */
|
|
+ vcap = kzalloc(sizeof(*vcap), GFP_KERNEL);
|
|
+ if (!vcap)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ /* Allocate the pads */
|
|
+ vcap->ved.pads =
|
|
+ dcmipp_pads_init(1,
|
|
+ (const unsigned long[1]) {MEDIA_PAD_FL_SINK});
|
|
+ if (IS_ERR(vcap->ved.pads)) {
|
|
+ ret = PTR_ERR(vcap->ved.pads);
|
|
+ goto err_free_vcap;
|
|
+ }
|
|
+
|
|
+ /* Initialize the media entity */
|
|
+ vcap->vdev.entity.name = pdata->entity_name;
|
|
+ vcap->vdev.entity.function = MEDIA_ENT_F_IO_V4L;
|
|
+ ret = media_entity_pads_init(&vcap->vdev.entity,
|
|
+ 1, vcap->ved.pads);
|
|
+ if (ret)
|
|
+ goto err_clean_pads;
|
|
+
|
|
+ /* Initialize the lock */
|
|
+ mutex_init(&vcap->lock);
|
|
+
|
|
+ /* Initialize the vb2 queue */
|
|
+ q = &vcap->queue;
|
|
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
+ q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF;
|
|
+ q->lock = &vcap->lock;
|
|
+ q->drv_priv = vcap;
|
|
+ q->buf_struct_size = sizeof(struct dcmipp_buf);
|
|
+ q->ops = &dcmipp_bytecap_qops;
|
|
+ q->mem_ops = &vb2_dma_contig_memops;
|
|
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
|
|
+ q->min_buffers_needed = 1;
|
|
+ q->dev = comp;
|
|
+
|
|
+ ret = vb2_queue_init(q);
|
|
+ if (ret) {
|
|
+ dev_err(comp, "%s: vb2 queue init failed (err=%d)\n",
|
|
+ pdata->entity_name, ret);
|
|
+ goto err_clean_m_ent;
|
|
+ }
|
|
+
|
|
+ /* Initialize buffer list and its lock */
|
|
+ INIT_LIST_HEAD(&vcap->buffers);
|
|
+ spin_lock_init(&vcap->irqlock);
|
|
+
|
|
+ /* Set default frame format */
|
|
+ vcap->format = fmt_default;
|
|
+ format = &vcap->format;
|
|
+ format->bytesperline = frame_stride(format->width, format->pixelformat);
|
|
+ format->sizeimage = frame_size(format->width,
|
|
+ format->height,
|
|
+ format->pixelformat);
|
|
+
|
|
+ /* Fill the dcmipp_ent_device struct */
|
|
+ vcap->ved.ent = &vcap->vdev.entity;
|
|
+ vcap->ved.vdev_get_format = dcmipp_bytecap_get_format;
|
|
+ vcap->ved.handler = dcmipp_bytecap_irq_callback;
|
|
+ vcap->ved.thread_fn = dcmipp_bytecap_irq_thread;
|
|
+ dev_set_drvdata(comp, &vcap->ved);
|
|
+ vcap->dev = comp;
|
|
+ vcap->regs = bind_data->regs;
|
|
+ vcap->rstc = bind_data->rstc;
|
|
+ vcap->cdev = master;
|
|
+
|
|
+ /* Initialize the video_device struct */
|
|
+ vdev = &vcap->vdev;
|
|
+ vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
|
|
+ V4L2_CAP_READWRITE;
|
|
+ vdev->entity.ops = &dcmipp_bytecap_mops;
|
|
+ vdev->release = dcmipp_bytecap_release;
|
|
+ vdev->fops = &dcmipp_bytecap_fops;
|
|
+ vdev->ioctl_ops = &dcmipp_bytecap_ioctl_ops;
|
|
+ vdev->lock = &vcap->lock;
|
|
+ vdev->queue = q;
|
|
+ vdev->v4l2_dev = bind_data->v4l2_dev;
|
|
+ strscpy(vdev->name, pdata->entity_name, sizeof(vdev->name));
|
|
+ video_set_drvdata(vdev, &vcap->ved);
|
|
+
|
|
+ /* Register the video_device with the v4l2 and the media framework */
|
|
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
|
|
+ if (ret) {
|
|
+ dev_err(comp, "%s: video register failed (err=%d)\n",
|
|
+ vcap->vdev.name, ret);
|
|
+ goto err_clean_m_ent;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_clean_m_ent:
|
|
+ media_entity_cleanup(&vcap->vdev.entity);
|
|
+err_clean_pads:
|
|
+ dcmipp_pads_cleanup(vcap->ved.pads);
|
|
+err_free_vcap:
|
|
+ kfree(vcap);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static const struct component_ops dcmipp_bytecap_comp_ops = {
|
|
+ .bind = dcmipp_bytecap_comp_bind,
|
|
+ .unbind = dcmipp_bytecap_comp_unbind,
|
|
+};
|
|
+
|
|
+static int dcmipp_bytecap_probe(struct platform_device *pdev)
|
|
+{
|
|
+ return component_add(&pdev->dev, &dcmipp_bytecap_comp_ops);
|
|
+}
|
|
+
|
|
+static int dcmipp_bytecap_remove(struct platform_device *pdev)
|
|
+{
|
|
+ component_del(&pdev->dev, &dcmipp_bytecap_comp_ops);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct platform_device_id dcmipp_bytecap_driver_ids[] = {
|
|
+ {
|
|
+ .name = DCMIPP_BYTECAP_DRV_NAME,
|
|
+ },
|
|
+ { }
|
|
+};
|
|
+
|
|
+static struct platform_driver dcmipp_bytecap_pdrv = {
|
|
+ .probe = dcmipp_bytecap_probe,
|
|
+ .remove = dcmipp_bytecap_remove,
|
|
+ .id_table = dcmipp_bytecap_driver_ids,
|
|
+ .driver = {
|
|
+ .name = DCMIPP_BYTECAP_DRV_NAME,
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(dcmipp_bytecap_pdrv);
|
|
+
|
|
+MODULE_DEVICE_TABLE(platform, dcmipp_bytecap_driver_ids);
|
|
+
|
|
+MODULE_AUTHOR("Hugues Fruchet <hugues.fruchet@foss.st.com>");
|
|
+MODULE_AUTHOR("Alain Volmat <alain.volmat@foss.st.com>");
|
|
+MODULE_DESCRIPTION("STMicroelectronics STM32 Digital Camera Memory Interface with Pixel Processor driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-byteproc.c b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-byteproc.c
|
|
new file mode 100644
|
|
index 000000000..703843601
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-byteproc.c
|
|
@@ -0,0 +1,737 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Driver for STM32 Digital Camera Memory Interface Pixel Processor
|
|
+ *
|
|
+ * Copyright (C) STMicroelectronics SA 2021
|
|
+ * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
|
|
+ * Alain Volmat <alain.volmat@foss.st.com>
|
|
+ * for STMicroelectronics.
|
|
+ */
|
|
+
|
|
+#include <linux/component.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mod_devicetable.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/vmalloc.h>
|
|
+#include <linux/v4l2-mediabus.h>
|
|
+#include <media/v4l2-rect.h>
|
|
+#include <media/v4l2-subdev.h>
|
|
+
|
|
+#include "dcmipp-common.h"
|
|
+
|
|
+#define DCMIPP_BYTEPROC_DRV_NAME "dcmipp-byteproc"
|
|
+
|
|
+#define DCMIPP_FMT_WIDTH_DEFAULT 640
|
|
+#define DCMIPP_FMT_HEIGHT_DEFAULT 480
|
|
+
|
|
+#define DCMIPP_P0FCTCR (0x500)
|
|
+#define DCMIPP_P0FCTCR_FRATE_MASK GENMASK(1, 0)
|
|
+#define DCMIPP_P0SCSTR (0x504)
|
|
+#define DCMIPP_P0SCSTR_HSTART_SHIFT 0
|
|
+#define DCMIPP_P0SCSTR_VSTART_SHIFT 16
|
|
+#define DCMIPP_P0SCSZR (0x508)
|
|
+#define DCMIPP_P0SCSZR_ENABLE BIT(31)
|
|
+#define DCMIPP_P0SCSZR_HSIZE_SHIFT 0
|
|
+#define DCMIPP_P0SCSZR_VSIZE_SHIFT 16
|
|
+#define DCMIPP_P0PPCR (0x5C0)
|
|
+#define DCMIPP_P0PPCR_BSM_1_2 0x1
|
|
+#define DCMIPP_P0PPCR_BSM_1_4 0x2
|
|
+#define DCMIPP_P0PPCR_BSM_2_4 0x3
|
|
+#define DCMIPP_P0PPCR_BSM_MASK GENMASK(8, 7)
|
|
+#define DCMIPP_P0PPCR_BSM_SHIFT 0x7
|
|
+#define DCMIPP_P0PPCR_LSM BIT(10)
|
|
+
|
|
+#define IS_SINK(pad) (!(pad))
|
|
+#define IS_SRC(pad) ((pad))
|
|
+#define PAD_STR(pad) (IS_SRC((pad))) ? "src" : "sink"
|
|
+
|
|
+#define BYTEPROC_MEDIA_BUS_FMT_DEFAULT MEDIA_BUS_FMT_RGB565_2X8_LE
|
|
+
|
|
+struct dcmipp_byteproc_pix_map {
|
|
+ unsigned int code;
|
|
+ unsigned int bpp;
|
|
+};
|
|
+
|
|
+#define PIXMAP_MBUS_BPP(mbus, byteperpixel) \
|
|
+ { \
|
|
+ .code = MEDIA_BUS_FMT_##mbus, \
|
|
+ .bpp = byteperpixel, \
|
|
+ }
|
|
+static const struct dcmipp_byteproc_pix_map dcmipp_byteproc_pix_map_list[] = {
|
|
+ PIXMAP_MBUS_BPP(RGB565_2X8_LE, 2),
|
|
+ PIXMAP_MBUS_BPP(YUYV8_2X8, 2),
|
|
+ PIXMAP_MBUS_BPP(YVYU8_2X8, 2),
|
|
+ PIXMAP_MBUS_BPP(UYVY8_2X8, 2),
|
|
+ PIXMAP_MBUS_BPP(VYUY8_2X8, 2),
|
|
+ PIXMAP_MBUS_BPP(Y8_1X8, 1),
|
|
+ PIXMAP_MBUS_BPP(SBGGR8_1X8, 1),
|
|
+ PIXMAP_MBUS_BPP(SGBRG8_1X8, 1),
|
|
+ PIXMAP_MBUS_BPP(SGRBG8_1X8, 1),
|
|
+ PIXMAP_MBUS_BPP(SRGGB8_1X8, 1),
|
|
+ PIXMAP_MBUS_BPP(JPEG_1X8, 1),
|
|
+};
|
|
+
|
|
+static const struct dcmipp_byteproc_pix_map *dcmipp_byteproc_pix_map_by_index(unsigned int i)
|
|
+{
|
|
+ const struct dcmipp_byteproc_pix_map *l = dcmipp_byteproc_pix_map_list;
|
|
+ unsigned int size = ARRAY_SIZE(dcmipp_byteproc_pix_map_list);
|
|
+
|
|
+ if (i >= size)
|
|
+ return NULL;
|
|
+
|
|
+ return &l[i];
|
|
+}
|
|
+
|
|
+static const struct dcmipp_byteproc_pix_map *dcmipp_byteproc_pix_map_by_code(u32 code)
|
|
+{
|
|
+ const struct dcmipp_byteproc_pix_map *l = dcmipp_byteproc_pix_map_list;
|
|
+ unsigned int size = ARRAY_SIZE(dcmipp_byteproc_pix_map_list);
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < size; i++) {
|
|
+ if (l[i].code == code)
|
|
+ return &l[i];
|
|
+ }
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+struct dcmipp_byteproc_device {
|
|
+ struct dcmipp_ent_device ved;
|
|
+ struct v4l2_subdev sd;
|
|
+ struct device *dev;
|
|
+ struct v4l2_mbus_framefmt sink_fmt;
|
|
+ bool streaming;
|
|
+ /* Protect this data structure */
|
|
+ struct mutex lock;
|
|
+
|
|
+ void __iomem *regs;
|
|
+
|
|
+ struct v4l2_fract sink_interval;
|
|
+ struct v4l2_fract src_interval;
|
|
+ unsigned int frate;
|
|
+ u32 src_code;
|
|
+ struct v4l2_rect crop;
|
|
+ struct v4l2_rect compose;
|
|
+};
|
|
+
|
|
+static const struct v4l2_mbus_framefmt fmt_default = {
|
|
+ .width = DCMIPP_FMT_WIDTH_DEFAULT,
|
|
+ .height = DCMIPP_FMT_HEIGHT_DEFAULT,
|
|
+ .code = BYTEPROC_MEDIA_BUS_FMT_DEFAULT,
|
|
+ .field = V4L2_FIELD_NONE,
|
|
+ .colorspace = V4L2_COLORSPACE_DEFAULT,
|
|
+};
|
|
+
|
|
+static const struct v4l2_rect crop_min = {
|
|
+ .width = DCMIPP_FRAME_MIN_WIDTH,
|
|
+ .height = DCMIPP_FRAME_MIN_HEIGHT,
|
|
+ .top = 0,
|
|
+ .left = 0,
|
|
+};
|
|
+
|
|
+static struct v4l2_rect
|
|
+dcmipp_byteproc_get_compose_bound(const struct v4l2_mbus_framefmt *fmt)
|
|
+{
|
|
+ /* Get the crop bounds to clamp the crop rectangle correctly */
|
|
+ struct v4l2_rect r = {
|
|
+ .left = 0,
|
|
+ .top = 0,
|
|
+ .width = fmt->width,
|
|
+ .height = fmt->height,
|
|
+ };
|
|
+
|
|
+ return r;
|
|
+}
|
|
+
|
|
+static void dcmipp_byteproc_adjust_crop(struct v4l2_rect *r, struct v4l2_rect *compose)
|
|
+{
|
|
+ /* Disallow rectangles smaller than the minimal one. */
|
|
+ v4l2_rect_set_min_size(r, &crop_min);
|
|
+ v4l2_rect_map_inside(r, compose);
|
|
+}
|
|
+
|
|
+static void dcmipp_byteproc_adjust_compose(struct v4l2_rect *r,
|
|
+ const struct v4l2_mbus_framefmt *fmt)
|
|
+{
|
|
+ r->top = r->left = 0;
|
|
+
|
|
+ /* Compose is not possible for JPEG or Bayer formats */
|
|
+ if ((fmt->code == MEDIA_BUS_FMT_JPEG_1X8) ||
|
|
+ (fmt->code == MEDIA_BUS_FMT_SBGGR8_1X8) || (fmt->code == MEDIA_BUS_FMT_SGBRG8_1X8) ||
|
|
+ (fmt->code == MEDIA_BUS_FMT_SGRBG8_1X8) || (fmt->code == MEDIA_BUS_FMT_SRGGB8_1X8)) {
|
|
+ r->width = fmt->width;
|
|
+ r->height = fmt->height;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Adjust height - we can only perform 1/2 decimation */
|
|
+ if (r->height <= (fmt->height / 2))
|
|
+ r->height = fmt->height / 2;
|
|
+ else
|
|
+ r->height = fmt->height;
|
|
+
|
|
+ /* Adjust width - /2 or /4 for 8bits formats and /2 for 16bits formats */
|
|
+ if ((fmt->code == MEDIA_BUS_FMT_Y8_1X8) && (r->width <= (fmt->width / 4)))
|
|
+ r->width = fmt->width / 4;
|
|
+ else if (r->width <= (fmt->width / 2))
|
|
+ r->width = fmt->width / 2;
|
|
+ else
|
|
+ r->width = fmt->width;
|
|
+}
|
|
+
|
|
+static void dcmipp_byteproc_adjust_fmt(struct v4l2_mbus_framefmt *fmt)
|
|
+{
|
|
+ const struct dcmipp_byteproc_pix_map *vpix;
|
|
+
|
|
+ /* Only accept code in the pix map table */
|
|
+ vpix = dcmipp_byteproc_pix_map_by_code(fmt->code);
|
|
+ if (!vpix)
|
|
+ fmt->code = fmt_default.code;
|
|
+
|
|
+ fmt->width = clamp_t(u32, fmt->width, DCMIPP_FRAME_MIN_WIDTH,
|
|
+ DCMIPP_FRAME_MAX_WIDTH) & ~1;
|
|
+ fmt->height = clamp_t(u32, fmt->height, DCMIPP_FRAME_MIN_HEIGHT,
|
|
+ DCMIPP_FRAME_MAX_HEIGHT) & ~1;
|
|
+
|
|
+ if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE)
|
|
+ fmt->field = fmt_default.field;
|
|
+
|
|
+ dcmipp_colorimetry_clamp(fmt);
|
|
+}
|
|
+
|
|
+static int dcmipp_byteproc_init_cfg(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < sd->entity.num_pads; i++) {
|
|
+ struct v4l2_mbus_framefmt *mf;
|
|
+ struct v4l2_rect *r;
|
|
+
|
|
+ mf = v4l2_subdev_get_try_format(sd, sd_state, i);
|
|
+ *mf = fmt_default;
|
|
+
|
|
+ if (IS_SINK(i))
|
|
+ r = v4l2_subdev_get_try_compose(sd, sd_state, i);
|
|
+ else
|
|
+ r = v4l2_subdev_get_try_crop(sd, sd_state, i);
|
|
+
|
|
+ r->top = r->left = 0;
|
|
+ r->width = DCMIPP_FMT_WIDTH_DEFAULT;
|
|
+ r->height = DCMIPP_FMT_HEIGHT_DEFAULT;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_byteproc_enum_mbus_code(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_mbus_code_enum *code)
|
|
+{
|
|
+ const struct dcmipp_byteproc_pix_map *vpix;
|
|
+
|
|
+ vpix = dcmipp_byteproc_pix_map_by_index(code->index);
|
|
+ if (!vpix)
|
|
+ return -EINVAL;
|
|
+
|
|
+ code->code = vpix->code;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_byteproc_enum_frame_size(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_frame_size_enum *fse)
|
|
+{
|
|
+ const struct dcmipp_byteproc_pix_map *vpix;
|
|
+
|
|
+ if (fse->index)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* Only accept code in the pix map table */
|
|
+ vpix = dcmipp_byteproc_pix_map_by_code(fse->code);
|
|
+ if (!vpix)
|
|
+ return -EINVAL;
|
|
+
|
|
+ fse->min_width = DCMIPP_FRAME_MIN_WIDTH;
|
|
+ fse->max_width = DCMIPP_FRAME_MAX_WIDTH;
|
|
+ fse->min_height = DCMIPP_FRAME_MIN_HEIGHT;
|
|
+ fse->max_height = DCMIPP_FRAME_MAX_HEIGHT;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_byteproc_get_fmt(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
|
|
+ struct v4l2_rect *crop_rect;
|
|
+
|
|
+ mutex_lock(&byteproc->lock);
|
|
+
|
|
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
|
|
+ fmt->format = *v4l2_subdev_get_try_format(sd, sd_state, 0);
|
|
+ crop_rect = v4l2_subdev_get_try_crop(sd, sd_state, 1);
|
|
+ } else {
|
|
+ fmt->format = byteproc->sink_fmt;
|
|
+ crop_rect = &byteproc->crop;
|
|
+ }
|
|
+
|
|
+ if (IS_SRC(fmt->pad)) {
|
|
+ fmt->format.width = crop_rect->width;
|
|
+ fmt->format.height = crop_rect->height;
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&byteproc->lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_byteproc_set_fmt(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
|
|
+ struct v4l2_mbus_framefmt *sink_fmt;
|
|
+ struct v4l2_rect *crop, *compose;
|
|
+ int ret = 0;
|
|
+
|
|
+ mutex_lock(&byteproc->lock);
|
|
+
|
|
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
|
|
+ if (byteproc->streaming) {
|
|
+ ret = -EBUSY;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ sink_fmt = &byteproc->sink_fmt;
|
|
+ crop = &byteproc->crop;
|
|
+ compose = &byteproc->compose;
|
|
+ } else {
|
|
+ sink_fmt = v4l2_subdev_get_try_format(sd, sd_state, 0);
|
|
+ crop = v4l2_subdev_get_try_crop(sd, sd_state, 1);
|
|
+ compose = v4l2_subdev_get_try_compose(sd, sd_state, 0);
|
|
+ }
|
|
+
|
|
+ if (IS_SRC(fmt->pad)) {
|
|
+ fmt->format = *sink_fmt;
|
|
+ fmt->format.width = crop->width;
|
|
+ fmt->format.height = crop->height;
|
|
+ } else {
|
|
+ dcmipp_byteproc_adjust_fmt(&fmt->format);
|
|
+ crop->top = crop->left = compose->top = compose->left = 0;
|
|
+ crop->width = compose->width = fmt->format.width;
|
|
+ crop->height = compose->height = fmt->format.height;
|
|
+ *sink_fmt = fmt->format;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&byteproc->lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int dcmipp_byteproc_get_selection(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_selection *s)
|
|
+{
|
|
+ struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
|
|
+ struct v4l2_mbus_framefmt *sink_fmt;
|
|
+ struct v4l2_rect *crop, *compose;
|
|
+
|
|
+ /*
|
|
+ * In the HW, the decimation block is located prior to the crop hence:
|
|
+ * Compose is done on the sink pad
|
|
+ * Crop is done on the src pad
|
|
+ */
|
|
+ if (((s->target == V4L2_SEL_TGT_CROP) ||
|
|
+ (s->target == V4L2_SEL_TGT_CROP_BOUNDS) ||
|
|
+ (s->target == V4L2_SEL_TGT_CROP_DEFAULT)) && IS_SINK(s->pad))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (((s->target == V4L2_SEL_TGT_COMPOSE) ||
|
|
+ (s->target == V4L2_SEL_TGT_COMPOSE_BOUNDS) ||
|
|
+ (s->target == V4L2_SEL_TGT_COMPOSE_DEFAULT)) && IS_SRC(s->pad))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (s->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
|
|
+ sink_fmt = &byteproc->sink_fmt;
|
|
+ crop = &byteproc->crop;
|
|
+ compose = &byteproc->compose;
|
|
+ } else {
|
|
+ sink_fmt = v4l2_subdev_get_try_format(sd, sd_state, 0);
|
|
+ crop = v4l2_subdev_get_try_crop(sd, sd_state, 1);
|
|
+ compose = v4l2_subdev_get_try_compose(sd, sd_state, 0);
|
|
+ }
|
|
+
|
|
+ switch (s->target) {
|
|
+ case V4L2_SEL_TGT_CROP:
|
|
+ s->r = *crop;
|
|
+ break;
|
|
+ case V4L2_SEL_TGT_CROP_BOUNDS:
|
|
+ case V4L2_SEL_TGT_CROP_DEFAULT:
|
|
+ s->r = *compose;
|
|
+ break;
|
|
+ case V4L2_SEL_TGT_COMPOSE:
|
|
+ s->r = *compose;
|
|
+ break;
|
|
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
|
|
+ s->r = dcmipp_byteproc_get_compose_bound(sink_fmt);
|
|
+ break;
|
|
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
|
|
+ s->r.top = s->r.left = 0;
|
|
+ s->r.width = sink_fmt->width;
|
|
+ s->r.height = sink_fmt->height;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_byteproc_set_selection(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_selection *s)
|
|
+{
|
|
+ struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
|
|
+ struct v4l2_mbus_framefmt *sink_fmt;
|
|
+ struct v4l2_rect *crop, *compose;
|
|
+
|
|
+ /*
|
|
+ * In the HW, the decimation block is located prior to the crop hence:
|
|
+ * Compose is done on the sink pad
|
|
+ * Crop is done on the src pad
|
|
+ */
|
|
+ if (((s->target == V4L2_SEL_TGT_CROP) ||
|
|
+ (s->target == V4L2_SEL_TGT_CROP_BOUNDS) ||
|
|
+ (s->target == V4L2_SEL_TGT_CROP_DEFAULT)) && IS_SINK(s->pad))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (((s->target == V4L2_SEL_TGT_COMPOSE) ||
|
|
+ (s->target == V4L2_SEL_TGT_COMPOSE_BOUNDS) ||
|
|
+ (s->target == V4L2_SEL_TGT_COMPOSE_DEFAULT)) && IS_SRC(s->pad))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (s->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
|
|
+ sink_fmt = &byteproc->sink_fmt;
|
|
+ crop = &byteproc->crop;
|
|
+ compose = &byteproc->compose;
|
|
+ } else {
|
|
+ sink_fmt = v4l2_subdev_get_try_format(sd, sd_state, 0);
|
|
+ crop = v4l2_subdev_get_try_crop(sd, sd_state, 1);
|
|
+ compose = v4l2_subdev_get_try_compose(sd, sd_state, 0);
|
|
+ }
|
|
+
|
|
+ switch (s->target) {
|
|
+ case V4L2_SEL_TGT_CROP:
|
|
+ dcmipp_byteproc_adjust_crop(&s->r, compose);
|
|
+
|
|
+ *crop = s->r;
|
|
+
|
|
+ dev_dbg(byteproc->dev, "s_selection: crop %ux%u@(%u,%u)\n",
|
|
+ crop->width, crop->height, crop->left, crop->top);
|
|
+ break;
|
|
+ case V4L2_SEL_TGT_COMPOSE:
|
|
+ dcmipp_byteproc_adjust_compose(&s->r, sink_fmt);
|
|
+ *compose = s->r;
|
|
+ *crop = s->r;
|
|
+
|
|
+ dev_dbg(byteproc->dev, "s_selection: compose %ux%u@(%u,%u)\n",
|
|
+ compose->width, compose->height, compose->left, compose->top);
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct v4l2_subdev_pad_ops dcmipp_byteproc_pad_ops = {
|
|
+ .init_cfg = dcmipp_byteproc_init_cfg,
|
|
+ .enum_mbus_code = dcmipp_byteproc_enum_mbus_code,
|
|
+ .enum_frame_size = dcmipp_byteproc_enum_frame_size,
|
|
+ .get_fmt = dcmipp_byteproc_get_fmt,
|
|
+ .set_fmt = dcmipp_byteproc_set_fmt,
|
|
+ .get_selection = dcmipp_byteproc_get_selection,
|
|
+ .set_selection = dcmipp_byteproc_set_selection,
|
|
+};
|
|
+
|
|
+static int dcmipp_byteproc_configure_scale_crop
|
|
+ (struct dcmipp_byteproc_device *byteproc)
|
|
+{
|
|
+ const struct dcmipp_byteproc_pix_map *vpix;
|
|
+ u32 hprediv, vprediv;
|
|
+ struct v4l2_rect *crop = &byteproc->crop;
|
|
+ u32 val = 0;
|
|
+
|
|
+ /* find output format bpp */
|
|
+ vpix = dcmipp_byteproc_pix_map_by_code(byteproc->sink_fmt.code);
|
|
+ if (!vpix)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* clear decimation/crop */
|
|
+ reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_BSM_MASK);
|
|
+ reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_LSM);
|
|
+ reg_write(byteproc, DCMIPP_P0SCSTR, 0);
|
|
+ reg_write(byteproc, DCMIPP_P0SCSZR, 0);
|
|
+
|
|
+ /* Ignore decimation/crop with JPEG */
|
|
+ if (vpix->code == MEDIA_BUS_FMT_JPEG_1X8)
|
|
+ return 0;
|
|
+
|
|
+ /* decimation */
|
|
+ hprediv = byteproc->sink_fmt.width / byteproc->compose.width;
|
|
+ if (hprediv == 4)
|
|
+ val |= DCMIPP_P0PPCR_BSM_1_4 << DCMIPP_P0PPCR_BSM_SHIFT;
|
|
+ else if ((vpix->code == MEDIA_BUS_FMT_Y8_1X8) && (hprediv == 2))
|
|
+ val |= DCMIPP_P0PPCR_BSM_1_2 << DCMIPP_P0PPCR_BSM_SHIFT;
|
|
+ else if (hprediv == 2)
|
|
+ val |= DCMIPP_P0PPCR_BSM_2_4 << DCMIPP_P0PPCR_BSM_SHIFT;
|
|
+
|
|
+ vprediv = byteproc->sink_fmt.height / byteproc->compose.height;
|
|
+ if (vprediv == 2)
|
|
+ val |= DCMIPP_P0PPCR_LSM; /* one line out of two */
|
|
+
|
|
+ /* decimate using bytes and lines skipping */
|
|
+ if (val) {
|
|
+ reg_set(byteproc, DCMIPP_P0PPCR, val);
|
|
+
|
|
+ dev_dbg(byteproc->dev, "decimate to %dx%d [prediv=%dx%d]\n",
|
|
+ byteproc->compose.width, byteproc->compose.height, hprediv, vprediv);
|
|
+ }
|
|
+
|
|
+ dev_dbg(byteproc->dev, "crop to %dx%d\n", crop->width, crop->height);
|
|
+
|
|
+ /* expressed in 32-bits words on X axis, lines on Y axis */
|
|
+ reg_write(byteproc, DCMIPP_P0SCSTR,
|
|
+ (((crop->left * vpix->bpp) / 4) << DCMIPP_P0SCSTR_HSTART_SHIFT) |
|
|
+ (crop->top << DCMIPP_P0SCSTR_VSTART_SHIFT));
|
|
+ reg_write(byteproc, DCMIPP_P0SCSZR,
|
|
+ DCMIPP_P0SCSZR_ENABLE |
|
|
+ (((crop->width * vpix->bpp) / 4) << DCMIPP_P0SCSZR_HSIZE_SHIFT) |
|
|
+ (crop->height << DCMIPP_P0SCSZR_VSIZE_SHIFT));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dcmipp_byteproc_configure_framerate
|
|
+ (struct dcmipp_byteproc_device *byteproc)
|
|
+{
|
|
+ /* Frame skipping */
|
|
+ reg_clear(byteproc, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_FRATE_MASK);
|
|
+ reg_set(byteproc, DCMIPP_P0FCTCR, byteproc->frate);
|
|
+}
|
|
+
|
|
+static int dcmipp_byteproc_g_frame_interval(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_frame_interval *fi)
|
|
+{
|
|
+ struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
|
|
+
|
|
+ if (IS_SINK(fi->pad))
|
|
+ fi->interval = byteproc->sink_interval;
|
|
+ else
|
|
+ fi->interval = byteproc->src_interval;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_byteproc_s_frame_interval(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_frame_interval *fi)
|
|
+{
|
|
+ struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
|
|
+
|
|
+ mutex_lock(&byteproc->lock);
|
|
+
|
|
+ if (byteproc->streaming) {
|
|
+ mutex_unlock(&byteproc->lock);
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+ if (IS_SINK(fi->pad)) {
|
|
+ byteproc->sink_interval = fi->interval;
|
|
+ /*
|
|
+ * Setting sink frame interval resets frame skipping.
|
|
+ * Sink frame interval is propagated to src.
|
|
+ */
|
|
+ byteproc->frate = 0;
|
|
+ byteproc->src_interval = byteproc->sink_interval;
|
|
+ } else {
|
|
+ unsigned int ratio;
|
|
+ /* Normalize ratio */
|
|
+ ratio = (byteproc->sink_interval.denominator * fi->interval.numerator) /
|
|
+ (byteproc->sink_interval.numerator * fi->interval.denominator);
|
|
+
|
|
+ /* Hardware can skip 1 frame over 2, 4 or 8 */
|
|
+ byteproc->frate = ratio >= 8 ? 3 :
|
|
+ ratio >= 4 ? 2 :
|
|
+ ratio >= 2 ? 1 : 0;
|
|
+
|
|
+ /* Adjust src frame interval to what hardware can really do */
|
|
+ byteproc->src_interval.numerator = 1;
|
|
+ byteproc->src_interval.denominator =
|
|
+ (byteproc->sink_interval.denominator / byteproc->sink_interval.numerator) /
|
|
+ (1 << byteproc->frate);
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&byteproc->lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#define STOP_TIMEOUT_US 1000
|
|
+#define POLL_INTERVAL_US 50
|
|
+static int dcmipp_byteproc_s_stream(struct v4l2_subdev *sd, int enable)
|
|
+{
|
|
+ struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
|
|
+ int ret = 0;
|
|
+
|
|
+ mutex_lock(&byteproc->lock);
|
|
+ if (enable) {
|
|
+ dcmipp_byteproc_configure_framerate(byteproc);
|
|
+
|
|
+ ret = dcmipp_byteproc_configure_scale_crop(byteproc);
|
|
+ if (ret)
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+err:
|
|
+ mutex_unlock(&byteproc->lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static const struct v4l2_subdev_video_ops dcmipp_byteproc_video_ops = {
|
|
+ .g_frame_interval = dcmipp_byteproc_g_frame_interval,
|
|
+ .s_frame_interval = dcmipp_byteproc_s_frame_interval,
|
|
+ .s_stream = dcmipp_byteproc_s_stream,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_ops dcmipp_byteproc_ops = {
|
|
+ .pad = &dcmipp_byteproc_pad_ops,
|
|
+ .video = &dcmipp_byteproc_video_ops,
|
|
+};
|
|
+
|
|
+/* FIXME */
|
|
+static void dcmipp_byteproc_release(struct v4l2_subdev *sd)
|
|
+{
|
|
+ struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
|
|
+
|
|
+ kfree(byteproc);
|
|
+}
|
|
+
|
|
+static const struct v4l2_subdev_internal_ops dcmipp_byteproc_int_ops = {
|
|
+ .release = dcmipp_byteproc_release,
|
|
+};
|
|
+
|
|
+static void dcmipp_byteproc_comp_unbind(struct device *comp,
|
|
+ struct device *master,
|
|
+ void *master_data)
|
|
+{
|
|
+ struct dcmipp_ent_device *ved = dev_get_drvdata(comp);
|
|
+ struct dcmipp_byteproc_device *byteproc =
|
|
+ container_of(ved, struct dcmipp_byteproc_device, ved);
|
|
+
|
|
+ dcmipp_ent_sd_unregister(ved, &byteproc->sd);
|
|
+}
|
|
+
|
|
+static int dcmipp_byteproc_comp_bind(struct device *comp, struct device *master,
|
|
+ void *master_data)
|
|
+{
|
|
+ struct dcmipp_bind_data *bind_data = master_data;
|
|
+ struct dcmipp_platform_data *pdata = comp->platform_data;
|
|
+ struct dcmipp_byteproc_device *byteproc;
|
|
+ struct v4l2_rect r = {
|
|
+ .top = 0,
|
|
+ .left = 0,
|
|
+ .width = DCMIPP_FMT_WIDTH_DEFAULT,
|
|
+ .height = DCMIPP_FMT_HEIGHT_DEFAULT,
|
|
+ };
|
|
+ struct v4l2_fract interval = {
|
|
+ .numerator = 1,
|
|
+ .denominator = 30,
|
|
+ };
|
|
+ int ret;
|
|
+
|
|
+ /* Allocate the byteproc struct */
|
|
+ byteproc = kzalloc(sizeof(*byteproc), GFP_KERNEL);
|
|
+ if (!byteproc)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ byteproc->regs = bind_data->regs;
|
|
+
|
|
+ /* Initialize the lock */
|
|
+ mutex_init(&byteproc->lock);
|
|
+
|
|
+ /* Initialize ved and sd */
|
|
+ ret = dcmipp_ent_sd_register(&byteproc->ved, &byteproc->sd,
|
|
+ bind_data->v4l2_dev,
|
|
+ pdata->entity_name,
|
|
+ MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER, 2,
|
|
+ (const unsigned long[2]) {
|
|
+ MEDIA_PAD_FL_SINK,
|
|
+ MEDIA_PAD_FL_SOURCE,
|
|
+ },
|
|
+ &dcmipp_byteproc_int_ops,
|
|
+ &dcmipp_byteproc_ops,
|
|
+ NULL, NULL);
|
|
+ if (ret) {
|
|
+ kfree(byteproc);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ dev_set_drvdata(comp, &byteproc->ved);
|
|
+ byteproc->dev = comp;
|
|
+
|
|
+ /* Initialize the frame format */
|
|
+ byteproc->sink_fmt = fmt_default;
|
|
+ byteproc->compose = byteproc->crop = r;
|
|
+ byteproc->sink_interval = byteproc->src_interval = interval;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct component_ops dcmipp_byteproc_comp_ops = {
|
|
+ .bind = dcmipp_byteproc_comp_bind,
|
|
+ .unbind = dcmipp_byteproc_comp_unbind,
|
|
+};
|
|
+
|
|
+static int dcmipp_byteproc_probe(struct platform_device *pdev)
|
|
+{
|
|
+ return component_add(&pdev->dev, &dcmipp_byteproc_comp_ops);
|
|
+}
|
|
+
|
|
+static int dcmipp_byteproc_remove(struct platform_device *pdev)
|
|
+{
|
|
+ component_del(&pdev->dev, &dcmipp_byteproc_comp_ops);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct platform_device_id dcmipp_byteproc_driver_ids[] = {
|
|
+ {
|
|
+ .name = DCMIPP_BYTEPROC_DRV_NAME,
|
|
+ },
|
|
+ { }
|
|
+};
|
|
+
|
|
+static struct platform_driver dcmipp_byteproc_pdrv = {
|
|
+ .probe = dcmipp_byteproc_probe,
|
|
+ .remove = dcmipp_byteproc_remove,
|
|
+ .id_table = dcmipp_byteproc_driver_ids,
|
|
+ .driver = {
|
|
+ .name = DCMIPP_BYTEPROC_DRV_NAME,
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(dcmipp_byteproc_pdrv);
|
|
+
|
|
+MODULE_DEVICE_TABLE(platform, dcmipp_byteproc_driver_ids);
|
|
+
|
|
+MODULE_AUTHOR("Hugues Fruchet <hugues.fruchet@foss.st.com>");
|
|
+MODULE_AUTHOR("Alain Volmat <alain.volmat@foss.st.com>");
|
|
+MODULE_DESCRIPTION("STMicroelectronics STM32 Digital Camera Memory Interface with Pixel Processor driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-common.c b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-common.c
|
|
new file mode 100644
|
|
index 000000000..2a566bacc
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-common.c
|
|
@@ -0,0 +1,116 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Driver for STM32 Digital Camera Memory Interface Pixel Processor
|
|
+ *
|
|
+ * Copyright (C) STMicroelectronics SA 2021
|
|
+ * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
|
|
+ * Alain Volmat <alain.volmat@foss.st.com>
|
|
+ * for STMicroelectronics.
|
|
+ */
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+
|
|
+#include "dcmipp-common.h"
|
|
+
|
|
+/* Helper function to allocate and initialize pads */
|
|
+struct media_pad *dcmipp_pads_init(u16 num_pads, const unsigned long *pads_flag)
|
|
+{
|
|
+ struct media_pad *pads;
|
|
+ unsigned int i;
|
|
+
|
|
+ /* Allocate memory for the pads */
|
|
+ pads = kcalloc(num_pads, sizeof(*pads), GFP_KERNEL);
|
|
+ if (!pads)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ /* Initialize the pads */
|
|
+ for (i = 0; i < num_pads; i++) {
|
|
+ pads[i].index = i;
|
|
+ pads[i].flags = pads_flag[i];
|
|
+ }
|
|
+
|
|
+ return pads;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(dcmipp_pads_init);
|
|
+
|
|
+int dcmipp_link_validate(struct media_link *link)
|
|
+{
|
|
+ /* TODO */
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(dcmipp_link_validate);
|
|
+
|
|
+static const struct media_entity_operations dcmipp_ent_sd_mops = {
|
|
+ .link_validate = dcmipp_link_validate,
|
|
+};
|
|
+
|
|
+int dcmipp_ent_sd_register(struct dcmipp_ent_device *ved,
|
|
+ struct v4l2_subdev *sd,
|
|
+ struct v4l2_device *v4l2_dev,
|
|
+ const char *const name,
|
|
+ u32 function,
|
|
+ u16 num_pads,
|
|
+ const unsigned long *pads_flag,
|
|
+ const struct v4l2_subdev_internal_ops *sd_int_ops,
|
|
+ const struct v4l2_subdev_ops *sd_ops,
|
|
+ irq_handler_t handler,
|
|
+ irq_handler_t thread_fn)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ /* Allocate the pads. Should be released from the sd_int_op release */
|
|
+ ved->pads = dcmipp_pads_init(num_pads, pads_flag);
|
|
+ if (IS_ERR(ved->pads))
|
|
+ return PTR_ERR(ved->pads);
|
|
+
|
|
+ /* Fill the dcmipp_ent_device struct */
|
|
+ ved->ent = &sd->entity;
|
|
+
|
|
+ /* Initialize the subdev */
|
|
+ v4l2_subdev_init(sd, sd_ops);
|
|
+ sd->internal_ops = sd_int_ops;
|
|
+ sd->entity.function = function;
|
|
+ sd->entity.ops = &dcmipp_ent_sd_mops;
|
|
+ sd->owner = THIS_MODULE;
|
|
+ strscpy(sd->name, name, sizeof(sd->name));
|
|
+ v4l2_set_subdevdata(sd, ved);
|
|
+
|
|
+ /* Expose this subdev to user space */
|
|
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
|
+ if (sd->ctrl_handler)
|
|
+ sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS;
|
|
+
|
|
+ /* Initialize the media entity */
|
|
+ ret = media_entity_pads_init(&sd->entity, num_pads, ved->pads);
|
|
+ if (ret)
|
|
+ goto err_clean_pads;
|
|
+
|
|
+ /* Register the subdev with the v4l2 and the media framework */
|
|
+ ret = v4l2_device_register_subdev(v4l2_dev, sd);
|
|
+ if (ret) {
|
|
+ dev_err(v4l2_dev->dev,
|
|
+ "%s: subdev register failed (err=%d)\n",
|
|
+ name, ret);
|
|
+ goto err_clean_m_ent;
|
|
+ }
|
|
+
|
|
+ ved->handler = handler;
|
|
+ ved->thread_fn = thread_fn;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_clean_m_ent:
|
|
+ media_entity_cleanup(&sd->entity);
|
|
+err_clean_pads:
|
|
+ dcmipp_pads_cleanup(ved->pads);
|
|
+ return ret;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(dcmipp_ent_sd_register);
|
|
+
|
|
+void dcmipp_ent_sd_unregister(struct dcmipp_ent_device *ved, struct v4l2_subdev *sd)
|
|
+{
|
|
+ media_entity_cleanup(ved->ent);
|
|
+ v4l2_device_unregister_subdev(sd);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(dcmipp_ent_sd_unregister);
|
|
diff --git a/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-common.h b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-common.h
|
|
new file mode 100644
|
|
index 000000000..5cfd08595
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-common.h
|
|
@@ -0,0 +1,235 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
+/*
|
|
+ * Driver for STM32 Digital Camera Memory Interface Pixel Processor
|
|
+ *
|
|
+ * Copyright (C) STMicroelectronics SA 2021
|
|
+ * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
|
|
+ * Alain Volmat <alain.volmat@foss.st.com>
|
|
+ * for STMicroelectronics.
|
|
+ */
|
|
+
|
|
+#ifndef _DCMIPP_COMMON_H_
|
|
+#define _DCMIPP_COMMON_H_
|
|
+
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/slab.h>
|
|
+#include <media/media-device.h>
|
|
+#include <media/v4l2-device.h>
|
|
+#include <media/v4l2-fwnode.h>
|
|
+
|
|
+#define DCMIPP_PDEV_NAME "dcmipp"
|
|
+
|
|
+/* DCMIPP-specific controls */
|
|
+#define DCMIPP_CID_DCMIPP_BASE (0x00f00000 | 0xf000)
|
|
+#define DCMIPP_CID_DCMIPP_CLASS (0x00f00000 | 1)
|
|
+#define DCMIPP_CID_TEST_PATTERN (DCMIPP_CID_DCMIPP_BASE + 0)
|
|
+
|
|
+#define DCMIPP_FRAME_MAX_WIDTH 4096
|
|
+#define DCMIPP_FRAME_MAX_HEIGHT 2160
|
|
+#define DCMIPP_FRAME_MIN_WIDTH 16
|
|
+#define DCMIPP_FRAME_MIN_HEIGHT 16
|
|
+
|
|
+#define DCMIPP_FMT_WIDTH_DEFAULT 640
|
|
+#define DCMIPP_FMT_HEIGHT_DEFAULT 480
|
|
+
|
|
+#define DCMIPP_FRAME_INDEX(lin, col, width, bpp) \
|
|
+ (((lin) * (width) + (col)) * (bpp))
|
|
+
|
|
+/**
|
|
+ * struct dcmipp_colorimetry_clamp - Adjust colorimetry parameters
|
|
+ *
|
|
+ * @fmt: the pointer to struct v4l2_pix_format or
|
|
+ * struct v4l2_mbus_framefmt
|
|
+ *
|
|
+ * Entities must check if colorimetry given by the userspace is valid, if not
|
|
+ * then set them as DEFAULT
|
|
+ */
|
|
+#define dcmipp_colorimetry_clamp(fmt) \
|
|
+do { \
|
|
+ if ((fmt)->colorspace == V4L2_COLORSPACE_DEFAULT || \
|
|
+ (fmt)->colorspace > V4L2_COLORSPACE_DCI_P3) { \
|
|
+ (fmt)->colorspace = V4L2_COLORSPACE_DEFAULT; \
|
|
+ (fmt)->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; \
|
|
+ (fmt)->quantization = V4L2_QUANTIZATION_DEFAULT; \
|
|
+ (fmt)->xfer_func = V4L2_XFER_FUNC_DEFAULT; \
|
|
+ } \
|
|
+ if ((fmt)->ycbcr_enc > V4L2_YCBCR_ENC_SMPTE240M) \
|
|
+ (fmt)->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; \
|
|
+ if ((fmt)->quantization > V4L2_QUANTIZATION_LIM_RANGE) \
|
|
+ (fmt)->quantization = V4L2_QUANTIZATION_DEFAULT; \
|
|
+ if ((fmt)->xfer_func > V4L2_XFER_FUNC_SMPTE2084) \
|
|
+ (fmt)->xfer_func = V4L2_XFER_FUNC_DEFAULT; \
|
|
+} while (0)
|
|
+
|
|
+/**
|
|
+ * struct dcmipp_platform_data - platform data to components
|
|
+ *
|
|
+ * @entity_name: The name of the entity to be created
|
|
+ *
|
|
+ * Board setup code will often provide additional information using the device's
|
|
+ * platform_data field to hold additional information.
|
|
+ * When injecting a new platform_device in the component system the core needs
|
|
+ * to provide to the corresponding submodules the name of the entity that should
|
|
+ * be used when registering the subdevice in the Media Controller system.
|
|
+ */
|
|
+struct dcmipp_platform_data {
|
|
+ char entity_name[32];
|
|
+};
|
|
+
|
|
+struct dcmipp_bind_data {
|
|
+ /* Internal v4l2 parent device*/
|
|
+ struct v4l2_device *v4l2_dev;
|
|
+
|
|
+ /* Hardware resources */
|
|
+ struct reset_control *rstc;
|
|
+ void __iomem *regs;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * struct dcmipp_ent_device - core struct that represents a node in the topology
|
|
+ *
|
|
+ * @ent: the pointer to struct media_entity for the node
|
|
+ * @pads: the list of pads of the node
|
|
+ * @process_frame: callback send a frame to that node
|
|
+ * @vdev_get_format: callback that returns the current format a pad, used
|
|
+ * only when is_media_entity_v4l2_video_device(ent) returns
|
|
+ * true
|
|
+ *
|
|
+ * Each node of the topology must create a dcmipp_ent_device struct. Depending on
|
|
+ * the node it will be of an instance of v4l2_subdev or video_device struct
|
|
+ * where both contains a struct media_entity.
|
|
+ * Those structures should embedded the dcmipp_ent_device struct through
|
|
+ * v4l2_set_subdevdata() and video_set_drvdata() respectivaly, allowing the
|
|
+ * dcmipp_ent_device struct to be retrieved from the corresponding struct
|
|
+ * media_entity
|
|
+ */
|
|
+struct dcmipp_ent_device {
|
|
+ struct media_entity *ent;
|
|
+ struct media_pad *pads;
|
|
+ void * (*process_frame)(struct dcmipp_ent_device *ved,
|
|
+ const void *frame);
|
|
+ void (*vdev_get_format)(struct dcmipp_ent_device *ved,
|
|
+ struct v4l2_pix_format *fmt);
|
|
+
|
|
+ /* Parallel input device */
|
|
+ struct v4l2_fwnode_bus_parallel bus;
|
|
+ enum v4l2_mbus_type bus_type;
|
|
+ irq_handler_t handler;
|
|
+ irqreturn_t handler_ret;
|
|
+ irq_handler_t thread_fn;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * dcmipp_pads_init - initialize pads
|
|
+ *
|
|
+ * @num_pads: number of pads to initialize
|
|
+ * @pads_flags: flags to use in each pad
|
|
+ *
|
|
+ * Helper functions to allocate/initialize pads
|
|
+ */
|
|
+struct media_pad *dcmipp_pads_init(u16 num_pads,
|
|
+ const unsigned long *pads_flag);
|
|
+
|
|
+/**
|
|
+ * dcmipp_pads_cleanup - free pads
|
|
+ *
|
|
+ * @pads: pointer to the pads
|
|
+ *
|
|
+ * Helper function to free the pads initialized with dcmipp_pads_init
|
|
+ */
|
|
+static inline void dcmipp_pads_cleanup(struct media_pad *pads)
|
|
+{
|
|
+ kfree(pads);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * dcmipp_ent_sd_register - initialize and register a subdev node
|
|
+ *
|
|
+ * @ved: the dcmipp_ent_device struct to be initialize
|
|
+ * @sd: the v4l2_subdev struct to be initialize and registered
|
|
+ * @v4l2_dev: the v4l2 device to register the v4l2_subdev
|
|
+ * @name: name of the sub-device. Please notice that the name must be
|
|
+ * unique.
|
|
+ * @function: media entity function defined by MEDIA_ENT_F_* macros
|
|
+ * @num_pads: number of pads to initialize
|
|
+ * @pads_flag: flags to use in each pad
|
|
+ * @sd_int_ops: pointer to &struct v4l2_subdev_internal_ops
|
|
+ * @sd_ops: pointer to &struct v4l2_subdev_ops.
|
|
+ *
|
|
+ * Helper function initialize and register the struct dcmipp_ent_device and struct
|
|
+ * v4l2_subdev which represents a subdev node in the topology
|
|
+ */
|
|
+int dcmipp_ent_sd_register(struct dcmipp_ent_device *ved,
|
|
+ struct v4l2_subdev *sd,
|
|
+ struct v4l2_device *v4l2_dev,
|
|
+ const char *const name,
|
|
+ u32 function,
|
|
+ u16 num_pads,
|
|
+ const unsigned long *pads_flag,
|
|
+ const struct v4l2_subdev_internal_ops *sd_int_ops,
|
|
+ const struct v4l2_subdev_ops *sd_ops,
|
|
+ irq_handler_t handler,
|
|
+ irq_handler_t thread_fn);
|
|
+
|
|
+/**
|
|
+ * dcmipp_ent_sd_unregister - cleanup and unregister a subdev node
|
|
+ *
|
|
+ * @ved: the dcmipp_ent_device struct to be cleaned up
|
|
+ * @sd: the v4l2_subdev struct to be unregistered
|
|
+ *
|
|
+ * Helper function cleanup and unregister the struct dcmipp_ent_device and struct
|
|
+ * v4l2_subdev which represents a subdev node in the topology
|
|
+ */
|
|
+void dcmipp_ent_sd_unregister(struct dcmipp_ent_device *ved,
|
|
+ struct v4l2_subdev *sd);
|
|
+
|
|
+/**
|
|
+ * dcmipp_link_validate - validates a media link
|
|
+ *
|
|
+ * @link: pointer to &struct media_link
|
|
+ *
|
|
+ * This function call validates if a media link is valid for streaming.
|
|
+ */
|
|
+int dcmipp_link_validate(struct media_link *link);
|
|
+
|
|
+#define reg_write(device, reg, val) \
|
|
+ (reg_write_dbg((device)->dev, #reg, (device)->regs, (reg), (val)))
|
|
+#define reg_read(device, reg) \
|
|
+ (reg_read_dbg((device)->dev, #reg, (device)->regs, (reg)))
|
|
+#define reg_set(device, reg, mask) \
|
|
+ (reg_set_dbg((device)->dev, #reg, (device)->regs, (reg), (mask)))
|
|
+#define reg_clear(device, reg, mask) \
|
|
+ (reg_clear_dbg((device)->dev, #reg, (device)->regs, (reg), (mask)))
|
|
+
|
|
+static inline u32 reg_read_dbg(struct device *dev, const char *regname,
|
|
+ void __iomem *base, u32 reg)
|
|
+{
|
|
+ u32 val = readl_relaxed(base + reg);
|
|
+
|
|
+ dev_dbg(dev, "RD %s %#10.8x\n", regname, val);
|
|
+ return val;
|
|
+}
|
|
+
|
|
+static inline void reg_write_dbg(struct device *dev, const char *regname,
|
|
+ void __iomem *base, u32 reg, u32 val)
|
|
+{
|
|
+ dev_dbg(dev, "WR %s %#10.8x\n", regname, val);
|
|
+ writel_relaxed(val, base + reg);
|
|
+}
|
|
+
|
|
+static inline void reg_set_dbg(struct device *dev, const char *regname,
|
|
+ void __iomem *base, u32 reg, u32 mask)
|
|
+{
|
|
+ dev_dbg(dev, "SET %s %#10.8x\n", regname, mask);
|
|
+ reg_write_dbg(dev, regname, base, reg, readl_relaxed(base + reg) | mask);
|
|
+}
|
|
+
|
|
+static inline void reg_clear_dbg(struct device *dev, const char *regname,
|
|
+ void __iomem *base, u32 reg, u32 mask)
|
|
+{
|
|
+ dev_dbg(dev, "CLR %s %#10.8x\n", regname, mask);
|
|
+ reg_write_dbg(dev, regname, base, reg, readl_relaxed(base + reg) & ~mask);
|
|
+}
|
|
+
|
|
+#endif
|
|
+
|
|
diff --git a/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-core.c b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-core.c
|
|
new file mode 100644
|
|
index 000000000..38b6eaaf1
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-core.c
|
|
@@ -0,0 +1,688 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Driver for STM32 Digital Camera Memory Interface Pixel Processor
|
|
+ *
|
|
+ * Copyright (C) STMicroelectronics SA 2021
|
|
+ * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
|
|
+ * Alain Volmat <alain.volmat@foss.st.com>
|
|
+ * for STMicroelectronics.
|
|
+ */
|
|
+
|
|
+#include <linux/clk.h>
|
|
+#include <linux/component.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <linux/of_graph.h>
|
|
+#include <linux/pinctrl/consumer.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/reset.h>
|
|
+#include <media/media-device.h>
|
|
+#include <media/v4l2-device.h>
|
|
+#include <media/v4l2-fwnode.h>
|
|
+
|
|
+#include "dcmipp-common.h"
|
|
+
|
|
+#define DCMIPP_MDEV_MODEL_NAME "DCMIPP MDEV"
|
|
+
|
|
+#define DCMIPP_ENT_LINK(src, srcpad, sink, sinkpad, link_flags) { \
|
|
+ .src_ent = src, \
|
|
+ .src_pad = srcpad, \
|
|
+ .sink_ent = sink, \
|
|
+ .sink_pad = sinkpad, \
|
|
+ .flags = link_flags, \
|
|
+}
|
|
+
|
|
+#define DCMIPP_CMHWCFGR (0x200)
|
|
+#define DCMIPP_P0HWCFGR (0x400)
|
|
+#define DCMIPP_VERR (0xFF4)
|
|
+
|
|
+struct dcmipp_device {
|
|
+ /* The platform device */
|
|
+ struct platform_device pdev;
|
|
+ struct device *dev;
|
|
+
|
|
+ /* Hardware resources */
|
|
+ struct reset_control *rstc;
|
|
+ void __iomem *regs;
|
|
+ struct clk *mclk;
|
|
+ struct clk *kclk;
|
|
+
|
|
+ /* The pipeline configuration */
|
|
+ const struct dcmipp_pipeline_config *pipe_cfg;
|
|
+
|
|
+ /* The Associated media_device parent */
|
|
+ struct media_device mdev;
|
|
+
|
|
+ /* Internal v4l2 parent device*/
|
|
+ struct v4l2_device v4l2_dev;
|
|
+
|
|
+ /* Subdevices */
|
|
+ struct platform_device **subdevs;
|
|
+
|
|
+ struct v4l2_async_notifier notifier;
|
|
+};
|
|
+
|
|
+static inline struct dcmipp_device *notifier_to_dcmipp(struct v4l2_async_notifier *n)
|
|
+{
|
|
+ return container_of(n, struct dcmipp_device, notifier);
|
|
+}
|
|
+
|
|
+/* Structure which describes individual configuration for each entity */
|
|
+struct dcmipp_ent_config {
|
|
+ const char *name;
|
|
+ const char *drv;
|
|
+};
|
|
+
|
|
+/* Structure which describes links between entities */
|
|
+struct dcmipp_ent_link {
|
|
+ unsigned int src_ent;
|
|
+ u16 src_pad;
|
|
+ unsigned int sink_ent;
|
|
+ u16 sink_pad;
|
|
+ u32 flags;
|
|
+};
|
|
+
|
|
+/* Structure which describes the whole topology */
|
|
+struct dcmipp_pipeline_config {
|
|
+ const struct dcmipp_ent_config *ents;
|
|
+ size_t num_ents;
|
|
+ const struct dcmipp_ent_link *links;
|
|
+ size_t num_links;
|
|
+};
|
|
+
|
|
+/* --------------------------------------------------------------------------
|
|
+ * Topology Configuration
|
|
+ */
|
|
+
|
|
+static const struct dcmipp_ent_config stm32mp13_ent_config[] = {
|
|
+ {
|
|
+ .name = "dcmipp_parallel",
|
|
+ .drv = "dcmipp-parallel",
|
|
+ },
|
|
+ {
|
|
+ .name = "dcmipp_dump_postproc",
|
|
+ .drv = "dcmipp-byteproc",
|
|
+ },
|
|
+ {
|
|
+ .name = "dcmipp_dump_capture",
|
|
+ .drv = "dcmipp-bytecap",
|
|
+ },
|
|
+};
|
|
+
|
|
+#define ID_PARALLEL 0
|
|
+#define ID_DUMP_BYTEPROC 1
|
|
+#define ID_DUMP_CAPTURE 2
|
|
+
|
|
+static const struct dcmipp_ent_link stm32mp13_ent_links[] = {
|
|
+ DCMIPP_ENT_LINK(ID_PARALLEL, 1, ID_DUMP_BYTEPROC, 0,
|
|
+ MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE),
|
|
+ DCMIPP_ENT_LINK(ID_DUMP_BYTEPROC, 1, ID_DUMP_CAPTURE, 0,
|
|
+ MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE),
|
|
+};
|
|
+
|
|
+static const struct dcmipp_pipeline_config stm32mp13_pipe_cfg = {
|
|
+ .ents = stm32mp13_ent_config,
|
|
+ .num_ents = ARRAY_SIZE(stm32mp13_ent_config),
|
|
+ .links = stm32mp13_ent_links,
|
|
+ .num_links = ARRAY_SIZE(stm32mp13_ent_links)
|
|
+};
|
|
+
|
|
+/* -------------------------------------------------------------------------- */
|
|
+#define LINK_FLAG_TO_STR(f) ((f) == 0 ? "" :\
|
|
+ (f) == MEDIA_LNK_FL_ENABLED ? "ENABLED" :\
|
|
+ (f) == MEDIA_LNK_FL_IMMUTABLE ? "IMMUTABLE" :\
|
|
+ (f) == (MEDIA_LNK_FL_ENABLED |\
|
|
+ MEDIA_LNK_FL_IMMUTABLE) ?\
|
|
+ "ENABLED, IMMUTABLE" :\
|
|
+ "UNKNOWN")
|
|
+
|
|
+static int dcmipp_create_links(struct dcmipp_device *dcmipp)
|
|
+{
|
|
+ unsigned int i;
|
|
+ int ret;
|
|
+
|
|
+ /* Initialize the links between entities */
|
|
+ for (i = 0; i < dcmipp->pipe_cfg->num_links; i++) {
|
|
+ const struct dcmipp_ent_link *link = &dcmipp->pipe_cfg->links[i];
|
|
+ /*
|
|
+ * TODO: Check another way of retrieving ved struct without
|
|
+ * relying on platform_get_drvdata
|
|
+ */
|
|
+ struct dcmipp_ent_device *ved_src =
|
|
+ platform_get_drvdata(dcmipp->subdevs[link->src_ent]);
|
|
+ struct dcmipp_ent_device *ved_sink =
|
|
+ platform_get_drvdata(dcmipp->subdevs[link->sink_ent]);
|
|
+
|
|
+ dev_dbg(dcmipp->dev, "Create link \"%s\":%d -> %d:\"%s\" [%s]\n",
|
|
+ dcmipp->pipe_cfg->ents[link->src_ent].name,
|
|
+ link->src_pad,
|
|
+ link->sink_pad,
|
|
+ dcmipp->pipe_cfg->ents[link->sink_ent].name,
|
|
+ LINK_FLAG_TO_STR(link->flags));
|
|
+
|
|
+ ret = media_create_pad_link(ved_src->ent, link->src_pad,
|
|
+ ved_sink->ent, link->sink_pad,
|
|
+ link->flags);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_graph_init(struct dcmipp_device *dcmipp);
|
|
+
|
|
+static int dcmipp_comp_bind(struct device *master)
|
|
+{
|
|
+ struct dcmipp_device *dcmipp = platform_get_drvdata(to_platform_device(master));
|
|
+ struct dcmipp_bind_data bind_data;
|
|
+ int ret;
|
|
+
|
|
+ /* Register the v4l2 struct */
|
|
+ ret = v4l2_device_register(dcmipp->mdev.dev, &dcmipp->v4l2_dev);
|
|
+ if (ret) {
|
|
+ dev_err(dcmipp->mdev.dev,
|
|
+ "v4l2 device register failed (err=%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Bind subdevices */
|
|
+ bind_data.v4l2_dev = &dcmipp->v4l2_dev;
|
|
+ bind_data.rstc = dcmipp->rstc;
|
|
+ bind_data.regs = dcmipp->regs;
|
|
+ ret = component_bind_all(master, &bind_data);
|
|
+ if (ret)
|
|
+ goto err_v4l2_unregister;
|
|
+
|
|
+ /* Initialize links */
|
|
+ ret = dcmipp_create_links(dcmipp);
|
|
+ if (ret)
|
|
+ goto err_comp_unbind_all;
|
|
+
|
|
+ ret = dcmipp_graph_init(dcmipp);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+ media_device_unregister(&dcmipp->mdev);
|
|
+ media_device_cleanup(&dcmipp->mdev);
|
|
+err_comp_unbind_all:
|
|
+ component_unbind_all(master, NULL);
|
|
+err_v4l2_unregister:
|
|
+ v4l2_device_unregister(&dcmipp->v4l2_dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void dcmipp_comp_unbind(struct device *master)
|
|
+{
|
|
+ struct dcmipp_device *dcmipp = platform_get_drvdata(to_platform_device(master));
|
|
+
|
|
+ v4l2_async_notifier_unregister(&dcmipp->notifier);
|
|
+ v4l2_async_notifier_cleanup(&dcmipp->notifier);
|
|
+
|
|
+ media_device_unregister(&dcmipp->mdev);
|
|
+ media_device_cleanup(&dcmipp->mdev);
|
|
+ component_unbind_all(master, NULL);
|
|
+ v4l2_device_unregister(&dcmipp->v4l2_dev);
|
|
+}
|
|
+
|
|
+static int dcmipp_comp_compare(struct device *comp, void *data)
|
|
+{
|
|
+ return comp == data;
|
|
+}
|
|
+
|
|
+static struct component_match *dcmipp_add_subdevs(struct dcmipp_device *dcmipp)
|
|
+{
|
|
+ struct component_match *match = NULL;
|
|
+ struct dcmipp_platform_data pdata;
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) {
|
|
+ dev_dbg(dcmipp->dev, "new pdev for %s (%s)\n",
|
|
+ dcmipp->pipe_cfg->ents[i].drv,
|
|
+ dcmipp->pipe_cfg->ents[i].name);
|
|
+
|
|
+ strscpy(pdata.entity_name, dcmipp->pipe_cfg->ents[i].name,
|
|
+ sizeof(pdata.entity_name));
|
|
+
|
|
+ dcmipp->subdevs[i] =
|
|
+ platform_device_register_data
|
|
+ (dcmipp->dev,
|
|
+ dcmipp->pipe_cfg->ents[i].drv,
|
|
+ PLATFORM_DEVID_AUTO,
|
|
+ &pdata,
|
|
+ sizeof(pdata));
|
|
+ if (IS_ERR(dcmipp->subdevs[i])) {
|
|
+ match = ERR_CAST(dcmipp->subdevs[i]);
|
|
+ while (--i >= 0)
|
|
+ platform_device_unregister(dcmipp->subdevs[i]);
|
|
+
|
|
+ dev_err(dcmipp->mdev.dev,
|
|
+ "%s error (err=%ld)\n", __func__,
|
|
+ PTR_ERR(match));
|
|
+ return match;
|
|
+ }
|
|
+
|
|
+ component_match_add(dcmipp->dev, &match, dcmipp_comp_compare,
|
|
+ &dcmipp->subdevs[i]->dev);
|
|
+ }
|
|
+
|
|
+ return match;
|
|
+}
|
|
+
|
|
+static void dcmipp_rm_subdevs(struct dcmipp_device *dcmipp)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++)
|
|
+ platform_device_unregister(dcmipp->subdevs[i]);
|
|
+}
|
|
+
|
|
+static const struct component_master_ops dcmipp_comp_ops = {
|
|
+ .bind = dcmipp_comp_bind,
|
|
+ .unbind = dcmipp_comp_unbind,
|
|
+};
|
|
+
|
|
+static const struct of_device_id dcmipp_of_match[] = {
|
|
+ { .compatible = "st,stm32mp13-dcmipp", .data = &stm32mp13_pipe_cfg},
|
|
+ { /* end node */ },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, dcmipp_of_match);
|
|
+
|
|
+static int dcmipp_graph_notify_complete(struct v4l2_async_notifier *notifier)
|
|
+{
|
|
+ struct dcmipp_device *dcmipp = notifier_to_dcmipp(notifier);
|
|
+ int ret;
|
|
+
|
|
+ /* Register the media device */
|
|
+ ret = media_device_register(&dcmipp->mdev);
|
|
+ if (ret) {
|
|
+ dev_err(dcmipp->mdev.dev,
|
|
+ "media device register failed (err=%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Expose all subdev's nodes*/
|
|
+ ret = v4l2_device_register_subdev_nodes(&dcmipp->v4l2_dev);
|
|
+ if (ret) {
|
|
+ dev_err(dcmipp->mdev.dev,
|
|
+ "dcmipp subdev nodes registration failed (err=%d)\n",
|
|
+ ret);
|
|
+ media_device_unregister(&dcmipp->mdev);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ dev_dbg(dcmipp->dev, "Notify complete !\n");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dcmipp_graph_notify_unbind(struct v4l2_async_notifier *notifier,
|
|
+ struct v4l2_subdev *sd,
|
|
+ struct v4l2_async_subdev *asd)
|
|
+{
|
|
+ struct dcmipp_device *dcmipp = notifier_to_dcmipp(notifier);
|
|
+
|
|
+ dev_dbg(dcmipp->dev, "Removing %s\n", sd->name);
|
|
+}
|
|
+
|
|
+static irqreturn_t dcmipp_irq_thread(int irq, void *arg)
|
|
+{
|
|
+ struct dcmipp_device *dcmipp = arg;
|
|
+ struct dcmipp_ent_device *ved;
|
|
+ unsigned int i;
|
|
+
|
|
+ /* Call irq thread of each entities of pipeline */
|
|
+ for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) {
|
|
+ ved = platform_get_drvdata(dcmipp->subdevs[i]);
|
|
+ if (ved->thread_fn && ved->handler_ret == IRQ_WAKE_THREAD)
|
|
+ ved->thread_fn(irq, ved);
|
|
+ }
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static irqreturn_t dcmipp_irq_callback(int irq, void *arg)
|
|
+{
|
|
+ struct dcmipp_device *dcmipp = arg;
|
|
+ struct dcmipp_ent_device *ved;
|
|
+ irqreturn_t ret = IRQ_HANDLED;
|
|
+ unsigned int i;
|
|
+
|
|
+ /* Call irq handler of each entities of pipeline */
|
|
+ for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) {
|
|
+ ved = platform_get_drvdata(dcmipp->subdevs[i]);
|
|
+ if (ved->handler)
|
|
+ ved->handler_ret = ved->handler(irq, ved);
|
|
+ else if (ved->thread_fn)
|
|
+ ved->handler_ret = IRQ_WAKE_THREAD;
|
|
+ else
|
|
+ ved->handler_ret = IRQ_HANDLED;
|
|
+ if (ved->handler_ret != IRQ_HANDLED)
|
|
+ ret = ved->handler_ret;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int dcmipp_graph_notify_bound(struct v4l2_async_notifier *notifier,
|
|
+ struct v4l2_subdev *subdev,
|
|
+ struct v4l2_async_subdev *asd)
|
|
+{
|
|
+ struct dcmipp_device *dcmipp = notifier_to_dcmipp(notifier);
|
|
+ unsigned int ret;
|
|
+ int src_pad;
|
|
+ struct dcmipp_ent_device *sink;
|
|
+ struct device_node *np = dcmipp->dev->of_node;
|
|
+ struct v4l2_fwnode_endpoint ep = { .bus_type = 0 };
|
|
+
|
|
+ dev_dbg(dcmipp->dev, "Subdev \"%s\" bound\n", subdev->name);
|
|
+
|
|
+ /*
|
|
+ * Link this sub-device to DCMIPP, it could be
|
|
+ * a parallel camera sensor or a CSI-2 to parallel bridge
|
|
+ */
|
|
+
|
|
+ src_pad = media_entity_get_fwnode_pad(&subdev->entity,
|
|
+ subdev->fwnode,
|
|
+ MEDIA_PAD_FL_SOURCE);
|
|
+
|
|
+ /* Get bus characteristics from devicetree */
|
|
+ np = of_graph_get_next_endpoint(np, NULL);
|
|
+ if (!np) {
|
|
+ dev_err(dcmipp->dev, "Could not find the endpoint\n");
|
|
+ of_node_put(np);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(np), &ep);
|
|
+ of_node_put(np);
|
|
+ if (ret) {
|
|
+ dev_err(dcmipp->dev, "Could not parse the endpoint\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if ((ep.bus_type == V4L2_MBUS_PARALLEL ||
|
|
+ ep.bus_type == V4L2_MBUS_BT656) &&
|
|
+ ep.bus.parallel.bus_width > 0) {
|
|
+ /* Only 8 bits bus width supported with BT656 bus */
|
|
+ if (ep.bus_type == V4L2_MBUS_BT656 &&
|
|
+ ep.bus.parallel.bus_width != 8) {
|
|
+ dev_err(dcmipp->dev, "BT656 bus conflicts with %u bits bus width (8 bits required)\n",
|
|
+ ep.bus.parallel.bus_width);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Parallel input device detected
|
|
+ * Connect it to parallel subdev
|
|
+ */
|
|
+ sink = platform_get_drvdata(dcmipp->subdevs[ID_PARALLEL]);
|
|
+ sink->bus.flags = ep.bus.parallel.flags;
|
|
+ sink->bus.bus_width = ep.bus.parallel.bus_width;
|
|
+ sink->bus.data_shift = ep.bus.parallel.data_shift;
|
|
+ sink->bus_type = ep.bus_type;
|
|
+ ret = media_create_pad_link(&subdev->entity, src_pad,
|
|
+ sink->ent, 0,
|
|
+ MEDIA_LNK_FL_IMMUTABLE |
|
|
+ MEDIA_LNK_FL_ENABLED);
|
|
+ if (ret)
|
|
+ dev_err(dcmipp->dev, "Failed to create media pad link with subdev \"%s\"\n",
|
|
+ subdev->name);
|
|
+ else
|
|
+ dev_dbg(dcmipp->dev, "DCMIPP is now linked to \"%s\"\n",
|
|
+ subdev->name);
|
|
+
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static const struct v4l2_async_notifier_operations dcmipp_graph_notify_ops = {
|
|
+ .bound = dcmipp_graph_notify_bound,
|
|
+ .unbind = dcmipp_graph_notify_unbind,
|
|
+ .complete = dcmipp_graph_notify_complete,
|
|
+};
|
|
+
|
|
+static int dcmipp_graph_init(struct dcmipp_device *dcmipp)
|
|
+{
|
|
+ struct v4l2_async_subdev *asd;
|
|
+ struct device_node *ep;
|
|
+ int ret;
|
|
+
|
|
+ ep = of_graph_get_next_endpoint(dcmipp->dev->of_node, NULL);
|
|
+ if (!ep) {
|
|
+ dev_err(dcmipp->dev, "Failed to get next endpoint\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ v4l2_async_notifier_init(&dcmipp->notifier);
|
|
+
|
|
+ asd = v4l2_async_notifier_add_fwnode_remote_subdev
|
|
+ (&dcmipp->notifier, of_fwnode_handle(ep),
|
|
+ struct v4l2_async_subdev);
|
|
+
|
|
+ of_node_put(ep);
|
|
+
|
|
+ if (IS_ERR(asd)) {
|
|
+ dev_err(dcmipp->dev, "Failed to add fwnode remote subdev\n");
|
|
+ return PTR_ERR(asd);
|
|
+ }
|
|
+
|
|
+ dcmipp->notifier.ops = &dcmipp_graph_notify_ops;
|
|
+
|
|
+ ret = v4l2_async_notifier_register(&dcmipp->v4l2_dev, &dcmipp->notifier);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dcmipp->dev, "Failed to register notifier\n");
|
|
+ v4l2_async_notifier_cleanup(&dcmipp->notifier);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct dcmipp_device *dcmipp;
|
|
+ struct component_match *comp_match = NULL;
|
|
+ struct resource *res;
|
|
+ struct clk *kclk;
|
|
+ const struct dcmipp_pipeline_config *pipe_cfg;
|
|
+ int irq;
|
|
+ int ret;
|
|
+
|
|
+ dcmipp = devm_kzalloc(&pdev->dev, sizeof(struct dcmipp_device), GFP_KERNEL);
|
|
+ if (!dcmipp)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ dcmipp->dev = &pdev->dev;
|
|
+
|
|
+ pipe_cfg = of_device_get_match_data(&pdev->dev);
|
|
+ if (!pipe_cfg) {
|
|
+ dev_err(&pdev->dev, "Can't get device data\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ dcmipp->pipe_cfg = pipe_cfg;
|
|
+
|
|
+ platform_set_drvdata(pdev, dcmipp);
|
|
+
|
|
+ /* Get hardware resources from devicetree */
|
|
+ dcmipp->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
|
|
+ if (IS_ERR(dcmipp->rstc))
|
|
+ return dev_err_probe(&pdev->dev, PTR_ERR(dcmipp->rstc),
|
|
+ "Could not get reset control\n");
|
|
+
|
|
+ irq = platform_get_irq(pdev, 0);
|
|
+ if (irq <= 0) {
|
|
+ if (irq != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Could not get irq\n");
|
|
+ return irq ? irq : -ENXIO;
|
|
+ }
|
|
+
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ if (!res) {
|
|
+ dev_err(&pdev->dev, "Could not get resource\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ dcmipp->regs = devm_ioremap_resource(&pdev->dev, res);
|
|
+ if (IS_ERR(dcmipp->regs)) {
|
|
+ dev_err(&pdev->dev, "Could not map registers\n");
|
|
+ return PTR_ERR(dcmipp->regs);
|
|
+ }
|
|
+
|
|
+ ret = devm_request_threaded_irq(&pdev->dev, irq, dcmipp_irq_callback,
|
|
+ dcmipp_irq_thread, IRQF_ONESHOT,
|
|
+ dev_name(&pdev->dev), dcmipp);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Unable to request irq %d\n", irq);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Reset device */
|
|
+ ret = reset_control_assert(dcmipp->rstc);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Failed to assert the reset line\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ usleep_range(3000, 5000);
|
|
+
|
|
+ ret = reset_control_deassert(dcmipp->rstc);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Failed to deassert the reset line\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ kclk = devm_clk_get(&pdev->dev, "kclk");
|
|
+ if (IS_ERR(kclk))
|
|
+ return dev_err_probe(&pdev->dev, PTR_ERR(kclk),
|
|
+ "Unable to get kclk\n");
|
|
+ dcmipp->kclk = kclk;
|
|
+
|
|
+ /* Create platform_device for each entity in the topology */
|
|
+ dcmipp->subdevs = devm_kcalloc(&pdev->dev, dcmipp->pipe_cfg->num_ents,
|
|
+ sizeof(*dcmipp->subdevs), GFP_KERNEL);
|
|
+ if (!dcmipp->subdevs)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ comp_match = dcmipp_add_subdevs(dcmipp);
|
|
+ if (IS_ERR(comp_match))
|
|
+ return PTR_ERR(comp_match);
|
|
+
|
|
+ /* Link the media device within the v4l2_device */
|
|
+ dcmipp->v4l2_dev.mdev = &dcmipp->mdev;
|
|
+
|
|
+ /* Initialize media device */
|
|
+ strscpy(dcmipp->mdev.model, DCMIPP_MDEV_MODEL_NAME,
|
|
+ sizeof(dcmipp->mdev.model));
|
|
+ snprintf(dcmipp->mdev.bus_info, sizeof(dcmipp->mdev.bus_info),
|
|
+ "platform:%s", DCMIPP_PDEV_NAME);
|
|
+ dcmipp->mdev.dev = &pdev->dev;
|
|
+ media_device_init(&dcmipp->mdev);
|
|
+
|
|
+ /* Add self to the component system */
|
|
+ ret = component_master_add_with_match(&pdev->dev, &dcmipp_comp_ops,
|
|
+ comp_match);
|
|
+ if (ret) {
|
|
+ media_device_cleanup(&dcmipp->mdev);
|
|
+ dcmipp_rm_subdevs(dcmipp);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ pm_runtime_enable(dcmipp->dev);
|
|
+
|
|
+ dev_info(&pdev->dev, "Probe done");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct dcmipp_device *dcmipp = platform_get_drvdata(pdev);
|
|
+
|
|
+ pm_runtime_disable(&pdev->dev);
|
|
+
|
|
+ component_master_del(&pdev->dev, &dcmipp_comp_ops);
|
|
+ dcmipp_rm_subdevs(dcmipp);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static __maybe_unused int dcmipp_runtime_suspend(struct device *dev)
|
|
+{
|
|
+ struct dcmipp_device *dcmipp = dev_get_drvdata(dev);
|
|
+
|
|
+ clk_disable_unprepare(dcmipp->kclk);
|
|
+ clk_disable_unprepare(dcmipp->mclk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static __maybe_unused int dcmipp_runtime_resume(struct device *dev)
|
|
+{
|
|
+ struct dcmipp_device *dcmipp = dev_get_drvdata(dev);
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_prepare_enable(dcmipp->mclk);
|
|
+ if (ret)
|
|
+ dev_err(dev, "%s: Failed to prepare_enable clock\n", __func__);
|
|
+
|
|
+ ret = clk_prepare_enable(dcmipp->kclk);
|
|
+ if (ret)
|
|
+ dev_err(dev, "%s: Failed to prepare_enable k clock\n", __func__);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static __maybe_unused int dcmipp_suspend(struct device *dev)
|
|
+{
|
|
+ /* disable clock */
|
|
+ pm_runtime_force_suspend(dev);
|
|
+
|
|
+ /* change pinctrl state */
|
|
+ pinctrl_pm_select_sleep_state(dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static __maybe_unused int dcmipp_resume(struct device *dev)
|
|
+{
|
|
+ /* restore pinctl default state */
|
|
+ pinctrl_pm_select_default_state(dev);
|
|
+
|
|
+ /* clock enable */
|
|
+ pm_runtime_force_resume(dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct dev_pm_ops dcmipp_pm_ops = {
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(dcmipp_suspend, dcmipp_resume)
|
|
+ SET_RUNTIME_PM_OPS(dcmipp_runtime_suspend,
|
|
+ dcmipp_runtime_resume, NULL)
|
|
+};
|
|
+
|
|
+static struct platform_driver dcmipp_pdrv = {
|
|
+ .probe = dcmipp_probe,
|
|
+ .remove = dcmipp_remove,
|
|
+ .driver = {
|
|
+ .name = DCMIPP_PDEV_NAME,
|
|
+ .of_match_table = of_match_ptr(dcmipp_of_match),
|
|
+ .pm = &dcmipp_pm_ops,
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(dcmipp_pdrv);
|
|
+
|
|
+MODULE_AUTHOR("Hugues Fruchet <hugues.fruchet@foss.st.com>");
|
|
+MODULE_AUTHOR("Alain Volmat <alain.volmat@foss.st.com>");
|
|
+MODULE_DESCRIPTION("STMicroelectronics STM32 Digital Camera Memory Interface with Pixel Processor driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-parallel.c b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-parallel.c
|
|
new file mode 100644
|
|
index 000000000..d123f64e9
|
|
--- /dev/null
|
|
+++ b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-parallel.c
|
|
@@ -0,0 +1,494 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Driver for STM32 Digital Camera Memory Interface Pixel Processor
|
|
+ *
|
|
+ * Copyright (C) STMicroelectronics SA 2021
|
|
+ * Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
|
|
+ * Alain Volmat <alain.volmat@foss.st.com>
|
|
+ * for STMicroelectronics.
|
|
+ */
|
|
+
|
|
+#include <linux/component.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mod_devicetable.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/v4l2-mediabus.h>
|
|
+#include <linux/vmalloc.h>
|
|
+#include <media/v4l2-event.h>
|
|
+#include <media/v4l2-subdev.h>
|
|
+
|
|
+#include "dcmipp-common.h"
|
|
+
|
|
+#define DCMIPP_PAR_DRV_NAME "dcmipp-parallel"
|
|
+
|
|
+#define DCMIPP_PRCR (0x104)
|
|
+#define DCMIPP_PRCR_FORMAT_SHIFT 16
|
|
+#define DCMIPP_PRCR_FORMAT_YUV422 0x1E
|
|
+#define DCMIPP_PRCR_FORMAT_RGB565 0x22
|
|
+#define DCMIPP_PRCR_FORMAT_RAW8 0x2A
|
|
+#define DCMIPP_PRCR_FORMAT_G8 0x4A
|
|
+#define DCMIPP_PRCR_FORMAT_BYTE_STREAM 0x5A
|
|
+#define DCMIPP_PRCR_ESS BIT(4)
|
|
+#define DCMIPP_PRCR_PCKPOL BIT(5)
|
|
+#define DCMIPP_PRCR_HSPOL BIT(6)
|
|
+#define DCMIPP_PRCR_VSPOL BIT(7)
|
|
+#define DCMIPP_PRCR_ENABLE BIT(14)
|
|
+#define DCMIPP_PRCR_SWAPCYCLES BIT(25)
|
|
+#define DCMIPP_PRCR_SWAPBITS BIT(26)
|
|
+
|
|
+#define DCMIPP_PRESCR (0x108)
|
|
+#define DCMIPP_PRESUR (0x10c)
|
|
+
|
|
+#define IS_SINK(pad) (!(pad))
|
|
+#define IS_SRC(pad) ((pad))
|
|
+
|
|
+#define PAR_MEDIA_BUS_FMT_DEFAULT MEDIA_BUS_FMT_RGB565_2X8_LE
|
|
+
|
|
+struct dcmipp_par_pix_map {
|
|
+ unsigned int code_sink;
|
|
+ unsigned int code_src;
|
|
+ u8 prcr_format;
|
|
+ u8 prcr_swapbits;
|
|
+ u8 prcr_swapcycles;
|
|
+};
|
|
+
|
|
+#define PIXMAP_SINK_SRC_PRCR_SWAP(sink, src, prcr, swap) \
|
|
+ { \
|
|
+ .code_sink = MEDIA_BUS_FMT_##sink, \
|
|
+ .code_src = MEDIA_BUS_FMT_##src, \
|
|
+ .prcr_format = DCMIPP_PRCR_FORMAT_##prcr, \
|
|
+ .prcr_swapcycles = swap, \
|
|
+ }
|
|
+static const struct dcmipp_par_pix_map dcmipp_par_pix_map_list[] = {
|
|
+ /* RGB565 */
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(RGB565_2X8_LE, RGB565_2X8_LE, RGB565, 1),
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(RGB565_2X8_BE, RGB565_2X8_LE, RGB565, 0),
|
|
+ /* YUV422 */
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_2X8, YUYV8_2X8, YUV422, 1),
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_2X8, UYVY8_2X8, YUV422, 0),
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, UYVY8_2X8, YUV422, 1),
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, YUYV8_2X8, YUV422, 0),
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(YVYU8_2X8, YVYU8_2X8, YUV422, 1),
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(VYUY8_2X8, VYUY8_2X8, YUV422, 1),
|
|
+ /* GREY */
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(Y8_1X8, Y8_1X8, G8, 0),
|
|
+ /* Raw Bayer */
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(SBGGR8_1X8, SBGGR8_1X8, RAW8, 0),
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(SGBRG8_1X8, SGBRG8_1X8, RAW8, 0),
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(SGRBG8_1X8, SGRBG8_1X8, RAW8, 0),
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(SRGGB8_1X8, SRGGB8_1X8, RAW8, 0),
|
|
+ /* JPEG */
|
|
+ PIXMAP_SINK_SRC_PRCR_SWAP(JPEG_1X8, JPEG_1X8, BYTE_STREAM, 0),
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Search through the pix_map table, skipping two consecutive entry with the
|
|
+ * same code
|
|
+ */
|
|
+static inline const struct dcmipp_par_pix_map *dcmipp_par_pix_map_by_index
|
|
+ (unsigned int index,
|
|
+ unsigned int pad)
|
|
+{
|
|
+ const struct dcmipp_par_pix_map *l = dcmipp_par_pix_map_list;
|
|
+ unsigned int size = ARRAY_SIZE(dcmipp_par_pix_map_list);
|
|
+ unsigned int i = 0;
|
|
+ u32 prev_code = 0, cur_code;
|
|
+
|
|
+ while (i < size) {
|
|
+ if (IS_SRC(pad))
|
|
+ cur_code = l[i].code_src;
|
|
+ else
|
|
+ cur_code = l[i].code_sink;
|
|
+
|
|
+ if (cur_code == prev_code) {
|
|
+ i++;
|
|
+ continue;
|
|
+ } else {
|
|
+ prev_code = cur_code;
|
|
+ }
|
|
+
|
|
+ if (index == 0)
|
|
+ break;
|
|
+ i++;
|
|
+ index--;
|
|
+ }
|
|
+
|
|
+ if (i >= size)
|
|
+ return NULL;
|
|
+
|
|
+ return &l[i];
|
|
+}
|
|
+
|
|
+static inline const struct dcmipp_par_pix_map *dcmipp_par_pix_map_by_code
|
|
+ (u32 code_sink, u32 code_src)
|
|
+{
|
|
+ const struct dcmipp_par_pix_map *l = dcmipp_par_pix_map_list;
|
|
+ unsigned int size = ARRAY_SIZE(dcmipp_par_pix_map_list);
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < size; i++) {
|
|
+ if ((l[i].code_sink == code_sink && l[i].code_src == code_src) ||
|
|
+ (l[i].code_sink == code_src && l[i].code_src == code_sink) ||
|
|
+ (l[i].code_sink == code_sink && code_src == 0) ||
|
|
+ (code_sink == 0 && l[i].code_src == code_src))
|
|
+ return &l[i];
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+struct dcmipp_par_device {
|
|
+ struct dcmipp_ent_device ved;
|
|
+ struct v4l2_subdev sd;
|
|
+ struct device *dev;
|
|
+ /* The active format */
|
|
+ struct v4l2_mbus_framefmt sink_format;
|
|
+ struct v4l2_mbus_framefmt src_format;
|
|
+ bool streaming;
|
|
+ void __iomem *regs;
|
|
+};
|
|
+
|
|
+static const struct v4l2_mbus_framefmt fmt_default = {
|
|
+ .width = DCMIPP_FMT_WIDTH_DEFAULT,
|
|
+ .height = DCMIPP_FMT_HEIGHT_DEFAULT,
|
|
+ .code = PAR_MEDIA_BUS_FMT_DEFAULT,
|
|
+ .field = V4L2_FIELD_NONE,
|
|
+ .colorspace = V4L2_COLORSPACE_DEFAULT,
|
|
+};
|
|
+
|
|
+static int dcmipp_par_init_cfg(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < sd->entity.num_pads; i++) {
|
|
+ struct v4l2_mbus_framefmt *mf;
|
|
+
|
|
+ mf = v4l2_subdev_get_try_format(sd, sd_state, i);
|
|
+ *mf = fmt_default;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_par_enum_mbus_code(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_mbus_code_enum *code)
|
|
+{
|
|
+ const struct dcmipp_par_pix_map *vpix =
|
|
+ dcmipp_par_pix_map_by_index(code->index, code->pad);
|
|
+
|
|
+ if (!vpix)
|
|
+ return -EINVAL;
|
|
+
|
|
+ code->code = IS_SRC(code->pad) ? vpix->code_src : vpix->code_sink;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_par_enum_frame_size(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_frame_size_enum *fse)
|
|
+{
|
|
+ const struct dcmipp_par_pix_map *vpix;
|
|
+
|
|
+ if (fse->index)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* Only accept code in the pix map table */
|
|
+ vpix = dcmipp_par_pix_map_by_code(IS_SINK(fse->pad) ? fse->code : 0,
|
|
+ IS_SRC(fse->pad) ? fse->code : 0);
|
|
+ if (!vpix)
|
|
+ return -EINVAL;
|
|
+
|
|
+ fse->min_width = DCMIPP_FRAME_MIN_WIDTH;
|
|
+ fse->max_width = DCMIPP_FRAME_MAX_WIDTH;
|
|
+ fse->min_height = DCMIPP_FRAME_MIN_HEIGHT;
|
|
+ fse->max_height = DCMIPP_FRAME_MAX_HEIGHT;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_par_get_fmt(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ struct dcmipp_par_device *par =
|
|
+ container_of(sd, struct dcmipp_par_device, sd);
|
|
+
|
|
+ fmt->format = fmt->which == V4L2_SUBDEV_FORMAT_TRY ?
|
|
+ *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) :
|
|
+ (IS_SRC(fmt->pad) ? par->src_format : par->sink_format);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dcmipp_par_adjust_fmt(struct dcmipp_par_device *par,
|
|
+ struct v4l2_mbus_framefmt *fmt, __u32 pad)
|
|
+{
|
|
+ const struct dcmipp_par_pix_map *vpix;
|
|
+
|
|
+ /* Only accept code in the pix map table */
|
|
+ vpix = dcmipp_par_pix_map_by_code(IS_SINK(pad) ? fmt->code : 0,
|
|
+ IS_SRC(pad) ? fmt->code : 0);
|
|
+ if (!vpix)
|
|
+ fmt->code = fmt_default.code;
|
|
+
|
|
+ /* Exclude JPEG if BT656 bus is selected */
|
|
+ if (vpix->code_sink == MEDIA_BUS_FMT_JPEG_1X8 &&
|
|
+ par->ved.bus_type == V4L2_MBUS_BT656)
|
|
+ fmt->code = fmt_default.code;
|
|
+
|
|
+ fmt->width = clamp_t(u32, fmt->width, DCMIPP_FRAME_MIN_WIDTH,
|
|
+ DCMIPP_FRAME_MAX_WIDTH) & ~1;
|
|
+ fmt->height = clamp_t(u32, fmt->height, DCMIPP_FRAME_MIN_HEIGHT,
|
|
+ DCMIPP_FRAME_MAX_HEIGHT) & ~1;
|
|
+
|
|
+ if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE)
|
|
+ fmt->field = fmt_default.field;
|
|
+
|
|
+ dcmipp_colorimetry_clamp(fmt);
|
|
+}
|
|
+
|
|
+static int dcmipp_par_set_fmt(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_state *sd_state,
|
|
+ struct v4l2_subdev_format *fmt)
|
|
+{
|
|
+ struct dcmipp_par_device *par = v4l2_get_subdevdata(sd);
|
|
+ struct v4l2_mbus_framefmt *mf;
|
|
+
|
|
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
|
|
+ /* Do not change the format while stream is on */
|
|
+ if (par->streaming)
|
|
+ return -EBUSY;
|
|
+
|
|
+ mf = IS_SRC(fmt->pad) ? &par->src_format : &par->sink_format;
|
|
+ } else {
|
|
+ mf = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
|
|
+ }
|
|
+
|
|
+ /* Set the new format */
|
|
+ dcmipp_par_adjust_fmt(par, &fmt->format, fmt->pad);
|
|
+
|
|
+ dev_dbg(par->dev, "%s: format update: old:%dx%d (0x%x, %d, %d, %d, %d) new:%dx%d (0x%x, %d, %d, %d, %d)\n",
|
|
+ par->sd.name,
|
|
+ /* old */
|
|
+ mf->width, mf->height, mf->code,
|
|
+ mf->colorspace, mf->quantization,
|
|
+ mf->xfer_func, mf->ycbcr_enc,
|
|
+ /* new */
|
|
+ fmt->format.width, fmt->format.height, fmt->format.code,
|
|
+ fmt->format.colorspace, fmt->format.quantization,
|
|
+ fmt->format.xfer_func, fmt->format.ycbcr_enc);
|
|
+
|
|
+ *mf = fmt->format;
|
|
+
|
|
+ /* When setting the sink format, report that format on the src pad as well */
|
|
+ if (IS_SINK(fmt->pad))
|
|
+ par->src_format = fmt->format;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct v4l2_subdev_pad_ops dcmipp_par_pad_ops = {
|
|
+ .init_cfg = dcmipp_par_init_cfg,
|
|
+ .enum_mbus_code = dcmipp_par_enum_mbus_code,
|
|
+ .enum_frame_size = dcmipp_par_enum_frame_size,
|
|
+ .get_fmt = dcmipp_par_get_fmt,
|
|
+ .set_fmt = dcmipp_par_set_fmt,
|
|
+};
|
|
+
|
|
+static int dcmipp_par_configure(struct dcmipp_par_device *par)
|
|
+{
|
|
+ u32 val = 0;
|
|
+ const struct dcmipp_par_pix_map *vpix;
|
|
+
|
|
+ /* Set vertical synchronization polarity */
|
|
+ if (par->ved.bus.flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
|
|
+ val |= DCMIPP_PRCR_VSPOL;
|
|
+
|
|
+ /* Set horizontal synchronization polarity */
|
|
+ if (par->ved.bus.flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
|
|
+ val |= DCMIPP_PRCR_HSPOL;
|
|
+
|
|
+ /* Set pixel clock polarity */
|
|
+ if (par->ved.bus.flags & V4L2_MBUS_PCLK_SAMPLE_RISING)
|
|
+ val |= DCMIPP_PRCR_PCKPOL;
|
|
+
|
|
+ /*
|
|
+ * BT656 embedded synchronisation bus mode.
|
|
+ *
|
|
+ * Default SAV/EAV mode is supported here with default codes
|
|
+ * SAV=0xff000080 & EAV=0xff00009d.
|
|
+ * With DCMIPP this means LSC=SAV=0x80 & LEC=EAV=0x9d.
|
|
+ */
|
|
+ if (par->ved.bus_type == V4L2_MBUS_BT656) {
|
|
+ val |= DCMIPP_PRCR_ESS;
|
|
+
|
|
+ /* Unmask all codes */
|
|
+ reg_write(par, DCMIPP_PRESUR, 0xffffffff);/* FEC:LEC:LSC:FSC */
|
|
+
|
|
+ /* Trig on LSC=0x80 & LEC=0x9d codes, ignore FSC and FEC */
|
|
+ reg_write(par, DCMIPP_PRESCR, 0xff9d80ff);/* FEC:LEC:LSC:FSC */
|
|
+ }
|
|
+
|
|
+ /* Set format */
|
|
+ vpix = dcmipp_par_pix_map_by_code(par->sink_format.code,
|
|
+ par->src_format.code);
|
|
+ if (!vpix) {
|
|
+ dev_err(par->dev, "Invalid sink/src format configuration\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ val |= vpix->prcr_format << DCMIPP_PRCR_FORMAT_SHIFT;
|
|
+
|
|
+ /* swap LSB vs MSB within one cycle */
|
|
+ if (vpix->prcr_swapbits)
|
|
+ val |= DCMIPP_PRCR_SWAPBITS;
|
|
+
|
|
+ /* swap cycles */
|
|
+ if (vpix->prcr_swapcycles)
|
|
+ val |= DCMIPP_PRCR_SWAPCYCLES;
|
|
+
|
|
+ reg_write(par, DCMIPP_PRCR, val);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmipp_par_s_stream(struct v4l2_subdev *sd, int enable)
|
|
+{
|
|
+ struct dcmipp_par_device *par =
|
|
+ container_of(sd, struct dcmipp_par_device, sd);
|
|
+ int ret = 0;
|
|
+
|
|
+ if (enable) {
|
|
+ ret = dcmipp_par_configure(par);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Enable parallel interface */
|
|
+ reg_set(par, DCMIPP_PRCR, DCMIPP_PRCR_ENABLE);
|
|
+ } else {
|
|
+ /* Disable parallel interface */
|
|
+ reg_clear(par, DCMIPP_PRCR, DCMIPP_PRCR_ENABLE);
|
|
+ }
|
|
+
|
|
+ par->streaming = enable;
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static const struct v4l2_subdev_video_ops dcmipp_par_video_ops = {
|
|
+ .s_stream = dcmipp_par_s_stream,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_ops dcmipp_par_ops = {
|
|
+ .pad = &dcmipp_par_pad_ops,
|
|
+ .video = &dcmipp_par_video_ops,
|
|
+};
|
|
+
|
|
+static void dcmipp_par_release(struct v4l2_subdev *sd)
|
|
+{
|
|
+ struct dcmipp_par_device *par =
|
|
+ container_of(sd, struct dcmipp_par_device, sd);
|
|
+
|
|
+ kfree(par);
|
|
+}
|
|
+
|
|
+static const struct v4l2_subdev_internal_ops dcmipp_par_int_ops = {
|
|
+ .release = dcmipp_par_release,
|
|
+};
|
|
+
|
|
+static void dcmipp_par_comp_unbind(struct device *comp, struct device *master,
|
|
+ void *master_data)
|
|
+{
|
|
+ struct dcmipp_ent_device *ved = dev_get_drvdata(comp);
|
|
+ struct dcmipp_par_device *par =
|
|
+ container_of(ved, struct dcmipp_par_device, ved);
|
|
+
|
|
+ dcmipp_ent_sd_unregister(ved, &par->sd);
|
|
+}
|
|
+
|
|
+static int dcmipp_par_comp_bind(struct device *comp, struct device *master,
|
|
+ void *master_data)
|
|
+{
|
|
+ struct dcmipp_bind_data *bind_data = master_data;
|
|
+ struct dcmipp_platform_data *pdata = comp->platform_data;
|
|
+ struct dcmipp_par_device *par;
|
|
+ int ret;
|
|
+
|
|
+ /* Allocate the par struct */
|
|
+ par = kzalloc(sizeof(*par), GFP_KERNEL);
|
|
+ if (!par)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ par->regs = bind_data->regs;
|
|
+
|
|
+ /* Initialize ved and sd */
|
|
+ ret = dcmipp_ent_sd_register
|
|
+ (&par->ved, &par->sd, bind_data->v4l2_dev,
|
|
+ pdata->entity_name,
|
|
+ MEDIA_ENT_F_VID_IF_BRIDGE, 2,
|
|
+ (const unsigned long[2]) {
|
|
+ MEDIA_PAD_FL_SINK,
|
|
+ MEDIA_PAD_FL_SOURCE,
|
|
+ },
|
|
+ &dcmipp_par_int_ops, &dcmipp_par_ops,
|
|
+ NULL, NULL);
|
|
+ if (ret)
|
|
+ goto err_free_hdl;
|
|
+
|
|
+ dev_set_drvdata(comp, &par->ved);
|
|
+ par->dev = comp;
|
|
+
|
|
+ /* Initialize the frame format */
|
|
+ par->sink_format = fmt_default;
|
|
+ par->src_format = fmt_default;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_free_hdl:
|
|
+ kfree(par);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static const struct component_ops dcmipp_par_comp_ops = {
|
|
+ .bind = dcmipp_par_comp_bind,
|
|
+ .unbind = dcmipp_par_comp_unbind,
|
|
+};
|
|
+
|
|
+static int dcmipp_par_probe(struct platform_device *pdev)
|
|
+{
|
|
+ return component_add(&pdev->dev, &dcmipp_par_comp_ops);
|
|
+}
|
|
+
|
|
+static int dcmipp_par_remove(struct platform_device *pdev)
|
|
+{
|
|
+ component_del(&pdev->dev, &dcmipp_par_comp_ops);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct platform_device_id dcmipp_par_driver_ids[] = {
|
|
+ {
|
|
+ .name = DCMIPP_PAR_DRV_NAME,
|
|
+ },
|
|
+ { }
|
|
+};
|
|
+
|
|
+static struct platform_driver dcmipp_par_pdrv = {
|
|
+ .probe = dcmipp_par_probe,
|
|
+ .remove = dcmipp_par_remove,
|
|
+ .id_table = dcmipp_par_driver_ids,
|
|
+ .driver = {
|
|
+ .name = DCMIPP_PAR_DRV_NAME,
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(dcmipp_par_pdrv);
|
|
+
|
|
+MODULE_DEVICE_TABLE(platform, dcmipp_par_driver_ids);
|
|
+
|
|
+MODULE_AUTHOR("Hugues Fruchet <hugues.fruchet@foss.st.com>");
|
|
+MODULE_AUTHOR("Alain Volmat <hugues.fruchet@foss.st.com>");
|
|
+MODULE_DESCRIPTION("STMicroelectronics STM32 Digital Camera Memory Interface with Pixel Processor driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c
|
|
index 843259c30..1259d094a 100644
|
|
--- a/drivers/media/v4l2-core/v4l2-fwnode.c
|
|
+++ b/drivers/media/v4l2-core/v4l2-fwnode.c
|
|
@@ -345,6 +345,9 @@ v4l2_fwnode_endpoint_parse_parallel_bus(struct fwnode_handle *fwnode,
|
|
pr_debug("data-enable-active %s\n", v ? "high" : "low");
|
|
}
|
|
|
|
+ if (!fwnode_property_read_u32(fwnode, "pclk-max-frequency", &v))
|
|
+ bus->pclk_max_frequency = v;
|
|
+
|
|
switch (bus_type) {
|
|
default:
|
|
bus->flags = flags;
|
|
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
|
|
index 54df9cfd5..61f236e03 100644
|
|
--- a/drivers/mtd/mtdcore.c
|
|
+++ b/drivers/mtd/mtdcore.c
|
|
@@ -546,6 +546,7 @@ static int mtd_nvmem_add(struct mtd_info *mtd)
|
|
config.stride = 1;
|
|
config.read_only = true;
|
|
config.root_only = true;
|
|
+ config.ignore_wp = true;
|
|
config.no_of_node = !of_device_is_compatible(node, "nvmem-cells");
|
|
config.priv = mtd;
|
|
|
|
@@ -830,6 +831,7 @@ static struct nvmem_device *mtd_otp_nvmem_register(struct mtd_info *mtd,
|
|
config.owner = THIS_MODULE;
|
|
config.type = NVMEM_TYPE_OTP;
|
|
config.root_only = true;
|
|
+ config.ignore_wp = true;
|
|
config.reg_read = reg_read;
|
|
config.size = size;
|
|
config.of_node = np;
|
|
diff --git a/drivers/mtd/nand/raw/stm32_fmc2_nand.c b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
|
|
index 1c277fbb9..733f9857e 100644
|
|
--- a/drivers/mtd/nand/raw/stm32_fmc2_nand.c
|
|
+++ b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
|
|
@@ -9,6 +9,7 @@
|
|
#include <linux/dmaengine.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/errno.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/mfd/syscon.h>
|
|
@@ -231,6 +232,7 @@ struct stm32_fmc2_timings {
|
|
|
|
struct stm32_fmc2_nand {
|
|
struct nand_chip chip;
|
|
+ struct gpio_desc *wp_gpio;
|
|
struct stm32_fmc2_timings timings;
|
|
int ncs;
|
|
int cs_used[FMC2_MAX_CE];
|
|
@@ -1747,6 +1749,18 @@ static const struct nand_controller_ops stm32_fmc2_nfc_controller_ops = {
|
|
.setup_interface = stm32_fmc2_nfc_setup_interface,
|
|
};
|
|
|
|
+static void stm32_fmc2_nfc_wp_enable(struct stm32_fmc2_nand *nand)
|
|
+{
|
|
+ if (nand->wp_gpio)
|
|
+ gpiod_set_value(nand->wp_gpio, 1);
|
|
+}
|
|
+
|
|
+static void stm32_fmc2_nfc_wp_disable(struct stm32_fmc2_nand *nand)
|
|
+{
|
|
+ if (nand->wp_gpio)
|
|
+ gpiod_set_value(nand->wp_gpio, 0);
|
|
+}
|
|
+
|
|
static int stm32_fmc2_nfc_parse_child(struct stm32_fmc2_nfc *nfc,
|
|
struct device_node *dn)
|
|
{
|
|
@@ -1785,6 +1799,18 @@ static int stm32_fmc2_nfc_parse_child(struct stm32_fmc2_nfc *nfc,
|
|
nand->cs_used[i] = cs;
|
|
}
|
|
|
|
+ nand->wp_gpio = devm_gpiod_get_from_of_node(nfc->dev, dn,
|
|
+ "wp-gpios", 0,
|
|
+ GPIOD_OUT_HIGH, "wp");
|
|
+ if (IS_ERR(nand->wp_gpio)) {
|
|
+ ret = PTR_ERR(nand->wp_gpio);
|
|
+ if (ret != -ENOENT)
|
|
+ return dev_err_probe(nfc->dev, ret,
|
|
+ "failed to request WP GPIO\n");
|
|
+
|
|
+ nand->wp_gpio = NULL;
|
|
+ }
|
|
+
|
|
nand_set_flash_node(&nand->chip, dn);
|
|
|
|
return 0;
|
|
@@ -1960,10 +1986,12 @@ static int stm32_fmc2_nfc_probe(struct platform_device *pdev)
|
|
chip->options |= NAND_BUSWIDTH_AUTO | NAND_NO_SUBPAGE_WRITE |
|
|
NAND_USES_DMA;
|
|
|
|
+ stm32_fmc2_nfc_wp_disable(nand);
|
|
+
|
|
/* Scan to find existence of the device */
|
|
ret = nand_scan(chip, nand->ncs);
|
|
if (ret)
|
|
- goto err_release_dma;
|
|
+ goto err_wp_enable;
|
|
|
|
ret = mtd_device_register(mtd, NULL, 0);
|
|
if (ret)
|
|
@@ -1976,6 +2004,9 @@ static int stm32_fmc2_nfc_probe(struct platform_device *pdev)
|
|
err_nand_cleanup:
|
|
nand_cleanup(chip);
|
|
|
|
+err_wp_enable:
|
|
+ stm32_fmc2_nfc_wp_enable(nand);
|
|
+
|
|
err_release_dma:
|
|
if (nfc->dma_ecc_ch)
|
|
dma_release_channel(nfc->dma_ecc_ch);
|
|
@@ -2016,15 +2047,20 @@ static int stm32_fmc2_nfc_remove(struct platform_device *pdev)
|
|
|
|
clk_disable_unprepare(nfc->clk);
|
|
|
|
+ stm32_fmc2_nfc_wp_enable(nand);
|
|
+
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused stm32_fmc2_nfc_suspend(struct device *dev)
|
|
{
|
|
struct stm32_fmc2_nfc *nfc = dev_get_drvdata(dev);
|
|
+ struct stm32_fmc2_nand *nand = &nfc->nand;
|
|
|
|
clk_disable_unprepare(nfc->clk);
|
|
|
|
+ stm32_fmc2_nfc_wp_enable(nand);
|
|
+
|
|
pinctrl_pm_select_sleep_state(dev);
|
|
|
|
return 0;
|
|
@@ -2046,6 +2082,8 @@ static int __maybe_unused stm32_fmc2_nfc_resume(struct device *dev)
|
|
|
|
stm32_fmc2_nfc_init(nfc);
|
|
|
|
+ stm32_fmc2_nfc_wp_disable(nand);
|
|
+
|
|
for (chip_cs = 0; chip_cs < FMC2_MAX_CE; chip_cs++) {
|
|
if (!(nfc->cs_assigned & BIT(chip_cs)))
|
|
continue;
|
|
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
|
|
index 9aecb8302..fb7840c73 100644
|
|
--- a/drivers/nvmem/core.c
|
|
+++ b/drivers/nvmem/core.c
|
|
@@ -768,7 +768,7 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
|
|
|
|
if (config->wp_gpio)
|
|
nvmem->wp_gpio = config->wp_gpio;
|
|
- else
|
|
+ else if (!config->ignore_wp)
|
|
nvmem->wp_gpio = gpiod_get_optional(config->dev, "wp",
|
|
GPIOD_OUT_HIGH);
|
|
if (IS_ERR(nvmem->wp_gpio)) {
|
|
diff --git a/drivers/nvmem/stm32-romem.c b/drivers/nvmem/stm32-romem.c
|
|
index 354be5268..36b38add8 100644
|
|
--- a/drivers/nvmem/stm32-romem.c
|
|
+++ b/drivers/nvmem/stm32-romem.c
|
|
@@ -2,15 +2,18 @@
|
|
/*
|
|
* STM32 Factory-programmed memory read access driver
|
|
*
|
|
- * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
|
|
+ * Copyright (C) 2017-2021, STMicroelectronics - All Rights Reserved
|
|
* Author: Fabrice Gasnier <fabrice.gasnier@st.com> for STMicroelectronics.
|
|
*/
|
|
|
|
#include <linux/arm-smccc.h>
|
|
+#include <linux/clk.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/nvmem-provider.h>
|
|
#include <linux/of_device.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/tee_drv.h>
|
|
|
|
/* BSEC secure service access from non-secure */
|
|
#define STM32_SMC_BSEC 0x82001003
|
|
@@ -22,28 +25,51 @@
|
|
/* shadow registers offest */
|
|
#define STM32MP15_BSEC_DATA0 0x200
|
|
|
|
-/* 32 (x 32-bits) lower shadow registers */
|
|
+/*
|
|
+ * BSEC OTP regions: 4096 OTP bits (with 3072 effective bits)
|
|
+ * - Lower: 1K bits, 2:1 redundancy, incremental bit programming
|
|
+ * => 32 (x 32-bits) lower shadow registers = words 0 to 31
|
|
+ * - Upper: 2K bits, ECC protection, word programming only
|
|
+ * => 64 (x 32-bits) = words 32 to 95
|
|
+ */
|
|
#define STM32MP15_BSEC_NUM_LOWER 32
|
|
|
|
+#define STM32_ROMEM_AUTOSUSPEND_DELAY_MS 50
|
|
+
|
|
struct stm32_romem_cfg {
|
|
int size;
|
|
+ bool ta;
|
|
};
|
|
|
|
struct stm32_romem_priv {
|
|
void __iomem *base;
|
|
struct nvmem_config cfg;
|
|
+ struct clk *clk;
|
|
+ struct device *ta;
|
|
};
|
|
|
|
+struct device *stm32_bsec_pta_find(struct device *dev);
|
|
+static int stm32_bsec_pta_read(void *context, unsigned int offset, void *buf,
|
|
+ size_t bytes);
|
|
+
|
|
static int stm32_romem_read(void *context, unsigned int offset, void *buf,
|
|
size_t bytes)
|
|
{
|
|
struct stm32_romem_priv *priv = context;
|
|
+ struct device *dev = priv->cfg.dev;
|
|
u8 *buf8 = buf;
|
|
- int i;
|
|
+ int i, ret;
|
|
+
|
|
+ ret = pm_runtime_resume_and_get(dev);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
|
|
for (i = offset; i < offset + bytes; i++)
|
|
*buf8++ = readb_relaxed(priv->base + i);
|
|
|
|
+ pm_runtime_mark_last_busy(dev);
|
|
+ pm_runtime_put_autosuspend(dev);
|
|
+
|
|
return 0;
|
|
}
|
|
|
|
@@ -65,6 +91,17 @@ static int stm32_bsec_smc(u8 op, u32 otp, u32 data, u32 *result)
|
|
#endif
|
|
}
|
|
|
|
+static bool stm32_bsec_check(void)
|
|
+{
|
|
+ u32 val;
|
|
+ int ret;
|
|
+
|
|
+ /* check that the OP-TEE support the BSEC SMC (legacy mode) */
|
|
+ ret = stm32_bsec_smc(STM32_SMC_READ_SHADOW, 0, 0, &val);
|
|
+
|
|
+ return !ret;
|
|
+}
|
|
+
|
|
static int stm32_bsec_read(void *context, unsigned int offset, void *buf,
|
|
size_t bytes)
|
|
{
|
|
@@ -74,13 +111,19 @@ static int stm32_bsec_read(void *context, unsigned int offset, void *buf,
|
|
u8 *buf8 = buf, *val8 = (u8 *)&val;
|
|
int i, j = 0, ret, skip_bytes, size;
|
|
|
|
+ ret = pm_runtime_resume_and_get(dev);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
/* Round unaligned access to 32-bits */
|
|
roffset = rounddown(offset, 4);
|
|
skip_bytes = offset & 0x3;
|
|
rbytes = roundup(bytes + skip_bytes, 4);
|
|
|
|
- if (roffset + rbytes > priv->cfg.size)
|
|
- return -EINVAL;
|
|
+ if (roffset + rbytes > priv->cfg.size) {
|
|
+ ret = -EINVAL;
|
|
+ goto end_read;
|
|
+ }
|
|
|
|
for (i = roffset; (i < roffset + rbytes); i += 4) {
|
|
u32 otp = i >> 2;
|
|
@@ -95,7 +138,7 @@ static int stm32_bsec_read(void *context, unsigned int offset, void *buf,
|
|
if (ret) {
|
|
dev_err(dev, "Can't read data%d (%d)\n", otp,
|
|
ret);
|
|
- return ret;
|
|
+ goto end_read;
|
|
}
|
|
}
|
|
/* skip first bytes in case of unaligned read */
|
|
@@ -109,7 +152,11 @@ static int stm32_bsec_read(void *context, unsigned int offset, void *buf,
|
|
skip_bytes = 0;
|
|
}
|
|
|
|
- return 0;
|
|
+end_read:
|
|
+ pm_runtime_mark_last_busy(dev);
|
|
+ pm_runtime_put_autosuspend(dev);
|
|
+
|
|
+ return ret;
|
|
}
|
|
|
|
static int stm32_bsec_write(void *context, unsigned int offset, void *buf,
|
|
@@ -120,20 +167,61 @@ static int stm32_bsec_write(void *context, unsigned int offset, void *buf,
|
|
u32 *buf32 = buf;
|
|
int ret, i;
|
|
|
|
+ ret = pm_runtime_resume_and_get(dev);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
/* Allow only writing complete 32-bits aligned words */
|
|
- if ((bytes % 4) || (offset % 4))
|
|
- return -EINVAL;
|
|
+ if ((bytes % 4) || (offset % 4)) {
|
|
+ ret = -EINVAL;
|
|
+ goto end_write;
|
|
+ }
|
|
|
|
for (i = offset; i < offset + bytes; i += 4) {
|
|
ret = stm32_bsec_smc(STM32_SMC_PROG_OTP, i >> 2, *buf32++,
|
|
NULL);
|
|
if (ret) {
|
|
dev_err(dev, "Can't write data%d (%d)\n", i >> 2, ret);
|
|
- return ret;
|
|
+ goto end_write;
|
|
}
|
|
}
|
|
|
|
- return 0;
|
|
+ if (offset + bytes >= STM32MP15_BSEC_NUM_LOWER * 4)
|
|
+ dev_warn(dev, "Update of upper OTPs with ECC protection (word programming, only once)\n");
|
|
+
|
|
+end_write:
|
|
+ pm_runtime_mark_last_busy(dev);
|
|
+ pm_runtime_put_autosuspend(dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static bool optee_presence_check(void)
|
|
+{
|
|
+ struct device_node *np;
|
|
+ bool tee_detected = false;
|
|
+
|
|
+ /* check that the OP-TEE node is present and available. */
|
|
+ np = of_find_node_by_path("/firmware/optee");
|
|
+ if (np && of_device_is_available(np))
|
|
+ tee_detected = true;
|
|
+ of_node_put(np);
|
|
+
|
|
+ return tee_detected;
|
|
+}
|
|
+
|
|
+static void stm32_romem_disable_clk(void *data)
|
|
+{
|
|
+ struct stm32_romem_priv *priv = dev_get_drvdata(data);
|
|
+
|
|
+ pm_runtime_dont_use_autosuspend(data);
|
|
+ pm_runtime_get_sync(data);
|
|
+ pm_runtime_disable(data);
|
|
+ pm_runtime_set_suspended(data);
|
|
+ pm_runtime_put_noidle(data);
|
|
+
|
|
+ if (priv->clk)
|
|
+ clk_disable_unprepare(priv->clk);
|
|
}
|
|
|
|
static int stm32_romem_probe(struct platform_device *pdev)
|
|
@@ -142,6 +230,8 @@ static int stm32_romem_probe(struct platform_device *pdev)
|
|
struct device *dev = &pdev->dev;
|
|
struct stm32_romem_priv *priv;
|
|
struct resource *res;
|
|
+ struct nvmem_device *nvmem;
|
|
+ int ret;
|
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
@@ -158,6 +248,7 @@ static int stm32_romem_probe(struct platform_device *pdev)
|
|
priv->cfg.dev = dev;
|
|
priv->cfg.priv = priv;
|
|
priv->cfg.owner = THIS_MODULE;
|
|
+ priv->cfg.type = NVMEM_TYPE_OTP;
|
|
|
|
cfg = (const struct stm32_romem_cfg *)
|
|
of_match_device(dev->driver->of_match_table, dev)->data;
|
|
@@ -167,15 +258,108 @@ static int stm32_romem_probe(struct platform_device *pdev)
|
|
priv->cfg.reg_read = stm32_romem_read;
|
|
} else {
|
|
priv->cfg.size = cfg->size;
|
|
- priv->cfg.reg_read = stm32_bsec_read;
|
|
- priv->cfg.reg_write = stm32_bsec_write;
|
|
+ if (cfg->ta || optee_presence_check()) {
|
|
+ priv->ta = stm32_bsec_pta_find(dev);
|
|
+ /* wait for OP-TEE client driver to be up and ready */
|
|
+ if (!priv->ta) {
|
|
+ /* BSEC PTA is required or SMC not ready */
|
|
+ if (cfg->ta || !stm32_bsec_check()) {
|
|
+ return -EPROBE_DEFER;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (priv->ta) {
|
|
+ priv->cfg.read_only = true;
|
|
+ priv->cfg.reg_read = stm32_bsec_pta_read;
|
|
+ } else {
|
|
+ priv->cfg.reg_read = stm32_bsec_read;
|
|
+ priv->cfg.reg_write = stm32_bsec_write;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ platform_set_drvdata(pdev, priv);
|
|
+
|
|
+ /* the clock / pm is required only without TA support, for direct BSEC access */
|
|
+ if (!priv->ta) {
|
|
+ priv->clk = devm_clk_get_optional(&pdev->dev, NULL);
|
|
+ if (IS_ERR(priv->clk))
|
|
+ return dev_err_probe(dev, PTR_ERR(priv->clk),
|
|
+ "failed to get clock\n");
|
|
+
|
|
+ if (priv->clk) {
|
|
+ ret = clk_prepare_enable(priv->clk);
|
|
+ if (ret)
|
|
+ return dev_err_probe(dev, ret,"failed to enable clock\n");
|
|
+ }
|
|
+
|
|
+ pm_runtime_set_autosuspend_delay(dev, STM32_ROMEM_AUTOSUSPEND_DELAY_MS);
|
|
+ pm_runtime_use_autosuspend(dev);
|
|
+ pm_runtime_get_noresume(dev);
|
|
+ pm_runtime_set_active(dev);
|
|
+ pm_runtime_enable(dev);
|
|
+ pm_runtime_put(dev);
|
|
+
|
|
+ ret = devm_add_action_or_reset(dev, stm32_romem_disable_clk, dev);
|
|
+ if (ret)
|
|
+ return dev_err_probe(dev, ret,
|
|
+ "unable to register cleanup action\n");
|
|
}
|
|
|
|
- return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &priv->cfg));
|
|
+ nvmem = devm_nvmem_register(dev, &priv->cfg);
|
|
+
|
|
+ return PTR_ERR_OR_ZERO(nvmem);
|
|
+}
|
|
+
|
|
+static int __maybe_unused stm32_romem_runtime_suspend(struct device *dev)
|
|
+{
|
|
+ struct stm32_romem_priv *priv;
|
|
+
|
|
+ priv = dev_get_drvdata(dev);
|
|
+ if (!priv)
|
|
+ return -ENODEV;
|
|
+
|
|
+ if (priv->clk)
|
|
+ clk_disable_unprepare(priv->clk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __maybe_unused stm32_romem_runtime_resume(struct device *dev)
|
|
+{
|
|
+ struct stm32_romem_priv *priv;
|
|
+ int ret;
|
|
+
|
|
+ priv = dev_get_drvdata(dev);
|
|
+ if (!priv)
|
|
+ return -ENODEV;
|
|
+
|
|
+ if (priv->clk) {
|
|
+ ret = clk_prepare_enable(priv->clk);
|
|
+ if (ret) {
|
|
+ dev_err(priv->cfg.dev,
|
|
+ "Failed to prepare_enable clock (%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
+static const struct dev_pm_ops stm32_romem_pm_ops = {
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
|
+ pm_runtime_force_resume)
|
|
+ SET_RUNTIME_PM_OPS(stm32_romem_runtime_suspend,
|
|
+ stm32_romem_runtime_resume, NULL)
|
|
+};
|
|
+
|
|
static const struct stm32_romem_cfg stm32mp15_bsec_cfg = {
|
|
.size = 384, /* 96 x 32-bits data words */
|
|
+ .ta = false,
|
|
+};
|
|
+
|
|
+static const struct stm32_romem_cfg stm32mp13_bsec_cfg = {
|
|
+ .size = 384, /* 96 x 32-bits data words */
|
|
+ .ta = true,
|
|
};
|
|
|
|
static const struct of_device_id stm32_romem_of_match[] = {
|
|
@@ -183,6 +367,8 @@ static const struct of_device_id stm32_romem_of_match[] = {
|
|
.compatible = "st,stm32mp15-bsec",
|
|
.data = (void *)&stm32mp15_bsec_cfg,
|
|
}, {
|
|
+ .compatible = "st,stm32mp13-bsec",
|
|
+ .data = (void *)&stm32mp13_bsec_cfg,
|
|
},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, stm32_romem_of_match);
|
|
@@ -191,10 +377,292 @@ static struct platform_driver stm32_romem_driver = {
|
|
.probe = stm32_romem_probe,
|
|
.driver = {
|
|
.name = "stm32-romem",
|
|
+ .pm = &stm32_romem_pm_ops,
|
|
.of_match_table = of_match_ptr(stm32_romem_of_match),
|
|
},
|
|
};
|
|
-module_platform_driver(stm32_romem_driver);
|
|
+
|
|
+#if IS_ENABLED(CONFIG_OPTEE)
|
|
+/*************************************************************************
|
|
+ * BSEC PTA : OP-TEE client driver to pseudo trusted application
|
|
+ *************************************************************************/
|
|
+/*
|
|
+ * Read OTP memory
|
|
+ *
|
|
+ * [in] value a: OTP start offset in byte
|
|
+ * b: access type
|
|
+ * 0 to read from shadow
|
|
+ * 1 to read from fuse
|
|
+ * 2 to read lock status
|
|
+ * [out] memref buffer: Output buffer to store read values
|
|
+ * size: Size of OTP to be read
|
|
+ *
|
|
+ * Return codes:
|
|
+ * TEE_SUCCESS - Invoke command success
|
|
+ * TEE_ERROR_BAD_PARAMETERS - Incorrect input param
|
|
+ */
|
|
+#define PTA_BSEC_READ_MEM 0x0 /* Read OTP */
|
|
+
|
|
+/* value of PTA_BSEC access type = value[in] b */
|
|
+#define SHADOW_ACCESS 0
|
|
+#define FUSE_ACCESS 1
|
|
+#define LOCK_ACCESS 2
|
|
+
|
|
+/**
|
|
+ * struct stm32_bsec_pta_priv - OP-TEE BSEC TA private data
|
|
+ * @ctx: OP-TEE context handler.
|
|
+ * @session_id: TA session identifier.
|
|
+ */
|
|
+struct stm32_bsec_pta_priv {
|
|
+ struct tee_context *ctx;
|
|
+ u32 session_id;
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Check whether this driver supports the BSEC TA in the TEE instance
|
|
+ * represented by the params (ver/data) to this function.
|
|
+ */
|
|
+static int stm32_bsec_pta_match(struct tee_ioctl_version_data *ver, const void *data)
|
|
+{
|
|
+ /*
|
|
+ * Currently this driver only supports GP compliant, OP-TEE based TA
|
|
+ */
|
|
+ if ((ver->impl_id == TEE_IMPL_ID_OPTEE) &&
|
|
+ (ver->gen_caps & TEE_GEN_CAP_GP))
|
|
+ return 1;
|
|
+ else
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * stm32_bsec_pta_probe() - initialize the PTA BSEC
|
|
+ * @dev: the platform_device description.
|
|
+ *
|
|
+ * Return:
|
|
+ * On success, 0. On failure, -errno.
|
|
+ */
|
|
+static int stm32_bsec_pta_probe(struct device *dev)
|
|
+{
|
|
+ int rc;
|
|
+ struct tee_ioctl_open_session_arg sess_arg;
|
|
+ struct tee_client_device *tee_device = to_tee_client_device(dev);
|
|
+ struct stm32_bsec_pta_priv *priv;
|
|
+
|
|
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
+ if (!priv)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ /* Open context with TEE driver */
|
|
+ priv->ctx = tee_client_open_context(NULL, stm32_bsec_pta_match, NULL, NULL);
|
|
+ if (IS_ERR(priv->ctx)) {
|
|
+ if (PTR_ERR(priv->ctx) == -ENOENT)
|
|
+ return -EPROBE_DEFER;
|
|
+ dev_err(dev, "%s: tee_client_open_context failed\n", __func__);
|
|
+ return PTR_ERR(priv->ctx);
|
|
+ }
|
|
+
|
|
+ /* Open a session with BSEC TA */
|
|
+ memset(&sess_arg, 0, sizeof(sess_arg));
|
|
+ export_uuid(sess_arg.uuid, &tee_device->id.uuid);
|
|
+ sess_arg.clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL;
|
|
+ sess_arg.num_params = 0;
|
|
+
|
|
+ rc = tee_client_open_session(priv->ctx, &sess_arg, NULL);
|
|
+ if ((rc < 0) || (sess_arg.ret != 0)) {
|
|
+ dev_err(dev, "%s: tee_client_open_session failed, err=%x\n",
|
|
+ __func__, sess_arg.ret);
|
|
+ rc = -EINVAL;
|
|
+ goto out_tee_session;
|
|
+ }
|
|
+ priv->session_id = sess_arg.session;
|
|
+ dev_set_drvdata(dev, priv);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+out_tee_session:
|
|
+ tee_client_close_context(priv->ctx);
|
|
+ priv->ctx = NULL;
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * stm32_bsec_pta_remove() - remove the BSEC TEE device
|
|
+ * @dev: the platform_device description.
|
|
+ *
|
|
+ * Return:
|
|
+ * 0 always.
|
|
+ */
|
|
+static int stm32_bsec_pta_remove(struct device *dev)
|
|
+{
|
|
+ struct stm32_bsec_pta_priv *priv = dev_get_drvdata(dev);
|
|
+
|
|
+ if (!IS_ERR_OR_NULL(priv->ctx)) {
|
|
+ tee_client_close_session(priv->ctx, priv->session_id);
|
|
+ tee_client_close_context(priv->ctx);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * stm32_bsec_pta_read() - nvmem read access using PTA client driver
|
|
+ * @context: nvmem context => romem privdate data
|
|
+ * @offset: nvmem offset
|
|
+ * @buf: buffer to fill with nvem values
|
|
+ * @bytes: number of bytes to read
|
|
+ *
|
|
+ * Return:
|
|
+ * On success, 0. On failure, -errno.
|
|
+ */
|
|
+static int stm32_bsec_pta_read(void *context, unsigned int offset, void *buf,
|
|
+ size_t bytes)
|
|
+{
|
|
+ struct stm32_romem_priv *romem_priv = context;
|
|
+ struct device *dev;
|
|
+ struct stm32_bsec_pta_priv *priv;
|
|
+ struct tee_shm *shm;
|
|
+ struct tee_ioctl_invoke_arg arg;
|
|
+ struct tee_param param[2];
|
|
+ u8 *shm_buf;
|
|
+ u32 start, num_bytes;
|
|
+ int ret;
|
|
+
|
|
+ dev = romem_priv->ta;
|
|
+ if (!dev) {
|
|
+ pr_err("TA_BSEC invoke with driver\n");
|
|
+ return -ENXIO;
|
|
+ }
|
|
+
|
|
+ priv = dev_get_drvdata(dev);
|
|
+
|
|
+ memset(&arg, 0, sizeof(arg));
|
|
+ memset(¶m, 0, sizeof(param));
|
|
+
|
|
+ arg.func = PTA_BSEC_READ_MEM;
|
|
+ arg.session = priv->session_id;
|
|
+ arg.num_params = 2;
|
|
+
|
|
+ /* align access on 32bits */
|
|
+ start = ALIGN_DOWN(offset, 4);
|
|
+ num_bytes = round_up(offset + bytes - start, 4);
|
|
+ param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT;
|
|
+ param[0].u.value.a = start;
|
|
+ param[0].u.value.b = SHADOW_ACCESS;
|
|
+
|
|
+ shm = tee_shm_alloc(priv->ctx, num_bytes, TEE_SHM_MAPPED | TEE_SHM_DMA_BUF);
|
|
+ if (IS_ERR(shm))
|
|
+ return PTR_ERR(shm);
|
|
+
|
|
+ param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT;
|
|
+ param[1].u.memref.shm = shm;
|
|
+ param[1].u.memref.size = num_bytes;
|
|
+
|
|
+ ret = tee_client_invoke_func(priv->ctx, &arg, param);
|
|
+ if (ret < 0 || arg.ret != 0) {
|
|
+ dev_err(dev, "TA_BSEC invoke failed TEE err: %x, ret:%x\n",
|
|
+ arg.ret, ret);
|
|
+ if (!ret)
|
|
+ ret = -EIO;
|
|
+ }
|
|
+ if (!ret) {
|
|
+ shm_buf = tee_shm_get_va(shm, 0);
|
|
+ if (IS_ERR(shm_buf)) {
|
|
+ dev_err(dev, "tee_shm_get_va failed for transmit\n");
|
|
+ ret = PTR_ERR(shm_buf);
|
|
+ } else {
|
|
+ ret = 0;
|
|
+ /* read data from 32 bits aligned buffer */
|
|
+ memcpy(buf, &shm_buf[offset % 4], bytes);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ tee_shm_free(shm);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static const struct tee_client_device_id stm32_bsec_id_table[] = {
|
|
+ {
|
|
+ UUID_INIT(0x94cf71ad, 0x80e6, 0x40b5,
|
|
+ 0xa7, 0xc6, 0x3d, 0xc5, 0x01, 0xeb, 0x28, 0x03)
|
|
+ },
|
|
+ { }
|
|
+};
|
|
+
|
|
+MODULE_DEVICE_TABLE(tee, stm32_bsec_id_table);
|
|
+
|
|
+static struct tee_client_driver stm32_bsec_pta_driver = {
|
|
+ .id_table = stm32_bsec_id_table,
|
|
+ .driver = {
|
|
+ .name = "stm32-bsec-pta",
|
|
+ .bus = &tee_bus_type,
|
|
+ .probe = stm32_bsec_pta_probe,
|
|
+ .remove = stm32_bsec_pta_remove,
|
|
+ },
|
|
+};
|
|
+
|
|
+static void stm32_bsec_put_device(void *data)
|
|
+{
|
|
+ put_device(data);
|
|
+}
|
|
+
|
|
+struct device *stm32_bsec_pta_find(struct device *dev)
|
|
+{
|
|
+ struct device *pta_dev;
|
|
+
|
|
+ pta_dev = driver_find_next_device(&stm32_bsec_pta_driver.driver, NULL);
|
|
+
|
|
+ if (pta_dev && devm_add_action_or_reset(dev, stm32_bsec_put_device, pta_dev)) {
|
|
+ dev_err(dev, "unable to register cleanup action\n");
|
|
+
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ return pta_dev;
|
|
+}
|
|
+
|
|
+#else
|
|
+static int stm32_bsec_pta_read(void *context, unsigned int offset, void *buf,
|
|
+ size_t bytes)
|
|
+{
|
|
+ pr_debug("%s: TA BSEC request without OPTEE support\n", __func__);
|
|
+
|
|
+ return -ENXIO;
|
|
+}
|
|
+struct device *stm32_bsec_pta_find(struct device *dev)
|
|
+{
|
|
+ pr_debug("%s: TA BSEC request without OPTEE support\n", __func__);
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int __init stm32_romem_init(void)
|
|
+{
|
|
+ int rc;
|
|
+
|
|
+ rc = platform_driver_register(&stm32_romem_driver);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+
|
|
+#if IS_ENABLED(CONFIG_OPTEE)
|
|
+ rc = driver_register(&stm32_bsec_pta_driver.driver);
|
|
+#endif
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static void __exit stm32_romem_exit(void)
|
|
+{
|
|
+ platform_driver_unregister(&stm32_romem_driver);
|
|
+#if IS_ENABLED(CONFIG_OPTEE)
|
|
+ driver_unregister(&stm32_bsec_pta_driver.driver);
|
|
+#endif
|
|
+}
|
|
+
|
|
+module_init(stm32_romem_init);
|
|
+module_exit(stm32_romem_exit);
|
|
|
|
MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>");
|
|
MODULE_DESCRIPTION("STMicroelectronics STM32 RO-MEM");
|
|
diff --git a/drivers/of/platform.c b/drivers/of/platform.c
|
|
index 74afbb7a4..654ca9e98 100644
|
|
--- a/drivers/of/platform.c
|
|
+++ b/drivers/of/platform.c
|
|
@@ -537,6 +537,10 @@ static int __init of_platform_default_populate_init(void)
|
|
of_node_put(node);
|
|
}
|
|
|
|
+ node = of_get_compatible_child(of_chosen, "simple-framebuffer");
|
|
+ of_platform_device_create(node, NULL, NULL);
|
|
+ of_node_put(node);
|
|
+
|
|
/* Populate everything else. */
|
|
of_platform_default_populate(NULL, NULL, NULL);
|
|
|
|
diff --git a/drivers/pwm/pwm-stm32-lp.c b/drivers/pwm/pwm-stm32-lp.c
|
|
index 3115abb3f..917428242 100644
|
|
--- a/drivers/pwm/pwm-stm32-lp.c
|
|
+++ b/drivers/pwm/pwm-stm32-lp.c
|
|
@@ -58,7 +58,7 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
|
|
/* Calculate the period and prescaler value */
|
|
div = (unsigned long long)clk_get_rate(priv->clk) * state->period;
|
|
- do_div(div, NSEC_PER_SEC);
|
|
+ div = DIV_ROUND_CLOSEST_ULL(div, NSEC_PER_SEC);
|
|
if (!div) {
|
|
/* Clock is too slow to achieve requested period. */
|
|
dev_dbg(priv->chip.dev, "Can't reach %llu ns\n", state->period);
|
|
@@ -78,7 +78,7 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
|
|
/* Calculate the duty cycle */
|
|
dty = prd * state->duty_cycle;
|
|
- do_div(dty, state->period);
|
|
+ dty = DIV_ROUND_CLOSEST_ULL(dty, state->period);
|
|
|
|
if (!cstate.enabled) {
|
|
/* enable clock to drive PWM counter */
|
|
diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c
|
|
index 794ca5b02..24aab0450 100644
|
|
--- a/drivers/pwm/pwm-stm32.c
|
|
+++ b/drivers/pwm/pwm-stm32.c
|
|
@@ -207,6 +207,10 @@ static int stm32_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
regmap_write(priv->regmap, TIM_ARR, priv->max_arr);
|
|
regmap_write(priv->regmap, TIM_PSC, psc);
|
|
|
|
+ /* Reset input selector to its default input and disable slave mode */
|
|
+ regmap_write(priv->regmap, TIM_TISEL, 0x0);
|
|
+ regmap_write(priv->regmap, TIM_SMCR, 0x0);
|
|
+
|
|
/* Map TI1 or TI2 PWM input to IC1 & IC2 (or TI3/4 to IC3 & IC4) */
|
|
regmap_update_bits(priv->regmap,
|
|
pwm->hwpwm < 2 ? TIM_CCMR1 : TIM_CCMR2,
|
|
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
|
|
index e8a30c4c5..c73f79a37 100644
|
|
--- a/drivers/soc/Kconfig
|
|
+++ b/drivers/soc/Kconfig
|
|
@@ -17,6 +17,7 @@ source "drivers/soc/renesas/Kconfig"
|
|
source "drivers/soc/rockchip/Kconfig"
|
|
source "drivers/soc/samsung/Kconfig"
|
|
source "drivers/soc/sifive/Kconfig"
|
|
+source "drivers/soc/st/Kconfig"
|
|
source "drivers/soc/sunxi/Kconfig"
|
|
source "drivers/soc/tegra/Kconfig"
|
|
source "drivers/soc/ti/Kconfig"
|
|
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
|
|
index a05e9fbcd..e4909f646 100644
|
|
--- a/drivers/soc/Makefile
|
|
+++ b/drivers/soc/Makefile
|
|
@@ -23,6 +23,7 @@ obj-y += renesas/
|
|
obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
|
|
obj-$(CONFIG_SOC_SAMSUNG) += samsung/
|
|
obj-$(CONFIG_SOC_SIFIVE) += sifive/
|
|
+obj-$(CONFIG_ARCH_STM32) += st/
|
|
obj-y += sunxi/
|
|
obj-$(CONFIG_ARCH_TEGRA) += tegra/
|
|
obj-y += ti/
|
|
diff --git a/drivers/soc/st/Kconfig b/drivers/soc/st/Kconfig
|
|
new file mode 100644
|
|
index 000000000..8fde9d232
|
|
--- /dev/null
|
|
+++ b/drivers/soc/st/Kconfig
|
|
@@ -0,0 +1,25 @@
|
|
+if ARCH_STM32
|
|
+
|
|
+config STM32_HDP
|
|
+ bool "STMicroelectronics STM32MP157 Hardware Debug Port (HDP) pin control"
|
|
+ depends on MACH_STM32MP157 || MACH_STM32MP13
|
|
+ default n if MACH_STM32MP157 || MACH_STM32MP13
|
|
+ help
|
|
+ The Hardware Debug Port allows the observation of internal signals. By using multiplexers,
|
|
+ up to 16 signals for each of 8-bit output can be observed.
|
|
+
|
|
+config STM32_HSLV
|
|
+ tristate "STMicroelectronics STM32 HSLV driver"
|
|
+ depends on OF && MACH_STM32MP13
|
|
+ default MACH_STM32MP13
|
|
+ help
|
|
+ This driver supports internal high speed low voltage configuration
|
|
+ in the STMicroelectronics STM32 chips.
|
|
+
|
|
+config STM32_PM_DOMAINS
|
|
+ bool "STM32 PM domains"
|
|
+ depends on MACH_STM32MP157
|
|
+ select PM_GENERIC_DOMAINS
|
|
+ default y if MACH_STM32MP157
|
|
+
|
|
+endif # ARCH_STM32
|
|
diff --git a/drivers/soc/st/Makefile b/drivers/soc/st/Makefile
|
|
new file mode 100644
|
|
index 000000000..b57d95080
|
|
--- /dev/null
|
|
+++ b/drivers/soc/st/Makefile
|
|
@@ -0,0 +1,3 @@
|
|
+obj-$(CONFIG_STM32_HDP) += stm32_hdp.o
|
|
+obj-$(CONFIG_STM32_HSLV) += stm32-hslv.o
|
|
+obj-$(CONFIG_STM32_PM_DOMAINS) += stm32_pm_domain.o
|
|
diff --git a/drivers/soc/st/stm32-hslv.c b/drivers/soc/st/stm32-hslv.c
|
|
new file mode 100644
|
|
index 000000000..224b0cbd8
|
|
--- /dev/null
|
|
+++ b/drivers/soc/st/stm32-hslv.c
|
|
@@ -0,0 +1,157 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+// Copyright (C) STMicroelectronics 2021
|
|
+// Authors: Pascal Paillet <p.paillet@st.com>.
|
|
+
|
|
+#include <linux/mfd/syscon.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
+
|
|
+#define MAX_HS_VOLTAGE 2700000
|
|
+
|
|
+struct stm32_hslv_data {
|
|
+ struct device *dev;
|
|
+ struct regmap *regmap;
|
|
+ u32 reg, mask;
|
|
+
|
|
+ struct regulator *regu;
|
|
+ struct notifier_block hslv_nb;
|
|
+};
|
|
+
|
|
+static int hslv_set_speed(struct stm32_hslv_data *priv, int uV)
|
|
+{
|
|
+ int ret;
|
|
+ unsigned int val;
|
|
+
|
|
+ if (uV < MAX_HS_VOLTAGE) {
|
|
+ dev_info(priv->dev, "HSLV high speed\n");
|
|
+ ret = regmap_write(priv->regmap, priv->reg, priv->mask);
|
|
+ if (ret) {
|
|
+ dev_err(priv->dev, "set hslv failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ } else {
|
|
+ /* Check that high speed is not set while voltage is high */
|
|
+ ret = regmap_read(priv->regmap, priv->reg, &val);
|
|
+ if (ret) {
|
|
+ dev_err(priv->dev, "hslv read failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ WARN_ON(val != 0);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int hslv_event(struct notifier_block *nb, unsigned long event,
|
|
+ void *data)
|
|
+{
|
|
+ struct stm32_hslv_data *priv = container_of(nb, struct stm32_hslv_data,
|
|
+ hslv_nb);
|
|
+ int ret;
|
|
+
|
|
+ if (event & REGULATOR_EVENT_PRE_VOLTAGE_CHANGE) {
|
|
+ /* Prevent forbidden high voltage + high speed configuration */
|
|
+ dev_info(priv->dev, "HSLV low speed\n");
|
|
+ ret = regmap_write(priv->regmap, priv->reg, 0);
|
|
+ if (ret) {
|
|
+ dev_err(priv->dev, "set hslv failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if ((event & REGULATOR_EVENT_VOLTAGE_CHANGE) ||
|
|
+ (event & REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE)) {
|
|
+ int uV = (unsigned long)data;
|
|
+
|
|
+ ret = hslv_set_speed(priv, uV);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_hslv_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+ struct stm32_hslv_data *priv;
|
|
+ int ret, uV;
|
|
+
|
|
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct stm32_hslv_data),
|
|
+ GFP_KERNEL);
|
|
+ if (!priv)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ priv->dev = &pdev->dev;
|
|
+
|
|
+ priv->regu = devm_regulator_get(&pdev->dev, "hslv");
|
|
+ if (IS_ERR(priv->regu)) {
|
|
+ ret = PTR_ERR(priv->regu);
|
|
+ if (ret != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "cannot get regulator: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ priv->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscon");
|
|
+ if (IS_ERR(priv->regmap)) {
|
|
+ if (PTR_ERR(priv->regmap) != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "syscon required\n");
|
|
+ return PTR_ERR(priv->regmap);
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32_index(np, "st,syscon", 1, &priv->reg);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "syscon offset required !\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32_index(np, "st,syscon", 2, &priv->mask);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "syscon mask required !\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ uV = regulator_get_voltage(priv->regu);
|
|
+ if (uV < 0) {
|
|
+ dev_err(priv->dev, "get voltage failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Set initial state */
|
|
+ ret = hslv_set_speed(priv, uV);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ priv->hslv_nb.notifier_call = hslv_event;
|
|
+
|
|
+ ret = devm_regulator_register_notifier(priv->regu,
|
|
+ &priv->hslv_nb);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Failed to register HSLV notifier: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id __maybe_unused stm32_hslv_of_match[] = {
|
|
+ { .compatible = "st,stm32mp13,hslv", },
|
|
+ {},
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, stm32_hslv_of_match);
|
|
+
|
|
+static struct platform_driver stm32_hslv_driver = {
|
|
+ .probe = stm32_hslv_probe,
|
|
+ .driver = {
|
|
+ .name = "stm32mp13-hslv",
|
|
+ .of_match_table = of_match_ptr(stm32_hslv_of_match),
|
|
+ },
|
|
+};
|
|
+module_platform_driver(stm32_hslv_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("STM32MP1 HSLV config assistant driver");
|
|
+MODULE_AUTHOR("Pascal Paillet <p.paillet@st.com>");
|
|
+MODULE_LICENSE("GPL v2");
|
|
diff --git a/drivers/soc/st/stm32_hdp.c b/drivers/soc/st/stm32_hdp.c
|
|
new file mode 100644
|
|
index 000000000..47687ebd1
|
|
--- /dev/null
|
|
+++ b/drivers/soc/st/stm32_hdp.c
|
|
@@ -0,0 +1,242 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
|
|
+ * Author: Christophe Roullier <christophe.roullier@st.com>
|
|
+ * for STMicroelectronics.
|
|
+ */
|
|
+
|
|
+#include <linux/clk.h>
|
|
+#include <linux/debugfs.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/pinctrl/pinconf.h>
|
|
+#include <linux/pinctrl/pinconf-generic.h>
|
|
+#include <linux/pinctrl/pinctrl.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/suspend.h>
|
|
+
|
|
+#define HDP_CTRL_ENABLE 1
|
|
+#define HDP_CTRL_DISABLE 0
|
|
+
|
|
+enum {
|
|
+ HDP_CTRL = 0,
|
|
+ HDP_MUX = 0x4,
|
|
+ HDP_VAL = 0x10,
|
|
+ HDP_GPOSET = 0x14,
|
|
+ HDP_GPOCLR = 0x18,
|
|
+ HDP_GPOVAL = 0x1c,
|
|
+ HDP_VERR = 0x3f4,
|
|
+ HDP_IPIDR = 0x3f8,
|
|
+ HDP_SIDR = 0x3fc
|
|
+} HDP_register_offsets;
|
|
+
|
|
+struct data_priv {
|
|
+ struct clk *clk;
|
|
+ int clk_is_enabled;
|
|
+ struct dentry *pwr_dentry;
|
|
+ unsigned char __iomem *hdp_membase;
|
|
+ unsigned int hdp_ctrl;
|
|
+ unsigned int hdp_mux;
|
|
+};
|
|
+
|
|
+/* enable/disable */
|
|
+static int stm32_hdp_enable_set(void *data, int val)
|
|
+{
|
|
+ struct data_priv *e = (struct data_priv *)data;
|
|
+
|
|
+ if (!e->clk)
|
|
+ return -EPERM;
|
|
+
|
|
+ if (val == 1) {
|
|
+ if (clk_prepare_enable(e->clk) < 0) {
|
|
+ pr_err("Failed to enable HDP clock\n");
|
|
+ return -EPERM;
|
|
+ }
|
|
+ e->clk_is_enabled = 1;
|
|
+ } else {
|
|
+ clk_disable_unprepare(e->clk);
|
|
+ e->clk_is_enabled = 0;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_hdp_fops_set(void *data, u64 val)
|
|
+{
|
|
+ unsigned char __iomem *addr = (unsigned char __iomem *)data;
|
|
+
|
|
+ writel_relaxed(val, addr);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_hdp_fops_get(void *data, u64 *val)
|
|
+{
|
|
+ unsigned char __iomem *addr = (unsigned char __iomem *)data;
|
|
+
|
|
+ *val = readl_relaxed(addr);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+DEFINE_SIMPLE_ATTRIBUTE(stm32_hdp_fops, stm32_hdp_fops_get,
|
|
+ stm32_hdp_fops_set, "0x%llx\n");
|
|
+
|
|
+static int stm32_hdp_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct resource *res;
|
|
+
|
|
+ struct data_priv *data;
|
|
+ struct dentry *r;
|
|
+
|
|
+ int ret;
|
|
+ const __be32 *getmuxing;
|
|
+ u32 muxing, version;
|
|
+
|
|
+ if (!np)
|
|
+ return -ENODEV;
|
|
+
|
|
+ data = devm_kzalloc(&pdev->dev, sizeof(struct data_priv), GFP_KERNEL);
|
|
+ if (!data)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ data->hdp_membase = devm_ioremap_resource(&pdev->dev, res);
|
|
+ if (IS_ERR(data->hdp_membase))
|
|
+ return PTR_ERR(data->hdp_membase);
|
|
+
|
|
+ /* Get HDP clocks */
|
|
+ data->clk = devm_clk_get(dev, "hdp");
|
|
+ if (IS_ERR(data->clk)) {
|
|
+ dev_err(dev, "No HDP CK clock provided...\n");
|
|
+ return PTR_ERR(data->clk);
|
|
+ }
|
|
+
|
|
+ /* Enable clock */
|
|
+ ret = stm32_hdp_enable_set(data, 1);
|
|
+ if (ret != 0)
|
|
+ return ret;
|
|
+
|
|
+ getmuxing = of_get_property(np, "muxing-hdp", NULL);
|
|
+ if (!getmuxing) {
|
|
+ dev_err(dev,
|
|
+ "no muxing-hdp property in node\n");
|
|
+ /* Disable clock */
|
|
+ ret = stm32_hdp_enable_set(data, 0);
|
|
+ if (ret != 0)
|
|
+ return ret;
|
|
+
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* add hdp directory */
|
|
+ r = debugfs_create_dir("hdp", NULL);
|
|
+ if (!r) {
|
|
+ dev_err(dev, "Unable to create HDP debugFS\n");
|
|
+ /* Disable clock */
|
|
+ ret = stm32_hdp_enable_set(data, 0);
|
|
+ if (ret != 0)
|
|
+ return ret;
|
|
+
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ debugfs_create_file("ctrl", 0644, r,
|
|
+ data->hdp_membase + HDP_CTRL, &stm32_hdp_fops);
|
|
+ debugfs_create_file("mux", 0644, r,
|
|
+ data->hdp_membase + HDP_MUX, &stm32_hdp_fops);
|
|
+ debugfs_create_file("val", 0644, r,
|
|
+ data->hdp_membase + HDP_VAL, &stm32_hdp_fops);
|
|
+ debugfs_create_file("gposet", 0644, r,
|
|
+ data->hdp_membase + HDP_GPOSET, &stm32_hdp_fops);
|
|
+ debugfs_create_file("gpoclr", 0644, r,
|
|
+ data->hdp_membase + HDP_GPOCLR, &stm32_hdp_fops);
|
|
+ debugfs_create_file("gpoval", 0644, r,
|
|
+ data->hdp_membase + HDP_GPOVAL, &stm32_hdp_fops);
|
|
+
|
|
+ /* Enable HDP */
|
|
+ writel(HDP_CTRL_ENABLE, data->hdp_membase + HDP_CTRL);
|
|
+
|
|
+ /* HDP Multiplexing */
|
|
+ muxing = of_read_number(getmuxing,
|
|
+ of_n_addr_cells(np));
|
|
+
|
|
+ writel(muxing, data->hdp_membase + HDP_MUX);
|
|
+
|
|
+ platform_set_drvdata(pdev, data);
|
|
+
|
|
+ /* Get Majeur, Minor version */
|
|
+ version = readl(data->hdp_membase + HDP_VERR);
|
|
+
|
|
+ dev_info(dev, "STM32 HDP version %d.%d initialized\n",
|
|
+ version >> 4, version & 0x0F);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_hdp_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct data_priv *data = platform_get_drvdata(pdev);
|
|
+
|
|
+ /* Disable HDP */
|
|
+ writel(HDP_CTRL_DISABLE, data->hdp_membase + HDP_CTRL);
|
|
+
|
|
+ if (data->clk) {
|
|
+ if (data->clk_is_enabled)
|
|
+ clk_disable_unprepare(data->clk);
|
|
+ }
|
|
+
|
|
+ pr_info("driver STM32 HDP removed\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+static int stm32_hdp_suspend(struct device *dev)
|
|
+{
|
|
+ struct data_priv *data = dev_get_drvdata(dev);
|
|
+
|
|
+ data->hdp_ctrl = readl_relaxed(data->hdp_membase + HDP_CTRL);
|
|
+ data->hdp_mux = readl_relaxed(data->hdp_membase + HDP_MUX);
|
|
+
|
|
+ pinctrl_pm_select_sleep_state(dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_hdp_resume(struct device *dev)
|
|
+{
|
|
+ struct data_priv *data = dev_get_drvdata(dev);
|
|
+
|
|
+ writel_relaxed(data->hdp_ctrl, data->hdp_membase + HDP_CTRL);
|
|
+ writel_relaxed(data->hdp_mux, data->hdp_membase + HDP_MUX);
|
|
+
|
|
+ pinctrl_pm_select_default_state(dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#endif /* CONFIG_PM_SLEEP */
|
|
+
|
|
+static SIMPLE_DEV_PM_OPS(stm32_hdp_pm_ops,
|
|
+ stm32_hdp_suspend,
|
|
+ stm32_hdp_resume);
|
|
+
|
|
+static const struct of_device_id hdp_match[] = {
|
|
+ { .compatible = "st,stm32mp1-hdp",},
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, hdp_match);
|
|
+
|
|
+static struct platform_driver hdp_driver = {
|
|
+ .probe = stm32_hdp_probe,
|
|
+ .remove = stm32_hdp_remove,
|
|
+ .driver = {
|
|
+ .name = "hdp",
|
|
+ .of_match_table = hdp_match,
|
|
+ .pm = &stm32_hdp_pm_ops,
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(hdp_driver);
|
|
diff --git a/drivers/soc/st/stm32_pm_domain.c b/drivers/soc/st/stm32_pm_domain.c
|
|
new file mode 100644
|
|
index 000000000..0386624c2
|
|
--- /dev/null
|
|
+++ b/drivers/soc/st/stm32_pm_domain.c
|
|
@@ -0,0 +1,212 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
|
|
+ * Author: Alexandre Torgue <alexandre.torgue@st.com> for STMicroelectronics.
|
|
+ * Author: Olivier Bideau <olivier.bideau@st.com> for STMicroelectronics.
|
|
+ */
|
|
+
|
|
+#include <linux/arm-smccc.h>
|
|
+#include <linux/err.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include <linux/pm_domain.h>
|
|
+#include <linux/printk.h>
|
|
+#include <linux/slab.h>
|
|
+
|
|
+#define SMC(domain, state) \
|
|
+{ \
|
|
+ struct arm_smccc_res res; \
|
|
+ arm_smccc_smc(0x82001008, domain, state, 0, \
|
|
+ 0, 0, 0, 0, &res); \
|
|
+}
|
|
+
|
|
+#define STM32_SMC_PD_DOMAIN_ON 0
|
|
+#define STM32_SMC_PD_DOMAIN_OFF 1
|
|
+
|
|
+struct stm32_pm_domain {
|
|
+ struct device *dev;
|
|
+ struct generic_pm_domain genpd;
|
|
+ int id;
|
|
+};
|
|
+
|
|
+static int stm32_pd_power_off(struct generic_pm_domain *domain)
|
|
+{
|
|
+ struct stm32_pm_domain *priv = container_of(domain,
|
|
+ struct stm32_pm_domain,
|
|
+ genpd);
|
|
+
|
|
+ SMC(priv->id, STM32_SMC_PD_DOMAIN_OFF);
|
|
+
|
|
+ dev_dbg(priv->dev, "%s OFF\n", domain->name);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int stm32_pd_power_on(struct generic_pm_domain *domain)
|
|
+{
|
|
+ struct stm32_pm_domain *priv = container_of(domain,
|
|
+ struct stm32_pm_domain,
|
|
+ genpd);
|
|
+
|
|
+ SMC(priv->id, STM32_SMC_PD_DOMAIN_ON);
|
|
+
|
|
+ dev_dbg(priv->dev, "%s ON\n", domain->name);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void stm32_pm_domain_remove(struct stm32_pm_domain *domain)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = pm_genpd_remove(&domain->genpd);
|
|
+ if (ret)
|
|
+ dev_err(domain->dev, "failed to remove PM domain %s: %d\n",
|
|
+ domain->genpd.name, ret);
|
|
+}
|
|
+
|
|
+static int stm32_pm_domain_add(struct stm32_pm_domain *domain,
|
|
+ struct device *dev,
|
|
+ struct device_node *np)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ domain->dev = dev;
|
|
+ domain->genpd.name = np->name;
|
|
+ domain->genpd.power_off = stm32_pd_power_off;
|
|
+ domain->genpd.power_on = stm32_pd_power_on;
|
|
+ domain->genpd.flags |= GENPD_FLAG_ACTIVE_WAKEUP;
|
|
+
|
|
+ ret = of_property_read_u32(np, "reg", &domain->id);
|
|
+ if (ret) {
|
|
+ dev_err(domain->dev, "no domain ID\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = pm_genpd_init(&domain->genpd, NULL, 0);
|
|
+ if (ret < 0) {
|
|
+ dev_err(domain->dev, "failed to initialise PM domain %s: %d\n",
|
|
+ np->name, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = of_genpd_add_provider_simple(np, &domain->genpd);
|
|
+ if (ret < 0) {
|
|
+ dev_err(domain->dev, "failed to register PM domain %s: %d\n",
|
|
+ np->name, ret);
|
|
+ stm32_pm_domain_remove(domain);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ dev_info(domain->dev, "domain %s registered\n", np->name);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void stm32_pm_subdomain_add(struct stm32_pm_domain *domain,
|
|
+ struct device *dev,
|
|
+ struct device_node *np)
|
|
+{
|
|
+ struct device_node *np_child;
|
|
+ int ret;
|
|
+
|
|
+ for_each_child_of_node(np, np_child) {
|
|
+ struct stm32_pm_domain *sub_domain;
|
|
+
|
|
+ sub_domain = devm_kzalloc(dev, sizeof(*sub_domain), GFP_KERNEL);
|
|
+ if (!sub_domain)
|
|
+ continue;
|
|
+
|
|
+ sub_domain->dev = dev;
|
|
+ sub_domain->genpd.name = np_child->name;
|
|
+ sub_domain->genpd.power_off = stm32_pd_power_off;
|
|
+ sub_domain->genpd.power_on = stm32_pd_power_on;
|
|
+ sub_domain->genpd.flags |= GENPD_FLAG_ACTIVE_WAKEUP;
|
|
+
|
|
+ ret = of_property_read_u32(np_child, "reg", &sub_domain->id);
|
|
+ if (ret) {
|
|
+ dev_err(sub_domain->dev, "no domain ID\n");
|
|
+ devm_kfree(dev, sub_domain);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ ret = pm_genpd_init(&sub_domain->genpd, NULL, 0);
|
|
+ if (ret < 0) {
|
|
+ dev_err(sub_domain->dev, "failed to initialise PM domain %s: %d\n"
|
|
+ , np_child->name, ret);
|
|
+ devm_kfree(dev, sub_domain);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ ret = of_genpd_add_provider_simple(np_child,
|
|
+ &sub_domain->genpd);
|
|
+ if (ret < 0) {
|
|
+ dev_err(sub_domain->dev, "failed to register PM domain %s: %d\n"
|
|
+ , np_child->name, ret);
|
|
+ stm32_pm_domain_remove(sub_domain);
|
|
+ devm_kfree(dev, sub_domain);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ ret = pm_genpd_add_subdomain(&domain->genpd,
|
|
+ &sub_domain->genpd);
|
|
+
|
|
+ if (ret < 0) {
|
|
+ dev_err(sub_domain->dev, "failed to add Sub PM domain %s: %d\n"
|
|
+ , np_child->name, ret);
|
|
+ stm32_pm_domain_remove(sub_domain);
|
|
+ devm_kfree(dev, sub_domain);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ dev_info(sub_domain->dev, "subdomain %s registered\n",
|
|
+ np_child->name);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int stm32_pm_domain_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct device_node *np = dev->of_node, *child_np;
|
|
+ int ret;
|
|
+
|
|
+ for_each_child_of_node(np, child_np) {
|
|
+ struct stm32_pm_domain *domain;
|
|
+
|
|
+ domain = devm_kzalloc(dev, sizeof(*domain), GFP_KERNEL);
|
|
+ if (!domain)
|
|
+ continue;
|
|
+
|
|
+ ret = stm32_pm_domain_add(domain, dev, child_np);
|
|
+ if (ret) {
|
|
+ devm_kfree(dev, domain);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ stm32_pm_subdomain_add(domain, dev, child_np);
|
|
+ }
|
|
+
|
|
+ dev_info(dev, "domains probed\n");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id stm32_pm_domain_matches[] = {
|
|
+ { .compatible = "st,stm32mp157c-pd", },
|
|
+ { },
|
|
+};
|
|
+
|
|
+static struct platform_driver stm32_pm_domains_driver = {
|
|
+ .probe = stm32_pm_domain_probe,
|
|
+ .driver = {
|
|
+ .name = "stm32-pm-domain",
|
|
+ .of_match_table = stm32_pm_domain_matches,
|
|
+ },
|
|
+};
|
|
+
|
|
+static int __init stm32_pm_domains_init(void)
|
|
+{
|
|
+ return platform_driver_register(&stm32_pm_domains_driver);
|
|
+}
|
|
+core_initcall(stm32_pm_domains_init);
|
|
diff --git a/drivers/thermal/st/stm_thermal.c b/drivers/thermal/st/stm_thermal.c
|
|
index 5fd3fb891..1e065a332 100644
|
|
--- a/drivers/thermal/st/stm_thermal.c
|
|
+++ b/drivers/thermal/st/stm_thermal.c
|
|
@@ -82,8 +82,7 @@
|
|
#define ONE_MHZ 1000000
|
|
#define POLL_TIMEOUT 5000
|
|
#define STARTUP_TIME 40
|
|
-#define TS1_T0_VAL0 30000 /* 30 celsius */
|
|
-#define TS1_T0_VAL1 130000 /* 130 celsius */
|
|
+#define T0 30000 /* 30 celsius */
|
|
#define NO_HW_TRIG 0
|
|
#define SAMPLING_TIME 15
|
|
|
|
@@ -96,7 +95,7 @@ struct stm_thermal_sensor {
|
|
unsigned int high_temp_enabled;
|
|
int irq;
|
|
void __iomem *base;
|
|
- int t0, fmt0, ramp_coeff;
|
|
+ int fmt0, ramp_coeff;
|
|
};
|
|
|
|
static int stm_enable_irq(struct stm_thermal_sensor *sensor)
|
|
@@ -243,14 +242,6 @@ static int stm_thermal_calibration(struct stm_thermal_sensor *sensor)
|
|
/* Fill in DTS structure with factory sensor values */
|
|
static int stm_thermal_read_factory_settings(struct stm_thermal_sensor *sensor)
|
|
{
|
|
- /* Retrieve engineering calibration temperature */
|
|
- sensor->t0 = readl_relaxed(sensor->base + DTS_T0VALR1_OFFSET) &
|
|
- TS1_T0_MASK;
|
|
- if (!sensor->t0)
|
|
- sensor->t0 = TS1_T0_VAL0;
|
|
- else
|
|
- sensor->t0 = TS1_T0_VAL1;
|
|
-
|
|
/* Retrieve fmt0 and put it on Hz */
|
|
sensor->fmt0 = ADJUST * (readl_relaxed(sensor->base +
|
|
DTS_T0VALR1_OFFSET) & TS1_FMT0_MASK);
|
|
@@ -264,8 +255,8 @@ static int stm_thermal_read_factory_settings(struct stm_thermal_sensor *sensor)
|
|
return -EINVAL;
|
|
}
|
|
|
|
- dev_dbg(sensor->dev, "%s: T0 = %doC, FMT0 = %dHz, RAMP_COEFF = %dHz/oC",
|
|
- __func__, sensor->t0, sensor->fmt0, sensor->ramp_coeff);
|
|
+ dev_dbg(sensor->dev, "%s: FMT0 = %dHz, RAMP_COEFF = %dHz/oC",
|
|
+ __func__, sensor->fmt0, sensor->ramp_coeff);
|
|
|
|
return 0;
|
|
}
|
|
@@ -276,8 +267,7 @@ static int stm_thermal_calculate_threshold(struct stm_thermal_sensor *sensor,
|
|
int freqM;
|
|
|
|
/* Figure out the CLK_PTAT frequency for a given temperature */
|
|
- freqM = ((temp - sensor->t0) * sensor->ramp_coeff) / 1000 +
|
|
- sensor->fmt0;
|
|
+ freqM = ((temp - T0) * sensor->ramp_coeff) / 1000 + sensor->fmt0;
|
|
|
|
/* Figure out the threshold sample number */
|
|
*th = clk_get_rate(sensor->clk) * SAMPLING_TIME / freqM;
|
|
@@ -372,7 +362,7 @@ static int stm_thermal_get_temp(void *data, int *temp)
|
|
return -EINVAL;
|
|
|
|
/* Figure out the temperature in mili celsius */
|
|
- *temp = (freqM - sensor->fmt0) * 1000 / sensor->ramp_coeff + sensor->t0;
|
|
+ *temp = (freqM - sensor->fmt0) * 1000 / sensor->ramp_coeff + T0;
|
|
|
|
return 0;
|
|
}
|
|
@@ -515,11 +505,9 @@ static int stm_thermal_probe(struct platform_device *pdev)
|
|
sensor->base = base;
|
|
|
|
sensor->clk = devm_clk_get(&pdev->dev, "pclk");
|
|
- if (IS_ERR(sensor->clk)) {
|
|
- dev_err(&pdev->dev, "%s: failed to fetch PCLK clock\n",
|
|
- __func__);
|
|
- return PTR_ERR(sensor->clk);
|
|
- }
|
|
+ if (IS_ERR(sensor->clk))
|
|
+ return dev_err_probe(&pdev->dev, PTR_ERR(sensor->clk),
|
|
+ "Failed to get PCLK clock\n");
|
|
|
|
stm_disable_irq(sensor);
|
|
|
|
diff --git a/include/dt-bindings/soc/stm32-hdp.h b/include/dt-bindings/soc/stm32-hdp.h
|
|
new file mode 100644
|
|
index 000000000..d98665327
|
|
--- /dev/null
|
|
+++ b/include/dt-bindings/soc/stm32-hdp.h
|
|
@@ -0,0 +1,108 @@
|
|
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
|
|
+/*
|
|
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
|
|
+ * Author: Roullier Christophe <christophe.roullier@st.com>
|
|
+ * for STMicroelectronics.
|
|
+ */
|
|
+
|
|
+#ifndef _DT_BINDINGS_STM32_HDP_H
|
|
+#define _DT_BINDINGS_STM32_HDP_H
|
|
+
|
|
+#define STM32_HDP(port, value) ((value) << ((port) * 4))
|
|
+
|
|
+/* define HDP Pins number*/
|
|
+#define HDP0_PWR_PWRWAKE_SYS 0
|
|
+#define HDP0_CM4_SLEEPDEEP 1
|
|
+#define HDP0_PWR_STDBY_WKUP 2
|
|
+#define HDP0_PWR_ENCOMP_VDDCORE 3
|
|
+#define HDP0_BSEC_OUT_SEC_NIDEN 4
|
|
+#define HDP0_RCC_CM4_SLEEPDEEP 6
|
|
+#define HDP0_GPU_DBG7 7
|
|
+#define HDP0_DDRCTRL_LP_REQ 8
|
|
+#define HDP0_PWR_DDR_RET_ENABLE_N 9
|
|
+#define HDP0_GPOVAL_0 15
|
|
+
|
|
+#define HDP1_PWR_PWRWAKE_MCU 0
|
|
+#define HDP1_CM4_HALTED 1
|
|
+#define HDP1_CA7_NAXIERRIRQ 2
|
|
+#define HDP1_PWR_OKIN_MR 3
|
|
+#define HDP1_BSEC_OUT_SEC_DBGEN 4
|
|
+#define HDP1_EXTI_SYS_WAKEUP 5
|
|
+#define HDP1_RCC_PWRDS_MPU 6
|
|
+#define HDP1_GPU_DBG6 7
|
|
+#define HDP1_DDRCTRL_DFI_CTRLUPD_REQ 8
|
|
+#define HDP1_DDRCTRL_CACTIVE_DDRC_ASR 9
|
|
+#define HDP1_GPOVAL_1 15
|
|
+
|
|
+#define HDP2_PWR_PWRWAKE_MPU 0
|
|
+#define HDP2_CM4_RXEV 1
|
|
+#define HDP2_CA7_NPMUIRQ1 2
|
|
+#define HDP2_CA7_NFIQOUT1 3
|
|
+#define HDP2_BSEC_IN_RSTCORE_N 4
|
|
+#define HDP2_EXTI_C2_WAKEUP 5
|
|
+#define HDP2_RCC_PWRDS_MCU 6
|
|
+#define HDP2_GPU_DBG5 7
|
|
+#define HDP2_DDRCTRL_DFI_INIT_COMPLETE 8
|
|
+#define HDP2_DDRCTRL_PERF_OP_IS_REFRESH 9
|
|
+#define HDP2_DDRCTRL_GSKP_DFI_LP_REQ 10
|
|
+#define HDP2_GPOVAL_2 15
|
|
+
|
|
+#define HDP3_PWR_SEL_VTH_VDD_CORE 0
|
|
+#define HDP3_CM4_TXEV 1
|
|
+#define HDP3_CA7_NPMUIRQ0 2
|
|
+#define HDP3_CA7_NFIQOUT0 3
|
|
+#define HDP3_BSEC_OUT_SEC_DFTLOCK 4
|
|
+#define HDP3_EXTI_C1_WAKEUP 5
|
|
+#define HDP3_RCC_PWRDS_SYS 6
|
|
+#define HDP3_GPU_DBG4 7
|
|
+#define HDP3_DDRCTRL_STAT_DDRC_REG_SELREF_TYPE0 8
|
|
+#define HDP3_DDRCTRL_CACTIVE_1 9
|
|
+#define HDP3_GPOVAL_3 15
|
|
+
|
|
+#define HDP4_PWR_PDDS 0
|
|
+#define HDP4_CM4_SLEEPING 1
|
|
+#define HDP4_CA7_NRESET1 2
|
|
+#define HDP4_CA7_NIRQOUT1 3
|
|
+#define HDP4_BSEC_OUT_SEC_DFTEN 4
|
|
+#define HDP4_BSEC_OUT_SEC_DBGSWENABLE 5
|
|
+#define HDP4_ETH_OUT_PMT_INTR_O 6
|
|
+#define HDP4_GPU_DBG3 7
|
|
+#define HDP4_DDRCTRL_STAT_DDRC_REG_SELREF_TYPE1 8
|
|
+#define HDP4_DDRCTRL_CACTIVE_0 9
|
|
+#define HDP4_GPOVAL_4 15
|
|
+
|
|
+#define HDP5_CA7_STANDBYWFIL2 0
|
|
+#define HDP5_PWR_VTH_VDDCORE_ACK 1
|
|
+#define HDP5_CA7_NRESET0 2
|
|
+#define HDP5_CA7_NIRQOUT0 3
|
|
+#define HDP5_BSEC_IN_PWROK 4
|
|
+#define HDP5_BSEC_OUT_SEC_DEVICEEN 5
|
|
+#define HDP5_ETH_OUT_LPI_INTR_O 6
|
|
+#define HDP5_GPU_DBG2 7
|
|
+#define HDP5_DDRCTRL_CACTIVE_DDRC 8
|
|
+#define HDP5_DDRCTRL_WR_CREDIT_CNT 9
|
|
+#define HDP5_GPOVAL_5 15
|
|
+
|
|
+#define HDP6_CA7_STANDBYWFI1 0
|
|
+#define HDP6_CA7_STANDBYWFE1 1
|
|
+#define HDP6_CA7_EVENT0 2
|
|
+#define HDP6_CA7_DBGACK1 3
|
|
+#define HDP6_BSEC_OUT_SEC_SPNIDEN 5
|
|
+#define HDP6_ETH_OUT_MAC_SPEED_O1 6
|
|
+#define HDP6_GPU_DBG1 7
|
|
+#define HDP6_DDRCTRL_CSYSACK_DDRC 8
|
|
+#define HDP6_DDRCTRL_LPR_CREDIT_CNT 9
|
|
+#define HDP6_GPOVAL_6 15
|
|
+
|
|
+#define HDP7_CA7_STANDBYWFI0 0
|
|
+#define HDP7_CA7_STANDBYWFE0 1
|
|
+#define HDP7_CA7_DBGACK0 3
|
|
+#define HDP7_BSEC_OUT_FUSE_OK 4
|
|
+#define HDP7_BSEC_OUT_SEC_SPIDEN 5
|
|
+#define HDP7_ETH_OUT_MAC_SPEED_O0 6
|
|
+#define HDP7_GPU_DBG0 7
|
|
+#define HDP7_DDRCTRL_CSYSREQ_DDRC 8
|
|
+#define HDP7_DDRCTRL_HPR_CREDIT_CNT 9
|
|
+#define HDP7_GPOVAL_7 15
|
|
+
|
|
+#endif /* _DT_BINDINGS_STM32_HDP_H */
|
|
diff --git a/include/dt-bindings/soc/stm32mp13-hdp.h b/include/dt-bindings/soc/stm32mp13-hdp.h
|
|
new file mode 100644
|
|
index 000000000..091c1c835
|
|
--- /dev/null
|
|
+++ b/include/dt-bindings/soc/stm32mp13-hdp.h
|
|
@@ -0,0 +1,133 @@
|
|
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
|
|
+/*
|
|
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
|
|
+ * Author: Roullier Christophe <christophe.roullier@st.com>
|
|
+ * for STMicroelectronics.
|
|
+ */
|
|
+
|
|
+#ifndef _DT_BINDINGS_STM32_HDP_H
|
|
+#define _DT_BINDINGS_STM32_HDP_H
|
|
+
|
|
+#define STM32_HDP(port, value) ((value) << ((port) * 4))
|
|
+
|
|
+/* define HDP Pins number*/
|
|
+#define HDP0_PWR_PWRWAKE_SYS 0
|
|
+#define HDP0_PWR_STOP_FORBIDDEN 1
|
|
+#define HDP0_PWR_STDBY_WKUP 2
|
|
+#define HDP0_PWR_ENCOMP_VDDCORE 3
|
|
+#define HDP0_BSEC_OUT_SEC_NIDEN 4
|
|
+#define HDP0_AIEC_SYS_WAKEUP 5
|
|
+#define HDP0_DDRCTRL_LP_REQ 8
|
|
+#define HDP0_PWR_DDR_RET_ENABLE_N 9
|
|
+#define HDP0_DTS_CLK_PTAT 10
|
|
+#define HDP0_SRAM3CTRL_TAMP_ERASE_ACT 12
|
|
+#define HDP0_GPOVAL_0 15
|
|
+
|
|
+#define HDP1_PWR_SEL_VTH_VDDCPU 0
|
|
+#define HDP1_PWR_MPU_RAM_LOWSPEED 1
|
|
+#define HDP1_CA7_NAXIERRIRQ 2
|
|
+#define HDP1_PWR_OKIN_MR 3
|
|
+#define HDP1_BSEC_OUT_SEC_DBGEN 4
|
|
+#define HDP1_AIEC_C1_WAKEUP 5
|
|
+#define HDP1_RCC_PWRDS_MPU 6
|
|
+#define HDP1_DDRCTRL_DFI_CTRLUPD_REQ 8
|
|
+#define HDP1_DDRCTRL_CACTIVE_DDRC_ASR 9
|
|
+#define HDP1_SRAM3CTRL_HW_ERASE_ACT 12
|
|
+#define HDP1_NIC400_S0_BREADY 13
|
|
+#define HDP1_GPOVAL_1 15
|
|
+
|
|
+#define HDP2_PWR_PWRWAKE_MPU 0
|
|
+#define HDP2_PWR_MPU_CLOCK_DISABLE_ACK 1
|
|
+#define HDP2_CA7_NDGBRESET_I 2
|
|
+#define HDP2_BSEC_IN_RSTCORE_N 4
|
|
+#define HDP2_BSEC_OUT_SEC_BSC_DIS 5
|
|
+#define HDP2_DDRCTRL_DFI_INIT_COMPLETE 8
|
|
+#define HDP2_DDRCTRL_PERF_OP_IS_REFRESH 9
|
|
+#define HDP2_DDRCTRL_GSKP_DFI_LP_REQ 10
|
|
+#define HDP2_SRAM3CTRL_SW_ERASE_ACT 12
|
|
+#define HDP2_NIC400_S0_BVALID 13
|
|
+#define HDP2_GPOVAL_2 15
|
|
+
|
|
+#define HDP3_PWR_SEL_VTH_VDD_CORE 0
|
|
+#define HDP3_PWR_MPU_CLOCK_DISABLE_REQ 1
|
|
+#define HDP3_CA7_NPMUIRQ0 2
|
|
+#define HDP3_CA7_NFIQOUT0 3
|
|
+#define HDP3_BSEC_OUT_SEC_DFTLOCK 4
|
|
+#define HDP3_BSEC_OUT_SEC_JTAG_DIS 5
|
|
+#define HDP3_RCC_PWRDS_SYS 6
|
|
+#define HDP3_SRAM3CTRL_TAMP_ERASE_REQ 7
|
|
+#define HDP3_DDRCTRL_STAT_DDRC_REG_SELREF_TYPE0 8
|
|
+#define HDP3_DTS_VALOBUS1_0 10
|
|
+#define HDP3_DTS_VALOBUS2_0 11
|
|
+#define HDP3_TAMP_POTENTIAL_TAMP_ERFCFG 12
|
|
+#define HDP3_NIC400_S0_WREADY 13
|
|
+#define HDP3_NIC400_S0_RREADY 14
|
|
+#define HDP3_GPOVAL_3 15
|
|
+
|
|
+#define HDP4_PWR_STOP2_ACTIVE 1
|
|
+#define HDP4_CA7_NL2RESET1 2
|
|
+#define HDP4_CA7_NPORESET_VARM_I 3
|
|
+#define HDP4_BSEC_OUT_SEC_DFTEN 4
|
|
+#define HDP4_BSEC_OUT_SEC_DBGSWENABLE 5
|
|
+#define HDP4_ETH1_OUT_PMT_INTR_O 6
|
|
+#define HDP4_ETH2_OUT_PMT_INTR_O 7
|
|
+#define HDP4_DDRCTRL_STAT_DDRC_REG_SELREF_TYPE1 8
|
|
+#define HDP4_DDRCTRL_CACTIVE_0 9
|
|
+#define HDP4_DTS_VALOBUS1_1 10
|
|
+#define HDP4_DTS_VALOBUS2_1 11
|
|
+#define HDP4_TAMP_NRESET_SRAM_ERCFG 12
|
|
+#define HDP4_NIC400_S0_WLAST 13
|
|
+#define HDP4_NIC400_S0_RLAST 14
|
|
+#define HDP4_GPOVAL_4 15
|
|
+
|
|
+#define HDP5_CA7_STANDBYWFIL2 0
|
|
+#define HDP5_PWR_VTH_VDDCORE_ACK 1
|
|
+#define HDP5_CA7_NCORERESET_I 2
|
|
+#define HDP5_CA7_NIRQOUT0 3
|
|
+#define HDP5_BSEC_IN_PWROK 4
|
|
+#define HDP5_BSEC_OUT_SEC_DEVICEEN 5
|
|
+#define HDP5_ETH1_OUT_LPI_INTR_O 6
|
|
+#define HDP5_ETH2_OUT_LPI_INTR_O 7
|
|
+#define HDP5_DDRCTRL_CACTIVE_DDRC 8
|
|
+#define HDP5_DDRCTRL_WR_CREDIT_CNT 9
|
|
+#define HDP5_DTS_VALOBUS1_2 10
|
|
+#define HDP5_DTS_VALOBUS2_2 11
|
|
+#define HDP5_PKA_PKA_ITAMP_OUT 12
|
|
+#define HDP5_NIC400_S0_WVALID 13
|
|
+#define HDP5_NIC400_S0_RVALID 14
|
|
+#define HDP5_GPOVAL_5 15
|
|
+
|
|
+#define HDP6_CA7_STANDBYWFE0 0
|
|
+#define HDP6_PWR_VTH_VDDCPU_ACK 1
|
|
+#define HDP6_CA7_EVENT0 2
|
|
+#define HDP6_BSEC_IN_TAMPER_DET 4
|
|
+#define HDP6_BSEC_OUT_SEC_SPNIDEN 5
|
|
+#define HDP6_ETH1_OUT_MAC_SPEED_O1 6
|
|
+#define HDP6_ETH2_OUT_MAC_SPEED_O1 7
|
|
+#define HDP6_DDRCTRL_CSYSACK_DDRC 8
|
|
+#define HDP6_DDRCTRL_LPR_CREDIT_CNT 9
|
|
+#define HDP6_DTS_VALOBUS1_3 10
|
|
+#define HDP6_DTS_VALOBUS2_3 11
|
|
+#define HDP6_SAES_TAMPER_OUT 12
|
|
+#define HDP6_NIC400_S0_AWREADY 13
|
|
+#define HDP6_NIC400_S0_ARREADY 14
|
|
+#define HDP6_GPOVAL_6 15
|
|
+
|
|
+#define HDP7_CA7_STANDBYWFI0 0
|
|
+#define HDP7_PWR_RCC_VCPU_RDY 1
|
|
+#define HDP7_CA7_EVENTI 2
|
|
+#define HDP7_CA7_DBGACK0 3
|
|
+#define HDP7_BSEC_OUT_FUSE_OK 4
|
|
+#define HDP7_BSEC_OUT_SEC_SPIDEN 5
|
|
+#define HDP7_ETH1_OUT_MAC_SPEED_O0 6
|
|
+#define HDP7_ETH2_OUT_MAC_SPEED_O0 7
|
|
+#define HDP7_DDRCTRL_CSYSREQ_DDRC 8
|
|
+#define HDP7_DDRCTRL_HPR_CREDIT_CNT 9
|
|
+#define HDP7_DTS_VALOBUS1_4 10
|
|
+#define HDP7_DTS_VALOBUS2_4 11
|
|
+#define HDP7_RNG_TAMPER_OUT 12
|
|
+#define HDP7_NIC400_S0_AWVALID 13
|
|
+#define HDP7_NIC400_S0_ARVALID 14
|
|
+#define HDP7_GPOVAL_7 15
|
|
+
|
|
+#endif /* _DT_BINDINGS_STM32_HDP_H */
|
|
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
|
|
index 104505e90..87932bdb2 100644
|
|
--- a/include/linux/nvmem-provider.h
|
|
+++ b/include/linux/nvmem-provider.h
|
|
@@ -66,7 +66,8 @@ struct nvmem_keepout {
|
|
* @word_size: Minimum read/write access granularity.
|
|
* @stride: Minimum read/write access stride.
|
|
* @priv: User context passed to read/write callbacks.
|
|
- * @wp-gpio: Write protect pin
|
|
+ * @wp-gpio: Write protect pin
|
|
+ * @ignore_wp: Write Protect pin is managed by the provider.
|
|
*
|
|
* Note: A default "nvmem<id>" name will be assigned to the device if
|
|
* no name is specified in its configuration. In such case "<id>" is
|
|
@@ -88,6 +89,7 @@ struct nvmem_config {
|
|
enum nvmem_type type;
|
|
bool read_only;
|
|
bool root_only;
|
|
+ bool ignore_wp;
|
|
struct device_node *of_node;
|
|
bool no_of_node;
|
|
nvmem_reg_read_t reg_read;
|
|
diff --git a/include/media/mipi-csi2.h b/include/media/mipi-csi2.h
|
|
new file mode 100644
|
|
index 000000000..392794e5b
|
|
--- /dev/null
|
|
+++ b/include/media/mipi-csi2.h
|
|
@@ -0,0 +1,45 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * MIPI CSI-2 Data Types
|
|
+ *
|
|
+ * Copyright (C) 2022 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
+ */
|
|
+
|
|
+#ifndef _MEDIA_MIPI_CSI2_H
|
|
+#define _MEDIA_MIPI_CSI2_H
|
|
+
|
|
+/* Short packet data types */
|
|
+#define MIPI_CSI2_DT_FS 0x00
|
|
+#define MIPI_CSI2_DT_FE 0x01
|
|
+#define MIPI_CSI2_DT_LS 0x02
|
|
+#define MIPI_CSI2_DT_LE 0x03
|
|
+#define MIPI_CSI2_DT_GENERIC_SHORT(n) (0x08 + (n)) /* 0..7 */
|
|
+
|
|
+/* Long packet data types */
|
|
+#define MIPI_CSI2_DT_NULL 0x10
|
|
+#define MIPI_CSI2_DT_BLANKING 0x11
|
|
+#define MIPI_CSI2_DT_EMBEDDED_8B 0x12
|
|
+#define MIPI_CSI2_DT_YUV420_8B 0x18
|
|
+#define MIPI_CSI2_DT_YUV420_10B 0x19
|
|
+#define MIPI_CSI2_DT_YUV420_8B_LEGACY 0x1a
|
|
+#define MIPI_CSI2_DT_YUV420_8B_CS 0x1c
|
|
+#define MIPI_CSI2_DT_YUV420_10B_CS 0x1d
|
|
+#define MIPI_CSI2_DT_YUV422_8B 0x1e
|
|
+#define MIPI_CSI2_DT_YUV422_10B 0x1f
|
|
+#define MIPI_CSI2_DT_RGB444 0x20
|
|
+#define MIPI_CSI2_DT_RGB555 0x21
|
|
+#define MIPI_CSI2_DT_RGB565 0x22
|
|
+#define MIPI_CSI2_DT_RGB666 0x23
|
|
+#define MIPI_CSI2_DT_RGB888 0x24
|
|
+#define MIPI_CSI2_DT_RAW24 0x27
|
|
+#define MIPI_CSI2_DT_RAW6 0x28
|
|
+#define MIPI_CSI2_DT_RAW7 0x29
|
|
+#define MIPI_CSI2_DT_RAW8 0x2a
|
|
+#define MIPI_CSI2_DT_RAW10 0x2b
|
|
+#define MIPI_CSI2_DT_RAW12 0x2c
|
|
+#define MIPI_CSI2_DT_RAW14 0x2d
|
|
+#define MIPI_CSI2_DT_RAW16 0x2e
|
|
+#define MIPI_CSI2_DT_RAW20 0x2f
|
|
+#define MIPI_CSI2_DT_USER_DEFINED(n) (0x30 + (n)) /* 0..7 */
|
|
+
|
|
+#endif /* _MEDIA_MIPI_CSI2_H */
|
|
diff --git a/include/media/v4l2-fwnode.h b/include/media/v4l2-fwnode.h
|
|
index 7ab033b81..065ce8d26 100644
|
|
--- a/include/media/v4l2-fwnode.h
|
|
+++ b/include/media/v4l2-fwnode.h
|
|
@@ -49,11 +49,13 @@ struct v4l2_fwnode_bus_mipi_csi2 {
|
|
* @flags: media bus (V4L2_MBUS_*) flags
|
|
* @bus_width: bus width in bits
|
|
* @data_shift: data shift in bits
|
|
+ * @max_pclk_frequency: maximum pixel clock in hertz
|
|
*/
|
|
struct v4l2_fwnode_bus_parallel {
|
|
unsigned int flags;
|
|
unsigned char bus_width;
|
|
unsigned char data_shift;
|
|
+ unsigned int pclk_max_frequency;
|
|
};
|
|
|
|
/**
|
|
--
|
|
2.25.1
|
|
|