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