meta-st-stm32mp/recipes-kernel/linux/linux-stm32mp/5.15/5.15.118/0011-v5.15-stm32mp-r2.1-MIS...

9371 lines
275 KiB
Diff
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From 7ec3e513a3c37c2e20b1f2545f6cb84e4422b045 Mon Sep 17 00:00:00 2001
From: Romuald Jeanne <romuald.jeanne@st.com>
Date: Tue, 25 Jul 2023 10:50:21 +0200
Subject: [PATCH 11/22] v5.15-stm32mp-r2.1 MISC-MEDIA-SOC-THERMAL
Signed-off-by: Romuald Jeanne <romuald.jeanne@st.com>
---
CONTRIBUTING.md | 30 +
MAINTAINERS | 17 +
SECURITY.md | 8 +
drivers/char/hw_random/stm32-rng.c | 229 +-
drivers/firmware/Kconfig | 2 +-
drivers/gpio/gpiolib-acpi.c | 3 +
drivers/gpio/gpiolib-of.c | 7 +
drivers/gpio/gpiolib.c | 28 +-
drivers/input/touchscreen/edt-ft5x06.c | 31 +
drivers/input/touchscreen/goodix.c | 51 +-
drivers/media/i2c/Kconfig | 12 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/gc2145.c | 1948 +++++++++++++++++
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 | 1112 ++++++++++
.../stm32/stm32-dcmipp/dcmipp-byteproc.c | 790 +++++++
.../stm32/stm32-dcmipp/dcmipp-common.c | 116 +
.../stm32/stm32-dcmipp/dcmipp-common.h | 240 ++
.../platform/stm32/stm32-dcmipp/dcmipp-core.c | 682 ++++++
.../stm32/stm32-dcmipp/dcmipp-parallel.c | 497 +++++
drivers/media/v4l2-core/v4l2-fwnode.c | 3 +
drivers/nvmem/stm32-romem.c | 642 +++++-
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/Kconfig | 4 +-
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/gpio/machine.h | 1 +
include/linux/smscphy.h | 20 +
include/media/mipi-csi2.h | 45 +
include/media/v4l2-fwnode.h | 2 +
45 files changed, 7805 insertions(+), 257 deletions(-)
create mode 100644 CONTRIBUTING.md
create mode 100644 SECURITY.md
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/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000000..3d1bacd78a54
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,30 @@
+# Contributing guide
+
+This document serves as a checklist before contributing to this repository. It includes links to read up on if topics are unclear to you.
+
+This guide mainly focuses on the proper use of Git.
+
+## 1. Issues
+
+STM32MPU projects do not activate "Github issues" feature for the time being. If you need to report an issue or question about this project deliverables, you can report them using [ ST Support Center ](https://my.st.com/ols#/ols/newrequest) or [ ST Community MPU Forum ](https://community.st.com/s/topic/0TO0X0000003u2AWAQ/stm32-mpus).
+
+## 2. Pull Requests
+
+STMicrolectronics is happy to receive contributions from the community, based on an initial Contributor License Agreement (CLA) procedure.
+
+* If you are an individual writing original source code and you are sure **you own the intellectual property**, then you need to sign an Individual CLA (https://cla.st.com).
+* If you work for a company that wants also to allow you to contribute with your work, your company needs to provide a Corporate CLA (https://cla.st.com) mentioning your GitHub account name.
+* If you are not sure that a CLA (Individual or Corporate) has been signed for your GitHub account you can check here (https://cla.st.com).
+
+Please note that:
+* The Corporate CLA will always take precedence over the Individual CLA.
+* One CLA submission is sufficient, for any project proposed by STMicroelectronics.
+
+__How to proceed__
+
+* We recommend to fork the project in your GitHub account to further develop your contribution. Please use the latest commit version.
+* Please, submit one Pull Request for one new feature or proposal. This will ease the analysis and final merge if accepted.
+
+__Note__
+
+Merge will not be done directly in GitHub but it will need first to follow internal integration process before public deliver in a standard release. The Pull request will stay open until it is merged and delivered.
diff --git a/MAINTAINERS b/MAINTAINERS
index 2bf1ad0fb2a6..f27b6574d93e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7743,6 +7743,14 @@ F: kernel/futex/*
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>
@@ -11720,6 +11728,15 @@ T: git git://linuxtv.org/media_tree.git
F: Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml
F: drivers/media/platform/stm32/stm32-dcmi.c
+MEDIA DRIVERS FOR STM32 - DCMIPP
+M: Hugues Fruchet <hugues.fruchet@foss.st.com>
+M: Alain Volmat <alain.volmat@foss.st.com>
+L: linux-media@vger.kernel.org
+S: Supported
+T: git git://linuxtv.org/media_tree.git
+F: Documentation/devicetree/bindings/media/st,stm32-dcmipp.yaml
+F: drivers/media/platform/stm32/stm32-dcmipp/*
+
MEDIA INPUT INFRASTRUCTURE (V4L/DVB)
M: Mauro Carvalho Chehab <mchehab@kernel.org>
L: linux-media@vger.kernel.org
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000000..4b3e4e6ba5e1
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,8 @@
+# Report potential product security vulnerabilities
+ST places a high priority on security, and our Product Security Incident Response Team (PSIRT) is committed to rapidly addressing potential security vulnerabilities affecting our products. PSIRT's long history and vast experience in security allows ST to perform clear analyses and provide appropriate guidance on mitigations and solutions when applicable.
+If you wish to report potential security vulnerabilities regarding our products, **please do not report them through public GitHub issues.** Instead, we encourage you to report them to our ST PSIRT following the process described at: **https://www.st.com/content/st_com/en/security/report-vulnerabilities.html**
+
+### IMPORTANT - READ CAREFULLY:
+STMicroelectronics International N.V., on behalf of itself, its affiliates and subsidiaries, (collectively “ST”) takes all potential security vulnerability reports or other related communications (“Report(s)”) seriously. In order to review Your Report (the terms “You” and “Yours” include your employer, and all affiliates, subsidiaries and related persons or entities) and take actions as deemed appropriate, ST requires that we have the rights and Your permission to do so.
+As such, by submitting Your Report to ST, You agree that You have the right to do so, and You grant to ST the rights to use the Report for purposes related to security vulnerability analysis, testing, correction, patching, reporting and any other related purpose or function.
+By submitting Your Report, You agree that STs [Privacy Policy](https://www.st.com/content/st_com/en/common/privacy-portal.html) applies to all related communications.
diff --git a/drivers/char/hw_random/stm32-rng.c b/drivers/char/hw_random/stm32-rng.c
index bc22178f83e8..e0a8025b45db 100644
--- a/drivers/char/hw_random/stm32-rng.c
+++ b/drivers/char/hw_random/stm32-rng.c
@@ -16,22 +16,36 @@
#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)
+
+#define RNG_MAX_NOISE_CLK_FREQ 3000000
+
+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;
+ u32 pm_cr;
bool ced;
};
@@ -79,39 +93,168 @@ static int stm32_rng_read(struct hwrng *rng, void *data, size_t max, bool wait)
return retval || !wait ? retval : -EIO;
}
+static uint stm32_rng_clock_freq_restrain(struct hwrng *rng)
+{
+ struct stm32_rng_private *priv =
+ container_of(rng, struct stm32_rng_private, rng);
+ unsigned long clock_rate = 0;
+ uint clock_div = 0;
+
+ clock_rate = clk_get_rate(priv->clk);
+
+ /*
+ * Get the exponent to apply on the CLKDIV field in RNG_CR register
+ * No need to handle the case when clock-div > 0xF as it is physically
+ * impossible
+ */
+ while ((clock_rate >> clock_div) > RNG_MAX_NOISE_CLK_FREQ)
+ clock_div++;
+
+ pr_debug("RNG clk rate : %lu\n", clk_get_rate(priv->clk) >> clock_div);
+
+ return clock_div;
+}
+
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) {
+ uint clock_div = stm32_rng_clock_freq_restrain(rng);
+
+ reg &= ~RNG_NIST_CONFIG_MASK;
+ reg |= RNG_CR_CONDRST | RNG_NIST_CONFIG_B | clock_div;
+ 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);
+
+ 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);
- writel_relaxed(0, priv->base + RNG_CR);
+ reg = readl_relaxed(priv->base + RNG_CR);
+ reg &= ~RNG_CR_RNGEN;
+ priv->pm_cr = reg;
+ 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);
+
+ /* Clean error indications */
+ writel_relaxed(0, priv->base + RNG_SR);
+
+ if (priv->data->has_cond_reset) {
+ /*
+ * Correct configuration in bits [29:4] must be set in the same
+ * access that set RNG_CR_CONDRST bit. Else config setting is
+ * not taken into account. CONFIGLOCK bit must also be unset but
+ * it is not handled at the moment.
+ */
+ writel_relaxed(priv->pm_cr | RNG_CR_CONDRST, priv->base + RNG_CR);
+
+ reg = readl_relaxed(priv->base + RNG_CR);
+ reg |= RNG_CR_RNGEN;
+ reg &= ~RNG_CR_CONDRST;
+ writel_relaxed(reg, priv->base + RNG_CR);
+ } else {
+ 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 +286,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 +309,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 97ce31e667fc..b54bfd332613 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/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c
index 53be0bdf2bc3..dba8dfebf26a 100644
--- a/drivers/gpio/gpiolib-acpi.c
+++ b/drivers/gpio/gpiolib-acpi.c
@@ -702,6 +702,9 @@ int acpi_gpio_update_gpiod_lookup_flags(unsigned long *lookupflags,
case ACPI_PIN_CONFIG_PULLDOWN:
*lookupflags |= GPIO_PULL_DOWN;
break;
+ case ACPI_PIN_CONFIG_NOPULL:
+ *lookupflags |= GPIO_PULL_DISABLE;
+ break;
default:
break;
}
diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c
index 7a96eb626a08..e37a619aed56 100644
--- a/drivers/gpio/gpiolib-of.c
+++ b/drivers/gpio/gpiolib-of.c
@@ -354,6 +354,9 @@ struct gpio_desc *gpiod_get_from_of_node(const struct device_node *node,
if (flags & OF_GPIO_PULL_DOWN)
lflags |= GPIO_PULL_DOWN;
+ if (flags & OF_GPIO_PULL_DISABLE)
+ lflags |= GPIO_PULL_DISABLE;
+
ret = gpiod_configure_flags(desc, propname, lflags, dflags);
if (ret < 0) {
gpiod_put(desc);
@@ -556,6 +559,8 @@ struct gpio_desc *of_find_gpio(struct device *dev, const char *con_id,
*flags |= GPIO_PULL_UP;
if (of_flags & OF_GPIO_PULL_DOWN)
*flags |= GPIO_PULL_DOWN;
+ if (of_flags & OF_GPIO_PULL_DISABLE)
+ *flags |= GPIO_PULL_DISABLE;
return desc;
}
@@ -621,6 +626,8 @@ static struct gpio_desc *of_parse_own_gpio(struct device_node *np,
*lflags |= GPIO_PULL_UP;
if (xlate_flags & OF_GPIO_PULL_DOWN)
*lflags |= GPIO_PULL_DOWN;
+ if (xlate_flags & OF_GPIO_PULL_DISABLE)
+ *lflags |= GPIO_PULL_DISABLE;
if (of_property_read_bool(np, "input"))
*dflags |= GPIOD_IN;
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 8c041a8dd9d8..73737407cc82 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -421,8 +421,16 @@ static int devprop_gpiochip_set_names(struct gpio_chip *chip)
if (count > chip->ngpio)
count = chip->ngpio;
- for (i = 0; i < count; i++)
- gdev->descs[i].name = names[chip->offset + i];
+ for (i = 0; i < count; i++) {
+ /*
+ * Allow overriding "fixed" names provided by the GPIO
+ * provider. The "fixed" names are more often than not
+ * generic and less informative than the names given in
+ * device properties.
+ */
+ if (names[chip->offset + i] && names[chip->offset + i][0])
+ gdev->descs[i].name = names[chip->offset + i];
+ }
kfree(names);
@@ -733,10 +741,12 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
INIT_LIST_HEAD(&gdev->pin_ranges);
#endif
- if (gc->names)
+ if (gc->names) {
ret = gpiochip_set_desc_names(gc);
- else
- ret = devprop_gpiochip_set_names(gc);
+ if (ret)
+ goto err_remove_from_list;
+ }
+ ret = devprop_gpiochip_set_names(gc);
if (ret)
goto err_remove_from_list;
@@ -3869,9 +3879,11 @@ int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id,
if (lflags & GPIO_OPEN_SOURCE)
set_bit(FLAG_OPEN_SOURCE, &desc->flags);
- if ((lflags & GPIO_PULL_UP) && (lflags & GPIO_PULL_DOWN)) {
+ if (((lflags & GPIO_PULL_UP) && (lflags & GPIO_PULL_DOWN)) ||
+ ((lflags & GPIO_PULL_UP) && (lflags & GPIO_PULL_DISABLE)) ||
+ ((lflags & GPIO_PULL_DOWN) && (lflags & GPIO_PULL_DISABLE))) {
gpiod_err(desc,
- "both pull-up and pull-down enabled, invalid configuration\n");
+ "multiple pull-up, pull-down or pull-disable enabled, invalid configuration\n");
return -EINVAL;
}
@@ -3879,6 +3891,8 @@ int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id,
set_bit(FLAG_PULL_UP, &desc->flags);
else if (lflags & GPIO_PULL_DOWN)
set_bit(FLAG_PULL_DOWN, &desc->flags);
+ else if (lflags & GPIO_PULL_DISABLE)
+ set_bit(FLAG_BIAS_DISABLE, &desc->flags);
ret = gpiod_set_transitory(desc, (lflags & GPIO_TRANSITORY));
if (ret < 0)
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index bb2e1cbffba7..8acf3d4d8d75 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,13 +1084,38 @@ 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;
+ struct device_link *dlink;
int error;
char fw_version[EDT_NAME_LEN];
dev_dbg(&client->dev, "probing for EDT FT5x06 I2C\n");
+ 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;
+
+ dlink = device_link_add(&client->dev, &panel->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
+
+ if (IS_ERR(dlink)) {
+ error = PTR_ERR(dlink);
+ dev_err(&client->dev,
+ "Failed to add link to device %d\n", error);
+ return error;
+ }
+
+ if (dlink && dlink->status != DL_STATE_CONSUMER_PROBE)
+ return -EPROBE_DEFER;
+
+ put_device(&panel->dev);
+ }
+
tsdata = devm_kzalloc(&client->dev, sizeof(*tsdata), GFP_KERNEL);
if (!tsdata) {
dev_err(&client->dev, "failed to allocate driver data.\n");
diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c
index 166d36b2626e..819880e7d00c 100644
--- a/drivers/input/touchscreen/goodix.c
+++ b/drivers/input/touchscreen/goodix.c
@@ -16,6 +16,7 @@
#include <linux/firmware.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/slab.h>
@@ -257,7 +258,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;
}
@@ -385,7 +386,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;
}
@@ -1155,10 +1156,35 @@ 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;
+ struct device_link *dlink;
int error;
dev_dbg(&client->dev, "I2C Address: 0x%02x\n", client->addr);
+ 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;
+
+ dlink = device_link_add(&client->dev, &panel->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
+
+ if (IS_ERR(dlink)) {
+ error = PTR_ERR(dlink);
+ dev_err(&client->dev,
+ "Failed to add link to device %d\n", error);
+ return error;
+ }
+
+ if (dlink && dlink->status != DL_STATE_CONSUMER_PROBE)
+ return -EPROBE_DEFER;
+
+ put_device(&panel->dev);
+ }
+
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev, "I2C check functionality failed.\n");
return -ENXIO;
@@ -1206,6 +1232,26 @@ static int goodix_ts_probe(struct i2c_client *client,
error = goodix_reset(ts);
if (error)
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);
@@ -1307,6 +1353,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 6157e73eef24..9343c680b87c 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 83268f20aa3a..3916c2818522 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 000000000000..222e47d76321
--- /dev/null
+++ b/drivers/media/i2c/gc2145.c
@@ -0,0 +1,1948 @@
+// 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_ANALOG_MODE1 0x17
+#define GC2145_REG_OUTPUT_FMT 0x84
+#define GC2145_REG_DEBUG_MODE2 0x8c
+#define GC2145_REG_DEBUG_MODE3 0x8d
+#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_ctrls {
+ struct v4l2_ctrl_handler handler;
+ struct v4l2_ctrl *pixel_rate;
+ struct v4l2_ctrl *test_pattern;
+ struct v4l2_ctrl *hflip;
+ struct v4l2_ctrl *vflip;
+};
+
+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];
+
+ /* V4L2 controls */
+ struct gc2145_ctrls ctrls;
+
+ /* 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 inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
+{
+ return &container_of(ctrl->handler, struct gc2145,
+ ctrls.handler)->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;
+}
+
+static int gc2145_mod_reg(struct gc2145 *gc2145, u16 reg, u8 mask, u8 val)
+{
+ u8 readval;
+ int ret;
+
+ ret = gc2145_read_reg(gc2145, reg, &readval, 1);
+ if (ret)
+ return ret;
+
+ readval &= ~mask;
+ val &= mask;
+ val |= readval;
+
+ return gc2145_write_reg(gc2145, reg, val);
+}
+
+/* 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 int gc2145_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_frame_interval_enum *fie)
+{
+ struct gc2145 *gc2145 = to_gc2145(sd);
+ const struct gc2145_format *gc2145_format;
+ u32 code, i;
+
+ /* The driver currently only support a unique framerate per resolution */
+ if (fie->index > 0)
+ return -EINVAL;
+
+ gc2145_format = gc2145_get_format_code(gc2145, fie->code);
+ code = gc2145_format->code;
+ if (fie->code != code)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(supported_modes); i++)
+ if (supported_modes[i].width == fie->width &&
+ supported_modes[i].height == fie->height)
+ break;
+
+ if (i >= ARRAY_SIZE(supported_modes))
+ return -EINVAL;
+
+ fie->interval.numerator = supported_modes[i].frame_interval.numerator;
+ fie->interval.denominator = supported_modes[i].frame_interval.denominator;
+
+ 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;
+ struct gc2145_ctrls *ctrls = &gc2145->ctrls;
+
+ 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(ctrls->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
+ * 640x480 RGB: 0x0190
+ * 1280x720 / 1600x1200 (aka no scaler) non RAW: 0x0001
+ * 1600x1200 RAW: 0x0190
+ */
+ if (gc2145_format->colorspace != V4L2_COLORSPACE_RAW) {
+ if (gc2145->mode->width == 1280 || gc2145->mode->width == 1600)
+ fifo_full_lvl = 0x0001;
+ 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;
+
+ /* Come back on page 0 by default */
+ ret = gc2145_write_reg(gc2145, GC2145_REG_PAGE_SELECT, 0x00);
+ if (ret)
+ return ret;
+
+ /* Apply customized values from user */
+ ret = __v4l2_ctrl_handler_setup(&gc2145->ctrls.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 char * const test_pattern_menu[] = {
+ "Disabled",
+ "Colored patterns",
+ "Uniform white",
+ "Uniform yellow",
+ "Uniform cyan",
+ "Uniform green",
+ "Uniform magenta",
+ "Uniform red",
+ "Uniform black",
+};
+
+#define GC2145_TEST_PATTERN_ENABLE BIT(0)
+#define GC2145_TEST_PATTERN_UXGA BIT(3)
+
+#define GC2145_TEST_UNIFORM BIT(3)
+#define GC2145_TEST_WHITE (4 << 4)
+#define GC2145_TEST_YELLOW (8 << 4)
+#define GC2145_TEST_CYAN (9 << 4)
+#define GC2145_TEST_GREEN (6 << 4)
+#define GC2145_TEST_MAGENTA (10 << 4)
+#define GC2145_TEST_RED (5 << 4)
+#define GC2145_TEST_BLACK (0)
+
+static const u8 test_pattern_val[] = {
+ 0,
+ GC2145_TEST_PATTERN_ENABLE,
+ GC2145_TEST_UNIFORM | GC2145_TEST_WHITE,
+ GC2145_TEST_UNIFORM | GC2145_TEST_YELLOW,
+ GC2145_TEST_UNIFORM | GC2145_TEST_CYAN,
+ GC2145_TEST_UNIFORM | GC2145_TEST_GREEN,
+ GC2145_TEST_UNIFORM | GC2145_TEST_MAGENTA,
+ GC2145_TEST_UNIFORM | GC2145_TEST_RED,
+ GC2145_TEST_UNIFORM | GC2145_TEST_BLACK,
+};
+
+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,
+ .enum_frame_interval = gc2145_enum_frame_interval,
+};
+
+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,
+};
+
+static int gc2145_set_ctrl_test_pattern(struct gc2145 *gc2145, int value)
+{
+ int ret;
+
+ if (!value) {
+ /* Disable test pattern */
+ ret = gc2145_write_reg(gc2145, GC2145_REG_DEBUG_MODE2, 0);
+ if (ret)
+ return ret;
+
+ return gc2145_write_reg(gc2145, GC2145_REG_DEBUG_MODE3, 0);
+ }
+
+ /* Enable test pattern, colored or uniform */
+ ret = gc2145_write_reg(gc2145, GC2145_REG_DEBUG_MODE2,
+ GC2145_TEST_PATTERN_ENABLE |
+ GC2145_TEST_PATTERN_UXGA);
+ if (ret)
+ return ret;
+
+ if (!(test_pattern_val[value] & GC2145_TEST_UNIFORM))
+ return gc2145_write_reg(gc2145, GC2145_REG_DEBUG_MODE3, 0);
+
+ /* Uniform */
+ return gc2145_write_reg(gc2145, GC2145_REG_DEBUG_MODE3,
+ test_pattern_val[value]);
+}
+
+static int gc2145_set_ctrl_hflip(struct gc2145 *gc2145, int value)
+{
+ return gc2145_mod_reg(gc2145, GC2145_REG_ANALOG_MODE1,
+ BIT(0), (value ? BIT(0) : 0));
+}
+
+static int gc2145_set_ctrl_vflip(struct gc2145 *gc2145, int value)
+{
+ return gc2145_mod_reg(gc2145, GC2145_REG_ANALOG_MODE1,
+ BIT(1), (value ? BIT(1) : 0));
+}
+
+static int gc2145_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ struct gc2145 *gc2145 = to_gc2145(sd);
+ int ret;
+
+ /* v4l2_ctrl_lock() locks our own mutex */
+
+ ret = pm_runtime_resume_and_get(&client->dev);
+ if (ret < 0)
+ return ret;
+
+ switch (ctrl->id) {
+ case V4L2_CID_TEST_PATTERN:
+ ret = gc2145_set_ctrl_test_pattern(gc2145, ctrl->val);
+ break;
+ case V4L2_CID_HFLIP:
+ ret = gc2145_set_ctrl_hflip(gc2145, ctrl->val);
+ break;
+ case V4L2_CID_VFLIP:
+ ret = gc2145_set_ctrl_vflip(gc2145, ctrl->val);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ pm_runtime_put(&client->dev);
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops gc2145_ctrl_ops = {
+ .s_ctrl = gc2145_s_ctrl,
+};
+
+/* Initialize control handlers */
+static int gc2145_init_controls(struct gc2145 *gc2145)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&gc2145->sd);
+ const struct v4l2_ctrl_ops *ops = &gc2145_ctrl_ops;
+ struct gc2145_ctrls *ctrls = &gc2145->ctrls;
+ struct v4l2_ctrl_handler *hdl = &ctrls->handler;
+ int ret;
+
+ ret = v4l2_ctrl_handler_init(hdl, 12);
+ if (ret)
+ return ret;
+
+ hdl->lock = &gc2145->mutex;
+
+ /* By default, PIXEL_RATE is read only */
+ ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE,
+ 0, INT_MAX, 1,
+ GC2145_640_480_PIXELRATE);
+ ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ ctrls->test_pattern =
+ v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN,
+ ARRAY_SIZE(test_pattern_menu) - 1,
+ 0, 0, test_pattern_menu);
+ ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP,
+ 0, 1, 1, 0);
+ ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP,
+ 0, 1, 1, 0);
+
+ if (hdl->error) {
+ ret = hdl->error;
+ dev_err(&client->dev, "control init failed (%d)\n", ret);
+ goto error;
+ }
+
+ gc2145->sd.ctrl_handler = hdl;
+
+ return 0;
+
+error:
+ v4l2_ctrl_handler_free(hdl);
+ mutex_destroy(&gc2145->mutex);
+
+ return ret;
+}
+
+static void gc2145_free_controls(struct gc2145 *gc2145)
+{
+ v4l2_ctrl_handler_free(&gc2145->ctrls.handler);
+}
+
+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;
+
+ mutex_init(&gc2145->mutex);
+
+ 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);
+ mutex_destroy(&gc2145->mutex);
+
+ 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);
+
+ mutex_destroy(&gc2145->mutex);
+
+ 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 a141552531f7..5ec6addf777b 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,
/* update format even if code is unchanged, resolution might change */
sensor->fmt = *mbus_fmt;
- __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 f630b88cbfaa..b69e5baafb2e 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 80321e03809a..94a4ba771a7f 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 48b36db2c2e2..a37eee4bfad7 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 6110718645a4..c6d58e3ecd41 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 000000000000..cbddc98141a4
--- /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 000000000000..01b1fb6ce488
--- /dev/null
+++ b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-bytecap.c
@@ -0,0 +1,1112 @@
+// 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 = DCMIPP_COLORSPACE_DEFAULT,
+ .ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT,
+ .quantization = DCMIPP_QUANTIZATION_DEFAULT,
+ .xfer_func = DCMIPP_XFER_FUNC_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 000000000000..62e55987cc17
--- /dev/null
+++ b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-byteproc.c
@@ -0,0 +1,790 @@
+// 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 DCMIPP_P0PPCR_OELS BIT(11)
+
+#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 = DCMIPP_COLORSPACE_DEFAULT,
+ .ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT,
+ .quantization = DCMIPP_QUANTIZATION_DEFAULT,
+ .xfer_func = DCMIPP_XFER_FUNC_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 = 0;
+ 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 = 0;
+ 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 = 0;
+ crop->left = 0;
+ crop->width = fmt->format.width;
+ crop->height = fmt->format.height;
+ *compose = *crop;
+ *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 = 0;
+ 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 unsigned int dcmipp_frates[] = {1, 2, 4, 8};
+
+static int dcmipp_byteproc_enum_frame_interval
+ (struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_frame_interval_enum *fie)
+{
+ struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
+ struct v4l2_fract *sink_interval = &byteproc->sink_interval;
+ unsigned int ratio;
+ int ret = 0;
+
+ if (fie->pad > 1 ||
+ fie->index >= (IS_SRC(fie->pad) ? ARRAY_SIZE(dcmipp_frates) : 1) ||
+ fie->width > DCMIPP_FRAME_MAX_WIDTH ||
+ fie->height > DCMIPP_FRAME_MAX_HEIGHT)
+ return -EINVAL;
+
+ mutex_lock(&byteproc->lock);
+
+ if (IS_SINK(fie->pad)) {
+ fie->interval = *sink_interval;
+ goto out;
+ }
+
+ ratio = dcmipp_frates[fie->index];
+
+ fie->interval.numerator = sink_interval->numerator * ratio;
+ fie->interval.denominator = sink_interval->denominator;
+
+out:
+ mutex_unlock(&byteproc->lock);
+ return ret;
+}
+
+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,
+ .enum_frame_interval = dcmipp_byteproc_enum_frame_interval,
+ .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 | DCMIPP_P0PPCR_OELS;
+
+ /* 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 (fi->interval.numerator == 0 || fi->interval.denominator == 0)
+ fi->interval = byteproc->sink_interval;
+
+ if (IS_SINK(fi->pad)) {
+ /*
+ * Setting sink frame interval resets frame skipping.
+ * Sink frame interval is propagated to src.
+ */
+ byteproc->frate = 0;
+ byteproc->sink_interval = fi->interval;
+ 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 =
+ byteproc->sink_interval.numerator * ratio;
+ byteproc->src_interval.denominator =
+ byteproc->sink_interval.denominator;
+ }
+
+ 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->crop = r;
+ byteproc->compose = r;
+ byteproc->src_interval = interval;
+ byteproc->sink_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 000000000000..2a566bacc80e
--- /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 000000000000..50108fd4563b
--- /dev/null
+++ b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-common.h
@@ -0,0 +1,240 @@
+/* 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))
+
+#define DCMIPP_COLORSPACE_DEFAULT V4L2_COLORSPACE_REC709
+#define DCMIPP_YCBCR_ENC_DEFAULT V4L2_YCBCR_ENC_DEFAULT
+#define DCMIPP_QUANTIZATION_DEFAULT V4L2_QUANTIZATION_DEFAULT
+#define DCMIPP_XFER_FUNC_DEFAULT V4L2_XFER_FUNC_DEFAULT
+
+/**
+ * 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 = DCMIPP_COLORSPACE_DEFAULT; \
+ (fmt)->ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT; \
+ (fmt)->quantization = DCMIPP_QUANTIZATION_DEFAULT; \
+ (fmt)->xfer_func = DCMIPP_XFER_FUNC_DEFAULT; \
+ } \
+ if ((fmt)->ycbcr_enc > V4L2_YCBCR_ENC_SMPTE240M) \
+ (fmt)->ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT; \
+ if ((fmt)->quantization > V4L2_QUANTIZATION_LIM_RANGE) \
+ (fmt)->quantization = DCMIPP_QUANTIZATION_DEFAULT; \
+ if ((fmt)->xfer_func > V4L2_XFER_FUNC_SMPTE2084) \
+ (fmt)->xfer_func = DCMIPP_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 000000000000..7935ee3ead71
--- /dev/null
+++ b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-core.c
@@ -0,0 +1,682 @@
+// 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 *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);
+
+ 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->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 000000000000..e737a48cfd66
--- /dev/null
+++ b/drivers/media/platform/stm32/stm32-dcmipp/dcmipp-parallel.c
@@ -0,0 +1,497 @@
+// 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 = DCMIPP_COLORSPACE_DEFAULT,
+ .ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT,
+ .quantization = DCMIPP_QUANTIZATION_DEFAULT,
+ .xfer_func = DCMIPP_XFER_FUNC_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 && 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 843259c304bb..1259d094a61b 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/nvmem/stm32-romem.c b/drivers/nvmem/stm32-romem.c
index 354be526897f..bb1d52988179 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,54 @@
/* shadow registers offest */
#define STM32MP15_BSEC_DATA0 0x200
-/* 32 (x 32-bits) lower shadow registers */
-#define STM32MP15_BSEC_NUM_LOWER 32
+/*
+ * 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 STM32_ROMEM_AUTOSUSPEND_DELAY_MS 50
struct stm32_romem_cfg {
int size;
+ u8 lower;
+ bool ta;
};
struct stm32_romem_priv {
void __iomem *base;
struct nvmem_config cfg;
+ struct clk *clk;
+ struct device *ta;
+ u8 lower;
};
+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_bsec_pta_write(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 +94,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,18 +114,24 @@ 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;
- if (otp < STM32MP15_BSEC_NUM_LOWER) {
+ if (otp < priv->lower) {
/* read lower data from shadow registers */
val = readl_relaxed(
priv->base + STM32MP15_BSEC_DATA0 + i);
@@ -95,7 +141,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 +155,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 +170,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 >= priv->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 +233,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 +251,9 @@ 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;
+
+ priv->lower = 0;
cfg = (const struct stm32_romem_cfg *)
of_match_device(dev->driver->of_match_table, dev)->data;
@@ -167,15 +263,110 @@ 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;
+ priv->lower = cfg->lower;
+ 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.reg_read = stm32_bsec_pta_read;
+ priv->cfg.reg_write = stm32_bsec_pta_write;
+ } 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");
+ }
+
+ 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 PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &priv->cfg));
+ 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 */
+ .lower = 32, /* 32 word with incremental bit programming */
+ .ta = false,
+};
+
+static const struct stm32_romem_cfg stm32mp13_bsec_cfg = {
+ .size = 384, /* 96 x 32-bits data words */
+ .lower = 32, /* 32 word with incremental bit programming */
+ .ta = true,
};
static const struct of_device_id stm32_romem_of_match[] = {
@@ -183,6 +374,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 +384,425 @@ 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[0].a OTP start offset in byte
+ * [in] value[0].b Access type (0 : shadow,
+ * 1 : fuse, 2 : lock)
+ * [out] memref[1].buffer Output buffer to store read values
+ * [out] memref[1].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 */
+
+/*
+ * Write OTP memory
+ *
+ * [in] value[0].a OTP start offset in byte
+ * [in] value[0].b Access type (0 : shadow,
+ * 1 : fuse, 2 : lock)
+ * [in] memref[1].buffer Input buffer to read values
+ * [in] memref[1].size Size of OTP to be written
+ *
+ * Return codes:
+ * TEE_SUCCESS - Invoke command success
+ * TEE_ERROR_BAD_PARAMETERS - Incorrect input param
+ */
+#define PTA_BSEC_WRITE_MEM 0x1 /* Write OTP */
+
+/* value of PTA_BSEC access type = value[in] b */
+#define SHADOW_ACCESS 0
+#define FUSE_ACCESS 1
+#define LOCK_ACCESS 2
+
+/* Bitfield definition for LOCK status */
+#define LOCK_PERM BIT(30)
+
+/**
+ * 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(&param, 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;
+}
+
+/**
+ * stm32_bsec_pta_write() - nvmem write access using PTA client driver
+ * @context: nvmem context => romem privdate data
+ * @offset: nvmem offset
+ * @buf: buffer with nvem values
+ * @bytes: number of bytes to write
+ *
+ * Return:
+ * On success, 0. On failure, -errno.
+ */
+static int stm32_bsec_pta_write(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;
+ int ret;
+
+ dev = romem_priv->ta;
+ if (!dev) {
+ pr_err("TA_BSEC invoke with driver\n");
+ return -ENXIO;
+ }
+
+ /* Allow only writing complete 32-bits aligned words */
+ if ((bytes % 4) || (offset % 4))
+ return -EINVAL;
+
+ priv = dev_get_drvdata(dev);
+
+ memset(&arg, 0, sizeof(arg));
+ memset(&param, 0, sizeof(param));
+
+ arg.func = PTA_BSEC_WRITE_MEM;
+ arg.session = priv->session_id;
+ arg.num_params = 2;
+
+ param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT;
+ param[0].u.value.a = offset;
+ param[0].u.value.b = FUSE_ACCESS;
+
+ shm = tee_shm_alloc(priv->ctx, 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_INPUT;
+ param[1].u.memref.shm = shm;
+ param[1].u.memref.size = bytes;
+
+ shm_buf = tee_shm_get_va(shm, 0);
+ if (IS_ERR(shm_buf)) {
+ dev_err(dev, "tee_shm_get_va failed for transmit\n");
+ return PTR_ERR(shm_buf);
+ }
+
+ memcpy(shm_buf, buf, 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;
+ }
+ dev_dbg(dev, "Write OTPs %d to %d, ret=%d\n",
+ offset / 4, (offset + bytes) / 4, ret);
+
+ /* Lock the upper OTPs with ECC protection, word programming only */
+ if (!ret && ((offset + bytes) >= (romem_priv->lower * 4))) {
+ u32 start, nb_lock;
+ u32 *lock = (u32 *)shm_buf;
+ int i;
+
+ /*
+ * don't lock the lower OTPs, no ECC protection and incremental
+ * bit programming, a second write is allowed
+ */
+ start = max_t(u32, offset, romem_priv->lower * 4);
+ nb_lock = (offset + bytes - start) / 4;
+
+ param[0].u.value.a = start;
+ param[0].u.value.b = LOCK_ACCESS;
+ param[1].u.memref.size = nb_lock * 4;
+
+ for (i = 0; i < nb_lock; i++)
+ lock[i] = LOCK_PERM;
+
+ 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;
+ }
+ dev_dbg(dev, "Lock upper OTPs %d to %d, ret=%d\n",
+ start / 4, start / 4 + nb_lock, ret);
+ }
+
+ 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;
+}
+
+static int stm32_bsec_pta_write(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 74afbb7a4f5e..654ca9e9808c 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 61a1c87cd501..1c03db71661d 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 794ca5b02968..24aab0450c78 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 e8a30c4c5aec..c73f79a3799a 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 a05e9fbcd3e0..e4909f6467f9 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 000000000000..e0ee54bcd04e
--- /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 || MACH_STM32MP13
+ select PM_GENERIC_DOMAINS
+ default y if MACH_STM32MP157 || MACH_STM32MP13
+
+endif # ARCH_STM32
diff --git a/drivers/soc/st/Makefile b/drivers/soc/st/Makefile
new file mode 100644
index 000000000000..b57d9508021f
--- /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 000000000000..224b0cbd84de
--- /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 000000000000..47687ebd1ffd
--- /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 000000000000..0386624c20f2
--- /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/Kconfig b/drivers/thermal/st/Kconfig
index 58ece381956b..91db3e2885d6 100644
--- a/drivers/thermal/st/Kconfig
+++ b/drivers/thermal/st/Kconfig
@@ -18,10 +18,10 @@ config ST_THERMAL_MEMMAP
config STM32_THERMAL
tristate "Thermal framework support on STMicroelectronics STM32 series of SoCs"
- depends on MACH_STM32MP157
+ depends on MACH_STM32MP157 || MACH_STM32MP13
default y
help
Support for thermal framework on STMicroelectronics STM32 series of
SoCs. This thermal driver allows to access to general thermal framework
functionalities and to access to SoC sensor functionalities. This
- configuration is fully dependent of MACH_STM32MP157.
+ configuration is fully dependent of MACH_STM32MP157 or MACH_STM32MP13.
diff --git a/drivers/thermal/st/stm_thermal.c b/drivers/thermal/st/stm_thermal.c
index 5fd3fb8912a6..1e065a3323f9 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 000000000000..d98665327281
--- /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 000000000000..091c1c83587a
--- /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/gpio/machine.h b/include/linux/gpio/machine.h
index d755e529c1e3..1ed588fec969 100644
--- a/include/linux/gpio/machine.h
+++ b/include/linux/gpio/machine.h
@@ -14,6 +14,7 @@ enum gpio_lookup_flags {
GPIO_TRANSITORY = (1 << 3),
GPIO_PULL_UP = (1 << 4),
GPIO_PULL_DOWN = (1 << 5),
+ GPIO_PULL_DISABLE = (1 << 6),
GPIO_LOOKUP_FLAGS_DEFAULT = GPIO_ACTIVE_HIGH | GPIO_PERSISTENT,
};
diff --git a/include/linux/smscphy.h b/include/linux/smscphy.h
index 1a136271ba6a..00884908e509 100644
--- a/include/linux/smscphy.h
+++ b/include/linux/smscphy.h
@@ -14,6 +14,7 @@
#define MII_LAN83C185_ISF_INT5 (1<<5) /* Remote Fault Detected */
#define MII_LAN83C185_ISF_INT6 (1<<6) /* Auto-Negotiation complete */
#define MII_LAN83C185_ISF_INT7 (1<<7) /* ENERGYON */
+#define MII_LAN83C185_ISF_INT8 (1<<8) /* Wake on LAN */
#define MII_LAN83C185_ISF_INT_ALL (0x0e)
@@ -28,4 +29,23 @@
#define MII_LAN83C185_MODE_POWERDOWN 0xC0 /* Power Down mode */
#define MII_LAN83C185_MODE_ALL 0xE0 /* All capable mode */
+/* MMD 3 Registers */
+#define LAN8742_MMD3_WAKEUP_CTRL (32784)
+#define LAN8742_MMD3_WUCSR_LED2_AS_NPME BIT(12)
+#define LAN8742_MMD3_WUCSR_WOL BIT(8)
+#define LAN8742_MMD3_WUCSR_PFDA_FR BIT(7)
+#define LAN8742_MMD3_WUCSR_WUFR BIT(6)
+#define LAN8742_MMD3_WUCSR_MPR BIT(5)
+#define LAN8742_MMD3_WUCSR_BCAST_FR BIT(4)
+#define LAN8742_MMD3_WUCSR_MPEN BIT(1)
+
+#define LAN8742_MMD3_WAKEUP_FILTER (32785)
+#define LAN8742_MMD3_WUF_CFGA_FE BIT(15)
+#define LAN8742_MMD3_WUF_CFGA_AME BIT(10)
+
+#define LAN8742_MMD3_MAC_ADDRA (32865)
+#define LAN8742_MMD3_MAC_ADDRB (32866)
+#define LAN8742_MMD3_MAC_ADDRC (32867)
+#define LAN8742_MMD3_PME_ASSERT_DELAY (32868)
+
#endif /* __LINUX_SMSCPHY_H__ */
diff --git a/include/media/mipi-csi2.h b/include/media/mipi-csi2.h
new file mode 100644
index 000000000000..392794e5badd
--- /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 7ab033b819eb..065ce8d26fa6 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.17.1