diff options
Diffstat (limited to 'target/linux/starfive/patches-6.6')
140 files changed, 67707 insertions, 0 deletions
diff --git a/target/linux/starfive/patches-6.6/0001-clk-starfive-jh7110-sys-Fix-lower-rate-of-CPUfreq-by.patch b/target/linux/starfive/patches-6.6/0001-clk-starfive-jh7110-sys-Fix-lower-rate-of-CPUfreq-by.patch new file mode 100644 index 0000000000..e98e6dfaaf --- /dev/null +++ b/target/linux/starfive/patches-6.6/0001-clk-starfive-jh7110-sys-Fix-lower-rate-of-CPUfreq-by.patch @@ -0,0 +1,76 @@ +From 69275b667bd930cf5d5f577ba0ab1987c9d13987 Mon Sep 17 00:00:00 2001 +From: Xingyu Wu <xingyu.wu@starfivetech.com> +Date: Mon, 21 Aug 2023 23:29:15 +0800 +Subject: [PATCH 001/116] clk: starfive: jh7110-sys: Fix lower rate of CPUfreq + by setting PLL0 rate to 1.5GHz + +CPUfreq supports 4 cpu frequency loads on 375/500/750/1500MHz. +But now PLL0 rate is 1GHz and the cpu frequency loads become +333/500/500/1000MHz in fact. + +So PLL0 rate should be set to 1.5GHz. Change the parent of cpu_root clock +and the divider of cpu_core before the setting. + +Reviewed-by: Hal Feng <hal.feng@starfivetech.com> +Fixes: e2c510d6d630 ("riscv: dts: starfive: Add cpu scaling for JH7110 SoC") +Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com> +--- + .../clk/starfive/clk-starfive-jh7110-sys.c | 47 ++++++++++++++++++- + 1 file changed, 46 insertions(+), 1 deletion(-) + +--- a/drivers/clk/starfive/clk-starfive-jh7110-sys.c ++++ b/drivers/clk/starfive/clk-starfive-jh7110-sys.c +@@ -530,7 +530,52 @@ static int __init jh7110_syscrg_probe(st + if (ret) + return ret; + +- return jh7110_reset_controller_register(priv, "rst-sys", 0); ++ ret = jh7110_reset_controller_register(priv, "rst-sys", 0); ++ if (ret) ++ return ret; ++ ++ /* ++ * Set PLL0 rate to 1.5GHz ++ * In order to not affect the cpu when the PLL0 rate is changing, ++ * we need to switch the parent of cpu_root clock to osc clock first, ++ * and then switch back after setting the PLL0 rate. ++ */ ++ pllclk = clk_get(priv->dev, "pll0_out"); ++ if (!IS_ERR(pllclk)) { ++ struct clk *osc = clk_get(&pdev->dev, "osc"); ++ struct clk *cpu_root = priv->reg[JH7110_SYSCLK_CPU_ROOT].hw.clk; ++ struct clk *cpu_core = priv->reg[JH7110_SYSCLK_CPU_CORE].hw.clk; ++ ++ if (IS_ERR(osc)) { ++ clk_put(pllclk); ++ return PTR_ERR(osc); ++ } ++ ++ /* ++ * CPU need voltage regulation by CPUfreq if set 1.5GHz. ++ * So in this driver, cpu_core need to be set the divider to be 2 first ++ * and will be 750M after setting parent. ++ */ ++ ret = clk_set_rate(cpu_core, clk_get_rate(cpu_core) / 2); ++ if (ret) ++ goto failed_set; ++ ++ ret = clk_set_parent(cpu_root, osc); ++ if (ret) ++ goto failed_set; ++ ++ ret = clk_set_rate(pllclk, 1500000000); ++ if (ret) ++ goto failed_set; ++ ++ ret = clk_set_parent(cpu_root, pllclk); ++ ++failed_set: ++ clk_put(pllclk); ++ clk_put(osc); ++ } ++ ++ return ret; + } + + static const struct of_device_id jh7110_syscrg_match[] = { diff --git a/target/linux/starfive/patches-6.6/0002-dt-bindings-timer-Add-timer-for-StarFive-JH7110-SoC.patch b/target/linux/starfive/patches-6.6/0002-dt-bindings-timer-Add-timer-for-StarFive-JH7110-SoC.patch new file mode 100644 index 0000000000..2b5318bb44 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0002-dt-bindings-timer-Add-timer-for-StarFive-JH7110-SoC.patch @@ -0,0 +1,114 @@ +From 7d0dbcbc079e4f72b69f53442b7759da6ebc4f87 Mon Sep 17 00:00:00 2001 +From: Xingyu Wu <xingyu.wu@starfivetech.com> +Date: Thu, 19 Oct 2023 13:34:59 +0800 +Subject: [PATCH 002/116] dt-bindings: timer: Add timer for StarFive JH7110 SoC + +Add bindings for the timer on the JH7110 RISC-V SoC +by StarFive Technology Ltd. + +Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> +Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com> +--- + .../bindings/timer/starfive,jh7110-timer.yaml | 96 +++++++++++++++++++ + 1 file changed, 96 insertions(+) + create mode 100644 Documentation/devicetree/bindings/timer/starfive,jh7110-timer.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/timer/starfive,jh7110-timer.yaml +@@ -0,0 +1,96 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/timer/starfive,jh7110-timer.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: StarFive JH7110 Timer ++ ++maintainers: ++ - Xingyu Wu <xingyu.wu@starfivetech.com> ++ - Samin Guo <samin.guo@starfivetech.com> ++ ++description: ++ This timer has four free-running 32 bit counters in StarFive JH7110 SoC. ++ And each channel(counter) triggers an interrupt when timeout. They support ++ one-shot mode and continuous-run mode. ++ ++properties: ++ compatible: ++ const: starfive,jh7110-timer ++ ++ reg: ++ maxItems: 1 ++ ++ interrupts: ++ items: ++ - description: channel 0 ++ - description: channel 1 ++ - description: channel 2 ++ - description: channel 3 ++ ++ clocks: ++ items: ++ - description: timer APB ++ - description: channel 0 ++ - description: channel 1 ++ - description: channel 2 ++ - description: channel 3 ++ ++ clock-names: ++ items: ++ - const: apb ++ - const: ch0 ++ - const: ch1 ++ - const: ch2 ++ - const: ch3 ++ ++ resets: ++ items: ++ - description: timer APB ++ - description: channel 0 ++ - description: channel 1 ++ - description: channel 2 ++ - description: channel 3 ++ ++ reset-names: ++ items: ++ - const: apb ++ - const: ch0 ++ - const: ch1 ++ - const: ch2 ++ - const: ch3 ++ ++required: ++ - compatible ++ - reg ++ - interrupts ++ - clocks ++ - clock-names ++ - resets ++ - reset-names ++ ++additionalProperties: false ++ ++examples: ++ - | ++ timer@13050000 { ++ compatible = "starfive,jh7110-timer"; ++ reg = <0x13050000 0x10000>; ++ interrupts = <69>, <70>, <71> ,<72>; ++ clocks = <&clk 124>, ++ <&clk 125>, ++ <&clk 126>, ++ <&clk 127>, ++ <&clk 128>; ++ clock-names = "apb", "ch0", "ch1", ++ "ch2", "ch3"; ++ resets = <&rst 117>, ++ <&rst 118>, ++ <&rst 119>, ++ <&rst 120>, ++ <&rst 121>; ++ reset-names = "apb", "ch0", "ch1", ++ "ch2", "ch3"; ++ }; ++ diff --git a/target/linux/starfive/patches-6.6/0003-clocksource-Add-JH7110-timer-driver.patch b/target/linux/starfive/patches-6.6/0003-clocksource-Add-JH7110-timer-driver.patch new file mode 100644 index 0000000000..68b9c38d5b --- /dev/null +++ b/target/linux/starfive/patches-6.6/0003-clocksource-Add-JH7110-timer-driver.patch @@ -0,0 +1,428 @@ +From 7cb47848f8a10aed6e050c0ea483b4bb5eaa62a4 Mon Sep 17 00:00:00 2001 +From: Xingyu Wu <xingyu.wu@starfivetech.com> +Date: Thu, 19 Oct 2023 13:35:00 +0800 +Subject: [PATCH 003/116] clocksource: Add JH7110 timer driver + +Add timer driver for the StarFive JH7110 SoC. + +Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com> +--- + drivers/clocksource/Kconfig | 11 + + drivers/clocksource/Makefile | 1 + + drivers/clocksource/timer-jh7110.c | 380 +++++++++++++++++++++++++++++ + 3 files changed, 392 insertions(+) + create mode 100644 drivers/clocksource/timer-jh7110.c + +--- a/drivers/clocksource/Kconfig ++++ b/drivers/clocksource/Kconfig +@@ -641,6 +641,17 @@ config RISCV_TIMER + is accessed via both the SBI and the rdcycle instruction. This is + required for all RISC-V systems. + ++config STARFIVE_JH7110_TIMER ++ bool "Timer for the STARFIVE JH7110 SoC" ++ depends on ARCH_STARFIVE || COMPILE_TEST ++ select TIMER_OF ++ select CLKSRC_MMIO ++ default ARCH_STARFIVE ++ help ++ This enables the timer for StarFive JH7110 SoC. On RISC-V platform, ++ the system has started RISCV_TIMER, but you can also use this timer ++ which can provide four channels to do a lot more things on JH7110 SoC. ++ + config CLINT_TIMER + bool "CLINT Timer for the RISC-V platform" if COMPILE_TEST + depends on GENERIC_SCHED_CLOCK && RISCV +--- a/drivers/clocksource/Makefile ++++ b/drivers/clocksource/Makefile +@@ -80,6 +80,7 @@ obj-$(CONFIG_INGENIC_TIMER) += ingenic- + obj-$(CONFIG_CLKSRC_ST_LPC) += clksrc_st_lpc.o + obj-$(CONFIG_X86_NUMACHIP) += numachip.o + obj-$(CONFIG_RISCV_TIMER) += timer-riscv.o ++obj-$(CONFIG_STARFIVE_JH7110_TIMER) += timer-jh7110.o + obj-$(CONFIG_CLINT_TIMER) += timer-clint.o + obj-$(CONFIG_CSKY_MP_TIMER) += timer-mp-csky.o + obj-$(CONFIG_GX6605S_TIMER) += timer-gx6605s.o +--- /dev/null ++++ b/drivers/clocksource/timer-jh7110.c +@@ -0,0 +1,380 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Starfive JH7110 Timer driver ++ * ++ * Copyright (C) 2022-2023 StarFive Technology Co., Ltd. ++ * ++ * Author: ++ * Xingyu Wu <xingyu.wu@starfivetech.com> ++ * Samin Guo <samin.guo@starfivetech.com> ++ */ ++ ++#include <linux/clk.h> ++#include <linux/clockchips.h> ++#include <linux/clocksource.h> ++#include <linux/err.h> ++#include <linux/interrupt.h> ++#include <linux/io.h> ++#include <linux/iopoll.h> ++#include <linux/irq.h> ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/of_device.h> ++#include <linux/platform_device.h> ++#include <linux/reset.h> ++#include <linux/sched_clock.h> ++ ++/* Bias: Ch0-0x0, Ch1-0x40, Ch2-0x80, and so on. */ ++#define JH7110_TIMER_CH_LEN 0x40 ++#define JH7110_TIMER_CH_BASE(x) ((x) * JH7110_TIMER_CH_LEN) ++#define JH7110_TIMER_CH_MAX 4 ++ ++#define JH7110_CLOCK_SOURCE_RATING 200 ++#define JH7110_VALID_BITS 32 ++#define JH7110_DELAY_US 0 ++#define JH7110_TIMEOUT_US 10000 ++#define JH7110_CLOCKEVENT_RATING 300 ++#define JH7110_TIMER_MAX_TICKS 0xffffffff ++#define JH7110_TIMER_MIN_TICKS 0xf ++#define JH7110_TIMER_RELOAD_VALUE 0 ++ ++#define JH7110_TIMER_INT_STATUS 0x00 /* RO[0:4]: Interrupt Status for channel0~4 */ ++#define JH7110_TIMER_CTL 0x04 /* RW[0]: 0-continuous run, 1-single run */ ++#define JH7110_TIMER_LOAD 0x08 /* RW: load value to counter */ ++#define JH7110_TIMER_ENABLE 0x10 /* RW[0]: timer enable register */ ++#define JH7110_TIMER_RELOAD 0x14 /* RW: write 1 or 0 both reload counter */ ++#define JH7110_TIMER_VALUE 0x18 /* RO: timer value register */ ++#define JH7110_TIMER_INT_CLR 0x20 /* RW: timer interrupt clear register */ ++#define JH7110_TIMER_INT_MASK 0x24 /* RW[0]: timer interrupt mask register */ ++ ++#define JH7110_TIMER_INT_CLR_ENA BIT(0) ++#define JH7110_TIMER_INT_CLR_AVA_MASK BIT(1) ++ ++struct jh7110_clkevt { ++ struct clock_event_device evt; ++ struct clocksource cs; ++ bool cs_is_valid; ++ struct clk *clk; ++ struct reset_control *rst; ++ u32 rate; ++ u32 reload_val; ++ void __iomem *base; ++ char name[sizeof("jh7110-timer.chX")]; ++}; ++ ++struct jh7110_timer_priv { ++ struct clk *pclk; ++ struct reset_control *prst; ++ struct jh7110_clkevt clkevt[JH7110_TIMER_CH_MAX]; ++}; ++ ++/* 0:continuous-run mode, 1:single-run mode */ ++enum jh7110_timer_mode { ++ JH7110_TIMER_MODE_CONTIN, ++ JH7110_TIMER_MODE_SINGLE, ++}; ++ ++/* Interrupt Mask, 0:Unmask, 1:Mask */ ++enum jh7110_timer_int_mask { ++ JH7110_TIMER_INT_ENA, ++ JH7110_TIMER_INT_DIS, ++}; ++ ++enum jh7110_timer_enable { ++ JH7110_TIMER_DIS, ++ JH7110_TIMER_ENA, ++}; ++ ++static inline struct jh7110_clkevt *to_jh7110_clkevt(struct clock_event_device *evt) ++{ ++ return container_of(evt, struct jh7110_clkevt, evt); ++} ++ ++/* ++ * BIT(0): Read value represent channel int status. ++ * Write 1 to this bit to clear interrupt. Write 0 has no effects. ++ * BIT(1): "1" means that it is clearing interrupt. BIT(0) can not be written. ++ */ ++static inline int jh7110_timer_int_clear(struct jh7110_clkevt *clkevt) ++{ ++ u32 value; ++ int ret; ++ ++ /* Waiting interrupt can be cleared */ ++ ret = readl_poll_timeout_atomic(clkevt->base + JH7110_TIMER_INT_CLR, value, ++ !(value & JH7110_TIMER_INT_CLR_AVA_MASK), ++ JH7110_DELAY_US, JH7110_TIMEOUT_US); ++ if (!ret) ++ writel(JH7110_TIMER_INT_CLR_ENA, clkevt->base + JH7110_TIMER_INT_CLR); ++ ++ return ret; ++} ++ ++static int jh7110_timer_start(struct jh7110_clkevt *clkevt) ++{ ++ int ret; ++ ++ /* Disable and clear interrupt first */ ++ writel(JH7110_TIMER_INT_DIS, clkevt->base + JH7110_TIMER_INT_MASK); ++ ret = jh7110_timer_int_clear(clkevt); ++ if (ret) ++ return ret; ++ ++ writel(JH7110_TIMER_INT_ENA, clkevt->base + JH7110_TIMER_INT_MASK); ++ writel(JH7110_TIMER_ENA, clkevt->base + JH7110_TIMER_ENABLE); ++ ++ return 0; ++} ++ ++static int jh7110_timer_shutdown(struct clock_event_device *evt) ++{ ++ struct jh7110_clkevt *clkevt = to_jh7110_clkevt(evt); ++ ++ writel(JH7110_TIMER_DIS, clkevt->base + JH7110_TIMER_ENABLE); ++ return jh7110_timer_int_clear(clkevt); ++} ++ ++static void jh7110_timer_suspend(struct clock_event_device *evt) ++{ ++ struct jh7110_clkevt *clkevt = to_jh7110_clkevt(evt); ++ ++ clkevt->reload_val = readl(clkevt->base + JH7110_TIMER_LOAD); ++ jh7110_timer_shutdown(evt); ++} ++ ++static void jh7110_timer_resume(struct clock_event_device *evt) ++{ ++ struct jh7110_clkevt *clkevt = to_jh7110_clkevt(evt); ++ ++ writel(clkevt->reload_val, clkevt->base + JH7110_TIMER_LOAD); ++ writel(JH7110_TIMER_RELOAD_VALUE, clkevt->base + JH7110_TIMER_RELOAD); ++ jh7110_timer_start(clkevt); ++} ++ ++static int jh7110_timer_tick_resume(struct clock_event_device *evt) ++{ ++ jh7110_timer_resume(evt); ++ ++ return 0; ++} ++ ++/* IRQ handler for the timer */ ++static irqreturn_t jh7110_timer_interrupt(int irq, void *priv) ++{ ++ struct clock_event_device *evt = (struct clock_event_device *)priv; ++ struct jh7110_clkevt *clkevt = to_jh7110_clkevt(evt); ++ ++ if (jh7110_timer_int_clear(clkevt)) ++ return IRQ_NONE; ++ ++ if (evt->event_handler) ++ evt->event_handler(evt); ++ ++ return IRQ_HANDLED; ++} ++ ++static int jh7110_timer_set_periodic(struct clock_event_device *evt) ++{ ++ struct jh7110_clkevt *clkevt = to_jh7110_clkevt(evt); ++ u32 periodic = DIV_ROUND_CLOSEST(clkevt->rate, HZ); ++ ++ writel(JH7110_TIMER_MODE_CONTIN, clkevt->base + JH7110_TIMER_CTL); ++ writel(periodic, clkevt->base + JH7110_TIMER_LOAD); ++ ++ return jh7110_timer_start(clkevt); ++} ++ ++static int jh7110_timer_set_oneshot(struct clock_event_device *evt) ++{ ++ struct jh7110_clkevt *clkevt = to_jh7110_clkevt(evt); ++ ++ writel(JH7110_TIMER_MODE_SINGLE, clkevt->base + JH7110_TIMER_CTL); ++ writel(JH7110_TIMER_MAX_TICKS, clkevt->base + JH7110_TIMER_LOAD); ++ ++ return jh7110_timer_start(clkevt); ++} ++ ++static int jh7110_timer_set_next_event(unsigned long next, ++ struct clock_event_device *evt) ++{ ++ struct jh7110_clkevt *clkevt = to_jh7110_clkevt(evt); ++ ++ writel(JH7110_TIMER_MODE_SINGLE, clkevt->base + JH7110_TIMER_CTL); ++ writel(next, clkevt->base + JH7110_TIMER_LOAD); ++ ++ return jh7110_timer_start(clkevt); ++} ++ ++static void jh7110_set_clockevent(struct clock_event_device *evt) ++{ ++ evt->features = CLOCK_EVT_FEAT_PERIODIC | ++ CLOCK_EVT_FEAT_ONESHOT | ++ CLOCK_EVT_FEAT_DYNIRQ; ++ evt->set_state_shutdown = jh7110_timer_shutdown; ++ evt->set_state_periodic = jh7110_timer_set_periodic; ++ evt->set_state_oneshot = jh7110_timer_set_oneshot; ++ evt->set_state_oneshot_stopped = jh7110_timer_shutdown; ++ evt->tick_resume = jh7110_timer_tick_resume; ++ evt->set_next_event = jh7110_timer_set_next_event; ++ evt->suspend = jh7110_timer_suspend; ++ evt->resume = jh7110_timer_resume; ++ evt->rating = JH7110_CLOCKEVENT_RATING; ++} ++ ++static u64 jh7110_timer_clocksource_read(struct clocksource *cs) ++{ ++ struct jh7110_clkevt *clkevt = container_of(cs, struct jh7110_clkevt, cs); ++ ++ return (u64)readl(clkevt->base + JH7110_TIMER_VALUE); ++} ++ ++static int jh7110_clocksource_init(struct jh7110_clkevt *clkevt) ++{ ++ int ret; ++ ++ clkevt->cs.name = clkevt->name; ++ clkevt->cs.rating = JH7110_CLOCK_SOURCE_RATING; ++ clkevt->cs.read = jh7110_timer_clocksource_read; ++ clkevt->cs.mask = CLOCKSOURCE_MASK(JH7110_VALID_BITS); ++ clkevt->cs.flags = CLOCK_SOURCE_IS_CONTINUOUS; ++ ++ ret = clocksource_register_hz(&clkevt->cs, clkevt->rate); ++ if (ret) ++ return ret; ++ ++ clkevt->cs_is_valid = true; /* clocksource register done */ ++ writel(JH7110_TIMER_MODE_CONTIN, clkevt->base + JH7110_TIMER_CTL); ++ writel(JH7110_TIMER_MAX_TICKS, clkevt->base + JH7110_TIMER_LOAD); ++ ++ return jh7110_timer_start(clkevt); ++} ++ ++static void jh7110_clockevents_register(struct jh7110_clkevt *clkevt) ++{ ++ clkevt->rate = clk_get_rate(clkevt->clk); ++ ++ jh7110_set_clockevent(&clkevt->evt); ++ clkevt->evt.name = clkevt->name; ++ clkevt->evt.cpumask = cpu_possible_mask; ++ ++ clockevents_config_and_register(&clkevt->evt, clkevt->rate, ++ JH7110_TIMER_MIN_TICKS, JH7110_TIMER_MAX_TICKS); ++} ++ ++static void jh7110_timer_release(void *data) ++{ ++ struct jh7110_timer_priv *priv = data; ++ int i; ++ ++ for (i = 0; i < JH7110_TIMER_CH_MAX; i++) { ++ /* Disable each channel of timer */ ++ if (priv->clkevt[i].base) ++ writel(JH7110_TIMER_DIS, priv->clkevt[i].base + JH7110_TIMER_ENABLE); ++ ++ /* Avoid no initialization in the loop of the probe */ ++ if (!IS_ERR_OR_NULL(priv->clkevt[i].rst)) ++ reset_control_assert(priv->clkevt[i].rst); ++ ++ if (priv->clkevt[i].cs_is_valid) ++ clocksource_unregister(&priv->clkevt[i].cs); ++ } ++ ++ reset_control_assert(priv->prst); ++} ++ ++static int jh7110_timer_probe(struct platform_device *pdev) ++{ ++ struct jh7110_timer_priv *priv; ++ struct jh7110_clkevt *clkevt; ++ char name[sizeof("chX")]; ++ int ch; ++ int ret; ++ void __iomem *base; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ base = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(base)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(base), ++ "failed to map registers\n"); ++ ++ priv->prst = devm_reset_control_get_exclusive(&pdev->dev, "apb"); ++ if (IS_ERR(priv->prst)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(priv->prst), ++ "failed to get apb reset\n"); ++ ++ priv->pclk = devm_clk_get_enabled(&pdev->dev, "apb"); ++ if (IS_ERR(priv->pclk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(priv->pclk), ++ "failed to get & enable apb clock\n"); ++ ++ ret = reset_control_deassert(priv->prst); ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, "failed to deassert apb reset\n"); ++ ++ ret = devm_add_action_or_reset(&pdev->dev, jh7110_timer_release, priv); ++ if (ret) ++ return ret; ++ ++ for (ch = 0; ch < JH7110_TIMER_CH_MAX; ch++) { ++ clkevt = &priv->clkevt[ch]; ++ snprintf(name, sizeof(name), "ch%d", ch); ++ ++ clkevt->base = base + JH7110_TIMER_CH_BASE(ch); ++ /* Ensure timer is disabled */ ++ writel(JH7110_TIMER_DIS, clkevt->base + JH7110_TIMER_ENABLE); ++ ++ clkevt->rst = devm_reset_control_get_exclusive(&pdev->dev, name); ++ if (IS_ERR(clkevt->rst)) ++ return PTR_ERR(clkevt->rst); ++ ++ clkevt->clk = devm_clk_get_enabled(&pdev->dev, name); ++ if (IS_ERR(clkevt->clk)) ++ return PTR_ERR(clkevt->clk); ++ ++ ret = reset_control_deassert(clkevt->rst); ++ if (ret) ++ return ret; ++ ++ clkevt->evt.irq = platform_get_irq(pdev, ch); ++ if (clkevt->evt.irq < 0) ++ return clkevt->evt.irq; ++ ++ snprintf(clkevt->name, sizeof(clkevt->name), "jh7110-timer.ch%d", ch); ++ jh7110_clockevents_register(clkevt); ++ ++ ret = devm_request_irq(&pdev->dev, clkevt->evt.irq, jh7110_timer_interrupt, ++ IRQF_TIMER | IRQF_IRQPOLL, ++ clkevt->name, &clkevt->evt); ++ if (ret) ++ return ret; ++ ++ ret = jh7110_clocksource_init(clkevt); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct of_device_id jh7110_timer_match[] = { ++ { .compatible = "starfive,jh7110-timer", }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, jh7110_timer_match); ++ ++static struct platform_driver jh7110_timer_driver = { ++ .probe = jh7110_timer_probe, ++ .driver = { ++ .name = "jh7110-timer", ++ .of_match_table = jh7110_timer_match, ++ }, ++}; ++module_platform_driver(jh7110_timer_driver); ++ ++MODULE_AUTHOR("Xingyu Wu <xingyu.wu@starfivetech.com>"); ++MODULE_DESCRIPTION("StarFive JH7110 timer driver"); ++MODULE_LICENSE("GPL"); diff --git a/target/linux/starfive/patches-6.6/0004-dt-bindings-mmc-starfive-Remove-properties-from-requ.patch b/target/linux/starfive/patches-6.6/0004-dt-bindings-mmc-starfive-Remove-properties-from-requ.patch new file mode 100644 index 0000000000..9c1e78b29b --- /dev/null +++ b/target/linux/starfive/patches-6.6/0004-dt-bindings-mmc-starfive-Remove-properties-from-requ.patch @@ -0,0 +1,34 @@ +From b513eb2cabee212ba1a23839f18c0026a21e653e Mon Sep 17 00:00:00 2001 +From: William Qiu <william.qiu@starfivetech.com> +Date: Fri, 22 Sep 2023 14:28:32 +0800 +Subject: [PATCH 004/116] dt-bindings: mmc: starfive: Remove properties from + required + +Due to the change of tuning implementation, it's no longer necessary to +use the "starfive,sysreg" property in dts, so remove it from required. + +Signed-off-by: William Qiu <william.qiu@starfivetech.com> +Acked-by: Conor Dooley <conor.dooley@microchip.com> +Reviewed-by: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Link: https://lore.kernel.org/r/20230922062834.39212-2-william.qiu@starfivetech.com +Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> +--- + Documentation/devicetree/bindings/mmc/starfive,jh7110-mmc.yaml | 2 -- + 1 file changed, 2 deletions(-) + +--- a/Documentation/devicetree/bindings/mmc/starfive,jh7110-mmc.yaml ++++ b/Documentation/devicetree/bindings/mmc/starfive,jh7110-mmc.yaml +@@ -55,7 +55,6 @@ required: + - clocks + - clock-names + - interrupts +- - starfive,sysreg + + unevaluatedProperties: false + +@@ -73,5 +72,4 @@ examples: + fifo-depth = <32>; + fifo-watermark-aligned; + data-addr = <0>; +- starfive,sysreg = <&sys_syscon 0x14 0x1a 0x7c000000>; + }; diff --git a/target/linux/starfive/patches-6.6/0005-mmc-starfive-Change-tuning-implementation.patch b/target/linux/starfive/patches-6.6/0005-mmc-starfive-Change-tuning-implementation.patch new file mode 100644 index 0000000000..b8ea96f3c8 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0005-mmc-starfive-Change-tuning-implementation.patch @@ -0,0 +1,199 @@ +From 3ae8cec8fd28e18847edb67dfea04718c2f3369f Mon Sep 17 00:00:00 2001 +From: William Qiu <william.qiu@starfivetech.com> +Date: Fri, 22 Sep 2023 14:28:33 +0800 +Subject: [PATCH 005/116] mmc: starfive: Change tuning implementation + +Before, we used syscon to achieve tuning, but the actual measurement +showed little effect, so the tuning implementation was modified here, +and it was realized by reading and writing the UHS_REG_EXT register. + +Signed-off-by: William Qiu <william.qiu@starfivetech.com> +Reviewed-by: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Link: https://lore.kernel.org/r/20230922062834.39212-3-william.qiu@starfivetech.com +Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> +--- + drivers/mmc/host/dw_mmc-starfive.c | 137 +++++++++-------------------- + 1 file changed, 40 insertions(+), 97 deletions(-) + +--- a/drivers/mmc/host/dw_mmc-starfive.c ++++ b/drivers/mmc/host/dw_mmc-starfive.c +@@ -5,6 +5,7 @@ + * Copyright (c) 2022 StarFive Technology Co., Ltd. + */ + ++#include <linux/bitfield.h> + #include <linux/clk.h> + #include <linux/delay.h> + #include <linux/mfd/syscon.h> +@@ -20,13 +21,7 @@ + #define ALL_INT_CLR 0x1ffff + #define MAX_DELAY_CHAIN 32 + +-struct starfive_priv { +- struct device *dev; +- struct regmap *reg_syscon; +- u32 syscon_offset; +- u32 syscon_shift; +- u32 syscon_mask; +-}; ++#define STARFIVE_SMPL_PHASE GENMASK(20, 16) + + static void dw_mci_starfive_set_ios(struct dw_mci *host, struct mmc_ios *ios) + { +@@ -44,117 +39,65 @@ static void dw_mci_starfive_set_ios(stru + } + } + ++static void dw_mci_starfive_set_sample_phase(struct dw_mci *host, u32 smpl_phase) ++{ ++ /* change driver phase and sample phase */ ++ u32 reg_value = mci_readl(host, UHS_REG_EXT); ++ ++ /* In UHS_REG_EXT, only 5 bits valid in DRV_PHASE and SMPL_PHASE */ ++ reg_value &= ~STARFIVE_SMPL_PHASE; ++ reg_value |= FIELD_PREP(STARFIVE_SMPL_PHASE, smpl_phase); ++ mci_writel(host, UHS_REG_EXT, reg_value); ++ ++ /* We should delay 1ms wait for timing setting finished. */ ++ mdelay(1); ++} ++ + static int dw_mci_starfive_execute_tuning(struct dw_mci_slot *slot, + u32 opcode) + { + static const int grade = MAX_DELAY_CHAIN; + struct dw_mci *host = slot->host; +- struct starfive_priv *priv = host->priv; +- int rise_point = -1, fall_point = -1; +- int err, prev_err = 0; +- int i; +- bool found = 0; +- u32 regval; +- +- /* +- * Use grade as the max delay chain, and use the rise_point and +- * fall_point to ensure the best sampling point of a data input +- * signals. +- */ +- for (i = 0; i < grade; i++) { +- regval = i << priv->syscon_shift; +- err = regmap_update_bits(priv->reg_syscon, priv->syscon_offset, +- priv->syscon_mask, regval); +- if (err) +- return err; +- mci_writel(host, RINTSTS, ALL_INT_CLR); +- +- err = mmc_send_tuning(slot->mmc, opcode, NULL); +- if (!err) +- found = 1; +- +- if (i > 0) { +- if (err && !prev_err) +- fall_point = i - 1; +- if (!err && prev_err) +- rise_point = i; +- } ++ int smpl_phase, smpl_raise = -1, smpl_fall = -1; ++ int ret; + +- if (rise_point != -1 && fall_point != -1) +- goto tuning_out; ++ for (smpl_phase = 0; smpl_phase < grade; smpl_phase++) { ++ dw_mci_starfive_set_sample_phase(host, smpl_phase); ++ mci_writel(host, RINTSTS, ALL_INT_CLR); + +- prev_err = err; +- err = 0; +- } ++ ret = mmc_send_tuning(slot->mmc, opcode, NULL); + +-tuning_out: +- if (found) { +- if (rise_point == -1) +- rise_point = 0; +- if (fall_point == -1) +- fall_point = grade - 1; +- if (fall_point < rise_point) { +- if ((rise_point + fall_point) > +- (grade - 1)) +- i = fall_point / 2; +- else +- i = (rise_point + grade - 1) / 2; +- } else { +- i = (rise_point + fall_point) / 2; ++ if (!ret && smpl_raise < 0) { ++ smpl_raise = smpl_phase; ++ } else if (ret && smpl_raise >= 0) { ++ smpl_fall = smpl_phase - 1; ++ break; + } +- +- regval = i << priv->syscon_shift; +- err = regmap_update_bits(priv->reg_syscon, priv->syscon_offset, +- priv->syscon_mask, regval); +- if (err) +- return err; +- mci_writel(host, RINTSTS, ALL_INT_CLR); +- +- dev_info(host->dev, "Found valid delay chain! use it [delay=%d]\n", i); +- } else { +- dev_err(host->dev, "No valid delay chain! use default\n"); +- err = -EINVAL; + } + +- mci_writel(host, RINTSTS, ALL_INT_CLR); +- return err; +-} +- +-static int dw_mci_starfive_parse_dt(struct dw_mci *host) +-{ +- struct of_phandle_args args; +- struct starfive_priv *priv; +- int ret; ++ if (smpl_phase >= grade) ++ smpl_fall = grade - 1; + +- priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); +- if (!priv) +- return -ENOMEM; +- +- ret = of_parse_phandle_with_fixed_args(host->dev->of_node, +- "starfive,sysreg", 3, 0, &args); +- if (ret) { +- dev_err(host->dev, "Failed to parse starfive,sysreg\n"); +- return -EINVAL; ++ if (smpl_raise < 0) { ++ smpl_phase = 0; ++ dev_err(host->dev, "No valid delay chain! use default\n"); ++ ret = -EINVAL; ++ goto out; + } + +- priv->reg_syscon = syscon_node_to_regmap(args.np); +- of_node_put(args.np); +- if (IS_ERR(priv->reg_syscon)) +- return PTR_ERR(priv->reg_syscon); +- +- priv->syscon_offset = args.args[0]; +- priv->syscon_shift = args.args[1]; +- priv->syscon_mask = args.args[2]; +- +- host->priv = priv; ++ smpl_phase = (smpl_raise + smpl_fall) / 2; ++ dev_dbg(host->dev, "Found valid delay chain! use it [delay=%d]\n", smpl_phase); ++ ret = 0; + +- return 0; ++out: ++ dw_mci_starfive_set_sample_phase(host, smpl_phase); ++ mci_writel(host, RINTSTS, ALL_INT_CLR); ++ return ret; + } + + static const struct dw_mci_drv_data starfive_data = { + .common_caps = MMC_CAP_CMD23, + .set_ios = dw_mci_starfive_set_ios, +- .parse_dt = dw_mci_starfive_parse_dt, + .execute_tuning = dw_mci_starfive_execute_tuning, + }; + diff --git a/target/linux/starfive/patches-6.6/0006-dt-bindings-pwm-Add-bindings-for-OpenCores-PWM-Contr.patch b/target/linux/starfive/patches-6.6/0006-dt-bindings-pwm-Add-bindings-for-OpenCores-PWM-Contr.patch new file mode 100644 index 0000000000..aafbee5d42 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0006-dt-bindings-pwm-Add-bindings-for-OpenCores-PWM-Contr.patch @@ -0,0 +1,79 @@ +From e366df2ff64e9f93a5b35eea6a198b005d5a0911 Mon Sep 17 00:00:00 2001 +From: William Qiu <william.qiu@starfivetech.com> +Date: Fri, 22 Dec 2023 17:45:45 +0800 +Subject: [PATCH 006/116] dt-bindings: pwm: Add bindings for OpenCores PWM + Controller +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add bindings for OpenCores PWM Controller. + +Signed-off-by: William Qiu <william.qiu@starfivetech.com> +Reviewed-by: Hal Feng <hal.feng@starfivetech.com> +Reviewed-by: Conor Dooley <conor.dooley@microchip.com> +Reviewed-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> +Acked-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> +--- + .../bindings/pwm/opencores,pwm.yaml | 55 +++++++++++++++++++ + 1 file changed, 55 insertions(+) + create mode 100644 Documentation/devicetree/bindings/pwm/opencores,pwm.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/pwm/opencores,pwm.yaml +@@ -0,0 +1,55 @@ ++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/pwm/opencores,pwm.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: OpenCores PWM controller ++ ++maintainers: ++ - William Qiu <william.qiu@starfivetech.com> ++ ++description: ++ The OpenCores PTC ip core contains a PWM controller. When operating in PWM ++ mode, the PTC core generates binary signal with user-programmable low and ++ high periods. All PTC counters and registers are 32-bit. ++ ++allOf: ++ - $ref: pwm.yaml# ++ ++properties: ++ compatible: ++ items: ++ - enum: ++ - starfive,jh7100-pwm ++ - starfive,jh7110-pwm ++ - const: opencores,pwm-v1 ++ ++ reg: ++ maxItems: 1 ++ ++ clocks: ++ maxItems: 1 ++ ++ resets: ++ maxItems: 1 ++ ++ "#pwm-cells": ++ const: 3 ++ ++required: ++ - compatible ++ - reg ++ - clocks ++ ++additionalProperties: false ++ ++examples: ++ - | ++ pwm@12490000 { ++ compatible = "starfive,jh7110-pwm", "opencores,pwm-v1"; ++ reg = <0x12490000 0x10000>; ++ clocks = <&clkgen 181>; ++ resets = <&rstgen 109>; ++ #pwm-cells = <3>; ++ }; diff --git a/target/linux/starfive/patches-6.6/0007-pwm-opencores-Add-PWM-driver-support.patch b/target/linux/starfive/patches-6.6/0007-pwm-opencores-Add-PWM-driver-support.patch new file mode 100644 index 0000000000..27e510d86a --- /dev/null +++ b/target/linux/starfive/patches-6.6/0007-pwm-opencores-Add-PWM-driver-support.patch @@ -0,0 +1,285 @@ +From 644bfe581dde9b762460a4916da4d71c148be06e Mon Sep 17 00:00:00 2001 +From: William Qiu <william.qiu@starfivetech.com> +Date: Fri, 22 Dec 2023 17:45:46 +0800 +Subject: [PATCH 007/116] pwm: opencores: Add PWM driver support + +Add driver for OpenCores PWM Controller. And add compatibility code +which based on StarFive SoC. + +Co-developed-by: Hal Feng <hal.feng@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +Signed-off-by: William Qiu <william.qiu@starfivetech.com> +--- + drivers/pwm/Kconfig | 12 ++ + drivers/pwm/Makefile | 1 + + drivers/pwm/pwm-ocores.c | 233 +++++++++++++++++++++++++++++++++++++++ + 3 files changed, 246 insertions(+) + create mode 100644 drivers/pwm/pwm-ocores.c + +--- a/drivers/pwm/Kconfig ++++ b/drivers/pwm/Kconfig +@@ -434,6 +434,18 @@ config PWM_NTXEC + controller found in certain e-book readers designed by the original + design manufacturer Netronix. + ++config PWM_OCORES ++ tristate "OpenCores PWM support" ++ depends on HAS_IOMEM && OF ++ depends on COMMON_CLK ++ depends on ARCH_STARFIVE || COMPILE_TEST ++ help ++ If you say yes to this option, support will be included for the ++ OpenCores PWM. For details see https://opencores.org/projects/ptc. ++ ++ To compile this driver as a module, choose M here: the module ++ will be called pwm-ocores. ++ + config PWM_OMAP_DMTIMER + tristate "OMAP Dual-Mode Timer PWM support" + depends on OF +--- a/drivers/pwm/Makefile ++++ b/drivers/pwm/Makefile +@@ -39,6 +39,7 @@ obj-$(CONFIG_PWM_MICROCHIP_CORE) += pwm- + obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o + obj-$(CONFIG_PWM_MXS) += pwm-mxs.o + obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o ++obj-$(CONFIG_PWM_OCORES) += pwm-ocores.o + obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o + obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o + obj-$(CONFIG_PWM_PXA) += pwm-pxa.o +--- /dev/null ++++ b/drivers/pwm/pwm-ocores.c +@@ -0,0 +1,233 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * OpenCores PWM Driver ++ * ++ * https://opencores.org/projects/ptc ++ * ++ * Copyright (C) 2018-2023 StarFive Technology Co., Ltd. ++ * ++ * Limitations: ++ * - The hardware only do inverted polarity. ++ * - The hardware minimum period / duty_cycle is (1 / pwm_apb clock frequency) ns. ++ * - The hardware maximum period / duty_cycle is (U32_MAX / pwm_apb clock frequency) ns. ++ */ ++ ++#include <linux/clk.h> ++#include <linux/io.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/of_device.h> ++#include <linux/platform_device.h> ++#include <linux/pwm.h> ++#include <linux/reset.h> ++#include <linux/slab.h> ++ ++/* OCPWM_CTRL register bits*/ ++#define REG_OCPWM_EN BIT(0) ++#define REG_OCPWM_ECLK BIT(1) ++#define REG_OCPWM_NEC BIT(2) ++#define REG_OCPWM_OE BIT(3) ++#define REG_OCPWM_SIGNLE BIT(4) ++#define REG_OCPWM_INTE BIT(5) ++#define REG_OCPWM_INT BIT(6) ++#define REG_OCPWM_CNTRRST BIT(7) ++#define REG_OCPWM_CAPTE BIT(8) ++ ++struct ocores_pwm_device { ++ struct pwm_chip chip; ++ struct clk *clk; ++ struct reset_control *rst; ++ const struct ocores_pwm_data *data; ++ void __iomem *regs; ++ u32 clk_rate; /* PWM APB clock frequency */ ++}; ++ ++struct ocores_pwm_data { ++ void __iomem *(*get_ch_base)(void __iomem *base, unsigned int channel); ++}; ++ ++static inline u32 ocores_readl(struct ocores_pwm_device *ddata, ++ unsigned int channel, ++ unsigned int offset) ++{ ++ void __iomem *base = ddata->data->get_ch_base ? ++ ddata->data->get_ch_base(ddata->regs, channel) : ddata->regs; ++ ++ return readl(base + offset); ++} ++ ++static inline void ocores_writel(struct ocores_pwm_device *ddata, ++ unsigned int channel, ++ unsigned int offset, u32 val) ++{ ++ void __iomem *base = ddata->data->get_ch_base ? ++ ddata->data->get_ch_base(ddata->regs, channel) : ddata->regs; ++ ++ writel(val, base + offset); ++} ++ ++static inline struct ocores_pwm_device *chip_to_ocores(struct pwm_chip *chip) ++{ ++ return container_of(chip, struct ocores_pwm_device, chip); ++} ++ ++static void __iomem *starfive_jh71x0_get_ch_base(void __iomem *base, ++ unsigned int channel) ++{ ++ unsigned int offset = (channel > 3 ? 1 << 15 : 0) + (channel & 3) * 0x10; ++ ++ return base + offset; ++} ++ ++static int ocores_pwm_get_state(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ struct pwm_state *state) ++{ ++ struct ocores_pwm_device *ddata = chip_to_ocores(chip); ++ u32 period_data, duty_data, ctrl_data; ++ ++ period_data = ocores_readl(ddata, pwm->hwpwm, 0x8); ++ duty_data = ocores_readl(ddata, pwm->hwpwm, 0x4); ++ ctrl_data = ocores_readl(ddata, pwm->hwpwm, 0xC); ++ ++ state->period = DIV_ROUND_UP_ULL((u64)period_data * NSEC_PER_SEC, ddata->clk_rate); ++ state->duty_cycle = DIV_ROUND_UP_ULL((u64)duty_data * NSEC_PER_SEC, ddata->clk_rate); ++ state->polarity = PWM_POLARITY_INVERSED; ++ state->enabled = (ctrl_data & REG_OCPWM_EN) ? true : false; ++ ++ return 0; ++} ++ ++static int ocores_pwm_apply(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ const struct pwm_state *state) ++{ ++ struct ocores_pwm_device *ddata = chip_to_ocores(chip); ++ u32 ctrl_data = 0; ++ u64 period_data, duty_data; ++ ++ if (state->polarity != PWM_POLARITY_INVERSED) ++ return -EINVAL; ++ ++ ctrl_data = ocores_readl(ddata, pwm->hwpwm, 0xC); ++ ocores_writel(ddata, pwm->hwpwm, 0xC, 0); ++ ++ period_data = DIV_ROUND_DOWN_ULL(state->period * ddata->clk_rate, NSEC_PER_SEC); ++ if (period_data <= U32_MAX) ++ ocores_writel(ddata, pwm->hwpwm, 0x8, (u32)period_data); ++ else ++ return -EINVAL; ++ ++ duty_data = DIV_ROUND_DOWN_ULL(state->duty_cycle * ddata->clk_rate, NSEC_PER_SEC); ++ if (duty_data <= U32_MAX) ++ ocores_writel(ddata, pwm->hwpwm, 0x4, (u32)duty_data); ++ else ++ return -EINVAL; ++ ++ ocores_writel(ddata, pwm->hwpwm, 0xC, 0); ++ ++ if (state->enabled) { ++ ctrl_data = ocores_readl(ddata, pwm->hwpwm, 0xC); ++ ocores_writel(ddata, pwm->hwpwm, 0xC, ctrl_data | REG_OCPWM_EN | REG_OCPWM_OE); ++ } ++ ++ return 0; ++} ++ ++static const struct pwm_ops ocores_pwm_ops = { ++ .get_state = ocores_pwm_get_state, ++ .apply = ocores_pwm_apply, ++}; ++ ++static const struct ocores_pwm_data jh7100_pwm_data = { ++ .get_ch_base = starfive_jh71x0_get_ch_base, ++}; ++ ++static const struct ocores_pwm_data jh7110_pwm_data = { ++ .get_ch_base = starfive_jh71x0_get_ch_base, ++}; ++ ++static const struct of_device_id ocores_pwm_of_match[] = { ++ { .compatible = "opencores,pwm-v1" }, ++ { .compatible = "starfive,jh7100-pwm", .data = &jh7100_pwm_data}, ++ { .compatible = "starfive,jh7110-pwm", .data = &jh7110_pwm_data}, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, ocores_pwm_of_match); ++ ++static void ocores_reset_control_assert(void *data) ++{ ++ reset_control_assert(data); ++} ++ ++static int ocores_pwm_probe(struct platform_device *pdev) ++{ ++ const struct of_device_id *id; ++ struct device *dev = &pdev->dev; ++ struct ocores_pwm_device *ddata; ++ struct pwm_chip *chip; ++ int ret; ++ ++ id = of_match_device(ocores_pwm_of_match, dev); ++ if (!id) ++ return -EINVAL; ++ ++ ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); ++ if (!ddata) ++ return -ENOMEM; ++ ++ ddata->data = id->data; ++ chip = &ddata->chip; ++ chip->dev = dev; ++ chip->ops = &ocores_pwm_ops; ++ chip->npwm = 8; ++ chip->of_pwm_n_cells = 3; ++ ++ ddata->regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(ddata->regs)) ++ return dev_err_probe(dev, PTR_ERR(ddata->regs), ++ "Unable to map IO resources\n"); ++ ++ ddata->clk = devm_clk_get_enabled(dev, NULL); ++ if (IS_ERR(ddata->clk)) ++ return dev_err_probe(dev, PTR_ERR(ddata->clk), ++ "Unable to get pwm's clock\n"); ++ ++ ddata->rst = devm_reset_control_get_optional_exclusive(dev, NULL); ++ if (IS_ERR(ddata->rst)) ++ return dev_err_probe(dev, PTR_ERR(ddata->rst), ++ "Unable to get pwm's reset\n"); ++ ++ reset_control_deassert(ddata->rst); ++ ++ ret = devm_add_action_or_reset(dev, ocores_reset_control_assert, ddata->rst); ++ if (ret) ++ return ret; ++ ++ ddata->clk_rate = clk_get_rate(ddata->clk); ++ if (ddata->clk_rate <= 0) ++ return dev_err_probe(dev, ddata->clk_rate, ++ "Unable to get clock's rate\n"); ++ ++ ret = devm_pwmchip_add(dev, chip); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "Could not register PWM chip\n"); ++ ++ platform_set_drvdata(pdev, ddata); ++ ++ return ret; ++} ++ ++static struct platform_driver ocores_pwm_driver = { ++ .probe = ocores_pwm_probe, ++ .driver = { ++ .name = "ocores-pwm", ++ .of_match_table = ocores_pwm_of_match, ++ }, ++}; ++module_platform_driver(ocores_pwm_driver); ++ ++MODULE_AUTHOR("Jieqin Chen"); ++MODULE_AUTHOR("Hal Feng <hal.feng@starfivetech.com>"); ++MODULE_DESCRIPTION("OpenCores PWM PTC driver"); ++MODULE_LICENSE("GPL"); diff --git a/target/linux/starfive/patches-6.6/0008-crypto-starfive-remove-unnecessary-alignmask-for-aha.patch b/target/linux/starfive/patches-6.6/0008-crypto-starfive-remove-unnecessary-alignmask-for-aha.patch new file mode 100644 index 0000000000..a6ede31342 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0008-crypto-starfive-remove-unnecessary-alignmask-for-aha.patch @@ -0,0 +1,117 @@ +From 7d9521cad6474d45e9056176982e6da54d40bc19 Mon Sep 17 00:00:00 2001 +From: Eric Biggers <ebiggers@google.com> +Date: Sun, 22 Oct 2023 01:10:42 -0700 +Subject: [PATCH 008/116] crypto: starfive - remove unnecessary alignmask for + ahashes + +The crypto API's support for alignmasks for ahash algorithms is nearly +useless, as its only effect is to cause the API to align the key and +result buffers. The drivers that happen to be specifying an alignmask +for ahash rarely actually need it. When they do, it's easily fixable, +especially considering that these buffers cannot be used for DMA. + +In preparation for removing alignmask support from ahash, this patch +makes the starfive driver no longer use it. This driver did actually +rely on it, but only for storing to the result buffer using int stores +in starfive_hash_copy_hash(). This patch makes +starfive_hash_copy_hash() use put_unaligned() instead. (It really +should use a specific endianness, but that's an existing bug.) + +Signed-off-by: Eric Biggers <ebiggers@google.com> +Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> +--- + drivers/crypto/starfive/jh7110-hash.c | 13 ++----------- + 1 file changed, 2 insertions(+), 11 deletions(-) + +--- a/drivers/crypto/starfive/jh7110-hash.c ++++ b/drivers/crypto/starfive/jh7110-hash.c +@@ -209,7 +209,8 @@ static int starfive_hash_copy_hash(struc + data = (u32 *)req->result; + + for (count = 0; count < mlen; count++) +- data[count] = readl(ctx->cryp->base + STARFIVE_HASH_SHARDR); ++ put_unaligned(readl(ctx->cryp->base + STARFIVE_HASH_SHARDR), ++ &data[count]); + + return 0; + } +@@ -628,7 +629,6 @@ static struct ahash_engine_alg algs_sha2 + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = SHA224_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct starfive_cryp_ctx), +- .cra_alignmask = 3, + .cra_module = THIS_MODULE, + } + }, +@@ -658,7 +658,6 @@ static struct ahash_engine_alg algs_sha2 + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = SHA224_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct starfive_cryp_ctx), +- .cra_alignmask = 3, + .cra_module = THIS_MODULE, + } + }, +@@ -687,7 +686,6 @@ static struct ahash_engine_alg algs_sha2 + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = SHA256_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct starfive_cryp_ctx), +- .cra_alignmask = 3, + .cra_module = THIS_MODULE, + } + }, +@@ -717,7 +715,6 @@ static struct ahash_engine_alg algs_sha2 + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = SHA256_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct starfive_cryp_ctx), +- .cra_alignmask = 3, + .cra_module = THIS_MODULE, + } + }, +@@ -746,7 +743,6 @@ static struct ahash_engine_alg algs_sha2 + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = SHA384_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct starfive_cryp_ctx), +- .cra_alignmask = 3, + .cra_module = THIS_MODULE, + } + }, +@@ -776,7 +772,6 @@ static struct ahash_engine_alg algs_sha2 + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = SHA384_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct starfive_cryp_ctx), +- .cra_alignmask = 3, + .cra_module = THIS_MODULE, + } + }, +@@ -805,7 +800,6 @@ static struct ahash_engine_alg algs_sha2 + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = SHA512_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct starfive_cryp_ctx), +- .cra_alignmask = 3, + .cra_module = THIS_MODULE, + } + }, +@@ -835,7 +829,6 @@ static struct ahash_engine_alg algs_sha2 + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = SHA512_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct starfive_cryp_ctx), +- .cra_alignmask = 3, + .cra_module = THIS_MODULE, + } + }, +@@ -864,7 +857,6 @@ static struct ahash_engine_alg algs_sha2 + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = SM3_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct starfive_cryp_ctx), +- .cra_alignmask = 3, + .cra_module = THIS_MODULE, + } + }, +@@ -894,7 +886,6 @@ static struct ahash_engine_alg algs_sha2 + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = SM3_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct starfive_cryp_ctx), +- .cra_alignmask = 3, + .cra_module = THIS_MODULE, + } + }, diff --git a/target/linux/starfive/patches-6.6/0009-crypto-starfive-Update-driver-dependencies.patch b/target/linux/starfive/patches-6.6/0009-crypto-starfive-Update-driver-dependencies.patch new file mode 100644 index 0000000000..145c01377d --- /dev/null +++ b/target/linux/starfive/patches-6.6/0009-crypto-starfive-Update-driver-dependencies.patch @@ -0,0 +1,25 @@ +From 52de0270ed6453727936b5a793dc367d75280e84 Mon Sep 17 00:00:00 2001 +From: Jia Jie Ho <jiajie.ho@starfivetech.com> +Date: Wed, 15 Nov 2023 01:12:13 +0800 +Subject: [PATCH 009/116] crypto: starfive - Update driver dependencies + +Change AMBA_PL08X to required dependency as the hash ops depends on it +for data transfer. + +Signed-off-by: Jia Jie Ho <jiajie.ho@starfivetech.com> +Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> +--- + drivers/crypto/starfive/Kconfig | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/crypto/starfive/Kconfig ++++ b/drivers/crypto/starfive/Kconfig +@@ -4,7 +4,7 @@ + + config CRYPTO_DEV_JH7110 + tristate "StarFive JH7110 cryptographic engine driver" +- depends on SOC_STARFIVE || AMBA_PL08X || COMPILE_TEST ++ depends on (SOC_STARFIVE && AMBA_PL08X) || COMPILE_TEST + depends on HAS_DMA + select CRYPTO_ENGINE + select CRYPTO_HMAC diff --git a/target/linux/starfive/patches-6.6/0010-crypto-starfive-RSA-poll-csr-for-done-status.patch b/target/linux/starfive/patches-6.6/0010-crypto-starfive-RSA-poll-csr-for-done-status.patch new file mode 100644 index 0000000000..5e353e35ad --- /dev/null +++ b/target/linux/starfive/patches-6.6/0010-crypto-starfive-RSA-poll-csr-for-done-status.patch @@ -0,0 +1,200 @@ +From 68a1bbb99455fd5ea80b7e21ec726f369abc9572 Mon Sep 17 00:00:00 2001 +From: Jia Jie Ho <jiajie.ho@starfivetech.com> +Date: Wed, 15 Nov 2023 01:12:14 +0800 +Subject: [PATCH 010/116] crypto: starfive - RSA poll csr for done status + +Hardware could not clear irq status without resetting the entire module. +Driver receives irq immediately when mask bit is cleared causing +intermittent errors in RSA calculations. Switch to use csr polling for +done status instead. + +Signed-off-by: Jia Jie Ho <jiajie.ho@starfivetech.com> +Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> +--- + drivers/crypto/starfive/jh7110-cryp.c | 8 ----- + drivers/crypto/starfive/jh7110-cryp.h | 10 +++++- + drivers/crypto/starfive/jh7110-rsa.c | 49 +++++++-------------------- + 3 files changed, 22 insertions(+), 45 deletions(-) + +--- a/drivers/crypto/starfive/jh7110-cryp.c ++++ b/drivers/crypto/starfive/jh7110-cryp.c +@@ -109,12 +109,6 @@ static irqreturn_t starfive_cryp_irq(int + tasklet_schedule(&cryp->hash_done); + } + +- if (status & STARFIVE_IE_FLAG_PKA_DONE) { +- mask |= STARFIVE_IE_MASK_PKA_DONE; +- writel(mask, cryp->base + STARFIVE_IE_MASK_OFFSET); +- complete(&cryp->pka_done); +- } +- + return IRQ_HANDLED; + } + +@@ -159,8 +153,6 @@ static int starfive_cryp_probe(struct pl + return dev_err_probe(&pdev->dev, PTR_ERR(cryp->rst), + "Error getting hardware reset line\n"); + +- init_completion(&cryp->pka_done); +- + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; +--- a/drivers/crypto/starfive/jh7110-cryp.h ++++ b/drivers/crypto/starfive/jh7110-cryp.h +@@ -126,6 +126,15 @@ union starfive_pka_cacr { + }; + }; + ++union starfive_pka_casr { ++ u32 v; ++ struct { ++#define STARFIVE_PKA_DONE BIT(0) ++ u32 done :1; ++ u32 rsvd_0 :31; ++ }; ++}; ++ + struct starfive_rsa_key { + u8 *n; + u8 *e; +@@ -184,7 +193,6 @@ struct starfive_cryp_dev { + struct crypto_engine *engine; + struct tasklet_struct aes_done; + struct tasklet_struct hash_done; +- struct completion pka_done; + size_t assoclen; + size_t total_in; + size_t total_out; +--- a/drivers/crypto/starfive/jh7110-rsa.c ++++ b/drivers/crypto/starfive/jh7110-rsa.c +@@ -6,13 +6,7 @@ + */ + + #include <linux/crypto.h> +-#include <linux/delay.h> +-#include <linux/device.h> +-#include <linux/dma-direct.h> +-#include <linux/interrupt.h> + #include <linux/iopoll.h> +-#include <linux/io.h> +-#include <linux/mod_devicetable.h> + #include <crypto/akcipher.h> + #include <crypto/algapi.h> + #include <crypto/internal/akcipher.h> +@@ -28,13 +22,13 @@ + #define STARFIVE_PKA_CAER_OFFSET (STARFIVE_PKA_REGS_OFFSET + 0x108) + #define STARFIVE_PKA_CANR_OFFSET (STARFIVE_PKA_REGS_OFFSET + 0x208) + +-// R^2 mod N and N0' ++/* R ^ 2 mod N and N0' */ + #define CRYPTO_CMD_PRE 0x0 +-// A * R mod N ==> A ++/* A * R mod N ==> A */ + #define CRYPTO_CMD_ARN 0x5 +-// A * E * R mod N ==> A ++/* A * E * R mod N ==> A */ + #define CRYPTO_CMD_AERN 0x6 +-// A * A * R mod N ==> A ++/* A * A * R mod N ==> A */ + #define CRYPTO_CMD_AARN 0x7 + + #define STARFIVE_RSA_RESET 0x2 +@@ -42,21 +36,10 @@ + static inline int starfive_pka_wait_done(struct starfive_cryp_ctx *ctx) + { + struct starfive_cryp_dev *cryp = ctx->cryp; ++ u32 status; + +- return wait_for_completion_timeout(&cryp->pka_done, +- usecs_to_jiffies(100000)); +-} +- +-static inline void starfive_pka_irq_mask_clear(struct starfive_cryp_ctx *ctx) +-{ +- struct starfive_cryp_dev *cryp = ctx->cryp; +- u32 stat; +- +- stat = readl(cryp->base + STARFIVE_IE_MASK_OFFSET); +- stat &= ~STARFIVE_IE_MASK_PKA_DONE; +- writel(stat, cryp->base + STARFIVE_IE_MASK_OFFSET); +- +- reinit_completion(&cryp->pka_done); ++ return readl_relaxed_poll_timeout(cryp->base + STARFIVE_PKA_CASR_OFFSET, status, ++ status & STARFIVE_PKA_DONE, 10, 100000); + } + + static void starfive_rsa_free_key(struct starfive_rsa_key *key) +@@ -113,10 +96,9 @@ static int starfive_rsa_montgomery_form( + rctx->csr.pka.not_r2 = 1; + rctx->csr.pka.ie = 1; + +- starfive_pka_irq_mask_clear(ctx); + writel(rctx->csr.pka.v, cryp->base + STARFIVE_PKA_CACR_OFFSET); + +- if (!starfive_pka_wait_done(ctx)) ++ if (starfive_pka_wait_done(ctx)) + return -ETIMEDOUT; + + for (loop = 0; loop <= opsize; loop++) +@@ -135,10 +117,9 @@ static int starfive_rsa_montgomery_form( + rctx->csr.pka.start = 1; + rctx->csr.pka.ie = 1; + +- starfive_pka_irq_mask_clear(ctx); + writel(rctx->csr.pka.v, cryp->base + STARFIVE_PKA_CACR_OFFSET); + +- if (!starfive_pka_wait_done(ctx)) ++ if (starfive_pka_wait_done(ctx)) + return -ETIMEDOUT; + } else { + rctx->csr.pka.v = 0; +@@ -150,10 +131,9 @@ static int starfive_rsa_montgomery_form( + rctx->csr.pka.pre_expf = 1; + rctx->csr.pka.ie = 1; + +- starfive_pka_irq_mask_clear(ctx); + writel(rctx->csr.pka.v, cryp->base + STARFIVE_PKA_CACR_OFFSET); + +- if (!starfive_pka_wait_done(ctx)) ++ if (starfive_pka_wait_done(ctx)) + return -ETIMEDOUT; + + for (loop = 0; loop <= count; loop++) +@@ -171,10 +151,9 @@ static int starfive_rsa_montgomery_form( + rctx->csr.pka.start = 1; + rctx->csr.pka.ie = 1; + +- starfive_pka_irq_mask_clear(ctx); + writel(rctx->csr.pka.v, cryp->base + STARFIVE_PKA_CACR_OFFSET); + +- if (!starfive_pka_wait_done(ctx)) ++ if (starfive_pka_wait_done(ctx)) + return -ETIMEDOUT; + } + +@@ -225,11 +204,10 @@ static int starfive_rsa_cpu_start(struct + rctx->csr.pka.start = 1; + rctx->csr.pka.ie = 1; + +- starfive_pka_irq_mask_clear(ctx); + writel(rctx->csr.pka.v, cryp->base + STARFIVE_PKA_CACR_OFFSET); + + ret = -ETIMEDOUT; +- if (!starfive_pka_wait_done(ctx)) ++ if (starfive_pka_wait_done(ctx)) + goto rsa_err; + + if (mlen) { +@@ -241,10 +219,9 @@ static int starfive_rsa_cpu_start(struct + rctx->csr.pka.start = 1; + rctx->csr.pka.ie = 1; + +- starfive_pka_irq_mask_clear(ctx); + writel(rctx->csr.pka.v, cryp->base + STARFIVE_PKA_CACR_OFFSET); + +- if (!starfive_pka_wait_done(ctx)) ++ if (starfive_pka_wait_done(ctx)) + goto rsa_err; + } + } diff --git a/target/linux/starfive/patches-6.6/0011-crypto-starfive-Pad-adata-with-zeroes.patch b/target/linux/starfive/patches-6.6/0011-crypto-starfive-Pad-adata-with-zeroes.patch new file mode 100644 index 0000000000..268e4055e0 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0011-crypto-starfive-Pad-adata-with-zeroes.patch @@ -0,0 +1,46 @@ +From eea9f2c55cf944bbd5cdd43eb655416a867846af Mon Sep 17 00:00:00 2001 +From: Jia Jie Ho <jiajie.ho@starfivetech.com> +Date: Mon, 20 Nov 2023 11:12:42 +0800 +Subject: [PATCH 011/116] crypto: starfive - Pad adata with zeroes + +Aad requires padding with zeroes up to 15 bytes in some cases. This +patch increases the allocated buffer size for aad and prevents the +driver accessing uninitialized memory region. + +v1->v2: Specify reason for alloc size change in descriptions. + +Signed-off-by: Jia Jie Ho <jiajie.ho@starfivetech.com> +Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> +--- + drivers/crypto/starfive/jh7110-aes.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +--- a/drivers/crypto/starfive/jh7110-aes.c ++++ b/drivers/crypto/starfive/jh7110-aes.c +@@ -500,7 +500,7 @@ static int starfive_aes_prepare_req(stru + scatterwalk_start(&cryp->out_walk, rctx->out_sg); + + if (cryp->assoclen) { +- rctx->adata = kzalloc(ALIGN(cryp->assoclen, AES_BLOCK_SIZE), GFP_KERNEL); ++ rctx->adata = kzalloc(cryp->assoclen + AES_BLOCK_SIZE, GFP_KERNEL); + if (!rctx->adata) + return dev_err_probe(cryp->dev, -ENOMEM, + "Failed to alloc memory for adata"); +@@ -569,7 +569,7 @@ static int starfive_aes_aead_do_one_req( + struct starfive_cryp_ctx *ctx = + crypto_aead_ctx(crypto_aead_reqtfm(req)); + struct starfive_cryp_dev *cryp = ctx->cryp; +- struct starfive_cryp_request_ctx *rctx = ctx->rctx; ++ struct starfive_cryp_request_ctx *rctx; + u32 block[AES_BLOCK_32]; + u32 stat; + int err; +@@ -579,6 +579,8 @@ static int starfive_aes_aead_do_one_req( + if (err) + return err; + ++ rctx = ctx->rctx; ++ + if (!cryp->assoclen) + goto write_text; + diff --git a/target/linux/starfive/patches-6.6/0012-crypto-starfive-Remove-cfb-and-ofb.patch b/target/linux/starfive/patches-6.6/0012-crypto-starfive-Remove-cfb-and-ofb.patch new file mode 100644 index 0000000000..102fbabf37 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0012-crypto-starfive-Remove-cfb-and-ofb.patch @@ -0,0 +1,125 @@ +From 922b213ad22f93fb2788ce119084622ab3d25bf8 Mon Sep 17 00:00:00 2001 +From: Herbert Xu <herbert@gondor.apana.org.au> +Date: Thu, 30 Nov 2023 18:12:55 +0800 +Subject: [PATCH 012/116] crypto: starfive - Remove cfb and ofb + +Remove the unused CFB/OFB implementation. + +Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> +--- + drivers/crypto/starfive/jh7110-aes.c | 71 +-------------------------- + drivers/crypto/starfive/jh7110-cryp.h | 2 - + 2 files changed, 1 insertion(+), 72 deletions(-) + +--- a/drivers/crypto/starfive/jh7110-aes.c ++++ b/drivers/crypto/starfive/jh7110-aes.c +@@ -262,12 +262,7 @@ static int starfive_aes_hw_init(struct s + rctx->csr.aes.mode = hw_mode; + rctx->csr.aes.cmode = !is_encrypt(cryp); + rctx->csr.aes.ie = 1; +- +- if (hw_mode == STARFIVE_AES_MODE_CFB || +- hw_mode == STARFIVE_AES_MODE_OFB) +- rctx->csr.aes.stmode = STARFIVE_AES_MODE_XFB_128; +- else +- rctx->csr.aes.stmode = STARFIVE_AES_MODE_XFB_1; ++ rctx->csr.aes.stmode = STARFIVE_AES_MODE_XFB_1; + + if (cryp->side_chan) { + rctx->csr.aes.delay_aes = 1; +@@ -294,8 +289,6 @@ static int starfive_aes_hw_init(struct s + starfive_aes_ccm_init(ctx); + starfive_aes_aead_hw_start(ctx, hw_mode); + break; +- case STARFIVE_AES_MODE_OFB: +- case STARFIVE_AES_MODE_CFB: + case STARFIVE_AES_MODE_CBC: + case STARFIVE_AES_MODE_CTR: + starfive_aes_write_iv(ctx, (void *)cryp->req.sreq->iv); +@@ -785,26 +778,6 @@ static int starfive_aes_cbc_decrypt(stru + return starfive_aes_crypt(req, STARFIVE_AES_MODE_CBC); + } + +-static int starfive_aes_cfb_encrypt(struct skcipher_request *req) +-{ +- return starfive_aes_crypt(req, STARFIVE_AES_MODE_CFB | FLG_ENCRYPT); +-} +- +-static int starfive_aes_cfb_decrypt(struct skcipher_request *req) +-{ +- return starfive_aes_crypt(req, STARFIVE_AES_MODE_CFB); +-} +- +-static int starfive_aes_ofb_encrypt(struct skcipher_request *req) +-{ +- return starfive_aes_crypt(req, STARFIVE_AES_MODE_OFB | FLG_ENCRYPT); +-} +- +-static int starfive_aes_ofb_decrypt(struct skcipher_request *req) +-{ +- return starfive_aes_crypt(req, STARFIVE_AES_MODE_OFB); +-} +- + static int starfive_aes_ctr_encrypt(struct skcipher_request *req) + { + return starfive_aes_crypt(req, STARFIVE_AES_MODE_CTR | FLG_ENCRYPT); +@@ -903,48 +876,6 @@ static struct skcipher_engine_alg skciph + .cra_priority = 200, + .cra_flags = CRYPTO_ALG_ASYNC, + .cra_blocksize = 1, +- .cra_ctxsize = sizeof(struct starfive_cryp_ctx), +- .cra_alignmask = 0xf, +- .cra_module = THIS_MODULE, +- }, +- .op = { +- .do_one_request = starfive_aes_do_one_req, +- }, +-}, { +- .base.init = starfive_aes_init_tfm, +- .base.setkey = starfive_aes_setkey, +- .base.encrypt = starfive_aes_cfb_encrypt, +- .base.decrypt = starfive_aes_cfb_decrypt, +- .base.min_keysize = AES_MIN_KEY_SIZE, +- .base.max_keysize = AES_MAX_KEY_SIZE, +- .base.ivsize = AES_BLOCK_SIZE, +- .base.base = { +- .cra_name = "cfb(aes)", +- .cra_driver_name = "starfive-cfb-aes", +- .cra_priority = 200, +- .cra_flags = CRYPTO_ALG_ASYNC, +- .cra_blocksize = 1, +- .cra_ctxsize = sizeof(struct starfive_cryp_ctx), +- .cra_alignmask = 0xf, +- .cra_module = THIS_MODULE, +- }, +- .op = { +- .do_one_request = starfive_aes_do_one_req, +- }, +-}, { +- .base.init = starfive_aes_init_tfm, +- .base.setkey = starfive_aes_setkey, +- .base.encrypt = starfive_aes_ofb_encrypt, +- .base.decrypt = starfive_aes_ofb_decrypt, +- .base.min_keysize = AES_MIN_KEY_SIZE, +- .base.max_keysize = AES_MAX_KEY_SIZE, +- .base.ivsize = AES_BLOCK_SIZE, +- .base.base = { +- .cra_name = "ofb(aes)", +- .cra_driver_name = "starfive-ofb-aes", +- .cra_priority = 200, +- .cra_flags = CRYPTO_ALG_ASYNC, +- .cra_blocksize = 1, + .cra_ctxsize = sizeof(struct starfive_cryp_ctx), + .cra_alignmask = 0xf, + .cra_module = THIS_MODULE, +--- a/drivers/crypto/starfive/jh7110-cryp.h ++++ b/drivers/crypto/starfive/jh7110-cryp.h +@@ -51,8 +51,6 @@ union starfive_aes_csr { + u32 ccm_start :1; + #define STARFIVE_AES_MODE_ECB 0x0 + #define STARFIVE_AES_MODE_CBC 0x1 +-#define STARFIVE_AES_MODE_CFB 0x2 +-#define STARFIVE_AES_MODE_OFB 0x3 + #define STARFIVE_AES_MODE_CTR 0x4 + #define STARFIVE_AES_MODE_CCM 0x5 + #define STARFIVE_AES_MODE_GCM 0x6 diff --git a/target/linux/starfive/patches-6.6/0013-crypto-starfive-Remove-unneeded-NULL-checks.patch b/target/linux/starfive/patches-6.6/0013-crypto-starfive-Remove-unneeded-NULL-checks.patch new file mode 100644 index 0000000000..2e12cfa09e --- /dev/null +++ b/target/linux/starfive/patches-6.6/0013-crypto-starfive-Remove-unneeded-NULL-checks.patch @@ -0,0 +1,33 @@ +From 0dbdc763f10c5cfa968dffc290a7c060ee740172 Mon Sep 17 00:00:00 2001 +From: Jia Jie Ho <jiajie.ho@starfivetech.com> +Date: Mon, 4 Dec 2023 11:02:39 +0800 +Subject: [PATCH 013/116] crypto: starfive - Remove unneeded NULL checks + +NULL check before kfree_sensitive function is not needed. + +Signed-off-by: Jia Jie Ho <jiajie.ho@starfivetech.com> +Reported-by: kernel test robot <lkp@intel.com> +Closes: https://lore.kernel.org/oe-kbuild-all/202311301702.LxswfETY-lkp@intel.com/ +Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> +--- + drivers/crypto/starfive/jh7110-rsa.c | 9 +++------ + 1 file changed, 3 insertions(+), 6 deletions(-) + +--- a/drivers/crypto/starfive/jh7110-rsa.c ++++ b/drivers/crypto/starfive/jh7110-rsa.c +@@ -44,12 +44,9 @@ static inline int starfive_pka_wait_done + + static void starfive_rsa_free_key(struct starfive_rsa_key *key) + { +- if (key->d) +- kfree_sensitive(key->d); +- if (key->e) +- kfree_sensitive(key->e); +- if (key->n) +- kfree_sensitive(key->n); ++ kfree_sensitive(key->d); ++ kfree_sensitive(key->e); ++ kfree_sensitive(key->n); + memset(key, 0, sizeof(*key)); + } + diff --git a/target/linux/starfive/patches-6.6/0014-dt-bindings-PCI-Add-PLDA-XpressRICH-PCIe-host-common.patch b/target/linux/starfive/patches-6.6/0014-dt-bindings-PCI-Add-PLDA-XpressRICH-PCIe-host-common.patch new file mode 100644 index 0000000000..430571565a --- /dev/null +++ b/target/linux/starfive/patches-6.6/0014-dt-bindings-PCI-Add-PLDA-XpressRICH-PCIe-host-common.patch @@ -0,0 +1,183 @@ +From 708695ebf1a779de9a1fd2f72f7938afa6c14ada Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:05:51 +0800 +Subject: [PATCH 014/116] dt-bindings: PCI: Add PLDA XpressRICH PCIe host + common properties + +Add PLDA XpressRICH PCIe host common properties dt-binding doc. +PolarFire PCIe host using PLDA IP. Move common properties from Microchip +PolarFire PCIe host to PLDA files. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Reviewed-by: Hal Feng <hal.feng@starfivetech.com> +Reviewed-by: Conor Dooley <conor.dooley@microchip.com> +Reviewed-by: Rob Herring <robh@kernel.org> +Tested-by: John Clark <inindev@gmail.com> +--- + .../bindings/pci/microchip,pcie-host.yaml | 55 +------------- + .../pci/plda,xpressrich3-axi-common.yaml | 75 +++++++++++++++++++ + 2 files changed, 76 insertions(+), 54 deletions(-) + create mode 100644 Documentation/devicetree/bindings/pci/plda,xpressrich3-axi-common.yaml + +--- a/Documentation/devicetree/bindings/pci/microchip,pcie-host.yaml ++++ b/Documentation/devicetree/bindings/pci/microchip,pcie-host.yaml +@@ -10,21 +10,13 @@ maintainers: + - Daire McNamara <daire.mcnamara@microchip.com> + + allOf: +- - $ref: /schemas/pci/pci-bus.yaml# ++ - $ref: plda,xpressrich3-axi-common.yaml# + - $ref: /schemas/interrupt-controller/msi-controller.yaml# + + properties: + compatible: + const: microchip,pcie-host-1.0 # PolarFire + +- reg: +- maxItems: 2 +- +- reg-names: +- items: +- - const: cfg +- - const: apb +- + clocks: + description: + Fabric Interface Controllers, FICs, are the interface between the FPGA +@@ -52,18 +44,6 @@ properties: + items: + pattern: '^fic[0-3]$' + +- interrupts: +- minItems: 1 +- items: +- - description: PCIe host controller +- - description: builtin MSI controller +- +- interrupt-names: +- minItems: 1 +- items: +- - const: pcie +- - const: msi +- + ranges: + maxItems: 1 + +@@ -71,39 +51,6 @@ properties: + minItems: 1 + maxItems: 6 + +- msi-controller: +- description: Identifies the node as an MSI controller. +- +- msi-parent: +- description: MSI controller the device is capable of using. +- +- interrupt-controller: +- type: object +- properties: +- '#address-cells': +- const: 0 +- +- '#interrupt-cells': +- const: 1 +- +- interrupt-controller: true +- +- required: +- - '#address-cells' +- - '#interrupt-cells' +- - interrupt-controller +- +- additionalProperties: false +- +-required: +- - reg +- - reg-names +- - "#interrupt-cells" +- - interrupts +- - interrupt-map-mask +- - interrupt-map +- - msi-controller +- + unevaluatedProperties: false + + examples: +--- /dev/null ++++ b/Documentation/devicetree/bindings/pci/plda,xpressrich3-axi-common.yaml +@@ -0,0 +1,75 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/pci/plda,xpressrich3-axi-common.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: PLDA XpressRICH PCIe host common properties ++ ++maintainers: ++ - Daire McNamara <daire.mcnamara@microchip.com> ++ - Kevin Xie <kevin.xie@starfivetech.com> ++ ++description: ++ Generic PLDA XpressRICH PCIe host common properties. ++ ++allOf: ++ - $ref: /schemas/pci/pci-bus.yaml# ++ ++properties: ++ reg: ++ maxItems: 2 ++ ++ reg-names: ++ items: ++ - const: cfg ++ - const: apb ++ ++ interrupts: ++ minItems: 1 ++ items: ++ - description: PCIe host controller ++ - description: builtin MSI controller ++ ++ interrupt-names: ++ minItems: 1 ++ items: ++ - const: pcie ++ - const: msi ++ ++ msi-controller: ++ description: Identifies the node as an MSI controller. ++ ++ msi-parent: ++ description: MSI controller the device is capable of using. ++ ++ interrupt-controller: ++ type: object ++ properties: ++ '#address-cells': ++ const: 0 ++ ++ '#interrupt-cells': ++ const: 1 ++ ++ interrupt-controller: true ++ ++ required: ++ - '#address-cells' ++ - '#interrupt-cells' ++ - interrupt-controller ++ ++ additionalProperties: false ++ ++required: ++ - reg ++ - reg-names ++ - interrupts ++ - msi-controller ++ - "#interrupt-cells" ++ - interrupt-map-mask ++ - interrupt-map ++ ++additionalProperties: true ++ ++... diff --git a/target/linux/starfive/patches-6.6/0015-PCI-microchip-Move-pcie-microchip-host.c-to-plda-dir.patch b/target/linux/starfive/patches-6.6/0015-PCI-microchip-Move-pcie-microchip-host.c-to-plda-dir.patch new file mode 100644 index 0000000000..9e7717fa5d --- /dev/null +++ b/target/linux/starfive/patches-6.6/0015-PCI-microchip-Move-pcie-microchip-host.c-to-plda-dir.patch @@ -0,0 +1,2523 @@ +From df67154aa92efdc774a8536ece6f431e7850aca2 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:05:52 +0800 +Subject: [PATCH 015/116] PCI: microchip: Move pcie-microchip-host.c to plda + directory + +For Microchip Polarfire PCIe host is PLDA XpressRich IP, move to plda +directory. Prepare for refactoring the codes. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Reviewed-by: Conor Dooley <conor.dooley@microchip.com> +--- + drivers/pci/controller/Kconfig | 9 +-------- + drivers/pci/controller/Makefile | 2 +- + drivers/pci/controller/plda/Kconfig | 14 ++++++++++++++ + drivers/pci/controller/plda/Makefile | 2 ++ + .../controller/{ => plda}/pcie-microchip-host.c | 2 +- + 5 files changed, 19 insertions(+), 10 deletions(-) + create mode 100644 drivers/pci/controller/plda/Kconfig + create mode 100644 drivers/pci/controller/plda/Makefile + rename drivers/pci/controller/{ => plda}/pcie-microchip-host.c (99%) + +--- a/drivers/pci/controller/Kconfig ++++ b/drivers/pci/controller/Kconfig +@@ -215,14 +215,6 @@ config PCIE_MT7621 + help + This selects a driver for the MediaTek MT7621 PCIe Controller. + +-config PCIE_MICROCHIP_HOST +- tristate "Microchip AXI PCIe controller" +- depends on PCI_MSI && OF +- select PCI_HOST_COMMON +- help +- Say Y here if you want kernel to support the Microchip AXI PCIe +- Host Bridge driver. +- + config PCI_HYPERV_INTERFACE + tristate "Microsoft Hyper-V PCI Interface" + depends on ((X86 && X86_64) || ARM64) && HYPERV && PCI_MSI +@@ -345,4 +337,5 @@ config PCIE_XILINX_CPM + source "drivers/pci/controller/cadence/Kconfig" + source "drivers/pci/controller/dwc/Kconfig" + source "drivers/pci/controller/mobiveil/Kconfig" ++source "drivers/pci/controller/plda/Kconfig" + endmenu +--- a/drivers/pci/controller/Makefile ++++ b/drivers/pci/controller/Makefile +@@ -32,7 +32,6 @@ obj-$(CONFIG_PCIE_ROCKCHIP_EP) += pcie-r + obj-$(CONFIG_PCIE_ROCKCHIP_HOST) += pcie-rockchip-host.o + obj-$(CONFIG_PCIE_MEDIATEK) += pcie-mediatek.o + obj-$(CONFIG_PCIE_MEDIATEK_GEN3) += pcie-mediatek-gen3.o +-obj-$(CONFIG_PCIE_MICROCHIP_HOST) += pcie-microchip-host.o + obj-$(CONFIG_VMD) += vmd.o + obj-$(CONFIG_PCIE_BRCMSTB) += pcie-brcmstb.o + obj-$(CONFIG_PCI_LOONGSON) += pci-loongson.o +@@ -43,6 +42,7 @@ obj-$(CONFIG_PCIE_MT7621) += pcie-mt7621 + # pcie-hisi.o quirks are needed even without CONFIG_PCIE_DW + obj-y += dwc/ + obj-y += mobiveil/ ++obj-y += plda/ + + + # The following drivers are for devices that use the generic ACPI +--- /dev/null ++++ b/drivers/pci/controller/plda/Kconfig +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0 ++ ++menu "PLDA-based PCIe controllers" ++ depends on PCI ++ ++config PCIE_MICROCHIP_HOST ++ tristate "Microchip AXI PCIe controller" ++ depends on PCI_MSI && OF ++ select PCI_HOST_COMMON ++ help ++ Say Y here if you want kernel to support the Microchip AXI PCIe ++ Host Bridge driver. ++ ++endmenu +--- /dev/null ++++ b/drivers/pci/controller/plda/Makefile +@@ -0,0 +1,2 @@ ++# SPDX-License-Identifier: GPL-2.0 ++obj-$(CONFIG_PCIE_MICROCHIP_HOST) += pcie-microchip-host.o +--- a/drivers/pci/controller/pcie-microchip-host.c ++++ /dev/null +@@ -1,1216 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0 +-/* +- * Microchip AXI PCIe Bridge host controller driver +- * +- * Copyright (c) 2018 - 2020 Microchip Corporation. All rights reserved. +- * +- * Author: Daire McNamara <daire.mcnamara@microchip.com> +- */ +- +-#include <linux/bitfield.h> +-#include <linux/clk.h> +-#include <linux/irqchip/chained_irq.h> +-#include <linux/irqdomain.h> +-#include <linux/module.h> +-#include <linux/msi.h> +-#include <linux/of_address.h> +-#include <linux/of_pci.h> +-#include <linux/pci-ecam.h> +-#include <linux/platform_device.h> +- +-#include "../pci.h" +- +-/* Number of MSI IRQs */ +-#define MC_MAX_NUM_MSI_IRQS 32 +- +-/* PCIe Bridge Phy and Controller Phy offsets */ +-#define MC_PCIE1_BRIDGE_ADDR 0x00008000u +-#define MC_PCIE1_CTRL_ADDR 0x0000a000u +- +-#define MC_PCIE_BRIDGE_ADDR (MC_PCIE1_BRIDGE_ADDR) +-#define MC_PCIE_CTRL_ADDR (MC_PCIE1_CTRL_ADDR) +- +-/* PCIe Bridge Phy Regs */ +-#define PCIE_PCI_IRQ_DW0 0xa8 +-#define MSIX_CAP_MASK BIT(31) +-#define NUM_MSI_MSGS_MASK GENMASK(6, 4) +-#define NUM_MSI_MSGS_SHIFT 4 +- +-#define IMASK_LOCAL 0x180 +-#define DMA_END_ENGINE_0_MASK 0x00000000u +-#define DMA_END_ENGINE_0_SHIFT 0 +-#define DMA_END_ENGINE_1_MASK 0x00000000u +-#define DMA_END_ENGINE_1_SHIFT 1 +-#define DMA_ERROR_ENGINE_0_MASK 0x00000100u +-#define DMA_ERROR_ENGINE_0_SHIFT 8 +-#define DMA_ERROR_ENGINE_1_MASK 0x00000200u +-#define DMA_ERROR_ENGINE_1_SHIFT 9 +-#define A_ATR_EVT_POST_ERR_MASK 0x00010000u +-#define A_ATR_EVT_POST_ERR_SHIFT 16 +-#define A_ATR_EVT_FETCH_ERR_MASK 0x00020000u +-#define A_ATR_EVT_FETCH_ERR_SHIFT 17 +-#define A_ATR_EVT_DISCARD_ERR_MASK 0x00040000u +-#define A_ATR_EVT_DISCARD_ERR_SHIFT 18 +-#define A_ATR_EVT_DOORBELL_MASK 0x00000000u +-#define A_ATR_EVT_DOORBELL_SHIFT 19 +-#define P_ATR_EVT_POST_ERR_MASK 0x00100000u +-#define P_ATR_EVT_POST_ERR_SHIFT 20 +-#define P_ATR_EVT_FETCH_ERR_MASK 0x00200000u +-#define P_ATR_EVT_FETCH_ERR_SHIFT 21 +-#define P_ATR_EVT_DISCARD_ERR_MASK 0x00400000u +-#define P_ATR_EVT_DISCARD_ERR_SHIFT 22 +-#define P_ATR_EVT_DOORBELL_MASK 0x00000000u +-#define P_ATR_EVT_DOORBELL_SHIFT 23 +-#define PM_MSI_INT_INTA_MASK 0x01000000u +-#define PM_MSI_INT_INTA_SHIFT 24 +-#define PM_MSI_INT_INTB_MASK 0x02000000u +-#define PM_MSI_INT_INTB_SHIFT 25 +-#define PM_MSI_INT_INTC_MASK 0x04000000u +-#define PM_MSI_INT_INTC_SHIFT 26 +-#define PM_MSI_INT_INTD_MASK 0x08000000u +-#define PM_MSI_INT_INTD_SHIFT 27 +-#define PM_MSI_INT_INTX_MASK 0x0f000000u +-#define PM_MSI_INT_INTX_SHIFT 24 +-#define PM_MSI_INT_MSI_MASK 0x10000000u +-#define PM_MSI_INT_MSI_SHIFT 28 +-#define PM_MSI_INT_AER_EVT_MASK 0x20000000u +-#define PM_MSI_INT_AER_EVT_SHIFT 29 +-#define PM_MSI_INT_EVENTS_MASK 0x40000000u +-#define PM_MSI_INT_EVENTS_SHIFT 30 +-#define PM_MSI_INT_SYS_ERR_MASK 0x80000000u +-#define PM_MSI_INT_SYS_ERR_SHIFT 31 +-#define NUM_LOCAL_EVENTS 15 +-#define ISTATUS_LOCAL 0x184 +-#define IMASK_HOST 0x188 +-#define ISTATUS_HOST 0x18c +-#define IMSI_ADDR 0x190 +-#define ISTATUS_MSI 0x194 +- +-/* PCIe Master table init defines */ +-#define ATR0_PCIE_WIN0_SRCADDR_PARAM 0x600u +-#define ATR0_PCIE_ATR_SIZE 0x25 +-#define ATR0_PCIE_ATR_SIZE_SHIFT 1 +-#define ATR0_PCIE_WIN0_SRC_ADDR 0x604u +-#define ATR0_PCIE_WIN0_TRSL_ADDR_LSB 0x608u +-#define ATR0_PCIE_WIN0_TRSL_ADDR_UDW 0x60cu +-#define ATR0_PCIE_WIN0_TRSL_PARAM 0x610u +- +-/* PCIe AXI slave table init defines */ +-#define ATR0_AXI4_SLV0_SRCADDR_PARAM 0x800u +-#define ATR_SIZE_SHIFT 1 +-#define ATR_IMPL_ENABLE 1 +-#define ATR0_AXI4_SLV0_SRC_ADDR 0x804u +-#define ATR0_AXI4_SLV0_TRSL_ADDR_LSB 0x808u +-#define ATR0_AXI4_SLV0_TRSL_ADDR_UDW 0x80cu +-#define ATR0_AXI4_SLV0_TRSL_PARAM 0x810u +-#define PCIE_TX_RX_INTERFACE 0x00000000u +-#define PCIE_CONFIG_INTERFACE 0x00000001u +- +-#define ATR_ENTRY_SIZE 32 +- +-/* PCIe Controller Phy Regs */ +-#define SEC_ERROR_EVENT_CNT 0x20 +-#define DED_ERROR_EVENT_CNT 0x24 +-#define SEC_ERROR_INT 0x28 +-#define SEC_ERROR_INT_TX_RAM_SEC_ERR_INT GENMASK(3, 0) +-#define SEC_ERROR_INT_RX_RAM_SEC_ERR_INT GENMASK(7, 4) +-#define SEC_ERROR_INT_PCIE2AXI_RAM_SEC_ERR_INT GENMASK(11, 8) +-#define SEC_ERROR_INT_AXI2PCIE_RAM_SEC_ERR_INT GENMASK(15, 12) +-#define SEC_ERROR_INT_ALL_RAM_SEC_ERR_INT GENMASK(15, 0) +-#define NUM_SEC_ERROR_INTS (4) +-#define SEC_ERROR_INT_MASK 0x2c +-#define DED_ERROR_INT 0x30 +-#define DED_ERROR_INT_TX_RAM_DED_ERR_INT GENMASK(3, 0) +-#define DED_ERROR_INT_RX_RAM_DED_ERR_INT GENMASK(7, 4) +-#define DED_ERROR_INT_PCIE2AXI_RAM_DED_ERR_INT GENMASK(11, 8) +-#define DED_ERROR_INT_AXI2PCIE_RAM_DED_ERR_INT GENMASK(15, 12) +-#define DED_ERROR_INT_ALL_RAM_DED_ERR_INT GENMASK(15, 0) +-#define NUM_DED_ERROR_INTS (4) +-#define DED_ERROR_INT_MASK 0x34 +-#define ECC_CONTROL 0x38 +-#define ECC_CONTROL_TX_RAM_INJ_ERROR_0 BIT(0) +-#define ECC_CONTROL_TX_RAM_INJ_ERROR_1 BIT(1) +-#define ECC_CONTROL_TX_RAM_INJ_ERROR_2 BIT(2) +-#define ECC_CONTROL_TX_RAM_INJ_ERROR_3 BIT(3) +-#define ECC_CONTROL_RX_RAM_INJ_ERROR_0 BIT(4) +-#define ECC_CONTROL_RX_RAM_INJ_ERROR_1 BIT(5) +-#define ECC_CONTROL_RX_RAM_INJ_ERROR_2 BIT(6) +-#define ECC_CONTROL_RX_RAM_INJ_ERROR_3 BIT(7) +-#define ECC_CONTROL_PCIE2AXI_RAM_INJ_ERROR_0 BIT(8) +-#define ECC_CONTROL_PCIE2AXI_RAM_INJ_ERROR_1 BIT(9) +-#define ECC_CONTROL_PCIE2AXI_RAM_INJ_ERROR_2 BIT(10) +-#define ECC_CONTROL_PCIE2AXI_RAM_INJ_ERROR_3 BIT(11) +-#define ECC_CONTROL_AXI2PCIE_RAM_INJ_ERROR_0 BIT(12) +-#define ECC_CONTROL_AXI2PCIE_RAM_INJ_ERROR_1 BIT(13) +-#define ECC_CONTROL_AXI2PCIE_RAM_INJ_ERROR_2 BIT(14) +-#define ECC_CONTROL_AXI2PCIE_RAM_INJ_ERROR_3 BIT(15) +-#define ECC_CONTROL_TX_RAM_ECC_BYPASS BIT(24) +-#define ECC_CONTROL_RX_RAM_ECC_BYPASS BIT(25) +-#define ECC_CONTROL_PCIE2AXI_RAM_ECC_BYPASS BIT(26) +-#define ECC_CONTROL_AXI2PCIE_RAM_ECC_BYPASS BIT(27) +-#define PCIE_EVENT_INT 0x14c +-#define PCIE_EVENT_INT_L2_EXIT_INT BIT(0) +-#define PCIE_EVENT_INT_HOTRST_EXIT_INT BIT(1) +-#define PCIE_EVENT_INT_DLUP_EXIT_INT BIT(2) +-#define PCIE_EVENT_INT_MASK GENMASK(2, 0) +-#define PCIE_EVENT_INT_L2_EXIT_INT_MASK BIT(16) +-#define PCIE_EVENT_INT_HOTRST_EXIT_INT_MASK BIT(17) +-#define PCIE_EVENT_INT_DLUP_EXIT_INT_MASK BIT(18) +-#define PCIE_EVENT_INT_ENB_MASK GENMASK(18, 16) +-#define PCIE_EVENT_INT_ENB_SHIFT 16 +-#define NUM_PCIE_EVENTS (3) +- +-/* PCIe Config space MSI capability structure */ +-#define MC_MSI_CAP_CTRL_OFFSET 0xe0u +- +-/* Events */ +-#define EVENT_PCIE_L2_EXIT 0 +-#define EVENT_PCIE_HOTRST_EXIT 1 +-#define EVENT_PCIE_DLUP_EXIT 2 +-#define EVENT_SEC_TX_RAM_SEC_ERR 3 +-#define EVENT_SEC_RX_RAM_SEC_ERR 4 +-#define EVENT_SEC_PCIE2AXI_RAM_SEC_ERR 5 +-#define EVENT_SEC_AXI2PCIE_RAM_SEC_ERR 6 +-#define EVENT_DED_TX_RAM_DED_ERR 7 +-#define EVENT_DED_RX_RAM_DED_ERR 8 +-#define EVENT_DED_PCIE2AXI_RAM_DED_ERR 9 +-#define EVENT_DED_AXI2PCIE_RAM_DED_ERR 10 +-#define EVENT_LOCAL_DMA_END_ENGINE_0 11 +-#define EVENT_LOCAL_DMA_END_ENGINE_1 12 +-#define EVENT_LOCAL_DMA_ERROR_ENGINE_0 13 +-#define EVENT_LOCAL_DMA_ERROR_ENGINE_1 14 +-#define EVENT_LOCAL_A_ATR_EVT_POST_ERR 15 +-#define EVENT_LOCAL_A_ATR_EVT_FETCH_ERR 16 +-#define EVENT_LOCAL_A_ATR_EVT_DISCARD_ERR 17 +-#define EVENT_LOCAL_A_ATR_EVT_DOORBELL 18 +-#define EVENT_LOCAL_P_ATR_EVT_POST_ERR 19 +-#define EVENT_LOCAL_P_ATR_EVT_FETCH_ERR 20 +-#define EVENT_LOCAL_P_ATR_EVT_DISCARD_ERR 21 +-#define EVENT_LOCAL_P_ATR_EVT_DOORBELL 22 +-#define EVENT_LOCAL_PM_MSI_INT_INTX 23 +-#define EVENT_LOCAL_PM_MSI_INT_MSI 24 +-#define EVENT_LOCAL_PM_MSI_INT_AER_EVT 25 +-#define EVENT_LOCAL_PM_MSI_INT_EVENTS 26 +-#define EVENT_LOCAL_PM_MSI_INT_SYS_ERR 27 +-#define NUM_EVENTS 28 +- +-#define PCIE_EVENT_CAUSE(x, s) \ +- [EVENT_PCIE_ ## x] = { __stringify(x), s } +- +-#define SEC_ERROR_CAUSE(x, s) \ +- [EVENT_SEC_ ## x] = { __stringify(x), s } +- +-#define DED_ERROR_CAUSE(x, s) \ +- [EVENT_DED_ ## x] = { __stringify(x), s } +- +-#define LOCAL_EVENT_CAUSE(x, s) \ +- [EVENT_LOCAL_ ## x] = { __stringify(x), s } +- +-#define PCIE_EVENT(x) \ +- .base = MC_PCIE_CTRL_ADDR, \ +- .offset = PCIE_EVENT_INT, \ +- .mask_offset = PCIE_EVENT_INT, \ +- .mask_high = 1, \ +- .mask = PCIE_EVENT_INT_ ## x ## _INT, \ +- .enb_mask = PCIE_EVENT_INT_ENB_MASK +- +-#define SEC_EVENT(x) \ +- .base = MC_PCIE_CTRL_ADDR, \ +- .offset = SEC_ERROR_INT, \ +- .mask_offset = SEC_ERROR_INT_MASK, \ +- .mask = SEC_ERROR_INT_ ## x ## _INT, \ +- .mask_high = 1, \ +- .enb_mask = 0 +- +-#define DED_EVENT(x) \ +- .base = MC_PCIE_CTRL_ADDR, \ +- .offset = DED_ERROR_INT, \ +- .mask_offset = DED_ERROR_INT_MASK, \ +- .mask_high = 1, \ +- .mask = DED_ERROR_INT_ ## x ## _INT, \ +- .enb_mask = 0 +- +-#define LOCAL_EVENT(x) \ +- .base = MC_PCIE_BRIDGE_ADDR, \ +- .offset = ISTATUS_LOCAL, \ +- .mask_offset = IMASK_LOCAL, \ +- .mask_high = 0, \ +- .mask = x ## _MASK, \ +- .enb_mask = 0 +- +-#define PCIE_EVENT_TO_EVENT_MAP(x) \ +- { PCIE_EVENT_INT_ ## x ## _INT, EVENT_PCIE_ ## x } +- +-#define SEC_ERROR_TO_EVENT_MAP(x) \ +- { SEC_ERROR_INT_ ## x ## _INT, EVENT_SEC_ ## x } +- +-#define DED_ERROR_TO_EVENT_MAP(x) \ +- { DED_ERROR_INT_ ## x ## _INT, EVENT_DED_ ## x } +- +-#define LOCAL_STATUS_TO_EVENT_MAP(x) \ +- { x ## _MASK, EVENT_LOCAL_ ## x } +- +-struct event_map { +- u32 reg_mask; +- u32 event_bit; +-}; +- +-struct mc_msi { +- struct mutex lock; /* Protect used bitmap */ +- struct irq_domain *msi_domain; +- struct irq_domain *dev_domain; +- u32 num_vectors; +- u64 vector_phy; +- DECLARE_BITMAP(used, MC_MAX_NUM_MSI_IRQS); +-}; +- +-struct mc_pcie { +- void __iomem *axi_base_addr; +- struct device *dev; +- struct irq_domain *intx_domain; +- struct irq_domain *event_domain; +- raw_spinlock_t lock; +- struct mc_msi msi; +-}; +- +-struct cause { +- const char *sym; +- const char *str; +-}; +- +-static const struct cause event_cause[NUM_EVENTS] = { +- PCIE_EVENT_CAUSE(L2_EXIT, "L2 exit event"), +- PCIE_EVENT_CAUSE(HOTRST_EXIT, "Hot reset exit event"), +- PCIE_EVENT_CAUSE(DLUP_EXIT, "DLUP exit event"), +- SEC_ERROR_CAUSE(TX_RAM_SEC_ERR, "sec error in tx buffer"), +- SEC_ERROR_CAUSE(RX_RAM_SEC_ERR, "sec error in rx buffer"), +- SEC_ERROR_CAUSE(PCIE2AXI_RAM_SEC_ERR, "sec error in pcie2axi buffer"), +- SEC_ERROR_CAUSE(AXI2PCIE_RAM_SEC_ERR, "sec error in axi2pcie buffer"), +- DED_ERROR_CAUSE(TX_RAM_DED_ERR, "ded error in tx buffer"), +- DED_ERROR_CAUSE(RX_RAM_DED_ERR, "ded error in rx buffer"), +- DED_ERROR_CAUSE(PCIE2AXI_RAM_DED_ERR, "ded error in pcie2axi buffer"), +- DED_ERROR_CAUSE(AXI2PCIE_RAM_DED_ERR, "ded error in axi2pcie buffer"), +- LOCAL_EVENT_CAUSE(DMA_ERROR_ENGINE_0, "dma engine 0 error"), +- LOCAL_EVENT_CAUSE(DMA_ERROR_ENGINE_1, "dma engine 1 error"), +- LOCAL_EVENT_CAUSE(A_ATR_EVT_POST_ERR, "axi write request error"), +- LOCAL_EVENT_CAUSE(A_ATR_EVT_FETCH_ERR, "axi read request error"), +- LOCAL_EVENT_CAUSE(A_ATR_EVT_DISCARD_ERR, "axi read timeout"), +- LOCAL_EVENT_CAUSE(P_ATR_EVT_POST_ERR, "pcie write request error"), +- LOCAL_EVENT_CAUSE(P_ATR_EVT_FETCH_ERR, "pcie read request error"), +- LOCAL_EVENT_CAUSE(P_ATR_EVT_DISCARD_ERR, "pcie read timeout"), +- LOCAL_EVENT_CAUSE(PM_MSI_INT_AER_EVT, "aer event"), +- LOCAL_EVENT_CAUSE(PM_MSI_INT_EVENTS, "pm/ltr/hotplug event"), +- LOCAL_EVENT_CAUSE(PM_MSI_INT_SYS_ERR, "system error"), +-}; +- +-static struct event_map pcie_event_to_event[] = { +- PCIE_EVENT_TO_EVENT_MAP(L2_EXIT), +- PCIE_EVENT_TO_EVENT_MAP(HOTRST_EXIT), +- PCIE_EVENT_TO_EVENT_MAP(DLUP_EXIT), +-}; +- +-static struct event_map sec_error_to_event[] = { +- SEC_ERROR_TO_EVENT_MAP(TX_RAM_SEC_ERR), +- SEC_ERROR_TO_EVENT_MAP(RX_RAM_SEC_ERR), +- SEC_ERROR_TO_EVENT_MAP(PCIE2AXI_RAM_SEC_ERR), +- SEC_ERROR_TO_EVENT_MAP(AXI2PCIE_RAM_SEC_ERR), +-}; +- +-static struct event_map ded_error_to_event[] = { +- DED_ERROR_TO_EVENT_MAP(TX_RAM_DED_ERR), +- DED_ERROR_TO_EVENT_MAP(RX_RAM_DED_ERR), +- DED_ERROR_TO_EVENT_MAP(PCIE2AXI_RAM_DED_ERR), +- DED_ERROR_TO_EVENT_MAP(AXI2PCIE_RAM_DED_ERR), +-}; +- +-static struct event_map local_status_to_event[] = { +- LOCAL_STATUS_TO_EVENT_MAP(DMA_END_ENGINE_0), +- LOCAL_STATUS_TO_EVENT_MAP(DMA_END_ENGINE_1), +- LOCAL_STATUS_TO_EVENT_MAP(DMA_ERROR_ENGINE_0), +- LOCAL_STATUS_TO_EVENT_MAP(DMA_ERROR_ENGINE_1), +- LOCAL_STATUS_TO_EVENT_MAP(A_ATR_EVT_POST_ERR), +- LOCAL_STATUS_TO_EVENT_MAP(A_ATR_EVT_FETCH_ERR), +- LOCAL_STATUS_TO_EVENT_MAP(A_ATR_EVT_DISCARD_ERR), +- LOCAL_STATUS_TO_EVENT_MAP(A_ATR_EVT_DOORBELL), +- LOCAL_STATUS_TO_EVENT_MAP(P_ATR_EVT_POST_ERR), +- LOCAL_STATUS_TO_EVENT_MAP(P_ATR_EVT_FETCH_ERR), +- LOCAL_STATUS_TO_EVENT_MAP(P_ATR_EVT_DISCARD_ERR), +- LOCAL_STATUS_TO_EVENT_MAP(P_ATR_EVT_DOORBELL), +- LOCAL_STATUS_TO_EVENT_MAP(PM_MSI_INT_INTX), +- LOCAL_STATUS_TO_EVENT_MAP(PM_MSI_INT_MSI), +- LOCAL_STATUS_TO_EVENT_MAP(PM_MSI_INT_AER_EVT), +- LOCAL_STATUS_TO_EVENT_MAP(PM_MSI_INT_EVENTS), +- LOCAL_STATUS_TO_EVENT_MAP(PM_MSI_INT_SYS_ERR), +-}; +- +-static struct { +- u32 base; +- u32 offset; +- u32 mask; +- u32 shift; +- u32 enb_mask; +- u32 mask_high; +- u32 mask_offset; +-} event_descs[] = { +- { PCIE_EVENT(L2_EXIT) }, +- { PCIE_EVENT(HOTRST_EXIT) }, +- { PCIE_EVENT(DLUP_EXIT) }, +- { SEC_EVENT(TX_RAM_SEC_ERR) }, +- { SEC_EVENT(RX_RAM_SEC_ERR) }, +- { SEC_EVENT(PCIE2AXI_RAM_SEC_ERR) }, +- { SEC_EVENT(AXI2PCIE_RAM_SEC_ERR) }, +- { DED_EVENT(TX_RAM_DED_ERR) }, +- { DED_EVENT(RX_RAM_DED_ERR) }, +- { DED_EVENT(PCIE2AXI_RAM_DED_ERR) }, +- { DED_EVENT(AXI2PCIE_RAM_DED_ERR) }, +- { LOCAL_EVENT(DMA_END_ENGINE_0) }, +- { LOCAL_EVENT(DMA_END_ENGINE_1) }, +- { LOCAL_EVENT(DMA_ERROR_ENGINE_0) }, +- { LOCAL_EVENT(DMA_ERROR_ENGINE_1) }, +- { LOCAL_EVENT(A_ATR_EVT_POST_ERR) }, +- { LOCAL_EVENT(A_ATR_EVT_FETCH_ERR) }, +- { LOCAL_EVENT(A_ATR_EVT_DISCARD_ERR) }, +- { LOCAL_EVENT(A_ATR_EVT_DOORBELL) }, +- { LOCAL_EVENT(P_ATR_EVT_POST_ERR) }, +- { LOCAL_EVENT(P_ATR_EVT_FETCH_ERR) }, +- { LOCAL_EVENT(P_ATR_EVT_DISCARD_ERR) }, +- { LOCAL_EVENT(P_ATR_EVT_DOORBELL) }, +- { LOCAL_EVENT(PM_MSI_INT_INTX) }, +- { LOCAL_EVENT(PM_MSI_INT_MSI) }, +- { LOCAL_EVENT(PM_MSI_INT_AER_EVT) }, +- { LOCAL_EVENT(PM_MSI_INT_EVENTS) }, +- { LOCAL_EVENT(PM_MSI_INT_SYS_ERR) }, +-}; +- +-static char poss_clks[][5] = { "fic0", "fic1", "fic2", "fic3" }; +- +-static struct mc_pcie *port; +- +-static void mc_pcie_enable_msi(struct mc_pcie *port, void __iomem *ecam) +-{ +- struct mc_msi *msi = &port->msi; +- u16 reg; +- u8 queue_size; +- +- /* Fixup MSI enable flag */ +- reg = readw_relaxed(ecam + MC_MSI_CAP_CTRL_OFFSET + PCI_MSI_FLAGS); +- reg |= PCI_MSI_FLAGS_ENABLE; +- writew_relaxed(reg, ecam + MC_MSI_CAP_CTRL_OFFSET + PCI_MSI_FLAGS); +- +- /* Fixup PCI MSI queue flags */ +- queue_size = FIELD_GET(PCI_MSI_FLAGS_QMASK, reg); +- reg |= FIELD_PREP(PCI_MSI_FLAGS_QSIZE, queue_size); +- writew_relaxed(reg, ecam + MC_MSI_CAP_CTRL_OFFSET + PCI_MSI_FLAGS); +- +- /* Fixup MSI addr fields */ +- writel_relaxed(lower_32_bits(msi->vector_phy), +- ecam + MC_MSI_CAP_CTRL_OFFSET + PCI_MSI_ADDRESS_LO); +- writel_relaxed(upper_32_bits(msi->vector_phy), +- ecam + MC_MSI_CAP_CTRL_OFFSET + PCI_MSI_ADDRESS_HI); +-} +- +-static void mc_handle_msi(struct irq_desc *desc) +-{ +- struct mc_pcie *port = irq_desc_get_handler_data(desc); +- struct irq_chip *chip = irq_desc_get_chip(desc); +- struct device *dev = port->dev; +- struct mc_msi *msi = &port->msi; +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; +- unsigned long status; +- u32 bit; +- int ret; +- +- chained_irq_enter(chip, desc); +- +- status = readl_relaxed(bridge_base_addr + ISTATUS_LOCAL); +- if (status & PM_MSI_INT_MSI_MASK) { +- writel_relaxed(status & PM_MSI_INT_MSI_MASK, bridge_base_addr + ISTATUS_LOCAL); +- status = readl_relaxed(bridge_base_addr + ISTATUS_MSI); +- for_each_set_bit(bit, &status, msi->num_vectors) { +- ret = generic_handle_domain_irq(msi->dev_domain, bit); +- if (ret) +- dev_err_ratelimited(dev, "bad MSI IRQ %d\n", +- bit); +- } +- } +- +- chained_irq_exit(chip, desc); +-} +- +-static void mc_msi_bottom_irq_ack(struct irq_data *data) +-{ +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; +- u32 bitpos = data->hwirq; +- +- writel_relaxed(BIT(bitpos), bridge_base_addr + ISTATUS_MSI); +-} +- +-static void mc_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) +-{ +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); +- phys_addr_t addr = port->msi.vector_phy; +- +- msg->address_lo = lower_32_bits(addr); +- msg->address_hi = upper_32_bits(addr); +- msg->data = data->hwirq; +- +- dev_dbg(port->dev, "msi#%x address_hi %#x address_lo %#x\n", +- (int)data->hwirq, msg->address_hi, msg->address_lo); +-} +- +-static int mc_msi_set_affinity(struct irq_data *irq_data, +- const struct cpumask *mask, bool force) +-{ +- return -EINVAL; +-} +- +-static struct irq_chip mc_msi_bottom_irq_chip = { +- .name = "Microchip MSI", +- .irq_ack = mc_msi_bottom_irq_ack, +- .irq_compose_msi_msg = mc_compose_msi_msg, +- .irq_set_affinity = mc_msi_set_affinity, +-}; +- +-static int mc_irq_msi_domain_alloc(struct irq_domain *domain, unsigned int virq, +- unsigned int nr_irqs, void *args) +-{ +- struct mc_pcie *port = domain->host_data; +- struct mc_msi *msi = &port->msi; +- unsigned long bit; +- +- mutex_lock(&msi->lock); +- bit = find_first_zero_bit(msi->used, msi->num_vectors); +- if (bit >= msi->num_vectors) { +- mutex_unlock(&msi->lock); +- return -ENOSPC; +- } +- +- set_bit(bit, msi->used); +- +- irq_domain_set_info(domain, virq, bit, &mc_msi_bottom_irq_chip, +- domain->host_data, handle_edge_irq, NULL, NULL); +- +- mutex_unlock(&msi->lock); +- +- return 0; +-} +- +-static void mc_irq_msi_domain_free(struct irq_domain *domain, unsigned int virq, +- unsigned int nr_irqs) +-{ +- struct irq_data *d = irq_domain_get_irq_data(domain, virq); +- struct mc_pcie *port = irq_data_get_irq_chip_data(d); +- struct mc_msi *msi = &port->msi; +- +- mutex_lock(&msi->lock); +- +- if (test_bit(d->hwirq, msi->used)) +- __clear_bit(d->hwirq, msi->used); +- else +- dev_err(port->dev, "trying to free unused MSI%lu\n", d->hwirq); +- +- mutex_unlock(&msi->lock); +-} +- +-static const struct irq_domain_ops msi_domain_ops = { +- .alloc = mc_irq_msi_domain_alloc, +- .free = mc_irq_msi_domain_free, +-}; +- +-static struct irq_chip mc_msi_irq_chip = { +- .name = "Microchip PCIe MSI", +- .irq_ack = irq_chip_ack_parent, +- .irq_mask = pci_msi_mask_irq, +- .irq_unmask = pci_msi_unmask_irq, +-}; +- +-static struct msi_domain_info mc_msi_domain_info = { +- .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | +- MSI_FLAG_PCI_MSIX), +- .chip = &mc_msi_irq_chip, +-}; +- +-static int mc_allocate_msi_domains(struct mc_pcie *port) +-{ +- struct device *dev = port->dev; +- struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node); +- struct mc_msi *msi = &port->msi; +- +- mutex_init(&port->msi.lock); +- +- msi->dev_domain = irq_domain_add_linear(NULL, msi->num_vectors, +- &msi_domain_ops, port); +- if (!msi->dev_domain) { +- dev_err(dev, "failed to create IRQ domain\n"); +- return -ENOMEM; +- } +- +- msi->msi_domain = pci_msi_create_irq_domain(fwnode, &mc_msi_domain_info, +- msi->dev_domain); +- if (!msi->msi_domain) { +- dev_err(dev, "failed to create MSI domain\n"); +- irq_domain_remove(msi->dev_domain); +- return -ENOMEM; +- } +- +- return 0; +-} +- +-static void mc_handle_intx(struct irq_desc *desc) +-{ +- struct mc_pcie *port = irq_desc_get_handler_data(desc); +- struct irq_chip *chip = irq_desc_get_chip(desc); +- struct device *dev = port->dev; +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; +- unsigned long status; +- u32 bit; +- int ret; +- +- chained_irq_enter(chip, desc); +- +- status = readl_relaxed(bridge_base_addr + ISTATUS_LOCAL); +- if (status & PM_MSI_INT_INTX_MASK) { +- status &= PM_MSI_INT_INTX_MASK; +- status >>= PM_MSI_INT_INTX_SHIFT; +- for_each_set_bit(bit, &status, PCI_NUM_INTX) { +- ret = generic_handle_domain_irq(port->intx_domain, bit); +- if (ret) +- dev_err_ratelimited(dev, "bad INTx IRQ %d\n", +- bit); +- } +- } +- +- chained_irq_exit(chip, desc); +-} +- +-static void mc_ack_intx_irq(struct irq_data *data) +-{ +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; +- u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); +- +- writel_relaxed(mask, bridge_base_addr + ISTATUS_LOCAL); +-} +- +-static void mc_mask_intx_irq(struct irq_data *data) +-{ +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; +- unsigned long flags; +- u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); +- u32 val; +- +- raw_spin_lock_irqsave(&port->lock, flags); +- val = readl_relaxed(bridge_base_addr + IMASK_LOCAL); +- val &= ~mask; +- writel_relaxed(val, bridge_base_addr + IMASK_LOCAL); +- raw_spin_unlock_irqrestore(&port->lock, flags); +-} +- +-static void mc_unmask_intx_irq(struct irq_data *data) +-{ +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; +- unsigned long flags; +- u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); +- u32 val; +- +- raw_spin_lock_irqsave(&port->lock, flags); +- val = readl_relaxed(bridge_base_addr + IMASK_LOCAL); +- val |= mask; +- writel_relaxed(val, bridge_base_addr + IMASK_LOCAL); +- raw_spin_unlock_irqrestore(&port->lock, flags); +-} +- +-static struct irq_chip mc_intx_irq_chip = { +- .name = "Microchip PCIe INTx", +- .irq_ack = mc_ack_intx_irq, +- .irq_mask = mc_mask_intx_irq, +- .irq_unmask = mc_unmask_intx_irq, +-}; +- +-static int mc_pcie_intx_map(struct irq_domain *domain, unsigned int irq, +- irq_hw_number_t hwirq) +-{ +- irq_set_chip_and_handler(irq, &mc_intx_irq_chip, handle_level_irq); +- irq_set_chip_data(irq, domain->host_data); +- +- return 0; +-} +- +-static const struct irq_domain_ops intx_domain_ops = { +- .map = mc_pcie_intx_map, +-}; +- +-static inline u32 reg_to_event(u32 reg, struct event_map field) +-{ +- return (reg & field.reg_mask) ? BIT(field.event_bit) : 0; +-} +- +-static u32 pcie_events(struct mc_pcie *port) +-{ +- void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; +- u32 reg = readl_relaxed(ctrl_base_addr + PCIE_EVENT_INT); +- u32 val = 0; +- int i; +- +- for (i = 0; i < ARRAY_SIZE(pcie_event_to_event); i++) +- val |= reg_to_event(reg, pcie_event_to_event[i]); +- +- return val; +-} +- +-static u32 sec_errors(struct mc_pcie *port) +-{ +- void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; +- u32 reg = readl_relaxed(ctrl_base_addr + SEC_ERROR_INT); +- u32 val = 0; +- int i; +- +- for (i = 0; i < ARRAY_SIZE(sec_error_to_event); i++) +- val |= reg_to_event(reg, sec_error_to_event[i]); +- +- return val; +-} +- +-static u32 ded_errors(struct mc_pcie *port) +-{ +- void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; +- u32 reg = readl_relaxed(ctrl_base_addr + DED_ERROR_INT); +- u32 val = 0; +- int i; +- +- for (i = 0; i < ARRAY_SIZE(ded_error_to_event); i++) +- val |= reg_to_event(reg, ded_error_to_event[i]); +- +- return val; +-} +- +-static u32 local_events(struct mc_pcie *port) +-{ +- void __iomem *bridge_base_addr = port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; +- u32 reg = readl_relaxed(bridge_base_addr + ISTATUS_LOCAL); +- u32 val = 0; +- int i; +- +- for (i = 0; i < ARRAY_SIZE(local_status_to_event); i++) +- val |= reg_to_event(reg, local_status_to_event[i]); +- +- return val; +-} +- +-static u32 get_events(struct mc_pcie *port) +-{ +- u32 events = 0; +- +- events |= pcie_events(port); +- events |= sec_errors(port); +- events |= ded_errors(port); +- events |= local_events(port); +- +- return events; +-} +- +-static irqreturn_t mc_event_handler(int irq, void *dev_id) +-{ +- struct mc_pcie *port = dev_id; +- struct device *dev = port->dev; +- struct irq_data *data; +- +- data = irq_domain_get_irq_data(port->event_domain, irq); +- +- if (event_cause[data->hwirq].str) +- dev_err_ratelimited(dev, "%s\n", event_cause[data->hwirq].str); +- else +- dev_err_ratelimited(dev, "bad event IRQ %ld\n", data->hwirq); +- +- return IRQ_HANDLED; +-} +- +-static void mc_handle_event(struct irq_desc *desc) +-{ +- struct mc_pcie *port = irq_desc_get_handler_data(desc); +- unsigned long events; +- u32 bit; +- struct irq_chip *chip = irq_desc_get_chip(desc); +- +- chained_irq_enter(chip, desc); +- +- events = get_events(port); +- +- for_each_set_bit(bit, &events, NUM_EVENTS) +- generic_handle_domain_irq(port->event_domain, bit); +- +- chained_irq_exit(chip, desc); +-} +- +-static void mc_ack_event_irq(struct irq_data *data) +-{ +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); +- u32 event = data->hwirq; +- void __iomem *addr; +- u32 mask; +- +- addr = port->axi_base_addr + event_descs[event].base + +- event_descs[event].offset; +- mask = event_descs[event].mask; +- mask |= event_descs[event].enb_mask; +- +- writel_relaxed(mask, addr); +-} +- +-static void mc_mask_event_irq(struct irq_data *data) +-{ +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); +- u32 event = data->hwirq; +- void __iomem *addr; +- u32 mask; +- u32 val; +- +- addr = port->axi_base_addr + event_descs[event].base + +- event_descs[event].mask_offset; +- mask = event_descs[event].mask; +- if (event_descs[event].enb_mask) { +- mask <<= PCIE_EVENT_INT_ENB_SHIFT; +- mask &= PCIE_EVENT_INT_ENB_MASK; +- } +- +- if (!event_descs[event].mask_high) +- mask = ~mask; +- +- raw_spin_lock(&port->lock); +- val = readl_relaxed(addr); +- if (event_descs[event].mask_high) +- val |= mask; +- else +- val &= mask; +- +- writel_relaxed(val, addr); +- raw_spin_unlock(&port->lock); +-} +- +-static void mc_unmask_event_irq(struct irq_data *data) +-{ +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); +- u32 event = data->hwirq; +- void __iomem *addr; +- u32 mask; +- u32 val; +- +- addr = port->axi_base_addr + event_descs[event].base + +- event_descs[event].mask_offset; +- mask = event_descs[event].mask; +- +- if (event_descs[event].enb_mask) +- mask <<= PCIE_EVENT_INT_ENB_SHIFT; +- +- if (event_descs[event].mask_high) +- mask = ~mask; +- +- if (event_descs[event].enb_mask) +- mask &= PCIE_EVENT_INT_ENB_MASK; +- +- raw_spin_lock(&port->lock); +- val = readl_relaxed(addr); +- if (event_descs[event].mask_high) +- val &= mask; +- else +- val |= mask; +- writel_relaxed(val, addr); +- raw_spin_unlock(&port->lock); +-} +- +-static struct irq_chip mc_event_irq_chip = { +- .name = "Microchip PCIe EVENT", +- .irq_ack = mc_ack_event_irq, +- .irq_mask = mc_mask_event_irq, +- .irq_unmask = mc_unmask_event_irq, +-}; +- +-static int mc_pcie_event_map(struct irq_domain *domain, unsigned int irq, +- irq_hw_number_t hwirq) +-{ +- irq_set_chip_and_handler(irq, &mc_event_irq_chip, handle_level_irq); +- irq_set_chip_data(irq, domain->host_data); +- +- return 0; +-} +- +-static const struct irq_domain_ops event_domain_ops = { +- .map = mc_pcie_event_map, +-}; +- +-static inline void mc_pcie_deinit_clk(void *data) +-{ +- struct clk *clk = data; +- +- clk_disable_unprepare(clk); +-} +- +-static inline struct clk *mc_pcie_init_clk(struct device *dev, const char *id) +-{ +- struct clk *clk; +- int ret; +- +- clk = devm_clk_get_optional(dev, id); +- if (IS_ERR(clk)) +- return clk; +- if (!clk) +- return clk; +- +- ret = clk_prepare_enable(clk); +- if (ret) +- return ERR_PTR(ret); +- +- devm_add_action_or_reset(dev, mc_pcie_deinit_clk, clk); +- +- return clk; +-} +- +-static int mc_pcie_init_clks(struct device *dev) +-{ +- int i; +- struct clk *fic; +- +- /* +- * PCIe may be clocked via Fabric Interface using between 1 and 4 +- * clocks. Scan DT for clocks and enable them if present +- */ +- for (i = 0; i < ARRAY_SIZE(poss_clks); i++) { +- fic = mc_pcie_init_clk(dev, poss_clks[i]); +- if (IS_ERR(fic)) +- return PTR_ERR(fic); +- } +- +- return 0; +-} +- +-static int mc_pcie_init_irq_domains(struct mc_pcie *port) +-{ +- struct device *dev = port->dev; +- struct device_node *node = dev->of_node; +- struct device_node *pcie_intc_node; +- +- /* Setup INTx */ +- pcie_intc_node = of_get_next_child(node, NULL); +- if (!pcie_intc_node) { +- dev_err(dev, "failed to find PCIe Intc node\n"); +- return -EINVAL; +- } +- +- port->event_domain = irq_domain_add_linear(pcie_intc_node, NUM_EVENTS, +- &event_domain_ops, port); +- if (!port->event_domain) { +- dev_err(dev, "failed to get event domain\n"); +- of_node_put(pcie_intc_node); +- return -ENOMEM; +- } +- +- irq_domain_update_bus_token(port->event_domain, DOMAIN_BUS_NEXUS); +- +- port->intx_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX, +- &intx_domain_ops, port); +- if (!port->intx_domain) { +- dev_err(dev, "failed to get an INTx IRQ domain\n"); +- of_node_put(pcie_intc_node); +- return -ENOMEM; +- } +- +- irq_domain_update_bus_token(port->intx_domain, DOMAIN_BUS_WIRED); +- +- of_node_put(pcie_intc_node); +- raw_spin_lock_init(&port->lock); +- +- return mc_allocate_msi_domains(port); +-} +- +-static void mc_pcie_setup_window(void __iomem *bridge_base_addr, u32 index, +- phys_addr_t axi_addr, phys_addr_t pci_addr, +- size_t size) +-{ +- u32 atr_sz = ilog2(size) - 1; +- u32 val; +- +- if (index == 0) +- val = PCIE_CONFIG_INTERFACE; +- else +- val = PCIE_TX_RX_INTERFACE; +- +- writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + +- ATR0_AXI4_SLV0_TRSL_PARAM); +- +- val = lower_32_bits(axi_addr) | (atr_sz << ATR_SIZE_SHIFT) | +- ATR_IMPL_ENABLE; +- writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + +- ATR0_AXI4_SLV0_SRCADDR_PARAM); +- +- val = upper_32_bits(axi_addr); +- writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + +- ATR0_AXI4_SLV0_SRC_ADDR); +- +- val = lower_32_bits(pci_addr); +- writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + +- ATR0_AXI4_SLV0_TRSL_ADDR_LSB); +- +- val = upper_32_bits(pci_addr); +- writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + +- ATR0_AXI4_SLV0_TRSL_ADDR_UDW); +- +- val = readl(bridge_base_addr + ATR0_PCIE_WIN0_SRCADDR_PARAM); +- val |= (ATR0_PCIE_ATR_SIZE << ATR0_PCIE_ATR_SIZE_SHIFT); +- writel(val, bridge_base_addr + ATR0_PCIE_WIN0_SRCADDR_PARAM); +- writel(0, bridge_base_addr + ATR0_PCIE_WIN0_SRC_ADDR); +-} +- +-static int mc_pcie_setup_windows(struct platform_device *pdev, +- struct mc_pcie *port) +-{ +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; +- struct pci_host_bridge *bridge = platform_get_drvdata(pdev); +- struct resource_entry *entry; +- u64 pci_addr; +- u32 index = 1; +- +- resource_list_for_each_entry(entry, &bridge->windows) { +- if (resource_type(entry->res) == IORESOURCE_MEM) { +- pci_addr = entry->res->start - entry->offset; +- mc_pcie_setup_window(bridge_base_addr, index, +- entry->res->start, pci_addr, +- resource_size(entry->res)); +- index++; +- } +- } +- +- return 0; +-} +- +-static inline void mc_clear_secs(struct mc_pcie *port) +-{ +- void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; +- +- writel_relaxed(SEC_ERROR_INT_ALL_RAM_SEC_ERR_INT, ctrl_base_addr + +- SEC_ERROR_INT); +- writel_relaxed(0, ctrl_base_addr + SEC_ERROR_EVENT_CNT); +-} +- +-static inline void mc_clear_deds(struct mc_pcie *port) +-{ +- void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; +- +- writel_relaxed(DED_ERROR_INT_ALL_RAM_DED_ERR_INT, ctrl_base_addr + +- DED_ERROR_INT); +- writel_relaxed(0, ctrl_base_addr + DED_ERROR_EVENT_CNT); +-} +- +-static void mc_disable_interrupts(struct mc_pcie *port) +-{ +- void __iomem *bridge_base_addr = port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; +- void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; +- u32 val; +- +- /* Ensure ECC bypass is enabled */ +- val = ECC_CONTROL_TX_RAM_ECC_BYPASS | +- ECC_CONTROL_RX_RAM_ECC_BYPASS | +- ECC_CONTROL_PCIE2AXI_RAM_ECC_BYPASS | +- ECC_CONTROL_AXI2PCIE_RAM_ECC_BYPASS; +- writel_relaxed(val, ctrl_base_addr + ECC_CONTROL); +- +- /* Disable SEC errors and clear any outstanding */ +- writel_relaxed(SEC_ERROR_INT_ALL_RAM_SEC_ERR_INT, ctrl_base_addr + +- SEC_ERROR_INT_MASK); +- mc_clear_secs(port); +- +- /* Disable DED errors and clear any outstanding */ +- writel_relaxed(DED_ERROR_INT_ALL_RAM_DED_ERR_INT, ctrl_base_addr + +- DED_ERROR_INT_MASK); +- mc_clear_deds(port); +- +- /* Disable local interrupts and clear any outstanding */ +- writel_relaxed(0, bridge_base_addr + IMASK_LOCAL); +- writel_relaxed(GENMASK(31, 0), bridge_base_addr + ISTATUS_LOCAL); +- writel_relaxed(GENMASK(31, 0), bridge_base_addr + ISTATUS_MSI); +- +- /* Disable PCIe events and clear any outstanding */ +- val = PCIE_EVENT_INT_L2_EXIT_INT | +- PCIE_EVENT_INT_HOTRST_EXIT_INT | +- PCIE_EVENT_INT_DLUP_EXIT_INT | +- PCIE_EVENT_INT_L2_EXIT_INT_MASK | +- PCIE_EVENT_INT_HOTRST_EXIT_INT_MASK | +- PCIE_EVENT_INT_DLUP_EXIT_INT_MASK; +- writel_relaxed(val, ctrl_base_addr + PCIE_EVENT_INT); +- +- /* Disable host interrupts and clear any outstanding */ +- writel_relaxed(0, bridge_base_addr + IMASK_HOST); +- writel_relaxed(GENMASK(31, 0), bridge_base_addr + ISTATUS_HOST); +-} +- +-static int mc_init_interrupts(struct platform_device *pdev, struct mc_pcie *port) +-{ +- struct device *dev = &pdev->dev; +- int irq; +- int i, intx_irq, msi_irq, event_irq; +- int ret; +- +- ret = mc_pcie_init_irq_domains(port); +- if (ret) { +- dev_err(dev, "failed creating IRQ domains\n"); +- return ret; +- } +- +- irq = platform_get_irq(pdev, 0); +- if (irq < 0) +- return -ENODEV; +- +- for (i = 0; i < NUM_EVENTS; i++) { +- event_irq = irq_create_mapping(port->event_domain, i); +- if (!event_irq) { +- dev_err(dev, "failed to map hwirq %d\n", i); +- return -ENXIO; +- } +- +- ret = devm_request_irq(dev, event_irq, mc_event_handler, +- 0, event_cause[i].sym, port); +- if (ret) { +- dev_err(dev, "failed to request IRQ %d\n", event_irq); +- return ret; +- } +- } +- +- intx_irq = irq_create_mapping(port->event_domain, +- EVENT_LOCAL_PM_MSI_INT_INTX); +- if (!intx_irq) { +- dev_err(dev, "failed to map INTx interrupt\n"); +- return -ENXIO; +- } +- +- /* Plug the INTx chained handler */ +- irq_set_chained_handler_and_data(intx_irq, mc_handle_intx, port); +- +- msi_irq = irq_create_mapping(port->event_domain, +- EVENT_LOCAL_PM_MSI_INT_MSI); +- if (!msi_irq) +- return -ENXIO; +- +- /* Plug the MSI chained handler */ +- irq_set_chained_handler_and_data(msi_irq, mc_handle_msi, port); +- +- /* Plug the main event chained handler */ +- irq_set_chained_handler_and_data(irq, mc_handle_event, port); +- +- return 0; +-} +- +-static int mc_platform_init(struct pci_config_window *cfg) +-{ +- struct device *dev = cfg->parent; +- struct platform_device *pdev = to_platform_device(dev); +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; +- int ret; +- +- /* Configure address translation table 0 for PCIe config space */ +- mc_pcie_setup_window(bridge_base_addr, 0, cfg->res.start, +- cfg->res.start, +- resource_size(&cfg->res)); +- +- /* Need some fixups in config space */ +- mc_pcie_enable_msi(port, cfg->win); +- +- /* Configure non-config space outbound ranges */ +- ret = mc_pcie_setup_windows(pdev, port); +- if (ret) +- return ret; +- +- /* Address translation is up; safe to enable interrupts */ +- ret = mc_init_interrupts(pdev, port); +- if (ret) +- return ret; +- +- return 0; +-} +- +-static int mc_host_probe(struct platform_device *pdev) +-{ +- struct device *dev = &pdev->dev; +- void __iomem *bridge_base_addr; +- int ret; +- u32 val; +- +- port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); +- if (!port) +- return -ENOMEM; +- +- port->dev = dev; +- +- port->axi_base_addr = devm_platform_ioremap_resource(pdev, 1); +- if (IS_ERR(port->axi_base_addr)) +- return PTR_ERR(port->axi_base_addr); +- +- mc_disable_interrupts(port); +- +- bridge_base_addr = port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; +- +- /* Allow enabling MSI by disabling MSI-X */ +- val = readl(bridge_base_addr + PCIE_PCI_IRQ_DW0); +- val &= ~MSIX_CAP_MASK; +- writel(val, bridge_base_addr + PCIE_PCI_IRQ_DW0); +- +- /* Pick num vectors from bitfile programmed onto FPGA fabric */ +- val = readl(bridge_base_addr + PCIE_PCI_IRQ_DW0); +- val &= NUM_MSI_MSGS_MASK; +- val >>= NUM_MSI_MSGS_SHIFT; +- +- port->msi.num_vectors = 1 << val; +- +- /* Pick vector address from design */ +- port->msi.vector_phy = readl_relaxed(bridge_base_addr + IMSI_ADDR); +- +- ret = mc_pcie_init_clks(dev); +- if (ret) { +- dev_err(dev, "failed to get clock resources, error %d\n", ret); +- return -ENODEV; +- } +- +- return pci_host_common_probe(pdev); +-} +- +-static const struct pci_ecam_ops mc_ecam_ops = { +- .init = mc_platform_init, +- .pci_ops = { +- .map_bus = pci_ecam_map_bus, +- .read = pci_generic_config_read, +- .write = pci_generic_config_write, +- } +-}; +- +-static const struct of_device_id mc_pcie_of_match[] = { +- { +- .compatible = "microchip,pcie-host-1.0", +- .data = &mc_ecam_ops, +- }, +- {}, +-}; +- +-MODULE_DEVICE_TABLE(of, mc_pcie_of_match); +- +-static struct platform_driver mc_pcie_driver = { +- .probe = mc_host_probe, +- .driver = { +- .name = "microchip-pcie", +- .of_match_table = mc_pcie_of_match, +- .suppress_bind_attrs = true, +- }, +-}; +- +-builtin_platform_driver(mc_pcie_driver); +-MODULE_LICENSE("GPL"); +-MODULE_DESCRIPTION("Microchip PCIe host controller driver"); +-MODULE_AUTHOR("Daire McNamara <daire.mcnamara@microchip.com>"); +--- /dev/null ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -0,0 +1,1216 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Microchip AXI PCIe Bridge host controller driver ++ * ++ * Copyright (c) 2018 - 2020 Microchip Corporation. All rights reserved. ++ * ++ * Author: Daire McNamara <daire.mcnamara@microchip.com> ++ */ ++ ++#include <linux/bitfield.h> ++#include <linux/clk.h> ++#include <linux/irqchip/chained_irq.h> ++#include <linux/irqdomain.h> ++#include <linux/module.h> ++#include <linux/msi.h> ++#include <linux/of_address.h> ++#include <linux/of_pci.h> ++#include <linux/pci-ecam.h> ++#include <linux/platform_device.h> ++ ++#include "../../pci.h" ++ ++/* Number of MSI IRQs */ ++#define MC_MAX_NUM_MSI_IRQS 32 ++ ++/* PCIe Bridge Phy and Controller Phy offsets */ ++#define MC_PCIE1_BRIDGE_ADDR 0x00008000u ++#define MC_PCIE1_CTRL_ADDR 0x0000a000u ++ ++#define MC_PCIE_BRIDGE_ADDR (MC_PCIE1_BRIDGE_ADDR) ++#define MC_PCIE_CTRL_ADDR (MC_PCIE1_CTRL_ADDR) ++ ++/* PCIe Bridge Phy Regs */ ++#define PCIE_PCI_IRQ_DW0 0xa8 ++#define MSIX_CAP_MASK BIT(31) ++#define NUM_MSI_MSGS_MASK GENMASK(6, 4) ++#define NUM_MSI_MSGS_SHIFT 4 ++ ++#define IMASK_LOCAL 0x180 ++#define DMA_END_ENGINE_0_MASK 0x00000000u ++#define DMA_END_ENGINE_0_SHIFT 0 ++#define DMA_END_ENGINE_1_MASK 0x00000000u ++#define DMA_END_ENGINE_1_SHIFT 1 ++#define DMA_ERROR_ENGINE_0_MASK 0x00000100u ++#define DMA_ERROR_ENGINE_0_SHIFT 8 ++#define DMA_ERROR_ENGINE_1_MASK 0x00000200u ++#define DMA_ERROR_ENGINE_1_SHIFT 9 ++#define A_ATR_EVT_POST_ERR_MASK 0x00010000u ++#define A_ATR_EVT_POST_ERR_SHIFT 16 ++#define A_ATR_EVT_FETCH_ERR_MASK 0x00020000u ++#define A_ATR_EVT_FETCH_ERR_SHIFT 17 ++#define A_ATR_EVT_DISCARD_ERR_MASK 0x00040000u ++#define A_ATR_EVT_DISCARD_ERR_SHIFT 18 ++#define A_ATR_EVT_DOORBELL_MASK 0x00000000u ++#define A_ATR_EVT_DOORBELL_SHIFT 19 ++#define P_ATR_EVT_POST_ERR_MASK 0x00100000u ++#define P_ATR_EVT_POST_ERR_SHIFT 20 ++#define P_ATR_EVT_FETCH_ERR_MASK 0x00200000u ++#define P_ATR_EVT_FETCH_ERR_SHIFT 21 ++#define P_ATR_EVT_DISCARD_ERR_MASK 0x00400000u ++#define P_ATR_EVT_DISCARD_ERR_SHIFT 22 ++#define P_ATR_EVT_DOORBELL_MASK 0x00000000u ++#define P_ATR_EVT_DOORBELL_SHIFT 23 ++#define PM_MSI_INT_INTA_MASK 0x01000000u ++#define PM_MSI_INT_INTA_SHIFT 24 ++#define PM_MSI_INT_INTB_MASK 0x02000000u ++#define PM_MSI_INT_INTB_SHIFT 25 ++#define PM_MSI_INT_INTC_MASK 0x04000000u ++#define PM_MSI_INT_INTC_SHIFT 26 ++#define PM_MSI_INT_INTD_MASK 0x08000000u ++#define PM_MSI_INT_INTD_SHIFT 27 ++#define PM_MSI_INT_INTX_MASK 0x0f000000u ++#define PM_MSI_INT_INTX_SHIFT 24 ++#define PM_MSI_INT_MSI_MASK 0x10000000u ++#define PM_MSI_INT_MSI_SHIFT 28 ++#define PM_MSI_INT_AER_EVT_MASK 0x20000000u ++#define PM_MSI_INT_AER_EVT_SHIFT 29 ++#define PM_MSI_INT_EVENTS_MASK 0x40000000u ++#define PM_MSI_INT_EVENTS_SHIFT 30 ++#define PM_MSI_INT_SYS_ERR_MASK 0x80000000u ++#define PM_MSI_INT_SYS_ERR_SHIFT 31 ++#define NUM_LOCAL_EVENTS 15 ++#define ISTATUS_LOCAL 0x184 ++#define IMASK_HOST 0x188 ++#define ISTATUS_HOST 0x18c ++#define IMSI_ADDR 0x190 ++#define ISTATUS_MSI 0x194 ++ ++/* PCIe Master table init defines */ ++#define ATR0_PCIE_WIN0_SRCADDR_PARAM 0x600u ++#define ATR0_PCIE_ATR_SIZE 0x25 ++#define ATR0_PCIE_ATR_SIZE_SHIFT 1 ++#define ATR0_PCIE_WIN0_SRC_ADDR 0x604u ++#define ATR0_PCIE_WIN0_TRSL_ADDR_LSB 0x608u ++#define ATR0_PCIE_WIN0_TRSL_ADDR_UDW 0x60cu ++#define ATR0_PCIE_WIN0_TRSL_PARAM 0x610u ++ ++/* PCIe AXI slave table init defines */ ++#define ATR0_AXI4_SLV0_SRCADDR_PARAM 0x800u ++#define ATR_SIZE_SHIFT 1 ++#define ATR_IMPL_ENABLE 1 ++#define ATR0_AXI4_SLV0_SRC_ADDR 0x804u ++#define ATR0_AXI4_SLV0_TRSL_ADDR_LSB 0x808u ++#define ATR0_AXI4_SLV0_TRSL_ADDR_UDW 0x80cu ++#define ATR0_AXI4_SLV0_TRSL_PARAM 0x810u ++#define PCIE_TX_RX_INTERFACE 0x00000000u ++#define PCIE_CONFIG_INTERFACE 0x00000001u ++ ++#define ATR_ENTRY_SIZE 32 ++ ++/* PCIe Controller Phy Regs */ ++#define SEC_ERROR_EVENT_CNT 0x20 ++#define DED_ERROR_EVENT_CNT 0x24 ++#define SEC_ERROR_INT 0x28 ++#define SEC_ERROR_INT_TX_RAM_SEC_ERR_INT GENMASK(3, 0) ++#define SEC_ERROR_INT_RX_RAM_SEC_ERR_INT GENMASK(7, 4) ++#define SEC_ERROR_INT_PCIE2AXI_RAM_SEC_ERR_INT GENMASK(11, 8) ++#define SEC_ERROR_INT_AXI2PCIE_RAM_SEC_ERR_INT GENMASK(15, 12) ++#define SEC_ERROR_INT_ALL_RAM_SEC_ERR_INT GENMASK(15, 0) ++#define NUM_SEC_ERROR_INTS (4) ++#define SEC_ERROR_INT_MASK 0x2c ++#define DED_ERROR_INT 0x30 ++#define DED_ERROR_INT_TX_RAM_DED_ERR_INT GENMASK(3, 0) ++#define DED_ERROR_INT_RX_RAM_DED_ERR_INT GENMASK(7, 4) ++#define DED_ERROR_INT_PCIE2AXI_RAM_DED_ERR_INT GENMASK(11, 8) ++#define DED_ERROR_INT_AXI2PCIE_RAM_DED_ERR_INT GENMASK(15, 12) ++#define DED_ERROR_INT_ALL_RAM_DED_ERR_INT GENMASK(15, 0) ++#define NUM_DED_ERROR_INTS (4) ++#define DED_ERROR_INT_MASK 0x34 ++#define ECC_CONTROL 0x38 ++#define ECC_CONTROL_TX_RAM_INJ_ERROR_0 BIT(0) ++#define ECC_CONTROL_TX_RAM_INJ_ERROR_1 BIT(1) ++#define ECC_CONTROL_TX_RAM_INJ_ERROR_2 BIT(2) ++#define ECC_CONTROL_TX_RAM_INJ_ERROR_3 BIT(3) ++#define ECC_CONTROL_RX_RAM_INJ_ERROR_0 BIT(4) ++#define ECC_CONTROL_RX_RAM_INJ_ERROR_1 BIT(5) ++#define ECC_CONTROL_RX_RAM_INJ_ERROR_2 BIT(6) ++#define ECC_CONTROL_RX_RAM_INJ_ERROR_3 BIT(7) ++#define ECC_CONTROL_PCIE2AXI_RAM_INJ_ERROR_0 BIT(8) ++#define ECC_CONTROL_PCIE2AXI_RAM_INJ_ERROR_1 BIT(9) ++#define ECC_CONTROL_PCIE2AXI_RAM_INJ_ERROR_2 BIT(10) ++#define ECC_CONTROL_PCIE2AXI_RAM_INJ_ERROR_3 BIT(11) ++#define ECC_CONTROL_AXI2PCIE_RAM_INJ_ERROR_0 BIT(12) ++#define ECC_CONTROL_AXI2PCIE_RAM_INJ_ERROR_1 BIT(13) ++#define ECC_CONTROL_AXI2PCIE_RAM_INJ_ERROR_2 BIT(14) ++#define ECC_CONTROL_AXI2PCIE_RAM_INJ_ERROR_3 BIT(15) ++#define ECC_CONTROL_TX_RAM_ECC_BYPASS BIT(24) ++#define ECC_CONTROL_RX_RAM_ECC_BYPASS BIT(25) ++#define ECC_CONTROL_PCIE2AXI_RAM_ECC_BYPASS BIT(26) ++#define ECC_CONTROL_AXI2PCIE_RAM_ECC_BYPASS BIT(27) ++#define PCIE_EVENT_INT 0x14c ++#define PCIE_EVENT_INT_L2_EXIT_INT BIT(0) ++#define PCIE_EVENT_INT_HOTRST_EXIT_INT BIT(1) ++#define PCIE_EVENT_INT_DLUP_EXIT_INT BIT(2) ++#define PCIE_EVENT_INT_MASK GENMASK(2, 0) ++#define PCIE_EVENT_INT_L2_EXIT_INT_MASK BIT(16) ++#define PCIE_EVENT_INT_HOTRST_EXIT_INT_MASK BIT(17) ++#define PCIE_EVENT_INT_DLUP_EXIT_INT_MASK BIT(18) ++#define PCIE_EVENT_INT_ENB_MASK GENMASK(18, 16) ++#define PCIE_EVENT_INT_ENB_SHIFT 16 ++#define NUM_PCIE_EVENTS (3) ++ ++/* PCIe Config space MSI capability structure */ ++#define MC_MSI_CAP_CTRL_OFFSET 0xe0u ++ ++/* Events */ ++#define EVENT_PCIE_L2_EXIT 0 ++#define EVENT_PCIE_HOTRST_EXIT 1 ++#define EVENT_PCIE_DLUP_EXIT 2 ++#define EVENT_SEC_TX_RAM_SEC_ERR 3 ++#define EVENT_SEC_RX_RAM_SEC_ERR 4 ++#define EVENT_SEC_PCIE2AXI_RAM_SEC_ERR 5 ++#define EVENT_SEC_AXI2PCIE_RAM_SEC_ERR 6 ++#define EVENT_DED_TX_RAM_DED_ERR 7 ++#define EVENT_DED_RX_RAM_DED_ERR 8 ++#define EVENT_DED_PCIE2AXI_RAM_DED_ERR 9 ++#define EVENT_DED_AXI2PCIE_RAM_DED_ERR 10 ++#define EVENT_LOCAL_DMA_END_ENGINE_0 11 ++#define EVENT_LOCAL_DMA_END_ENGINE_1 12 ++#define EVENT_LOCAL_DMA_ERROR_ENGINE_0 13 ++#define EVENT_LOCAL_DMA_ERROR_ENGINE_1 14 ++#define EVENT_LOCAL_A_ATR_EVT_POST_ERR 15 ++#define EVENT_LOCAL_A_ATR_EVT_FETCH_ERR 16 ++#define EVENT_LOCAL_A_ATR_EVT_DISCARD_ERR 17 ++#define EVENT_LOCAL_A_ATR_EVT_DOORBELL 18 ++#define EVENT_LOCAL_P_ATR_EVT_POST_ERR 19 ++#define EVENT_LOCAL_P_ATR_EVT_FETCH_ERR 20 ++#define EVENT_LOCAL_P_ATR_EVT_DISCARD_ERR 21 ++#define EVENT_LOCAL_P_ATR_EVT_DOORBELL 22 ++#define EVENT_LOCAL_PM_MSI_INT_INTX 23 ++#define EVENT_LOCAL_PM_MSI_INT_MSI 24 ++#define EVENT_LOCAL_PM_MSI_INT_AER_EVT 25 ++#define EVENT_LOCAL_PM_MSI_INT_EVENTS 26 ++#define EVENT_LOCAL_PM_MSI_INT_SYS_ERR 27 ++#define NUM_EVENTS 28 ++ ++#define PCIE_EVENT_CAUSE(x, s) \ ++ [EVENT_PCIE_ ## x] = { __stringify(x), s } ++ ++#define SEC_ERROR_CAUSE(x, s) \ ++ [EVENT_SEC_ ## x] = { __stringify(x), s } ++ ++#define DED_ERROR_CAUSE(x, s) \ ++ [EVENT_DED_ ## x] = { __stringify(x), s } ++ ++#define LOCAL_EVENT_CAUSE(x, s) \ ++ [EVENT_LOCAL_ ## x] = { __stringify(x), s } ++ ++#define PCIE_EVENT(x) \ ++ .base = MC_PCIE_CTRL_ADDR, \ ++ .offset = PCIE_EVENT_INT, \ ++ .mask_offset = PCIE_EVENT_INT, \ ++ .mask_high = 1, \ ++ .mask = PCIE_EVENT_INT_ ## x ## _INT, \ ++ .enb_mask = PCIE_EVENT_INT_ENB_MASK ++ ++#define SEC_EVENT(x) \ ++ .base = MC_PCIE_CTRL_ADDR, \ ++ .offset = SEC_ERROR_INT, \ ++ .mask_offset = SEC_ERROR_INT_MASK, \ ++ .mask = SEC_ERROR_INT_ ## x ## _INT, \ ++ .mask_high = 1, \ ++ .enb_mask = 0 ++ ++#define DED_EVENT(x) \ ++ .base = MC_PCIE_CTRL_ADDR, \ ++ .offset = DED_ERROR_INT, \ ++ .mask_offset = DED_ERROR_INT_MASK, \ ++ .mask_high = 1, \ ++ .mask = DED_ERROR_INT_ ## x ## _INT, \ ++ .enb_mask = 0 ++ ++#define LOCAL_EVENT(x) \ ++ .base = MC_PCIE_BRIDGE_ADDR, \ ++ .offset = ISTATUS_LOCAL, \ ++ .mask_offset = IMASK_LOCAL, \ ++ .mask_high = 0, \ ++ .mask = x ## _MASK, \ ++ .enb_mask = 0 ++ ++#define PCIE_EVENT_TO_EVENT_MAP(x) \ ++ { PCIE_EVENT_INT_ ## x ## _INT, EVENT_PCIE_ ## x } ++ ++#define SEC_ERROR_TO_EVENT_MAP(x) \ ++ { SEC_ERROR_INT_ ## x ## _INT, EVENT_SEC_ ## x } ++ ++#define DED_ERROR_TO_EVENT_MAP(x) \ ++ { DED_ERROR_INT_ ## x ## _INT, EVENT_DED_ ## x } ++ ++#define LOCAL_STATUS_TO_EVENT_MAP(x) \ ++ { x ## _MASK, EVENT_LOCAL_ ## x } ++ ++struct event_map { ++ u32 reg_mask; ++ u32 event_bit; ++}; ++ ++struct mc_msi { ++ struct mutex lock; /* Protect used bitmap */ ++ struct irq_domain *msi_domain; ++ struct irq_domain *dev_domain; ++ u32 num_vectors; ++ u64 vector_phy; ++ DECLARE_BITMAP(used, MC_MAX_NUM_MSI_IRQS); ++}; ++ ++struct mc_pcie { ++ void __iomem *axi_base_addr; ++ struct device *dev; ++ struct irq_domain *intx_domain; ++ struct irq_domain *event_domain; ++ raw_spinlock_t lock; ++ struct mc_msi msi; ++}; ++ ++struct cause { ++ const char *sym; ++ const char *str; ++}; ++ ++static const struct cause event_cause[NUM_EVENTS] = { ++ PCIE_EVENT_CAUSE(L2_EXIT, "L2 exit event"), ++ PCIE_EVENT_CAUSE(HOTRST_EXIT, "Hot reset exit event"), ++ PCIE_EVENT_CAUSE(DLUP_EXIT, "DLUP exit event"), ++ SEC_ERROR_CAUSE(TX_RAM_SEC_ERR, "sec error in tx buffer"), ++ SEC_ERROR_CAUSE(RX_RAM_SEC_ERR, "sec error in rx buffer"), ++ SEC_ERROR_CAUSE(PCIE2AXI_RAM_SEC_ERR, "sec error in pcie2axi buffer"), ++ SEC_ERROR_CAUSE(AXI2PCIE_RAM_SEC_ERR, "sec error in axi2pcie buffer"), ++ DED_ERROR_CAUSE(TX_RAM_DED_ERR, "ded error in tx buffer"), ++ DED_ERROR_CAUSE(RX_RAM_DED_ERR, "ded error in rx buffer"), ++ DED_ERROR_CAUSE(PCIE2AXI_RAM_DED_ERR, "ded error in pcie2axi buffer"), ++ DED_ERROR_CAUSE(AXI2PCIE_RAM_DED_ERR, "ded error in axi2pcie buffer"), ++ LOCAL_EVENT_CAUSE(DMA_ERROR_ENGINE_0, "dma engine 0 error"), ++ LOCAL_EVENT_CAUSE(DMA_ERROR_ENGINE_1, "dma engine 1 error"), ++ LOCAL_EVENT_CAUSE(A_ATR_EVT_POST_ERR, "axi write request error"), ++ LOCAL_EVENT_CAUSE(A_ATR_EVT_FETCH_ERR, "axi read request error"), ++ LOCAL_EVENT_CAUSE(A_ATR_EVT_DISCARD_ERR, "axi read timeout"), ++ LOCAL_EVENT_CAUSE(P_ATR_EVT_POST_ERR, "pcie write request error"), ++ LOCAL_EVENT_CAUSE(P_ATR_EVT_FETCH_ERR, "pcie read request error"), ++ LOCAL_EVENT_CAUSE(P_ATR_EVT_DISCARD_ERR, "pcie read timeout"), ++ LOCAL_EVENT_CAUSE(PM_MSI_INT_AER_EVT, "aer event"), ++ LOCAL_EVENT_CAUSE(PM_MSI_INT_EVENTS, "pm/ltr/hotplug event"), ++ LOCAL_EVENT_CAUSE(PM_MSI_INT_SYS_ERR, "system error"), ++}; ++ ++static struct event_map pcie_event_to_event[] = { ++ PCIE_EVENT_TO_EVENT_MAP(L2_EXIT), ++ PCIE_EVENT_TO_EVENT_MAP(HOTRST_EXIT), ++ PCIE_EVENT_TO_EVENT_MAP(DLUP_EXIT), ++}; ++ ++static struct event_map sec_error_to_event[] = { ++ SEC_ERROR_TO_EVENT_MAP(TX_RAM_SEC_ERR), ++ SEC_ERROR_TO_EVENT_MAP(RX_RAM_SEC_ERR), ++ SEC_ERROR_TO_EVENT_MAP(PCIE2AXI_RAM_SEC_ERR), ++ SEC_ERROR_TO_EVENT_MAP(AXI2PCIE_RAM_SEC_ERR), ++}; ++ ++static struct event_map ded_error_to_event[] = { ++ DED_ERROR_TO_EVENT_MAP(TX_RAM_DED_ERR), ++ DED_ERROR_TO_EVENT_MAP(RX_RAM_DED_ERR), ++ DED_ERROR_TO_EVENT_MAP(PCIE2AXI_RAM_DED_ERR), ++ DED_ERROR_TO_EVENT_MAP(AXI2PCIE_RAM_DED_ERR), ++}; ++ ++static struct event_map local_status_to_event[] = { ++ LOCAL_STATUS_TO_EVENT_MAP(DMA_END_ENGINE_0), ++ LOCAL_STATUS_TO_EVENT_MAP(DMA_END_ENGINE_1), ++ LOCAL_STATUS_TO_EVENT_MAP(DMA_ERROR_ENGINE_0), ++ LOCAL_STATUS_TO_EVENT_MAP(DMA_ERROR_ENGINE_1), ++ LOCAL_STATUS_TO_EVENT_MAP(A_ATR_EVT_POST_ERR), ++ LOCAL_STATUS_TO_EVENT_MAP(A_ATR_EVT_FETCH_ERR), ++ LOCAL_STATUS_TO_EVENT_MAP(A_ATR_EVT_DISCARD_ERR), ++ LOCAL_STATUS_TO_EVENT_MAP(A_ATR_EVT_DOORBELL), ++ LOCAL_STATUS_TO_EVENT_MAP(P_ATR_EVT_POST_ERR), ++ LOCAL_STATUS_TO_EVENT_MAP(P_ATR_EVT_FETCH_ERR), ++ LOCAL_STATUS_TO_EVENT_MAP(P_ATR_EVT_DISCARD_ERR), ++ LOCAL_STATUS_TO_EVENT_MAP(P_ATR_EVT_DOORBELL), ++ LOCAL_STATUS_TO_EVENT_MAP(PM_MSI_INT_INTX), ++ LOCAL_STATUS_TO_EVENT_MAP(PM_MSI_INT_MSI), ++ LOCAL_STATUS_TO_EVENT_MAP(PM_MSI_INT_AER_EVT), ++ LOCAL_STATUS_TO_EVENT_MAP(PM_MSI_INT_EVENTS), ++ LOCAL_STATUS_TO_EVENT_MAP(PM_MSI_INT_SYS_ERR), ++}; ++ ++static struct { ++ u32 base; ++ u32 offset; ++ u32 mask; ++ u32 shift; ++ u32 enb_mask; ++ u32 mask_high; ++ u32 mask_offset; ++} event_descs[] = { ++ { PCIE_EVENT(L2_EXIT) }, ++ { PCIE_EVENT(HOTRST_EXIT) }, ++ { PCIE_EVENT(DLUP_EXIT) }, ++ { SEC_EVENT(TX_RAM_SEC_ERR) }, ++ { SEC_EVENT(RX_RAM_SEC_ERR) }, ++ { SEC_EVENT(PCIE2AXI_RAM_SEC_ERR) }, ++ { SEC_EVENT(AXI2PCIE_RAM_SEC_ERR) }, ++ { DED_EVENT(TX_RAM_DED_ERR) }, ++ { DED_EVENT(RX_RAM_DED_ERR) }, ++ { DED_EVENT(PCIE2AXI_RAM_DED_ERR) }, ++ { DED_EVENT(AXI2PCIE_RAM_DED_ERR) }, ++ { LOCAL_EVENT(DMA_END_ENGINE_0) }, ++ { LOCAL_EVENT(DMA_END_ENGINE_1) }, ++ { LOCAL_EVENT(DMA_ERROR_ENGINE_0) }, ++ { LOCAL_EVENT(DMA_ERROR_ENGINE_1) }, ++ { LOCAL_EVENT(A_ATR_EVT_POST_ERR) }, ++ { LOCAL_EVENT(A_ATR_EVT_FETCH_ERR) }, ++ { LOCAL_EVENT(A_ATR_EVT_DISCARD_ERR) }, ++ { LOCAL_EVENT(A_ATR_EVT_DOORBELL) }, ++ { LOCAL_EVENT(P_ATR_EVT_POST_ERR) }, ++ { LOCAL_EVENT(P_ATR_EVT_FETCH_ERR) }, ++ { LOCAL_EVENT(P_ATR_EVT_DISCARD_ERR) }, ++ { LOCAL_EVENT(P_ATR_EVT_DOORBELL) }, ++ { LOCAL_EVENT(PM_MSI_INT_INTX) }, ++ { LOCAL_EVENT(PM_MSI_INT_MSI) }, ++ { LOCAL_EVENT(PM_MSI_INT_AER_EVT) }, ++ { LOCAL_EVENT(PM_MSI_INT_EVENTS) }, ++ { LOCAL_EVENT(PM_MSI_INT_SYS_ERR) }, ++}; ++ ++static char poss_clks[][5] = { "fic0", "fic1", "fic2", "fic3" }; ++ ++static struct mc_pcie *port; ++ ++static void mc_pcie_enable_msi(struct mc_pcie *port, void __iomem *ecam) ++{ ++ struct mc_msi *msi = &port->msi; ++ u16 reg; ++ u8 queue_size; ++ ++ /* Fixup MSI enable flag */ ++ reg = readw_relaxed(ecam + MC_MSI_CAP_CTRL_OFFSET + PCI_MSI_FLAGS); ++ reg |= PCI_MSI_FLAGS_ENABLE; ++ writew_relaxed(reg, ecam + MC_MSI_CAP_CTRL_OFFSET + PCI_MSI_FLAGS); ++ ++ /* Fixup PCI MSI queue flags */ ++ queue_size = FIELD_GET(PCI_MSI_FLAGS_QMASK, reg); ++ reg |= FIELD_PREP(PCI_MSI_FLAGS_QSIZE, queue_size); ++ writew_relaxed(reg, ecam + MC_MSI_CAP_CTRL_OFFSET + PCI_MSI_FLAGS); ++ ++ /* Fixup MSI addr fields */ ++ writel_relaxed(lower_32_bits(msi->vector_phy), ++ ecam + MC_MSI_CAP_CTRL_OFFSET + PCI_MSI_ADDRESS_LO); ++ writel_relaxed(upper_32_bits(msi->vector_phy), ++ ecam + MC_MSI_CAP_CTRL_OFFSET + PCI_MSI_ADDRESS_HI); ++} ++ ++static void mc_handle_msi(struct irq_desc *desc) ++{ ++ struct mc_pcie *port = irq_desc_get_handler_data(desc); ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ struct device *dev = port->dev; ++ struct mc_msi *msi = &port->msi; ++ void __iomem *bridge_base_addr = ++ port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ unsigned long status; ++ u32 bit; ++ int ret; ++ ++ chained_irq_enter(chip, desc); ++ ++ status = readl_relaxed(bridge_base_addr + ISTATUS_LOCAL); ++ if (status & PM_MSI_INT_MSI_MASK) { ++ writel_relaxed(status & PM_MSI_INT_MSI_MASK, bridge_base_addr + ISTATUS_LOCAL); ++ status = readl_relaxed(bridge_base_addr + ISTATUS_MSI); ++ for_each_set_bit(bit, &status, msi->num_vectors) { ++ ret = generic_handle_domain_irq(msi->dev_domain, bit); ++ if (ret) ++ dev_err_ratelimited(dev, "bad MSI IRQ %d\n", ++ bit); ++ } ++ } ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static void mc_msi_bottom_irq_ack(struct irq_data *data) ++{ ++ struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ void __iomem *bridge_base_addr = ++ port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ u32 bitpos = data->hwirq; ++ ++ writel_relaxed(BIT(bitpos), bridge_base_addr + ISTATUS_MSI); ++} ++ ++static void mc_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) ++{ ++ struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ phys_addr_t addr = port->msi.vector_phy; ++ ++ msg->address_lo = lower_32_bits(addr); ++ msg->address_hi = upper_32_bits(addr); ++ msg->data = data->hwirq; ++ ++ dev_dbg(port->dev, "msi#%x address_hi %#x address_lo %#x\n", ++ (int)data->hwirq, msg->address_hi, msg->address_lo); ++} ++ ++static int mc_msi_set_affinity(struct irq_data *irq_data, ++ const struct cpumask *mask, bool force) ++{ ++ return -EINVAL; ++} ++ ++static struct irq_chip mc_msi_bottom_irq_chip = { ++ .name = "Microchip MSI", ++ .irq_ack = mc_msi_bottom_irq_ack, ++ .irq_compose_msi_msg = mc_compose_msi_msg, ++ .irq_set_affinity = mc_msi_set_affinity, ++}; ++ ++static int mc_irq_msi_domain_alloc(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs, void *args) ++{ ++ struct mc_pcie *port = domain->host_data; ++ struct mc_msi *msi = &port->msi; ++ unsigned long bit; ++ ++ mutex_lock(&msi->lock); ++ bit = find_first_zero_bit(msi->used, msi->num_vectors); ++ if (bit >= msi->num_vectors) { ++ mutex_unlock(&msi->lock); ++ return -ENOSPC; ++ } ++ ++ set_bit(bit, msi->used); ++ ++ irq_domain_set_info(domain, virq, bit, &mc_msi_bottom_irq_chip, ++ domain->host_data, handle_edge_irq, NULL, NULL); ++ ++ mutex_unlock(&msi->lock); ++ ++ return 0; ++} ++ ++static void mc_irq_msi_domain_free(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs) ++{ ++ struct irq_data *d = irq_domain_get_irq_data(domain, virq); ++ struct mc_pcie *port = irq_data_get_irq_chip_data(d); ++ struct mc_msi *msi = &port->msi; ++ ++ mutex_lock(&msi->lock); ++ ++ if (test_bit(d->hwirq, msi->used)) ++ __clear_bit(d->hwirq, msi->used); ++ else ++ dev_err(port->dev, "trying to free unused MSI%lu\n", d->hwirq); ++ ++ mutex_unlock(&msi->lock); ++} ++ ++static const struct irq_domain_ops msi_domain_ops = { ++ .alloc = mc_irq_msi_domain_alloc, ++ .free = mc_irq_msi_domain_free, ++}; ++ ++static struct irq_chip mc_msi_irq_chip = { ++ .name = "Microchip PCIe MSI", ++ .irq_ack = irq_chip_ack_parent, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, ++}; ++ ++static struct msi_domain_info mc_msi_domain_info = { ++ .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | ++ MSI_FLAG_PCI_MSIX), ++ .chip = &mc_msi_irq_chip, ++}; ++ ++static int mc_allocate_msi_domains(struct mc_pcie *port) ++{ ++ struct device *dev = port->dev; ++ struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node); ++ struct mc_msi *msi = &port->msi; ++ ++ mutex_init(&port->msi.lock); ++ ++ msi->dev_domain = irq_domain_add_linear(NULL, msi->num_vectors, ++ &msi_domain_ops, port); ++ if (!msi->dev_domain) { ++ dev_err(dev, "failed to create IRQ domain\n"); ++ return -ENOMEM; ++ } ++ ++ msi->msi_domain = pci_msi_create_irq_domain(fwnode, &mc_msi_domain_info, ++ msi->dev_domain); ++ if (!msi->msi_domain) { ++ dev_err(dev, "failed to create MSI domain\n"); ++ irq_domain_remove(msi->dev_domain); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static void mc_handle_intx(struct irq_desc *desc) ++{ ++ struct mc_pcie *port = irq_desc_get_handler_data(desc); ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ struct device *dev = port->dev; ++ void __iomem *bridge_base_addr = ++ port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ unsigned long status; ++ u32 bit; ++ int ret; ++ ++ chained_irq_enter(chip, desc); ++ ++ status = readl_relaxed(bridge_base_addr + ISTATUS_LOCAL); ++ if (status & PM_MSI_INT_INTX_MASK) { ++ status &= PM_MSI_INT_INTX_MASK; ++ status >>= PM_MSI_INT_INTX_SHIFT; ++ for_each_set_bit(bit, &status, PCI_NUM_INTX) { ++ ret = generic_handle_domain_irq(port->intx_domain, bit); ++ if (ret) ++ dev_err_ratelimited(dev, "bad INTx IRQ %d\n", ++ bit); ++ } ++ } ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static void mc_ack_intx_irq(struct irq_data *data) ++{ ++ struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ void __iomem *bridge_base_addr = ++ port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); ++ ++ writel_relaxed(mask, bridge_base_addr + ISTATUS_LOCAL); ++} ++ ++static void mc_mask_intx_irq(struct irq_data *data) ++{ ++ struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ void __iomem *bridge_base_addr = ++ port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ unsigned long flags; ++ u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); ++ u32 val; ++ ++ raw_spin_lock_irqsave(&port->lock, flags); ++ val = readl_relaxed(bridge_base_addr + IMASK_LOCAL); ++ val &= ~mask; ++ writel_relaxed(val, bridge_base_addr + IMASK_LOCAL); ++ raw_spin_unlock_irqrestore(&port->lock, flags); ++} ++ ++static void mc_unmask_intx_irq(struct irq_data *data) ++{ ++ struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ void __iomem *bridge_base_addr = ++ port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ unsigned long flags; ++ u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); ++ u32 val; ++ ++ raw_spin_lock_irqsave(&port->lock, flags); ++ val = readl_relaxed(bridge_base_addr + IMASK_LOCAL); ++ val |= mask; ++ writel_relaxed(val, bridge_base_addr + IMASK_LOCAL); ++ raw_spin_unlock_irqrestore(&port->lock, flags); ++} ++ ++static struct irq_chip mc_intx_irq_chip = { ++ .name = "Microchip PCIe INTx", ++ .irq_ack = mc_ack_intx_irq, ++ .irq_mask = mc_mask_intx_irq, ++ .irq_unmask = mc_unmask_intx_irq, ++}; ++ ++static int mc_pcie_intx_map(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ irq_set_chip_and_handler(irq, &mc_intx_irq_chip, handle_level_irq); ++ irq_set_chip_data(irq, domain->host_data); ++ ++ return 0; ++} ++ ++static const struct irq_domain_ops intx_domain_ops = { ++ .map = mc_pcie_intx_map, ++}; ++ ++static inline u32 reg_to_event(u32 reg, struct event_map field) ++{ ++ return (reg & field.reg_mask) ? BIT(field.event_bit) : 0; ++} ++ ++static u32 pcie_events(struct mc_pcie *port) ++{ ++ void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; ++ u32 reg = readl_relaxed(ctrl_base_addr + PCIE_EVENT_INT); ++ u32 val = 0; ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(pcie_event_to_event); i++) ++ val |= reg_to_event(reg, pcie_event_to_event[i]); ++ ++ return val; ++} ++ ++static u32 sec_errors(struct mc_pcie *port) ++{ ++ void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; ++ u32 reg = readl_relaxed(ctrl_base_addr + SEC_ERROR_INT); ++ u32 val = 0; ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(sec_error_to_event); i++) ++ val |= reg_to_event(reg, sec_error_to_event[i]); ++ ++ return val; ++} ++ ++static u32 ded_errors(struct mc_pcie *port) ++{ ++ void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; ++ u32 reg = readl_relaxed(ctrl_base_addr + DED_ERROR_INT); ++ u32 val = 0; ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(ded_error_to_event); i++) ++ val |= reg_to_event(reg, ded_error_to_event[i]); ++ ++ return val; ++} ++ ++static u32 local_events(struct mc_pcie *port) ++{ ++ void __iomem *bridge_base_addr = port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ u32 reg = readl_relaxed(bridge_base_addr + ISTATUS_LOCAL); ++ u32 val = 0; ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(local_status_to_event); i++) ++ val |= reg_to_event(reg, local_status_to_event[i]); ++ ++ return val; ++} ++ ++static u32 get_events(struct mc_pcie *port) ++{ ++ u32 events = 0; ++ ++ events |= pcie_events(port); ++ events |= sec_errors(port); ++ events |= ded_errors(port); ++ events |= local_events(port); ++ ++ return events; ++} ++ ++static irqreturn_t mc_event_handler(int irq, void *dev_id) ++{ ++ struct mc_pcie *port = dev_id; ++ struct device *dev = port->dev; ++ struct irq_data *data; ++ ++ data = irq_domain_get_irq_data(port->event_domain, irq); ++ ++ if (event_cause[data->hwirq].str) ++ dev_err_ratelimited(dev, "%s\n", event_cause[data->hwirq].str); ++ else ++ dev_err_ratelimited(dev, "bad event IRQ %ld\n", data->hwirq); ++ ++ return IRQ_HANDLED; ++} ++ ++static void mc_handle_event(struct irq_desc *desc) ++{ ++ struct mc_pcie *port = irq_desc_get_handler_data(desc); ++ unsigned long events; ++ u32 bit; ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ ++ chained_irq_enter(chip, desc); ++ ++ events = get_events(port); ++ ++ for_each_set_bit(bit, &events, NUM_EVENTS) ++ generic_handle_domain_irq(port->event_domain, bit); ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static void mc_ack_event_irq(struct irq_data *data) ++{ ++ struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ u32 event = data->hwirq; ++ void __iomem *addr; ++ u32 mask; ++ ++ addr = port->axi_base_addr + event_descs[event].base + ++ event_descs[event].offset; ++ mask = event_descs[event].mask; ++ mask |= event_descs[event].enb_mask; ++ ++ writel_relaxed(mask, addr); ++} ++ ++static void mc_mask_event_irq(struct irq_data *data) ++{ ++ struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ u32 event = data->hwirq; ++ void __iomem *addr; ++ u32 mask; ++ u32 val; ++ ++ addr = port->axi_base_addr + event_descs[event].base + ++ event_descs[event].mask_offset; ++ mask = event_descs[event].mask; ++ if (event_descs[event].enb_mask) { ++ mask <<= PCIE_EVENT_INT_ENB_SHIFT; ++ mask &= PCIE_EVENT_INT_ENB_MASK; ++ } ++ ++ if (!event_descs[event].mask_high) ++ mask = ~mask; ++ ++ raw_spin_lock(&port->lock); ++ val = readl_relaxed(addr); ++ if (event_descs[event].mask_high) ++ val |= mask; ++ else ++ val &= mask; ++ ++ writel_relaxed(val, addr); ++ raw_spin_unlock(&port->lock); ++} ++ ++static void mc_unmask_event_irq(struct irq_data *data) ++{ ++ struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ u32 event = data->hwirq; ++ void __iomem *addr; ++ u32 mask; ++ u32 val; ++ ++ addr = port->axi_base_addr + event_descs[event].base + ++ event_descs[event].mask_offset; ++ mask = event_descs[event].mask; ++ ++ if (event_descs[event].enb_mask) ++ mask <<= PCIE_EVENT_INT_ENB_SHIFT; ++ ++ if (event_descs[event].mask_high) ++ mask = ~mask; ++ ++ if (event_descs[event].enb_mask) ++ mask &= PCIE_EVENT_INT_ENB_MASK; ++ ++ raw_spin_lock(&port->lock); ++ val = readl_relaxed(addr); ++ if (event_descs[event].mask_high) ++ val &= mask; ++ else ++ val |= mask; ++ writel_relaxed(val, addr); ++ raw_spin_unlock(&port->lock); ++} ++ ++static struct irq_chip mc_event_irq_chip = { ++ .name = "Microchip PCIe EVENT", ++ .irq_ack = mc_ack_event_irq, ++ .irq_mask = mc_mask_event_irq, ++ .irq_unmask = mc_unmask_event_irq, ++}; ++ ++static int mc_pcie_event_map(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ irq_set_chip_and_handler(irq, &mc_event_irq_chip, handle_level_irq); ++ irq_set_chip_data(irq, domain->host_data); ++ ++ return 0; ++} ++ ++static const struct irq_domain_ops event_domain_ops = { ++ .map = mc_pcie_event_map, ++}; ++ ++static inline void mc_pcie_deinit_clk(void *data) ++{ ++ struct clk *clk = data; ++ ++ clk_disable_unprepare(clk); ++} ++ ++static inline struct clk *mc_pcie_init_clk(struct device *dev, const char *id) ++{ ++ struct clk *clk; ++ int ret; ++ ++ clk = devm_clk_get_optional(dev, id); ++ if (IS_ERR(clk)) ++ return clk; ++ if (!clk) ++ return clk; ++ ++ ret = clk_prepare_enable(clk); ++ if (ret) ++ return ERR_PTR(ret); ++ ++ devm_add_action_or_reset(dev, mc_pcie_deinit_clk, clk); ++ ++ return clk; ++} ++ ++static int mc_pcie_init_clks(struct device *dev) ++{ ++ int i; ++ struct clk *fic; ++ ++ /* ++ * PCIe may be clocked via Fabric Interface using between 1 and 4 ++ * clocks. Scan DT for clocks and enable them if present ++ */ ++ for (i = 0; i < ARRAY_SIZE(poss_clks); i++) { ++ fic = mc_pcie_init_clk(dev, poss_clks[i]); ++ if (IS_ERR(fic)) ++ return PTR_ERR(fic); ++ } ++ ++ return 0; ++} ++ ++static int mc_pcie_init_irq_domains(struct mc_pcie *port) ++{ ++ struct device *dev = port->dev; ++ struct device_node *node = dev->of_node; ++ struct device_node *pcie_intc_node; ++ ++ /* Setup INTx */ ++ pcie_intc_node = of_get_next_child(node, NULL); ++ if (!pcie_intc_node) { ++ dev_err(dev, "failed to find PCIe Intc node\n"); ++ return -EINVAL; ++ } ++ ++ port->event_domain = irq_domain_add_linear(pcie_intc_node, NUM_EVENTS, ++ &event_domain_ops, port); ++ if (!port->event_domain) { ++ dev_err(dev, "failed to get event domain\n"); ++ of_node_put(pcie_intc_node); ++ return -ENOMEM; ++ } ++ ++ irq_domain_update_bus_token(port->event_domain, DOMAIN_BUS_NEXUS); ++ ++ port->intx_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX, ++ &intx_domain_ops, port); ++ if (!port->intx_domain) { ++ dev_err(dev, "failed to get an INTx IRQ domain\n"); ++ of_node_put(pcie_intc_node); ++ return -ENOMEM; ++ } ++ ++ irq_domain_update_bus_token(port->intx_domain, DOMAIN_BUS_WIRED); ++ ++ of_node_put(pcie_intc_node); ++ raw_spin_lock_init(&port->lock); ++ ++ return mc_allocate_msi_domains(port); ++} ++ ++static void mc_pcie_setup_window(void __iomem *bridge_base_addr, u32 index, ++ phys_addr_t axi_addr, phys_addr_t pci_addr, ++ size_t size) ++{ ++ u32 atr_sz = ilog2(size) - 1; ++ u32 val; ++ ++ if (index == 0) ++ val = PCIE_CONFIG_INTERFACE; ++ else ++ val = PCIE_TX_RX_INTERFACE; ++ ++ writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + ++ ATR0_AXI4_SLV0_TRSL_PARAM); ++ ++ val = lower_32_bits(axi_addr) | (atr_sz << ATR_SIZE_SHIFT) | ++ ATR_IMPL_ENABLE; ++ writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + ++ ATR0_AXI4_SLV0_SRCADDR_PARAM); ++ ++ val = upper_32_bits(axi_addr); ++ writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + ++ ATR0_AXI4_SLV0_SRC_ADDR); ++ ++ val = lower_32_bits(pci_addr); ++ writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + ++ ATR0_AXI4_SLV0_TRSL_ADDR_LSB); ++ ++ val = upper_32_bits(pci_addr); ++ writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + ++ ATR0_AXI4_SLV0_TRSL_ADDR_UDW); ++ ++ val = readl(bridge_base_addr + ATR0_PCIE_WIN0_SRCADDR_PARAM); ++ val |= (ATR0_PCIE_ATR_SIZE << ATR0_PCIE_ATR_SIZE_SHIFT); ++ writel(val, bridge_base_addr + ATR0_PCIE_WIN0_SRCADDR_PARAM); ++ writel(0, bridge_base_addr + ATR0_PCIE_WIN0_SRC_ADDR); ++} ++ ++static int mc_pcie_setup_windows(struct platform_device *pdev, ++ struct mc_pcie *port) ++{ ++ void __iomem *bridge_base_addr = ++ port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ struct pci_host_bridge *bridge = platform_get_drvdata(pdev); ++ struct resource_entry *entry; ++ u64 pci_addr; ++ u32 index = 1; ++ ++ resource_list_for_each_entry(entry, &bridge->windows) { ++ if (resource_type(entry->res) == IORESOURCE_MEM) { ++ pci_addr = entry->res->start - entry->offset; ++ mc_pcie_setup_window(bridge_base_addr, index, ++ entry->res->start, pci_addr, ++ resource_size(entry->res)); ++ index++; ++ } ++ } ++ ++ return 0; ++} ++ ++static inline void mc_clear_secs(struct mc_pcie *port) ++{ ++ void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; ++ ++ writel_relaxed(SEC_ERROR_INT_ALL_RAM_SEC_ERR_INT, ctrl_base_addr + ++ SEC_ERROR_INT); ++ writel_relaxed(0, ctrl_base_addr + SEC_ERROR_EVENT_CNT); ++} ++ ++static inline void mc_clear_deds(struct mc_pcie *port) ++{ ++ void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; ++ ++ writel_relaxed(DED_ERROR_INT_ALL_RAM_DED_ERR_INT, ctrl_base_addr + ++ DED_ERROR_INT); ++ writel_relaxed(0, ctrl_base_addr + DED_ERROR_EVENT_CNT); ++} ++ ++static void mc_disable_interrupts(struct mc_pcie *port) ++{ ++ void __iomem *bridge_base_addr = port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; ++ u32 val; ++ ++ /* Ensure ECC bypass is enabled */ ++ val = ECC_CONTROL_TX_RAM_ECC_BYPASS | ++ ECC_CONTROL_RX_RAM_ECC_BYPASS | ++ ECC_CONTROL_PCIE2AXI_RAM_ECC_BYPASS | ++ ECC_CONTROL_AXI2PCIE_RAM_ECC_BYPASS; ++ writel_relaxed(val, ctrl_base_addr + ECC_CONTROL); ++ ++ /* Disable SEC errors and clear any outstanding */ ++ writel_relaxed(SEC_ERROR_INT_ALL_RAM_SEC_ERR_INT, ctrl_base_addr + ++ SEC_ERROR_INT_MASK); ++ mc_clear_secs(port); ++ ++ /* Disable DED errors and clear any outstanding */ ++ writel_relaxed(DED_ERROR_INT_ALL_RAM_DED_ERR_INT, ctrl_base_addr + ++ DED_ERROR_INT_MASK); ++ mc_clear_deds(port); ++ ++ /* Disable local interrupts and clear any outstanding */ ++ writel_relaxed(0, bridge_base_addr + IMASK_LOCAL); ++ writel_relaxed(GENMASK(31, 0), bridge_base_addr + ISTATUS_LOCAL); ++ writel_relaxed(GENMASK(31, 0), bridge_base_addr + ISTATUS_MSI); ++ ++ /* Disable PCIe events and clear any outstanding */ ++ val = PCIE_EVENT_INT_L2_EXIT_INT | ++ PCIE_EVENT_INT_HOTRST_EXIT_INT | ++ PCIE_EVENT_INT_DLUP_EXIT_INT | ++ PCIE_EVENT_INT_L2_EXIT_INT_MASK | ++ PCIE_EVENT_INT_HOTRST_EXIT_INT_MASK | ++ PCIE_EVENT_INT_DLUP_EXIT_INT_MASK; ++ writel_relaxed(val, ctrl_base_addr + PCIE_EVENT_INT); ++ ++ /* Disable host interrupts and clear any outstanding */ ++ writel_relaxed(0, bridge_base_addr + IMASK_HOST); ++ writel_relaxed(GENMASK(31, 0), bridge_base_addr + ISTATUS_HOST); ++} ++ ++static int mc_init_interrupts(struct platform_device *pdev, struct mc_pcie *port) ++{ ++ struct device *dev = &pdev->dev; ++ int irq; ++ int i, intx_irq, msi_irq, event_irq; ++ int ret; ++ ++ ret = mc_pcie_init_irq_domains(port); ++ if (ret) { ++ dev_err(dev, "failed creating IRQ domains\n"); ++ return ret; ++ } ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return -ENODEV; ++ ++ for (i = 0; i < NUM_EVENTS; i++) { ++ event_irq = irq_create_mapping(port->event_domain, i); ++ if (!event_irq) { ++ dev_err(dev, "failed to map hwirq %d\n", i); ++ return -ENXIO; ++ } ++ ++ ret = devm_request_irq(dev, event_irq, mc_event_handler, ++ 0, event_cause[i].sym, port); ++ if (ret) { ++ dev_err(dev, "failed to request IRQ %d\n", event_irq); ++ return ret; ++ } ++ } ++ ++ intx_irq = irq_create_mapping(port->event_domain, ++ EVENT_LOCAL_PM_MSI_INT_INTX); ++ if (!intx_irq) { ++ dev_err(dev, "failed to map INTx interrupt\n"); ++ return -ENXIO; ++ } ++ ++ /* Plug the INTx chained handler */ ++ irq_set_chained_handler_and_data(intx_irq, mc_handle_intx, port); ++ ++ msi_irq = irq_create_mapping(port->event_domain, ++ EVENT_LOCAL_PM_MSI_INT_MSI); ++ if (!msi_irq) ++ return -ENXIO; ++ ++ /* Plug the MSI chained handler */ ++ irq_set_chained_handler_and_data(msi_irq, mc_handle_msi, port); ++ ++ /* Plug the main event chained handler */ ++ irq_set_chained_handler_and_data(irq, mc_handle_event, port); ++ ++ return 0; ++} ++ ++static int mc_platform_init(struct pci_config_window *cfg) ++{ ++ struct device *dev = cfg->parent; ++ struct platform_device *pdev = to_platform_device(dev); ++ void __iomem *bridge_base_addr = ++ port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ int ret; ++ ++ /* Configure address translation table 0 for PCIe config space */ ++ mc_pcie_setup_window(bridge_base_addr, 0, cfg->res.start, ++ cfg->res.start, ++ resource_size(&cfg->res)); ++ ++ /* Need some fixups in config space */ ++ mc_pcie_enable_msi(port, cfg->win); ++ ++ /* Configure non-config space outbound ranges */ ++ ret = mc_pcie_setup_windows(pdev, port); ++ if (ret) ++ return ret; ++ ++ /* Address translation is up; safe to enable interrupts */ ++ ret = mc_init_interrupts(pdev, port); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int mc_host_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ void __iomem *bridge_base_addr; ++ int ret; ++ u32 val; ++ ++ port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); ++ if (!port) ++ return -ENOMEM; ++ ++ port->dev = dev; ++ ++ port->axi_base_addr = devm_platform_ioremap_resource(pdev, 1); ++ if (IS_ERR(port->axi_base_addr)) ++ return PTR_ERR(port->axi_base_addr); ++ ++ mc_disable_interrupts(port); ++ ++ bridge_base_addr = port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ ++ /* Allow enabling MSI by disabling MSI-X */ ++ val = readl(bridge_base_addr + PCIE_PCI_IRQ_DW0); ++ val &= ~MSIX_CAP_MASK; ++ writel(val, bridge_base_addr + PCIE_PCI_IRQ_DW0); ++ ++ /* Pick num vectors from bitfile programmed onto FPGA fabric */ ++ val = readl(bridge_base_addr + PCIE_PCI_IRQ_DW0); ++ val &= NUM_MSI_MSGS_MASK; ++ val >>= NUM_MSI_MSGS_SHIFT; ++ ++ port->msi.num_vectors = 1 << val; ++ ++ /* Pick vector address from design */ ++ port->msi.vector_phy = readl_relaxed(bridge_base_addr + IMSI_ADDR); ++ ++ ret = mc_pcie_init_clks(dev); ++ if (ret) { ++ dev_err(dev, "failed to get clock resources, error %d\n", ret); ++ return -ENODEV; ++ } ++ ++ return pci_host_common_probe(pdev); ++} ++ ++static const struct pci_ecam_ops mc_ecam_ops = { ++ .init = mc_platform_init, ++ .pci_ops = { ++ .map_bus = pci_ecam_map_bus, ++ .read = pci_generic_config_read, ++ .write = pci_generic_config_write, ++ } ++}; ++ ++static const struct of_device_id mc_pcie_of_match[] = { ++ { ++ .compatible = "microchip,pcie-host-1.0", ++ .data = &mc_ecam_ops, ++ }, ++ {}, ++}; ++ ++MODULE_DEVICE_TABLE(of, mc_pcie_of_match); ++ ++static struct platform_driver mc_pcie_driver = { ++ .probe = mc_host_probe, ++ .driver = { ++ .name = "microchip-pcie", ++ .of_match_table = mc_pcie_of_match, ++ .suppress_bind_attrs = true, ++ }, ++}; ++ ++builtin_platform_driver(mc_pcie_driver); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("Microchip PCIe host controller driver"); ++MODULE_AUTHOR("Daire McNamara <daire.mcnamara@microchip.com>"); diff --git a/target/linux/starfive/patches-6.6/0016-PCI-microchip-Move-PLDA-IP-register-macros-to-pcie-p.patch b/target/linux/starfive/patches-6.6/0016-PCI-microchip-Move-PLDA-IP-register-macros-to-pcie-p.patch new file mode 100644 index 0000000000..0aacec2769 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0016-PCI-microchip-Move-PLDA-IP-register-macros-to-pcie-p.patch @@ -0,0 +1,259 @@ +From eca1b864bb2d4e8d9811506979560a89351c2e37 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:05:53 +0800 +Subject: [PATCH 016/116] PCI: microchip: Move PLDA IP register macros to + pcie-plda.h + +Move PLDA PCIe host controller IP registers macros to pcie-plda.h, +including bridge registers and local IRQ event number. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Reviewed-by: Conor Dooley <conor.dooley@microchip.com> +--- + .../pci/controller/plda/pcie-microchip-host.c | 108 +++--------------- + drivers/pci/controller/plda/pcie-plda.h | 108 ++++++++++++++++++ + 2 files changed, 124 insertions(+), 92 deletions(-) + create mode 100644 drivers/pci/controller/plda/pcie-plda.h + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -19,6 +19,7 @@ + #include <linux/platform_device.h> + + #include "../../pci.h" ++#include "pcie-plda.h" + + /* Number of MSI IRQs */ + #define MC_MAX_NUM_MSI_IRQS 32 +@@ -30,84 +31,6 @@ + #define MC_PCIE_BRIDGE_ADDR (MC_PCIE1_BRIDGE_ADDR) + #define MC_PCIE_CTRL_ADDR (MC_PCIE1_CTRL_ADDR) + +-/* PCIe Bridge Phy Regs */ +-#define PCIE_PCI_IRQ_DW0 0xa8 +-#define MSIX_CAP_MASK BIT(31) +-#define NUM_MSI_MSGS_MASK GENMASK(6, 4) +-#define NUM_MSI_MSGS_SHIFT 4 +- +-#define IMASK_LOCAL 0x180 +-#define DMA_END_ENGINE_0_MASK 0x00000000u +-#define DMA_END_ENGINE_0_SHIFT 0 +-#define DMA_END_ENGINE_1_MASK 0x00000000u +-#define DMA_END_ENGINE_1_SHIFT 1 +-#define DMA_ERROR_ENGINE_0_MASK 0x00000100u +-#define DMA_ERROR_ENGINE_0_SHIFT 8 +-#define DMA_ERROR_ENGINE_1_MASK 0x00000200u +-#define DMA_ERROR_ENGINE_1_SHIFT 9 +-#define A_ATR_EVT_POST_ERR_MASK 0x00010000u +-#define A_ATR_EVT_POST_ERR_SHIFT 16 +-#define A_ATR_EVT_FETCH_ERR_MASK 0x00020000u +-#define A_ATR_EVT_FETCH_ERR_SHIFT 17 +-#define A_ATR_EVT_DISCARD_ERR_MASK 0x00040000u +-#define A_ATR_EVT_DISCARD_ERR_SHIFT 18 +-#define A_ATR_EVT_DOORBELL_MASK 0x00000000u +-#define A_ATR_EVT_DOORBELL_SHIFT 19 +-#define P_ATR_EVT_POST_ERR_MASK 0x00100000u +-#define P_ATR_EVT_POST_ERR_SHIFT 20 +-#define P_ATR_EVT_FETCH_ERR_MASK 0x00200000u +-#define P_ATR_EVT_FETCH_ERR_SHIFT 21 +-#define P_ATR_EVT_DISCARD_ERR_MASK 0x00400000u +-#define P_ATR_EVT_DISCARD_ERR_SHIFT 22 +-#define P_ATR_EVT_DOORBELL_MASK 0x00000000u +-#define P_ATR_EVT_DOORBELL_SHIFT 23 +-#define PM_MSI_INT_INTA_MASK 0x01000000u +-#define PM_MSI_INT_INTA_SHIFT 24 +-#define PM_MSI_INT_INTB_MASK 0x02000000u +-#define PM_MSI_INT_INTB_SHIFT 25 +-#define PM_MSI_INT_INTC_MASK 0x04000000u +-#define PM_MSI_INT_INTC_SHIFT 26 +-#define PM_MSI_INT_INTD_MASK 0x08000000u +-#define PM_MSI_INT_INTD_SHIFT 27 +-#define PM_MSI_INT_INTX_MASK 0x0f000000u +-#define PM_MSI_INT_INTX_SHIFT 24 +-#define PM_MSI_INT_MSI_MASK 0x10000000u +-#define PM_MSI_INT_MSI_SHIFT 28 +-#define PM_MSI_INT_AER_EVT_MASK 0x20000000u +-#define PM_MSI_INT_AER_EVT_SHIFT 29 +-#define PM_MSI_INT_EVENTS_MASK 0x40000000u +-#define PM_MSI_INT_EVENTS_SHIFT 30 +-#define PM_MSI_INT_SYS_ERR_MASK 0x80000000u +-#define PM_MSI_INT_SYS_ERR_SHIFT 31 +-#define NUM_LOCAL_EVENTS 15 +-#define ISTATUS_LOCAL 0x184 +-#define IMASK_HOST 0x188 +-#define ISTATUS_HOST 0x18c +-#define IMSI_ADDR 0x190 +-#define ISTATUS_MSI 0x194 +- +-/* PCIe Master table init defines */ +-#define ATR0_PCIE_WIN0_SRCADDR_PARAM 0x600u +-#define ATR0_PCIE_ATR_SIZE 0x25 +-#define ATR0_PCIE_ATR_SIZE_SHIFT 1 +-#define ATR0_PCIE_WIN0_SRC_ADDR 0x604u +-#define ATR0_PCIE_WIN0_TRSL_ADDR_LSB 0x608u +-#define ATR0_PCIE_WIN0_TRSL_ADDR_UDW 0x60cu +-#define ATR0_PCIE_WIN0_TRSL_PARAM 0x610u +- +-/* PCIe AXI slave table init defines */ +-#define ATR0_AXI4_SLV0_SRCADDR_PARAM 0x800u +-#define ATR_SIZE_SHIFT 1 +-#define ATR_IMPL_ENABLE 1 +-#define ATR0_AXI4_SLV0_SRC_ADDR 0x804u +-#define ATR0_AXI4_SLV0_TRSL_ADDR_LSB 0x808u +-#define ATR0_AXI4_SLV0_TRSL_ADDR_UDW 0x80cu +-#define ATR0_AXI4_SLV0_TRSL_PARAM 0x810u +-#define PCIE_TX_RX_INTERFACE 0x00000000u +-#define PCIE_CONFIG_INTERFACE 0x00000001u +- +-#define ATR_ENTRY_SIZE 32 +- + /* PCIe Controller Phy Regs */ + #define SEC_ERROR_EVENT_CNT 0x20 + #define DED_ERROR_EVENT_CNT 0x24 +@@ -179,20 +102,21 @@ + #define EVENT_LOCAL_DMA_END_ENGINE_1 12 + #define EVENT_LOCAL_DMA_ERROR_ENGINE_0 13 + #define EVENT_LOCAL_DMA_ERROR_ENGINE_1 14 +-#define EVENT_LOCAL_A_ATR_EVT_POST_ERR 15 +-#define EVENT_LOCAL_A_ATR_EVT_FETCH_ERR 16 +-#define EVENT_LOCAL_A_ATR_EVT_DISCARD_ERR 17 +-#define EVENT_LOCAL_A_ATR_EVT_DOORBELL 18 +-#define EVENT_LOCAL_P_ATR_EVT_POST_ERR 19 +-#define EVENT_LOCAL_P_ATR_EVT_FETCH_ERR 20 +-#define EVENT_LOCAL_P_ATR_EVT_DISCARD_ERR 21 +-#define EVENT_LOCAL_P_ATR_EVT_DOORBELL 22 +-#define EVENT_LOCAL_PM_MSI_INT_INTX 23 +-#define EVENT_LOCAL_PM_MSI_INT_MSI 24 +-#define EVENT_LOCAL_PM_MSI_INT_AER_EVT 25 +-#define EVENT_LOCAL_PM_MSI_INT_EVENTS 26 +-#define EVENT_LOCAL_PM_MSI_INT_SYS_ERR 27 +-#define NUM_EVENTS 28 ++#define NUM_MC_EVENTS 15 ++#define EVENT_LOCAL_A_ATR_EVT_POST_ERR (NUM_MC_EVENTS + PLDA_AXI_POST_ERR) ++#define EVENT_LOCAL_A_ATR_EVT_FETCH_ERR (NUM_MC_EVENTS + PLDA_AXI_FETCH_ERR) ++#define EVENT_LOCAL_A_ATR_EVT_DISCARD_ERR (NUM_MC_EVENTS + PLDA_AXI_DISCARD_ERR) ++#define EVENT_LOCAL_A_ATR_EVT_DOORBELL (NUM_MC_EVENTS + PLDA_AXI_DOORBELL) ++#define EVENT_LOCAL_P_ATR_EVT_POST_ERR (NUM_MC_EVENTS + PLDA_PCIE_POST_ERR) ++#define EVENT_LOCAL_P_ATR_EVT_FETCH_ERR (NUM_MC_EVENTS + PLDA_PCIE_FETCH_ERR) ++#define EVENT_LOCAL_P_ATR_EVT_DISCARD_ERR (NUM_MC_EVENTS + PLDA_PCIE_DISCARD_ERR) ++#define EVENT_LOCAL_P_ATR_EVT_DOORBELL (NUM_MC_EVENTS + PLDA_PCIE_DOORBELL) ++#define EVENT_LOCAL_PM_MSI_INT_INTX (NUM_MC_EVENTS + PLDA_INTX) ++#define EVENT_LOCAL_PM_MSI_INT_MSI (NUM_MC_EVENTS + PLDA_MSI) ++#define EVENT_LOCAL_PM_MSI_INT_AER_EVT (NUM_MC_EVENTS + PLDA_AER_EVENT) ++#define EVENT_LOCAL_PM_MSI_INT_EVENTS (NUM_MC_EVENTS + PLDA_MISC_EVENTS) ++#define EVENT_LOCAL_PM_MSI_INT_SYS_ERR (NUM_MC_EVENTS + PLDA_SYS_ERR) ++#define NUM_EVENTS (NUM_MC_EVENTS + PLDA_INT_EVENT_NUM) + + #define PCIE_EVENT_CAUSE(x, s) \ + [EVENT_PCIE_ ## x] = { __stringify(x), s } +--- /dev/null ++++ b/drivers/pci/controller/plda/pcie-plda.h +@@ -0,0 +1,108 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * PLDA PCIe host controller driver ++ */ ++ ++#ifndef _PCIE_PLDA_H ++#define _PCIE_PLDA_H ++ ++/* PCIe Bridge Phy Regs */ ++#define PCIE_PCI_IRQ_DW0 0xa8 ++#define MSIX_CAP_MASK BIT(31) ++#define NUM_MSI_MSGS_MASK GENMASK(6, 4) ++#define NUM_MSI_MSGS_SHIFT 4 ++ ++#define IMASK_LOCAL 0x180 ++#define DMA_END_ENGINE_0_MASK 0x00000000u ++#define DMA_END_ENGINE_0_SHIFT 0 ++#define DMA_END_ENGINE_1_MASK 0x00000000u ++#define DMA_END_ENGINE_1_SHIFT 1 ++#define DMA_ERROR_ENGINE_0_MASK 0x00000100u ++#define DMA_ERROR_ENGINE_0_SHIFT 8 ++#define DMA_ERROR_ENGINE_1_MASK 0x00000200u ++#define DMA_ERROR_ENGINE_1_SHIFT 9 ++#define A_ATR_EVT_POST_ERR_MASK 0x00010000u ++#define A_ATR_EVT_POST_ERR_SHIFT 16 ++#define A_ATR_EVT_FETCH_ERR_MASK 0x00020000u ++#define A_ATR_EVT_FETCH_ERR_SHIFT 17 ++#define A_ATR_EVT_DISCARD_ERR_MASK 0x00040000u ++#define A_ATR_EVT_DISCARD_ERR_SHIFT 18 ++#define A_ATR_EVT_DOORBELL_MASK 0x00000000u ++#define A_ATR_EVT_DOORBELL_SHIFT 19 ++#define P_ATR_EVT_POST_ERR_MASK 0x00100000u ++#define P_ATR_EVT_POST_ERR_SHIFT 20 ++#define P_ATR_EVT_FETCH_ERR_MASK 0x00200000u ++#define P_ATR_EVT_FETCH_ERR_SHIFT 21 ++#define P_ATR_EVT_DISCARD_ERR_MASK 0x00400000u ++#define P_ATR_EVT_DISCARD_ERR_SHIFT 22 ++#define P_ATR_EVT_DOORBELL_MASK 0x00000000u ++#define P_ATR_EVT_DOORBELL_SHIFT 23 ++#define PM_MSI_INT_INTA_MASK 0x01000000u ++#define PM_MSI_INT_INTA_SHIFT 24 ++#define PM_MSI_INT_INTB_MASK 0x02000000u ++#define PM_MSI_INT_INTB_SHIFT 25 ++#define PM_MSI_INT_INTC_MASK 0x04000000u ++#define PM_MSI_INT_INTC_SHIFT 26 ++#define PM_MSI_INT_INTD_MASK 0x08000000u ++#define PM_MSI_INT_INTD_SHIFT 27 ++#define PM_MSI_INT_INTX_MASK 0x0f000000u ++#define PM_MSI_INT_INTX_SHIFT 24 ++#define PM_MSI_INT_MSI_MASK 0x10000000u ++#define PM_MSI_INT_MSI_SHIFT 28 ++#define PM_MSI_INT_AER_EVT_MASK 0x20000000u ++#define PM_MSI_INT_AER_EVT_SHIFT 29 ++#define PM_MSI_INT_EVENTS_MASK 0x40000000u ++#define PM_MSI_INT_EVENTS_SHIFT 30 ++#define PM_MSI_INT_SYS_ERR_MASK 0x80000000u ++#define PM_MSI_INT_SYS_ERR_SHIFT 31 ++#define NUM_LOCAL_EVENTS 15 ++#define ISTATUS_LOCAL 0x184 ++#define IMASK_HOST 0x188 ++#define ISTATUS_HOST 0x18c ++#define IMSI_ADDR 0x190 ++#define ISTATUS_MSI 0x194 ++ ++/* PCIe Master table init defines */ ++#define ATR0_PCIE_WIN0_SRCADDR_PARAM 0x600u ++#define ATR0_PCIE_ATR_SIZE 0x25 ++#define ATR0_PCIE_ATR_SIZE_SHIFT 1 ++#define ATR0_PCIE_WIN0_SRC_ADDR 0x604u ++#define ATR0_PCIE_WIN0_TRSL_ADDR_LSB 0x608u ++#define ATR0_PCIE_WIN0_TRSL_ADDR_UDW 0x60cu ++#define ATR0_PCIE_WIN0_TRSL_PARAM 0x610u ++ ++/* PCIe AXI slave table init defines */ ++#define ATR0_AXI4_SLV0_SRCADDR_PARAM 0x800u ++#define ATR_SIZE_SHIFT 1 ++#define ATR_IMPL_ENABLE 1 ++#define ATR0_AXI4_SLV0_SRC_ADDR 0x804u ++#define ATR0_AXI4_SLV0_TRSL_ADDR_LSB 0x808u ++#define ATR0_AXI4_SLV0_TRSL_ADDR_UDW 0x80cu ++#define ATR0_AXI4_SLV0_TRSL_PARAM 0x810u ++#define PCIE_TX_RX_INTERFACE 0x00000000u ++#define PCIE_CONFIG_INTERFACE 0x00000001u ++ ++#define ATR_ENTRY_SIZE 32 ++ ++enum plda_int_event { ++ PLDA_AXI_POST_ERR, ++ PLDA_AXI_FETCH_ERR, ++ PLDA_AXI_DISCARD_ERR, ++ PLDA_AXI_DOORBELL, ++ PLDA_PCIE_POST_ERR, ++ PLDA_PCIE_FETCH_ERR, ++ PLDA_PCIE_DISCARD_ERR, ++ PLDA_PCIE_DOORBELL, ++ PLDA_INTX, ++ PLDA_MSI, ++ PLDA_AER_EVENT, ++ PLDA_MISC_EVENTS, ++ PLDA_SYS_ERR, ++ PLDA_INT_EVENT_NUM ++}; ++ ++#define PLDA_NUM_DMA_EVENTS 16 ++ ++#define PLDA_MAX_INT_NUM (PLDA_NUM_DMA_EVENTS + PLDA_INT_EVENT_NUM) ++ ++#endif diff --git a/target/linux/starfive/patches-6.6/0017-PCI-microchip-Add-bridge_addr-field-to-struct-mc_pci.patch b/target/linux/starfive/patches-6.6/0017-PCI-microchip-Add-bridge_addr-field-to-struct-mc_pci.patch new file mode 100644 index 0000000000..bc3f909116 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0017-PCI-microchip-Add-bridge_addr-field-to-struct-mc_pci.patch @@ -0,0 +1,107 @@ +From 6ee4d4568314425079ae88229bb9abbff9b92b8b Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:05:54 +0800 +Subject: [PATCH 017/116] PCI: microchip: Add bridge_addr field to struct + mc_pcie + +For bridge address base is common PLDA field, Add this to struct mc_pcie +first. + +INTx and MSI codes interrupts codes will get the bridge base address from +port->bridge_addr. These codes will be changed to common codes. +axi_base_addr is Microchip its own data. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Reviewed-by: Conor Dooley <conor.dooley@microchip.com> +--- + .../pci/controller/plda/pcie-microchip-host.c | 23 ++++++++----------- + 1 file changed, 9 insertions(+), 14 deletions(-) + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -195,6 +195,7 @@ struct mc_pcie { + struct irq_domain *event_domain; + raw_spinlock_t lock; + struct mc_msi msi; ++ void __iomem *bridge_addr; + }; + + struct cause { +@@ -339,8 +340,7 @@ static void mc_handle_msi(struct irq_des + struct irq_chip *chip = irq_desc_get_chip(desc); + struct device *dev = port->dev; + struct mc_msi *msi = &port->msi; +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ void __iomem *bridge_base_addr = port->bridge_addr; + unsigned long status; + u32 bit; + int ret; +@@ -365,8 +365,7 @@ static void mc_handle_msi(struct irq_des + static void mc_msi_bottom_irq_ack(struct irq_data *data) + { + struct mc_pcie *port = irq_data_get_irq_chip_data(data); +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ void __iomem *bridge_base_addr = port->bridge_addr; + u32 bitpos = data->hwirq; + + writel_relaxed(BIT(bitpos), bridge_base_addr + ISTATUS_MSI); +@@ -488,8 +487,7 @@ static void mc_handle_intx(struct irq_de + struct mc_pcie *port = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + struct device *dev = port->dev; +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ void __iomem *bridge_base_addr = port->bridge_addr; + unsigned long status; + u32 bit; + int ret; +@@ -514,8 +512,7 @@ static void mc_handle_intx(struct irq_de + static void mc_ack_intx_irq(struct irq_data *data) + { + struct mc_pcie *port = irq_data_get_irq_chip_data(data); +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ void __iomem *bridge_base_addr = port->bridge_addr; + u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); + + writel_relaxed(mask, bridge_base_addr + ISTATUS_LOCAL); +@@ -524,8 +521,7 @@ static void mc_ack_intx_irq(struct irq_d + static void mc_mask_intx_irq(struct irq_data *data) + { + struct mc_pcie *port = irq_data_get_irq_chip_data(data); +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ void __iomem *bridge_base_addr = port->bridge_addr; + unsigned long flags; + u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); + u32 val; +@@ -540,8 +536,7 @@ static void mc_mask_intx_irq(struct irq_ + static void mc_unmask_intx_irq(struct irq_data *data) + { + struct mc_pcie *port = irq_data_get_irq_chip_data(data); +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ void __iomem *bridge_base_addr = port->bridge_addr; + unsigned long flags; + u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); + u32 val; +@@ -896,8 +891,7 @@ static void mc_pcie_setup_window(void __ + static int mc_pcie_setup_windows(struct platform_device *pdev, + struct mc_pcie *port) + { +- void __iomem *bridge_base_addr = +- port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ void __iomem *bridge_base_addr = port->bridge_addr; + struct pci_host_bridge *bridge = platform_get_drvdata(pdev); + struct resource_entry *entry; + u64 pci_addr; +@@ -1081,6 +1075,7 @@ static int mc_host_probe(struct platform + mc_disable_interrupts(port); + + bridge_base_addr = port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; ++ port->bridge_addr = bridge_base_addr; + + /* Allow enabling MSI by disabling MSI-X */ + val = readl(bridge_base_addr + PCIE_PCI_IRQ_DW0); diff --git a/target/linux/starfive/patches-6.6/0018-PCI-microchip-Rename-two-PCIe-data-structures.patch b/target/linux/starfive/patches-6.6/0018-PCI-microchip-Rename-two-PCIe-data-structures.patch new file mode 100644 index 0000000000..a9e7b8975b --- /dev/null +++ b/target/linux/starfive/patches-6.6/0018-PCI-microchip-Rename-two-PCIe-data-structures.patch @@ -0,0 +1,347 @@ +From 7c1c679bdd0b6b727248edbee77836024c935f91 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:05:55 +0800 +Subject: [PATCH 018/116] PCI: microchip: Rename two PCIe data structures + +Add PLDA PCIe related data structures by rename data structure name from +mc_* to plda_*. + +axi_base_addr is stayed in struct mc_pcie for it's microchip its own data. + +The event interrupt codes is still using struct mc_pcie because the event +interrupt codes can not be re-used. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Reviewed-by: Conor Dooley <conor.dooley@microchip.com> +--- + .../pci/controller/plda/pcie-microchip-host.c | 96 ++++++++++--------- + 1 file changed, 53 insertions(+), 43 deletions(-) + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -22,7 +22,7 @@ + #include "pcie-plda.h" + + /* Number of MSI IRQs */ +-#define MC_MAX_NUM_MSI_IRQS 32 ++#define PLDA_MAX_NUM_MSI_IRQS 32 + + /* PCIe Bridge Phy and Controller Phy offsets */ + #define MC_PCIE1_BRIDGE_ADDR 0x00008000u +@@ -179,25 +179,29 @@ struct event_map { + u32 event_bit; + }; + +-struct mc_msi { ++struct plda_msi { + struct mutex lock; /* Protect used bitmap */ + struct irq_domain *msi_domain; + struct irq_domain *dev_domain; + u32 num_vectors; + u64 vector_phy; +- DECLARE_BITMAP(used, MC_MAX_NUM_MSI_IRQS); ++ DECLARE_BITMAP(used, PLDA_MAX_NUM_MSI_IRQS); + }; + +-struct mc_pcie { +- void __iomem *axi_base_addr; ++struct plda_pcie_rp { + struct device *dev; + struct irq_domain *intx_domain; + struct irq_domain *event_domain; + raw_spinlock_t lock; +- struct mc_msi msi; ++ struct plda_msi msi; + void __iomem *bridge_addr; + }; + ++struct mc_pcie { ++ struct plda_pcie_rp plda; ++ void __iomem *axi_base_addr; ++}; ++ + struct cause { + const char *sym; + const char *str; +@@ -313,7 +317,7 @@ static struct mc_pcie *port; + + static void mc_pcie_enable_msi(struct mc_pcie *port, void __iomem *ecam) + { +- struct mc_msi *msi = &port->msi; ++ struct plda_msi *msi = &port->plda.msi; + u16 reg; + u8 queue_size; + +@@ -336,10 +340,10 @@ static void mc_pcie_enable_msi(struct mc + + static void mc_handle_msi(struct irq_desc *desc) + { +- struct mc_pcie *port = irq_desc_get_handler_data(desc); ++ struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + struct device *dev = port->dev; +- struct mc_msi *msi = &port->msi; ++ struct plda_msi *msi = &port->msi; + void __iomem *bridge_base_addr = port->bridge_addr; + unsigned long status; + u32 bit; +@@ -364,7 +368,7 @@ static void mc_handle_msi(struct irq_des + + static void mc_msi_bottom_irq_ack(struct irq_data *data) + { +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); + void __iomem *bridge_base_addr = port->bridge_addr; + u32 bitpos = data->hwirq; + +@@ -373,7 +377,7 @@ static void mc_msi_bottom_irq_ack(struct + + static void mc_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) + { +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); + phys_addr_t addr = port->msi.vector_phy; + + msg->address_lo = lower_32_bits(addr); +@@ -400,8 +404,8 @@ static struct irq_chip mc_msi_bottom_irq + static int mc_irq_msi_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *args) + { +- struct mc_pcie *port = domain->host_data; +- struct mc_msi *msi = &port->msi; ++ struct plda_pcie_rp *port = domain->host_data; ++ struct plda_msi *msi = &port->msi; + unsigned long bit; + + mutex_lock(&msi->lock); +@@ -425,8 +429,8 @@ static void mc_irq_msi_domain_free(struc + unsigned int nr_irqs) + { + struct irq_data *d = irq_domain_get_irq_data(domain, virq); +- struct mc_pcie *port = irq_data_get_irq_chip_data(d); +- struct mc_msi *msi = &port->msi; ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(d); ++ struct plda_msi *msi = &port->msi; + + mutex_lock(&msi->lock); + +@@ -456,11 +460,11 @@ static struct msi_domain_info mc_msi_dom + .chip = &mc_msi_irq_chip, + }; + +-static int mc_allocate_msi_domains(struct mc_pcie *port) ++static int mc_allocate_msi_domains(struct plda_pcie_rp *port) + { + struct device *dev = port->dev; + struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node); +- struct mc_msi *msi = &port->msi; ++ struct plda_msi *msi = &port->msi; + + mutex_init(&port->msi.lock); + +@@ -484,7 +488,7 @@ static int mc_allocate_msi_domains(struc + + static void mc_handle_intx(struct irq_desc *desc) + { +- struct mc_pcie *port = irq_desc_get_handler_data(desc); ++ struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + struct device *dev = port->dev; + void __iomem *bridge_base_addr = port->bridge_addr; +@@ -511,7 +515,7 @@ static void mc_handle_intx(struct irq_de + + static void mc_ack_intx_irq(struct irq_data *data) + { +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); + void __iomem *bridge_base_addr = port->bridge_addr; + u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); + +@@ -520,7 +524,7 @@ static void mc_ack_intx_irq(struct irq_d + + static void mc_mask_intx_irq(struct irq_data *data) + { +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); + void __iomem *bridge_base_addr = port->bridge_addr; + unsigned long flags; + u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); +@@ -535,7 +539,7 @@ static void mc_mask_intx_irq(struct irq_ + + static void mc_unmask_intx_irq(struct irq_data *data) + { +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); + void __iomem *bridge_base_addr = port->bridge_addr; + unsigned long flags; + u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); +@@ -625,21 +629,22 @@ static u32 local_events(struct mc_pcie * + return val; + } + +-static u32 get_events(struct mc_pcie *port) ++static u32 get_events(struct plda_pcie_rp *port) + { ++ struct mc_pcie *mc_port = container_of(port, struct mc_pcie, plda); + u32 events = 0; + +- events |= pcie_events(port); +- events |= sec_errors(port); +- events |= ded_errors(port); +- events |= local_events(port); ++ events |= pcie_events(mc_port); ++ events |= sec_errors(mc_port); ++ events |= ded_errors(mc_port); ++ events |= local_events(mc_port); + + return events; + } + + static irqreturn_t mc_event_handler(int irq, void *dev_id) + { +- struct mc_pcie *port = dev_id; ++ struct plda_pcie_rp *port = dev_id; + struct device *dev = port->dev; + struct irq_data *data; + +@@ -655,7 +660,7 @@ static irqreturn_t mc_event_handler(int + + static void mc_handle_event(struct irq_desc *desc) + { +- struct mc_pcie *port = irq_desc_get_handler_data(desc); ++ struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); + unsigned long events; + u32 bit; + struct irq_chip *chip = irq_desc_get_chip(desc); +@@ -672,12 +677,13 @@ static void mc_handle_event(struct irq_d + + static void mc_ack_event_irq(struct irq_data *data) + { +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ struct mc_pcie *mc_port = container_of(port, struct mc_pcie, plda); + u32 event = data->hwirq; + void __iomem *addr; + u32 mask; + +- addr = port->axi_base_addr + event_descs[event].base + ++ addr = mc_port->axi_base_addr + event_descs[event].base + + event_descs[event].offset; + mask = event_descs[event].mask; + mask |= event_descs[event].enb_mask; +@@ -687,13 +693,14 @@ static void mc_ack_event_irq(struct irq_ + + static void mc_mask_event_irq(struct irq_data *data) + { +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ struct mc_pcie *mc_port = container_of(port, struct mc_pcie, plda); + u32 event = data->hwirq; + void __iomem *addr; + u32 mask; + u32 val; + +- addr = port->axi_base_addr + event_descs[event].base + ++ addr = mc_port->axi_base_addr + event_descs[event].base + + event_descs[event].mask_offset; + mask = event_descs[event].mask; + if (event_descs[event].enb_mask) { +@@ -717,13 +724,14 @@ static void mc_mask_event_irq(struct irq + + static void mc_unmask_event_irq(struct irq_data *data) + { +- struct mc_pcie *port = irq_data_get_irq_chip_data(data); ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ struct mc_pcie *mc_port = container_of(port, struct mc_pcie, plda); + u32 event = data->hwirq; + void __iomem *addr; + u32 mask; + u32 val; + +- addr = port->axi_base_addr + event_descs[event].base + ++ addr = mc_port->axi_base_addr + event_descs[event].base + + event_descs[event].mask_offset; + mask = event_descs[event].mask; + +@@ -811,7 +819,7 @@ static int mc_pcie_init_clks(struct devi + return 0; + } + +-static int mc_pcie_init_irq_domains(struct mc_pcie *port) ++static int mc_pcie_init_irq_domains(struct plda_pcie_rp *port) + { + struct device *dev = port->dev; + struct device_node *node = dev->of_node; +@@ -889,7 +897,7 @@ static void mc_pcie_setup_window(void __ + } + + static int mc_pcie_setup_windows(struct platform_device *pdev, +- struct mc_pcie *port) ++ struct plda_pcie_rp *port) + { + void __iomem *bridge_base_addr = port->bridge_addr; + struct pci_host_bridge *bridge = platform_get_drvdata(pdev); +@@ -970,7 +978,7 @@ static void mc_disable_interrupts(struct + writel_relaxed(GENMASK(31, 0), bridge_base_addr + ISTATUS_HOST); + } + +-static int mc_init_interrupts(struct platform_device *pdev, struct mc_pcie *port) ++static int mc_init_interrupts(struct platform_device *pdev, struct plda_pcie_rp *port) + { + struct device *dev = &pdev->dev; + int irq; +@@ -1043,12 +1051,12 @@ static int mc_platform_init(struct pci_c + mc_pcie_enable_msi(port, cfg->win); + + /* Configure non-config space outbound ranges */ +- ret = mc_pcie_setup_windows(pdev, port); ++ ret = mc_pcie_setup_windows(pdev, &port->plda); + if (ret) + return ret; + + /* Address translation is up; safe to enable interrupts */ +- ret = mc_init_interrupts(pdev, port); ++ ret = mc_init_interrupts(pdev, &port->plda); + if (ret) + return ret; + +@@ -1059,6 +1067,7 @@ static int mc_host_probe(struct platform + { + struct device *dev = &pdev->dev; + void __iomem *bridge_base_addr; ++ struct plda_pcie_rp *plda; + int ret; + u32 val; + +@@ -1066,7 +1075,8 @@ static int mc_host_probe(struct platform + if (!port) + return -ENOMEM; + +- port->dev = dev; ++ plda = &port->plda; ++ plda->dev = dev; + + port->axi_base_addr = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(port->axi_base_addr)) +@@ -1075,7 +1085,7 @@ static int mc_host_probe(struct platform + mc_disable_interrupts(port); + + bridge_base_addr = port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; +- port->bridge_addr = bridge_base_addr; ++ plda->bridge_addr = bridge_base_addr; + + /* Allow enabling MSI by disabling MSI-X */ + val = readl(bridge_base_addr + PCIE_PCI_IRQ_DW0); +@@ -1087,10 +1097,10 @@ static int mc_host_probe(struct platform + val &= NUM_MSI_MSGS_MASK; + val >>= NUM_MSI_MSGS_SHIFT; + +- port->msi.num_vectors = 1 << val; ++ plda->msi.num_vectors = 1 << val; + + /* Pick vector address from design */ +- port->msi.vector_phy = readl_relaxed(bridge_base_addr + IMSI_ADDR); ++ plda->msi.vector_phy = readl_relaxed(bridge_base_addr + IMSI_ADDR); + + ret = mc_pcie_init_clks(dev); + if (ret) { diff --git a/target/linux/starfive/patches-6.6/0019-PCI-microchip-Move-PCIe-host-data-structures-to-plda.patch b/target/linux/starfive/patches-6.6/0019-PCI-microchip-Move-PCIe-host-data-structures-to-plda.patch new file mode 100644 index 0000000000..0823502cfd --- /dev/null +++ b/target/linux/starfive/patches-6.6/0019-PCI-microchip-Move-PCIe-host-data-structures-to-plda.patch @@ -0,0 +1,87 @@ +From a53cf9b237dd53c9538b51a8df592888935c8411 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:05:56 +0800 +Subject: [PATCH 019/116] PCI: microchip: Move PCIe host data structures to + plda-pcie.h + +Move the common data structures definition to head file for these two data +structures can be re-used. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Reviewed-by: Conor Dooley <conor.dooley@microchip.com> +--- + .../pci/controller/plda/pcie-microchip-host.c | 20 ------------------ + drivers/pci/controller/plda/pcie-plda.h | 21 +++++++++++++++++++ + 2 files changed, 21 insertions(+), 20 deletions(-) + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -21,9 +21,6 @@ + #include "../../pci.h" + #include "pcie-plda.h" + +-/* Number of MSI IRQs */ +-#define PLDA_MAX_NUM_MSI_IRQS 32 +- + /* PCIe Bridge Phy and Controller Phy offsets */ + #define MC_PCIE1_BRIDGE_ADDR 0x00008000u + #define MC_PCIE1_CTRL_ADDR 0x0000a000u +@@ -179,23 +176,6 @@ struct event_map { + u32 event_bit; + }; + +-struct plda_msi { +- struct mutex lock; /* Protect used bitmap */ +- struct irq_domain *msi_domain; +- struct irq_domain *dev_domain; +- u32 num_vectors; +- u64 vector_phy; +- DECLARE_BITMAP(used, PLDA_MAX_NUM_MSI_IRQS); +-}; +- +-struct plda_pcie_rp { +- struct device *dev; +- struct irq_domain *intx_domain; +- struct irq_domain *event_domain; +- raw_spinlock_t lock; +- struct plda_msi msi; +- void __iomem *bridge_addr; +-}; + + struct mc_pcie { + struct plda_pcie_rp plda; +--- a/drivers/pci/controller/plda/pcie-plda.h ++++ b/drivers/pci/controller/plda/pcie-plda.h +@@ -6,6 +6,9 @@ + #ifndef _PCIE_PLDA_H + #define _PCIE_PLDA_H + ++/* Number of MSI IRQs */ ++#define PLDA_MAX_NUM_MSI_IRQS 32 ++ + /* PCIe Bridge Phy Regs */ + #define PCIE_PCI_IRQ_DW0 0xa8 + #define MSIX_CAP_MASK BIT(31) +@@ -105,4 +108,22 @@ enum plda_int_event { + + #define PLDA_MAX_INT_NUM (PLDA_NUM_DMA_EVENTS + PLDA_INT_EVENT_NUM) + ++struct plda_msi { ++ struct mutex lock; /* Protect used bitmap */ ++ struct irq_domain *msi_domain; ++ struct irq_domain *dev_domain; ++ u32 num_vectors; ++ u64 vector_phy; ++ DECLARE_BITMAP(used, PLDA_MAX_NUM_MSI_IRQS); ++}; ++ ++struct plda_pcie_rp { ++ struct device *dev; ++ struct irq_domain *intx_domain; ++ struct irq_domain *event_domain; ++ raw_spinlock_t lock; ++ struct plda_msi msi; ++ void __iomem *bridge_addr; ++}; ++ + #endif diff --git a/target/linux/starfive/patches-6.6/0020-PCI-microchip-Rename-two-setup-functions.patch b/target/linux/starfive/patches-6.6/0020-PCI-microchip-Rename-two-setup-functions.patch new file mode 100644 index 0000000000..2bed600216 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0020-PCI-microchip-Rename-two-setup-functions.patch @@ -0,0 +1,76 @@ +From d0e56d1ef7398bbf76be6e48d77943cbf644688e Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:05:57 +0800 +Subject: [PATCH 020/116] PCI: microchip: Rename two setup functions + +Rename two setup functions to plda prefix. Prepare to re-use these two +setup function. + +For two setup functions names are similar, rename mc_pcie_setup_windows() +to plda_pcie_setup_iomems(). + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Reviewed-by: Conor Dooley <conor.dooley@microchip.com> +--- + .../pci/controller/plda/pcie-microchip-host.c | 24 +++++++++---------- + 1 file changed, 12 insertions(+), 12 deletions(-) + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -838,9 +838,9 @@ static int mc_pcie_init_irq_domains(stru + return mc_allocate_msi_domains(port); + } + +-static void mc_pcie_setup_window(void __iomem *bridge_base_addr, u32 index, +- phys_addr_t axi_addr, phys_addr_t pci_addr, +- size_t size) ++static void plda_pcie_setup_window(void __iomem *bridge_base_addr, u32 index, ++ phys_addr_t axi_addr, phys_addr_t pci_addr, ++ size_t size) + { + u32 atr_sz = ilog2(size) - 1; + u32 val; +@@ -876,8 +876,8 @@ static void mc_pcie_setup_window(void __ + writel(0, bridge_base_addr + ATR0_PCIE_WIN0_SRC_ADDR); + } + +-static int mc_pcie_setup_windows(struct platform_device *pdev, +- struct plda_pcie_rp *port) ++static int plda_pcie_setup_iomems(struct platform_device *pdev, ++ struct plda_pcie_rp *port) + { + void __iomem *bridge_base_addr = port->bridge_addr; + struct pci_host_bridge *bridge = platform_get_drvdata(pdev); +@@ -888,9 +888,9 @@ static int mc_pcie_setup_windows(struct + resource_list_for_each_entry(entry, &bridge->windows) { + if (resource_type(entry->res) == IORESOURCE_MEM) { + pci_addr = entry->res->start - entry->offset; +- mc_pcie_setup_window(bridge_base_addr, index, +- entry->res->start, pci_addr, +- resource_size(entry->res)); ++ plda_pcie_setup_window(bridge_base_addr, index, ++ entry->res->start, pci_addr, ++ resource_size(entry->res)); + index++; + } + } +@@ -1023,15 +1023,15 @@ static int mc_platform_init(struct pci_c + int ret; + + /* Configure address translation table 0 for PCIe config space */ +- mc_pcie_setup_window(bridge_base_addr, 0, cfg->res.start, +- cfg->res.start, +- resource_size(&cfg->res)); ++ plda_pcie_setup_window(bridge_base_addr, 0, cfg->res.start, ++ cfg->res.start, ++ resource_size(&cfg->res)); + + /* Need some fixups in config space */ + mc_pcie_enable_msi(port, cfg->win); + + /* Configure non-config space outbound ranges */ +- ret = mc_pcie_setup_windows(pdev, &port->plda); ++ ret = plda_pcie_setup_iomems(pdev, &port->plda); + if (ret) + return ret; + diff --git a/target/linux/starfive/patches-6.6/0021-PCI-microchip-Change-the-argument-of-plda_pcie_setup.patch b/target/linux/starfive/patches-6.6/0021-PCI-microchip-Change-the-argument-of-plda_pcie_setup.patch new file mode 100644 index 0000000000..de6868b9f6 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0021-PCI-microchip-Change-the-argument-of-plda_pcie_setup.patch @@ -0,0 +1,49 @@ +From 2fd7c88ef318fd39023ce1eb73f37a29fbd25d74 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:05:58 +0800 +Subject: [PATCH 021/116] PCI: microchip: Change the argument of + plda_pcie_setup_iomems() + +If other vendor do not select PCI_HOST_COMMON, the driver data is not +struct pci_host_bridge. + +Move calling platform_get_drvdata() to mc_platform_init(). + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Reviewed-by: Conor Dooley <conor.dooley@microchip.com> +--- + drivers/pci/controller/plda/pcie-microchip-host.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -876,11 +876,10 @@ static void plda_pcie_setup_window(void + writel(0, bridge_base_addr + ATR0_PCIE_WIN0_SRC_ADDR); + } + +-static int plda_pcie_setup_iomems(struct platform_device *pdev, ++static int plda_pcie_setup_iomems(struct pci_host_bridge *bridge, + struct plda_pcie_rp *port) + { + void __iomem *bridge_base_addr = port->bridge_addr; +- struct pci_host_bridge *bridge = platform_get_drvdata(pdev); + struct resource_entry *entry; + u64 pci_addr; + u32 index = 1; +@@ -1018,6 +1017,7 @@ static int mc_platform_init(struct pci_c + { + struct device *dev = cfg->parent; + struct platform_device *pdev = to_platform_device(dev); ++ struct pci_host_bridge *bridge = platform_get_drvdata(pdev); + void __iomem *bridge_base_addr = + port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; + int ret; +@@ -1031,7 +1031,7 @@ static int mc_platform_init(struct pci_c + mc_pcie_enable_msi(port, cfg->win); + + /* Configure non-config space outbound ranges */ +- ret = plda_pcie_setup_iomems(pdev, &port->plda); ++ ret = plda_pcie_setup_iomems(bridge, &port->plda); + if (ret) + return ret; + diff --git a/target/linux/starfive/patches-6.6/0022-PCI-microchip-Move-setup-functions-to-pcie-plda-host.patch b/target/linux/starfive/patches-6.6/0022-PCI-microchip-Move-setup-functions-to-pcie-plda-host.patch new file mode 100644 index 0000000000..62f458be4a --- /dev/null +++ b/target/linux/starfive/patches-6.6/0022-PCI-microchip-Move-setup-functions-to-pcie-plda-host.patch @@ -0,0 +1,200 @@ +From 201ce99897ff5fff2612cb633406e90c1b2acbcf Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:05:59 +0800 +Subject: [PATCH 022/116] PCI: microchip: Move setup functions to + pcie-plda-host.c + +Move setup functions to common pcie-plda-host.c. So these two functions +can be re-used. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Reviewed-by: Conor Dooley <conor.dooley@microchip.com> +--- + drivers/pci/controller/plda/Kconfig | 4 + + drivers/pci/controller/plda/Makefile | 1 + + .../pci/controller/plda/pcie-microchip-host.c | 59 --------------- + drivers/pci/controller/plda/pcie-plda-host.c | 74 +++++++++++++++++++ + drivers/pci/controller/plda/pcie-plda.h | 5 ++ + 5 files changed, 84 insertions(+), 59 deletions(-) + create mode 100644 drivers/pci/controller/plda/pcie-plda-host.c + +--- a/drivers/pci/controller/plda/Kconfig ++++ b/drivers/pci/controller/plda/Kconfig +@@ -3,10 +3,14 @@ + menu "PLDA-based PCIe controllers" + depends on PCI + ++config PCIE_PLDA_HOST ++ bool ++ + config PCIE_MICROCHIP_HOST + tristate "Microchip AXI PCIe controller" + depends on PCI_MSI && OF + select PCI_HOST_COMMON ++ select PCIE_PLDA_HOST + help + Say Y here if you want kernel to support the Microchip AXI PCIe + Host Bridge driver. +--- a/drivers/pci/controller/plda/Makefile ++++ b/drivers/pci/controller/plda/Makefile +@@ -1,2 +1,3 @@ + # SPDX-License-Identifier: GPL-2.0 ++obj-$(CONFIG_PCIE_PLDA_HOST) += pcie-plda-host.o + obj-$(CONFIG_PCIE_MICROCHIP_HOST) += pcie-microchip-host.o +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -838,65 +838,6 @@ static int mc_pcie_init_irq_domains(stru + return mc_allocate_msi_domains(port); + } + +-static void plda_pcie_setup_window(void __iomem *bridge_base_addr, u32 index, +- phys_addr_t axi_addr, phys_addr_t pci_addr, +- size_t size) +-{ +- u32 atr_sz = ilog2(size) - 1; +- u32 val; +- +- if (index == 0) +- val = PCIE_CONFIG_INTERFACE; +- else +- val = PCIE_TX_RX_INTERFACE; +- +- writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + +- ATR0_AXI4_SLV0_TRSL_PARAM); +- +- val = lower_32_bits(axi_addr) | (atr_sz << ATR_SIZE_SHIFT) | +- ATR_IMPL_ENABLE; +- writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + +- ATR0_AXI4_SLV0_SRCADDR_PARAM); +- +- val = upper_32_bits(axi_addr); +- writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + +- ATR0_AXI4_SLV0_SRC_ADDR); +- +- val = lower_32_bits(pci_addr); +- writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + +- ATR0_AXI4_SLV0_TRSL_ADDR_LSB); +- +- val = upper_32_bits(pci_addr); +- writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + +- ATR0_AXI4_SLV0_TRSL_ADDR_UDW); +- +- val = readl(bridge_base_addr + ATR0_PCIE_WIN0_SRCADDR_PARAM); +- val |= (ATR0_PCIE_ATR_SIZE << ATR0_PCIE_ATR_SIZE_SHIFT); +- writel(val, bridge_base_addr + ATR0_PCIE_WIN0_SRCADDR_PARAM); +- writel(0, bridge_base_addr + ATR0_PCIE_WIN0_SRC_ADDR); +-} +- +-static int plda_pcie_setup_iomems(struct pci_host_bridge *bridge, +- struct plda_pcie_rp *port) +-{ +- void __iomem *bridge_base_addr = port->bridge_addr; +- struct resource_entry *entry; +- u64 pci_addr; +- u32 index = 1; +- +- resource_list_for_each_entry(entry, &bridge->windows) { +- if (resource_type(entry->res) == IORESOURCE_MEM) { +- pci_addr = entry->res->start - entry->offset; +- plda_pcie_setup_window(bridge_base_addr, index, +- entry->res->start, pci_addr, +- resource_size(entry->res)); +- index++; +- } +- } +- +- return 0; +-} +- + static inline void mc_clear_secs(struct mc_pcie *port) + { + void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; +--- /dev/null ++++ b/drivers/pci/controller/plda/pcie-plda-host.c +@@ -0,0 +1,74 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * PLDA PCIe XpressRich host controller driver ++ * ++ * Copyright (C) 2023 Microchip Co. Ltd ++ * ++ * Author: Daire McNamara <daire.mcnamara@microchip.com> ++ */ ++ ++#include <linux/pci_regs.h> ++#include <linux/pci-ecam.h> ++ ++#include "pcie-plda.h" ++ ++void plda_pcie_setup_window(void __iomem *bridge_base_addr, u32 index, ++ phys_addr_t axi_addr, phys_addr_t pci_addr, ++ size_t size) ++{ ++ u32 atr_sz = ilog2(size) - 1; ++ u32 val; ++ ++ if (index == 0) ++ val = PCIE_CONFIG_INTERFACE; ++ else ++ val = PCIE_TX_RX_INTERFACE; ++ ++ writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + ++ ATR0_AXI4_SLV0_TRSL_PARAM); ++ ++ val = lower_32_bits(axi_addr) | (atr_sz << ATR_SIZE_SHIFT) | ++ ATR_IMPL_ENABLE; ++ writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + ++ ATR0_AXI4_SLV0_SRCADDR_PARAM); ++ ++ val = upper_32_bits(axi_addr); ++ writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + ++ ATR0_AXI4_SLV0_SRC_ADDR); ++ ++ val = lower_32_bits(pci_addr); ++ writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + ++ ATR0_AXI4_SLV0_TRSL_ADDR_LSB); ++ ++ val = upper_32_bits(pci_addr); ++ writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) + ++ ATR0_AXI4_SLV0_TRSL_ADDR_UDW); ++ ++ val = readl(bridge_base_addr + ATR0_PCIE_WIN0_SRCADDR_PARAM); ++ val |= (ATR0_PCIE_ATR_SIZE << ATR0_PCIE_ATR_SIZE_SHIFT); ++ writel(val, bridge_base_addr + ATR0_PCIE_WIN0_SRCADDR_PARAM); ++ writel(0, bridge_base_addr + ATR0_PCIE_WIN0_SRC_ADDR); ++} ++EXPORT_SYMBOL_GPL(plda_pcie_setup_window); ++ ++int plda_pcie_setup_iomems(struct pci_host_bridge *bridge, ++ struct plda_pcie_rp *port) ++{ ++ void __iomem *bridge_base_addr = port->bridge_addr; ++ struct resource_entry *entry; ++ u64 pci_addr; ++ u32 index = 1; ++ ++ resource_list_for_each_entry(entry, &bridge->windows) { ++ if (resource_type(entry->res) == IORESOURCE_MEM) { ++ pci_addr = entry->res->start - entry->offset; ++ plda_pcie_setup_window(bridge_base_addr, index, ++ entry->res->start, pci_addr, ++ resource_size(entry->res)); ++ index++; ++ } ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(plda_pcie_setup_iomems); +--- a/drivers/pci/controller/plda/pcie-plda.h ++++ b/drivers/pci/controller/plda/pcie-plda.h +@@ -126,4 +126,9 @@ struct plda_pcie_rp { + void __iomem *bridge_addr; + }; + ++void plda_pcie_setup_window(void __iomem *bridge_base_addr, u32 index, ++ phys_addr_t axi_addr, phys_addr_t pci_addr, ++ size_t size); ++int plda_pcie_setup_iomems(struct pci_host_bridge *bridge, ++ struct plda_pcie_rp *port); + #endif diff --git a/target/linux/starfive/patches-6.6/0023-PCI-microchip-Rename-interrupt-related-functions.patch b/target/linux/starfive/patches-6.6/0023-PCI-microchip-Rename-interrupt-related-functions.patch new file mode 100644 index 0000000000..09c1633b6f --- /dev/null +++ b/target/linux/starfive/patches-6.6/0023-PCI-microchip-Rename-interrupt-related-functions.patch @@ -0,0 +1,336 @@ +From 2a48bc45dcf8cbe736b594d013cfa9d682214c43 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:06:00 +0800 +Subject: [PATCH 023/116] PCI: microchip: Rename interrupt related functions + +Rename mc_* to plda_* for IRQ functions and related IRQ domain ops data +instances. + +MSI, INTx interrupt code and IRQ init code are all can be re-used. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Acked-by: Conor Dooley <conor.dooley@microchip.com> +--- + .../pci/controller/plda/pcie-microchip-host.c | 109 +++++++++--------- + 1 file changed, 57 insertions(+), 52 deletions(-) + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -318,7 +318,7 @@ static void mc_pcie_enable_msi(struct mc + ecam + MC_MSI_CAP_CTRL_OFFSET + PCI_MSI_ADDRESS_HI); + } + +-static void mc_handle_msi(struct irq_desc *desc) ++static void plda_handle_msi(struct irq_desc *desc) + { + struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); +@@ -346,7 +346,7 @@ static void mc_handle_msi(struct irq_des + chained_irq_exit(chip, desc); + } + +-static void mc_msi_bottom_irq_ack(struct irq_data *data) ++static void plda_msi_bottom_irq_ack(struct irq_data *data) + { + struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); + void __iomem *bridge_base_addr = port->bridge_addr; +@@ -355,7 +355,7 @@ static void mc_msi_bottom_irq_ack(struct + writel_relaxed(BIT(bitpos), bridge_base_addr + ISTATUS_MSI); + } + +-static void mc_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) ++static void plda_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) + { + struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); + phys_addr_t addr = port->msi.vector_phy; +@@ -368,21 +368,23 @@ static void mc_compose_msi_msg(struct ir + (int)data->hwirq, msg->address_hi, msg->address_lo); + } + +-static int mc_msi_set_affinity(struct irq_data *irq_data, +- const struct cpumask *mask, bool force) ++static int plda_msi_set_affinity(struct irq_data *irq_data, ++ const struct cpumask *mask, bool force) + { + return -EINVAL; + } + +-static struct irq_chip mc_msi_bottom_irq_chip = { +- .name = "Microchip MSI", +- .irq_ack = mc_msi_bottom_irq_ack, +- .irq_compose_msi_msg = mc_compose_msi_msg, +- .irq_set_affinity = mc_msi_set_affinity, ++static struct irq_chip plda_msi_bottom_irq_chip = { ++ .name = "PLDA MSI", ++ .irq_ack = plda_msi_bottom_irq_ack, ++ .irq_compose_msi_msg = plda_compose_msi_msg, ++ .irq_set_affinity = plda_msi_set_affinity, + }; + +-static int mc_irq_msi_domain_alloc(struct irq_domain *domain, unsigned int virq, +- unsigned int nr_irqs, void *args) ++static int plda_irq_msi_domain_alloc(struct irq_domain *domain, ++ unsigned int virq, ++ unsigned int nr_irqs, ++ void *args) + { + struct plda_pcie_rp *port = domain->host_data; + struct plda_msi *msi = &port->msi; +@@ -397,7 +399,7 @@ static int mc_irq_msi_domain_alloc(struc + + set_bit(bit, msi->used); + +- irq_domain_set_info(domain, virq, bit, &mc_msi_bottom_irq_chip, ++ irq_domain_set_info(domain, virq, bit, &plda_msi_bottom_irq_chip, + domain->host_data, handle_edge_irq, NULL, NULL); + + mutex_unlock(&msi->lock); +@@ -405,8 +407,9 @@ static int mc_irq_msi_domain_alloc(struc + return 0; + } + +-static void mc_irq_msi_domain_free(struct irq_domain *domain, unsigned int virq, +- unsigned int nr_irqs) ++static void plda_irq_msi_domain_free(struct irq_domain *domain, ++ unsigned int virq, ++ unsigned int nr_irqs) + { + struct irq_data *d = irq_domain_get_irq_data(domain, virq); + struct plda_pcie_rp *port = irq_data_get_irq_chip_data(d); +@@ -423,24 +426,24 @@ static void mc_irq_msi_domain_free(struc + } + + static const struct irq_domain_ops msi_domain_ops = { +- .alloc = mc_irq_msi_domain_alloc, +- .free = mc_irq_msi_domain_free, ++ .alloc = plda_irq_msi_domain_alloc, ++ .free = plda_irq_msi_domain_free, + }; + +-static struct irq_chip mc_msi_irq_chip = { +- .name = "Microchip PCIe MSI", ++static struct irq_chip plda_msi_irq_chip = { ++ .name = "PLDA PCIe MSI", + .irq_ack = irq_chip_ack_parent, + .irq_mask = pci_msi_mask_irq, + .irq_unmask = pci_msi_unmask_irq, + }; + +-static struct msi_domain_info mc_msi_domain_info = { ++static struct msi_domain_info plda_msi_domain_info = { + .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | + MSI_FLAG_PCI_MSIX), +- .chip = &mc_msi_irq_chip, ++ .chip = &plda_msi_irq_chip, + }; + +-static int mc_allocate_msi_domains(struct plda_pcie_rp *port) ++static int plda_allocate_msi_domains(struct plda_pcie_rp *port) + { + struct device *dev = port->dev; + struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node); +@@ -455,7 +458,8 @@ static int mc_allocate_msi_domains(struc + return -ENOMEM; + } + +- msi->msi_domain = pci_msi_create_irq_domain(fwnode, &mc_msi_domain_info, ++ msi->msi_domain = pci_msi_create_irq_domain(fwnode, ++ &plda_msi_domain_info, + msi->dev_domain); + if (!msi->msi_domain) { + dev_err(dev, "failed to create MSI domain\n"); +@@ -466,7 +470,7 @@ static int mc_allocate_msi_domains(struc + return 0; + } + +-static void mc_handle_intx(struct irq_desc *desc) ++static void plda_handle_intx(struct irq_desc *desc) + { + struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); +@@ -493,7 +497,7 @@ static void mc_handle_intx(struct irq_de + chained_irq_exit(chip, desc); + } + +-static void mc_ack_intx_irq(struct irq_data *data) ++static void plda_ack_intx_irq(struct irq_data *data) + { + struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); + void __iomem *bridge_base_addr = port->bridge_addr; +@@ -502,7 +506,7 @@ static void mc_ack_intx_irq(struct irq_d + writel_relaxed(mask, bridge_base_addr + ISTATUS_LOCAL); + } + +-static void mc_mask_intx_irq(struct irq_data *data) ++static void plda_mask_intx_irq(struct irq_data *data) + { + struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); + void __iomem *bridge_base_addr = port->bridge_addr; +@@ -517,7 +521,7 @@ static void mc_mask_intx_irq(struct irq_ + raw_spin_unlock_irqrestore(&port->lock, flags); + } + +-static void mc_unmask_intx_irq(struct irq_data *data) ++static void plda_unmask_intx_irq(struct irq_data *data) + { + struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); + void __iomem *bridge_base_addr = port->bridge_addr; +@@ -532,24 +536,24 @@ static void mc_unmask_intx_irq(struct ir + raw_spin_unlock_irqrestore(&port->lock, flags); + } + +-static struct irq_chip mc_intx_irq_chip = { +- .name = "Microchip PCIe INTx", +- .irq_ack = mc_ack_intx_irq, +- .irq_mask = mc_mask_intx_irq, +- .irq_unmask = mc_unmask_intx_irq, ++static struct irq_chip plda_intx_irq_chip = { ++ .name = "PLDA PCIe INTx", ++ .irq_ack = plda_ack_intx_irq, ++ .irq_mask = plda_mask_intx_irq, ++ .irq_unmask = plda_unmask_intx_irq, + }; + +-static int mc_pcie_intx_map(struct irq_domain *domain, unsigned int irq, +- irq_hw_number_t hwirq) ++static int plda_pcie_intx_map(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) + { +- irq_set_chip_and_handler(irq, &mc_intx_irq_chip, handle_level_irq); ++ irq_set_chip_and_handler(irq, &plda_intx_irq_chip, handle_level_irq); + irq_set_chip_data(irq, domain->host_data); + + return 0; + } + + static const struct irq_domain_ops intx_domain_ops = { +- .map = mc_pcie_intx_map, ++ .map = plda_pcie_intx_map, + }; + + static inline u32 reg_to_event(u32 reg, struct event_map field) +@@ -609,7 +613,7 @@ static u32 local_events(struct mc_pcie * + return val; + } + +-static u32 get_events(struct plda_pcie_rp *port) ++static u32 mc_get_events(struct plda_pcie_rp *port) + { + struct mc_pcie *mc_port = container_of(port, struct mc_pcie, plda); + u32 events = 0; +@@ -638,7 +642,7 @@ static irqreturn_t mc_event_handler(int + return IRQ_HANDLED; + } + +-static void mc_handle_event(struct irq_desc *desc) ++static void plda_handle_event(struct irq_desc *desc) + { + struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); + unsigned long events; +@@ -647,7 +651,7 @@ static void mc_handle_event(struct irq_d + + chained_irq_enter(chip, desc); + +- events = get_events(port); ++ events = mc_get_events(port); + + for_each_set_bit(bit, &events, NUM_EVENTS) + generic_handle_domain_irq(port->event_domain, bit); +@@ -741,8 +745,8 @@ static struct irq_chip mc_event_irq_chip + .irq_unmask = mc_unmask_event_irq, + }; + +-static int mc_pcie_event_map(struct irq_domain *domain, unsigned int irq, +- irq_hw_number_t hwirq) ++static int plda_pcie_event_map(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) + { + irq_set_chip_and_handler(irq, &mc_event_irq_chip, handle_level_irq); + irq_set_chip_data(irq, domain->host_data); +@@ -750,8 +754,8 @@ static int mc_pcie_event_map(struct irq_ + return 0; + } + +-static const struct irq_domain_ops event_domain_ops = { +- .map = mc_pcie_event_map, ++static const struct irq_domain_ops plda_event_domain_ops = { ++ .map = plda_pcie_event_map, + }; + + static inline void mc_pcie_deinit_clk(void *data) +@@ -799,7 +803,7 @@ static int mc_pcie_init_clks(struct devi + return 0; + } + +-static int mc_pcie_init_irq_domains(struct plda_pcie_rp *port) ++static int plda_pcie_init_irq_domains(struct plda_pcie_rp *port) + { + struct device *dev = port->dev; + struct device_node *node = dev->of_node; +@@ -813,7 +817,8 @@ static int mc_pcie_init_irq_domains(stru + } + + port->event_domain = irq_domain_add_linear(pcie_intc_node, NUM_EVENTS, +- &event_domain_ops, port); ++ &plda_event_domain_ops, ++ port); + if (!port->event_domain) { + dev_err(dev, "failed to get event domain\n"); + of_node_put(pcie_intc_node); +@@ -835,7 +840,7 @@ static int mc_pcie_init_irq_domains(stru + of_node_put(pcie_intc_node); + raw_spin_lock_init(&port->lock); + +- return mc_allocate_msi_domains(port); ++ return plda_allocate_msi_domains(port); + } + + static inline void mc_clear_secs(struct mc_pcie *port) +@@ -898,14 +903,14 @@ static void mc_disable_interrupts(struct + writel_relaxed(GENMASK(31, 0), bridge_base_addr + ISTATUS_HOST); + } + +-static int mc_init_interrupts(struct platform_device *pdev, struct plda_pcie_rp *port) ++static int plda_init_interrupts(struct platform_device *pdev, struct plda_pcie_rp *port) + { + struct device *dev = &pdev->dev; + int irq; + int i, intx_irq, msi_irq, event_irq; + int ret; + +- ret = mc_pcie_init_irq_domains(port); ++ ret = plda_pcie_init_irq_domains(port); + if (ret) { + dev_err(dev, "failed creating IRQ domains\n"); + return ret; +@@ -938,7 +943,7 @@ static int mc_init_interrupts(struct pla + } + + /* Plug the INTx chained handler */ +- irq_set_chained_handler_and_data(intx_irq, mc_handle_intx, port); ++ irq_set_chained_handler_and_data(intx_irq, plda_handle_intx, port); + + msi_irq = irq_create_mapping(port->event_domain, + EVENT_LOCAL_PM_MSI_INT_MSI); +@@ -946,10 +951,10 @@ static int mc_init_interrupts(struct pla + return -ENXIO; + + /* Plug the MSI chained handler */ +- irq_set_chained_handler_and_data(msi_irq, mc_handle_msi, port); ++ irq_set_chained_handler_and_data(msi_irq, plda_handle_msi, port); + + /* Plug the main event chained handler */ +- irq_set_chained_handler_and_data(irq, mc_handle_event, port); ++ irq_set_chained_handler_and_data(irq, plda_handle_event, port); + + return 0; + } +@@ -977,7 +982,7 @@ static int mc_platform_init(struct pci_c + return ret; + + /* Address translation is up; safe to enable interrupts */ +- ret = mc_init_interrupts(pdev, &port->plda); ++ ret = plda_init_interrupts(pdev, &port->plda); + if (ret) + return ret; + diff --git a/target/linux/starfive/patches-6.6/0024-PCI-microchip-Add-num_events-field-to-struct-plda_pc.patch b/target/linux/starfive/patches-6.6/0024-PCI-microchip-Add-num_events-field-to-struct-plda_pc.patch new file mode 100644 index 0000000000..4071feb1b5 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0024-PCI-microchip-Add-num_events-field-to-struct-plda_pc.patch @@ -0,0 +1,65 @@ +From ab04dadb45a4150c9fd55b97fdd7397f4739a629 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:06:01 +0800 +Subject: [PATCH 024/116] PCI: microchip: Add num_events field to struct + plda_pcie_rp + +The number of events is different across platforms. In order to share +interrupt processing code, add a variable that defines the number of +events so that it can be set per-platform instead of hardcoding it. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Reviewed-by: Conor Dooley <conor.dooley@microchip.com> +--- + drivers/pci/controller/plda/pcie-microchip-host.c | 8 +++++--- + drivers/pci/controller/plda/pcie-plda.h | 1 + + 2 files changed, 6 insertions(+), 3 deletions(-) + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -653,7 +653,7 @@ static void plda_handle_event(struct irq + + events = mc_get_events(port); + +- for_each_set_bit(bit, &events, NUM_EVENTS) ++ for_each_set_bit(bit, &events, port->num_events) + generic_handle_domain_irq(port->event_domain, bit); + + chained_irq_exit(chip, desc); +@@ -816,7 +816,8 @@ static int plda_pcie_init_irq_domains(st + return -EINVAL; + } + +- port->event_domain = irq_domain_add_linear(pcie_intc_node, NUM_EVENTS, ++ port->event_domain = irq_domain_add_linear(pcie_intc_node, ++ port->num_events, + &plda_event_domain_ops, + port); + if (!port->event_domain) { +@@ -920,7 +921,7 @@ static int plda_init_interrupts(struct p + if (irq < 0) + return -ENODEV; + +- for (i = 0; i < NUM_EVENTS; i++) { ++ for (i = 0; i < port->num_events; i++) { + event_irq = irq_create_mapping(port->event_domain, i); + if (!event_irq) { + dev_err(dev, "failed to map hwirq %d\n", i); +@@ -1012,6 +1013,7 @@ static int mc_host_probe(struct platform + + bridge_base_addr = port->axi_base_addr + MC_PCIE_BRIDGE_ADDR; + plda->bridge_addr = bridge_base_addr; ++ plda->num_events = NUM_EVENTS; + + /* Allow enabling MSI by disabling MSI-X */ + val = readl(bridge_base_addr + PCIE_PCI_IRQ_DW0); +--- a/drivers/pci/controller/plda/pcie-plda.h ++++ b/drivers/pci/controller/plda/pcie-plda.h +@@ -124,6 +124,7 @@ struct plda_pcie_rp { + raw_spinlock_t lock; + struct plda_msi msi; + void __iomem *bridge_addr; ++ int num_events; + }; + + void plda_pcie_setup_window(void __iomem *bridge_base_addr, u32 index, diff --git a/target/linux/starfive/patches-6.6/0025-PCI-microchip-Add-request_event_irq-callback-functio.patch b/target/linux/starfive/patches-6.6/0025-PCI-microchip-Add-request_event_irq-callback-functio.patch new file mode 100644 index 0000000000..2b299e01e9 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0025-PCI-microchip-Add-request_event_irq-callback-functio.patch @@ -0,0 +1,114 @@ +From 9f202f211cc79eefecbb03715c884e54eb95a62c Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:06:02 +0800 +Subject: [PATCH 025/116] PCI: microchip: Add request_event_irq() callback + function + +As PLDA dts binding doc(Documentation/devicetree/bindings/pci/ +plda,xpressrich3-axi-common.yaml) showes, PLDA PCIe contains an interrupt +controller. Microchip Polarfire PCIe add some PCIe interrupts base on +PLDA IP interrupt controller. + +Microchip Polarfire PCIe additional intrerrupts: +EVENT_PCIE_L2_EXIT +EVENT_PCIE_HOTRST_EXIT +EVENT_PCIE_DLUP_EXIT +EVENT_SEC_TX_RAM_SEC_ERR +EVENT_SEC_RX_RAM_SEC_ERR +.... + +Both codes of register interrupts and mc_event_handler() contain +additional interrupts symbol names, these can not be re-used. So add a +new plda_event_handler() functions, which implements PLDA interrupt +defalt handler. Add request_event_irq() callback function to +compat Microchip Polorfire PCIe additional interrupts. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Acked-by: Conor Dooley <conor.dooley@microchip.com> +--- + .../pci/controller/plda/pcie-microchip-host.c | 31 ++++++++++++++++--- + drivers/pci/controller/plda/pcie-plda.h | 5 +++ + 2 files changed, 32 insertions(+), 4 deletions(-) + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -642,6 +642,11 @@ static irqreturn_t mc_event_handler(int + return IRQ_HANDLED; + } + ++static irqreturn_t plda_event_handler(int irq, void *dev_id) ++{ ++ return IRQ_HANDLED; ++} ++ + static void plda_handle_event(struct irq_desc *desc) + { + struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); +@@ -803,6 +808,17 @@ static int mc_pcie_init_clks(struct devi + return 0; + } + ++static int mc_request_event_irq(struct plda_pcie_rp *plda, int event_irq, ++ int event) ++{ ++ return devm_request_irq(plda->dev, event_irq, mc_event_handler, ++ 0, event_cause[event].sym, plda); ++} ++ ++static const struct plda_event mc_event = { ++ .request_event_irq = mc_request_event_irq, ++}; ++ + static int plda_pcie_init_irq_domains(struct plda_pcie_rp *port) + { + struct device *dev = port->dev; +@@ -904,7 +920,9 @@ static void mc_disable_interrupts(struct + writel_relaxed(GENMASK(31, 0), bridge_base_addr + ISTATUS_HOST); + } + +-static int plda_init_interrupts(struct platform_device *pdev, struct plda_pcie_rp *port) ++static int plda_init_interrupts(struct platform_device *pdev, ++ struct plda_pcie_rp *port, ++ const struct plda_event *event) + { + struct device *dev = &pdev->dev; + int irq; +@@ -928,8 +946,13 @@ static int plda_init_interrupts(struct p + return -ENXIO; + } + +- ret = devm_request_irq(dev, event_irq, mc_event_handler, +- 0, event_cause[i].sym, port); ++ if (event->request_event_irq) ++ ret = event->request_event_irq(port, event_irq, i); ++ else ++ ret = devm_request_irq(dev, event_irq, ++ plda_event_handler, ++ 0, NULL, port); ++ + if (ret) { + dev_err(dev, "failed to request IRQ %d\n", event_irq); + return ret; +@@ -983,7 +1006,7 @@ static int mc_platform_init(struct pci_c + return ret; + + /* Address translation is up; safe to enable interrupts */ +- ret = plda_init_interrupts(pdev, &port->plda); ++ ret = plda_init_interrupts(pdev, &port->plda, &mc_event); + if (ret) + return ret; + +--- a/drivers/pci/controller/plda/pcie-plda.h ++++ b/drivers/pci/controller/plda/pcie-plda.h +@@ -127,6 +127,11 @@ struct plda_pcie_rp { + int num_events; + }; + ++struct plda_event { ++ int (*request_event_irq)(struct plda_pcie_rp *pcie, ++ int event_irq, int event); ++}; ++ + void plda_pcie_setup_window(void __iomem *bridge_base_addr, u32 index, + phys_addr_t axi_addr, phys_addr_t pci_addr, + size_t size); diff --git a/target/linux/starfive/patches-6.6/0026-PCI-microchip-Add-INTx-and-MSI-event-num-to-struct-p.patch b/target/linux/starfive/patches-6.6/0026-PCI-microchip-Add-INTx-and-MSI-event-num-to-struct-p.patch new file mode 100644 index 0000000000..85b8143268 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0026-PCI-microchip-Add-INTx-and-MSI-event-num-to-struct-p.patch @@ -0,0 +1,56 @@ +From 3cdc20d9cc029ba9444be111bf4e55fd5331ccbe Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:06:03 +0800 +Subject: [PATCH 026/116] PCI: microchip: Add INTx and MSI event num to struct + plda_event + +The INTx and MSI interrupt event num is different in Microchip and +StarFive platform. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Acked-by: Conor Dooley <conor.dooley@microchip.com> +--- + drivers/pci/controller/plda/pcie-microchip-host.c | 6 ++++-- + drivers/pci/controller/plda/pcie-plda.h | 2 ++ + 2 files changed, 6 insertions(+), 2 deletions(-) + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -817,6 +817,8 @@ static int mc_request_event_irq(struct p + + static const struct plda_event mc_event = { + .request_event_irq = mc_request_event_irq, ++ .intx_event = EVENT_LOCAL_PM_MSI_INT_INTX, ++ .msi_event = EVENT_LOCAL_PM_MSI_INT_MSI, + }; + + static int plda_pcie_init_irq_domains(struct plda_pcie_rp *port) +@@ -960,7 +962,7 @@ static int plda_init_interrupts(struct p + } + + intx_irq = irq_create_mapping(port->event_domain, +- EVENT_LOCAL_PM_MSI_INT_INTX); ++ event->intx_event); + if (!intx_irq) { + dev_err(dev, "failed to map INTx interrupt\n"); + return -ENXIO; +@@ -970,7 +972,7 @@ static int plda_init_interrupts(struct p + irq_set_chained_handler_and_data(intx_irq, plda_handle_intx, port); + + msi_irq = irq_create_mapping(port->event_domain, +- EVENT_LOCAL_PM_MSI_INT_MSI); ++ event->msi_event); + if (!msi_irq) + return -ENXIO; + +--- a/drivers/pci/controller/plda/pcie-plda.h ++++ b/drivers/pci/controller/plda/pcie-plda.h +@@ -130,6 +130,8 @@ struct plda_pcie_rp { + struct plda_event { + int (*request_event_irq)(struct plda_pcie_rp *pcie, + int event_irq, int event); ++ int intx_event; ++ int msi_event; + }; + + void plda_pcie_setup_window(void __iomem *bridge_base_addr, u32 index, diff --git a/target/linux/starfive/patches-6.6/0027-PCI-microchip-Add-get_events-callback-and-add-PLDA-g.patch b/target/linux/starfive/patches-6.6/0027-PCI-microchip-Add-get_events-callback-and-add-PLDA-g.patch new file mode 100644 index 0000000000..a0cb4789b8 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0027-PCI-microchip-Add-get_events-callback-and-add-PLDA-g.patch @@ -0,0 +1,167 @@ +From b4a38ef87661f21fe2fb3e085ae98f25f78aaf99 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:06:04 +0800 +Subject: [PATCH 027/116] PCI: microchip: Add get_events() callback and add + PLDA get_event() + +As PLDA dts binding doc(Documentation/devicetree/bindings/pci/ +plda,xpressrich3-axi-common.yaml) showes, PLDA PCIe contains an interrupt +controller. + +PolarFire implements its own PCIe interrupts, additional to the regular +PCIe interrupts, due to lack of an MSI controller, so the interrupt to +event number mapping is different to the PLDA regular interrupts, +necessitating a custom get_events() implementation. + +Microchip Polarfire PCIe additional intrerrupts: +EVENT_PCIE_L2_EXIT +EVENT_PCIE_HOTRST_EXIT +EVENT_PCIE_DLUP_EXIT +EVENT_SEC_TX_RAM_SEC_ERR +EVENT_SEC_RX_RAM_SEC_ERR +.... + +plda_get_events() adds interrupt register to PLDA local event num mapping +codes. All The PLDA interrupts can be seen in new added graph. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Acked-by: Conor Dooley <conor.dooley@microchip.com> +--- + .../pci/controller/plda/pcie-microchip-host.c | 35 ++++++++++++++++++- + drivers/pci/controller/plda/pcie-plda.h | 32 +++++++++++++++++ + 2 files changed, 66 insertions(+), 1 deletion(-) + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -626,6 +626,26 @@ static u32 mc_get_events(struct plda_pci + return events; + } + ++static u32 plda_get_events(struct plda_pcie_rp *port) ++{ ++ u32 events, val, origin; ++ ++ origin = readl_relaxed(port->bridge_addr + ISTATUS_LOCAL); ++ ++ /* MSI event and sys events */ ++ val = (origin & SYS_AND_MSI_MASK) >> PM_MSI_INT_MSI_SHIFT; ++ events = val << (PM_MSI_INT_MSI_SHIFT - PCI_NUM_INTX + 1); ++ ++ /* INTx events */ ++ if (origin & PM_MSI_INT_INTX_MASK) ++ events |= BIT(PM_MSI_INT_INTX_SHIFT); ++ ++ /* remains are same with register */ ++ events |= origin & GENMASK(P_ATR_EVT_DOORBELL_SHIFT, 0); ++ ++ return events; ++} ++ + static irqreturn_t mc_event_handler(int irq, void *dev_id) + { + struct plda_pcie_rp *port = dev_id; +@@ -656,7 +676,7 @@ static void plda_handle_event(struct irq + + chained_irq_enter(chip, desc); + +- events = mc_get_events(port); ++ events = port->event_ops->get_events(port); + + for_each_set_bit(bit, &events, port->num_events) + generic_handle_domain_irq(port->event_domain, bit); +@@ -750,6 +770,10 @@ static struct irq_chip mc_event_irq_chip + .irq_unmask = mc_unmask_event_irq, + }; + ++static const struct plda_event_ops plda_event_ops = { ++ .get_events = plda_get_events, ++}; ++ + static int plda_pcie_event_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) + { +@@ -815,6 +839,10 @@ static int mc_request_event_irq(struct p + 0, event_cause[event].sym, plda); + } + ++static const struct plda_event_ops mc_event_ops = { ++ .get_events = mc_get_events, ++}; ++ + static const struct plda_event mc_event = { + .request_event_irq = mc_request_event_irq, + .intx_event = EVENT_LOCAL_PM_MSI_INT_INTX, +@@ -931,6 +959,9 @@ static int plda_init_interrupts(struct p + int i, intx_irq, msi_irq, event_irq; + int ret; + ++ if (!port->event_ops) ++ port->event_ops = &plda_event_ops; ++ + ret = plda_pcie_init_irq_domains(port); + if (ret) { + dev_err(dev, "failed creating IRQ domains\n"); +@@ -1007,6 +1038,8 @@ static int mc_platform_init(struct pci_c + if (ret) + return ret; + ++ port->plda.event_ops = &mc_event_ops; ++ + /* Address translation is up; safe to enable interrupts */ + ret = plda_init_interrupts(pdev, &port->plda, &mc_event); + if (ret) +--- a/drivers/pci/controller/plda/pcie-plda.h ++++ b/drivers/pci/controller/plda/pcie-plda.h +@@ -58,6 +58,7 @@ + #define PM_MSI_INT_EVENTS_SHIFT 30 + #define PM_MSI_INT_SYS_ERR_MASK 0x80000000u + #define PM_MSI_INT_SYS_ERR_SHIFT 31 ++#define SYS_AND_MSI_MASK GENMASK(31, 28) + #define NUM_LOCAL_EVENTS 15 + #define ISTATUS_LOCAL 0x184 + #define IMASK_HOST 0x188 +@@ -108,6 +109,36 @@ enum plda_int_event { + + #define PLDA_MAX_INT_NUM (PLDA_NUM_DMA_EVENTS + PLDA_INT_EVENT_NUM) + ++/* ++ * PLDA interrupt register ++ * ++ * 31 27 23 15 7 0 ++ * +--+--+--+-+------+-+-+-+-+-+-+-+-+-----------+-----------+ ++ * |12|11|10|9| intx |7|6|5|4|3|2|1|0| DMA error | DMA end | ++ * +--+--+--+-+------+-+-+-+-+-+-+-+-+-----------+-----------+ ++ * bit 0-7 DMA interrupt end : reserved for vendor implement ++ * bit 8-15 DMA error : reserved for vendor implement ++ * 0: AXI post error (PLDA_AXI_POST_ERR) ++ * 1: AXI fetch error (PLDA_AXI_FETCH_ERR) ++ * 2: AXI discard error (PLDA_AXI_DISCARD_ERR) ++ * 3: AXI doorbell (PLDA_PCIE_DOORBELL) ++ * 4: PCIe post error (PLDA_PCIE_POST_ERR) ++ * 5: PCIe fetch error (PLDA_PCIE_FETCH_ERR) ++ * 6: PCIe discard error (PLDA_PCIE_DISCARD_ERR) ++ * 7: PCIe doorbell (PLDA_PCIE_DOORBELL) ++ * 8: 4 INTx interruts (PLDA_INTX) ++ * 9: MSI interrupt (PLDA_MSI) ++ * 10: AER event (PLDA_AER_EVENT) ++ * 11: PM/LTR/Hotplug (PLDA_MISC_EVENTS) ++ * 12: System error (PLDA_SYS_ERR) ++ */ ++ ++struct plda_pcie_rp; ++ ++struct plda_event_ops { ++ u32 (*get_events)(struct plda_pcie_rp *pcie); ++}; ++ + struct plda_msi { + struct mutex lock; /* Protect used bitmap */ + struct irq_domain *msi_domain; +@@ -123,6 +154,7 @@ struct plda_pcie_rp { + struct irq_domain *event_domain; + raw_spinlock_t lock; + struct plda_msi msi; ++ const struct plda_event_ops *event_ops; + void __iomem *bridge_addr; + int num_events; + }; diff --git a/target/linux/starfive/patches-6.6/0028-PCI-microchip-Add-event-irqchip-field-to-host-port-a.patch b/target/linux/starfive/patches-6.6/0028-PCI-microchip-Add-event-irqchip-field-to-host-port-a.patch new file mode 100644 index 0000000000..9ed8119b1e --- /dev/null +++ b/target/linux/starfive/patches-6.6/0028-PCI-microchip-Add-event-irqchip-field-to-host-port-a.patch @@ -0,0 +1,144 @@ +From 229ea8e7b674eb5c9bc4f70d43df1bd02a79862a Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:06:05 +0800 +Subject: [PATCH 028/116] PCI: microchip: Add event irqchip field to host port + and add PLDA irqchip + +As PLDA dts binding doc(Documentation/devicetree/bindings/pci/ +plda,xpressrich3-axi-common.yaml) showes, PLDA PCIe contains an interrupt +controller. + +Microchip PolarFire PCIE event IRQs includes PLDA interrupts and +Polarfire their own interrupts. The interrupt irqchip ops includes +ack/mask/unmask interrupt ops, which will write correct registers. +Microchip Polarfire PCIe additional interrupts require to write Polarfire +SoC self-defined registers. So Microchip PCIe event irqchip ops can not +be re-used. + +To support PLDA its own event IRQ process, implements PLDA irqchip ops and +add event irqchip field to struct pcie_plda_rp. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Acked-by: Conor Dooley <conor.dooley@microchip.com> +--- + .../pci/controller/plda/pcie-microchip-host.c | 66 ++++++++++++++++++- + drivers/pci/controller/plda/pcie-plda.h | 5 +- + 2 files changed, 69 insertions(+), 2 deletions(-) + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -770,6 +770,64 @@ static struct irq_chip mc_event_irq_chip + .irq_unmask = mc_unmask_event_irq, + }; + ++static u32 plda_hwirq_to_mask(int hwirq) ++{ ++ u32 mask; ++ ++ /* hwirq 23 - 0 are the same with register */ ++ if (hwirq < EVENT_PM_MSI_INT_INTX) ++ mask = BIT(hwirq); ++ else if (hwirq == EVENT_PM_MSI_INT_INTX) ++ mask = PM_MSI_INT_INTX_MASK; ++ else ++ mask = BIT(hwirq + PCI_NUM_INTX - 1); ++ ++ return mask; ++} ++ ++static void plda_ack_event_irq(struct irq_data *data) ++{ ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ ++ writel_relaxed(plda_hwirq_to_mask(data->hwirq), ++ port->bridge_addr + ISTATUS_LOCAL); ++} ++ ++static void plda_mask_event_irq(struct irq_data *data) ++{ ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ u32 mask, val; ++ ++ mask = plda_hwirq_to_mask(data->hwirq); ++ ++ raw_spin_lock(&port->lock); ++ val = readl_relaxed(port->bridge_addr + IMASK_LOCAL); ++ val &= ~mask; ++ writel_relaxed(val, port->bridge_addr + IMASK_LOCAL); ++ raw_spin_unlock(&port->lock); ++} ++ ++static void plda_unmask_event_irq(struct irq_data *data) ++{ ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ u32 mask, val; ++ ++ mask = plda_hwirq_to_mask(data->hwirq); ++ ++ raw_spin_lock(&port->lock); ++ val = readl_relaxed(port->bridge_addr + IMASK_LOCAL); ++ val |= mask; ++ writel_relaxed(val, port->bridge_addr + IMASK_LOCAL); ++ raw_spin_unlock(&port->lock); ++} ++ ++static struct irq_chip plda_event_irq_chip = { ++ .name = "PLDA PCIe EVENT", ++ .irq_ack = plda_ack_event_irq, ++ .irq_mask = plda_mask_event_irq, ++ .irq_unmask = plda_unmask_event_irq, ++}; ++ + static const struct plda_event_ops plda_event_ops = { + .get_events = plda_get_events, + }; +@@ -777,7 +835,9 @@ static const struct plda_event_ops plda_ + static int plda_pcie_event_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) + { +- irq_set_chip_and_handler(irq, &mc_event_irq_chip, handle_level_irq); ++ struct plda_pcie_rp *port = (void *)domain->host_data; ++ ++ irq_set_chip_and_handler(irq, port->event_irq_chip, handle_level_irq); + irq_set_chip_data(irq, domain->host_data); + + return 0; +@@ -962,6 +1022,9 @@ static int plda_init_interrupts(struct p + if (!port->event_ops) + port->event_ops = &plda_event_ops; + ++ if (!port->event_irq_chip) ++ port->event_irq_chip = &plda_event_irq_chip; ++ + ret = plda_pcie_init_irq_domains(port); + if (ret) { + dev_err(dev, "failed creating IRQ domains\n"); +@@ -1039,6 +1102,7 @@ static int mc_platform_init(struct pci_c + return ret; + + port->plda.event_ops = &mc_event_ops; ++ port->plda.event_irq_chip = &mc_event_irq_chip; + + /* Address translation is up; safe to enable interrupts */ + ret = plda_init_interrupts(pdev, &port->plda, &mc_event); +--- a/drivers/pci/controller/plda/pcie-plda.h ++++ b/drivers/pci/controller/plda/pcie-plda.h +@@ -107,7 +107,9 @@ enum plda_int_event { + + #define PLDA_NUM_DMA_EVENTS 16 + +-#define PLDA_MAX_INT_NUM (PLDA_NUM_DMA_EVENTS + PLDA_INT_EVENT_NUM) ++#define EVENT_PM_MSI_INT_INTX (PLDA_NUM_DMA_EVENTS + PLDA_INTX) ++#define EVENT_PM_MSI_INT_MSI (PLDA_NUM_DMA_EVENTS + PLDA_MSI) ++#define PLDA_MAX_EVENT_NUM (PLDA_NUM_DMA_EVENTS + PLDA_INT_EVENT_NUM) + + /* + * PLDA interrupt register +@@ -155,6 +157,7 @@ struct plda_pcie_rp { + raw_spinlock_t lock; + struct plda_msi msi; + const struct plda_event_ops *event_ops; ++ const struct irq_chip *event_irq_chip; + void __iomem *bridge_addr; + int num_events; + }; diff --git a/target/linux/starfive/patches-6.6/0029-PCI-microchip-Move-IRQ-functions-to-pcie-plda-host.c.patch b/target/linux/starfive/patches-6.6/0029-PCI-microchip-Move-IRQ-functions-to-pcie-plda-host.c.patch new file mode 100644 index 0000000000..840e334d20 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0029-PCI-microchip-Move-IRQ-functions-to-pcie-plda-host.c.patch @@ -0,0 +1,1028 @@ +From 6be452d8e61594790ae57b282a612ec0df473e61 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:06:06 +0800 +Subject: [PATCH 029/116] PCI: microchip: Move IRQ functions to + pcie-plda-host.c + +Move IRQ related functions to pcie-plda-host.c for re-use these codes. +Now Refactoring codes complete. + +Including MSI, INTx, event interrupts and IRQ init functions. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Acked-by: Conor Dooley <conor.dooley@microchip.com> +--- + .../pci/controller/plda/pcie-microchip-host.c | 467 ----------------- + drivers/pci/controller/plda/pcie-plda-host.c | 472 ++++++++++++++++++ + drivers/pci/controller/plda/pcie-plda.h | 3 + + 3 files changed, 475 insertions(+), 467 deletions(-) + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -318,244 +318,6 @@ static void mc_pcie_enable_msi(struct mc + ecam + MC_MSI_CAP_CTRL_OFFSET + PCI_MSI_ADDRESS_HI); + } + +-static void plda_handle_msi(struct irq_desc *desc) +-{ +- struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); +- struct irq_chip *chip = irq_desc_get_chip(desc); +- struct device *dev = port->dev; +- struct plda_msi *msi = &port->msi; +- void __iomem *bridge_base_addr = port->bridge_addr; +- unsigned long status; +- u32 bit; +- int ret; +- +- chained_irq_enter(chip, desc); +- +- status = readl_relaxed(bridge_base_addr + ISTATUS_LOCAL); +- if (status & PM_MSI_INT_MSI_MASK) { +- writel_relaxed(status & PM_MSI_INT_MSI_MASK, bridge_base_addr + ISTATUS_LOCAL); +- status = readl_relaxed(bridge_base_addr + ISTATUS_MSI); +- for_each_set_bit(bit, &status, msi->num_vectors) { +- ret = generic_handle_domain_irq(msi->dev_domain, bit); +- if (ret) +- dev_err_ratelimited(dev, "bad MSI IRQ %d\n", +- bit); +- } +- } +- +- chained_irq_exit(chip, desc); +-} +- +-static void plda_msi_bottom_irq_ack(struct irq_data *data) +-{ +- struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); +- void __iomem *bridge_base_addr = port->bridge_addr; +- u32 bitpos = data->hwirq; +- +- writel_relaxed(BIT(bitpos), bridge_base_addr + ISTATUS_MSI); +-} +- +-static void plda_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) +-{ +- struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); +- phys_addr_t addr = port->msi.vector_phy; +- +- msg->address_lo = lower_32_bits(addr); +- msg->address_hi = upper_32_bits(addr); +- msg->data = data->hwirq; +- +- dev_dbg(port->dev, "msi#%x address_hi %#x address_lo %#x\n", +- (int)data->hwirq, msg->address_hi, msg->address_lo); +-} +- +-static int plda_msi_set_affinity(struct irq_data *irq_data, +- const struct cpumask *mask, bool force) +-{ +- return -EINVAL; +-} +- +-static struct irq_chip plda_msi_bottom_irq_chip = { +- .name = "PLDA MSI", +- .irq_ack = plda_msi_bottom_irq_ack, +- .irq_compose_msi_msg = plda_compose_msi_msg, +- .irq_set_affinity = plda_msi_set_affinity, +-}; +- +-static int plda_irq_msi_domain_alloc(struct irq_domain *domain, +- unsigned int virq, +- unsigned int nr_irqs, +- void *args) +-{ +- struct plda_pcie_rp *port = domain->host_data; +- struct plda_msi *msi = &port->msi; +- unsigned long bit; +- +- mutex_lock(&msi->lock); +- bit = find_first_zero_bit(msi->used, msi->num_vectors); +- if (bit >= msi->num_vectors) { +- mutex_unlock(&msi->lock); +- return -ENOSPC; +- } +- +- set_bit(bit, msi->used); +- +- irq_domain_set_info(domain, virq, bit, &plda_msi_bottom_irq_chip, +- domain->host_data, handle_edge_irq, NULL, NULL); +- +- mutex_unlock(&msi->lock); +- +- return 0; +-} +- +-static void plda_irq_msi_domain_free(struct irq_domain *domain, +- unsigned int virq, +- unsigned int nr_irqs) +-{ +- struct irq_data *d = irq_domain_get_irq_data(domain, virq); +- struct plda_pcie_rp *port = irq_data_get_irq_chip_data(d); +- struct plda_msi *msi = &port->msi; +- +- mutex_lock(&msi->lock); +- +- if (test_bit(d->hwirq, msi->used)) +- __clear_bit(d->hwirq, msi->used); +- else +- dev_err(port->dev, "trying to free unused MSI%lu\n", d->hwirq); +- +- mutex_unlock(&msi->lock); +-} +- +-static const struct irq_domain_ops msi_domain_ops = { +- .alloc = plda_irq_msi_domain_alloc, +- .free = plda_irq_msi_domain_free, +-}; +- +-static struct irq_chip plda_msi_irq_chip = { +- .name = "PLDA PCIe MSI", +- .irq_ack = irq_chip_ack_parent, +- .irq_mask = pci_msi_mask_irq, +- .irq_unmask = pci_msi_unmask_irq, +-}; +- +-static struct msi_domain_info plda_msi_domain_info = { +- .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | +- MSI_FLAG_PCI_MSIX), +- .chip = &plda_msi_irq_chip, +-}; +- +-static int plda_allocate_msi_domains(struct plda_pcie_rp *port) +-{ +- struct device *dev = port->dev; +- struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node); +- struct plda_msi *msi = &port->msi; +- +- mutex_init(&port->msi.lock); +- +- msi->dev_domain = irq_domain_add_linear(NULL, msi->num_vectors, +- &msi_domain_ops, port); +- if (!msi->dev_domain) { +- dev_err(dev, "failed to create IRQ domain\n"); +- return -ENOMEM; +- } +- +- msi->msi_domain = pci_msi_create_irq_domain(fwnode, +- &plda_msi_domain_info, +- msi->dev_domain); +- if (!msi->msi_domain) { +- dev_err(dev, "failed to create MSI domain\n"); +- irq_domain_remove(msi->dev_domain); +- return -ENOMEM; +- } +- +- return 0; +-} +- +-static void plda_handle_intx(struct irq_desc *desc) +-{ +- struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); +- struct irq_chip *chip = irq_desc_get_chip(desc); +- struct device *dev = port->dev; +- void __iomem *bridge_base_addr = port->bridge_addr; +- unsigned long status; +- u32 bit; +- int ret; +- +- chained_irq_enter(chip, desc); +- +- status = readl_relaxed(bridge_base_addr + ISTATUS_LOCAL); +- if (status & PM_MSI_INT_INTX_MASK) { +- status &= PM_MSI_INT_INTX_MASK; +- status >>= PM_MSI_INT_INTX_SHIFT; +- for_each_set_bit(bit, &status, PCI_NUM_INTX) { +- ret = generic_handle_domain_irq(port->intx_domain, bit); +- if (ret) +- dev_err_ratelimited(dev, "bad INTx IRQ %d\n", +- bit); +- } +- } +- +- chained_irq_exit(chip, desc); +-} +- +-static void plda_ack_intx_irq(struct irq_data *data) +-{ +- struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); +- void __iomem *bridge_base_addr = port->bridge_addr; +- u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); +- +- writel_relaxed(mask, bridge_base_addr + ISTATUS_LOCAL); +-} +- +-static void plda_mask_intx_irq(struct irq_data *data) +-{ +- struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); +- void __iomem *bridge_base_addr = port->bridge_addr; +- unsigned long flags; +- u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); +- u32 val; +- +- raw_spin_lock_irqsave(&port->lock, flags); +- val = readl_relaxed(bridge_base_addr + IMASK_LOCAL); +- val &= ~mask; +- writel_relaxed(val, bridge_base_addr + IMASK_LOCAL); +- raw_spin_unlock_irqrestore(&port->lock, flags); +-} +- +-static void plda_unmask_intx_irq(struct irq_data *data) +-{ +- struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); +- void __iomem *bridge_base_addr = port->bridge_addr; +- unsigned long flags; +- u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); +- u32 val; +- +- raw_spin_lock_irqsave(&port->lock, flags); +- val = readl_relaxed(bridge_base_addr + IMASK_LOCAL); +- val |= mask; +- writel_relaxed(val, bridge_base_addr + IMASK_LOCAL); +- raw_spin_unlock_irqrestore(&port->lock, flags); +-} +- +-static struct irq_chip plda_intx_irq_chip = { +- .name = "PLDA PCIe INTx", +- .irq_ack = plda_ack_intx_irq, +- .irq_mask = plda_mask_intx_irq, +- .irq_unmask = plda_unmask_intx_irq, +-}; +- +-static int plda_pcie_intx_map(struct irq_domain *domain, unsigned int irq, +- irq_hw_number_t hwirq) +-{ +- irq_set_chip_and_handler(irq, &plda_intx_irq_chip, handle_level_irq); +- irq_set_chip_data(irq, domain->host_data); +- +- return 0; +-} +- +-static const struct irq_domain_ops intx_domain_ops = { +- .map = plda_pcie_intx_map, +-}; +- + static inline u32 reg_to_event(u32 reg, struct event_map field) + { + return (reg & field.reg_mask) ? BIT(field.event_bit) : 0; +@@ -626,26 +388,6 @@ static u32 mc_get_events(struct plda_pci + return events; + } + +-static u32 plda_get_events(struct plda_pcie_rp *port) +-{ +- u32 events, val, origin; +- +- origin = readl_relaxed(port->bridge_addr + ISTATUS_LOCAL); +- +- /* MSI event and sys events */ +- val = (origin & SYS_AND_MSI_MASK) >> PM_MSI_INT_MSI_SHIFT; +- events = val << (PM_MSI_INT_MSI_SHIFT - PCI_NUM_INTX + 1); +- +- /* INTx events */ +- if (origin & PM_MSI_INT_INTX_MASK) +- events |= BIT(PM_MSI_INT_INTX_SHIFT); +- +- /* remains are same with register */ +- events |= origin & GENMASK(P_ATR_EVT_DOORBELL_SHIFT, 0); +- +- return events; +-} +- + static irqreturn_t mc_event_handler(int irq, void *dev_id) + { + struct plda_pcie_rp *port = dev_id; +@@ -662,28 +404,6 @@ static irqreturn_t mc_event_handler(int + return IRQ_HANDLED; + } + +-static irqreturn_t plda_event_handler(int irq, void *dev_id) +-{ +- return IRQ_HANDLED; +-} +- +-static void plda_handle_event(struct irq_desc *desc) +-{ +- struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); +- unsigned long events; +- u32 bit; +- struct irq_chip *chip = irq_desc_get_chip(desc); +- +- chained_irq_enter(chip, desc); +- +- events = port->event_ops->get_events(port); +- +- for_each_set_bit(bit, &events, port->num_events) +- generic_handle_domain_irq(port->event_domain, bit); +- +- chained_irq_exit(chip, desc); +-} +- + static void mc_ack_event_irq(struct irq_data *data) + { + struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); +@@ -770,83 +490,6 @@ static struct irq_chip mc_event_irq_chip + .irq_unmask = mc_unmask_event_irq, + }; + +-static u32 plda_hwirq_to_mask(int hwirq) +-{ +- u32 mask; +- +- /* hwirq 23 - 0 are the same with register */ +- if (hwirq < EVENT_PM_MSI_INT_INTX) +- mask = BIT(hwirq); +- else if (hwirq == EVENT_PM_MSI_INT_INTX) +- mask = PM_MSI_INT_INTX_MASK; +- else +- mask = BIT(hwirq + PCI_NUM_INTX - 1); +- +- return mask; +-} +- +-static void plda_ack_event_irq(struct irq_data *data) +-{ +- struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); +- +- writel_relaxed(plda_hwirq_to_mask(data->hwirq), +- port->bridge_addr + ISTATUS_LOCAL); +-} +- +-static void plda_mask_event_irq(struct irq_data *data) +-{ +- struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); +- u32 mask, val; +- +- mask = plda_hwirq_to_mask(data->hwirq); +- +- raw_spin_lock(&port->lock); +- val = readl_relaxed(port->bridge_addr + IMASK_LOCAL); +- val &= ~mask; +- writel_relaxed(val, port->bridge_addr + IMASK_LOCAL); +- raw_spin_unlock(&port->lock); +-} +- +-static void plda_unmask_event_irq(struct irq_data *data) +-{ +- struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); +- u32 mask, val; +- +- mask = plda_hwirq_to_mask(data->hwirq); +- +- raw_spin_lock(&port->lock); +- val = readl_relaxed(port->bridge_addr + IMASK_LOCAL); +- val |= mask; +- writel_relaxed(val, port->bridge_addr + IMASK_LOCAL); +- raw_spin_unlock(&port->lock); +-} +- +-static struct irq_chip plda_event_irq_chip = { +- .name = "PLDA PCIe EVENT", +- .irq_ack = plda_ack_event_irq, +- .irq_mask = plda_mask_event_irq, +- .irq_unmask = plda_unmask_event_irq, +-}; +- +-static const struct plda_event_ops plda_event_ops = { +- .get_events = plda_get_events, +-}; +- +-static int plda_pcie_event_map(struct irq_domain *domain, unsigned int irq, +- irq_hw_number_t hwirq) +-{ +- struct plda_pcie_rp *port = (void *)domain->host_data; +- +- irq_set_chip_and_handler(irq, port->event_irq_chip, handle_level_irq); +- irq_set_chip_data(irq, domain->host_data); +- +- return 0; +-} +- +-static const struct irq_domain_ops plda_event_domain_ops = { +- .map = plda_pcie_event_map, +-}; +- + static inline void mc_pcie_deinit_clk(void *data) + { + struct clk *clk = data; +@@ -909,47 +552,6 @@ static const struct plda_event mc_event + .msi_event = EVENT_LOCAL_PM_MSI_INT_MSI, + }; + +-static int plda_pcie_init_irq_domains(struct plda_pcie_rp *port) +-{ +- struct device *dev = port->dev; +- struct device_node *node = dev->of_node; +- struct device_node *pcie_intc_node; +- +- /* Setup INTx */ +- pcie_intc_node = of_get_next_child(node, NULL); +- if (!pcie_intc_node) { +- dev_err(dev, "failed to find PCIe Intc node\n"); +- return -EINVAL; +- } +- +- port->event_domain = irq_domain_add_linear(pcie_intc_node, +- port->num_events, +- &plda_event_domain_ops, +- port); +- if (!port->event_domain) { +- dev_err(dev, "failed to get event domain\n"); +- of_node_put(pcie_intc_node); +- return -ENOMEM; +- } +- +- irq_domain_update_bus_token(port->event_domain, DOMAIN_BUS_NEXUS); +- +- port->intx_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX, +- &intx_domain_ops, port); +- if (!port->intx_domain) { +- dev_err(dev, "failed to get an INTx IRQ domain\n"); +- of_node_put(pcie_intc_node); +- return -ENOMEM; +- } +- +- irq_domain_update_bus_token(port->intx_domain, DOMAIN_BUS_WIRED); +- +- of_node_put(pcie_intc_node); +- raw_spin_lock_init(&port->lock); +- +- return plda_allocate_msi_domains(port); +-} +- + static inline void mc_clear_secs(struct mc_pcie *port) + { + void __iomem *ctrl_base_addr = port->axi_base_addr + MC_PCIE_CTRL_ADDR; +@@ -1010,75 +612,6 @@ static void mc_disable_interrupts(struct + writel_relaxed(GENMASK(31, 0), bridge_base_addr + ISTATUS_HOST); + } + +-static int plda_init_interrupts(struct platform_device *pdev, +- struct plda_pcie_rp *port, +- const struct plda_event *event) +-{ +- struct device *dev = &pdev->dev; +- int irq; +- int i, intx_irq, msi_irq, event_irq; +- int ret; +- +- if (!port->event_ops) +- port->event_ops = &plda_event_ops; +- +- if (!port->event_irq_chip) +- port->event_irq_chip = &plda_event_irq_chip; +- +- ret = plda_pcie_init_irq_domains(port); +- if (ret) { +- dev_err(dev, "failed creating IRQ domains\n"); +- return ret; +- } +- +- irq = platform_get_irq(pdev, 0); +- if (irq < 0) +- return -ENODEV; +- +- for (i = 0; i < port->num_events; i++) { +- event_irq = irq_create_mapping(port->event_domain, i); +- if (!event_irq) { +- dev_err(dev, "failed to map hwirq %d\n", i); +- return -ENXIO; +- } +- +- if (event->request_event_irq) +- ret = event->request_event_irq(port, event_irq, i); +- else +- ret = devm_request_irq(dev, event_irq, +- plda_event_handler, +- 0, NULL, port); +- +- if (ret) { +- dev_err(dev, "failed to request IRQ %d\n", event_irq); +- return ret; +- } +- } +- +- intx_irq = irq_create_mapping(port->event_domain, +- event->intx_event); +- if (!intx_irq) { +- dev_err(dev, "failed to map INTx interrupt\n"); +- return -ENXIO; +- } +- +- /* Plug the INTx chained handler */ +- irq_set_chained_handler_and_data(intx_irq, plda_handle_intx, port); +- +- msi_irq = irq_create_mapping(port->event_domain, +- event->msi_event); +- if (!msi_irq) +- return -ENXIO; +- +- /* Plug the MSI chained handler */ +- irq_set_chained_handler_and_data(msi_irq, plda_handle_msi, port); +- +- /* Plug the main event chained handler */ +- irq_set_chained_handler_and_data(irq, plda_handle_event, port); +- +- return 0; +-} +- + static int mc_platform_init(struct pci_config_window *cfg) + { + struct device *dev = cfg->parent; +--- a/drivers/pci/controller/plda/pcie-plda-host.c ++++ b/drivers/pci/controller/plda/pcie-plda-host.c +@@ -7,11 +7,483 @@ + * Author: Daire McNamara <daire.mcnamara@microchip.com> + */ + ++#include <linux/irqchip/chained_irq.h> ++#include <linux/irqdomain.h> ++#include <linux/msi.h> + #include <linux/pci_regs.h> + #include <linux/pci-ecam.h> + + #include "pcie-plda.h" + ++static void plda_handle_msi(struct irq_desc *desc) ++{ ++ struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ struct device *dev = port->dev; ++ struct plda_msi *msi = &port->msi; ++ void __iomem *bridge_base_addr = port->bridge_addr; ++ unsigned long status; ++ u32 bit; ++ int ret; ++ ++ chained_irq_enter(chip, desc); ++ ++ status = readl_relaxed(bridge_base_addr + ISTATUS_LOCAL); ++ if (status & PM_MSI_INT_MSI_MASK) { ++ writel_relaxed(status & PM_MSI_INT_MSI_MASK, ++ bridge_base_addr + ISTATUS_LOCAL); ++ status = readl_relaxed(bridge_base_addr + ISTATUS_MSI); ++ for_each_set_bit(bit, &status, msi->num_vectors) { ++ ret = generic_handle_domain_irq(msi->dev_domain, bit); ++ if (ret) ++ dev_err_ratelimited(dev, "bad MSI IRQ %d\n", ++ bit); ++ } ++ } ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static void plda_msi_bottom_irq_ack(struct irq_data *data) ++{ ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ void __iomem *bridge_base_addr = port->bridge_addr; ++ u32 bitpos = data->hwirq; ++ ++ writel_relaxed(BIT(bitpos), bridge_base_addr + ISTATUS_MSI); ++} ++ ++static void plda_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) ++{ ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ phys_addr_t addr = port->msi.vector_phy; ++ ++ msg->address_lo = lower_32_bits(addr); ++ msg->address_hi = upper_32_bits(addr); ++ msg->data = data->hwirq; ++ ++ dev_dbg(port->dev, "msi#%x address_hi %#x address_lo %#x\n", ++ (int)data->hwirq, msg->address_hi, msg->address_lo); ++} ++ ++static int plda_msi_set_affinity(struct irq_data *irq_data, ++ const struct cpumask *mask, bool force) ++{ ++ return -EINVAL; ++} ++ ++static struct irq_chip plda_msi_bottom_irq_chip = { ++ .name = "PLDA MSI", ++ .irq_ack = plda_msi_bottom_irq_ack, ++ .irq_compose_msi_msg = plda_compose_msi_msg, ++ .irq_set_affinity = plda_msi_set_affinity, ++}; ++ ++static int plda_irq_msi_domain_alloc(struct irq_domain *domain, ++ unsigned int virq, ++ unsigned int nr_irqs, ++ void *args) ++{ ++ struct plda_pcie_rp *port = domain->host_data; ++ struct plda_msi *msi = &port->msi; ++ unsigned long bit; ++ ++ mutex_lock(&msi->lock); ++ bit = find_first_zero_bit(msi->used, msi->num_vectors); ++ if (bit >= msi->num_vectors) { ++ mutex_unlock(&msi->lock); ++ return -ENOSPC; ++ } ++ ++ set_bit(bit, msi->used); ++ ++ irq_domain_set_info(domain, virq, bit, &plda_msi_bottom_irq_chip, ++ domain->host_data, handle_edge_irq, NULL, NULL); ++ ++ mutex_unlock(&msi->lock); ++ ++ return 0; ++} ++ ++static void plda_irq_msi_domain_free(struct irq_domain *domain, ++ unsigned int virq, ++ unsigned int nr_irqs) ++{ ++ struct irq_data *d = irq_domain_get_irq_data(domain, virq); ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(d); ++ struct plda_msi *msi = &port->msi; ++ ++ mutex_lock(&msi->lock); ++ ++ if (test_bit(d->hwirq, msi->used)) ++ __clear_bit(d->hwirq, msi->used); ++ else ++ dev_err(port->dev, "trying to free unused MSI%lu\n", d->hwirq); ++ ++ mutex_unlock(&msi->lock); ++} ++ ++static const struct irq_domain_ops msi_domain_ops = { ++ .alloc = plda_irq_msi_domain_alloc, ++ .free = plda_irq_msi_domain_free, ++}; ++ ++static struct irq_chip plda_msi_irq_chip = { ++ .name = "PLDA PCIe MSI", ++ .irq_ack = irq_chip_ack_parent, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, ++}; ++ ++static struct msi_domain_info plda_msi_domain_info = { ++ .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | ++ MSI_FLAG_PCI_MSIX), ++ .chip = &plda_msi_irq_chip, ++}; ++ ++static int plda_allocate_msi_domains(struct plda_pcie_rp *port) ++{ ++ struct device *dev = port->dev; ++ struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node); ++ struct plda_msi *msi = &port->msi; ++ ++ mutex_init(&port->msi.lock); ++ ++ msi->dev_domain = irq_domain_add_linear(NULL, msi->num_vectors, ++ &msi_domain_ops, port); ++ if (!msi->dev_domain) { ++ dev_err(dev, "failed to create IRQ domain\n"); ++ return -ENOMEM; ++ } ++ ++ msi->msi_domain = pci_msi_create_irq_domain(fwnode, ++ &plda_msi_domain_info, ++ msi->dev_domain); ++ if (!msi->msi_domain) { ++ dev_err(dev, "failed to create MSI domain\n"); ++ irq_domain_remove(msi->dev_domain); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static void plda_handle_intx(struct irq_desc *desc) ++{ ++ struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ struct device *dev = port->dev; ++ void __iomem *bridge_base_addr = port->bridge_addr; ++ unsigned long status; ++ u32 bit; ++ int ret; ++ ++ chained_irq_enter(chip, desc); ++ ++ status = readl_relaxed(bridge_base_addr + ISTATUS_LOCAL); ++ if (status & PM_MSI_INT_INTX_MASK) { ++ status &= PM_MSI_INT_INTX_MASK; ++ status >>= PM_MSI_INT_INTX_SHIFT; ++ for_each_set_bit(bit, &status, PCI_NUM_INTX) { ++ ret = generic_handle_domain_irq(port->intx_domain, bit); ++ if (ret) ++ dev_err_ratelimited(dev, "bad INTx IRQ %d\n", ++ bit); ++ } ++ } ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static void plda_ack_intx_irq(struct irq_data *data) ++{ ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ void __iomem *bridge_base_addr = port->bridge_addr; ++ u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); ++ ++ writel_relaxed(mask, bridge_base_addr + ISTATUS_LOCAL); ++} ++ ++static void plda_mask_intx_irq(struct irq_data *data) ++{ ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ void __iomem *bridge_base_addr = port->bridge_addr; ++ unsigned long flags; ++ u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); ++ u32 val; ++ ++ raw_spin_lock_irqsave(&port->lock, flags); ++ val = readl_relaxed(bridge_base_addr + IMASK_LOCAL); ++ val &= ~mask; ++ writel_relaxed(val, bridge_base_addr + IMASK_LOCAL); ++ raw_spin_unlock_irqrestore(&port->lock, flags); ++} ++ ++static void plda_unmask_intx_irq(struct irq_data *data) ++{ ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ void __iomem *bridge_base_addr = port->bridge_addr; ++ unsigned long flags; ++ u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT); ++ u32 val; ++ ++ raw_spin_lock_irqsave(&port->lock, flags); ++ val = readl_relaxed(bridge_base_addr + IMASK_LOCAL); ++ val |= mask; ++ writel_relaxed(val, bridge_base_addr + IMASK_LOCAL); ++ raw_spin_unlock_irqrestore(&port->lock, flags); ++} ++ ++static struct irq_chip plda_intx_irq_chip = { ++ .name = "PLDA PCIe INTx", ++ .irq_ack = plda_ack_intx_irq, ++ .irq_mask = plda_mask_intx_irq, ++ .irq_unmask = plda_unmask_intx_irq, ++}; ++ ++static int plda_pcie_intx_map(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ irq_set_chip_and_handler(irq, &plda_intx_irq_chip, handle_level_irq); ++ irq_set_chip_data(irq, domain->host_data); ++ ++ return 0; ++} ++ ++static const struct irq_domain_ops intx_domain_ops = { ++ .map = plda_pcie_intx_map, ++}; ++ ++static u32 plda_get_events(struct plda_pcie_rp *port) ++{ ++ u32 events, val, origin; ++ ++ origin = readl_relaxed(port->bridge_addr + ISTATUS_LOCAL); ++ ++ /* MSI event and sys events */ ++ val = (origin & SYS_AND_MSI_MASK) >> PM_MSI_INT_MSI_SHIFT; ++ events = val << (PM_MSI_INT_MSI_SHIFT - PCI_NUM_INTX + 1); ++ ++ /* INTx events */ ++ if (origin & PM_MSI_INT_INTX_MASK) ++ events |= BIT(PM_MSI_INT_INTX_SHIFT); ++ ++ /* remains are same with register */ ++ events |= origin & GENMASK(P_ATR_EVT_DOORBELL_SHIFT, 0); ++ ++ return events; ++} ++ ++static irqreturn_t plda_event_handler(int irq, void *dev_id) ++{ ++ return IRQ_HANDLED; ++} ++ ++static void plda_handle_event(struct irq_desc *desc) ++{ ++ struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); ++ unsigned long events; ++ u32 bit; ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ ++ chained_irq_enter(chip, desc); ++ ++ events = port->event_ops->get_events(port); ++ ++ for_each_set_bit(bit, &events, port->num_events) ++ generic_handle_domain_irq(port->event_domain, bit); ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static u32 plda_hwirq_to_mask(int hwirq) ++{ ++ u32 mask; ++ ++ /* hwirq 23 - 0 are the same with register */ ++ if (hwirq < EVENT_PM_MSI_INT_INTX) ++ mask = BIT(hwirq); ++ else if (hwirq == EVENT_PM_MSI_INT_INTX) ++ mask = PM_MSI_INT_INTX_MASK; ++ else ++ mask = BIT(hwirq + PCI_NUM_INTX - 1); ++ ++ return mask; ++} ++ ++static void plda_ack_event_irq(struct irq_data *data) ++{ ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ ++ writel_relaxed(plda_hwirq_to_mask(data->hwirq), ++ port->bridge_addr + ISTATUS_LOCAL); ++} ++ ++static void plda_mask_event_irq(struct irq_data *data) ++{ ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ u32 mask, val; ++ ++ mask = plda_hwirq_to_mask(data->hwirq); ++ ++ raw_spin_lock(&port->lock); ++ val = readl_relaxed(port->bridge_addr + IMASK_LOCAL); ++ val &= ~mask; ++ writel_relaxed(val, port->bridge_addr + IMASK_LOCAL); ++ raw_spin_unlock(&port->lock); ++} ++ ++static void plda_unmask_event_irq(struct irq_data *data) ++{ ++ struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data); ++ u32 mask, val; ++ ++ mask = plda_hwirq_to_mask(data->hwirq); ++ ++ raw_spin_lock(&port->lock); ++ val = readl_relaxed(port->bridge_addr + IMASK_LOCAL); ++ val |= mask; ++ writel_relaxed(val, port->bridge_addr + IMASK_LOCAL); ++ raw_spin_unlock(&port->lock); ++} ++ ++static struct irq_chip plda_event_irq_chip = { ++ .name = "PLDA PCIe EVENT", ++ .irq_ack = plda_ack_event_irq, ++ .irq_mask = plda_mask_event_irq, ++ .irq_unmask = plda_unmask_event_irq, ++}; ++ ++static const struct plda_event_ops plda_event_ops = { ++ .get_events = plda_get_events, ++}; ++ ++static int plda_pcie_event_map(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ struct plda_pcie_rp *port = (void *)domain->host_data; ++ ++ irq_set_chip_and_handler(irq, port->event_irq_chip, handle_level_irq); ++ irq_set_chip_data(irq, domain->host_data); ++ ++ return 0; ++} ++ ++static const struct irq_domain_ops plda_event_domain_ops = { ++ .map = plda_pcie_event_map, ++}; ++ ++static int plda_pcie_init_irq_domains(struct plda_pcie_rp *port) ++{ ++ struct device *dev = port->dev; ++ struct device_node *node = dev->of_node; ++ struct device_node *pcie_intc_node; ++ ++ /* Setup INTx */ ++ pcie_intc_node = of_get_next_child(node, NULL); ++ if (!pcie_intc_node) { ++ dev_err(dev, "failed to find PCIe Intc node\n"); ++ return -EINVAL; ++ } ++ ++ port->event_domain = irq_domain_add_linear(pcie_intc_node, ++ port->num_events, ++ &plda_event_domain_ops, ++ port); ++ if (!port->event_domain) { ++ dev_err(dev, "failed to get event domain\n"); ++ of_node_put(pcie_intc_node); ++ return -ENOMEM; ++ } ++ ++ irq_domain_update_bus_token(port->event_domain, DOMAIN_BUS_NEXUS); ++ ++ port->intx_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX, ++ &intx_domain_ops, port); ++ if (!port->intx_domain) { ++ dev_err(dev, "failed to get an INTx IRQ domain\n"); ++ of_node_put(pcie_intc_node); ++ return -ENOMEM; ++ } ++ ++ irq_domain_update_bus_token(port->intx_domain, DOMAIN_BUS_WIRED); ++ ++ of_node_put(pcie_intc_node); ++ raw_spin_lock_init(&port->lock); ++ ++ return plda_allocate_msi_domains(port); ++} ++ ++int plda_init_interrupts(struct platform_device *pdev, ++ struct plda_pcie_rp *port, ++ const struct plda_event *event) ++{ ++ struct device *dev = &pdev->dev; ++ int irq; ++ int i, intx_irq, msi_irq, event_irq; ++ int ret; ++ ++ if (!port->event_ops) ++ port->event_ops = &plda_event_ops; ++ ++ if (!port->event_irq_chip) ++ port->event_irq_chip = &plda_event_irq_chip; ++ ++ ret = plda_pcie_init_irq_domains(port); ++ if (ret) { ++ dev_err(dev, "failed creating IRQ domains\n"); ++ return ret; ++ } ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return -ENODEV; ++ ++ for (i = 0; i < port->num_events; i++) { ++ event_irq = irq_create_mapping(port->event_domain, i); ++ if (!event_irq) { ++ dev_err(dev, "failed to map hwirq %d\n", i); ++ return -ENXIO; ++ } ++ ++ if (event->request_event_irq) ++ ret = event->request_event_irq(port, event_irq, i); ++ else ++ ret = devm_request_irq(dev, event_irq, ++ plda_event_handler, ++ 0, NULL, port); ++ ++ if (ret) { ++ dev_err(dev, "failed to request IRQ %d\n", event_irq); ++ return ret; ++ } ++ } ++ ++ intx_irq = irq_create_mapping(port->event_domain, ++ event->intx_event); ++ if (!intx_irq) { ++ dev_err(dev, "failed to map INTx interrupt\n"); ++ return -ENXIO; ++ } ++ ++ /* Plug the INTx chained handler */ ++ irq_set_chained_handler_and_data(intx_irq, plda_handle_intx, port); ++ ++ msi_irq = irq_create_mapping(port->event_domain, ++ event->msi_event); ++ if (!msi_irq) ++ return -ENXIO; ++ ++ /* Plug the MSI chained handler */ ++ irq_set_chained_handler_and_data(msi_irq, plda_handle_msi, port); ++ ++ /* Plug the main event chained handler */ ++ irq_set_chained_handler_and_data(irq, plda_handle_event, port); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(plda_init_interrupts); ++ + void plda_pcie_setup_window(void __iomem *bridge_base_addr, u32 index, + phys_addr_t axi_addr, phys_addr_t pci_addr, + size_t size) +--- a/drivers/pci/controller/plda/pcie-plda.h ++++ b/drivers/pci/controller/plda/pcie-plda.h +@@ -169,6 +169,9 @@ struct plda_event { + int msi_event; + }; + ++int plda_init_interrupts(struct platform_device *pdev, ++ struct plda_pcie_rp *port, ++ const struct plda_event *event); + void plda_pcie_setup_window(void __iomem *bridge_base_addr, u32 index, + phys_addr_t axi_addr, phys_addr_t pci_addr, + size_t size); diff --git a/target/linux/starfive/patches-6.6/0030-pci-plda-Add-event-bitmap-field-to-struct-plda_pcie_.patch b/target/linux/starfive/patches-6.6/0030-pci-plda-Add-event-bitmap-field-to-struct-plda_pcie_.patch new file mode 100644 index 0000000000..dcd2310e5e --- /dev/null +++ b/target/linux/starfive/patches-6.6/0030-pci-plda-Add-event-bitmap-field-to-struct-plda_pcie_.patch @@ -0,0 +1,67 @@ +From 142fc300fd7511a217783dcfa342031d8ad70188 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:06:07 +0800 +Subject: [PATCH 030/116] pci: plda: Add event bitmap field to struct + plda_pcie_rp + +For PLDA DMA interrupts are not all implemented. The non-implemented +interrupts should be masked. So add a bitmap field to mask the non- +implemented interrupts. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +--- + drivers/pci/controller/plda/pcie-microchip-host.c | 1 + + drivers/pci/controller/plda/pcie-plda-host.c | 6 ++++-- + drivers/pci/controller/plda/pcie-plda.h | 1 + + 3 files changed, 6 insertions(+), 2 deletions(-) + +--- a/drivers/pci/controller/plda/pcie-microchip-host.c ++++ b/drivers/pci/controller/plda/pcie-microchip-host.c +@@ -636,6 +636,7 @@ static int mc_platform_init(struct pci_c + + port->plda.event_ops = &mc_event_ops; + port->plda.event_irq_chip = &mc_event_irq_chip; ++ port->plda.events_bitmap = GENMASK(NUM_EVENTS - 1, 0); + + /* Address translation is up; safe to enable interrupts */ + ret = plda_init_interrupts(pdev, &port->plda, &mc_event); +--- a/drivers/pci/controller/plda/pcie-plda-host.c ++++ b/drivers/pci/controller/plda/pcie-plda-host.c +@@ -290,6 +290,7 @@ static void plda_handle_event(struct irq + + events = port->event_ops->get_events(port); + ++ events &= port->events_bitmap; + for_each_set_bit(bit, &events, port->num_events) + generic_handle_domain_irq(port->event_domain, bit); + +@@ -420,8 +421,9 @@ int plda_init_interrupts(struct platform + { + struct device *dev = &pdev->dev; + int irq; +- int i, intx_irq, msi_irq, event_irq; ++ int intx_irq, msi_irq, event_irq; + int ret; ++ u32 i; + + if (!port->event_ops) + port->event_ops = &plda_event_ops; +@@ -439,7 +441,7 @@ int plda_init_interrupts(struct platform + if (irq < 0) + return -ENODEV; + +- for (i = 0; i < port->num_events; i++) { ++ for_each_set_bit(i, &port->events_bitmap, port->num_events) { + event_irq = irq_create_mapping(port->event_domain, i); + if (!event_irq) { + dev_err(dev, "failed to map hwirq %d\n", i); +--- a/drivers/pci/controller/plda/pcie-plda.h ++++ b/drivers/pci/controller/plda/pcie-plda.h +@@ -159,6 +159,7 @@ struct plda_pcie_rp { + const struct plda_event_ops *event_ops; + const struct irq_chip *event_irq_chip; + void __iomem *bridge_addr; ++ unsigned long events_bitmap; + int num_events; + }; + diff --git a/target/linux/starfive/patches-6.6/0031-PCI-plda-Add-host-init-deinit-and-map-bus-functions.patch b/target/linux/starfive/patches-6.6/0031-PCI-plda-Add-host-init-deinit-and-map-bus-functions.patch new file mode 100644 index 0000000000..eb22849da6 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0031-PCI-plda-Add-host-init-deinit-and-map-bus-functions.patch @@ -0,0 +1,256 @@ +From 3b9991438094dc472dacb4555603bdc379653411 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:06:08 +0800 +Subject: [PATCH 031/116] PCI: plda: Add host init/deinit and map bus functions + +Add PLDA host plda_pcie_host_init()/plda_pcie_host_deinit() and map bus +function. So vendor can use it to init PLDA PCIe host core. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Reviewed-by: Mason Huo <mason.huo@starfivetech.com> +--- + drivers/pci/controller/plda/pcie-plda-host.c | 131 +++++++++++++++++-- + drivers/pci/controller/plda/pcie-plda.h | 22 ++++ + 2 files changed, 139 insertions(+), 14 deletions(-) + +--- a/drivers/pci/controller/plda/pcie-plda-host.c ++++ b/drivers/pci/controller/plda/pcie-plda-host.c +@@ -3,6 +3,7 @@ + * PLDA PCIe XpressRich host controller driver + * + * Copyright (C) 2023 Microchip Co. Ltd ++ * StarFive Co. Ltd + * + * Author: Daire McNamara <daire.mcnamara@microchip.com> + */ +@@ -15,6 +16,15 @@ + + #include "pcie-plda.h" + ++void __iomem *plda_pcie_map_bus(struct pci_bus *bus, unsigned int devfn, ++ int where) ++{ ++ struct plda_pcie_rp *pcie = bus->sysdata; ++ ++ return pcie->config_base + PCIE_ECAM_OFFSET(bus->number, devfn, where); ++} ++EXPORT_SYMBOL_GPL(plda_pcie_map_bus); ++ + static void plda_handle_msi(struct irq_desc *desc) + { + struct plda_pcie_rp *port = irq_desc_get_handler_data(desc); +@@ -420,9 +430,7 @@ int plda_init_interrupts(struct platform + const struct plda_event *event) + { + struct device *dev = &pdev->dev; +- int irq; +- int intx_irq, msi_irq, event_irq; +- int ret; ++ int event_irq, ret; + u32 i; + + if (!port->event_ops) +@@ -437,8 +445,8 @@ int plda_init_interrupts(struct platform + return ret; + } + +- irq = platform_get_irq(pdev, 0); +- if (irq < 0) ++ port->irq = platform_get_irq(pdev, 0); ++ if (port->irq < 0) + return -ENODEV; + + for_each_set_bit(i, &port->events_bitmap, port->num_events) { +@@ -461,26 +469,26 @@ int plda_init_interrupts(struct platform + } + } + +- intx_irq = irq_create_mapping(port->event_domain, +- event->intx_event); +- if (!intx_irq) { ++ port->intx_irq = irq_create_mapping(port->event_domain, ++ event->intx_event); ++ if (!port->intx_irq) { + dev_err(dev, "failed to map INTx interrupt\n"); + return -ENXIO; + } + + /* Plug the INTx chained handler */ +- irq_set_chained_handler_and_data(intx_irq, plda_handle_intx, port); ++ irq_set_chained_handler_and_data(port->intx_irq, plda_handle_intx, port); + +- msi_irq = irq_create_mapping(port->event_domain, +- event->msi_event); +- if (!msi_irq) ++ port->msi_irq = irq_create_mapping(port->event_domain, ++ event->msi_event); ++ if (!port->msi_irq) + return -ENXIO; + + /* Plug the MSI chained handler */ +- irq_set_chained_handler_and_data(msi_irq, plda_handle_msi, port); ++ irq_set_chained_handler_and_data(port->msi_irq, plda_handle_msi, port); + + /* Plug the main event chained handler */ +- irq_set_chained_handler_and_data(irq, plda_handle_event, port); ++ irq_set_chained_handler_and_data(port->irq, plda_handle_event, port); + + return 0; + } +@@ -546,3 +554,98 @@ int plda_pcie_setup_iomems(struct pci_ho + return 0; + } + EXPORT_SYMBOL_GPL(plda_pcie_setup_iomems); ++ ++static void plda_pcie_irq_domain_deinit(struct plda_pcie_rp *pcie) ++{ ++ irq_set_chained_handler_and_data(pcie->irq, NULL, NULL); ++ irq_set_chained_handler_and_data(pcie->msi_irq, NULL, NULL); ++ irq_set_chained_handler_and_data(pcie->intx_irq, NULL, NULL); ++ ++ irq_domain_remove(pcie->msi.msi_domain); ++ irq_domain_remove(pcie->msi.dev_domain); ++ ++ irq_domain_remove(pcie->intx_domain); ++ irq_domain_remove(pcie->event_domain); ++} ++ ++int plda_pcie_host_init(struct plda_pcie_rp *port, struct pci_ops *ops, ++ const struct plda_event *plda_event) ++{ ++ struct device *dev = port->dev; ++ struct pci_host_bridge *bridge; ++ struct platform_device *pdev = to_platform_device(dev); ++ struct resource *cfg_res; ++ int ret; ++ ++ pdev = to_platform_device(dev); ++ ++ port->bridge_addr = ++ devm_platform_ioremap_resource_byname(pdev, "apb"); ++ ++ if (IS_ERR(port->bridge_addr)) ++ return dev_err_probe(dev, PTR_ERR(port->bridge_addr), ++ "failed to map reg memory\n"); ++ ++ cfg_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg"); ++ if (!cfg_res) ++ return dev_err_probe(dev, -ENODEV, ++ "failed to get config memory\n"); ++ ++ port->config_base = devm_ioremap_resource(dev, cfg_res); ++ if (IS_ERR(port->config_base)) ++ return dev_err_probe(dev, PTR_ERR(port->config_base), ++ "failed to map config memory\n"); ++ ++ bridge = devm_pci_alloc_host_bridge(dev, 0); ++ if (!bridge) ++ return dev_err_probe(dev, -ENOMEM, ++ "failed to alloc bridge\n"); ++ ++ if (port->host_ops && port->host_ops->host_init) { ++ ret = port->host_ops->host_init(port); ++ if (ret) ++ return ret; ++ } ++ ++ port->bridge = bridge; ++ plda_pcie_setup_window(port->bridge_addr, 0, cfg_res->start, 0, ++ resource_size(cfg_res)); ++ plda_pcie_setup_iomems(bridge, port); ++ plda_set_default_msi(&port->msi); ++ ret = plda_init_interrupts(pdev, port, plda_event); ++ if (ret) ++ goto err_host; ++ ++ /* Set default bus ops */ ++ bridge->ops = ops; ++ bridge->sysdata = port; ++ ++ ret = pci_host_probe(bridge); ++ if (ret < 0) { ++ dev_err_probe(dev, ret, "failed to probe pci host\n"); ++ goto err_probe; ++ } ++ ++ return ret; ++ ++err_probe: ++ plda_pcie_irq_domain_deinit(port); ++err_host: ++ if (port->host_ops && port->host_ops->host_deinit) ++ port->host_ops->host_deinit(port); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(plda_pcie_host_init); ++ ++void plda_pcie_host_deinit(struct plda_pcie_rp *port) ++{ ++ pci_stop_root_bus(port->bridge->bus); ++ pci_remove_root_bus(port->bridge->bus); ++ ++ plda_pcie_irq_domain_deinit(port); ++ ++ if (port->host_ops && port->host_ops->host_deinit) ++ port->host_ops->host_deinit(port); ++} ++EXPORT_SYMBOL_GPL(plda_pcie_host_deinit); +--- a/drivers/pci/controller/plda/pcie-plda.h ++++ b/drivers/pci/controller/plda/pcie-plda.h +@@ -141,6 +141,11 @@ struct plda_event_ops { + u32 (*get_events)(struct plda_pcie_rp *pcie); + }; + ++struct plda_pcie_host_ops { ++ int (*host_init)(struct plda_pcie_rp *pcie); ++ void (*host_deinit)(struct plda_pcie_rp *pcie); ++}; ++ + struct plda_msi { + struct mutex lock; /* Protect used bitmap */ + struct irq_domain *msi_domain; +@@ -152,14 +157,20 @@ struct plda_msi { + + struct plda_pcie_rp { + struct device *dev; ++ struct pci_host_bridge *bridge; + struct irq_domain *intx_domain; + struct irq_domain *event_domain; + raw_spinlock_t lock; + struct plda_msi msi; + const struct plda_event_ops *event_ops; + const struct irq_chip *event_irq_chip; ++ const struct plda_pcie_host_ops *host_ops; + void __iomem *bridge_addr; ++ void __iomem *config_base; + unsigned long events_bitmap; ++ int irq; ++ int msi_irq; ++ int intx_irq; + int num_events; + }; + +@@ -170,6 +181,8 @@ struct plda_event { + int msi_event; + }; + ++void __iomem *plda_pcie_map_bus(struct pci_bus *bus, unsigned int devfn, ++ int where); + int plda_init_interrupts(struct platform_device *pdev, + struct plda_pcie_rp *port, + const struct plda_event *event); +@@ -178,4 +191,13 @@ void plda_pcie_setup_window(void __iomem + size_t size); + int plda_pcie_setup_iomems(struct pci_host_bridge *bridge, + struct plda_pcie_rp *port); ++int plda_pcie_host_init(struct plda_pcie_rp *port, struct pci_ops *ops, ++ const struct plda_event *plda_event); ++void plda_pcie_host_deinit(struct plda_pcie_rp *pcie); ++ ++static inline void plda_set_default_msi(struct plda_msi *msi) ++{ ++ msi->vector_phy = IMSI_ADDR; ++ msi->num_vectors = PLDA_MAX_NUM_MSI_IRQS; ++} + #endif diff --git a/target/linux/starfive/patches-6.6/0032-dt-bindings-PCI-Add-StarFive-JH7110-PCIe-controller.patch b/target/linux/starfive/patches-6.6/0032-dt-bindings-PCI-Add-StarFive-JH7110-PCIe-controller.patch new file mode 100644 index 0000000000..9e33f7d68a --- /dev/null +++ b/target/linux/starfive/patches-6.6/0032-dt-bindings-PCI-Add-StarFive-JH7110-PCIe-controller.patch @@ -0,0 +1,140 @@ +From bc3f8207d9f0af3cb96a7eae232074a644a175f6 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:06:09 +0800 +Subject: [PATCH 032/116] dt-bindings: PCI: Add StarFive JH7110 PCIe controller + +Add StarFive JH7110 SoC PCIe controller dt-bindings. JH7110 using PLDA +XpressRICH PCIe host controller IP. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Reviewed-by: Hal Feng <hal.feng@starfivetech.com> +Reviewed-by: Conor Dooley <conor.dooley@microchip.com> +Reviewed-by: Rob Herring <robh@kernel.org> +--- + .../bindings/pci/starfive,jh7110-pcie.yaml | 120 ++++++++++++++++++ + 1 file changed, 120 insertions(+) + create mode 100644 Documentation/devicetree/bindings/pci/starfive,jh7110-pcie.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/pci/starfive,jh7110-pcie.yaml +@@ -0,0 +1,120 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/pci/starfive,jh7110-pcie.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: StarFive JH7110 PCIe host controller ++ ++maintainers: ++ - Kevin Xie <kevin.xie@starfivetech.com> ++ ++allOf: ++ - $ref: plda,xpressrich3-axi-common.yaml# ++ ++properties: ++ compatible: ++ const: starfive,jh7110-pcie ++ ++ clocks: ++ items: ++ - description: NOC bus clock ++ - description: Transport layer clock ++ - description: AXI MST0 clock ++ - description: APB clock ++ ++ clock-names: ++ items: ++ - const: noc ++ - const: tl ++ - const: axi_mst0 ++ - const: apb ++ ++ resets: ++ items: ++ - description: AXI MST0 reset ++ - description: AXI SLAVE0 reset ++ - description: AXI SLAVE reset ++ - description: PCIE BRIDGE reset ++ - description: PCIE CORE reset ++ - description: PCIE APB reset ++ ++ reset-names: ++ items: ++ - const: mst0 ++ - const: slv0 ++ - const: slv ++ - const: brg ++ - const: core ++ - const: apb ++ ++ starfive,stg-syscon: ++ $ref: /schemas/types.yaml#/definitions/phandle-array ++ description: ++ The phandle to System Register Controller syscon node. ++ ++ perst-gpios: ++ description: GPIO controlled connection to PERST# signal ++ maxItems: 1 ++ ++ phys: ++ description: ++ Specified PHY is attached to PCIe controller. ++ maxItems: 1 ++ ++required: ++ - clocks ++ - resets ++ - starfive,stg-syscon ++ ++unevaluatedProperties: false ++ ++examples: ++ - | ++ #include <dt-bindings/gpio/gpio.h> ++ soc { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ pcie@940000000 { ++ compatible = "starfive,jh7110-pcie"; ++ reg = <0x9 0x40000000 0x0 0x10000000>, ++ <0x0 0x2b000000 0x0 0x1000000>; ++ reg-names = "cfg", "apb"; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ #interrupt-cells = <1>; ++ device_type = "pci"; ++ ranges = <0x82000000 0x0 0x30000000 0x0 0x30000000 0x0 0x08000000>, ++ <0xc3000000 0x9 0x00000000 0x9 0x00000000 0x0 0x40000000>; ++ starfive,stg-syscon = <&stg_syscon>; ++ bus-range = <0x0 0xff>; ++ interrupt-parent = <&plic>; ++ interrupts = <56>; ++ interrupt-map-mask = <0x0 0x0 0x0 0x7>; ++ interrupt-map = <0x0 0x0 0x0 0x1 &pcie_intc0 0x1>, ++ <0x0 0x0 0x0 0x2 &pcie_intc0 0x2>, ++ <0x0 0x0 0x0 0x3 &pcie_intc0 0x3>, ++ <0x0 0x0 0x0 0x4 &pcie_intc0 0x4>; ++ msi-controller; ++ clocks = <&syscrg 86>, ++ <&stgcrg 10>, ++ <&stgcrg 8>, ++ <&stgcrg 9>; ++ clock-names = "noc", "tl", "axi_mst0", "apb"; ++ resets = <&stgcrg 11>, ++ <&stgcrg 12>, ++ <&stgcrg 13>, ++ <&stgcrg 14>, ++ <&stgcrg 15>, ++ <&stgcrg 16>; ++ perst-gpios = <&gpios 26 GPIO_ACTIVE_LOW>; ++ phys = <&pciephy0>; ++ ++ pcie_intc0: interrupt-controller { ++ #address-cells = <0>; ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ }; ++ }; ++ }; diff --git a/target/linux/starfive/patches-6.6/0033-PCI-Add-PCIE_RESET_CONFIG_DEVICE_WAIT_MS-waiting-tim.patch b/target/linux/starfive/patches-6.6/0033-PCI-Add-PCIE_RESET_CONFIG_DEVICE_WAIT_MS-waiting-tim.patch new file mode 100644 index 0000000000..cc50dfe68e --- /dev/null +++ b/target/linux/starfive/patches-6.6/0033-PCI-Add-PCIE_RESET_CONFIG_DEVICE_WAIT_MS-waiting-tim.patch @@ -0,0 +1,55 @@ +From abb20b7b8f5e3a7f36dbd6264e6d346275434154 Mon Sep 17 00:00:00 2001 +From: Kevin Xie <kevin.xie@starfivetech.com> +Date: Mon, 8 Jan 2024 19:06:10 +0800 +Subject: [PATCH 033/116] PCI: Add PCIE_RESET_CONFIG_DEVICE_WAIT_MS waiting + time value + +Add the PCIE_RESET_CONFIG_DEVICE_WAIT_MS macro to define the minimum +waiting time between exit from a conventional reset and sending the +first configuration request to the device. + +As described in PCI base specification r6.0, section 6.6.1 <Conventional +Reset>, there are two different use cases of the value: + + - "With a Downstream Port that does not support Link speeds greater + than 5.0 GT/s, software must wait a minimum of 100 ms following exit + from a Conventional Reset before sending a Configuration Request to + the device immediately below that Port." + + - "With a Downstream Port that supports Link speeds greater than + 5.0 GT/s, software must wait a minimum of 100 ms after Link training + completes before sending a Configuration Request to the device + immediately below that Port." + +Signed-off-by: Kevin Xie <kevin.xie@starfivetech.com> +Reviewed-by: Mason Huo <mason.huo@starfivetech.com> +Acked-by: Bjorn Helgaas <bhelgaas@google.com> +--- + drivers/pci/pci.h | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -19,6 +19,22 @@ + */ + #define PCIE_PME_TO_L2_TIMEOUT_US 10000 + ++/* ++ * As described in PCI base specification r6.0, section 6.6.1 <Conventional ++ * Reset>, there are two different use cases of the value: ++ * ++ * - "With a Downstream Port that does not support Link speeds greater ++ * than 5.0 GT/s, software must wait a minimum of 100 ms following exit ++ * from a Conventional Reset before sending a Configuration Request to ++ * the device immediately below that Port." ++ * ++ * - "With a Downstream Port that supports Link speeds greater than ++ * 5.0 GT/s, software must wait a minimum of 100 ms after Link training ++ * completes before sending a Configuration Request to the device ++ * immediately below that Port." ++ */ ++#define PCIE_RESET_CONFIG_DEVICE_WAIT_MS 100 ++ + extern const unsigned char pcie_link_speed[]; + extern bool pci_early_dump; + diff --git a/target/linux/starfive/patches-6.6/0034-PCI-starfive-Add-JH7110-PCIe-controller.patch b/target/linux/starfive/patches-6.6/0034-PCI-starfive-Add-JH7110-PCIe-controller.patch new file mode 100644 index 0000000000..45a549f489 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0034-PCI-starfive-Add-JH7110-PCIe-controller.patch @@ -0,0 +1,623 @@ +From 323aedef34315b758dc30ba23e2cabca259bb4b2 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 8 Jan 2024 19:06:11 +0800 +Subject: [PATCH 034/116] PCI: starfive: Add JH7110 PCIe controller + +Add StarFive JH7110 SoC PCIe controller platform driver codes, JH7110 +with PLDA host PCIe core. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Co-developed-by: Kevin Xie <kevin.xie@starfivetech.com> +Reviewed-by: Mason Huo <mason.huo@starfivetech.com> +--- + drivers/pci/controller/plda/Kconfig | 12 + + drivers/pci/controller/plda/Makefile | 1 + + drivers/pci/controller/plda/pcie-plda.h | 71 ++- + drivers/pci/controller/plda/pcie-starfive.c | 473 ++++++++++++++++++++ + 4 files changed, 556 insertions(+), 1 deletion(-) + create mode 100644 drivers/pci/controller/plda/pcie-starfive.c + +--- a/drivers/pci/controller/plda/Kconfig ++++ b/drivers/pci/controller/plda/Kconfig +@@ -15,4 +15,16 @@ config PCIE_MICROCHIP_HOST + Say Y here if you want kernel to support the Microchip AXI PCIe + Host Bridge driver. + ++config PCIE_STARFIVE_HOST ++ tristate "StarFive PCIe host controller" ++ depends on PCI_MSI && OF ++ depends on ARCH_STARFIVE || COMPILE_TEST ++ select PCIE_PLDA_HOST ++ help ++ Say Y here if you want to support the StarFive PCIe controller in ++ host mode. StarFive PCIe controller uses PLDA PCIe core. ++ ++ If you choose to build this driver as module it will be dynamically ++ linked and module will be called pcie-starfive.ko. ++ + endmenu +--- a/drivers/pci/controller/plda/Makefile ++++ b/drivers/pci/controller/plda/Makefile +@@ -1,3 +1,4 @@ + # SPDX-License-Identifier: GPL-2.0 + obj-$(CONFIG_PCIE_PLDA_HOST) += pcie-plda-host.o + obj-$(CONFIG_PCIE_MICROCHIP_HOST) += pcie-microchip-host.o ++obj-$(CONFIG_PCIE_STARFIVE_HOST) += pcie-starfive.o +--- a/drivers/pci/controller/plda/pcie-plda.h ++++ b/drivers/pci/controller/plda/pcie-plda.h +@@ -10,10 +10,20 @@ + #define PLDA_MAX_NUM_MSI_IRQS 32 + + /* PCIe Bridge Phy Regs */ ++#define GEN_SETTINGS 0x80 ++#define RP_ENABLE 1 ++#define PCIE_PCI_IDS_DW1 0x9c ++#define IDS_CLASS_CODE_SHIFT 16 ++#define REVISION_ID_MASK GENMASK(7, 0) ++#define CLASS_CODE_ID_MASK GENMASK(31, 8) + #define PCIE_PCI_IRQ_DW0 0xa8 + #define MSIX_CAP_MASK BIT(31) + #define NUM_MSI_MSGS_MASK GENMASK(6, 4) + #define NUM_MSI_MSGS_SHIFT 4 ++#define PCI_MISC 0xb4 ++#define PHY_FUNCTION_DIS BIT(15) ++#define PCIE_WINROM 0xfc ++#define PREF_MEM_WIN_64_SUPPORT BIT(3) + + #define IMASK_LOCAL 0x180 + #define DMA_END_ENGINE_0_MASK 0x00000000u +@@ -65,6 +75,8 @@ + #define ISTATUS_HOST 0x18c + #define IMSI_ADDR 0x190 + #define ISTATUS_MSI 0x194 ++#define PMSG_SUPPORT_RX 0x3f0 ++#define PMSG_LTR_SUPPORT BIT(2) + + /* PCIe Master table init defines */ + #define ATR0_PCIE_WIN0_SRCADDR_PARAM 0x600u +@@ -86,6 +98,8 @@ + #define PCIE_TX_RX_INTERFACE 0x00000000u + #define PCIE_CONFIG_INTERFACE 0x00000001u + ++#define CONFIG_SPACE_ADDR_OFFSET 0x1000u ++ + #define ATR_ENTRY_SIZE 32 + + enum plda_int_event { +@@ -200,4 +214,59 @@ static inline void plda_set_default_msi( + msi->vector_phy = IMSI_ADDR; + msi->num_vectors = PLDA_MAX_NUM_MSI_IRQS; + } +-#endif ++ ++static inline void plda_pcie_enable_root_port(struct plda_pcie_rp *plda) ++{ ++ u32 value; ++ ++ value = readl_relaxed(plda->bridge_addr + GEN_SETTINGS); ++ value |= RP_ENABLE; ++ writel_relaxed(value, plda->bridge_addr + GEN_SETTINGS); ++} ++ ++static inline void plda_pcie_set_standard_class(struct plda_pcie_rp *plda) ++{ ++ u32 value; ++ ++ /* set class code and reserve revision id */ ++ value = readl_relaxed(plda->bridge_addr + PCIE_PCI_IDS_DW1); ++ value &= REVISION_ID_MASK; ++ value |= (PCI_CLASS_BRIDGE_PCI << IDS_CLASS_CODE_SHIFT); ++ writel_relaxed(value, plda->bridge_addr + PCIE_PCI_IDS_DW1); ++} ++ ++static inline void plda_pcie_set_pref_win_64bit(struct plda_pcie_rp *plda) ++{ ++ u32 value; ++ ++ value = readl_relaxed(plda->bridge_addr + PCIE_WINROM); ++ value |= PREF_MEM_WIN_64_SUPPORT; ++ writel_relaxed(value, plda->bridge_addr + PCIE_WINROM); ++} ++ ++static inline void plda_pcie_disable_ltr(struct plda_pcie_rp *plda) ++{ ++ u32 value; ++ ++ value = readl_relaxed(plda->bridge_addr + PMSG_SUPPORT_RX); ++ value &= ~PMSG_LTR_SUPPORT; ++ writel_relaxed(value, plda->bridge_addr + PMSG_SUPPORT_RX); ++} ++ ++static inline void plda_pcie_disable_func(struct plda_pcie_rp *plda) ++{ ++ u32 value; ++ ++ value = readl_relaxed(plda->bridge_addr + PCI_MISC); ++ value |= PHY_FUNCTION_DIS; ++ writel_relaxed(value, plda->bridge_addr + PCI_MISC); ++} ++ ++static inline void plda_pcie_write_rc_bar(struct plda_pcie_rp *plda, u64 val) ++{ ++ void __iomem *addr = plda->bridge_addr + CONFIG_SPACE_ADDR_OFFSET; ++ ++ writel_relaxed(lower_32_bits(val), addr + PCI_BASE_ADDRESS_0); ++ writel_relaxed(upper_32_bits(val), addr + PCI_BASE_ADDRESS_1); ++} ++#endif /* _PCIE_PLDA_H */ +--- /dev/null ++++ b/drivers/pci/controller/plda/pcie-starfive.c +@@ -0,0 +1,473 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * PCIe host controller driver for StarFive JH7110 Soc. ++ * ++ * Copyright (C) 2023 StarFive Technology Co., Ltd. ++ */ ++ ++#include <linux/bitfield.h> ++#include <linux/clk.h> ++#include <linux/delay.h> ++#include <linux/gpio/consumer.h> ++#include <linux/interrupt.h> ++#include <linux/kernel.h> ++#include <linux/mfd/syscon.h> ++#include <linux/module.h> ++#include <linux/of_address.h> ++#include <linux/of_irq.h> ++#include <linux/of_pci.h> ++#include <linux/pci.h> ++#include <linux/phy/phy.h> ++#include <linux/platform_device.h> ++#include <linux/pm_runtime.h> ++#include <linux/regmap.h> ++#include <linux/reset.h> ++#include "../../pci.h" ++ ++#include "pcie-plda.h" ++ ++#define PCIE_FUNC_NUM 4 ++ ++/* system control */ ++#define STG_SYSCON_PCIE0_BASE 0x48 ++#define STG_SYSCON_PCIE1_BASE 0x1f8 ++ ++#define STG_SYSCON_AR_OFFSET 0x78 ++#define STG_SYSCON_AXI4_SLVL_AR_MASK GENMASK(22, 8) ++#define STG_SYSCON_AXI4_SLVL_PHY_AR(x) FIELD_PREP(GENMASK(20, 17), x) ++#define STG_SYSCON_AW_OFFSET 0x7c ++#define STG_SYSCON_AXI4_SLVL_AW_MASK GENMASK(14, 0) ++#define STG_SYSCON_AXI4_SLVL_PHY_AW(x) FIELD_PREP(GENMASK(12, 9), x) ++#define STG_SYSCON_CLKREQ BIT(22) ++#define STG_SYSCON_CKREF_SRC_MASK GENMASK(19, 18) ++#define STG_SYSCON_RP_NEP_OFFSET 0xe8 ++#define STG_SYSCON_K_RP_NEP BIT(8) ++#define STG_SYSCON_LNKSTA_OFFSET 0x170 ++#define DATA_LINK_ACTIVE BIT(5) ++ ++/* Parameters for the waiting for link up routine */ ++#define LINK_WAIT_MAX_RETRIES 10 ++#define LINK_WAIT_USLEEP_MIN 90000 ++#define LINK_WAIT_USLEEP_MAX 100000 ++ ++struct starfive_jh7110_pcie { ++ struct plda_pcie_rp plda; ++ struct reset_control *resets; ++ struct clk_bulk_data *clks; ++ struct regmap *reg_syscon; ++ struct gpio_desc *power_gpio; ++ struct gpio_desc *reset_gpio; ++ struct phy *phy; ++ ++ unsigned int stg_pcie_base; ++ int num_clks; ++}; ++ ++/* ++ * The BAR0/1 of bridge should be hidden during enumeration to ++ * avoid the sizing and resource allocation by PCIe core. ++ */ ++static bool starfive_pcie_hide_rc_bar(struct pci_bus *bus, unsigned int devfn, ++ int offset) ++{ ++ if (pci_is_root_bus(bus) && !devfn && ++ (offset == PCI_BASE_ADDRESS_0 || offset == PCI_BASE_ADDRESS_1)) ++ return true; ++ ++ return false; ++} ++ ++static int starfive_pcie_config_write(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 value) ++{ ++ if (starfive_pcie_hide_rc_bar(bus, devfn, where)) ++ return PCIBIOS_SUCCESSFUL; ++ ++ return pci_generic_config_write(bus, devfn, where, size, value); ++} ++ ++static int starfive_pcie_config_read(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 *value) ++{ ++ if (starfive_pcie_hide_rc_bar(bus, devfn, where)) { ++ *value = 0; ++ return PCIBIOS_SUCCESSFUL; ++ } ++ ++ return pci_generic_config_read(bus, devfn, where, size, value); ++} ++ ++static int starfive_pcie_parse_dt(struct starfive_jh7110_pcie *pcie, ++ struct device *dev) ++{ ++ int domain_nr; ++ ++ pcie->num_clks = devm_clk_bulk_get_all(dev, &pcie->clks); ++ if (pcie->num_clks < 0) ++ return dev_err_probe(dev, pcie->num_clks, ++ "failed to get pcie clocks\n"); ++ ++ pcie->resets = devm_reset_control_array_get_exclusive(dev); ++ if (IS_ERR(pcie->resets)) ++ return dev_err_probe(dev, PTR_ERR(pcie->resets), ++ "failed to get pcie resets"); ++ ++ pcie->reg_syscon = ++ syscon_regmap_lookup_by_phandle(dev->of_node, ++ "starfive,stg-syscon"); ++ ++ if (IS_ERR(pcie->reg_syscon)) ++ return dev_err_probe(dev, PTR_ERR(pcie->reg_syscon), ++ "failed to parse starfive,stg-syscon\n"); ++ ++ pcie->phy = devm_phy_optional_get(dev, NULL); ++ if (IS_ERR(pcie->phy)) ++ return dev_err_probe(dev, PTR_ERR(pcie->phy), ++ "failed to get pcie phy\n"); ++ ++ domain_nr = of_get_pci_domain_nr(dev->of_node); ++ ++ if (domain_nr < 0 || domain_nr > 1) ++ return dev_err_probe(dev, -ENODEV, ++ "failed to get valid pcie domain\n"); ++ ++ if (domain_nr == 0) ++ pcie->stg_pcie_base = STG_SYSCON_PCIE0_BASE; ++ else ++ pcie->stg_pcie_base = STG_SYSCON_PCIE1_BASE; ++ ++ pcie->reset_gpio = devm_gpiod_get_optional(dev, "perst", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(pcie->reset_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pcie->reset_gpio), ++ "failed to get perst-gpio\n"); ++ ++ pcie->power_gpio = devm_gpiod_get_optional(dev, "enable", ++ GPIOD_OUT_LOW); ++ if (IS_ERR(pcie->power_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pcie->power_gpio), ++ "failed to get power-gpio\n"); ++ ++ return 0; ++} ++ ++static struct pci_ops starfive_pcie_ops = { ++ .map_bus = plda_pcie_map_bus, ++ .read = starfive_pcie_config_read, ++ .write = starfive_pcie_config_write, ++}; ++ ++static int starfive_pcie_clk_rst_init(struct starfive_jh7110_pcie *pcie) ++{ ++ struct device *dev = pcie->plda.dev; ++ int ret; ++ ++ ret = clk_bulk_prepare_enable(pcie->num_clks, pcie->clks); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to enable clocks\n"); ++ ++ ret = reset_control_deassert(pcie->resets); ++ if (ret) { ++ clk_bulk_disable_unprepare(pcie->num_clks, pcie->clks); ++ dev_err_probe(dev, ret, "failed to deassert resets\n"); ++ } ++ ++ return ret; ++} ++ ++static void starfive_pcie_clk_rst_deinit(struct starfive_jh7110_pcie *pcie) ++{ ++ reset_control_assert(pcie->resets); ++ clk_bulk_disable_unprepare(pcie->num_clks, pcie->clks); ++} ++ ++static bool starfive_pcie_link_up(struct plda_pcie_rp *plda) ++{ ++ struct starfive_jh7110_pcie *pcie = ++ container_of(plda, struct starfive_jh7110_pcie, plda); ++ int ret; ++ u32 stg_reg_val; ++ ++ ret = regmap_read(pcie->reg_syscon, ++ pcie->stg_pcie_base + STG_SYSCON_LNKSTA_OFFSET, ++ &stg_reg_val); ++ if (ret) { ++ dev_err(pcie->plda.dev, "failed to read link status\n"); ++ return false; ++ } ++ ++ return !!(stg_reg_val & DATA_LINK_ACTIVE); ++} ++ ++static int starfive_pcie_host_wait_for_link(struct starfive_jh7110_pcie *pcie) ++{ ++ int retries; ++ ++ /* Check if the link is up or not */ ++ for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) { ++ if (starfive_pcie_link_up(&pcie->plda)) { ++ dev_info(pcie->plda.dev, "port link up\n"); ++ return 0; ++ } ++ usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX); ++ } ++ ++ return -ETIMEDOUT; ++} ++ ++static int starfive_pcie_enable_phy(struct device *dev, ++ struct starfive_jh7110_pcie *pcie) ++{ ++ int ret; ++ ++ if (!pcie->phy) ++ return 0; ++ ++ ret = phy_init(pcie->phy); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "failed to initialize pcie phy\n"); ++ ++ ret = phy_set_mode(pcie->phy, PHY_MODE_PCIE); ++ if (ret) { ++ dev_err_probe(dev, ret, "failed to set pcie mode\n"); ++ goto err_phy_on; ++ } ++ ++ ret = phy_power_on(pcie->phy); ++ if (ret) { ++ dev_err_probe(dev, ret, "failed to power on pcie phy\n"); ++ goto err_phy_on; ++ } ++ ++ return 0; ++ ++err_phy_on: ++ phy_exit(pcie->phy); ++ return ret; ++} ++ ++static void starfive_pcie_disable_phy(struct starfive_jh7110_pcie *pcie) ++{ ++ phy_power_off(pcie->phy); ++ phy_exit(pcie->phy); ++} ++ ++static void starfive_pcie_host_deinit(struct plda_pcie_rp *plda) ++{ ++ struct starfive_jh7110_pcie *pcie = ++ container_of(plda, struct starfive_jh7110_pcie, plda); ++ ++ starfive_pcie_clk_rst_deinit(pcie); ++ if (pcie->power_gpio) ++ gpiod_set_value_cansleep(pcie->power_gpio, 0); ++ starfive_pcie_disable_phy(pcie); ++} ++ ++static int starfive_pcie_host_init(struct plda_pcie_rp *plda) ++{ ++ struct starfive_jh7110_pcie *pcie = ++ container_of(plda, struct starfive_jh7110_pcie, plda); ++ struct device *dev = plda->dev; ++ int ret; ++ int i; ++ ++ ret = starfive_pcie_enable_phy(dev, pcie); ++ if (ret) ++ return ret; ++ ++ regmap_update_bits(pcie->reg_syscon, ++ pcie->stg_pcie_base + STG_SYSCON_RP_NEP_OFFSET, ++ STG_SYSCON_K_RP_NEP, STG_SYSCON_K_RP_NEP); ++ ++ regmap_update_bits(pcie->reg_syscon, ++ pcie->stg_pcie_base + STG_SYSCON_AW_OFFSET, ++ STG_SYSCON_CKREF_SRC_MASK, ++ FIELD_PREP(STG_SYSCON_CKREF_SRC_MASK, 2)); ++ ++ regmap_update_bits(pcie->reg_syscon, ++ pcie->stg_pcie_base + STG_SYSCON_AW_OFFSET, ++ STG_SYSCON_CLKREQ, STG_SYSCON_CLKREQ); ++ ++ ret = starfive_pcie_clk_rst_init(pcie); ++ if (ret) ++ return ret; ++ ++ if (pcie->power_gpio) ++ gpiod_set_value_cansleep(pcie->power_gpio, 1); ++ ++ if (pcie->reset_gpio) ++ gpiod_set_value_cansleep(pcie->reset_gpio, 1); ++ ++ /* Disable physical functions except #0 */ ++ for (i = 1; i < PCIE_FUNC_NUM; i++) { ++ regmap_update_bits(pcie->reg_syscon, ++ pcie->stg_pcie_base + STG_SYSCON_AR_OFFSET, ++ STG_SYSCON_AXI4_SLVL_AR_MASK, ++ STG_SYSCON_AXI4_SLVL_PHY_AR(i)); ++ ++ regmap_update_bits(pcie->reg_syscon, ++ pcie->stg_pcie_base + STG_SYSCON_AW_OFFSET, ++ STG_SYSCON_AXI4_SLVL_AW_MASK, ++ STG_SYSCON_AXI4_SLVL_PHY_AW(i)); ++ ++ plda_pcie_disable_func(plda); ++ } ++ ++ regmap_update_bits(pcie->reg_syscon, ++ pcie->stg_pcie_base + STG_SYSCON_AR_OFFSET, ++ STG_SYSCON_AXI4_SLVL_AR_MASK, 0); ++ regmap_update_bits(pcie->reg_syscon, ++ pcie->stg_pcie_base + STG_SYSCON_AW_OFFSET, ++ STG_SYSCON_AXI4_SLVL_AW_MASK, 0); ++ ++ plda_pcie_enable_root_port(plda); ++ plda_pcie_write_rc_bar(plda, 0); ++ ++ /* PCIe PCI Standard Configuration Identification Settings. */ ++ plda_pcie_set_standard_class(plda); ++ ++ /* ++ * The LTR message forwarding of PCIe Message Reception was set by core ++ * as default, but the forward id & addr are also need to be reset. ++ * If we do not disable LTR message forwarding here, or set a legal ++ * forwarding address, the kernel will get stuck after the driver probe. ++ * To workaround, disable the LTR message forwarding support on ++ * PCIe Message Reception. ++ */ ++ plda_pcie_disable_ltr(plda); ++ ++ /* Prefetchable memory window 64-bit addressing support */ ++ plda_pcie_set_pref_win_64bit(plda); ++ ++ /* ++ * Ensure that PERST has been asserted for at least 100 ms, ++ * the sleep value is T_PVPERL from PCIe CEM spec r2.0 (Table 2-4) ++ */ ++ msleep(100); ++ if (pcie->reset_gpio) ++ gpiod_set_value_cansleep(pcie->reset_gpio, 0); ++ ++ /* ++ * With a Downstream Port (<=5GT/s), software must wait a minimum ++ * of 100ms following exit from a conventional reset before ++ * sending a configuration request to the device. ++ */ ++ msleep(PCIE_RESET_CONFIG_DEVICE_WAIT_MS); ++ ++ if (starfive_pcie_host_wait_for_link(pcie)) ++ dev_info(dev, "port link down\n"); ++ ++ return 0; ++} ++ ++static const struct plda_pcie_host_ops sf_host_ops = { ++ .host_init = starfive_pcie_host_init, ++ .host_deinit = starfive_pcie_host_deinit, ++}; ++ ++static const struct plda_event stf_pcie_event = { ++ .intx_event = EVENT_PM_MSI_INT_INTX, ++ .msi_event = EVENT_PM_MSI_INT_MSI ++}; ++ ++static int starfive_pcie_probe(struct platform_device *pdev) ++{ ++ struct starfive_jh7110_pcie *pcie; ++ struct device *dev = &pdev->dev; ++ struct plda_pcie_rp *plda; ++ int ret; ++ ++ pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); ++ if (!pcie) ++ return -ENOMEM; ++ ++ plda = &pcie->plda; ++ plda->dev = dev; ++ ++ ret = starfive_pcie_parse_dt(pcie, dev); ++ if (ret) ++ return ret; ++ ++ plda->host_ops = &sf_host_ops; ++ plda->num_events = PLDA_MAX_EVENT_NUM; ++ /* mask doorbell event */ ++ plda->events_bitmap = GENMASK(PLDA_INT_EVENT_NUM - 1, 0) ++ & ~BIT(PLDA_AXI_DOORBELL) ++ & ~BIT(PLDA_PCIE_DOORBELL); ++ plda->events_bitmap <<= PLDA_NUM_DMA_EVENTS; ++ ret = plda_pcie_host_init(&pcie->plda, &starfive_pcie_ops, ++ &stf_pcie_event); ++ if (ret) ++ return ret; ++ ++ pm_runtime_enable(&pdev->dev); ++ pm_runtime_get_sync(&pdev->dev); ++ platform_set_drvdata(pdev, pcie); ++ ++ return 0; ++} ++ ++static void starfive_pcie_remove(struct platform_device *pdev) ++{ ++ struct starfive_jh7110_pcie *pcie = platform_get_drvdata(pdev); ++ ++ pm_runtime_put(&pdev->dev); ++ pm_runtime_disable(&pdev->dev); ++ plda_pcie_host_deinit(&pcie->plda); ++ platform_set_drvdata(pdev, NULL); ++} ++ ++static int starfive_pcie_suspend_noirq(struct device *dev) ++{ ++ struct starfive_jh7110_pcie *pcie = dev_get_drvdata(dev); ++ ++ clk_bulk_disable_unprepare(pcie->num_clks, pcie->clks); ++ starfive_pcie_disable_phy(pcie); ++ ++ return 0; ++} ++ ++static int starfive_pcie_resume_noirq(struct device *dev) ++{ ++ struct starfive_jh7110_pcie *pcie = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = starfive_pcie_enable_phy(dev, pcie); ++ if (ret) ++ return ret; ++ ++ ret = clk_bulk_prepare_enable(pcie->num_clks, pcie->clks); ++ if (ret) { ++ dev_err(dev, "failed to enable clocks\n"); ++ starfive_pcie_disable_phy(pcie); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops starfive_pcie_pm_ops = { ++ NOIRQ_SYSTEM_SLEEP_PM_OPS(starfive_pcie_suspend_noirq, ++ starfive_pcie_resume_noirq) ++}; ++ ++static const struct of_device_id starfive_pcie_of_match[] = { ++ { .compatible = "starfive,jh7110-pcie", }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, starfive_pcie_of_match); ++ ++static struct platform_driver starfive_pcie_driver = { ++ .driver = { ++ .name = "pcie-starfive", ++ .of_match_table = of_match_ptr(starfive_pcie_of_match), ++ .pm = pm_sleep_ptr(&starfive_pcie_pm_ops), ++ }, ++ .probe = starfive_pcie_probe, ++ .remove_new = starfive_pcie_remove, ++}; ++module_platform_driver(starfive_pcie_driver); ++ ++MODULE_DESCRIPTION("StarFive JH7110 PCIe host driver"); ++MODULE_LICENSE("GPL v2"); diff --git a/target/linux/starfive/patches-6.6/0035-ASoC-dt-bindings-Add-StarFive-JH7110-PWM-DAC-control.patch b/target/linux/starfive/patches-6.6/0035-ASoC-dt-bindings-Add-StarFive-JH7110-PWM-DAC-control.patch new file mode 100644 index 0000000000..cf14242f4a --- /dev/null +++ b/target/linux/starfive/patches-6.6/0035-ASoC-dt-bindings-Add-StarFive-JH7110-PWM-DAC-control.patch @@ -0,0 +1,97 @@ +From a306724fd4f32808d1e27efbd87019d56f60db20 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Mon, 14 Aug 2023 16:06:16 +0800 +Subject: [PATCH 035/116] ASoC: dt-bindings: Add StarFive JH7110 PWM-DAC + controller + +Add bindings for the PWM-DAC controller on the JH7110 +RISC-V SoC by StarFive Ltd. + +Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +Link: https://lore.kernel.org/r/20230814080618.10036-2-hal.feng@starfivetech.com +Signed-off-by: Mark Brown <broonie@kernel.org> +--- + .../sound/starfive,jh7110-pwmdac.yaml | 76 +++++++++++++++++++ + 1 file changed, 76 insertions(+) + create mode 100644 Documentation/devicetree/bindings/sound/starfive,jh7110-pwmdac.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/sound/starfive,jh7110-pwmdac.yaml +@@ -0,0 +1,76 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/sound/starfive,jh7110-pwmdac.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: StarFive JH7110 PWM-DAC Controller ++ ++description: ++ The PWM-DAC Controller uses PWM square wave generators plus RC filters to ++ form a DAC for audio play in StarFive JH7110 SoC. This audio play controller ++ supports 16 bit audio format, up to 48K sampling frequency, up to left and ++ right dual channels. ++ ++maintainers: ++ - Hal Feng <hal.feng@starfivetech.com> ++ ++allOf: ++ - $ref: dai-common.yaml# ++ ++properties: ++ compatible: ++ const: starfive,jh7110-pwmdac ++ ++ reg: ++ maxItems: 1 ++ ++ clocks: ++ items: ++ - description: PWMDAC APB ++ - description: PWMDAC CORE ++ ++ clock-names: ++ items: ++ - const: apb ++ - const: core ++ ++ resets: ++ maxItems: 1 ++ description: PWMDAC APB ++ ++ dmas: ++ maxItems: 1 ++ description: TX DMA Channel ++ ++ dma-names: ++ const: tx ++ ++ "#sound-dai-cells": ++ const: 0 ++ ++required: ++ - compatible ++ - reg ++ - clocks ++ - clock-names ++ - resets ++ - dmas ++ - dma-names ++ - "#sound-dai-cells" ++ ++additionalProperties: false ++ ++examples: ++ - | ++ pwmdac@100b0000 { ++ compatible = "starfive,jh7110-pwmdac"; ++ reg = <0x100b0000 0x1000>; ++ clocks = <&syscrg 157>, ++ <&syscrg 158>; ++ clock-names = "apb", "core"; ++ resets = <&syscrg 96>; ++ dmas = <&dma 22>; ++ dma-names = "tx"; ++ #sound-dai-cells = <0>; ++ }; diff --git a/target/linux/starfive/patches-6.6/0036-ASoC-starfive-Add-JH7110-PWM-DAC-driver.patch b/target/linux/starfive/patches-6.6/0036-ASoC-starfive-Add-JH7110-PWM-DAC-driver.patch new file mode 100644 index 0000000000..acc859317b --- /dev/null +++ b/target/linux/starfive/patches-6.6/0036-ASoC-starfive-Add-JH7110-PWM-DAC-driver.patch @@ -0,0 +1,574 @@ +From a79d2ec524012e35e32a2c4ae2401d0aa763697d Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Mon, 14 Aug 2023 16:06:17 +0800 +Subject: [PATCH 036/116] ASoC: starfive: Add JH7110 PWM-DAC driver + +Add PWM-DAC driver support for the StarFive JH7110 SoC. + +Reviewed-by: Walker Chen <walker.chen@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +Link: https://lore.kernel.org/r/20230814080618.10036-3-hal.feng@starfivetech.com +Signed-off-by: Mark Brown <broonie@kernel.org> +--- + sound/soc/starfive/Kconfig | 9 + + sound/soc/starfive/Makefile | 1 + + sound/soc/starfive/jh7110_pwmdac.c | 529 +++++++++++++++++++++++++++++ + 3 files changed, 539 insertions(+) + create mode 100644 sound/soc/starfive/jh7110_pwmdac.c + +--- a/sound/soc/starfive/Kconfig ++++ b/sound/soc/starfive/Kconfig +@@ -7,6 +7,15 @@ config SND_SOC_STARFIVE + the Starfive SoCs' Audio interfaces. You will also need to + select the audio interfaces to support below. + ++config SND_SOC_JH7110_PWMDAC ++ tristate "JH7110 PWM-DAC device driver" ++ depends on HAVE_CLK && SND_SOC_STARFIVE ++ select SND_SOC_GENERIC_DMAENGINE_PCM ++ select SND_SOC_SPDIF ++ help ++ Say Y or M if you want to add support for StarFive JH7110 ++ PWM-DAC driver. ++ + config SND_SOC_JH7110_TDM + tristate "JH7110 TDM device driver" + depends on HAVE_CLK && SND_SOC_STARFIVE +--- a/sound/soc/starfive/Makefile ++++ b/sound/soc/starfive/Makefile +@@ -1,2 +1,3 @@ + # StarFive Platform Support ++obj-$(CONFIG_SND_SOC_JH7110_PWMDAC) += jh7110_pwmdac.o + obj-$(CONFIG_SND_SOC_JH7110_TDM) += jh7110_tdm.o +--- /dev/null ++++ b/sound/soc/starfive/jh7110_pwmdac.c +@@ -0,0 +1,529 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * jh7110_pwmdac.c -- StarFive JH7110 PWM-DAC driver ++ * ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ * Authors: Jenny Zhang ++ * Curry Zhang ++ * Xingyu Wu <xingyu.wu@starfivetech.com> ++ * Hal Feng <hal.feng@starfivetech.com> ++ */ ++ ++#include <linux/clk.h> ++#include <linux/device.h> ++#include <linux/init.h> ++#include <linux/interrupt.h> ++#include <linux/io.h> ++#include <linux/module.h> ++#include <linux/pm_runtime.h> ++#include <linux/reset.h> ++#include <linux/slab.h> ++#include <linux/types.h> ++#include <sound/dmaengine_pcm.h> ++#include <sound/pcm.h> ++#include <sound/pcm_params.h> ++#include <sound/soc.h> ++ ++#define JH7110_PWMDAC_WDATA 0x00 ++#define JH7110_PWMDAC_CTRL 0x04 ++ #define JH7110_PWMDAC_ENABLE BIT(0) ++ #define JH7110_PWMDAC_SHIFT BIT(1) ++ #define JH7110_PWMDAC_DUTY_CYCLE_SHIFT 2 ++ #define JH7110_PWMDAC_DUTY_CYCLE_MASK GENMASK(3, 2) ++ #define JH7110_PWMDAC_CNT_N_SHIFT 4 ++ #define JH7110_PWMDAC_CNT_N_MASK GENMASK(12, 4) ++ #define JH7110_PWMDAC_DATA_CHANGE BIT(13) ++ #define JH7110_PWMDAC_DATA_MODE BIT(14) ++ #define JH7110_PWMDAC_DATA_SHIFT_SHIFT 15 ++ #define JH7110_PWMDAC_DATA_SHIFT_MASK GENMASK(17, 15) ++ ++enum JH7110_PWMDAC_SHIFT_VAL { ++ PWMDAC_SHIFT_8 = 0, ++ PWMDAC_SHIFT_10, ++}; ++ ++enum JH7110_PWMDAC_DUTY_CYCLE_VAL { ++ PWMDAC_CYCLE_LEFT = 0, ++ PWMDAC_CYCLE_RIGHT, ++ PWMDAC_CYCLE_CENTER, ++}; ++ ++enum JH7110_PWMDAC_CNT_N_VAL { ++ PWMDAC_SAMPLE_CNT_1 = 1, ++ PWMDAC_SAMPLE_CNT_2, ++ PWMDAC_SAMPLE_CNT_3, ++ PWMDAC_SAMPLE_CNT_512 = 512, /* max */ ++}; ++ ++enum JH7110_PWMDAC_DATA_CHANGE_VAL { ++ NO_CHANGE = 0, ++ CHANGE, ++}; ++ ++enum JH7110_PWMDAC_DATA_MODE_VAL { ++ UNSIGNED_DATA = 0, ++ INVERTER_DATA_MSB, ++}; ++ ++enum JH7110_PWMDAC_DATA_SHIFT_VAL { ++ PWMDAC_DATA_LEFT_SHIFT_BIT_0 = 0, ++ PWMDAC_DATA_LEFT_SHIFT_BIT_1, ++ PWMDAC_DATA_LEFT_SHIFT_BIT_2, ++ PWMDAC_DATA_LEFT_SHIFT_BIT_3, ++ PWMDAC_DATA_LEFT_SHIFT_BIT_4, ++ PWMDAC_DATA_LEFT_SHIFT_BIT_5, ++ PWMDAC_DATA_LEFT_SHIFT_BIT_6, ++ PWMDAC_DATA_LEFT_SHIFT_BIT_7, ++}; ++ ++struct jh7110_pwmdac_cfg { ++ enum JH7110_PWMDAC_SHIFT_VAL shift; ++ enum JH7110_PWMDAC_DUTY_CYCLE_VAL duty_cycle; ++ u16 cnt_n; ++ enum JH7110_PWMDAC_DATA_CHANGE_VAL data_change; ++ enum JH7110_PWMDAC_DATA_MODE_VAL data_mode; ++ enum JH7110_PWMDAC_DATA_SHIFT_VAL data_shift; ++}; ++ ++struct jh7110_pwmdac_dev { ++ void __iomem *base; ++ resource_size_t mapbase; ++ struct jh7110_pwmdac_cfg cfg; ++ ++ struct clk_bulk_data clks[2]; ++ struct reset_control *rst_apb; ++ struct device *dev; ++ struct snd_dmaengine_dai_dma_data play_dma_data; ++ u32 saved_ctrl; ++}; ++ ++static inline void jh7110_pwmdac_write_reg(void __iomem *io_base, int reg, u32 val) ++{ ++ writel(val, io_base + reg); ++} ++ ++static inline u32 jh7110_pwmdac_read_reg(void __iomem *io_base, int reg) ++{ ++ return readl(io_base + reg); ++} ++ ++static void jh7110_pwmdac_set_enable(struct jh7110_pwmdac_dev *dev, bool enable) ++{ ++ u32 value; ++ ++ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL); ++ if (enable) ++ value |= JH7110_PWMDAC_ENABLE; ++ else ++ value &= ~JH7110_PWMDAC_ENABLE; ++ ++ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value); ++} ++ ++static void jh7110_pwmdac_set_shift(struct jh7110_pwmdac_dev *dev) ++{ ++ u32 value; ++ ++ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL); ++ if (dev->cfg.shift == PWMDAC_SHIFT_8) ++ value &= ~JH7110_PWMDAC_SHIFT; ++ else if (dev->cfg.shift == PWMDAC_SHIFT_10) ++ value |= JH7110_PWMDAC_SHIFT; ++ ++ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value); ++} ++ ++static void jh7110_pwmdac_set_duty_cycle(struct jh7110_pwmdac_dev *dev) ++{ ++ u32 value; ++ ++ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL); ++ value &= ~JH7110_PWMDAC_DUTY_CYCLE_MASK; ++ value |= (dev->cfg.duty_cycle & 0x3) << JH7110_PWMDAC_DUTY_CYCLE_SHIFT; ++ ++ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value); ++} ++ ++static void jh7110_pwmdac_set_cnt_n(struct jh7110_pwmdac_dev *dev) ++{ ++ u32 value; ++ ++ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL); ++ value &= ~JH7110_PWMDAC_CNT_N_MASK; ++ value |= ((dev->cfg.cnt_n - 1) & 0x1ff) << JH7110_PWMDAC_CNT_N_SHIFT; ++ ++ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value); ++} ++ ++static void jh7110_pwmdac_set_data_change(struct jh7110_pwmdac_dev *dev) ++{ ++ u32 value; ++ ++ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL); ++ if (dev->cfg.data_change == NO_CHANGE) ++ value &= ~JH7110_PWMDAC_DATA_CHANGE; ++ else if (dev->cfg.data_change == CHANGE) ++ value |= JH7110_PWMDAC_DATA_CHANGE; ++ ++ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value); ++} ++ ++static void jh7110_pwmdac_set_data_mode(struct jh7110_pwmdac_dev *dev) ++{ ++ u32 value; ++ ++ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL); ++ if (dev->cfg.data_mode == UNSIGNED_DATA) ++ value &= ~JH7110_PWMDAC_DATA_MODE; ++ else if (dev->cfg.data_mode == INVERTER_DATA_MSB) ++ value |= JH7110_PWMDAC_DATA_MODE; ++ ++ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value); ++} ++ ++static void jh7110_pwmdac_set_data_shift(struct jh7110_pwmdac_dev *dev) ++{ ++ u32 value; ++ ++ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL); ++ value &= ~JH7110_PWMDAC_DATA_SHIFT_MASK; ++ value |= (dev->cfg.data_shift & 0x7) << JH7110_PWMDAC_DATA_SHIFT_SHIFT; ++ ++ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value); ++} ++ ++static void jh7110_pwmdac_set(struct jh7110_pwmdac_dev *dev) ++{ ++ jh7110_pwmdac_set_shift(dev); ++ jh7110_pwmdac_set_duty_cycle(dev); ++ jh7110_pwmdac_set_cnt_n(dev); ++ jh7110_pwmdac_set_enable(dev, true); ++ ++ jh7110_pwmdac_set_data_change(dev); ++ jh7110_pwmdac_set_data_mode(dev); ++ jh7110_pwmdac_set_data_shift(dev); ++} ++ ++static void jh7110_pwmdac_stop(struct jh7110_pwmdac_dev *dev) ++{ ++ jh7110_pwmdac_set_enable(dev, false); ++} ++ ++static int jh7110_pwmdac_startup(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); ++ struct snd_soc_dai_link *dai_link = rtd->dai_link; ++ ++ dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC; ++ ++ return 0; ++} ++ ++static int jh7110_pwmdac_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct jh7110_pwmdac_dev *dev = dev_get_drvdata(dai->dev); ++ unsigned long core_clk_rate; ++ int ret; ++ ++ switch (params_rate(params)) { ++ case 8000: ++ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_3; ++ core_clk_rate = 6144000; ++ break; ++ case 11025: ++ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_2; ++ core_clk_rate = 5644800; ++ break; ++ case 16000: ++ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_3; ++ core_clk_rate = 12288000; ++ break; ++ case 22050: ++ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1; ++ core_clk_rate = 5644800; ++ break; ++ case 32000: ++ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1; ++ core_clk_rate = 8192000; ++ break; ++ case 44100: ++ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1; ++ core_clk_rate = 11289600; ++ break; ++ case 48000: ++ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1; ++ core_clk_rate = 12288000; ++ break; ++ default: ++ dev_err(dai->dev, "%d rate not supported\n", ++ params_rate(params)); ++ return -EINVAL; ++ } ++ ++ switch (params_channels(params)) { ++ case 1: ++ dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; ++ break; ++ case 2: ++ dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; ++ break; ++ default: ++ dev_err(dai->dev, "%d channels not supported\n", ++ params_channels(params)); ++ return -EINVAL; ++ } ++ ++ /* ++ * The clock rate always rounds down when using clk_set_rate() ++ * so increase the rate a bit ++ */ ++ core_clk_rate += 64; ++ jh7110_pwmdac_set(dev); ++ ++ ret = clk_set_rate(dev->clks[1].clk, core_clk_rate); ++ if (ret) ++ return dev_err_probe(dai->dev, ret, ++ "failed to set rate %lu for core clock\n", ++ core_clk_rate); ++ ++ return 0; ++} ++ ++static int jh7110_pwmdac_trigger(struct snd_pcm_substream *substream, int cmd, ++ struct snd_soc_dai *dai) ++{ ++ struct jh7110_pwmdac_dev *dev = snd_soc_dai_get_drvdata(dai); ++ int ret = 0; ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ jh7110_pwmdac_set(dev); ++ break; ++ ++ case SNDRV_PCM_TRIGGER_STOP: ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ jh7110_pwmdac_stop(dev); ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int jh7110_pwmdac_crg_enable(struct jh7110_pwmdac_dev *dev, bool enable) ++{ ++ int ret; ++ ++ if (enable) { ++ ret = clk_bulk_prepare_enable(ARRAY_SIZE(dev->clks), dev->clks); ++ if (ret) ++ return dev_err_probe(dev->dev, ret, ++ "failed to enable pwmdac clocks\n"); ++ ++ ret = reset_control_deassert(dev->rst_apb); ++ if (ret) { ++ dev_err(dev->dev, "failed to deassert pwmdac apb reset\n"); ++ goto err_rst_apb; ++ } ++ } else { ++ clk_bulk_disable_unprepare(ARRAY_SIZE(dev->clks), dev->clks); ++ } ++ ++ return 0; ++ ++err_rst_apb: ++ clk_bulk_disable_unprepare(ARRAY_SIZE(dev->clks), dev->clks); ++ ++ return ret; ++} ++ ++static int jh7110_pwmdac_dai_probe(struct snd_soc_dai *dai) ++{ ++ struct jh7110_pwmdac_dev *dev = dev_get_drvdata(dai->dev); ++ ++ snd_soc_dai_init_dma_data(dai, &dev->play_dma_data, NULL); ++ snd_soc_dai_set_drvdata(dai, dev); ++ ++ return 0; ++} ++ ++static const struct snd_soc_dai_ops jh7110_pwmdac_dai_ops = { ++ .startup = jh7110_pwmdac_startup, ++ .hw_params = jh7110_pwmdac_hw_params, ++ .trigger = jh7110_pwmdac_trigger, ++}; ++ ++static const struct snd_soc_component_driver jh7110_pwmdac_component = { ++ .name = "jh7110-pwmdac", ++}; ++ ++static struct snd_soc_dai_driver jh7110_pwmdac_dai = { ++ .name = "jh7110-pwmdac", ++ .id = 0, ++ .probe = jh7110_pwmdac_dai_probe, ++ .playback = { ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE, ++ }, ++ .ops = &jh7110_pwmdac_dai_ops, ++}; ++ ++static int jh7110_pwmdac_runtime_suspend(struct device *dev) ++{ ++ struct jh7110_pwmdac_dev *pwmdac = dev_get_drvdata(dev); ++ ++ return jh7110_pwmdac_crg_enable(pwmdac, false); ++} ++ ++static int jh7110_pwmdac_runtime_resume(struct device *dev) ++{ ++ struct jh7110_pwmdac_dev *pwmdac = dev_get_drvdata(dev); ++ ++ return jh7110_pwmdac_crg_enable(pwmdac, true); ++} ++ ++static int jh7110_pwmdac_system_suspend(struct device *dev) ++{ ++ struct jh7110_pwmdac_dev *pwmdac = dev_get_drvdata(dev); ++ ++ /* save the CTRL register value */ ++ pwmdac->saved_ctrl = jh7110_pwmdac_read_reg(pwmdac->base, ++ JH7110_PWMDAC_CTRL); ++ return pm_runtime_force_suspend(dev); ++} ++ ++static int jh7110_pwmdac_system_resume(struct device *dev) ++{ ++ struct jh7110_pwmdac_dev *pwmdac = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = pm_runtime_force_resume(dev); ++ if (ret) ++ return ret; ++ ++ /* restore the CTRL register value */ ++ jh7110_pwmdac_write_reg(pwmdac->base, JH7110_PWMDAC_CTRL, ++ pwmdac->saved_ctrl); ++ return 0; ++} ++ ++static const struct dev_pm_ops jh7110_pwmdac_pm_ops = { ++ RUNTIME_PM_OPS(jh7110_pwmdac_runtime_suspend, ++ jh7110_pwmdac_runtime_resume, NULL) ++ SYSTEM_SLEEP_PM_OPS(jh7110_pwmdac_system_suspend, ++ jh7110_pwmdac_system_resume) ++}; ++ ++static void jh7110_pwmdac_init_params(struct jh7110_pwmdac_dev *dev) ++{ ++ dev->cfg.shift = PWMDAC_SHIFT_8; ++ dev->cfg.duty_cycle = PWMDAC_CYCLE_CENTER; ++ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1; ++ dev->cfg.data_change = NO_CHANGE; ++ dev->cfg.data_mode = INVERTER_DATA_MSB; ++ dev->cfg.data_shift = PWMDAC_DATA_LEFT_SHIFT_BIT_0; ++ ++ dev->play_dma_data.addr = dev->mapbase + JH7110_PWMDAC_WDATA; ++ dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; ++ dev->play_dma_data.fifo_size = 1; ++ dev->play_dma_data.maxburst = 16; ++} ++ ++static int jh7110_pwmdac_probe(struct platform_device *pdev) ++{ ++ struct jh7110_pwmdac_dev *dev; ++ struct resource *res; ++ int ret; ++ ++ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); ++ if (!dev) ++ return -ENOMEM; ++ ++ dev->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); ++ if (IS_ERR(dev->base)) ++ return PTR_ERR(dev->base); ++ ++ dev->mapbase = res->start; ++ ++ dev->clks[0].id = "apb"; ++ dev->clks[1].id = "core"; ++ ++ ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(dev->clks), dev->clks); ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "failed to get pwmdac clocks\n"); ++ ++ dev->rst_apb = devm_reset_control_get_exclusive(&pdev->dev, NULL); ++ if (IS_ERR(dev->rst_apb)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(dev->rst_apb), ++ "failed to get pwmdac apb reset\n"); ++ ++ jh7110_pwmdac_init_params(dev); ++ ++ dev->dev = &pdev->dev; ++ dev_set_drvdata(&pdev->dev, dev); ++ ret = devm_snd_soc_register_component(&pdev->dev, ++ &jh7110_pwmdac_component, ++ &jh7110_pwmdac_dai, 1); ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, "failed to register dai\n"); ++ ++ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n"); ++ ++ pm_runtime_enable(dev->dev); ++ if (!pm_runtime_enabled(&pdev->dev)) { ++ ret = jh7110_pwmdac_runtime_resume(&pdev->dev); ++ if (ret) ++ goto err_pm_disable; ++ } ++ ++ return 0; ++ ++err_pm_disable: ++ pm_runtime_disable(&pdev->dev); ++ ++ return ret; ++} ++ ++static int jh7110_pwmdac_remove(struct platform_device *pdev) ++{ ++ pm_runtime_disable(&pdev->dev); ++ return 0; ++} ++ ++static const struct of_device_id jh7110_pwmdac_of_match[] = { ++ { .compatible = "starfive,jh7110-pwmdac" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, jh7110_pwmdac_of_match); ++ ++static struct platform_driver jh7110_pwmdac_driver = { ++ .driver = { ++ .name = "jh7110-pwmdac", ++ .of_match_table = jh7110_pwmdac_of_match, ++ .pm = pm_ptr(&jh7110_pwmdac_pm_ops), ++ }, ++ .probe = jh7110_pwmdac_probe, ++ .remove = jh7110_pwmdac_remove, ++}; ++module_platform_driver(jh7110_pwmdac_driver); ++ ++MODULE_AUTHOR("Jenny Zhang"); ++MODULE_AUTHOR("Curry Zhang"); ++MODULE_AUTHOR("Xingyu Wu <xingyu.wu@starfivetech.com>"); ++MODULE_AUTHOR("Hal Feng <hal.feng@starfivetech.com>"); ++MODULE_DESCRIPTION("StarFive JH7110 PWM-DAC driver"); ++MODULE_LICENSE("GPL"); diff --git a/target/linux/starfive/patches-6.6/0037-ASoC-Update-jh7110-PWM-DAC-for-ops-move.patch b/target/linux/starfive/patches-6.6/0037-ASoC-Update-jh7110-PWM-DAC-for-ops-move.patch new file mode 100644 index 0000000000..4833067bac --- /dev/null +++ b/target/linux/starfive/patches-6.6/0037-ASoC-Update-jh7110-PWM-DAC-for-ops-move.patch @@ -0,0 +1,32 @@ +From c6b693f990e1f89ab5af0a139da31401b8cda74f Mon Sep 17 00:00:00 2001 +From: Mark Brown <broonie@kernel.org> +Date: Mon, 11 Sep 2023 23:48:39 +0100 +Subject: [PATCH 037/116] ASoC: Update jh7110 PWM DAC for ops move + +For some reason the JH7110 PWM DAC driver made it through build testing +in spite of not being updated for the move of probe() to the ops struct. +Make the required update. + +Signed-off-by: Mark Brown <broonie@kernel.org> +--- + sound/soc/starfive/jh7110_pwmdac.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/sound/soc/starfive/jh7110_pwmdac.c ++++ b/sound/soc/starfive/jh7110_pwmdac.c +@@ -357,6 +357,7 @@ static int jh7110_pwmdac_dai_probe(struc + } + + static const struct snd_soc_dai_ops jh7110_pwmdac_dai_ops = { ++ .probe = jh7110_pwmdac_dai_probe, + .startup = jh7110_pwmdac_startup, + .hw_params = jh7110_pwmdac_hw_params, + .trigger = jh7110_pwmdac_trigger, +@@ -369,7 +370,6 @@ static const struct snd_soc_component_dr + static struct snd_soc_dai_driver jh7110_pwmdac_dai = { + .name = "jh7110-pwmdac", + .id = 0, +- .probe = jh7110_pwmdac_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, diff --git a/target/linux/starfive/patches-6.6/0038-ASoC-starfive-jh7110-pwmdac-Convert-to-platform-remo.patch b/target/linux/starfive/patches-6.6/0038-ASoC-starfive-jh7110-pwmdac-Convert-to-platform-remo.patch new file mode 100644 index 0000000000..9b5f553f8c --- /dev/null +++ b/target/linux/starfive/patches-6.6/0038-ASoC-starfive-jh7110-pwmdac-Convert-to-platform-remo.patch @@ -0,0 +1,52 @@ +From 312c3c407c363f0ec7417d2d389cbe936c503729 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= <u.kleine-koenig@pengutronix.de> +Date: Sat, 14 Oct 2023 00:19:49 +0200 +Subject: [PATCH 038/116] ASoC: starfive/jh7110-pwmdac: Convert to platform + remove callback returning void +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The .remove() callback for a platform driver returns an int which makes +many driver authors wrongly assume it's possible to do error handling by +returning an error code. However the value returned is ignored (apart +from emitting a warning) and this typically results in resource leaks. + +To improve here there is a quest to make the remove callback return +void. In the first step of this quest all drivers are converted to +.remove_new(), which already returns void. Eventually after all drivers +are converted, .remove_new() will be renamed to .remove(). + +Trivially convert this driver from always returning zero in the remove +callback to the void returning variant. + +Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> +Link: https://lore.kernel.org/r/20231013221945.1489203-12-u.kleine-koenig@pengutronix.de +Signed-off-by: Mark Brown <broonie@kernel.org> +--- + sound/soc/starfive/jh7110_pwmdac.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +--- a/sound/soc/starfive/jh7110_pwmdac.c ++++ b/sound/soc/starfive/jh7110_pwmdac.c +@@ -498,10 +498,9 @@ err_pm_disable: + return ret; + } + +-static int jh7110_pwmdac_remove(struct platform_device *pdev) ++static void jh7110_pwmdac_remove(struct platform_device *pdev) + { + pm_runtime_disable(&pdev->dev); +- return 0; + } + + static const struct of_device_id jh7110_pwmdac_of_match[] = { +@@ -517,7 +516,7 @@ static struct platform_driver jh7110_pwm + .pm = pm_ptr(&jh7110_pwmdac_pm_ops), + }, + .probe = jh7110_pwmdac_probe, +- .remove = jh7110_pwmdac_remove, ++ .remove_new = jh7110_pwmdac_remove, + }; + module_platform_driver(jh7110_pwmdac_driver); + diff --git a/target/linux/starfive/patches-6.6/0039-dt-bindings-power-Add-power-domain-header-for-JH7110.patch b/target/linux/starfive/patches-6.6/0039-dt-bindings-power-Add-power-domain-header-for-JH7110.patch new file mode 100644 index 0000000000..bcc9af2d2c --- /dev/null +++ b/target/linux/starfive/patches-6.6/0039-dt-bindings-power-Add-power-domain-header-for-JH7110.patch @@ -0,0 +1,35 @@ +From 8d84bac6d7471ba2e29b33d19a2ef887822e9cce Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Wed, 13 Sep 2023 14:54:25 +0100 +Subject: [PATCH 039/116] dt-bindings: power: Add power-domain header for + JH7110 + +Add power-domain header for JH7110 SoC, it can use to operate dphy +power. + +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +Signed-off-by: Conor Dooley <conor.dooley@microchip.com> +Link: https://lore.kernel.org/r/20230913-grumbly-rewrite-34c85539f2ed@spud +Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> +--- + include/dt-bindings/power/starfive,jh7110-pmu.h | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +--- a/include/dt-bindings/power/starfive,jh7110-pmu.h ++++ b/include/dt-bindings/power/starfive,jh7110-pmu.h +@@ -1,6 +1,6 @@ + /* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ + /* +- * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ * Copyright (C) 2022-2023 StarFive Technology Co., Ltd. + * Author: Walker Chen <walker.chen@starfivetech.com> + */ + #ifndef __DT_BINDINGS_POWER_JH7110_POWER_H__ +@@ -14,4 +14,7 @@ + #define JH7110_PD_ISP 5 + #define JH7110_PD_VENC 6 + ++#define JH7110_PD_DPHY_TX 0 ++#define JH7110_PD_DPHY_RX 1 ++ + #endif diff --git a/target/linux/starfive/patches-6.6/0040-pmdomain-starfive-Replace-SOC_STARFIVE-with-ARCH_STA.patch b/target/linux/starfive/patches-6.6/0040-pmdomain-starfive-Replace-SOC_STARFIVE-with-ARCH_STA.patch new file mode 100644 index 0000000000..335193fbb6 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0040-pmdomain-starfive-Replace-SOC_STARFIVE-with-ARCH_STA.patch @@ -0,0 +1,31 @@ +From 0ac8e8b0e65d242f455401df0cc6c6d4772216e6 Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Wed, 13 Sep 2023 14:54:26 +0100 +Subject: [PATCH 040/116] pmdomain: starfive: Replace SOC_STARFIVE with + ARCH_STARFIVE + +Using ARCH_FOO symbol is preferred than SOC_FOO. + +Reviewed-by: Conor Dooley <conor.dooley@microchip.com> +Reviewed-by: Walker Chen <walker.chen@starfivetech.com> +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +Signed-off-by: Conor Dooley <conor.dooley@microchip.com> +Link: https://lore.kernel.org/r/20230913-legibly-treachery-567cffcb5604@spud +Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> +--- + drivers/soc/starfive/Kconfig | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/soc/starfive/Kconfig ++++ b/drivers/soc/starfive/Kconfig +@@ -3,8 +3,8 @@ + config JH71XX_PMU + bool "Support PMU for StarFive JH71XX Soc" + depends on PM +- depends on SOC_STARFIVE || COMPILE_TEST +- default SOC_STARFIVE ++ depends on ARCH_STARFIVE || COMPILE_TEST ++ default ARCH_STARFIVE + select PM_GENERIC_DOMAINS + help + Say 'y' here to enable support power domain support. diff --git a/target/linux/starfive/patches-6.6/0041-pmdomain-starfive-Extract-JH7110-pmu-private-operati.patch b/target/linux/starfive/patches-6.6/0041-pmdomain-starfive-Extract-JH7110-pmu-private-operati.patch new file mode 100644 index 0000000000..7425dd6dbb --- /dev/null +++ b/target/linux/starfive/patches-6.6/0041-pmdomain-starfive-Extract-JH7110-pmu-private-operati.patch @@ -0,0 +1,179 @@ +From a1ba60e35ca7f1b85243054556ecde2e259619e1 Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Wed, 13 Sep 2023 14:54:27 +0100 +Subject: [PATCH 041/116] pmdomain: starfive: Extract JH7110 pmu private + operations + +Move JH7110 private operation into private data of compatible. Convenient +to add AON PMU which would not have interrupts property. + +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +Reviewed-by: Walker Chen <walker.chen@starfivetech.com> +Signed-off-by: Conor Dooley <conor.dooley@microchip.com> +Link: https://lore.kernel.org/r/20230913-slideshow-luckiness-38ff17de84c6@spud +Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> +--- + drivers/pmdomain/starfive/jh71xx-pmu.c | 89 ++++++++++++++++++-------- + 1 file changed, 62 insertions(+), 27 deletions(-) + +--- a/drivers/pmdomain/starfive/jh71xx-pmu.c ++++ b/drivers/pmdomain/starfive/jh71xx-pmu.c +@@ -51,9 +51,17 @@ struct jh71xx_domain_info { + u8 bit; + }; + ++struct jh71xx_pmu; ++struct jh71xx_pmu_dev; ++ + struct jh71xx_pmu_match_data { + const struct jh71xx_domain_info *domain_info; + int num_domains; ++ unsigned int pmu_status; ++ int (*pmu_parse_irq)(struct platform_device *pdev, ++ struct jh71xx_pmu *pmu); ++ int (*pmu_set_state)(struct jh71xx_pmu_dev *pmd, ++ u32 mask, bool on); + }; + + struct jh71xx_pmu { +@@ -79,12 +87,12 @@ static int jh71xx_pmu_get_state(struct j + if (!mask) + return -EINVAL; + +- *is_on = readl(pmu->base + JH71XX_PMU_CURR_POWER_MODE) & mask; ++ *is_on = readl(pmu->base + pmu->match_data->pmu_status) & mask; + + return 0; + } + +-static int jh71xx_pmu_set_state(struct jh71xx_pmu_dev *pmd, u32 mask, bool on) ++static int jh7110_pmu_set_state(struct jh71xx_pmu_dev *pmd, u32 mask, bool on) + { + struct jh71xx_pmu *pmu = pmd->pmu; + unsigned long flags; +@@ -92,22 +100,8 @@ static int jh71xx_pmu_set_state(struct j + u32 mode; + u32 encourage_lo; + u32 encourage_hi; +- bool is_on; + int ret; + +- ret = jh71xx_pmu_get_state(pmd, mask, &is_on); +- if (ret) { +- dev_dbg(pmu->dev, "unable to get current state for %s\n", +- pmd->genpd.name); +- return ret; +- } +- +- if (is_on == on) { +- dev_dbg(pmu->dev, "pm domain [%s] is already %sable status.\n", +- pmd->genpd.name, on ? "en" : "dis"); +- return 0; +- } +- + spin_lock_irqsave(&pmu->lock, flags); + + /* +@@ -166,6 +160,29 @@ static int jh71xx_pmu_set_state(struct j + return 0; + } + ++static int jh71xx_pmu_set_state(struct jh71xx_pmu_dev *pmd, u32 mask, bool on) ++{ ++ struct jh71xx_pmu *pmu = pmd->pmu; ++ const struct jh71xx_pmu_match_data *match_data = pmu->match_data; ++ bool is_on; ++ int ret; ++ ++ ret = jh71xx_pmu_get_state(pmd, mask, &is_on); ++ if (ret) { ++ dev_dbg(pmu->dev, "unable to get current state for %s\n", ++ pmd->genpd.name); ++ return ret; ++ } ++ ++ if (is_on == on) { ++ dev_dbg(pmu->dev, "pm domain [%s] is already %sable status.\n", ++ pmd->genpd.name, on ? "en" : "dis"); ++ return 0; ++ } ++ ++ return match_data->pmu_set_state(pmd, mask, on); ++} ++ + static int jh71xx_pmu_on(struct generic_pm_domain *genpd) + { + struct jh71xx_pmu_dev *pmd = container_of(genpd, +@@ -226,6 +243,25 @@ static irqreturn_t jh71xx_pmu_interrupt( + return IRQ_HANDLED; + } + ++static int jh7110_pmu_parse_irq(struct platform_device *pdev, struct jh71xx_pmu *pmu) ++{ ++ struct device *dev = &pdev->dev; ++ int ret; ++ ++ pmu->irq = platform_get_irq(pdev, 0); ++ if (pmu->irq < 0) ++ return pmu->irq; ++ ++ ret = devm_request_irq(dev, pmu->irq, jh71xx_pmu_interrupt, ++ 0, pdev->name, pmu); ++ if (ret) ++ dev_err(dev, "failed to request irq\n"); ++ ++ jh71xx_pmu_int_enable(pmu, JH71XX_PMU_INT_ALL_MASK & ~JH71XX_PMU_INT_PCH_FAIL, true); ++ ++ return 0; ++} ++ + static int jh71xx_pmu_init_domain(struct jh71xx_pmu *pmu, int index) + { + struct jh71xx_pmu_dev *pmd; +@@ -275,19 +311,18 @@ static int jh71xx_pmu_probe(struct platf + if (IS_ERR(pmu->base)) + return PTR_ERR(pmu->base); + +- pmu->irq = platform_get_irq(pdev, 0); +- if (pmu->irq < 0) +- return pmu->irq; +- +- ret = devm_request_irq(dev, pmu->irq, jh71xx_pmu_interrupt, +- 0, pdev->name, pmu); +- if (ret) +- dev_err(dev, "failed to request irq\n"); ++ spin_lock_init(&pmu->lock); + + match_data = of_device_get_match_data(dev); + if (!match_data) + return -EINVAL; + ++ ret = match_data->pmu_parse_irq(pdev, pmu); ++ if (ret) { ++ dev_err(dev, "failed to parse irq\n"); ++ return ret; ++ } ++ + pmu->genpd = devm_kcalloc(dev, match_data->num_domains, + sizeof(struct generic_pm_domain *), + GFP_KERNEL); +@@ -307,9 +342,6 @@ static int jh71xx_pmu_probe(struct platf + } + } + +- spin_lock_init(&pmu->lock); +- jh71xx_pmu_int_enable(pmu, JH71XX_PMU_INT_ALL_MASK & ~JH71XX_PMU_INT_PCH_FAIL, true); +- + ret = of_genpd_add_provider_onecell(np, &pmu->genpd_data); + if (ret) { + dev_err(dev, "failed to register genpd driver: %d\n", ret); +@@ -357,6 +389,9 @@ static const struct jh71xx_domain_info j + static const struct jh71xx_pmu_match_data jh7110_pmu = { + .num_domains = ARRAY_SIZE(jh7110_power_domains), + .domain_info = jh7110_power_domains, ++ .pmu_status = JH71XX_PMU_CURR_POWER_MODE, ++ .pmu_parse_irq = jh7110_pmu_parse_irq, ++ .pmu_set_state = jh7110_pmu_set_state, + }; + + static const struct of_device_id jh71xx_pmu_of_match[] = { diff --git a/target/linux/starfive/patches-6.6/0042-pmdomain-starfive-Add-JH7110-AON-PMU-support.patch b/target/linux/starfive/patches-6.6/0042-pmdomain-starfive-Add-JH7110-AON-PMU-support.patch new file mode 100644 index 0000000000..2563d5421c --- /dev/null +++ b/target/linux/starfive/patches-6.6/0042-pmdomain-starfive-Add-JH7110-AON-PMU-support.patch @@ -0,0 +1,122 @@ +From 1bf849b606d0b4cae643f96685d3d3981643683d Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Wed, 13 Sep 2023 14:54:28 +0100 +Subject: [PATCH 042/116] pmdomain: starfive: Add JH7110 AON PMU support + +Add AON PMU for StarFive JH7110 SoC. It can be used to turn on/off the +dphy rx/tx power switch. + +Reviewed-by: Walker Chen <walker.chen@starfivetech.com> +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +Signed-off-by: Conor Dooley <conor.dooley@microchip.com> +Link: https://lore.kernel.org/r/20230913-dude-imprecise-fc32622bc947@spud +Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> +--- + drivers/pmdomain/starfive/jh71xx-pmu.c | 57 +++++++++++++++++++++++--- + 1 file changed, 52 insertions(+), 5 deletions(-) + +--- a/drivers/pmdomain/starfive/jh71xx-pmu.c ++++ b/drivers/pmdomain/starfive/jh71xx-pmu.c +@@ -2,7 +2,7 @@ + /* + * StarFive JH71XX PMU (Power Management Unit) Controller Driver + * +- * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ * Copyright (C) 2022-2023 StarFive Technology Co., Ltd. + */ + + #include <linux/interrupt.h> +@@ -24,6 +24,9 @@ + #define JH71XX_PMU_EVENT_STATUS 0x88 + #define JH71XX_PMU_INT_STATUS 0x8C + ++/* aon pmu register offset */ ++#define JH71XX_AON_PMU_SWITCH 0x00 ++ + /* sw encourage cfg */ + #define JH71XX_PMU_SW_ENCOURAGE_EN_LO 0x05 + #define JH71XX_PMU_SW_ENCOURAGE_EN_HI 0x50 +@@ -160,6 +163,26 @@ static int jh7110_pmu_set_state(struct j + return 0; + } + ++static int jh7110_aon_pmu_set_state(struct jh71xx_pmu_dev *pmd, u32 mask, bool on) ++{ ++ struct jh71xx_pmu *pmu = pmd->pmu; ++ unsigned long flags; ++ u32 val; ++ ++ spin_lock_irqsave(&pmu->lock, flags); ++ val = readl(pmu->base + JH71XX_AON_PMU_SWITCH); ++ ++ if (on) ++ val |= mask; ++ else ++ val &= ~mask; ++ ++ writel(val, pmu->base + JH71XX_AON_PMU_SWITCH); ++ spin_unlock_irqrestore(&pmu->lock, flags); ++ ++ return 0; ++} ++ + static int jh71xx_pmu_set_state(struct jh71xx_pmu_dev *pmd, u32 mask, bool on) + { + struct jh71xx_pmu *pmu = pmd->pmu; +@@ -317,10 +340,12 @@ static int jh71xx_pmu_probe(struct platf + if (!match_data) + return -EINVAL; + +- ret = match_data->pmu_parse_irq(pdev, pmu); +- if (ret) { +- dev_err(dev, "failed to parse irq\n"); +- return ret; ++ if (match_data->pmu_parse_irq) { ++ ret = match_data->pmu_parse_irq(pdev, pmu); ++ if (ret) { ++ dev_err(dev, "failed to parse irq\n"); ++ return ret; ++ } + } + + pmu->genpd = devm_kcalloc(dev, match_data->num_domains, +@@ -394,11 +419,32 @@ static const struct jh71xx_pmu_match_dat + .pmu_set_state = jh7110_pmu_set_state, + }; + ++static const struct jh71xx_domain_info jh7110_aon_power_domains[] = { ++ [JH7110_PD_DPHY_TX] = { ++ .name = "DPHY-TX", ++ .bit = 30, ++ }, ++ [JH7110_PD_DPHY_RX] = { ++ .name = "DPHY-RX", ++ .bit = 31, ++ }, ++}; ++ ++static const struct jh71xx_pmu_match_data jh7110_aon_pmu = { ++ .num_domains = ARRAY_SIZE(jh7110_aon_power_domains), ++ .domain_info = jh7110_aon_power_domains, ++ .pmu_status = JH71XX_AON_PMU_SWITCH, ++ .pmu_set_state = jh7110_aon_pmu_set_state, ++}; ++ + static const struct of_device_id jh71xx_pmu_of_match[] = { + { + .compatible = "starfive,jh7110-pmu", + .data = (void *)&jh7110_pmu, + }, { ++ .compatible = "starfive,jh7110-aon-syscon", ++ .data = (void *)&jh7110_aon_pmu, ++ }, { + /* sentinel */ + } + }; +@@ -414,5 +460,6 @@ static struct platform_driver jh71xx_pmu + builtin_platform_driver(jh71xx_pmu_driver); + + MODULE_AUTHOR("Walker Chen <walker.chen@starfivetech.com>"); ++MODULE_AUTHOR("Changhuang Liang <changhuang.liang@starfivetech.com>"); + MODULE_DESCRIPTION("StarFive JH71XX PMU Driver"); + MODULE_LICENSE("GPL"); diff --git a/target/linux/starfive/patches-6.6/0043-pmdomain-Prepare-to-move-Kconfig-files-into-the-pmdo.patch b/target/linux/starfive/patches-6.6/0043-pmdomain-Prepare-to-move-Kconfig-files-into-the-pmdo.patch new file mode 100644 index 0000000000..52be89f29e --- /dev/null +++ b/target/linux/starfive/patches-6.6/0043-pmdomain-Prepare-to-move-Kconfig-files-into-the-pmdo.patch @@ -0,0 +1,36 @@ +From dff6605dcd1fc1e2af1437e59187a6f71ce389cd Mon Sep 17 00:00:00 2001 +From: Ulf Hansson <ulf.hansson@linaro.org> +Date: Mon, 11 Sep 2023 17:22:15 +0200 +Subject: [PATCH 043/116] pmdomain: Prepare to move Kconfig files into the + pmdomain subsystem + +Rather than having the various Kconfig files for the genpd providers +sprinkled across subsystems, let's prepare to move them into the pmdomain +subsystem along with the implementations. + +Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be> +Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> +--- + drivers/Kconfig | 2 ++ + drivers/pmdomain/Kconfig | 4 ++++ + 2 files changed, 6 insertions(+) + create mode 100644 drivers/pmdomain/Kconfig + +--- a/drivers/Kconfig ++++ b/drivers/Kconfig +@@ -175,6 +175,8 @@ source "drivers/soundwire/Kconfig" + + source "drivers/soc/Kconfig" + ++source "drivers/pmdomain/Kconfig" ++ + source "drivers/devfreq/Kconfig" + + source "drivers/extcon/Kconfig" +--- /dev/null ++++ b/drivers/pmdomain/Kconfig +@@ -0,0 +1,4 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++menu "PM Domains" ++ ++endmenu diff --git a/target/linux/starfive/patches-6.6/0044-pmdomain-starfive-Move-Kconfig-file-to-the-pmdomain-.patch b/target/linux/starfive/patches-6.6/0044-pmdomain-starfive-Move-Kconfig-file-to-the-pmdomain-.patch new file mode 100644 index 0000000000..909f4cc63a --- /dev/null +++ b/target/linux/starfive/patches-6.6/0044-pmdomain-starfive-Move-Kconfig-file-to-the-pmdomain-.patch @@ -0,0 +1,69 @@ +From de12fe43dbd0ea9fa980ffa05822bd7fd5eed330 Mon Sep 17 00:00:00 2001 +From: Ulf Hansson <ulf.hansson@linaro.org> +Date: Tue, 12 Sep 2023 13:31:44 +0200 +Subject: [PATCH 044/116] pmdomain: starfive: Move Kconfig file to the pmdomain + subsystem + +The Kconfig belongs closer to the corresponding implementation, hence let's +move it from the soc subsystem to the pmdomain subsystem. + +Cc: Walker Chen <walker.chen@starfivetech.com> +Cc: Conor Dooley <conor@kernel.org> +Acked-by: Conor Dooley <conor.dooley@microchip.com> +Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> +--- + drivers/pmdomain/Kconfig | 2 ++ + drivers/{soc => pmdomain}/starfive/Kconfig | 0 + drivers/soc/Kconfig | 1 - + 3 files changed, 2 insertions(+), 1 deletion(-) + rename drivers/{soc => pmdomain}/starfive/Kconfig (100%) + +--- a/drivers/pmdomain/Kconfig ++++ b/drivers/pmdomain/Kconfig +@@ -1,4 +1,6 @@ + # SPDX-License-Identifier: GPL-2.0-only + menu "PM Domains" + ++source "drivers/pmdomain/starfive/Kconfig" ++ + endmenu +--- a/drivers/soc/Kconfig ++++ b/drivers/soc/Kconfig +@@ -24,7 +24,6 @@ source "drivers/soc/renesas/Kconfig" + source "drivers/soc/rockchip/Kconfig" + source "drivers/soc/samsung/Kconfig" + source "drivers/soc/sifive/Kconfig" +-source "drivers/soc/starfive/Kconfig" + source "drivers/soc/sunxi/Kconfig" + source "drivers/soc/tegra/Kconfig" + source "drivers/soc/ti/Kconfig" +--- /dev/null ++++ b/drivers/pmdomain/starfive/Kconfig +@@ -0,0 +1,12 @@ ++# SPDX-License-Identifier: GPL-2.0 ++ ++config JH71XX_PMU ++ bool "Support PMU for StarFive JH71XX Soc" ++ depends on PM ++ depends on ARCH_STARFIVE || COMPILE_TEST ++ default ARCH_STARFIVE ++ select PM_GENERIC_DOMAINS ++ help ++ Say 'y' here to enable support power domain support. ++ In order to meet low power requirements, a Power Management Unit (PMU) ++ is designed for controlling power resources in StarFive JH71XX SoCs. +--- a/drivers/soc/starfive/Kconfig ++++ /dev/null +@@ -1,12 +0,0 @@ +-# SPDX-License-Identifier: GPL-2.0 +- +-config JH71XX_PMU +- bool "Support PMU for StarFive JH71XX Soc" +- depends on PM +- depends on ARCH_STARFIVE || COMPILE_TEST +- default ARCH_STARFIVE +- select PM_GENERIC_DOMAINS +- help +- Say 'y' here to enable support power domain support. +- In order to meet low power requirements, a Power Management Unit (PMU) +- is designed for controlling power resources in StarFive JH71XX SoCs. diff --git a/target/linux/starfive/patches-6.6/0045-dt-bindings-power-Update-prefixes-for-AON-power-doma.patch b/target/linux/starfive/patches-6.6/0045-dt-bindings-power-Update-prefixes-for-AON-power-doma.patch new file mode 100644 index 0000000000..c6e0012528 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0045-dt-bindings-power-Update-prefixes-for-AON-power-doma.patch @@ -0,0 +1,31 @@ +From cac9ce9c7f388a741389b1ec47af65420254db55 Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Wed, 27 Sep 2023 06:07:33 -0700 +Subject: [PATCH 045/116] dt-bindings: power: Update prefixes for AON power + domain + +Use "JH7110_AON_PD_" prefix for AON power domain for JH7110 SoC. + +Reviewed-by: Walker Chen <walker.chen@starfivetech.com> +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +Acked-by: Conor Dooley <conor.dooley@microchip.com> +Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be> +Link: https://lore.kernel.org/r/20230927130734.9921-2-changhuang.liang@starfivetech.com +Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> +--- + include/dt-bindings/power/starfive,jh7110-pmu.h | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +--- a/include/dt-bindings/power/starfive,jh7110-pmu.h ++++ b/include/dt-bindings/power/starfive,jh7110-pmu.h +@@ -14,7 +14,8 @@ + #define JH7110_PD_ISP 5 + #define JH7110_PD_VENC 6 + +-#define JH7110_PD_DPHY_TX 0 +-#define JH7110_PD_DPHY_RX 1 ++/* AON Power Domain */ ++#define JH7110_AON_PD_DPHY_TX 0 ++#define JH7110_AON_PD_DPHY_RX 1 + + #endif diff --git a/target/linux/starfive/patches-6.6/0046-pmdomain-starfive-Update-prefixes-for-AON-power-doma.patch b/target/linux/starfive/patches-6.6/0046-pmdomain-starfive-Update-prefixes-for-AON-power-doma.patch new file mode 100644 index 0000000000..5cb89ae052 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0046-pmdomain-starfive-Update-prefixes-for-AON-power-doma.patch @@ -0,0 +1,32 @@ +From 3ea89ffbd6cc5a15acca6bc2130572f8bd85b9d4 Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Wed, 27 Sep 2023 06:07:34 -0700 +Subject: [PATCH 046/116] pmdomain: starfive: Update prefixes for AON power + domain + +Use "JH7110_AON_PD_" prefix for AON power doamin for JH7110 SoC. + +Reviewed-by: Walker Chen <walker.chen@starfivetech.com> +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +Link: https://lore.kernel.org/r/20230927130734.9921-3-changhuang.liang@starfivetech.com +Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> +--- + drivers/pmdomain/starfive/jh71xx-pmu.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/pmdomain/starfive/jh71xx-pmu.c ++++ b/drivers/pmdomain/starfive/jh71xx-pmu.c +@@ -420,11 +420,11 @@ static const struct jh71xx_pmu_match_dat + }; + + static const struct jh71xx_domain_info jh7110_aon_power_domains[] = { +- [JH7110_PD_DPHY_TX] = { ++ [JH7110_AON_PD_DPHY_TX] = { + .name = "DPHY-TX", + .bit = 30, + }, +- [JH7110_PD_DPHY_RX] = { ++ [JH7110_AON_PD_DPHY_RX] = { + .name = "DPHY-RX", + .bit = 31, + }, diff --git a/target/linux/starfive/patches-6.6/0047-riscv-dts-starfive-pinfunc-Fix-the-pins-name-of-I2ST.patch b/target/linux/starfive/patches-6.6/0047-riscv-dts-starfive-pinfunc-Fix-the-pins-name-of-I2ST.patch new file mode 100644 index 0000000000..782e71a0eb --- /dev/null +++ b/target/linux/starfive/patches-6.6/0047-riscv-dts-starfive-pinfunc-Fix-the-pins-name-of-I2ST.patch @@ -0,0 +1,30 @@ +From e7e3d62b7a470ddf15e30574232b52b2e23ba606 Mon Sep 17 00:00:00 2001 +From: Xingyu Wu <xingyu.wu@starfivetech.com> +Date: Mon, 21 Aug 2023 22:41:50 +0800 +Subject: [PATCH 047/116] riscv: dts: starfive: pinfunc: Fix the pins name of + I2STX1 + +These pins are actually I2STX1 clock input, not I2STX0, +so their names should be changed. + +Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com> +Reviewed-by: Walker Chen <walker.chen@starfivetech.com> +Acked-by: Rob Herring <robh@kernel.org> +Signed-off-by: Conor Dooley <conor.dooley@microchip.com> +--- + arch/riscv/boot/dts/starfive/jh7110-pinfunc.h | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/arch/riscv/boot/dts/starfive/jh7110-pinfunc.h ++++ b/arch/riscv/boot/dts/starfive/jh7110-pinfunc.h +@@ -240,8 +240,8 @@ + #define GPI_SYS_MCLK_EXT 30 + #define GPI_SYS_I2SRX_BCLK 31 + #define GPI_SYS_I2SRX_LRCK 32 +-#define GPI_SYS_I2STX0_BCLK 33 +-#define GPI_SYS_I2STX0_LRCK 34 ++#define GPI_SYS_I2STX1_BCLK 33 ++#define GPI_SYS_I2STX1_LRCK 34 + #define GPI_SYS_TDM_CLK 35 + #define GPI_SYS_TDM_RXD 36 + #define GPI_SYS_TDM_SYNC 37 diff --git a/target/linux/starfive/patches-6.6/0048-riscv-dts-starfive-Add-full-support-except-VIN-and-V.patch b/target/linux/starfive/patches-6.6/0048-riscv-dts-starfive-Add-full-support-except-VIN-and-V.patch new file mode 100644 index 0000000000..75dd965c94 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0048-riscv-dts-starfive-Add-full-support-except-VIN-and-V.patch @@ -0,0 +1,536 @@ +From a3d3f611f31fa2dca3deefa7cd443abca02e03fa Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Tue, 11 Apr 2023 16:31:15 +0800 +Subject: [PATCH 048/116] riscv: dts: starfive: Add full support (except VIN + and VOUT) for JH7110 and VisionFive 2 board + +Merge all StarFive dts patches together except VIN and VOUT. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + .../jh7110-starfive-visionfive-2.dtsi | 199 +++++++++++++++ + arch/riscv/boot/dts/starfive/jh7110.dtsi | 233 ++++++++++++++++++ + 2 files changed, 432 insertions(+) + +--- a/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi +@@ -19,6 +19,8 @@ + i2c6 = &i2c6; + mmc0 = &mmc0; + mmc1 = &mmc1; ++ pcie0 = &pcie0; ++ pcie1 = &pcie1; + serial0 = &uart0; + }; + +@@ -40,6 +42,33 @@ + gpios = <&sysgpio 35 GPIO_ACTIVE_HIGH>; + priority = <224>; + }; ++ ++ pwmdac_codec: pwmdac-codec { ++ compatible = "linux,spdif-dit"; ++ #sound-dai-cells = <0>; ++ }; ++ ++ sound-pwmdac { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "StarFive-PWMDAC-Sound-Card"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ simple-audio-card,dai-link@0 { ++ reg = <0>; ++ format = "left_j"; ++ bitclock-master = <&sndcpu0>; ++ frame-master = <&sndcpu0>; ++ ++ sndcpu0: cpu { ++ sound-dai = <&pwmdac>; ++ }; ++ ++ codec { ++ sound-dai = <&pwmdac_codec>; ++ }; ++ }; ++ }; + }; + + &dvp_clk { +@@ -202,6 +231,24 @@ + status = "okay"; + }; + ++&i2srx { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2srx_pins>; ++ status = "okay"; ++}; ++ ++&i2stx0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&mclk_ext_pins>; ++ status = "okay"; ++}; ++ ++&i2stx1 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2stx1_pins>; ++ status = "okay"; ++}; ++ + &mmc0 { + max-frequency = <100000000>; + assigned-clocks = <&syscrg JH7110_SYSCLK_SDIO0_SDCARD>; +@@ -235,6 +282,34 @@ + status = "okay"; + }; + ++&pcie0 { ++ perst-gpios = <&sysgpio 26 GPIO_ACTIVE_LOW>; ++ phys = <&pciephy0>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pcie0_pins>; ++ status = "okay"; ++}; ++ ++&pcie1 { ++ perst-gpios = <&sysgpio 28 GPIO_ACTIVE_LOW>; ++ phys = <&pciephy1>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pcie1_pins>; ++ status = "okay"; ++}; ++ ++&pwm { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwm_pins>; ++ status = "okay"; ++}; ++ ++&pwmdac { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwmdac_pins>; ++ status = "okay"; ++}; ++ + &qspi { + #address-cells = <1>; + #size-cells = <0>; +@@ -340,6 +415,46 @@ + }; + }; + ++ i2srx_pins: i2srx-0 { ++ clk-sd-pins { ++ pinmux = <GPIOMUX(38, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_I2SRX_BCLK)>, ++ <GPIOMUX(63, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_I2SRX_LRCK)>, ++ <GPIOMUX(38, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_I2STX1_BCLK)>, ++ <GPIOMUX(63, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_I2STX1_LRCK)>, ++ <GPIOMUX(61, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_I2SRX_SDIN0)>; ++ input-enable; ++ }; ++ }; ++ ++ i2stx1_pins: i2stx1-0 { ++ sd-pins { ++ pinmux = <GPIOMUX(44, GPOUT_SYS_I2STX1_SDO0, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ }; ++ }; ++ ++ mclk_ext_pins: mclk-ext-0 { ++ mclk-ext-pins { ++ pinmux = <GPIOMUX(4, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_MCLK_EXT)>; ++ input-enable; ++ }; ++ }; ++ + mmc0_pins: mmc0-0 { + rst-pins { + pinmux = <GPIOMUX(62, GPOUT_SYS_SDIO0_RST, +@@ -404,6 +519,86 @@ + slew-rate = <0>; + }; + }; ++ ++ pcie0_pins: pcie0-0 { ++ clkreq-pins { ++ pinmux = <GPIOMUX(27, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_NONE)>; ++ bias-pull-down; ++ drive-strength = <2>; ++ input-enable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ ++ wake-pins { ++ pinmux = <GPIOMUX(32, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_NONE)>; ++ bias-pull-up; ++ drive-strength = <2>; ++ input-enable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ }; ++ ++ pcie1_pins: pcie1-0 { ++ clkreq-pins { ++ pinmux = <GPIOMUX(29, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_NONE)>; ++ bias-pull-down; ++ drive-strength = <2>; ++ input-enable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ ++ wake-pins { ++ pinmux = <GPIOMUX(21, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_NONE)>; ++ bias-pull-up; ++ drive-strength = <2>; ++ input-enable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ }; ++ ++ pwm_pins: pwm-0 { ++ pwm-pins { ++ pinmux = <GPIOMUX(46, GPOUT_SYS_PWM_CHANNEL0, ++ GPOEN_SYS_PWM0_CHANNEL0, ++ GPI_NONE)>, ++ <GPIOMUX(59, GPOUT_SYS_PWM_CHANNEL1, ++ GPOEN_SYS_PWM0_CHANNEL1, ++ GPI_NONE)>; ++ bias-disable; ++ drive-strength = <12>; ++ input-disable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ }; ++ ++ pwmdac_pins: pwmdac-0 { ++ pwmdac-pins { ++ pinmux = <GPIOMUX(33, GPOUT_SYS_PWMDAC_LEFT, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <GPIOMUX(34, GPOUT_SYS_PWMDAC_RIGHT, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ drive-strength = <2>; ++ input-disable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ }; + + spi0_pins: spi0-0 { + mosi-pins { +--- a/arch/riscv/boot/dts/starfive/jh7110.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7110.dtsi +@@ -244,6 +244,7 @@ + clock-output-names = "dvp_clk"; + #clock-cells = <0>; + }; ++ + gmac0_rgmii_rxin: gmac0-rgmii-rxin-clock { + compatible = "fixed-clock"; + clock-output-names = "gmac0_rgmii_rxin"; +@@ -512,6 +513,43 @@ + status = "disabled"; + }; + ++ pwmdac: pwmdac@100b0000 { ++ compatible = "starfive,jh7110-pwmdac"; ++ reg = <0x0 0x100b0000 0x0 0x1000>; ++ clocks = <&syscrg JH7110_SYSCLK_PWMDAC_APB>, ++ <&syscrg JH7110_SYSCLK_PWMDAC_CORE>; ++ clock-names = "apb", "core"; ++ resets = <&syscrg JH7110_SYSRST_PWMDAC_APB>; ++ dmas = <&dma 22>; ++ dma-names = "tx"; ++ #sound-dai-cells = <0>; ++ status = "disabled"; ++ }; ++ ++ i2srx: i2s@100e0000 { ++ compatible = "starfive,jh7110-i2srx"; ++ reg = <0x0 0x100e0000 0x0 0x1000>; ++ clocks = <&syscrg JH7110_SYSCLK_I2SRX_BCLK_MST>, ++ <&syscrg JH7110_SYSCLK_I2SRX_APB>, ++ <&syscrg JH7110_SYSCLK_MCLK>, ++ <&syscrg JH7110_SYSCLK_MCLK_INNER>, ++ <&mclk_ext>, ++ <&syscrg JH7110_SYSCLK_I2SRX_BCLK>, ++ <&syscrg JH7110_SYSCLK_I2SRX_LRCK>, ++ <&i2srx_bclk_ext>, ++ <&i2srx_lrck_ext>; ++ clock-names = "i2sclk", "apb", "mclk", ++ "mclk_inner", "mclk_ext", "bclk", ++ "lrck", "bclk_ext", "lrck_ext"; ++ resets = <&syscrg JH7110_SYSRST_I2SRX_APB>, ++ <&syscrg JH7110_SYSRST_I2SRX_BCLK>; ++ dmas = <0>, <&dma 24>; ++ dma-names = "tx", "rx"; ++ starfive,syscon = <&sys_syscon 0x18 0x2>; ++ #sound-dai-cells = <0>; ++ status = "disabled"; ++ }; ++ + usb0: usb@10100000 { + compatible = "starfive,jh7110-usb"; + ranges = <0x0 0x0 0x10100000 0x100000>; +@@ -736,6 +774,56 @@ + status = "disabled"; + }; + ++ i2stx0: i2s@120b0000 { ++ compatible = "starfive,jh7110-i2stx0"; ++ reg = <0x0 0x120b0000 0x0 0x1000>; ++ clocks = <&syscrg JH7110_SYSCLK_I2STX0_BCLK_MST>, ++ <&syscrg JH7110_SYSCLK_I2STX0_APB>, ++ <&syscrg JH7110_SYSCLK_MCLK>, ++ <&syscrg JH7110_SYSCLK_MCLK_INNER>, ++ <&mclk_ext>; ++ clock-names = "i2sclk", "apb", "mclk", ++ "mclk_inner","mclk_ext"; ++ resets = <&syscrg JH7110_SYSRST_I2STX0_APB>, ++ <&syscrg JH7110_SYSRST_I2STX0_BCLK>; ++ dmas = <&dma 47>; ++ dma-names = "tx"; ++ #sound-dai-cells = <0>; ++ status = "disabled"; ++ }; ++ ++ i2stx1: i2s@120c0000 { ++ compatible = "starfive,jh7110-i2stx1"; ++ reg = <0x0 0x120c0000 0x0 0x1000>; ++ clocks = <&syscrg JH7110_SYSCLK_I2STX1_BCLK_MST>, ++ <&syscrg JH7110_SYSCLK_I2STX1_APB>, ++ <&syscrg JH7110_SYSCLK_MCLK>, ++ <&syscrg JH7110_SYSCLK_MCLK_INNER>, ++ <&mclk_ext>, ++ <&syscrg JH7110_SYSCLK_I2STX1_BCLK>, ++ <&syscrg JH7110_SYSCLK_I2STX1_LRCK>, ++ <&i2stx_bclk_ext>, ++ <&i2stx_lrck_ext>; ++ clock-names = "i2sclk", "apb", "mclk", ++ "mclk_inner", "mclk_ext", "bclk", ++ "lrck", "bclk_ext", "lrck_ext"; ++ resets = <&syscrg JH7110_SYSRST_I2STX1_APB>, ++ <&syscrg JH7110_SYSRST_I2STX1_BCLK>; ++ dmas = <&dma 48>; ++ dma-names = "tx"; ++ #sound-dai-cells = <0>; ++ status = "disabled"; ++ }; ++ ++ pwm: pwm@120d0000 { ++ compatible = "starfive,jh7110-pwm", "opencores,pwm-v1"; ++ reg = <0x0 0x120d0000 0x0 0x10000>; ++ clocks = <&syscrg JH7110_SYSCLK_PWM_APB>; ++ resets = <&syscrg JH7110_SYSRST_PWM_APB>; ++ #pwm-cells = <3>; ++ status = "disabled"; ++ }; ++ + sfctemp: temperature-sensor@120e0000 { + compatible = "starfive,jh7110-temp"; + reg = <0x0 0x120e0000 0x0 0x10000>; +@@ -811,6 +899,26 @@ + #gpio-cells = <2>; + }; + ++ timer@13050000 { ++ compatible = "starfive,jh7110-timer"; ++ reg = <0x0 0x13050000 0x0 0x10000>; ++ interrupts = <69>, <70>, <71>, <72>; ++ clocks = <&syscrg JH7110_SYSCLK_TIMER_APB>, ++ <&syscrg JH7110_SYSCLK_TIMER0>, ++ <&syscrg JH7110_SYSCLK_TIMER1>, ++ <&syscrg JH7110_SYSCLK_TIMER2>, ++ <&syscrg JH7110_SYSCLK_TIMER3>; ++ clock-names = "apb", "ch0", "ch1", ++ "ch2", "ch3"; ++ resets = <&syscrg JH7110_SYSRST_TIMER_APB>, ++ <&syscrg JH7110_SYSRST_TIMER0>, ++ <&syscrg JH7110_SYSRST_TIMER1>, ++ <&syscrg JH7110_SYSRST_TIMER2>, ++ <&syscrg JH7110_SYSRST_TIMER3>; ++ reset-names = "apb", "ch0", "ch1", ++ "ch2", "ch3"; ++ }; ++ + watchdog@13070000 { + compatible = "starfive,jh7110-wdt"; + reg = <0x0 0x13070000 0x0 0x10000>; +@@ -1011,6 +1119,32 @@ + #power-domain-cells = <1>; + }; + ++ csi2rx: csi-bridge@19800000 { ++ compatible = "starfive,jh7110-csi2rx"; ++ reg = <0x0 0x19800000 0x0 0x10000>; ++ clocks = <&ispcrg JH7110_ISPCLK_VIN_SYS>, ++ <&ispcrg JH7110_ISPCLK_VIN_APB>, ++ <&ispcrg JH7110_ISPCLK_VIN_PIXEL_IF0>, ++ <&ispcrg JH7110_ISPCLK_VIN_PIXEL_IF1>, ++ <&ispcrg JH7110_ISPCLK_VIN_PIXEL_IF2>, ++ <&ispcrg JH7110_ISPCLK_VIN_PIXEL_IF3>; ++ clock-names = "sys_clk", "p_clk", ++ "pixel_if0_clk", "pixel_if1_clk", ++ "pixel_if2_clk", "pixel_if3_clk"; ++ resets = <&ispcrg JH7110_ISPRST_VIN_SYS>, ++ <&ispcrg JH7110_ISPRST_VIN_APB>, ++ <&ispcrg JH7110_ISPRST_VIN_PIXEL_IF0>, ++ <&ispcrg JH7110_ISPRST_VIN_PIXEL_IF1>, ++ <&ispcrg JH7110_ISPRST_VIN_PIXEL_IF2>, ++ <&ispcrg JH7110_ISPRST_VIN_PIXEL_IF3>; ++ reset-names = "sys", "reg_bank", ++ "pixel_if0", "pixel_if1", ++ "pixel_if2", "pixel_if3"; ++ phys = <&csi_phy>; ++ phy-names = "dphy"; ++ status = "disabled"; ++ }; ++ + ispcrg: clock-controller@19810000 { + compatible = "starfive,jh7110-ispcrg"; + reg = <0x0 0x19810000 0x0 0x10000>; +@@ -1028,6 +1162,19 @@ + power-domains = <&pwrc JH7110_PD_ISP>; + }; + ++ csi_phy: phy@19820000 { ++ compatible = "starfive,jh7110-dphy-rx"; ++ reg = <0x0 0x19820000 0x0 0x10000>; ++ clocks = <&ispcrg JH7110_ISPCLK_M31DPHY_CFG_IN>, ++ <&ispcrg JH7110_ISPCLK_M31DPHY_REF_IN>, ++ <&ispcrg JH7110_ISPCLK_M31DPHY_TX_ESC_LAN0>; ++ clock-names = "cfg", "ref", "tx"; ++ resets = <&ispcrg JH7110_ISPRST_M31DPHY_HW>, ++ <&ispcrg JH7110_ISPRST_M31DPHY_B09_AON>; ++ power-domains = <&aon_syscon JH7110_AON_PD_DPHY_RX>; ++ #phy-cells = <0>; ++ }; ++ + voutcrg: clock-controller@295c0000 { + compatible = "starfive,jh7110-voutcrg"; + reg = <0x0 0x295c0000 0x0 0x10000>; +@@ -1045,5 +1192,91 @@ + #reset-cells = <1>; + power-domains = <&pwrc JH7110_PD_VOUT>; + }; ++ ++ pcie0: pcie@940000000 { ++ compatible = "starfive,jh7110-pcie"; ++ reg = <0x9 0x40000000 0x0 0x1000000>, ++ <0x0 0x2b000000 0x0 0x100000>; ++ reg-names = "cfg", "apb"; ++ linux,pci-domain = <0>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ #interrupt-cells = <1>; ++ ranges = <0x82000000 0x0 0x30000000 0x0 0x30000000 0x0 0x08000000>, ++ <0xc3000000 0x9 0x00000000 0x9 0x00000000 0x0 0x40000000>; ++ interrupts = <56>; ++ interrupt-map-mask = <0x0 0x0 0x0 0x7>; ++ interrupt-map = <0x0 0x0 0x0 0x1 &pcie_intc0 0x1>, ++ <0x0 0x0 0x0 0x2 &pcie_intc0 0x2>, ++ <0x0 0x0 0x0 0x3 &pcie_intc0 0x3>, ++ <0x0 0x0 0x0 0x4 &pcie_intc0 0x4>; ++ msi-controller; ++ device_type = "pci"; ++ starfive,stg-syscon = <&stg_syscon>; ++ bus-range = <0x0 0xff>; ++ clocks = <&syscrg JH7110_SYSCLK_NOC_BUS_STG_AXI>, ++ <&stgcrg JH7110_STGCLK_PCIE0_TL>, ++ <&stgcrg JH7110_STGCLK_PCIE0_AXI_MST0>, ++ <&stgcrg JH7110_STGCLK_PCIE0_APB>; ++ clock-names = "noc", "tl", "axi_mst0", "apb"; ++ resets = <&stgcrg JH7110_STGRST_PCIE0_AXI_MST0>, ++ <&stgcrg JH7110_STGRST_PCIE0_AXI_SLV0>, ++ <&stgcrg JH7110_STGRST_PCIE0_AXI_SLV>, ++ <&stgcrg JH7110_STGRST_PCIE0_BRG>, ++ <&stgcrg JH7110_STGRST_PCIE0_CORE>, ++ <&stgcrg JH7110_STGRST_PCIE0_APB>; ++ reset-names = "mst0", "slv0", "slv", "brg", ++ "core", "apb"; ++ status = "disabled"; ++ ++ pcie_intc0: interrupt-controller { ++ #address-cells = <0>; ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ }; ++ }; ++ ++ pcie1: pcie@9c0000000 { ++ compatible = "starfive,jh7110-pcie"; ++ reg = <0x9 0xc0000000 0x0 0x1000000>, ++ <0x0 0x2c000000 0x0 0x100000>; ++ reg-names = "cfg", "apb"; ++ linux,pci-domain = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ #interrupt-cells = <1>; ++ ranges = <0x82000000 0x0 0x38000000 0x0 0x38000000 0x0 0x08000000>, ++ <0xc3000000 0x9 0x80000000 0x9 0x80000000 0x0 0x40000000>; ++ interrupts = <57>; ++ interrupt-map-mask = <0x0 0x0 0x0 0x7>; ++ interrupt-map = <0x0 0x0 0x0 0x1 &pcie_intc1 0x1>, ++ <0x0 0x0 0x0 0x2 &pcie_intc1 0x2>, ++ <0x0 0x0 0x0 0x3 &pcie_intc1 0x3>, ++ <0x0 0x0 0x0 0x4 &pcie_intc1 0x4>; ++ msi-controller; ++ device_type = "pci"; ++ starfive,stg-syscon = <&stg_syscon>; ++ bus-range = <0x0 0xff>; ++ clocks = <&syscrg JH7110_SYSCLK_NOC_BUS_STG_AXI>, ++ <&stgcrg JH7110_STGCLK_PCIE1_TL>, ++ <&stgcrg JH7110_STGCLK_PCIE1_AXI_MST0>, ++ <&stgcrg JH7110_STGCLK_PCIE1_APB>; ++ clock-names = "noc", "tl", "axi_mst0", "apb"; ++ resets = <&stgcrg JH7110_STGRST_PCIE1_AXI_MST0>, ++ <&stgcrg JH7110_STGRST_PCIE1_AXI_SLV0>, ++ <&stgcrg JH7110_STGRST_PCIE1_AXI_SLV>, ++ <&stgcrg JH7110_STGRST_PCIE1_BRG>, ++ <&stgcrg JH7110_STGRST_PCIE1_CORE>, ++ <&stgcrg JH7110_STGRST_PCIE1_APB>; ++ reset-names = "mst0", "slv0", "slv", "brg", ++ "core", "apb"; ++ status = "disabled"; ++ ++ pcie_intc1: interrupt-controller { ++ #address-cells = <0>; ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ }; ++ }; + }; + }; diff --git a/target/linux/starfive/patches-6.6/0049-MAINTAINERS-Update-all-StarFive-entries.patch b/target/linux/starfive/patches-6.6/0049-MAINTAINERS-Update-all-StarFive-entries.patch new file mode 100644 index 0000000000..a7a832dce2 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0049-MAINTAINERS-Update-all-StarFive-entries.patch @@ -0,0 +1,147 @@ +From ae7b57a0c69953f5ec06a378aedeed4c86637998 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Tue, 11 Apr 2023 16:25:57 +0800 +Subject: [PATCH 049/116] MAINTAINERS: Update all StarFive entries + +Merge all StarFive maintainers changes together. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + MAINTAINERS | 61 +++++++++++++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 57 insertions(+), 4 deletions(-) + +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -7053,6 +7053,14 @@ T: git git://anongit.freedesktop.org/drm + F: Documentation/devicetree/bindings/display/rockchip/ + F: drivers/gpu/drm/rockchip/ + ++DRM DRIVERS FOR STARFIVE ++M: Keith Zhao <keith.zhao@starfivetech.com> ++L: dri-devel@lists.freedesktop.org ++S: Maintained ++T: git git://anongit.freedesktop.org/drm/drm-misc ++F: Documentation/devicetree/bindings/display/starfive/ ++F: drivers/gpu/drm/verisilicon/ ++ + DRM DRIVERS FOR STI + M: Alain Volmat <alain.volmat@foss.st.com> + L: dri-devel@lists.freedesktop.org +@@ -16016,6 +16024,13 @@ F: Documentation/i2c/busses/i2c-ocores.r + F: drivers/i2c/busses/i2c-ocores.c + F: include/linux/platform_data/i2c-ocores.h + ++OPENCORES PWM DRIVER ++M: William Qiu <william.qiu@starfivetech.com> ++M: Hal Feng <hal.feng@starfivetech.com> ++S: Supported ++F: Documentation/devicetree/bindings/pwm/opencores,pwm.yaml ++F: drivers/pwm/pwm-ocores.c ++ + OPENRISC ARCHITECTURE + M: Jonas Bonn <jonas@southpole.se> + M: Stefan Kristiansson <stefan.kristiansson@saunalahti.fi> +@@ -16427,6 +16442,14 @@ S: Maintained + F: Documentation/devicetree/bindings/pci/layerscape-pcie-gen4.txt + F: drivers/pci/controller/mobiveil/pcie-layerscape-gen4.c + ++PCI DRIVER FOR PLDA PCIE IP ++M: Daire McNamara <daire.mcnamara@microchip.com> ++M: Kevin Xie <kevin.xie@starfivetech.com> ++L: linux-pci@vger.kernel.org ++S: Maintained ++F: Documentation/devicetree/bindings/pci/plda,* ++F: drivers/pci/controller/plda/*plda* ++ + PCI DRIVER FOR RENESAS R-CAR + M: Marek Vasut <marek.vasut+renesas@gmail.com> + M: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> +@@ -16658,7 +16681,7 @@ M: Daire McNamara <daire.mcnamara@microc + L: linux-pci@vger.kernel.org + S: Supported + F: Documentation/devicetree/bindings/pci/microchip* +-F: drivers/pci/controller/*microchip* ++F: drivers/pci/controller/plda/*microchip* + + PCIE DRIVER FOR QUALCOMM MSM + M: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> +@@ -16682,6 +16705,13 @@ S: Maintained + F: Documentation/devicetree/bindings/pci/socionext,uniphier-pcie* + F: drivers/pci/controller/dwc/pcie-uniphier* + ++PCIE DRIVER FOR STARFIVE JH71x0 ++M: Kevin Xie <kevin.xie@starfivetech.com> ++L: linux-pci@vger.kernel.org ++S: Maintained ++F: Documentation/devicetree/bindings/pci/starfive* ++F: drivers/pci/controller/plda/pcie-starfive.c ++ + PCIE DRIVER FOR ST SPEAR13XX + M: Pratyush Anand <pratyush.anand@gmail.com> + L: linux-pci@vger.kernel.org +@@ -18454,7 +18484,7 @@ F: drivers/char/hw_random/mpfs-rng.c + F: drivers/clk/microchip/clk-mpfs*.c + F: drivers/i2c/busses/i2c-microchip-corei2c.c + F: drivers/mailbox/mailbox-mpfs.c +-F: drivers/pci/controller/pcie-microchip-host.c ++F: drivers/pci/controller/plda/pcie-microchip-host.c + F: drivers/pwm/pwm-microchip-core.c + F: drivers/reset/reset-mpfs.c + F: drivers/rtc/rtc-mpfs.c +@@ -20435,6 +20465,15 @@ M: Ion Badulescu <ionut@badula.org> + S: Odd Fixes + F: drivers/net/ethernet/adaptec/starfire* + ++STARFIVE CAMERA SUBSYSTEM DRIVER ++M: Jack Zhu <jack.zhu@starfivetech.com> ++M: Changhuang Liang <changhuang.liang@starfivetech.com> ++L: linux-media@vger.kernel.org ++S: Maintained ++F: Documentation/admin-guide/media/starfive_camss.rst ++F: Documentation/devicetree/bindings/media/starfive,jh7110-camss.yaml ++F: drivers/staging/media/starfive/camss ++ + STARFIVE CRYPTO DRIVER + M: Jia Jie Ho <jiajie.ho@starfivetech.com> + M: William Qiu <william.qiu@starfivetech.com> +@@ -20473,6 +20512,13 @@ S: Supported + F: Documentation/devicetree/bindings/clock/starfive,jh7110-pll.yaml + F: drivers/clk/starfive/clk-starfive-jh7110-pll.c + ++STARFIVE JH7110 PWMDAC DRIVER ++M: Hal Feng <hal.feng@starfivetech.com> ++M: Xingyu Wu <xingyu.wu@starfivetech.com> ++S: Supported ++F: Documentation/devicetree/bindings/sound/starfive,jh7110-pwmdac.yaml ++F: sound/soc/starfive/jh7110_pwmdac.c ++ + STARFIVE JH7110 SYSCON + M: William Qiu <william.qiu@starfivetech.com> + M: Xingyu Wu <xingyu.wu@starfivetech.com> +@@ -20520,9 +20566,10 @@ F: drivers/usb/cdns3/cdns3-starfive.c + + STARFIVE JH71XX PMU CONTROLLER DRIVER + M: Walker Chen <walker.chen@starfivetech.com> ++M: Changhuang Liang <changhuang.liang@starfivetech.com> + S: Supported + F: Documentation/devicetree/bindings/power/starfive* +-F: drivers/pmdomain/starfive/jh71xx-pmu.c ++F: drivers/pmdomain/starfive/ + F: include/dt-bindings/power/starfive,jh7110-pmu.h + + STARFIVE SOC DRIVERS +@@ -20530,7 +20577,13 @@ M: Conor Dooley <conor@kernel.org> + S: Maintained + T: git https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux.git/ + F: Documentation/devicetree/bindings/soc/starfive/ +-F: drivers/soc/starfive/ ++ ++STARFIVE JH7110 TIMER DRIVER ++M: Samin Guo <samin.guo@starfivetech.com> ++M: Xingyu Wu <xingyu.wu@starfivetech.com> ++S: Supported ++F: Documentation/devicetree/bindings/timer/starfive,jh7110-timer.yaml ++F: drivers/clocksource/timer-jh7110.c + + STARFIVE TRNG DRIVER + M: Jia Jie Ho <jiajie.ho@starfivetech.com> diff --git a/target/linux/starfive/patches-6.6/0050-mmc-starfive-Change-the-voltage-to-adapt-to-JH7110-E.patch b/target/linux/starfive/patches-6.6/0050-mmc-starfive-Change-the-voltage-to-adapt-to-JH7110-E.patch new file mode 100644 index 0000000000..a38a885894 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0050-mmc-starfive-Change-the-voltage-to-adapt-to-JH7110-E.patch @@ -0,0 +1,64 @@ +From e394195396995456ef98f52ac123c0cb64687748 Mon Sep 17 00:00:00 2001 +From: William Qiu <william.qiu@starfivetech.com> +Date: Mon, 9 Oct 2023 10:59:03 +0800 +Subject: [PATCH 050/116] mmc: starfive: Change the voltage to adapt to JH7110 + EVB + +Change the voltage, so the driver can adapt to JH7110 EVB. + +Signed-off-by: William Qiu <william.qiu@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/mmc/host/dw_mmc-starfive.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +--- a/drivers/mmc/host/dw_mmc-starfive.c ++++ b/drivers/mmc/host/dw_mmc-starfive.c +@@ -8,6 +8,7 @@ + #include <linux/bitfield.h> + #include <linux/clk.h> + #include <linux/delay.h> ++#include <linux/gpio.h> + #include <linux/mfd/syscon.h> + #include <linux/mmc/host.h> + #include <linux/module.h> +@@ -95,10 +96,39 @@ out: + return ret; + } + ++static int dw_mci_starfive_switch_voltage(struct mmc_host *mmc, struct mmc_ios *ios) ++{ ++ ++ struct dw_mci_slot *slot = mmc_priv(mmc); ++ struct dw_mci *host = slot->host; ++ u32 ret; ++ ++ if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) ++ ret = gpio_direction_output(25, 0); ++ else if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_180) ++ ret = gpio_direction_output(25, 1); ++ if (ret) ++ return ret; ++ ++ if (!IS_ERR(mmc->supply.vqmmc)) { ++ ret = mmc_regulator_set_vqmmc(mmc, ios); ++ if (ret < 0) { ++ dev_err(host->dev, "Regulator set error %d\n", ret); ++ return ret; ++ } ++ } ++ ++ /* We should delay 20ms wait for timing setting finished. */ ++ mdelay(20); ++ ++ return 0; ++} ++ + static const struct dw_mci_drv_data starfive_data = { + .common_caps = MMC_CAP_CMD23, + .set_ios = dw_mci_starfive_set_ios, + .execute_tuning = dw_mci_starfive_execute_tuning, ++ .switch_voltage = dw_mci_starfive_switch_voltage, + }; + + static const struct of_device_id dw_mci_starfive_match[] = { diff --git a/target/linux/starfive/patches-6.6/0051-spi-spl022-Get-and-deassert-reset-in-probe.patch b/target/linux/starfive/patches-6.6/0051-spi-spl022-Get-and-deassert-reset-in-probe.patch new file mode 100644 index 0000000000..485ab0d9e1 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0051-spi-spl022-Get-and-deassert-reset-in-probe.patch @@ -0,0 +1,60 @@ +From 2cd3e51cb76d49d8db6274ebdc1ba1eb5c872f10 Mon Sep 17 00:00:00 2001 +From: "ziv.xu" <ziv.xu@starfivetech.com> +Date: Sun, 4 Feb 2024 10:35:24 +0800 +Subject: [PATCH 051/116] spi: spl022: Get and deassert reset in probe() + +This fix spi1~6 communication time out. + +Signed-off-by: ziv.xu <ziv.xu@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/spi/spi-pl022.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +--- a/drivers/spi/spi-pl022.c ++++ b/drivers/spi/spi-pl022.c +@@ -33,6 +33,7 @@ + #include <linux/pm_runtime.h> + #include <linux/of.h> + #include <linux/pinctrl/consumer.h> ++#include <linux/reset.h> + + /* + * This macro is used to define some register default values. +@@ -370,6 +371,7 @@ struct pl022 { + resource_size_t phybase; + void __iomem *virtbase; + struct clk *clk; ++ struct reset_control *rst; + struct spi_controller *host; + struct pl022_ssp_controller *host_info; + /* Message per-transfer pump */ +@@ -2181,6 +2183,19 @@ static int pl022_probe(struct amba_devic + goto err_no_clk_en; + } + ++ pl022->rst = devm_reset_control_get(&adev->dev, NULL); ++ if (IS_ERR(pl022->rst)) { ++ status = PTR_ERR(pl022->rst); ++ dev_err(&adev->dev, "could not retrieve SSP/SPI bus reset\n"); ++ goto err_no_rst; ++ } ++ ++ status = reset_control_deassert(pl022->rst); ++ if (status) { ++ dev_err(&adev->dev, "could not deassert SSP/SPI bus reset\n"); ++ goto err_no_rst_de; ++ } ++ + /* Initialize transfer pump */ + tasklet_init(&pl022->pump_transfers, pump_transfers, + (unsigned long)pl022); +@@ -2240,6 +2255,8 @@ static int pl022_probe(struct amba_devic + if (platform_info->enable_dma) + pl022_dma_remove(pl022); + err_no_irq: ++ err_no_rst_de: ++ err_no_rst: + clk_disable_unprepare(pl022->clk); + err_no_clk_en: + err_no_clk: diff --git a/target/linux/starfive/patches-6.6/0052-ASoC-dwc-i2s-Fix-getting-platform-data-error-for-Sta.patch b/target/linux/starfive/patches-6.6/0052-ASoC-dwc-i2s-Fix-getting-platform-data-error-for-Sta.patch new file mode 100644 index 0000000000..e274e84a25 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0052-ASoC-dwc-i2s-Fix-getting-platform-data-error-for-Sta.patch @@ -0,0 +1,30 @@ +From 9cc8de0cdc1600f460f618e342e1f524adad07c4 Mon Sep 17 00:00:00 2001 +From: Xingyu Wu <xingyu.wu@starfivetech.com> +Date: Wed, 21 Feb 2024 10:23:48 +0800 +Subject: [PATCH 052/116] ASoC: dwc: i2s: Fix getting platform data error for + StarFive JH7110 + +JH7110 need to use a DT specific function to get the platform data, +otherwise, it fails in probe(). + +Fixes: 9c97790a07dc ("ASoC: dwc: Fix non-DT instantiation") +Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + sound/soc/dwc/dwc-i2s.c | 4 ++++ + 1 file changed, 4 insertions(+) + +--- a/sound/soc/dwc/dwc-i2s.c ++++ b/sound/soc/dwc/dwc-i2s.c +@@ -917,7 +917,11 @@ static int jh7110_i2stx0_clk_cfg(struct + + static int dw_i2s_probe(struct platform_device *pdev) + { ++#ifdef CONFIG_OF ++ const struct i2s_platform_data *pdata = of_device_get_match_data(&pdev->dev); ++#else + const struct i2s_platform_data *pdata = pdev->dev.platform_data; ++#endif + struct dw_i2s_dev *dev; + struct resource *res; + int ret, irq; diff --git a/target/linux/starfive/patches-6.6/0053-ASoC-dwc-i2s-Add-RX-master-support-for-StarFive-JH71.patch b/target/linux/starfive/patches-6.6/0053-ASoC-dwc-i2s-Add-RX-master-support-for-StarFive-JH71.patch new file mode 100644 index 0000000000..8203beca52 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0053-ASoC-dwc-i2s-Add-RX-master-support-for-StarFive-JH71.patch @@ -0,0 +1,67 @@ +From 1be9bd37fdb5f50162dba0158e1fee295ebca9aa Mon Sep 17 00:00:00 2001 +From: Xingyu Wu <xingyu.wu@starfivetech.com> +Date: Tue, 17 Oct 2023 17:22:52 +0800 +Subject: [PATCH 053/116] ASoC: dwc: i2s: Add RX master support for StarFive + JH7110 SoC + +Add JH7110 I2S RX master support, so the PDM can work on JH7110 +EVB board. + +Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + sound/soc/dwc/dwc-i2s.c | 31 +++++++++++++++++++++++++++++++ + 1 file changed, 31 insertions(+) + +--- a/sound/soc/dwc/dwc-i2s.c ++++ b/sound/soc/dwc/dwc-i2s.c +@@ -906,6 +906,27 @@ static int jh7110_i2srx_crg_init(struct + return jh7110_i2s_crg_slave_init(dev); + } + ++/* Special syscon initialization about RX channel with master mode on JH7110 SoC */ ++static int jh7110_i2srx_mst_crg_init(struct dw_i2s_dev *dev) ++{ ++ struct regmap *regmap; ++ unsigned int args[5]; ++ ++ regmap = syscon_regmap_lookup_by_phandle_args(dev->dev->of_node, ++ "starfive,syscon", ++ 5, args); ++ if (IS_ERR(regmap)) ++ return dev_err_probe(dev->dev, PTR_ERR(regmap), "getting the regmap failed\n"); ++ ++ /* Enable I2Srx with syscon register, args[0]: offset, args[1]: mask */ ++ regmap_update_bits(regmap, args[0], args[1], args[1]); ++ ++ /* Change I2Srx source (PDM) with syscon register, args[0]: offset, args[1]: mask */ ++ regmap_update_bits(regmap, args[2], args[3], args[4]); ++ ++ return jh7110_i2s_crg_master_init(dev); ++} ++ + static int jh7110_i2stx0_clk_cfg(struct i2s_clk_config_data *config) + { + struct dw_i2s_dev *dev = container_of(config, struct dw_i2s_dev, config); +@@ -1086,11 +1107,21 @@ static const struct i2s_platform_data jh + .i2s_pd_init = jh7110_i2srx_crg_init, + }; + ++static const struct i2s_platform_data jh7110_i2srx_mst_data = { ++ .cap = DWC_I2S_RECORD | DW_I2S_MASTER, ++ .channel = TWO_CHANNEL_SUPPORT, ++ .snd_fmts = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, ++ .snd_rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000, ++ .i2s_clk_cfg = jh7110_i2stx0_clk_cfg, ++ .i2s_pd_init = jh7110_i2srx_mst_crg_init, ++}; ++ + static const struct of_device_id dw_i2s_of_match[] = { + { .compatible = "snps,designware-i2s", }, + { .compatible = "starfive,jh7110-i2stx0", .data = &jh7110_i2stx0_data, }, + { .compatible = "starfive,jh7110-i2stx1", .data = &jh7110_i2stx1_data,}, + { .compatible = "starfive,jh7110-i2srx", .data = &jh7110_i2srx_data,}, ++ { .compatible = "starfive,jh7110-i2srx-master", .data = &jh7110_i2srx_mst_data,}, + {}, + }; + diff --git a/target/linux/starfive/patches-6.6/0054-pinctrl-starfive-jh7110-Unset-.strict-in-pinmux_ops.patch b/target/linux/starfive/patches-6.6/0054-pinctrl-starfive-jh7110-Unset-.strict-in-pinmux_ops.patch new file mode 100644 index 0000000000..51976c656e --- /dev/null +++ b/target/linux/starfive/patches-6.6/0054-pinctrl-starfive-jh7110-Unset-.strict-in-pinmux_ops.patch @@ -0,0 +1,24 @@ +From 1ec26ba377d8ae59cd09811ec78623a750a9c150 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Mon, 26 Feb 2024 11:35:44 +0800 +Subject: [PATCH 054/116] pinctrl: starfive: jh7110: Unset .strict in + pinmux_ops + +Allow simultaneous use of the same pin for GPIO and another function. +This feature is used in HDMI hot plug detect. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/pinctrl/starfive/pinctrl-starfive-jh7110.c | 1 - + 1 file changed, 1 deletion(-) + +--- a/drivers/pinctrl/starfive/pinctrl-starfive-jh7110.c ++++ b/drivers/pinctrl/starfive/pinctrl-starfive-jh7110.c +@@ -327,7 +327,6 @@ static const struct pinmux_ops jh7110_pi + .get_function_name = pinmux_generic_get_function_name, + .get_function_groups = pinmux_generic_get_function_groups, + .set_mux = jh7110_set_mux, +- .strict = true, + }; + + static const u8 jh7110_drive_strength_mA[4] = { 2, 4, 8, 12 }; diff --git a/target/linux/starfive/patches-6.6/0055-mm-pgtable-generic.c-Export-symbol-__pte_offset_map.patch b/target/linux/starfive/patches-6.6/0055-mm-pgtable-generic.c-Export-symbol-__pte_offset_map.patch new file mode 100644 index 0000000000..39699e8b97 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0055-mm-pgtable-generic.c-Export-symbol-__pte_offset_map.patch @@ -0,0 +1,22 @@ +From 005549a2bd839335b0e3dc4152f00f642b524f07 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Sat, 7 Oct 2023 18:10:20 +0800 +Subject: [PATCH 055/116] mm/pgtable-generic.c: Export symbol __pte_offset_map + +So JH7110 vdec can call pte_offset_map() when it is built as a module. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + mm/pgtable-generic.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/mm/pgtable-generic.c ++++ b/mm/pgtable-generic.c +@@ -304,6 +304,7 @@ nomap: + rcu_read_unlock(); + return NULL; + } ++EXPORT_SYMBOL(__pte_offset_map); + + pte_t *pte_offset_map_nolock(struct mm_struct *mm, pmd_t *pmd, + unsigned long addr, spinlock_t **ptlp) diff --git a/target/linux/starfive/patches-6.6/0056-riscv-dts-starfive-Add-JH7110-EVB-device-tree.patch b/target/linux/starfive/patches-6.6/0056-riscv-dts-starfive-Add-JH7110-EVB-device-tree.patch new file mode 100644 index 0000000000..7b803d3997 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0056-riscv-dts-starfive-Add-JH7110-EVB-device-tree.patch @@ -0,0 +1,2623 @@ +From 9f46b0d43f8945ff3a8b81ddc6567df370b60911 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Fri, 28 Jul 2023 17:19:12 +0800 +Subject: [PATCH 056/116] riscv: dts: starfive: Add JH7110 EVB device tree + +Add JH7110 evaluation board device tree. +The code is ported from tag JH7110_SDK_6.1_v5.11.3 + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + arch/riscv/boot/dts/starfive/Makefile | 3 + + arch/riscv/boot/dts/starfive/jh7110-clk.dtsi | 80 ++ + .../boot/dts/starfive/jh7110-evb-pinctrl.dtsi | 997 ++++++++++++++++++ + arch/riscv/boot/dts/starfive/jh7110-evb.dts | 35 + + arch/riscv/boot/dts/starfive/jh7110-evb.dtsi | 854 +++++++++++++++ + arch/riscv/boot/dts/starfive/jh7110.dtsi | 482 ++++++++- + 6 files changed, 2444 insertions(+), 7 deletions(-) + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-clk.dtsi + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-evb-pinctrl.dtsi + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-evb.dts + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-evb.dtsi + +--- a/arch/riscv/boot/dts/starfive/Makefile ++++ b/arch/riscv/boot/dts/starfive/Makefile +@@ -4,9 +4,12 @@ DTC_FLAGS_jh7100-beaglev-starlight := -@ + DTC_FLAGS_jh7100-starfive-visionfive-v1 := -@ + DTC_FLAGS_jh7110-starfive-visionfive-2-v1.2a := -@ + DTC_FLAGS_jh7110-starfive-visionfive-2-v1.3b := -@ ++DTC_FLAGS_jh7110-evb := -@ + + dtb-$(CONFIG_ARCH_STARFIVE) += jh7100-beaglev-starlight.dtb + dtb-$(CONFIG_ARCH_STARFIVE) += jh7100-starfive-visionfive-v1.dtb + + dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-starfive-visionfive-2-v1.2a.dtb + dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-starfive-visionfive-2-v1.3b.dtb ++ ++dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-evb.dtb +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-clk.dtsi +@@ -0,0 +1,80 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2023 StarFive Technology Co., Ltd. ++ */ ++ ++/ { ++ ac108_mclk: ac108_mclk { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ clock-frequency = <24000000>; ++ }; ++ ++ clk_ext_camera: clk-ext-camera { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ clock-frequency = <24000000>; ++ }; ++ ++ wm8960_mclk: wm8960_mclk { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ clock-frequency = <24576000>; ++ }; ++}; ++ ++&dvp_clk { ++ clock-frequency = <74250000>; ++}; ++ ++&gmac0_rgmii_rxin { ++ clock-frequency = <125000000>; ++}; ++ ++&gmac0_rmii_refin { ++ clock-frequency = <50000000>; ++}; ++ ++&gmac1_rgmii_rxin { ++ clock-frequency = <125000000>; ++}; ++ ++&gmac1_rmii_refin { ++ clock-frequency = <50000000>; ++}; ++ ++&hdmitx0_pixelclk { ++ clock-frequency = <297000000>; ++}; ++ ++&i2srx_bclk_ext { ++ clock-frequency = <12288000>; ++}; ++ ++&i2srx_lrck_ext { ++ clock-frequency = <192000>; ++}; ++ ++&i2stx_bclk_ext { ++ clock-frequency = <12288000>; ++}; ++ ++&i2stx_lrck_ext { ++ clock-frequency = <192000>; ++}; ++ ++&mclk_ext { ++ clock-frequency = <12288000>; ++}; ++ ++&osc { ++ clock-frequency = <24000000>; ++}; ++ ++&rtc_osc { ++ clock-frequency = <32768>; ++}; ++ ++&tdm_ext { ++ clock-frequency = <49152000>; ++}; +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb-pinctrl.dtsi +@@ -0,0 +1,997 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2023 StarFive Technology Co., Ltd. ++ * Author: Hal Feng <hal.feng@starfivetech.com> ++ */ ++ ++#include "jh7110-pinfunc.h" ++ ++&sysgpio { ++ can0_pins: can0-0 { ++ can-pins { ++ pinmux = <GPIOMUX(30, GPOUT_SYS_CAN0_TXD, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <GPIOMUX(31, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_CAN0_RXD)>, ++ <GPIOMUX(32, GPOUT_SYS_CAN0_STBY, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ input-enable; ++ }; ++ }; ++ ++ can1_pins: can1-0 { ++ can-pins { ++ pinmux = <GPIOMUX(29, GPOUT_SYS_CAN1_TXD, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <GPIOMUX(27, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_CAN1_RXD)>, ++ <GPIOMUX(45, GPOUT_SYS_CAN1_STBY, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ drive-strength = <12>; ++ input-enable; ++ }; ++ }; ++ ++ dvp_pins: dvp-0 { ++ dvp-pins{ ++ pinmux = <PINMUX(21, 2)>, ++ <PINMUX(22, 2)>, ++ <PINMUX(23, 2)>, ++ <PINMUX(24, 2)>, ++ <PINMUX(25, 2)>, ++ <PINMUX(26, 2)>, ++ <PINMUX(27, 2)>, ++ <PINMUX(28, 2)>, ++ <PINMUX(29, 2)>, ++ <PINMUX(30, 2)>, ++ <PINMUX(31, 2)>, ++ <PINMUX(32, 2)>, ++ <PINMUX(33, 2)>, ++ <PINMUX(34, 2)>, ++ <PINMUX(35, 2)>; ++ input-enable; ++ }; ++ }; ++ ++ emmc0_pins: emmc0-0 { ++ emmc-pins { ++ pinmux = <GPIOMUX(22, GPOUT_SYS_SDIO0_RST, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <PINMUX(64, 0)>, ++ <PINMUX(65, 0)>, ++ <PINMUX(66, 0)>, ++ <PINMUX(67, 0)>, ++ <PINMUX(68, 0)>, ++ <PINMUX(69, 0)>, ++ <PINMUX(70, 0)>, ++ <PINMUX(71, 0)>, ++ <PINMUX(72, 0)>, ++ <PINMUX(73, 0)>; ++ bias-pull-up; ++ drive-strength = <12>; ++ input-enable; ++ slew-rate = <1>; ++ }; ++ }; ++ ++ emmc1_pins: emmc1-0 { ++ emmc-pins { ++ pinmux = <GPIOMUX(51, GPOUT_SYS_SDIO1_RST, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <GPIOMUX(38, GPOUT_SYS_SDIO1_CLK, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <GPIOMUX(36, GPOUT_SYS_SDIO1_CMD, ++ GPOEN_SYS_SDIO1_CMD, ++ GPI_SYS_SDIO1_CMD)>, ++ <GPIOMUX(43, GPOUT_SYS_SDIO1_DATA0, ++ GPOEN_SYS_SDIO1_DATA0, ++ GPI_SYS_SDIO1_DATA0)>, ++ <GPIOMUX(48, GPOUT_SYS_SDIO1_DATA1, ++ GPOEN_SYS_SDIO1_DATA1, ++ GPI_SYS_SDIO1_DATA1)>, ++ <GPIOMUX(53, GPOUT_SYS_SDIO1_DATA2, ++ GPOEN_SYS_SDIO1_DATA2, ++ GPI_SYS_SDIO1_DATA2)>, ++ <GPIOMUX(63, GPOUT_SYS_SDIO1_DATA3, ++ GPOEN_SYS_SDIO1_DATA3, ++ GPI_SYS_SDIO1_DATA3)>, ++ <GPIOMUX(52, GPOUT_SYS_SDIO1_DATA4, ++ GPOEN_SYS_SDIO1_DATA4, ++ GPI_SYS_SDIO1_DATA4)>, ++ <GPIOMUX(39, GPOUT_SYS_SDIO1_DATA5, ++ GPOEN_SYS_SDIO1_DATA5, ++ GPI_SYS_SDIO1_DATA5)>, ++ <GPIOMUX(46, GPOUT_SYS_SDIO1_DATA6, ++ GPOEN_SYS_SDIO1_DATA6, ++ GPI_SYS_SDIO1_DATA6)>, ++ <GPIOMUX(47, GPOUT_SYS_SDIO1_DATA7, ++ GPOEN_SYS_SDIO1_DATA7, ++ GPI_SYS_SDIO1_DATA7)>; ++ bias-pull-up; ++ input-enable; ++ }; ++ }; ++ ++ gmac0_pins: gmac0-0 { ++ reset-pins { ++ pinmux = <GPIOMUX(13, GPOUT_HIGH, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-pull-up; ++ }; ++ }; ++ ++ gmac1_pins: gmac1-0 { ++ mdc-pins { ++ pinmux = <PINMUX(75, 0)>; ++ }; ++ }; ++ ++ hdmi_pins: hdmi-0 { ++ scl-pins { ++ pinmux = <GPIOMUX(7, GPOUT_SYS_HDMI_DDC_SCL, ++ GPOEN_SYS_HDMI_DDC_SCL, ++ GPI_SYS_HDMI_DDC_SCL)>; ++ bias-pull-up; ++ input-enable; ++ }; ++ ++ sda-pins { ++ pinmux = <GPIOMUX(8, GPOUT_SYS_HDMI_DDC_SDA, ++ GPOEN_SYS_HDMI_DDC_SDA, ++ GPI_SYS_HDMI_DDC_SDA)>; ++ bias-pull-up; ++ input-enable; ++ }; ++ ++ cec-pins { ++ pinmux = <GPIOMUX(14, GPOUT_SYS_HDMI_CEC_SDA, ++ GPOEN_SYS_HDMI_CEC_SDA, ++ GPI_SYS_HDMI_CEC_SDA)>; ++ bias-pull-up; ++ input-enable; ++ }; ++ ++ hpd-pins { ++ pinmux = <GPIOMUX(15, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_HDMI_HPD)>; ++ bias-disable; /* external pull-up */ ++ input-enable; ++ }; ++ }; ++ ++ i2c0_pins: i2c0-0 { ++ i2c-pins { ++ pinmux = <GPIOMUX(57, GPOUT_LOW, ++ GPOEN_SYS_I2C0_CLK, ++ GPI_SYS_I2C0_CLK)>, ++ <GPIOMUX(58, GPOUT_LOW, ++ GPOEN_SYS_I2C0_DATA, ++ GPI_SYS_I2C0_DATA)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ }; ++ ++ i2c1_pins: i2c1-0 { ++ i2c-pins { ++ pinmux = <GPIOMUX(49, GPOUT_LOW, ++ GPOEN_SYS_I2C1_CLK, ++ GPI_SYS_I2C1_CLK)>, ++ <GPIOMUX(50, GPOUT_LOW, ++ GPOEN_SYS_I2C1_DATA, ++ GPI_SYS_I2C1_DATA)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ }; ++ ++ i2c2_pins: i2c2-0 { ++ i2c-pins { ++ pinmux = <GPIOMUX(11, GPOUT_LOW, ++ GPOEN_SYS_I2C2_CLK, ++ GPI_SYS_I2C2_CLK)>, ++ <GPIOMUX(9, GPOUT_LOW, ++ GPOEN_SYS_I2C2_DATA, ++ GPI_SYS_I2C2_DATA)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ }; ++ ++ i2c3_pins: i2c3-0 { ++ i2c-pins { ++ pinmux = <GPIOMUX(51, GPOUT_LOW, ++ GPOEN_SYS_I2C3_CLK, ++ GPI_SYS_I2C3_CLK)>, ++ <GPIOMUX(52, GPOUT_LOW, ++ GPOEN_SYS_I2C3_DATA, ++ GPI_SYS_I2C3_DATA)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ }; ++ ++ i2c4_pins: i2c4-0 { ++ i2c-pins { ++ pinmux = <GPIOMUX(18, GPOUT_LOW, ++ GPOEN_SYS_I2C4_CLK, ++ GPI_SYS_I2C4_CLK)>, ++ <GPIOMUX(12, GPOUT_LOW, ++ GPOEN_SYS_I2C4_DATA, ++ GPI_SYS_I2C4_DATA)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ }; ++ ++ i2c5_pins: i2c5-0 { ++ i2c-pins { ++ pinmux = <GPIOMUX(19, GPOUT_LOW, ++ GPOEN_SYS_I2C5_CLK, ++ GPI_SYS_I2C5_CLK)>, ++ <GPIOMUX(20, GPOUT_LOW, ++ GPOEN_SYS_I2C5_DATA, ++ GPI_SYS_I2C5_DATA)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ }; ++ ++ i2c6_pins: i2c6-0 { ++ i2c-pins { ++ pinmux = <GPIOMUX(16, GPOUT_LOW, ++ GPOEN_SYS_I2C6_CLK, ++ GPI_SYS_I2C6_CLK)>, ++ <GPIOMUX(17, GPOUT_LOW, ++ GPOEN_SYS_I2C6_DATA, ++ GPI_SYS_I2C6_DATA)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ }; ++ ++ i2s_clk_pins: i2s-clk-0 { ++ bclk-lrck-pins { ++ pinmux = <GPIOMUX(38, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_I2STX1_BCLK)>, ++ <GPIOMUX(38, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_I2SRX_BCLK)>, ++ <GPIOMUX(63, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_I2STX1_LRCK)>, ++ <GPIOMUX(63, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_I2SRX_LRCK)>; ++ input-enable; ++ }; ++ }; ++ ++ i2srx_clk_pins: i2srx-clk-0 { ++ mclk-pins { ++ pinmux = <GPIOMUX(58, GPOUT_SYS_MCLK, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ input-enable; ++ }; ++ }; ++ ++ i2srx_pins: i2srx-0 { ++ i2srx-pins { ++ pinmux = <GPIOMUX(61, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_I2SRX_SDIN0)>; ++ input-enable; ++ }; ++ }; ++ ++ i2stx_pins: i2stx-0 { ++ i2stx-pins { ++ pinmux = <GPIOMUX(44, GPOUT_SYS_I2STX1_SDO0, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ input-enable; ++ }; ++ }; ++ ++ mclk_ext_pins: mclk-ext-0 { ++ mclk-ext-pins { ++ pinmux = <GPIOMUX(4, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_MCLK_EXT)>; ++ input-enable; ++ }; ++ }; ++ ++ pdm_pins: pdm-0 { ++ pdm-pins { ++ pinmux = <GPIOMUX(54, GPOUT_SYS_PDM_MCLK, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <GPIOMUX(60, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_PDM_DMIC0)>; ++ input-enable; ++ }; ++ }; ++ ++ pwm_ch0to3_pins: pwm-ch0to3-0 { ++ pwm-pins { ++ pinmux = <GPIOMUX(45, GPOUT_SYS_PWM_CHANNEL0, ++ GPOEN_SYS_PWM0_CHANNEL0, ++ GPI_NONE)>, ++ <GPIOMUX(46, GPOUT_SYS_PWM_CHANNEL1, ++ GPOEN_SYS_PWM0_CHANNEL1, ++ GPI_NONE)>, ++ <GPIOMUX(47, GPOUT_SYS_PWM_CHANNEL2, ++ GPOEN_SYS_PWM0_CHANNEL2, ++ GPI_NONE)>, ++ <GPIOMUX(48, GPOUT_SYS_PWM_CHANNEL3, ++ GPOEN_SYS_PWM0_CHANNEL3, ++ GPI_NONE)>; ++ drive-strength = <12>; ++ }; ++ }; ++ ++ pwmdac_pins: pwmdac-0 { ++ pwmdac-pins { ++ pinmux = <GPIOMUX(57, GPOUT_SYS_PWMDAC_LEFT, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <GPIOMUX(42, GPOUT_SYS_PWMDAC_RIGHT, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ }; ++ }; ++ ++ rgb_pad_pins: rgb-pad-pins { ++ rgb-0-pins { ++ pinmux = <PINMUX(36, 1)>; ++ drive-strength = <12>; ++ input-disable; ++ slew-rate = <1>; ++ }; ++ ++ rgb-pins { ++ pinmux = <PINMUX(37, 1)>, ++ <PINMUX(38, 1)>, ++ <PINMUX(39, 1)>, ++ <PINMUX(40, 1)>, ++ <PINMUX(41, 1)>, ++ <PINMUX(42, 1)>, ++ <PINMUX(43, 1)>, ++ <PINMUX(44, 1)>, ++ <PINMUX(45, 1)>, ++ <PINMUX(46, 1)>, ++ <PINMUX(47, 1)>, ++ <PINMUX(48, 1)>, ++ <PINMUX(49, 1)>, ++ <PINMUX(50, 1)>, ++ <PINMUX(51, 1)>, ++ <PINMUX(52, 1)>, ++ <PINMUX(53, 1)>, ++ <PINMUX(54, 1)>, ++ <PINMUX(55, 1)>, ++ <PINMUX(56, 1)>, ++ <PINMUX(57, 1)>, ++ <PINMUX(58, 1)>, ++ <PINMUX(59, 1)>, ++ <PINMUX(60, 1)>, ++ <PINMUX(61, 1)>, ++ <PINMUX(62, 1)>, ++ <PINMUX(63, 1)>; ++ drive-strength = <12>; ++ input-disable; ++ }; ++ }; ++ ++ sdcard0_pins: sdcard0-0 { ++ sdcard-pins { ++ pinmux = <GPIOMUX(24, GPOUT_SYS_SDIO0_RST, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <PINMUX(64, 0)>, ++ <PINMUX(65, 0)>, ++ <PINMUX(66, 0)>, ++ <PINMUX(67, 0)>, ++ <PINMUX(68, 0)>, ++ <PINMUX(69, 0)>; ++ bias-pull-up; ++ drive-strength = <12>; ++ input-enable; ++ slew-rate = <1>; ++ }; ++ }; ++ ++ sdcard1_pins: sdcard1-0 { ++ sdcard-pins { ++ pinmux = <GPIOMUX(56, GPOUT_SYS_SDIO1_CLK, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <GPIOMUX(50, GPOUT_SYS_SDIO1_CMD, ++ GPOEN_SYS_SDIO1_CMD, ++ GPI_SYS_SDIO1_CMD)>, ++ <GPIOMUX(49, GPOUT_SYS_SDIO1_DATA0, ++ GPOEN_SYS_SDIO1_DATA0, ++ GPI_SYS_SDIO1_DATA0)>, ++ <GPIOMUX(45, GPOUT_SYS_SDIO1_DATA1, ++ GPOEN_SYS_SDIO1_DATA1, ++ GPI_SYS_SDIO1_DATA1)>, ++ <GPIOMUX(62, GPOUT_SYS_SDIO1_DATA2, ++ GPOEN_SYS_SDIO1_DATA2, ++ GPI_SYS_SDIO1_DATA2)>, ++ <GPIOMUX(40, GPOUT_SYS_SDIO1_DATA3, ++ GPOEN_SYS_SDIO1_DATA3, ++ GPI_SYS_SDIO1_DATA3)>; ++ bias-pull-up; ++ input-enable; ++ }; ++ }; ++ ++ spdif_pins: spdif-0 { ++ spdif-pins { ++ pinmux = <GPIOMUX(57, GPOUT_SYS_SPDIF, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-pull-up; ++ input-enable; ++ }; ++ }; ++ ++ spi0_pins: spi0-0 { ++ mosi-pins { ++ pinmux = <GPIOMUX(38, GPOUT_SYS_SPI0_TXD, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ miso-pins { ++ pinmux = <GPIOMUX(39, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_SPI0_RXD)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ ++ sck-pins { ++ pinmux = <GPIOMUX(36, GPOUT_SYS_SPI0_CLK, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI0_CLK)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ ss-pins { ++ pinmux = <GPIOMUX(37, GPOUT_SYS_SPI0_FSS, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI0_FSS)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ }; ++ ++ spi1_pins: spi1-0 { ++ mosi-pins { ++ pinmux = <GPIOMUX(42, GPOUT_SYS_SPI1_TXD, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ miso-pins { ++ pinmux = <GPIOMUX(43, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_SPI1_RXD)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ ++ sck-pins { ++ pinmux = <GPIOMUX(40, GPOUT_SYS_SPI1_CLK, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI1_CLK)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ ss-pins { ++ pinmux = <GPIOMUX(41, GPOUT_SYS_SPI1_FSS, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI1_FSS)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ }; ++ ++ spi2_pins: spi2-0 { ++ mosi-pins { ++ pinmux = <GPIOMUX(46, GPOUT_SYS_SPI2_TXD, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ miso-pins { ++ pinmux = <GPIOMUX(47, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_SPI2_RXD)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ ++ sck-pins { ++ pinmux = <GPIOMUX(44, GPOUT_SYS_SPI2_CLK, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI2_CLK)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ ss-pins { ++ pinmux = <GPIOMUX(45, GPOUT_SYS_SPI2_FSS, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI2_FSS)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ }; ++ ++ spi3_pins: spi3-0 { ++ mosi-pins { ++ pinmux = <GPIOMUX(50, GPOUT_SYS_SPI3_TXD, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ miso-pins { ++ pinmux = <GPIOMUX(51, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_SPI3_RXD)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ ++ sck-pins { ++ pinmux = <GPIOMUX(48, GPOUT_SYS_SPI3_CLK, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI3_CLK)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ ss-pins { ++ pinmux = <GPIOMUX(49, GPOUT_SYS_SPI3_FSS, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI3_FSS)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ }; ++ ++ spi4_pins: spi4-0 { ++ mosi-pins { ++ pinmux = <GPIOMUX(54, GPOUT_SYS_SPI4_TXD, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ miso-pins { ++ pinmux = <GPIOMUX(55, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_SPI4_RXD)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ ++ sck-pins { ++ pinmux = <GPIOMUX(52, GPOUT_SYS_SPI4_CLK, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI4_CLK)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ ss-pins { ++ pinmux = <GPIOMUX(53, GPOUT_SYS_SPI4_FSS, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI4_FSS)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ }; ++ ++ spi5_pins: spi5-0 { ++ mosi-pins { ++ pinmux = <GPIOMUX(58, GPOUT_SYS_SPI5_TXD, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ miso-pins { ++ pinmux = <GPIOMUX(59, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_SPI5_RXD)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ ++ sck-pins { ++ pinmux = <GPIOMUX(56, GPOUT_SYS_SPI5_CLK, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI5_CLK)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ ss-pins { ++ pinmux = <GPIOMUX(57, GPOUT_SYS_SPI5_FSS, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI5_FSS)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ }; ++ ++ spi6_pins: spi6-0 { ++ mosi-pins { ++ pinmux = <GPIOMUX(62, GPOUT_SYS_SPI6_TXD, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ miso-pins { ++ pinmux = <GPIOMUX(63, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_SPI6_RXD)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ ++ sck-pins { ++ pinmux = <GPIOMUX(60, GPOUT_SYS_SPI6_CLK, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI6_CLK)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ++ ss-pins { ++ pinmux = <GPIOMUX(61, GPOUT_SYS_SPI6_FSS, ++ GPOEN_ENABLE, ++ GPI_SYS_SPI6_FSS)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ }; ++ ++ tdm_pins: tdm-0 { ++ tx-pins { ++ pinmux = <GPIOMUX(44, GPOUT_SYS_TDM_TXD, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-pull-up; ++ drive-strength = <2>; ++ input-disable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ ++ rx-pins { ++ pinmux = <GPIOMUX(61, GPOUT_HIGH, ++ GPOEN_DISABLE, ++ GPI_SYS_TDM_RXD)>; ++ input-enable; ++ }; ++ ++ sync-pins { ++ pinmux = <GPIOMUX(63, GPOUT_HIGH, ++ GPOEN_DISABLE, ++ GPI_SYS_TDM_SYNC)>; ++ input-enable; ++ }; ++ ++ pcmclk-pins { ++ pinmux = <GPIOMUX(38, GPOUT_HIGH, ++ GPOEN_DISABLE, ++ GPI_SYS_TDM_CLK)>; ++ input-enable; ++ }; ++ }; ++ ++ uart0_pins: uart0-0 { ++ tx-pins { ++ pinmux = <GPIOMUX(5, GPOUT_SYS_UART0_TX, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ drive-strength = <12>; ++ input-disable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ ++ rx-pins { ++ pinmux = <GPIOMUX(6, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_UART0_RX)>; ++ bias-pull-up; ++ drive-strength = <2>; ++ input-enable; ++ input-schmitt-enable; ++ slew-rate = <0>; ++ }; ++ }; ++ ++ uart1_pins: uart1-0 { ++ tx-pins { ++ pinmux = <GPIOMUX(30, GPOUT_SYS_UART1_TX, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ drive-strength = <12>; ++ input-disable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ ++ rx-pins { ++ pinmux = <GPIOMUX(31, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_UART1_RX)>; ++ bias-pull-up; ++ drive-strength = <2>; ++ input-enable; ++ input-schmitt-enable; ++ slew-rate = <0>; ++ }; ++ ++ cts-pins { ++ pinmux = <GPIOMUX(29, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_UART1_CTS)>; ++ input-enable; ++ }; ++ ++ rts-pins { ++ pinmux = <GPIOMUX(27, GPOUT_SYS_UART1_RTS, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ input-enable; ++ }; ++ }; ++ ++ uart2_pins: uart2-0 { ++ tx-pins { ++ pinmux = <GPIOMUX(30, GPOUT_SYS_UART2_TX, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ drive-strength = <12>; ++ input-disable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ ++ rx-pins { ++ pinmux = <GPIOMUX(31, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_UART2_RX)>; ++ bias-pull-up; ++ drive-strength = <2>; ++ input-enable; ++ input-schmitt-enable; ++ slew-rate = <0>; ++ }; ++ ++ cts-pins { ++ pinmux = <GPIOMUX(29, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_UART2_CTS)>; ++ input-enable; ++ }; ++ ++ rts-pins { ++ pinmux = <GPIOMUX(27, GPOUT_SYS_UART2_RTS, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ input-enable; ++ }; ++ }; ++ ++ uart3_pins: uart3-0 { ++ tx-pins { ++ pinmux = <GPIOMUX(30, GPOUT_SYS_UART3_TX, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ drive-strength = <12>; ++ input-disable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ ++ rx-pins { ++ pinmux = <GPIOMUX(31, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_UART3_RX)>; ++ bias-pull-up; ++ drive-strength = <2>; ++ input-enable; ++ input-schmitt-enable; ++ slew-rate = <0>; ++ }; ++ }; ++ ++ uart4_pins: uart4-0 { ++ tx-pins { ++ pinmux = <GPIOMUX(30, GPOUT_SYS_UART4_TX, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ drive-strength = <12>; ++ input-disable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ ++ rx-pins { ++ pinmux = <GPIOMUX(31, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_UART4_RX)>; ++ bias-pull-up; ++ drive-strength = <2>; ++ input-enable; ++ input-schmitt-enable; ++ slew-rate = <0>; ++ }; ++ ++ cts-pins { ++ pinmux = <GPIOMUX(29, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_UART4_CTS)>; ++ input-enable; ++ }; ++ ++ rts-pins { ++ pinmux = <GPIOMUX(27, GPOUT_SYS_UART4_RTS, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ input-enable; ++ }; ++ }; ++ ++ uart5_pins: uart5-0 { ++ tx-pins { ++ pinmux = <GPIOMUX(30, GPOUT_SYS_UART5_TX, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ drive-strength = <12>; ++ input-disable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ ++ rx-pins { ++ pinmux = <GPIOMUX(31, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_UART5_RX)>; ++ bias-pull-up; ++ drive-strength = <2>; ++ input-enable; ++ input-schmitt-enable; ++ slew-rate = <0>; ++ }; ++ ++ cts-pins { ++ pinmux = <GPIOMUX(29, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_UART5_CTS)>; ++ input-enable; ++ }; ++ ++ rts-pins { ++ pinmux = <GPIOMUX(27, GPOUT_SYS_UART5_RTS, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ input-enable; ++ }; ++ }; ++ ++ usb_pins: usb-0 { ++ usb-pins { ++ pinmux = <GPIOMUX(33, GPOUT_HIGH, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <GPIOMUX(34, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_USB_OVERCURRENT)>; ++ input-enable; ++ }; ++ }; ++}; ++ ++&aongpio { ++ pwm_ch4to5_pins: pwm-ch4to5-0 { ++ pwm-pins { ++ pinmux = <GPIOMUX(1, GPOUT_AON_PTC0_PWM4, /* PAD_RGPIO0 */ ++ GPOEN_AON_PTC0_OE_N_4, ++ GPI_NONE)>, ++ <GPIOMUX(2, GPOUT_AON_PTC0_PWM5, /* PAD_RGPIO1 */ ++ GPOEN_AON_PTC0_OE_N_5, ++ GPI_NONE)>; ++ drive-strength = <12>; ++ }; ++ }; ++ ++ pwm_ch6to7_pins: pwm-ch6to7-0 { ++ pwm-pins { ++ pinmux = <GPIOMUX(1, GPOUT_AON_PTC0_PWM6, /* PAD_RGPIO0 */ ++ GPOEN_AON_PTC0_OE_N_6, ++ GPI_NONE)>, ++ <GPIOMUX(2, GPOUT_AON_PTC0_PWM7, /* PAD_RGPIO1 */ ++ GPOEN_AON_PTC0_OE_N_7, ++ GPI_NONE)>; ++ drive-strength = <12>; ++ }; ++ }; ++}; +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb.dts +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2023 StarFive Technology Co., Ltd. ++ */ ++ ++/dts-v1/; ++#include "jh7110-evb.dtsi" ++ ++/ { ++ model = "StarFive JH7110 EVB"; ++ compatible = "starfive,jh7110-evb", "starfive,jh7110"; ++}; ++ ++&mmc0 { ++ assigned-clocks = <&syscrg JH7110_SYSCLK_SDIO0_SDCARD>; ++ assigned-clock-rates = <50000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&sdcard0_pins>; ++ max-frequency = <100000000>; ++ card-detect-delay = <300>; ++ bus-width = <4>; ++ no-sdio; ++ no-mmc; ++ broken-cd; ++ post-power-on-delay-ms = <200>; ++ status = "okay"; ++}; ++ ++&pcie1 { ++ status = "okay"; ++}; ++ ++&usb0 { ++ status = "okay"; ++}; +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb.dtsi +@@ -0,0 +1,854 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2023 StarFive Technology Co., Ltd. ++ */ ++ ++/dts-v1/; ++#include "jh7110.dtsi" ++#include "jh7110-clk.dtsi" ++#include "jh7110-evb-pinctrl.dtsi" ++#include <dt-bindings/gpio/gpio.h> ++ ++/ { ++ aliases { ++ ethernet0 = &gmac0; ++ ethernet1 = &gmac1; ++ i2c0 = &i2c0; ++ i2c1 = &i2c1; ++ i2c2 = &i2c2; ++ i2c3 = &i2c3; ++ i2c4 = &i2c4; ++ i2c5 = &i2c5; ++ i2c6 = &i2c6; ++ pcie0 = &pcie0; ++ pcie1 = &pcie1; ++ serial0 = &uart0; ++ serial3 = &uart3; ++ }; ++ ++ chosen { ++ stdout-path = "serial0:115200n8"; ++ }; ++ ++ cpus { ++ timebase-frequency = <4000000>; ++ }; ++ ++ memory@40000000 { ++ device_type = "memory"; ++ reg = <0x0 0x40000000 0x1 0x0>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ linux,cma { ++ compatible = "shared-dma-pool"; ++ reusable; ++ size = <0x0 0x20000000>; ++ alignment = <0x0 0x1000>; ++ alloc-ranges = <0x0 0x70000000 0x0 0x20000000>; ++ linux,cma-default; ++ }; ++ ++ e24_mem: e24@c0000000 { ++ reg = <0x0 0x6ce00000 0x0 0x1600000>; ++ }; ++ ++ xrp_reserved: xrpbuffer@f0000000 { ++ reg = <0x0 0x69c00000 0x0 0x01ffffff ++ 0x0 0x6bc00000 0x0 0x00001000 ++ 0x0 0x6bc01000 0x0 0x00fff000 ++ 0x0 0x6cc00000 0x0 0x00001000>; ++ }; ++ }; ++ ++ /* i2s + hdmi */ ++ sound1: snd-card1 { ++ compatible = "simple-audio-card"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ simple-audio-card,name = "StarFive-HDMI-Sound-Card"; ++ simple-audio-card,dai-link@0 { ++ reg = <0>; ++ format = "i2s"; ++ bitclock-master = <&sndi2s0>; ++ frame-master = <&sndi2s0>; ++ mclk-fs = <256>; ++ status = "okay"; ++ ++ sndi2s0: cpu { ++ sound-dai = <&i2stx0>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi>; ++ }; ++ }; ++ }; ++}; ++ ++&U74_1 { ++ /delete-property/ clocks; ++ /delete-property/ clock-names; ++}; ++ ++&U74_2 { ++ /delete-property/ clocks; ++ /delete-property/ clock-names; ++}; ++ ++&U74_3 { ++ /delete-property/ clocks; ++ /delete-property/ clock-names; ++}; ++ ++&U74_4 { ++ /delete-property/ clocks; ++ /delete-property/ clock-names; ++}; ++ ++&can0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&can0_pins>; ++ status = "disabled"; ++}; ++ ++&can1 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&can1_pins>; ++ status = "disabled"; ++}; ++ ++&co_process { ++ memory-region = <&e24_mem>; ++ status = "okay"; ++}; ++ ++&dc8200 { ++ status = "okay"; ++ ++ dc_out: port { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ dc_out_dpi0: endpoint@0 { ++ reg = <0>; ++ remote-endpoint = <&hdmi_input0>; ++ }; ++ dc_out_dpi1: endpoint@1 { ++ reg = <1>; ++ remote-endpoint = <&hdmi_in_lcdc>; ++ }; ++ dc_out_dpi2: endpoint@2 { ++ reg = <2>; ++ remote-endpoint = <&mipi_in>; ++ }; ++ }; ++}; ++ ++&display { ++ ports = <&dc_out_dpi0>; ++ status = "okay"; ++}; ++ ++&dsi_output { ++ status = "okay"; ++ ++ ports { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@0 { ++ reg = <0>; ++ mipi_in: endpoint { ++ remote-endpoint = <&dc_out_dpi2>; ++ }; ++ }; ++ ++ port@1 { ++ reg = <1>; ++ mipi_out: endpoint { ++ remote-endpoint = <&dsi_in_port>; ++ }; ++ }; ++ }; ++}; ++ ++&gmac0 { ++ phy-handle = <&phy0>; ++ phy-mode = "rgmii-id"; ++ status = "okay"; ++ ++ mdio { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ compatible = "snps,dwmac-mdio"; ++ ++ phy0: ethernet-phy@0 { ++ reg = <0>; ++ rx-internal-delay-ps = <1900>; ++ tx-internal-delay-ps = <1650>; ++ }; ++ }; ++}; ++ ++&gmac1 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ status = "okay"; ++ ++ phy1: ethernet-phy@1 { ++ reg = <0>; ++ rxc-skew-ps = <1060>; ++ txc-skew-ps = <1800>; ++ }; ++}; ++ ++&gpu { ++ status = "okay"; ++}; ++ ++&hdmi { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&hdmi_pins>; ++ hpd-gpio = <&sysgpio 15 GPIO_ACTIVE_HIGH>; ++ ++ hdmi_in: port { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ hdmi_in_lcdc: endpoint@0 { ++ reg = <0>; ++ remote-endpoint = <&dc_out_dpi1>; ++ }; ++ }; ++}; ++ ++&i2c0 { ++ clock-frequency = <100000>; ++ i2c-sda-hold-time-ns = <300>; ++ i2c-sda-falling-time-ns = <510>; ++ i2c-scl-falling-time-ns = <510>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2c0_pins>; ++ status = "disabled"; ++ ++ wm8960: codec@1a { ++ compatible = "wlf,wm8960"; ++ reg = <0x1a>; ++ wlf,shared-lrclk; ++ #sound-dai-cells = <0>; ++ }; ++ ++ ac108: ac108@3b { ++ compatible = "x-power,ac108_0"; ++ reg = <0x3b>; ++ #sound-dai-cells = <0>; ++ data-protocol = <0>; ++ }; ++}; ++ ++&i2c1 { ++ clock-frequency = <100000>; ++ i2c-sda-hold-time-ns = <300>; ++ i2c-sda-falling-time-ns = <510>; ++ i2c-scl-falling-time-ns = <510>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2c1_pins>; ++ status = "disabled"; ++}; ++ ++&i2c2 { ++ clock-frequency = <100000>; ++ i2c-sda-hold-time-ns = <300>; ++ i2c-sda-falling-time-ns = <510>; ++ i2c-scl-falling-time-ns = <510>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2c2_pins>; ++ status = "okay"; ++ ++ tinker_ft5406: tinker_ft5406@38 { ++ compatible = "tinker_ft5406"; ++ reg = <0x38>; ++ }; ++ ++ seeed_plane_i2c@45 { ++ compatible = "seeed_panel"; ++ reg = <0x45>; ++ ++ port { ++ panel_dsi_port: endpoint { ++ remote-endpoint = <&dsi_out_port>; ++ }; ++ }; ++ }; ++}; ++ ++&i2c3 { ++ clock-frequency = <100000>; ++ i2c-sda-hold-time-ns = <300>; ++ i2c-sda-falling-time-ns = <510>; ++ i2c-scl-falling-time-ns = <510>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2c3_pins>; ++ status = "disabled"; ++}; ++ ++&i2c4 { ++ clock-frequency = <100000>; ++ i2c-sda-hold-time-ns = <300>; ++ i2c-sda-falling-time-ns = <510>; ++ i2c-scl-falling-time-ns = <510>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2c4_pins>; ++ status = "okay"; ++ ++ sc2235: sc2235@30 { ++ compatible = "smartsens,sc2235"; ++ reg = <0x30>; ++ clocks = <&clk_ext_camera>; ++ clock-names = "xclk"; ++ ++ port { ++ /* Parallel bus endpoint */ ++ sc2235_to_parallel: endpoint { ++ remote-endpoint = <¶llel_from_sc2235>; ++ bus-type = <5>; /* Parallel */ ++ bus-width = <8>; ++ data-shift = <2>; /* lines 13:6 are used */ ++ hsync-active = <1>; ++ vsync-active = <1>; ++ pclk-sample = <1>; ++ }; ++ }; ++ }; ++ ++ tda998x@70 { ++ compatible = "nxp,tda998x"; ++ reg = <0x70>; ++ ++ port { ++ tda998x_0_input: endpoint { ++ remote-endpoint = <&hdmi_out>; ++ }; ++ }; ++ }; ++}; ++ ++&i2c5 { ++ clock-frequency = <100000>; ++ i2c-sda-hold-time-ns = <300>; ++ i2c-sda-falling-time-ns = <510>; ++ i2c-scl-falling-time-ns = <510>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2c5_pins>; ++ status = "okay"; ++ ++ pmic: jh7110_evb_reg@50 { ++ compatible = "starfive,jh7110-evb-regulator"; ++ reg = <0x50>; ++ ++ regulators { ++ hdmi_1p8: LDO_REG1 { ++ regulator-name = "hdmi_1p8"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <1800000>; ++ }; ++ mipitx_1p8: LDO_REG2 { ++ regulator-name = "mipitx_1p8"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <1800000>; ++ }; ++ mipirx_1p8: LDO_REG3 { ++ regulator-name = "mipirx_1p8"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <1800000>; ++ }; ++ hdmi_0p9: LDO_REG4 { ++ regulator-name = "hdmi_0p9"; ++ regulator-min-microvolt = <900000>; ++ regulator-max-microvolt = <900000>; ++ }; ++ mipitx_0p9: LDO_REG5 { ++ regulator-name = "mipitx_0p9"; ++ regulator-min-microvolt = <900000>; ++ regulator-max-microvolt = <900000>; ++ }; ++ mipirx_0p9: LDO_REG6 { ++ regulator-name = "mipirx_0p9"; ++ regulator-min-microvolt = <900000>; ++ regulator-max-microvolt = <900000>; ++ }; ++ sdio_vdd: LDO_REG7 { ++ regulator-name = "sdio_vdd"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <3300000>; ++ }; ++ }; ++ }; ++}; ++ ++&i2c6 { ++ clock-frequency = <100000>; ++ i2c-sda-hold-time-ns = <300>; ++ i2c-sda-falling-time-ns = <510>; ++ i2c-scl-falling-time-ns = <510>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2c6_pins>; ++ status = "okay"; ++ ++ ov4689: ov4689@36 { ++ compatible = "ovti,ov4689"; ++ reg = <0x36>; ++ clocks = <&clk_ext_camera>; ++ clock-names = "xclk"; ++ //reset-gpio = <&sysgpio 18 0>; ++ rotation = <180>; ++ ++ port { ++ /* Parallel bus endpoint */ ++ ov4689_to_csi2rx0: endpoint { ++ remote-endpoint = <&csi2rx0_from_ov4689>; ++ bus-type = <4>; /* MIPI CSI-2 D-PHY */ ++ clock-lanes = <0>; ++ data-lanes = <1 2 3 4>; ++ }; ++ }; ++ }; ++ ++ imx219: imx219@10 { ++ compatible = "sony,imx219"; ++ reg = <0x10>; ++ clocks = <&clk_ext_camera>; ++ clock-names = "xclk"; ++ reset-gpio = <&sysgpio 10 0>; ++ //DOVDD-supply = <&v2v8>; ++ rotation = <0>; ++ orientation = <1>; //CAMERA_ORIENTATION_BACK ++ ++ port { ++ /* CSI2 bus endpoint */ ++ imx219_to_csi2rx0: endpoint { ++ remote-endpoint = <&csi2rx0_from_imx219>; ++ bus-type = <4>; /* MIPI CSI-2 D-PHY */ ++ clock-lanes = <0>; ++ data-lanes = <2 1>; ++ lane-polarities = <1 1 1>; ++ link-frequencies = /bits/ 64 <456000000>; ++ }; ++ }; ++ }; ++ ++ imx708: imx708@1a { ++ compatible = "sony,imx708"; ++ reg = <0x1a>; ++ clocks = <&clk_ext_camera>; ++ reset-gpio = <&sysgpio 10 0>; ++ ++ port { ++ imx708_to_csi2rx0: endpoint { ++ remote-endpoint = <&csi2rx0_from_imx708>; ++ data-lanes = <1 2>; ++ clock-noncontinuous; ++ link-frequencies = /bits/ 64 <450000000>; ++ }; ++ }; ++ }; ++}; ++ ++&i2srx { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2s_clk_pins &i2srx_pins>; ++}; ++ ++&i2srx_mst { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2srx_clk_pins>; ++}; ++ ++&i2stx0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&mclk_ext_pins>; ++ status = "okay"; ++}; ++ ++&i2stx1 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2stx_pins>; ++}; ++ ++&jpu { ++ status = "okay"; ++}; ++ ++&mailbox_contrl0 { ++ status = "okay"; ++}; ++ ++&mailbox_client0 { ++ status = "okay"; ++}; ++ ++&mipi_dphy { ++ status = "okay"; ++}; ++ ++&mipi_dsi { ++ status = "okay"; ++ ++ port { ++ dsi_out_port: endpoint@0 { ++ remote-endpoint = <&panel_dsi_port>; ++ }; ++ dsi_in_port: endpoint@1 { ++ remote-endpoint = <&mipi_out>; ++ }; ++ }; ++ ++ mipi_panel: panel@0 { ++ /*compatible = "";*/ ++ status = "okay"; ++ }; ++}; ++ ++&pcie0 { ++ enable-gpios = <&sysgpio 32 GPIO_ACTIVE_HIGH>; ++ perst-gpios = <&sysgpio 26 GPIO_ACTIVE_LOW>; ++ phys = <&pciephy0>; ++ status = "disabled"; ++}; ++ ++&pcie1 { ++ enable-gpios = <&sysgpio 21 GPIO_ACTIVE_HIGH>; ++ perst-gpios = <&sysgpio 28 GPIO_ACTIVE_LOW>; ++ phys = <&pciephy1>; ++ status = "disabled"; ++}; ++ ++&pciephy0 { ++ starfive,sys-syscon = <&sys_syscon 0x18>; ++ starfive,stg-syscon = <&stg_syscon 0x148 0x1f4>; ++}; ++ ++&pdm { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pdm_pins>; ++ status = "disabled"; ++}; ++ ++&pwmdac { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwmdac_pins>; ++}; ++ ++&qspi { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ status = "okay"; ++ ++ nor_flash: flash@0 { ++ compatible = "jedec,spi-nor"; ++ reg=<0>; ++ cdns,read-delay = <5>; ++ spi-max-frequency = <4687500>; ++ cdns,tshsl-ns = <1>; ++ cdns,tsd2d-ns = <1>; ++ cdns,tchsh-ns = <1>; ++ cdns,tslch-ns = <1>; ++ ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ spl@0 { ++ reg = <0x0 0x40000>; ++ }; ++ uboot@100000 { ++ reg = <0x100000 0x300000>; ++ }; ++ data@f00000 { ++ reg = <0xf00000 0x100000>; ++ }; ++ }; ++ }; ++}; ++ ++&rgb_output { ++ status = "okay"; ++ ++ ports { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@0 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0>; ++ ++ hdmi_input0:endpoint@0 { ++ reg = <0>; ++ remote-endpoint = <&dc_out_dpi0>; ++ }; ++ }; ++ ++ port@1 { ++ reg = <1>; ++ ++ hdmi_out:endpoint { ++ remote-endpoint = <&tda998x_0_input>; ++ }; ++ }; ++ }; ++}; ++ ++&spdif { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&spdif_pins>; ++}; ++ ++&spi0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&spi0_pins>; ++ status = "disabled"; ++ ++ spi_dev0: spi_dev@0 { ++ compatible = "rohm,dh2228fv"; ++ reg = <0>; ++ pl022,com-mode = <1>; ++ spi-max-frequency = <10000000>; ++ }; ++}; ++ ++&spi1 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&spi1_pins>; ++ status = "disabled"; ++ ++ spi_dev1: spi_dev@0 { ++ compatible = "rohm,dh2228fv"; ++ reg = <0>; ++ pl022,com-mode = <1>; ++ spi-max-frequency = <10000000>; ++ }; ++}; ++ ++&spi2 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&spi2_pins>; ++ status = "disabled"; ++ ++ spi_dev2: spi_dev@0 { ++ compatible = "rohm,dh2228fv"; ++ reg = <0>; ++ pl022,com-mode = <1>; ++ spi-max-frequency = <10000000>; ++ }; ++}; ++ ++&spi3 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&spi3_pins>; ++ status = "disabled"; ++ ++ spi_dev3: spi_dev@0 { ++ compatible = "rohm,dh2228fv"; ++ reg = <0>; ++ pl022,com-mode = <1>; ++ spi-max-frequency = <10000000>; ++ }; ++}; ++ ++&spi4 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&spi4_pins>; ++ status = "disabled"; ++ ++ spi_dev4: spi_dev@0 { ++ compatible = "rohm,dh2228fv"; ++ reg = <0>; ++ pl022,com-mode = <1>; ++ spi-max-frequency = <10000000>; ++ }; ++}; ++ ++&spi5 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&spi5_pins>; ++ status = "disabled"; ++ ++ spi_dev5: spi_dev@0 { ++ compatible = "rohm,dh2228fv"; ++ reg = <0>; ++ pl022,com-mode = <1>; ++ spi-max-frequency = <10000000>; ++ }; ++}; ++ ++&spi6 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&spi6_pins>; ++ status = "disabled"; ++ ++ spi_dev6: spi_dev@0 { ++ compatible = "rohm,dh2228fv"; ++ reg = <0>; ++ pl022,com-mode = <1>; ++ spi-max-frequency = <10000000>; ++ }; ++}; ++ ++&tda988x_pin { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&rgb_pad_pins>; ++ status = "disabled"; ++}; ++ ++&tdm { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&tdm_pins>; ++ status = "disabled"; ++}; ++ ++&uart0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&uart0_pins>; ++ status = "okay"; ++}; ++ ++&uart1 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&uart1_pins>; ++ status = "disabled"; ++}; ++ ++&uart2 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&uart2_pins>; ++ status = "disabled"; ++}; ++ ++&uart3 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&uart3_pins>; ++ status = "disabled"; ++}; ++ ++&uart4 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&uart4_pins>; ++ status = "disabled"; ++}; ++ ++&uart5 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&uart5_pins>; ++ status = "disabled"; ++}; ++ ++&usb0 { ++ clocks = <&stgcrg JH7110_STGCLK_USB0_LPM>, ++ <&stgcrg JH7110_STGCLK_USB0_STB>, ++ <&stgcrg JH7110_STGCLK_USB0_APB>, ++ <&stgcrg JH7110_STGCLK_USB0_AXI>, ++ <&stgcrg JH7110_STGCLK_USB0_UTMI_APB>, ++ <&stgcrg JH7110_STGCLK_PCIE0_APB>; ++ clock-names = "lpm", "stb", "apb", "axi", "utmi_apb", "phy"; ++ resets = <&stgcrg JH7110_STGRST_USB0_PWRUP>, ++ <&stgcrg JH7110_STGRST_USB0_APB>, ++ <&stgcrg JH7110_STGRST_USB0_AXI>, ++ <&stgcrg JH7110_STGRST_USB0_UTMI_APB>, ++ <&stgcrg JH7110_STGRST_PCIE0_APB>; ++ reset-names = "pwrup", "apb", "axi", "utmi_apb", "phy"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&usb_pins>; ++ dr_mode = "host"; /* host or peripheral */ ++ status = "disabled"; ++}; ++ ++&usb_cdns3 { ++ phys = <&usbphy0>, <&pciephy0>; ++ phy-names = "cdns3,usb2-phy", "cdns3,usb3-phy"; ++}; ++ ++&vin_sysctl { ++ status = "okay"; ++ ++ ports { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@0 { ++ reg = <0>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ /* Parallel bus endpoint */ ++ parallel_from_sc2235: endpoint@0 { ++ reg = <0>; ++ remote-endpoint = <&sc2235_to_parallel>; ++ bus-type = <5>; /* Parallel */ ++ bus-width = <8>; ++ data-shift = <2>; /* lines 9:2 are used */ ++ hsync-active = <1>; ++ vsync-active = <0>; ++ pclk-sample = <1>; ++ status = "okay"; ++ }; ++ }; ++ ++ port@1 { ++ reg = <1>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ /* CSI2 bus endpoint */ ++ csi2rx0_from_ov4689: endpoint@0 { ++ reg = <0>; ++ remote-endpoint = <&ov4689_to_csi2rx0>; ++ bus-type = <4>; /* MIPI CSI-2 D-PHY */ ++ clock-lanes = <0>; ++ data-lanes = <1 2 3 4>; ++ status = "okay"; ++ }; ++ ++ /* CSI2 bus endpoint */ ++ csi2rx0_from_imx219: endpoint@1 { ++ reg = <1>; ++ remote-endpoint = <&imx219_to_csi2rx0>; ++ bus-type = <4>; /* MIPI CSI-2 D-PHY */ ++ clock-lanes = <0>; ++ data-lanes = <2 1>; ++ lane-polarities = <1 1 1>; ++ status = "okay"; ++ }; ++ ++ csi2rx0_from_imx708: endpoint@2 { ++ reg = <2>; ++ remote-endpoint = <&imx708_to_csi2rx0>; ++ bus-type = <4>; /* MIPI CSI-2 D-PHY */ ++ clock-lanes = <0>; ++ data-lanes = <2 1>; ++ lane-polarities = <1 1 1>; ++ status = "okay"; ++ }; ++ }; ++ }; ++}; ++ ++&vpu_dec { ++ status = "okay"; ++}; ++ ++&vpu_enc { ++ status = "okay"; ++}; ++ ++&xrp { ++ memory-region = <&xrp_reserved>; ++ status = "okay"; ++}; +--- a/arch/riscv/boot/dts/starfive/jh7110.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7110.dtsi +@@ -196,11 +196,60 @@ + opp-750000000 { + opp-hz = /bits/ 64 <750000000>; + opp-microvolt = <800000>; ++ opp-suspend; + }; + opp-1500000000 { + opp-hz = /bits/ 64 <1500000000>; + opp-microvolt = <1040000>; + }; ++ /* CPU opp table for 1.25GHz */ ++ opp-312500000 { ++ opp-hz = /bits/ 64 <312500000>; ++ opp-microvolt = <800000>; ++ }; ++ opp-417000000 { ++ opp-hz = /bits/ 64 <417000000>; ++ opp-microvolt = <800000>; ++ }; ++ opp-625000000 { ++ opp-hz = /bits/ 64 <625000000>; ++ opp-microvolt = <800000>; ++ opp-suspend; ++ }; ++ opp-1250000000 { ++ opp-hz = /bits/ 64 <1250000000>; ++ opp-microvolt = <1000000>; ++ }; ++ }; ++ ++ display: display-subsystem { ++ compatible = "starfive,jh7110-display","verisilicon,display-subsystem"; ++ status = "disabled"; ++ }; ++ ++ dsi_output: dsi-output { ++ compatible = "starfive,jh7110-display-encoder","verisilicon,dsi-encoder"; ++ status = "disabled"; ++ }; ++ ++ mailbox_client0: mailbox_client { ++ compatible = "starfive,mailbox-test"; ++ mbox-names = "rx", "tx"; ++ mboxes = <&mailbox_contrl0 0 1>,<&mailbox_contrl0 1 0>; ++ status = "disabled"; ++ }; ++ ++ rgb_output: rgb-output { ++ compatible = "starfive,jh7110-rgb_output","verisilicon,rgb-encoder"; ++ //verisilicon,dss-syscon = <&dssctrl>; ++ //verisilicon,mux-mask = <0x70 0x380>; ++ //verisilicon,mux-val = <0x40 0x280>; ++ status = "disabled"; ++ }; ++ ++ tda988x_pin: tda988x_pin { ++ compatible = "starfive,tda998x_rgb_pin"; ++ status = "disabled"; + }; + + thermal-zones { +@@ -349,7 +398,9 @@ + + ccache: cache-controller@2010000 { + compatible = "starfive,jh7110-ccache", "sifive,ccache0", "cache"; +- reg = <0x0 0x2010000 0x0 0x4000>; ++ reg = <0x0 0x2010000 0x0 0x4000>, ++ <0x0 0x8000000 0x0 0x2000000>, ++ <0x0 0xa000000 0x0 0x2000000>; + interrupts = <1>, <3>, <4>, <2>; + cache-block-size = <64>; + cache-level = <2>; +@@ -378,7 +429,8 @@ + clocks = <&syscrg JH7110_SYSCLK_UART0_CORE>, + <&syscrg JH7110_SYSCLK_UART0_APB>; + clock-names = "baudclk", "apb_pclk"; +- resets = <&syscrg JH7110_SYSRST_UART0_APB>; ++ resets = <&syscrg JH7110_SYSRST_UART0_APB>, ++ <&syscrg JH7110_SYSRST_UART0_CORE>; + interrupts = <32>; + reg-io-width = <4>; + reg-shift = <2>; +@@ -391,7 +443,8 @@ + clocks = <&syscrg JH7110_SYSCLK_UART1_CORE>, + <&syscrg JH7110_SYSCLK_UART1_APB>; + clock-names = "baudclk", "apb_pclk"; +- resets = <&syscrg JH7110_SYSRST_UART1_APB>; ++ resets = <&syscrg JH7110_SYSRST_UART1_APB>, ++ <&syscrg JH7110_SYSRST_UART1_CORE>; + interrupts = <33>; + reg-io-width = <4>; + reg-shift = <2>; +@@ -404,7 +457,8 @@ + clocks = <&syscrg JH7110_SYSCLK_UART2_CORE>, + <&syscrg JH7110_SYSCLK_UART2_APB>; + clock-names = "baudclk", "apb_pclk"; +- resets = <&syscrg JH7110_SYSRST_UART2_APB>; ++ resets = <&syscrg JH7110_SYSRST_UART2_APB>, ++ <&syscrg JH7110_SYSRST_UART2_CORE>; + interrupts = <34>; + reg-io-width = <4>; + reg-shift = <2>; +@@ -513,6 +567,25 @@ + status = "disabled"; + }; + ++ spdif: spdif@100a0000 { ++ compatible = "starfive,jh7110-spdif"; ++ reg = <0x0 0x100a0000 0x0 0x1000>; ++ clocks = <&syscrg JH7110_SYSCLK_SPDIF_APB>, ++ <&syscrg JH7110_SYSCLK_SPDIF_CORE>, ++ <&syscrg JH7110_SYSCLK_AUDIO_ROOT>, ++ <&syscrg JH7110_SYSCLK_MCLK_INNER>, ++ <&mclk_ext>, <&syscrg JH7110_SYSCLK_MCLK>; ++ clock-names = "apb", "core", ++ "audroot", "mclk_inner", ++ "mclk_ext", "mclk"; ++ resets = <&syscrg JH7110_SYSRST_SPDIF_APB>; ++ reset-names = "apb"; ++ interrupts = <84>; ++ interrupt-names = "tx"; ++ #sound-dai-cells = <0>; ++ status = "disabled"; ++ }; ++ + pwmdac: pwmdac@100b0000 { + compatible = "starfive,jh7110-pwmdac"; + reg = <0x0 0x100b0000 0x0 0x1000>; +@@ -526,6 +599,42 @@ + status = "disabled"; + }; + ++ pdm: pdm@100d0000 { ++ compatible = "starfive,jh7110-pdm"; ++ reg = <0x0 0x100d0000 0x0 0x1000>; ++ reg-names = "pdm"; ++ clocks = <&syscrg JH7110_SYSCLK_PDM_DMIC>, ++ <&syscrg JH7110_SYSCLK_PDM_APB>, ++ <&syscrg JH7110_SYSCLK_MCLK>, ++ <&mclk_ext>; ++ clock-names = "pdm_mclk", "pdm_apb", ++ "clk_mclk", "mclk_ext"; ++ resets = <&syscrg JH7110_SYSRST_PDM_DMIC>, ++ <&syscrg JH7110_SYSRST_PDM_APB>; ++ reset-names = "pdm_dmic", "pdm_apb"; ++ #sound-dai-cells = <0>; ++ status = "disabled"; ++ }; ++ ++ i2srx_mst: i2srx_mst@100e0000 { ++ compatible = "starfive,jh7110-i2srx-master"; ++ reg = <0x0 0x100e0000 0x0 0x1000>; ++ clocks = <&syscrg JH7110_SYSCLK_I2SRX_BCLK_MST>, ++ <&syscrg JH7110_SYSCLK_I2SRX_APB>, ++ <&syscrg JH7110_SYSCLK_MCLK>, ++ <&syscrg JH7110_SYSCLK_MCLK_INNER>, ++ <&mclk_ext>; ++ clock-names = "i2sclk", "apb", "mclk", ++ "mclk_inner","mclk_ext"; ++ resets = <&syscrg JH7110_SYSRST_I2SRX_APB>, ++ <&syscrg JH7110_SYSRST_I2SRX_BCLK>; ++ dmas = <&dma 24>; ++ dma-names = "rx"; ++ starfive,syscon = <&sys_syscon 0x18 0x2 0x34 0x3FC00 0x24400>; ++ #sound-dai-cells = <0>; ++ status = "disabled"; ++ }; ++ + i2srx: i2s@100e0000 { + compatible = "starfive,jh7110-i2srx"; + reg = <0x0 0x100e0000 0x0 0x1000>; +@@ -622,6 +731,26 @@ + #reset-cells = <1>; + }; + ++ xrp: xrp@10230000 { ++ compatible = "cdns,xrp"; ++ dma-coherent; ++ reg = <0x0 0x10230000 0x0 0x00010000 ++ 0x0 0x10240000 0x0 0x00010000>; ++ clocks = <&stgcrg JH7110_STGCLK_HIFI4_CLK_CORE>; ++ clock-names = "core_clk"; ++ resets = <&stgcrg JH7110_STGRST_HIFI4_CORE>, ++ <&stgcrg JH7110_STGRST_HIFI4_AXI>; ++ reset-names = "rst_core","rst_axi"; ++ starfive,stg-syscon = <&stg_syscon>; ++ firmware-name = "hifi4_elf"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x40000000 0x0 0x20000000 0x040000 ++ 0x69c00000 0x0 0x69c00000 0x03000000>; ++ status = "disabled"; ++ dsp@0 {}; ++ }; ++ + stg_syscon: syscon@10240000 { + compatible = "starfive,jh7110-stg-syscon", "syscon"; + reg = <0x0 0x10240000 0x0 0x1000>; +@@ -633,7 +762,8 @@ + clocks = <&syscrg JH7110_SYSCLK_UART3_CORE>, + <&syscrg JH7110_SYSCLK_UART3_APB>; + clock-names = "baudclk", "apb_pclk"; +- resets = <&syscrg JH7110_SYSRST_UART3_APB>; ++ resets = <&syscrg JH7110_SYSRST_UART3_APB>, ++ <&syscrg JH7110_SYSRST_UART3_CORE>; + interrupts = <45>; + reg-io-width = <4>; + reg-shift = <2>; +@@ -646,7 +776,8 @@ + clocks = <&syscrg JH7110_SYSCLK_UART4_CORE>, + <&syscrg JH7110_SYSCLK_UART4_APB>; + clock-names = "baudclk", "apb_pclk"; +- resets = <&syscrg JH7110_SYSRST_UART4_APB>; ++ resets = <&syscrg JH7110_SYSRST_UART4_APB>, ++ <&syscrg JH7110_SYSRST_UART4_CORE>; + interrupts = <46>; + reg-io-width = <4>; + reg-shift = <2>; +@@ -659,7 +790,8 @@ + clocks = <&syscrg JH7110_SYSCLK_UART5_CORE>, + <&syscrg JH7110_SYSCLK_UART5_APB>; + clock-names = "baudclk", "apb_pclk"; +- resets = <&syscrg JH7110_SYSRST_UART5_APB>; ++ resets = <&syscrg JH7110_SYSRST_UART5_APB>, ++ <&syscrg JH7110_SYSRST_UART5_CORE>; + interrupts = <47>; + reg-io-width = <4>; + reg-shift = <2>; +@@ -919,6 +1051,18 @@ + "ch2", "ch3"; + }; + ++ mailbox_contrl0: mailbox@13060000 { ++ compatible = "starfive,mail_box"; ++ reg = <0x0 0x13060000 0x0 0x0001000>; ++ clocks = <&syscrg JH7110_SYSCLK_MAILBOX_APB>; ++ clock-names = "clk_apb"; ++ resets = <&syscrg JH7110_SYSRST_MAILBOX_APB>; ++ reset-names = "mbx_rre"; ++ interrupts = <26 27>; ++ #mbox-cells = <2>; ++ status = "disabled"; ++ }; ++ + watchdog@13070000 { + compatible = "starfive,jh7110-wdt"; + reg = <0x0 0x13070000 0x0 0x10000>; +@@ -929,6 +1073,112 @@ + <&syscrg JH7110_SYSRST_WDT_CORE>; + }; + ++ jpu: jpu@13090000 { ++ compatible = "starfive,jpu"; ++ dma-coherent; ++ reg = <0x0 0x13090000 0x0 0x300>; ++ interrupts = <14>; ++ clocks = <&syscrg JH7110_SYSCLK_CODAJ12_AXI>, ++ <&syscrg JH7110_SYSCLK_CODAJ12_CORE>, ++ <&syscrg JH7110_SYSCLK_CODAJ12_APB>, ++ <&syscrg JH7110_SYSCLK_NOC_BUS_VDEC_AXI>, ++ <&syscrg JH7110_SYSCLK_VDEC_MAIN>, ++ <&syscrg JH7110_SYSCLK_VDEC_JPG>; ++ clock-names = "axi_clk", "core_clk", "apb_clk", ++ "noc_bus", "main_clk", "dec_clk"; ++ resets = <&syscrg JH7110_SYSRST_CODAJ12_AXI>, ++ <&syscrg JH7110_SYSRST_CODAJ12_CORE>, ++ <&syscrg JH7110_SYSRST_CODAJ12_APB>; ++ reset-names = "rst_axi", "rst_core", "rst_apb"; ++ power-domains = <&pwrc JH7110_PD_VDEC>; ++ status = "disabled"; ++ }; ++ ++ vpu_dec: vpu_dec@130a0000 { ++ compatible = "starfive,vdec"; ++ dma-coherent; ++ reg = <0x0 0x130a0000 0x0 0x10000>; ++ interrupts = <13>; ++ clocks = <&syscrg JH7110_SYSCLK_WAVE511_AXI>, ++ <&syscrg JH7110_SYSCLK_WAVE511_BPU>, ++ <&syscrg JH7110_SYSCLK_WAVE511_VCE>, ++ <&syscrg JH7110_SYSCLK_WAVE511_APB>, ++ <&syscrg JH7110_SYSCLK_NOC_BUS_VDEC_AXI>, ++ <&syscrg JH7110_SYSCLK_VDEC_MAIN>; ++ clock-names = "axi_clk", "bpu_clk", "vce_clk", ++ "apb_clk", "noc_bus", "main_clk"; ++ resets = <&syscrg JH7110_SYSRST_WAVE511_AXI>, ++ <&syscrg JH7110_SYSRST_WAVE511_BPU>, ++ <&syscrg JH7110_SYSRST_WAVE511_VCE>, ++ <&syscrg JH7110_SYSRST_WAVE511_APB>, ++ <&syscrg JH7110_SYSRST_AXIMEM0_AXI>; ++ reset-names = "rst_axi", "rst_bpu", "rst_vce", ++ "rst_apb", "rst_sram"; ++ starfive,vdec_noc_ctrl; ++ power-domains = <&pwrc JH7110_PD_VDEC>; ++ status = "disabled"; ++ }; ++ ++ vpu_enc: vpu_enc@130b0000 { ++ compatible = "starfive,venc"; ++ dma-coherent; ++ reg = <0x0 0x130b0000 0x0 0x10000>; ++ interrupts = <15>; ++ clocks = <&syscrg JH7110_SYSCLK_WAVE420L_AXI>, ++ <&syscrg JH7110_SYSCLK_WAVE420L_BPU>, ++ <&syscrg JH7110_SYSCLK_WAVE420L_VCE>, ++ <&syscrg JH7110_SYSCLK_WAVE420L_APB>, ++ <&syscrg JH7110_SYSCLK_NOC_BUS_VENC_AXI>; ++ clock-names = "axi_clk", "bpu_clk", "vce_clk", ++ "apb_clk", "noc_bus"; ++ resets = <&syscrg JH7110_SYSRST_WAVE420L_AXI>, ++ <&syscrg JH7110_SYSRST_WAVE420L_BPU>, ++ <&syscrg JH7110_SYSRST_WAVE420L_VCE>, ++ <&syscrg JH7110_SYSRST_WAVE420L_APB>, ++ <&syscrg JH7110_SYSRST_AXIMEM1_AXI>; ++ reset-names = "rst_axi", "rst_bpu", "rst_vce", ++ "rst_apb", "rst_sram"; ++ starfive,venc_noc_ctrl; ++ power-domains = <&pwrc JH7110_PD_VENC>; ++ status = "disabled"; ++ }; ++ ++ can0: can@130d0000 { ++ compatible = "starfive,jh7110-can", "ipms,can"; ++ reg = <0x0 0x130d0000 0x0 0x1000>; ++ interrupts = <112>; ++ clocks = <&syscrg JH7110_SYSCLK_CAN0_APB>, ++ <&syscrg JH7110_SYSCLK_CAN0_CAN>, ++ <&syscrg JH7110_SYSCLK_CAN0_TIMER>; ++ clock-names = "apb_clk", "core_clk", "timer_clk"; ++ resets = <&syscrg JH7110_SYSRST_CAN0_APB>, ++ <&syscrg JH7110_SYSRST_CAN0_CORE>, ++ <&syscrg JH7110_SYSRST_CAN0_TIMER>; ++ reset-names = "rst_apb", "rst_core", "rst_timer"; ++ frequency = <40000000>; ++ starfive,sys-syscon = <&sys_syscon 0x10 0x3 0x8>; ++ syscon,can_or_canfd = <0>; ++ status = "disabled"; ++ }; ++ ++ can1: can@130e0000 { ++ compatible = "starfive,jh7110-can", "ipms,can"; ++ reg = <0x0 0x130e0000 0x0 0x1000>; ++ interrupts = <113>; ++ clocks = <&syscrg JH7110_SYSCLK_CAN1_APB>, ++ <&syscrg JH7110_SYSCLK_CAN1_CAN>, ++ <&syscrg JH7110_SYSCLK_CAN1_TIMER>; ++ clock-names = "apb_clk", "core_clk", "timer_clk"; ++ resets = <&syscrg JH7110_SYSRST_CAN1_APB>, ++ <&syscrg JH7110_SYSRST_CAN1_CORE>, ++ <&syscrg JH7110_SYSRST_CAN1_TIMER>; ++ reset-names = "rst_apb", "rst_core", "rst_timer"; ++ frequency = <40000000>; ++ starfive,sys-syscon = <&sys_syscon 0x88 0x12 0x40000>; ++ syscon,can_or_canfd = <0>; ++ status = "disabled"; ++ }; ++ + crypto: crypto@16000000 { + compatible = "starfive,jh7110-crypto"; + reg = <0x0 0x16000000 0x0 0x4000>; +@@ -1119,6 +1369,42 @@ + #power-domain-cells = <1>; + }; + ++ rtc: rtc@17040000 { ++ compatible = "starfive,jh7110-rtc"; ++ reg = <0x0 0x17040000 0x0 0x10000>; ++ interrupts = <10>, <11>, <12>; ++ interrupt-names = "rtc_ms_pulse", "rtc_sec_pulse", "rtc"; ++ clocks = <&aoncrg JH7110_AONCLK_RTC_APB>, ++ <&aoncrg JH7110_AONCLK_RTC_CAL>; ++ clock-names = "pclk", "cal_clk"; ++ resets = <&aoncrg JH7110_AONRST_RTC_32K>, ++ <&aoncrg JH7110_AONRST_RTC_APB>, ++ <&aoncrg JH7110_AONRST_RTC_CAL>; ++ reset-names = "rst_osc", "rst_apb", "rst_cal"; ++ rtc,cal-clock-freq = <1000000>; ++ }; ++ ++ gpu: gpu@18000000 { ++ compatible = "img-gpu"; ++ reg = <0x0 0x18000000 0x0 0x100000>, ++ <0x0 0x130C000 0x0 0x10000>; ++ clocks = <&syscrg JH7110_SYSCLK_GPU_CORE>, ++ <&syscrg JH7110_SYSCLK_GPU_APB>, ++ <&syscrg JH7110_SYSCLK_GPU_RTC_TOGGLE>, ++ <&syscrg JH7110_SYSCLK_GPU_CORE_CLK>, ++ <&syscrg JH7110_SYSCLK_GPU_SYS_CLK>, ++ <&syscrg JH7110_SYSCLK_NOC_BUS_GPU_AXI>; ++ clock-names = "clk_bv", "clk_apb", "clk_rtc", ++ "clk_core", "clk_sys", "clk_axi"; ++ resets = <&syscrg JH7110_SYSRST_GPU_APB>, ++ <&syscrg JH7110_SYSRST_GPU_DOMA>; ++ reset-names = "rst_apb", "rst_doma"; ++ power-domains = <&pwrc JH7110_PD_GPUA>; ++ interrupts = <82>; ++ current-clock = <8000000>; ++ status = "disabled"; ++ }; ++ + csi2rx: csi-bridge@19800000 { + compatible = "starfive,jh7110-csi2rx"; + reg = <0x0 0x19800000 0x0 0x10000>; +@@ -1145,6 +1431,67 @@ + status = "disabled"; + }; + ++ vin_sysctl: vin_sysctl@19800000 { ++ compatible = "starfive,jh7110-vin"; ++ reg = <0x0 0x19800000 0x0 0x10000>, ++ <0x0 0x19810000 0x0 0x10000>, ++ <0x0 0x19820000 0x0 0x10000>, ++ <0x0 0x19840000 0x0 0x10000>, ++ <0x0 0x19870000 0x0 0x30000>, ++ <0x0 0x11840000 0x0 0x10000>, ++ <0x0 0x17030000 0x0 0x10000>, ++ <0x0 0x13020000 0x0 0x10000>; ++ reg-names = "csi2rx", "vclk", "vrst", "sctrl", ++ "isp", "trst", "pmu", "syscrg"; ++ clocks = <&ispcrg JH7110_ISPCLK_DOM4_APB_FUNC>, ++ <&ispcrg JH7110_ISPCLK_VIN_APB>, ++ <&ispcrg JH7110_ISPCLK_VIN_SYS>, ++ <&ispcrg JH7110_ISPCLK_ISPV2_TOP_WRAPPER_C>, ++ <&ispcrg JH7110_ISPCLK_DVP_INV>, ++ <&ispcrg JH7110_ISPCLK_VIN_P_AXI_WR>, ++ <&ispcrg JH7110_ISPCLK_MIPI_RX0_PXL>, ++ <&ispcrg JH7110_ISPCLK_VIN_PIXEL_IF0>, ++ <&ispcrg JH7110_ISPCLK_VIN_PIXEL_IF1>, ++ <&ispcrg JH7110_ISPCLK_VIN_PIXEL_IF2>, ++ <&ispcrg JH7110_ISPCLK_VIN_PIXEL_IF3>, ++ <&ispcrg JH7110_ISPCLK_M31DPHY_CFG_IN>, ++ <&ispcrg JH7110_ISPCLK_M31DPHY_REF_IN>, ++ <&ispcrg JH7110_ISPCLK_M31DPHY_TX_ESC_LAN0>, ++ <&syscrg JH7110_SYSCLK_ISP_TOP_CORE>, ++ <&syscrg JH7110_SYSCLK_ISP_TOP_AXI>; ++ clock-names = "clk_apb_func", "clk_pclk", "clk_sys_clk", ++ "clk_wrapper_clk_c", "clk_dvp_inv", "clk_axiwr", ++ "clk_mipi_rx0_pxl", "clk_pixel_clk_if0", ++ "clk_pixel_clk_if1", "clk_pixel_clk_if2", ++ "clk_pixel_clk_if3", "clk_m31dphy_cfgclk_in", ++ "clk_m31dphy_refclk_in", "clk_m31dphy_txclkesc_lan0", ++ "clk_ispcore_2x", "clk_isp_axi"; ++ resets = <&ispcrg JH7110_ISPRST_ISPV2_TOP_WRAPPER_P>, ++ <&ispcrg JH7110_ISPRST_ISPV2_TOP_WRAPPER_C>, ++ <&ispcrg JH7110_ISPRST_VIN_APB>, ++ <&ispcrg JH7110_ISPRST_VIN_SYS>, ++ <&ispcrg JH7110_ISPRST_VIN_P_AXI_RD>, ++ <&ispcrg JH7110_ISPRST_VIN_P_AXI_WR>, ++ <&ispcrg JH7110_ISPRST_VIN_PIXEL_IF0>, ++ <&ispcrg JH7110_ISPRST_VIN_PIXEL_IF1>, ++ <&ispcrg JH7110_ISPRST_VIN_PIXEL_IF2>, ++ <&ispcrg JH7110_ISPRST_VIN_PIXEL_IF3>, ++ <&ispcrg JH7110_ISPRST_M31DPHY_HW>, ++ <&ispcrg JH7110_ISPRST_M31DPHY_B09_AON>, ++ <&syscrg JH7110_SYSRST_ISP_TOP>, ++ <&syscrg JH7110_SYSRST_ISP_TOP_AXI>; ++ reset-names = "rst_wrapper_p", "rst_wrapper_c", "rst_pclk", ++ "rst_sys_clk", "rst_axird", "rst_axiwr", "rst_pixel_clk_if0", ++ "rst_pixel_clk_if1", "rst_pixel_clk_if2", "rst_pixel_clk_if3", ++ "rst_m31dphy_hw", "rst_m31dphy_b09_always_on", ++ "rst_isp_top_n", "rst_isp_top_axi"; ++ starfive,aon-syscon = <&aon_syscon 0x00>; ++ power-domains = <&pwrc JH7110_PD_ISP>; ++ /* irq nr: vin, isp, isp_csi, isp_scd, isp_csiline */ ++ interrupts = <92 87 88 89 90>; ++ status = "disabled"; ++ }; ++ + ispcrg: clock-controller@19810000 { + compatible = "starfive,jh7110-ispcrg"; + reg = <0x0 0x19810000 0x0 0x10000>; +@@ -1175,6 +1522,66 @@ + #phy-cells = <0>; + }; + ++ dc8200: dc8200@29400000 { ++ compatible = "starfive,jh7110-dc8200","verisilicon,dc8200"; ++ verisilicon,dss-syscon = <&dssctrl>;//20220624 panel syscon ++ reg = <0x0 0x29400000 0x0 0x100>, ++ <0x0 0x29400800 0x0 0x2000>, ++ <0x0 0x17030000 0x0 0x1000>; ++ interrupts = <95>; ++ clocks = <&syscrg JH7110_SYSCLK_NOC_BUS_DISP_AXI>, ++ <&syscrg JH7110_SYSCLK_VOUT_SRC>, ++ <&syscrg JH7110_SYSCLK_VOUT_TOP_AXI>, ++ <&syscrg JH7110_SYSCLK_VOUT_TOP_AHB>, ++ <&voutcrg JH7110_VOUTCLK_DC8200_PIX0>, ++ <&voutcrg JH7110_VOUTCLK_DC8200_PIX1>, ++ <&voutcrg JH7110_VOUTCLK_DC8200_AXI>, ++ <&voutcrg JH7110_VOUTCLK_DC8200_CORE>, ++ <&voutcrg JH7110_VOUTCLK_DC8200_AHB>, ++ <&syscrg JH7110_SYSCLK_VOUT_TOP_AXI>, ++ <&voutcrg JH7110_VOUTCLK_DOM_VOUT_TOP_LCD>, ++ <&hdmitx0_pixelclk>, ++ <&voutcrg JH7110_VOUTCLK_DC8200_PIX>; ++ clock-names = "noc_disp","vout_src", ++ "top_vout_axi","top_vout_ahb", ++ "pix_clk","vout_pix1", ++ "axi_clk","core_clk","vout_ahb", ++ "vout_top_axi","vout_top_lcd","hdmitx0_pixelclk","dc8200_pix0"; ++ resets = <&syscrg JH7110_SYSRST_VOUT_TOP_SRC>, ++ <&voutcrg JH7110_VOUTRST_DC8200_AXI>, ++ <&voutcrg JH7110_VOUTRST_DC8200_AHB>, ++ <&voutcrg JH7110_VOUTRST_DC8200_CORE>, ++ <&syscrg JH7110_SYSRST_NOC_BUS_DISP_AXI>; ++ reset-names = "rst_vout_src","rst_axi","rst_ahb","rst_core", ++ "rst_noc_disp"; ++ status = "disabled"; ++ }; ++ ++ hdmi: hdmi@29590000 { ++ compatible = "starfive,jh7110-hdmi","inno,hdmi"; ++ reg = <0x0 0x29590000 0x0 0x4000>; ++ interrupts = <99>; ++ /*interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;*/ ++ /*clocks = <&cru PCLK_HDMI>;*/ ++ /*clock-names = "pclk";*/ ++ /*pinctrl-names = "default";*/ ++ /*pinctrl-0 = <&hdmi_ctl>;*/ ++ clocks = <&voutcrg JH7110_VOUTCLK_HDMI_TX_SYS>, ++ <&voutcrg JH7110_VOUTCLK_HDMI_TX_MCLK>, ++ <&voutcrg JH7110_VOUTCLK_HDMI_TX_BCLK>, ++ <&hdmitx0_pixelclk>; ++ clock-names = "sysclk", "mclk","bclk","pclk"; ++ resets = <&voutcrg JH7110_VOUTRST_HDMI_TX_HDMI>; ++ reset-names = "hdmi_tx"; ++ #sound-dai-cells = <0>; ++ status = "disabled"; ++ }; ++ ++ dssctrl: dssctrl@295B0000 { ++ compatible = "starfive,jh7110-dssctrl","verisilicon,dss-ctrl", "syscon"; ++ reg = <0 0x295B0000 0 0x90>; ++ }; ++ + voutcrg: clock-controller@295c0000 { + compatible = "starfive,jh7110-voutcrg"; + reg = <0x0 0x295c0000 0x0 0x10000>; +@@ -1193,6 +1600,67 @@ + power-domains = <&pwrc JH7110_PD_VOUT>; + }; + ++ mipi_dsi: mipi@295d0000 { ++ compatible = "starfive,jh7110-mipi_dsi","cdns,dsi"; ++ reg = <0x0 0x295d0000 0x0 0x10000>; ++ interrupts = <98>; ++ reg-names = "dsi"; ++ clocks = <&voutcrg JH7110_VOUTCLK_DSITX_SYS>, ++ <&voutcrg JH7110_VOUTCLK_DSITX_APB>, ++ <&voutcrg JH7110_VOUTCLK_DSITX_TXESC>, ++ <&voutcrg JH7110_VOUTCLK_DSITX_DPI>; ++ clock-names = "dpi", "apb", "txesc", "sys"; ++ resets = <&voutcrg JH7110_VOUTRST_DSITX_DPI>, ++ <&voutcrg JH7110_VOUTRST_DSITX_APB>, ++ <&voutcrg JH7110_VOUTRST_DSITX_RXESC>, ++ <&voutcrg JH7110_VOUTRST_DSITX_SYS>, ++ <&voutcrg JH7110_VOUTRST_DSITX_TXBYTEHS>, ++ <&voutcrg JH7110_VOUTRST_DSITX_TXESC>; ++ reset-names = "dsi_dpi", "dsi_apb", "dsi_rxesc", ++ "dsi_sys", "dsi_txbytehs", "dsi_txesc"; ++ phys = <&mipi_dphy>; ++ phy-names = "dphy"; ++ status = "disabled"; ++ }; ++ ++ mipi_dphy: mipi-dphy@295e0000{ ++ compatible = "starfive,jh7110-mipi-dphy-tx","m31,mipi-dphy-tx"; ++ reg = <0x0 0x295e0000 0x0 0x10000>; ++ clocks = <&voutcrg JH7110_VOUTCLK_MIPITX_DPHY_TXESC>; ++ clock-names = "dphy_txesc"; ++ resets = <&voutcrg JH7110_VOUTRST_MIPITX_DPHY_SYS>, ++ <&voutcrg JH7110_VOUTRST_MIPITX_DPHY_TXBYTEHS>; ++ reset-names = "dphy_sys", "dphy_txbytehs"; ++ #phy-cells = <0>; ++ status = "disabled"; ++ }; ++ ++ co_process: e24@6e210000 { ++ compatible = "starfive,e24"; ++ dma-coherent; ++ reg = <0x0 0x6e210000 0x0 0x00001000>, ++ <0x0 0x6e211000 0x0 0x0003f000>; ++ reg-names = "ecmd", "espace"; ++ clocks = <&stgcrg JH7110_STGCLK_E2_RTC>, ++ <&stgcrg JH7110_STGCLK_E2_CORE>, ++ <&stgcrg JH7110_STGCLK_E2_DBG>; ++ clock-names = "clk_rtc", "clk_core", "clk_dbg"; ++ resets = <&stgcrg JH7110_STGRST_E24_CORE>; ++ reset-names = "e24_core"; ++ starfive,stg-syscon = <&stg_syscon>; ++ interrupt-parent = <&plic>; ++ firmware-name = "e24_elf"; ++ irq-mode = <1>; ++ mbox-names = "tx", "rx"; ++ mboxes = <&mailbox_contrl0 0 2>, ++ <&mailbox_contrl0 2 0>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x6ce00000 0x0 0x6ce00000 0x1600000>; ++ status = "disabled"; ++ dsp@0 {}; ++ }; ++ + pcie0: pcie@940000000 { + compatible = "starfive,jh7110-pcie"; + reg = <0x9 0x40000000 0x0 0x1000000>, diff --git a/target/linux/starfive/patches-6.6/0057-riscv-dts-starfive-Add-JH7110-EVB-expanded-device-tr.patch b/target/linux/starfive/patches-6.6/0057-riscv-dts-starfive-Add-JH7110-EVB-expanded-device-tr.patch new file mode 100644 index 0000000000..7a23bf931b --- /dev/null +++ b/target/linux/starfive/patches-6.6/0057-riscv-dts-starfive-Add-JH7110-EVB-expanded-device-tr.patch @@ -0,0 +1,728 @@ +From cae7550054ca0cd940bbc1501ae5611f5d2957e6 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Wed, 20 Sep 2023 14:53:22 +0800 +Subject: [PATCH 057/116] riscv: dts: starfive: Add JH7110 EVB expanded device + tree + +Add JH7110 EVB expanded device tree. +The code is ported from tag JH7110_SDK_6.1_v5.11.3 + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + arch/riscv/boot/dts/starfive/Makefile | 11 +- + .../starfive/jh7110-evb-can-pdm-pwmdac.dts | 102 ++++++++++++++++ + .../dts/starfive/jh7110-evb-dvp-rgb2hdmi.dts | 37 ++++++ + .../dts/starfive/jh7110-evb-i2s-ac108.dts | 72 ++++++++++++ + .../dts/starfive/jh7110-evb-pcie-i2s-sd.dts | 111 ++++++++++++++++++ + .../dts/starfive/jh7110-evb-spi-uart2.dts | 65 ++++++++++ + .../starfive/jh7110-evb-uart1-rgb2hdmi.dts | 57 +++++++++ + .../starfive/jh7110-evb-uart4-emmc-spdif.dts | 78 ++++++++++++ + .../starfive/jh7110-evb-uart5-pwm-i2c-tdm.dts | 95 +++++++++++++++ + .../dts/starfive/jh7110-evb-usbdevice.dts | 35 ++++++ + 10 files changed, 662 insertions(+), 1 deletion(-) + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-evb-can-pdm-pwmdac.dts + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-evb-dvp-rgb2hdmi.dts + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-evb-i2s-ac108.dts + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-evb-pcie-i2s-sd.dts + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-evb-spi-uart2.dts + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-evb-uart1-rgb2hdmi.dts + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-evb-uart4-emmc-spdif.dts + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-evb-uart5-pwm-i2c-tdm.dts + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-evb-usbdevice.dts + +--- a/arch/riscv/boot/dts/starfive/Makefile ++++ b/arch/riscv/boot/dts/starfive/Makefile +@@ -12,4 +12,13 @@ dtb-$(CONFIG_ARCH_STARFIVE) += jh7100-st + dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-starfive-visionfive-2-v1.2a.dtb + dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-starfive-visionfive-2-v1.3b.dtb + +-dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-evb.dtb ++dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-evb.dtb \ ++ jh7110-evb-pcie-i2s-sd.dtb \ ++ jh7110-evb-spi-uart2.dtb \ ++ jh7110-evb-uart4-emmc-spdif.dtb \ ++ jh7110-evb-uart5-pwm-i2c-tdm.dtb \ ++ jh7110-evb-dvp-rgb2hdmi.dtb \ ++ jh7110-evb-can-pdm-pwmdac.dtb \ ++ jh7110-evb-i2s-ac108.dtb \ ++ jh7110-evb-usbdevice.dtb \ ++ jh7110-evb-uart1-rgb2hdmi.dtb +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb-can-pdm-pwmdac.dts +@@ -0,0 +1,102 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ */ ++ ++/dts-v1/; ++#include "jh7110-evb.dtsi" ++ ++/ { ++ model = "StarFive JH7110 EVB"; ++ compatible = "starfive,jh7110-evb", "starfive,jh7110"; ++ ++ sound2: snd-card2 { ++ compatible = "simple-audio-card"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ simple-audio-card,name = "StarFive-PDM-Sound-Card"; ++ simple-audio-card,dai-link@0 { ++ reg = <0>; ++ format = "i2s"; ++ bitclock-master = <&dailink_master>; ++ frame-master = <&dailink_master>; ++ ++ dailink_master:cpu { ++ sound-dai = <&i2srx_mst>; ++ }; ++ ++ dailink_slave:codec { ++ sound-dai = <&pdm>; ++ }; ++ }; ++ }; ++ ++ pwmdac_codec: pwmdac-codec { ++ compatible = "linux,spdif-dit"; ++ #sound-dai-cells = <0>; ++ }; ++ ++ sound3: snd-card3 { ++ compatible = "simple-audio-card"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ simple-audio-card,name = "StarFive-PWMDAC-Sound-Card"; ++ simple-audio-card,dai-link@0 { ++ reg = <0>; ++ format = "left_j"; ++ bitclock-master = <&sndcpu0>; ++ frame-master = <&sndcpu0>; ++ ++ sndcpu0: cpu { ++ sound-dai = <&pwmdac>; ++ }; ++ ++ codec { ++ sound-dai = <&pwmdac_codec>; ++ }; ++ }; ++ }; ++}; ++ ++&mmc0 { ++ assigned-clocks = <&syscrg JH7110_SYSCLK_SDIO0_SDCARD>; ++ assigned-clock-rates = <50000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&sdcard0_pins>; ++ max-frequency = <100000000>; ++ card-detect-delay = <300>; ++ bus-width = <4>; ++ broken-cd; ++ post-power-on-delay-ms = <200>; ++ status = "okay"; ++}; ++ ++&usb0 { ++ status = "okay"; ++}; ++ ++&pcie1 { ++ status = "okay"; ++}; ++ ++&can0 { ++ status = "okay"; ++}; ++ ++&can1 { ++ status = "okay"; ++}; ++ ++&i2srx_mst { ++ status = "okay"; ++}; ++ ++&pwmdac { ++ status = "okay"; ++}; ++ ++&pdm { ++ status = "okay"; ++}; +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb-dvp-rgb2hdmi.dts +@@ -0,0 +1,37 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ */ ++ ++/dts-v1/; ++#include "jh7110-evb.dtsi" ++ ++/ { ++ model = "StarFive JH7110 EVB"; ++ compatible = "starfive,jh7110-evb", "starfive,jh7110"; ++}; ++ ++&vin_sysctl { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&dvp_pins>; ++}; ++ ++&rgb_output { ++ status = "okay"; ++}; ++ ++&tda988x_pin { ++ status = "okay"; ++}; ++ ++&dsi_output { ++ status = "disabled"; ++}; ++ ++&mipi_dsi { ++ status = "disabled"; ++}; ++ ++&mipi_dphy { ++ status = "disabled"; ++}; +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb-i2s-ac108.dts +@@ -0,0 +1,72 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ */ ++ ++/dts-v1/; ++#include "jh7110-evb.dtsi" ++ ++/ { ++ model = "StarFive JH7110 EVB"; ++ compatible = "starfive,jh7110-evb", "starfive,jh7110"; ++ ++ /* i2s + ac108 */ ++ sound0: snd-card0 { ++ compatible = "simple-audio-card"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ simple-audio-card,name = "StarFive-AC108-Sound-Card"; ++ simple-audio-card,dai-link@0 { ++ reg = <0>; ++ format = "i2s"; ++ bitclock-master = <&sndcodec1>; ++ frame-master = <&sndcodec1>; ++ ++ widgets = "Microphone", "Mic Jack", ++ "Line", "Line In", ++ "Line", "Line Out", ++ "Speaker", "Speaker", ++ "Headphone", "Headphone Jack"; ++ routing = "Headphone Jack", "HP_L", ++ "Headphone Jack", "HP_R", ++ "Speaker", "SPK_LP", ++ "Speaker", "SPK_LN", ++ "LINPUT1", "Mic Jack", ++ "LINPUT3", "Mic Jack", ++ "RINPUT1", "Mic Jack", ++ "RINPUT2", "Mic Jack"; ++ ++ cpu { ++ sound-dai = <&i2srx>; ++ }; ++ ++ sndcodec1: codec { ++ sound-dai = <&ac108>; ++ clocks = <&ac108_mclk>; ++ clock-names = "mclk"; ++ }; ++ }; ++ }; ++}; ++ ++&mmc0 { ++ assigned-clocks = <&syscrg JH7110_SYSCLK_SDIO0_SDCARD>; ++ assigned-clock-rates = <50000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&sdcard0_pins>; ++ max-frequency = <100000000>; ++ card-detect-delay = <300>; ++ bus-width = <4>; ++ broken-cd; ++ post-power-on-delay-ms = <200>; ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "okay"; ++}; ++ ++&i2srx { ++ status = "okay"; ++}; +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb-pcie-i2s-sd.dts +@@ -0,0 +1,111 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ */ ++ ++/dts-v1/; ++#include "jh7110-evb.dtsi" ++ ++/ { ++ model = "StarFive JH7110 EVB"; ++ compatible = "starfive,jh7110-evb", "starfive,jh7110"; ++ ++ /* i2s + wm8960 */ ++ sound6: snd-card6 { ++ compatible = "simple-audio-card"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ simple-audio-card,name = "StarFive-WM8960-Sound-Card"; ++ simple-audio-card,dai-link@0 { ++ reg = <0>; ++ status = "okay"; ++ format = "i2s"; ++ bitclock-master = <&sndcodec1>; ++ frame-master = <&sndcodec1>; ++ ++ widgets = "Microphone", "Mic Jack", ++ "Line", "Line In", ++ "Line", "Line Out", ++ "Speaker", "Speaker", ++ "Headphone", "Headphone Jack"; ++ routing = "Headphone Jack", "HP_L", ++ "Headphone Jack", "HP_R", ++ "Speaker", "SPK_LP", ++ "Speaker", "SPK_LN", ++ "LINPUT1", "Mic Jack", ++ "LINPUT3", "Mic Jack", ++ "RINPUT1", "Mic Jack", ++ "RINPUT2", "Mic Jack"; ++ cpu0 { ++ sound-dai = <&i2srx>; ++ }; ++ cpu1 { ++ sound-dai = <&i2stx1>; ++ }; ++ ++ sndcodec1:codec { ++ sound-dai = <&wm8960>; ++ clocks = <&wm8960_mclk>; ++ clock-names = "mclk"; ++ }; ++ }; ++ }; ++}; ++ ++&mmc0 { ++ assigned-clocks = <&syscrg JH7110_SYSCLK_SDIO0_SDCARD>; ++ assigned-clock-rates = <50000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&sdcard0_pins>; ++ max-frequency = <100000000>; ++ card-detect-delay = <300>; ++ bus-width = <4>; ++ broken-cd; ++ post-power-on-delay-ms = <200>; ++ status = "okay"; ++}; ++ ++&pcie1 { ++ status = "okay"; ++}; ++ ++&pcie0 { ++ status = "okay"; ++}; ++ ++&uart3 { ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "okay"; ++}; ++ ++&usb0 { ++ clocks = <&stgcrg JH7110_STGCLK_USB0_LPM>, ++ <&stgcrg JH7110_STGCLK_USB0_STB>, ++ <&stgcrg JH7110_STGCLK_USB0_APB>, ++ <&stgcrg JH7110_STGCLK_USB0_AXI>, ++ <&stgcrg JH7110_STGCLK_USB0_UTMI_APB>; ++ clock-names = "lpm", "stb", "apb", "axi", "utmi_apb"; ++ resets = <&stgcrg JH7110_STGRST_USB0_PWRUP>, ++ <&stgcrg JH7110_STGRST_USB0_APB>, ++ <&stgcrg JH7110_STGRST_USB0_AXI>, ++ <&stgcrg JH7110_STGRST_USB0_UTMI_APB>; ++ reset-names = "pwrup", "apb", "axi", "utmi_apb"; ++ dr_mode = "host"; /*host or peripheral*/ ++ starfive,usb2-only; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&usb_pins>; ++ status = "okay"; ++}; ++ ++&i2srx { ++ status = "okay"; ++}; ++ ++&i2stx1 { ++ status = "okay"; ++}; ++ +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb-spi-uart2.dts +@@ -0,0 +1,65 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ */ ++ ++/dts-v1/; ++#include "jh7110-evb.dtsi" ++ ++/ { ++ model = "StarFive JH7110 EVB"; ++ compatible = "starfive,jh7110-evb", "starfive,jh7110"; ++}; ++ ++&mmc0 { ++ assigned-clocks = <&syscrg JH7110_SYSCLK_SDIO0_SDCARD>; ++ assigned-clock-rates = <50000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&sdcard0_pins>; ++ max-frequency = <100000000>; ++ card-detect-delay = <300>; ++ bus-width = <4>; ++ broken-cd; ++ post-power-on-delay-ms = <200>; ++ status = "okay"; ++}; ++ ++&usb0 { ++ status = "okay"; ++}; ++ ++&pcie1 { ++ status = "okay"; ++}; ++ ++&uart2 { ++ status = "okay"; ++}; ++ ++&spi0 { ++ status = "okay"; ++}; ++ ++&spi1 { ++ status = "okay"; ++}; ++ ++&spi2 { ++ status = "okay"; ++}; ++ ++&spi3 { ++ status = "okay"; ++}; ++ ++&spi4 { ++ status = "okay"; ++}; ++ ++&spi5 { ++ status = "okay"; ++}; ++ ++&spi6 { ++ status = "okay"; ++}; +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb-uart1-rgb2hdmi.dts +@@ -0,0 +1,57 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ */ ++ ++/dts-v1/; ++#include "jh7110-evb.dtsi" ++ ++/ { ++ model = "StarFive JH7110 EVB"; ++ compatible = "starfive,jh7110-evb", "starfive,jh7110"; ++}; ++ ++&mmc0 { ++ assigned-clocks = <&syscrg JH7110_SYSCLK_SDIO0_SDCARD>; ++ assigned-clock-rates = <50000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&sdcard0_pins>; ++ max-frequency = <100000000>; ++ card-detect-delay = <300>; ++ bus-width = <4>; ++ broken-cd; ++ post-power-on-delay-ms = <200>; ++ status = "okay"; ++}; ++ ++&usb0 { ++ status = "okay"; ++}; ++ ++&pcie1 { ++ status = "okay"; ++}; ++ ++&uart1 { ++ status = "okay"; ++}; ++ ++&rgb_output { ++ status = "okay"; ++}; ++ ++&tda988x_pin { ++ status = "okay"; ++}; ++ ++&dsi_output { ++ status = "disabled"; ++}; ++ ++&mipi_dsi { ++ status = "disabled"; ++}; ++ ++&mipi_dphy { ++ status = "disabled"; ++}; +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb-uart4-emmc-spdif.dts +@@ -0,0 +1,78 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ */ ++ ++/dts-v1/; ++#include "jh7110-evb.dtsi" ++ ++/ { ++ model = "StarFive JH7110 EVB"; ++ compatible = "starfive,jh7110-evb", "starfive,jh7110"; ++ ++ spdif_transmitter: spdif_transmitter { ++ compatible = "linux,spdif-dit"; ++ #sound-dai-cells = <0>; ++ }; ++ ++ sound4: snd-card4 { ++ compatible = "simple-audio-card"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ simple-audio-card,name = "StarFive-SPDIF-Sound-Card"; ++ simple-audio-card,dai-link@0 { ++ reg = <0>; ++ format = "left_j"; ++ bitclock-master = <&sndcpu0>; ++ frame-master = <&sndcpu0>; ++ ++ sndcpu0: cpu { ++ sound-dai = <&spdif>; ++ }; ++ ++ codec { ++ sound-dai = <&spdif_transmitter>; ++ }; ++ }; ++ }; ++}; ++ ++&usb0 { ++ status = "okay"; ++}; ++ ++&pcie1 { ++ status = "okay"; ++}; ++ ++&uart4 { ++ status = "okay"; ++}; ++ ++&mmc0 { ++ assigned-clocks = <&syscrg JH7110_SYSCLK_SDIO0_SDCARD>; ++ assigned-clock-rates = <50000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&emmc0_pins>; ++ max-frequency = <100000000>; ++ card-detect-delay = <300>; ++ bus-width = <8>; ++ cap-mmc-highspeed; ++ mmc-hs200-1_8v; ++ non-removable; ++ cap-mmc-hw-reset; ++ board-is-evb; ++ post-power-on-delay-ms = <200>; ++ status = "okay"; ++}; ++ ++&pwm { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwm_ch6to7_pins>; ++ status = "okay"; ++}; ++ ++&spdif { ++ status = "okay"; ++}; +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb-uart5-pwm-i2c-tdm.dts +@@ -0,0 +1,95 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ */ ++ ++/dts-v1/; ++#include "jh7110-evb.dtsi" ++ ++/ { ++ model = "StarFive JH7110 EVB"; ++ compatible = "starfive,jh7110-evb", "starfive,jh7110"; ++ ++ sound5: snd-card5 { ++ compatible = "simple-audio-card"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ simple-audio-card,name = "StarFive-TDM-Sound-Card"; ++ simple-audio-card,widgets = "Microphone", "Mic Jack", ++ "Line", "Line In", ++ "Line", "Line Out", ++ "Speaker", "Speaker", ++ "Headphone", "Headphone Jack"; ++ simple-audio-card,routing = "Headphone Jack", "HP_L", ++ "Headphone Jack", "HP_R", ++ "Speaker", "SPK_LP", ++ "Speaker", "SPK_LN", ++ "LINPUT1", "Mic Jack", ++ "LINPUT3", "Mic Jack", ++ "RINPUT1", "Mic Jack", ++ "RINPUT2", "Mic Jack"; ++ ++ simple-audio-card,dai-link@0 { ++ reg = <0>; ++ format = "dsp_a"; ++ bitclock-master = <&dailink_master>; ++ frame-master = <&dailink_master>; ++ ++ cpu { ++ sound-dai = <&tdm>; ++ }; ++ dailink_master: codec { ++ sound-dai = <&wm8960>; ++ clocks = <&wm8960_mclk>; ++ }; ++ }; ++ }; ++}; ++ ++&mmc0 { ++ assigned-clocks = <&syscrg JH7110_SYSCLK_SDIO0_SDCARD>; ++ assigned-clock-rates = <50000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&sdcard0_pins>; ++ max-frequency = <100000000>; ++ card-detect-delay = <300>; ++ bus-width = <4>; ++ broken-cd; ++ post-power-on-delay-ms = <200>; ++ status = "okay"; ++}; ++ ++&usb0 { ++ status = "okay"; ++}; ++ ++&pcie1 { ++ status = "okay"; ++}; ++ ++&uart5 { ++ status = "okay"; ++}; ++ ++&pwm { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwm_ch0to3_pins &pwm_ch4to5_pins>; ++ status = "okay"; ++}; ++ ++&tdm { ++ status = "okay"; ++}; ++ ++&i2c0 { ++ status = "okay"; ++}; ++ ++&i2c1 { ++ status = "okay"; ++}; ++ ++&i2c3 { ++ status = "okay"; ++}; +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb-usbdevice.dts +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ */ ++ ++/dts-v1/; ++#include "jh7110-evb.dtsi" ++ ++/ { ++ model = "StarFive JH7110 EVB"; ++ compatible = "starfive,jh7110-evb", "starfive,jh7110"; ++}; ++ ++/* default sd card */ ++&mmc0 { ++ assigned-clocks = <&syscrg JH7110_SYSCLK_SDIO0_SDCARD>; ++ assigned-clock-rates = <50000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&sdcard0_pins>; ++ max-frequency = <100000000>; ++ card-detect-delay = <300>; ++ bus-width = <4>; ++ broken-cd; ++ post-power-on-delay-ms = <200>; ++ status = "okay"; ++}; ++ ++&usb0 { ++ dr_mode = "peripheral"; /*host or peripheral*/ ++ status = "okay"; ++}; ++ ++&pcie1 { ++ status = "okay"; ++}; diff --git a/target/linux/starfive/patches-6.6/0058-riscv-dts-starfive-Add-evb-overlay-dtso-subdir.patch b/target/linux/starfive/patches-6.6/0058-riscv-dts-starfive-Add-evb-overlay-dtso-subdir.patch new file mode 100644 index 0000000000..a75ffc2aff --- /dev/null +++ b/target/linux/starfive/patches-6.6/0058-riscv-dts-starfive-Add-evb-overlay-dtso-subdir.patch @@ -0,0 +1,485 @@ +From e9122ceaf2d8767753e2a126c14b29b78280446d Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Tue, 19 Sep 2023 21:35:39 +0800 +Subject: [PATCH 058/116] riscv: dts: starfive: Add evb-overlay dtso subdir + +Create subdir evb-overlay/ and add overlay .dtso for JH7110 EVB. +The code is ported from tag JH7110_SDK_6.1_v5.11.3 + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + arch/riscv/boot/dts/starfive/Makefile | 1 + + .../boot/dts/starfive/evb-overlay/Makefile | 7 + + .../evb-overlay/jh7110-evb-overlay-can.dtso | 24 ++++ + .../jh7110-evb-overlay-rgb2hdmi.dtso | 24 ++++ + .../evb-overlay/jh7110-evb-overlay-sdio.dtso | 78 +++++++++++ + .../evb-overlay/jh7110-evb-overlay-spi.dtso | 72 ++++++++++ + .../jh7110-evb-overlay-uart4-emmc.dtso | 130 ++++++++++++++++++ + .../jh7110-evb-overlay-uart5-pwm.dtso | 92 +++++++++++++ + 8 files changed, 428 insertions(+) + create mode 100644 arch/riscv/boot/dts/starfive/evb-overlay/Makefile + create mode 100644 arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-can.dtso + create mode 100644 arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-rgb2hdmi.dtso + create mode 100644 arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-sdio.dtso + create mode 100644 arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-spi.dtso + create mode 100644 arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-uart4-emmc.dtso + create mode 100644 arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-uart5-pwm.dtso + +--- a/arch/riscv/boot/dts/starfive/Makefile ++++ b/arch/riscv/boot/dts/starfive/Makefile +@@ -12,6 +12,7 @@ dtb-$(CONFIG_ARCH_STARFIVE) += jh7100-st + dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-starfive-visionfive-2-v1.2a.dtb + dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-starfive-visionfive-2-v1.3b.dtb + ++subdir-y += evb-overlay + dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-evb.dtb \ + jh7110-evb-pcie-i2s-sd.dtb \ + jh7110-evb-spi-uart2.dtb \ +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/evb-overlay/Makefile +@@ -0,0 +1,7 @@ ++# SPDX-License-Identifier: GPL-2.0 ++dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-evb-overlay-can.dtbo \ ++ jh7110-evb-overlay-sdio.dtbo \ ++ jh7110-evb-overlay-spi.dtbo \ ++ jh7110-evb-overlay-uart4-emmc.dtbo \ ++ jh7110-evb-overlay-uart5-pwm.dtbo \ ++ jh7110-evb-overlay-rgb2hdmi.dtbo +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-can.dtso +@@ -0,0 +1,24 @@ ++/dts-v1/; ++/plugin/; ++#include <dt-bindings/gpio/gpio.h> ++#include "../jh7110-pinfunc.h" ++/ { ++ compatible = "starfive,jh7110"; ++ ++ //can0 ++ fragment@0 { ++ target-path = "/soc/can@130d0000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //can1 ++ fragment@1 { ++ target-path = "/soc/can@130e0000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++}; ++ +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-rgb2hdmi.dtso +@@ -0,0 +1,24 @@ ++/dts-v1/; ++/plugin/; ++#include <dt-bindings/gpio/gpio.h> ++#include "../jh7110-pinfunc.h" ++/ { ++ compatible = "starfive,jh7110"; ++ ++ //hdmi_output ++ fragment@0 { ++ target-path = "/tda988x_pin"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //uart1 ++ fragment@1 { ++ target-path = "/soc/serial@10010000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++}; ++ +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-sdio.dtso +@@ -0,0 +1,78 @@ ++/dts-v1/; ++/plugin/; ++#include <dt-bindings/gpio/gpio.h> ++#include "../jh7110-pinfunc.h" ++/ { ++ compatible = "starfive,jh7110"; ++ ++ //sysgpio ++ fragment@0 { ++ target-path = "/soc/pinctrl@13040000"; ++ __overlay__ { ++ dt_sdcard1_pins: dt-sdcard1-0 { ++ sdcard-pins { ++ pinmux = <GPIOMUX(56, GPOUT_SYS_SDIO1_CLK, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <GPIOMUX(50, GPOUT_SYS_SDIO1_CMD, ++ GPOEN_SYS_SDIO1_CMD, ++ GPI_SYS_SDIO1_CMD)>, ++ <GPIOMUX(49, GPOUT_SYS_SDIO1_DATA0, ++ GPOEN_SYS_SDIO1_DATA0, ++ GPI_SYS_SDIO1_DATA0)>, ++ <GPIOMUX(45, GPOUT_SYS_SDIO1_DATA1, ++ GPOEN_SYS_SDIO1_DATA1, ++ GPI_SYS_SDIO1_DATA1)>, ++ <GPIOMUX(62, GPOUT_SYS_SDIO1_DATA2, ++ GPOEN_SYS_SDIO1_DATA2, ++ GPI_SYS_SDIO1_DATA2)>, ++ <GPIOMUX(40, GPOUT_SYS_SDIO1_DATA3, ++ GPOEN_SYS_SDIO1_DATA3, ++ GPI_SYS_SDIO1_DATA3)>; ++ bias-pull-up; ++ input-enable; ++ }; ++ }; ++ }; ++ }; ++ ++ //uart3 ++ fragment@1 { ++ target-path = "/soc/serial@12000000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //i2c0 ++ fragment@2 { ++ target-path = "/soc/i2c@10030000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //mmc1 ++ fragment@3 { ++ target-path = "/soc/mmc@16020000"; ++ __overlay__ { ++ max-frequency = <100000000>; ++ card-detect-delay = <300>; ++ bus-width = <4>; ++ no-sdio; ++ no-mmc; ++ broken-cd; ++ sd-uhs-sdr12; ++ sd-uhs-sdr25; ++ sd-uhs-sdr50; ++ sd-uhs-sdr104; ++ sd-uhs-ddr50; ++ cap-sd-highspeed; ++ post-power-on-delay-ms = <200>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&dt_sdcard1_pins>; ++ status = "okay"; ++ }; ++ }; ++}; ++ +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-spi.dtso +@@ -0,0 +1,72 @@ ++/dts-v1/; ++/plugin/; ++#include <dt-bindings/gpio/gpio.h> ++#include "../jh7110-pinfunc.h" ++/ { ++ compatible = "starfive,jh7110"; ++ ++ //spi0 ++ fragment@0 { ++ target-path = "/soc/spi@10060000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //spi1 ++ fragment@1 { ++ target-path = "/soc/spi@10070000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //spi2 ++ fragment@2 { ++ target-path = "/soc/spi@10080000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //spi3 ++ fragment@3 { ++ target-path = "/soc/spi@12070000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //spi4 ++ fragment@4 { ++ target-path = "/soc/spi@12080000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //spi5 ++ fragment@5 { ++ target-path = "/soc/spi@12090000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //spi6 ++ fragment@6 { ++ target-path = "/soc/spi@120a0000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //uart2 ++ fragment@7 { ++ target-path = "/soc/serial@10020000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++}; ++ +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-uart4-emmc.dtso +@@ -0,0 +1,130 @@ ++/dts-v1/; ++/plugin/; ++#include <dt-bindings/gpio/gpio.h> ++#include "../jh7110-pinfunc.h" ++/ { ++ compatible = "starfive,jh7110"; ++ ++ //sysgpio ++ fragment@0 { ++ target-path = "/soc/pinctrl@13040000"; ++ __overlay__ { ++ dt_emmc0_pins: dt-emmc0-0 { ++ emmc-pins { ++ pinmux = <GPIOMUX(22, GPOUT_SYS_SDIO0_RST, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <PINMUX(64, 0)>, ++ <PINMUX(65, 0)>, ++ <PINMUX(66, 0)>, ++ <PINMUX(67, 0)>, ++ <PINMUX(68, 0)>, ++ <PINMUX(69, 0)>, ++ <PINMUX(70, 0)>, ++ <PINMUX(71, 0)>, ++ <PINMUX(72, 0)>, ++ <PINMUX(73, 0)>; ++ bias-pull-up; ++ drive-strength = <12>; ++ input-enable; ++ slew-rate = <1>; ++ }; ++ }; ++ ++ dt_emmc1_pins: dt-emmc1-0 { ++ emmc-pins { ++ pinmux = <GPIOMUX(51, GPOUT_SYS_SDIO1_RST, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <GPIOMUX(38, GPOUT_SYS_SDIO1_CLK, ++ GPOEN_ENABLE, ++ GPI_NONE)>, ++ <GPIOMUX(36, GPOUT_SYS_SDIO1_CMD, ++ GPOEN_SYS_SDIO1_CMD, ++ GPI_SYS_SDIO1_CMD)>, ++ <GPIOMUX(43, GPOUT_SYS_SDIO1_DATA0, ++ GPOEN_SYS_SDIO1_DATA0, ++ GPI_SYS_SDIO1_DATA0)>, ++ <GPIOMUX(48, GPOUT_SYS_SDIO1_DATA1, ++ GPOEN_SYS_SDIO1_DATA1, ++ GPI_SYS_SDIO1_DATA1)>, ++ <GPIOMUX(53, GPOUT_SYS_SDIO1_DATA2, ++ GPOEN_SYS_SDIO1_DATA2, ++ GPI_SYS_SDIO1_DATA2)>, ++ <GPIOMUX(63, GPOUT_SYS_SDIO1_DATA3, ++ GPOEN_SYS_SDIO1_DATA3, ++ GPI_SYS_SDIO1_DATA3)>, ++ <GPIOMUX(52, GPOUT_SYS_SDIO1_DATA4, ++ GPOEN_SYS_SDIO1_DATA4, ++ GPI_SYS_SDIO1_DATA4)>, ++ <GPIOMUX(39, GPOUT_SYS_SDIO1_DATA5, ++ GPOEN_SYS_SDIO1_DATA5, ++ GPI_SYS_SDIO1_DATA5)>, ++ <GPIOMUX(46, GPOUT_SYS_SDIO1_DATA6, ++ GPOEN_SYS_SDIO1_DATA6, ++ GPI_SYS_SDIO1_DATA6)>, ++ <GPIOMUX(47, GPOUT_SYS_SDIO1_DATA7, ++ GPOEN_SYS_SDIO1_DATA7, ++ GPI_SYS_SDIO1_DATA7)>; ++ bias-pull-up; ++ input-enable; ++ }; ++ }; ++ }; ++ }; ++ ++ //aongpio ++ fragment@1 { ++ target-path = "/soc/pinctrl@17020000"; ++ __overlay__ { ++ dt_pwm_ch6to7_pins: dt-pwm-ch6to7-0 { ++ pwm-pins { ++ pinmux = <GPIOMUX(1, GPOUT_AON_PTC0_PWM6, /* PAD_RGPIO0 */ ++ GPOEN_AON_PTC0_OE_N_6, ++ GPI_NONE)>, ++ <GPIOMUX(2, GPOUT_AON_PTC0_PWM7, /* PAD_RGPIO1 */ ++ GPOEN_AON_PTC0_OE_N_7, ++ GPI_NONE)>; ++ drive-strength = <12>; ++ }; ++ }; ++ }; ++ }; ++ ++ //uart4 ++ fragment@2 { ++ target-path = "/soc/serial@12010000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //mmc1 ++ fragment@3 { ++ target-path = "/soc/mmc@16020000"; ++ __overlay__ { ++ clock-frequency = <102400000>; ++ max-frequency = <100000000>; ++ card-detect-delay = <300>; ++ bus-width = <8>; ++ cap-mmc-hw-reset; ++ non-removable; ++ cap-mmc-highspeed; ++ post-power-on-delay-ms = <200>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&dt_emmc1_pins>; ++ status = "okay"; ++ }; ++ }; ++ ++ //ptc ++ fragment@4 { ++ target-path = "/soc/pwm@120d0000"; ++ __overlay__ { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&dt_pwm_ch6to7_pins>; ++ status = "okay"; ++ }; ++ }; ++}; ++ +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-uart5-pwm.dtso +@@ -0,0 +1,92 @@ ++/dts-v1/; ++/plugin/; ++#include <dt-bindings/gpio/gpio.h> ++#include "../jh7110-pinfunc.h" ++/ { ++ compatible = "starfive,jh7110"; ++ ++ //sysgpio ++ fragment@0 { ++ target-path = "/soc/pinctrl@13040000"; ++ __overlay__ { ++ dt_pwm_ch0to3_pins: dt-pwm-ch0to3-0 { ++ pwm-pins { ++ pinmux = <GPIOMUX(45, GPOUT_SYS_PWM_CHANNEL0, ++ GPOEN_SYS_PWM0_CHANNEL0, ++ GPI_NONE)>, ++ <GPIOMUX(46, GPOUT_SYS_PWM_CHANNEL1, ++ GPOEN_SYS_PWM0_CHANNEL1, ++ GPI_NONE)>, ++ <GPIOMUX(47, GPOUT_SYS_PWM_CHANNEL2, ++ GPOEN_SYS_PWM0_CHANNEL2, ++ GPI_NONE)>, ++ <GPIOMUX(48, GPOUT_SYS_PWM_CHANNEL3, ++ GPOEN_SYS_PWM0_CHANNEL3, ++ GPI_NONE)>; ++ drive-strength = <12>; ++ }; ++ }; ++ }; ++ }; ++ ++ //aongpio ++ fragment@1 { ++ target-path = "/soc/pinctrl@17020000"; ++ __overlay__ { ++ dt_pwm_ch4to5_pins: dt-pwm-ch4to5-0 { ++ pwm-pins { ++ pinmux = <GPIOMUX(1, GPOUT_AON_PTC0_PWM4, /* PAD_RGPIO0 */ ++ GPOEN_AON_PTC0_OE_N_4, ++ GPI_NONE)>, ++ <GPIOMUX(2, GPOUT_AON_PTC0_PWM5, /* PAD_RGPIO1 */ ++ GPOEN_AON_PTC0_OE_N_5, ++ GPI_NONE)>; ++ drive-strength = <12>; ++ }; ++ }; ++ }; ++ }; ++ ++ //uart5 ++ fragment@2 { ++ target-path = "/soc/serial@12020000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //ptc ++ fragment@3 { ++ target-path = "/soc/pwm@120d0000"; ++ __overlay__ { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&dt_pwm_ch0to3_pins &dt_pwm_ch4to5_pins>; ++ status = "okay"; ++ }; ++ }; ++ ++ //i2c0 ++ fragment@4 { ++ target-path = "/soc/i2c@10030000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //i2c1 ++ fragment@5 { ++ target-path = "/soc/i2c@10040000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //i2c3 ++ fragment@6 { ++ target-path = "/soc/i2c@12030000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++}; ++ diff --git a/target/linux/starfive/patches-6.6/0059-riscv-configs-Add-starfive_jh7110_defconfig.patch b/target/linux/starfive/patches-6.6/0059-riscv-configs-Add-starfive_jh7110_defconfig.patch new file mode 100644 index 0000000000..ddf72454eb --- /dev/null +++ b/target/linux/starfive/patches-6.6/0059-riscv-configs-Add-starfive_jh7110_defconfig.patch @@ -0,0 +1,385 @@ +From 5c888fa081caf5d9473e733931d1c7b3d4b61e61 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Fri, 28 Jul 2023 18:42:55 +0800 +Subject: [PATCH 059/116] riscv: configs: Add starfive_jh7110_defconfig + +Add starfive_jh7110_defconfig for JH7110 EVB. +The code is ported from tag JH7110_SDK_6.1_v5.11.3 + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + arch/riscv/configs/starfive_jh7110_defconfig | 368 +++++++++++++++++++ + 1 file changed, 368 insertions(+) + create mode 100644 arch/riscv/configs/starfive_jh7110_defconfig + +--- /dev/null ++++ b/arch/riscv/configs/starfive_jh7110_defconfig +@@ -0,0 +1,368 @@ ++CONFIG_COMPILE_TEST=y ++# CONFIG_WERROR is not set ++CONFIG_DEFAULT_HOSTNAME="StarFive" ++CONFIG_SYSVIPC=y ++CONFIG_POSIX_MQUEUE=y ++CONFIG_USELIB=y ++CONFIG_NO_HZ_IDLE=y ++CONFIG_HIGH_RES_TIMERS=y ++CONFIG_BPF_SYSCALL=y ++CONFIG_IKCONFIG=y ++CONFIG_IKCONFIG_PROC=y ++CONFIG_CGROUPS=y ++CONFIG_MEMCG=y ++CONFIG_CGROUP_SCHED=y ++CONFIG_CFS_BANDWIDTH=y ++CONFIG_RT_GROUP_SCHED=y ++CONFIG_CGROUP_PIDS=y ++CONFIG_CGROUP_FREEZER=y ++CONFIG_CGROUP_HUGETLB=y ++CONFIG_CPUSETS=y ++CONFIG_CGROUP_DEVICE=y ++CONFIG_CGROUP_CPUACCT=y ++CONFIG_CGROUP_PERF=y ++CONFIG_CGROUP_BPF=y ++CONFIG_NAMESPACES=y ++CONFIG_USER_NS=y ++CONFIG_CHECKPOINT_RESTORE=y ++CONFIG_BLK_DEV_INITRD=y ++CONFIG_EXPERT=y ++# CONFIG_SYSFS_SYSCALL is not set ++CONFIG_PROFILING=y ++CONFIG_SOC_MICROCHIP_POLARFIRE=y ++CONFIG_SOC_STARFIVE=y ++CONFIG_SOC_VIRT=y ++CONFIG_ERRATA_SIFIVE=y ++CONFIG_NONPORTABLE=y ++CONFIG_SMP=y ++CONFIG_RISCV_SBI_V01=y ++# CONFIG_RISCV_BOOT_SPINWAIT is not set ++CONFIG_HIBERNATION=y ++CONFIG_PM_STD_PARTITION="PARTLABEL=hibernation" ++CONFIG_PM_DEBUG=y ++CONFIG_PM_ADVANCED_DEBUG=y ++CONFIG_PM_TEST_SUSPEND=y ++CONFIG_ENERGY_MODEL=y ++CONFIG_CPU_IDLE=y ++CONFIG_CPU_FREQ=y ++CONFIG_CPU_FREQ_STAT=y ++CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y ++CONFIG_CPU_FREQ_GOV_POWERSAVE=y ++CONFIG_CPU_FREQ_GOV_USERSPACE=y ++CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y ++CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y ++CONFIG_CPUFREQ_DT=y ++CONFIG_VIRTUALIZATION=y ++CONFIG_KVM=m ++CONFIG_JUMP_LABEL=y ++CONFIG_MODULES=y ++CONFIG_MODULE_UNLOAD=y ++CONFIG_BINFMT_MISC=y ++CONFIG_CMA=y ++CONFIG_NET=y ++CONFIG_PACKET=y ++CONFIG_XFRM_USER=m ++CONFIG_IP_MULTICAST=y ++CONFIG_IP_ADVANCED_ROUTER=y ++CONFIG_IP_MULTIPLE_TABLES=y ++CONFIG_IP_PNP=y ++CONFIG_IP_PNP_DHCP=y ++CONFIG_IP_PNP_BOOTP=y ++CONFIG_IP_PNP_RARP=y ++CONFIG_INET_ESP=m ++CONFIG_NETFILTER=y ++CONFIG_BRIDGE_NETFILTER=m ++CONFIG_NF_CONNTRACK=m ++CONFIG_NF_CONNTRACK_FTP=m ++CONFIG_NF_CONNTRACK_TFTP=m ++CONFIG_NETFILTER_XT_MARK=m ++CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m ++CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m ++CONFIG_NETFILTER_XT_MATCH_IPVS=m ++CONFIG_IP_VS=m ++CONFIG_IP_VS_PROTO_TCP=y ++CONFIG_IP_VS_PROTO_UDP=y ++CONFIG_IP_VS_RR=m ++CONFIG_IP_VS_NFCT=y ++CONFIG_NF_LOG_ARP=m ++CONFIG_NF_LOG_IPV4=m ++CONFIG_IP_NF_IPTABLES=m ++CONFIG_IP_NF_FILTER=m ++CONFIG_IP_NF_TARGET_REJECT=m ++CONFIG_IP_NF_NAT=m ++CONFIG_IP_NF_TARGET_MASQUERADE=m ++CONFIG_IP_NF_TARGET_REDIRECT=m ++CONFIG_IP_NF_MANGLE=m ++CONFIG_NF_LOG_IPV6=m ++CONFIG_IP6_NF_IPTABLES=m ++CONFIG_IP6_NF_MATCH_IPV6HEADER=m ++CONFIG_IP6_NF_FILTER=m ++CONFIG_IP6_NF_TARGET_REJECT=m ++CONFIG_IP6_NF_MANGLE=m ++CONFIG_BRIDGE=m ++CONFIG_BRIDGE_VLAN_FILTERING=y ++CONFIG_VLAN_8021Q=m ++CONFIG_NET_SCHED=y ++CONFIG_NET_CLS_CGROUP=m ++CONFIG_NETLINK_DIAG=y ++CONFIG_CGROUP_NET_PRIO=y ++CONFIG_CAN=y ++CONFIG_BT=y ++CONFIG_BT_RFCOMM=y ++CONFIG_BT_RFCOMM_TTY=y ++CONFIG_BT_BNEP=y ++CONFIG_BT_BNEP_MC_FILTER=y ++CONFIG_BT_BNEP_PROTO_FILTER=y ++CONFIG_BT_HCIUART=y ++CONFIG_BT_HCIUART_H4=y ++CONFIG_CFG80211=y ++CONFIG_MAC80211=y ++CONFIG_RFKILL=y ++CONFIG_NET_9P=y ++CONFIG_NET_9P_VIRTIO=y ++CONFIG_PCI=y ++CONFIG_PCIEPORTBUS=y ++# CONFIG_PCIEASPM is not set ++CONFIG_PCI_HOST_GENERIC=y ++CONFIG_PCIE_FU740=y ++CONFIG_PCIE_STARFIVE_HOST=y ++CONFIG_DEVTMPFS=y ++CONFIG_DEVTMPFS_MOUNT=y ++CONFIG_MTD=y ++CONFIG_MTD_BLOCK=y ++CONFIG_MTD_SPI_NOR=y ++CONFIG_OF_CONFIGFS=y ++CONFIG_BLK_DEV_LOOP=y ++CONFIG_VIRTIO_BLK=y ++CONFIG_BLK_DEV_NVME=y ++CONFIG_BLK_DEV_SD=y ++CONFIG_BLK_DEV_SR=y ++CONFIG_SCSI_VIRTIO=y ++CONFIG_ATA=y ++CONFIG_SATA_AHCI=y ++CONFIG_SATA_AHCI_PLATFORM=y ++CONFIG_MD=y ++CONFIG_BLK_DEV_DM=m ++CONFIG_DM_THIN_PROVISIONING=m ++CONFIG_NETDEVICES=y ++CONFIG_DUMMY=m ++CONFIG_MACVLAN=m ++CONFIG_IPVLAN=m ++CONFIG_VXLAN=m ++CONFIG_VETH=m ++CONFIG_VIRTIO_NET=y ++CONFIG_MACB=y ++CONFIG_E1000E=y ++CONFIG_R8169=y ++CONFIG_STMMAC_ETH=y ++CONFIG_DWMAC_DWC_QOS_ETH=y ++# CONFIG_DWMAC_GENERIC is not set ++CONFIG_DWMAC_STARFIVE=y ++CONFIG_MARVELL_PHY=y ++CONFIG_MICREL_PHY=y ++CONFIG_MICROCHIP_PHY=y ++CONFIG_MICROSEMI_PHY=y ++CONFIG_MOTORCOMM_PHY=y ++CONFIG_IPMS_CAN=y ++CONFIG_IWLWIFI=y ++CONFIG_IWLDVM=y ++CONFIG_IWLMVM=y ++CONFIG_INPUT_MOUSEDEV=y ++CONFIG_INPUT_EVDEV=y ++CONFIG_INPUT_TOUCHSCREEN=y ++CONFIG_TOUCHSCREEN_TINKER_FT5406=y ++CONFIG_SERIAL_8250=y ++CONFIG_SERIAL_8250_CONSOLE=y ++CONFIG_SERIAL_8250_NR_UARTS=6 ++CONFIG_SERIAL_8250_RUNTIME_UARTS=6 ++CONFIG_SERIAL_8250_EXTENDED=y ++CONFIG_SERIAL_8250_MANY_PORTS=y ++CONFIG_SERIAL_8250_DW=y ++CONFIG_SERIAL_OF_PLATFORM=y ++CONFIG_SERIAL_EARLYCON_RISCV_SBI=y ++CONFIG_TTY_PRINTK=y ++CONFIG_VIRTIO_CONSOLE=y ++CONFIG_HW_RANDOM=y ++CONFIG_HW_RANDOM_VIRTIO=y ++CONFIG_HW_RANDOM_JH7110=y ++CONFIG_I2C_CHARDEV=y ++CONFIG_I2C_DESIGNWARE_PLATFORM=y ++CONFIG_SPI=y ++CONFIG_SPI_CADENCE_QUADSPI=y ++CONFIG_SPI_PL022=y ++CONFIG_SPI_SIFIVE=y ++CONFIG_SPI_SPIDEV=y ++# CONFIG_PTP_1588_CLOCK is not set ++CONFIG_GPIO_SYSFS=y ++CONFIG_GPIO_SIFIVE=y ++CONFIG_SENSORS_SFCTEMP=y ++CONFIG_THERMAL=y ++CONFIG_THERMAL_WRITABLE_TRIPS=y ++CONFIG_CPU_THERMAL=y ++CONFIG_THERMAL_EMULATION=y ++CONFIG_WATCHDOG=y ++CONFIG_WATCHDOG_SYSFS=y ++CONFIG_MFD_AXP20X_I2C=y ++CONFIG_REGULATOR=y ++CONFIG_REGULATOR_AXP20X=y ++CONFIG_REGULATOR_STARFIVE_JH7110=y ++# CONFIG_MEDIA_CEC_SUPPORT is not set ++CONFIG_MEDIA_SUPPORT=y ++CONFIG_MEDIA_USB_SUPPORT=y ++CONFIG_USB_VIDEO_CLASS=y ++CONFIG_V4L_PLATFORM_DRIVERS=y ++CONFIG_V4L_MEM2MEM_DRIVERS=y ++CONFIG_VIDEO_WAVE_VPU=m ++CONFIG_VIN_SENSOR_SC2235=y ++CONFIG_VIN_SENSOR_OV4689=y ++CONFIG_VIN_SENSOR_IMX219=y ++CONFIG_VIDEO_STF_VIN=y ++CONFIG_VIDEO_IMX708=y ++CONFIG_DRM_I2C_NXP_TDA998X=y ++CONFIG_DRM_I2C_NXP_TDA9950=y ++CONFIG_DRM_RADEON=m ++CONFIG_DRM_VIRTIO_GPU=m ++CONFIG_DRM_VERISILICON=y ++CONFIG_STARFIVE_INNO_HDMI=y ++CONFIG_STARFIVE_DSI=y ++CONFIG_DRM_IMG_ROGUE=y ++CONFIG_DRM_LEGACY=y ++CONFIG_FB=y ++CONFIG_SOUND=y ++CONFIG_SND=y ++CONFIG_SND_USB_AUDIO=y ++CONFIG_SND_SOC=y ++CONFIG_SND_DESIGNWARE_I2S=y ++# CONFIG_SND_SOC_INTEL_SST_TOPLEVEL is not set ++CONFIG_SND_SOC_STARFIVE=y ++CONFIG_SND_SOC_JH7110_PDM=y ++CONFIG_SND_SOC_JH7110_PWMDAC=y ++CONFIG_SND_SOC_JH7110_SPDIF=y ++CONFIG_SND_SOC_JH7110_TDM=y ++CONFIG_SND_SOC_AC108=y ++CONFIG_SND_SOC_WM8960=y ++CONFIG_SND_SIMPLE_CARD=y ++CONFIG_USB=y ++CONFIG_USB_XHCI_HCD=y ++CONFIG_USB_EHCI_HCD=y ++CONFIG_USB_EHCI_HCD_PLATFORM=y ++CONFIG_USB_OHCI_HCD=y ++CONFIG_USB_OHCI_HCD_PLATFORM=y ++CONFIG_USB_STORAGE=y ++CONFIG_USB_UAS=y ++CONFIG_USB_CDNS_SUPPORT=y ++CONFIG_USB_CDNS3=y ++CONFIG_USB_CDNS3_GADGET=y ++CONFIG_USB_CDNS3_HOST=y ++CONFIG_USB_CDNS3_STARFIVE=y ++CONFIG_USB_GADGET=y ++CONFIG_USB_CONFIGFS=y ++CONFIG_USB_CONFIGFS_MASS_STORAGE=y ++CONFIG_USB_CONFIGFS_F_FS=y ++CONFIG_MMC=y ++CONFIG_MMC_SDHCI=y ++CONFIG_MMC_SDHCI_PLTFM=y ++CONFIG_MMC_SDHCI_CADENCE=y ++CONFIG_MMC_SPI=y ++CONFIG_MMC_DW=y ++CONFIG_MMC_DW_STARFIVE=y ++CONFIG_RTC_CLASS=y ++# CONFIG_RTC_DRV_SPEAR is not set ++CONFIG_RTC_DRV_STARFIVE=y ++CONFIG_DMADEVICES=y ++CONFIG_AMBA_PL08X=y ++CONFIG_DW_AXI_DMAC=y ++# CONFIG_SH_DMAE_BASE is not set ++# CONFIG_TI_EDMA is not set ++# CONFIG_DMA_OMAP is not set ++CONFIG_DMATEST=y ++CONFIG_VIRTIO_PCI=y ++CONFIG_VIRTIO_BALLOON=y ++CONFIG_VIRTIO_INPUT=y ++CONFIG_VIRTIO_MMIO=y ++CONFIG_STAGING=y ++CONFIG_STAGING_MEDIA=y ++CONFIG_CLK_STARFIVE_JH7110_AON=y ++CONFIG_CLK_STARFIVE_JH7110_STG=y ++CONFIG_CLK_STARFIVE_JH7110_ISP=y ++CONFIG_CLK_STARFIVE_JH7110_VOUT=y ++CONFIG_MAILBOX=y ++CONFIG_STARFIVE_MBOX=m ++CONFIG_STARFIVE_MBOX_TEST=m ++CONFIG_RPMSG_CHAR=y ++CONFIG_RPMSG_CTRL=y ++CONFIG_RPMSG_VIRTIO=y ++CONFIG_SIFIVE_CCACHE=y ++CONFIG_PWM=y ++CONFIG_PWM_OCORES=y ++CONFIG_PHY_STARFIVE_JH7110_PCIE=y ++CONFIG_PHY_STARFIVE_JH7110_USB=y ++CONFIG_PHY_M31_DPHY_RX0=y ++CONFIG_EXT4_FS=y ++CONFIG_EXT4_FS_POSIX_ACL=y ++CONFIG_EXT4_FS_SECURITY=y ++CONFIG_BTRFS_FS=m ++CONFIG_BTRFS_FS_POSIX_ACL=y ++CONFIG_AUTOFS_FS=y ++CONFIG_FUSE_FS=y ++CONFIG_OVERLAY_FS=y ++CONFIG_OVERLAY_FS_INDEX=y ++CONFIG_OVERLAY_FS_XINO_AUTO=y ++CONFIG_OVERLAY_FS_METACOPY=y ++CONFIG_ISO9660_FS=y ++CONFIG_JOLIET=y ++CONFIG_ZISOFS=y ++CONFIG_MSDOS_FS=y ++CONFIG_VFAT_FS=y ++CONFIG_EXFAT_FS=y ++CONFIG_TMPFS=y ++CONFIG_TMPFS_POSIX_ACL=y ++CONFIG_HUGETLBFS=y ++CONFIG_JFFS2_FS=y ++CONFIG_NFS_FS=y ++CONFIG_NFS_V4=y ++CONFIG_NFS_V4_1=y ++CONFIG_NFS_V4_2=y ++CONFIG_ROOT_NFS=y ++CONFIG_9P_FS=y ++CONFIG_NLS_CODEPAGE_437=y ++CONFIG_NLS_ISO8859_1=m ++CONFIG_SECURITY=y ++CONFIG_SECURITY_SELINUX=y ++CONFIG_SECURITY_APPARMOR=y ++CONFIG_DEFAULT_SECURITY_DAC=y ++CONFIG_LSM="" ++CONFIG_INIT_STACK_NONE=y ++CONFIG_CRYPTO_USER=y ++CONFIG_CRYPTO_ZSTD=y ++CONFIG_CRYPTO_USER_API_HASH=y ++CONFIG_CRYPTO_USER_API_SKCIPHER=y ++CONFIG_CRYPTO_USER_API_AEAD=y ++CONFIG_CRYPTO_STATS=y ++CONFIG_CRYPTO_DEV_VIRTIO=y ++CONFIG_CRYPTO_DEV_JH7110=y ++CONFIG_DMA_CMA=y ++CONFIG_PRINTK_TIME=y ++CONFIG_DEBUG_FS=y ++CONFIG_DEBUG_PAGEALLOC=y ++CONFIG_SCHED_STACK_END_CHECK=y ++CONFIG_DEBUG_VM=y ++CONFIG_DEBUG_VM_PGFLAGS=y ++CONFIG_DEBUG_MEMORY_INIT=y ++CONFIG_DEBUG_PER_CPU_MAPS=y ++CONFIG_SOFTLOCKUP_DETECTOR=y ++CONFIG_WQ_WATCHDOG=y ++CONFIG_DEBUG_TIMEKEEPING=y ++CONFIG_DEBUG_RT_MUTEXES=y ++CONFIG_DEBUG_SPINLOCK=y ++CONFIG_DEBUG_MUTEXES=y ++CONFIG_DEBUG_RWSEMS=y ++CONFIG_DEBUG_LIST=y ++CONFIG_DEBUG_PLIST=y ++CONFIG_DEBUG_SG=y ++CONFIG_RCU_CPU_STALL_TIMEOUT=60 ++# CONFIG_RCU_TRACE is not set ++CONFIG_RCU_EQS_DEBUG=y ++# CONFIG_FTRACE is not set ++# CONFIG_RUNTIME_TESTING_MENU is not set ++CONFIG_MEMTEST=y diff --git a/target/linux/starfive/patches-6.6/0060-of-configfs-Add-configfs-function.patch b/target/linux/starfive/patches-6.6/0060-of-configfs-Add-configfs-function.patch new file mode 100644 index 0000000000..68deb70c9b --- /dev/null +++ b/target/linux/starfive/patches-6.6/0060-of-configfs-Add-configfs-function.patch @@ -0,0 +1,318 @@ +From 95c702022f5e4cb786719fcf90170334b1e562cc Mon Sep 17 00:00:00 2001 +From: Jianlong Huang <jianlong.huang@starfivetech.com> +Date: Thu, 16 Jun 2022 17:13:57 +0800 +Subject: [PATCH 060/116] of: configfs: Add configfs function + +Signed-off-by: Jianlong Huang <jianlong.huang@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/of/Kconfig | 7 ++ + drivers/of/Makefile | 1 + + drivers/of/configfs.c | 277 ++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 285 insertions(+) + create mode 100644 drivers/of/configfs.c + +--- a/drivers/of/Kconfig ++++ b/drivers/of/Kconfig +@@ -102,4 +102,11 @@ config OF_OVERLAY + config OF_NUMA + bool + ++config OF_CONFIGFS ++ bool "Device Tree Overlay ConfigFS interface" ++ select CONFIGFS_FS ++ select OF_OVERLAY ++ help ++ Enable a simple user-space driven DT overlay interface. ++ + endif # OF +--- a/drivers/of/Makefile ++++ b/drivers/of/Makefile +@@ -11,6 +11,7 @@ obj-$(CONFIG_OF_UNITTEST) += unittest.o + obj-$(CONFIG_OF_RESERVED_MEM) += of_reserved_mem.o + obj-$(CONFIG_OF_RESOLVE) += resolver.o + obj-$(CONFIG_OF_OVERLAY) += overlay.o ++obj-$(CONFIG_OF_CONFIGFS) += configfs.o + obj-$(CONFIG_OF_NUMA) += of_numa.o + + ifdef CONFIG_KEXEC_FILE +--- /dev/null ++++ b/drivers/of/configfs.c +@@ -0,0 +1,277 @@ ++/* ++ * Configfs entries for device-tree ++ * ++ * Copyright (C) 2013 - Pantelis Antoniou <panto@antoniou-consulting.com> ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++#include <linux/ctype.h> ++#include <linux/cpu.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/of_fdt.h> ++#include <linux/spinlock.h> ++#include <linux/slab.h> ++#include <linux/proc_fs.h> ++#include <linux/configfs.h> ++#include <linux/types.h> ++#include <linux/stat.h> ++#include <linux/limits.h> ++#include <linux/file.h> ++#include <linux/vmalloc.h> ++#include <linux/firmware.h> ++#include <linux/sizes.h> ++ ++#include "of_private.h" ++ ++struct cfs_overlay_item { ++ struct config_item item; ++ ++ char path[PATH_MAX]; ++ ++ const struct firmware *fw; ++ struct device_node *overlay; ++ int ov_id; ++ ++ void *dtbo; ++ int dtbo_size; ++}; ++ ++static inline struct cfs_overlay_item *to_cfs_overlay_item( ++ struct config_item *item) ++{ ++ return item ? container_of(item, struct cfs_overlay_item, item) : NULL; ++} ++ ++static ssize_t cfs_overlay_item_path_show(struct config_item *item, ++ char *page) ++{ ++ struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); ++ return sprintf(page, "%s\n", overlay->path); ++} ++ ++static ssize_t cfs_overlay_item_path_store(struct config_item *item, ++ const char *page, size_t count) ++{ ++ struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); ++ const char *p = page; ++ char *s; ++ int err; ++ ++ /* if it's set do not allow changes */ ++ if (overlay->path[0] != '\0' || overlay->dtbo_size > 0) ++ return -EPERM; ++ ++ /* copy to path buffer (and make sure it's always zero terminated */ ++ count = snprintf(overlay->path, sizeof(overlay->path) - 1, "%s", p); ++ overlay->path[sizeof(overlay->path) - 1] = '\0'; ++ ++ /* strip trailing newlines */ ++ s = overlay->path + strlen(overlay->path); ++ while (s > overlay->path && *--s == '\n') ++ *s = '\0'; ++ ++ pr_debug("%s: path is '%s'\n", __func__, overlay->path); ++ ++ err = request_firmware(&overlay->fw, overlay->path, NULL); ++ if (err != 0) ++ return err; ++ ++ err = of_overlay_fdt_apply((void *)overlay->fw->data, ++ (u32)overlay->fw->size, &overlay->ov_id, NULL); ++ if (err != 0) ++ goto out_err; ++ ++ return count; ++ ++out_err: ++ ++ release_firmware(overlay->fw); ++ overlay->fw = NULL; ++ ++ overlay->path[0] = '\0'; ++ return err; ++} ++ ++static ssize_t cfs_overlay_item_status_show(struct config_item *item, ++ char *page) ++{ ++ struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); ++ ++ return sprintf(page, "%s\n", ++ overlay->ov_id > 0 ? "applied" : "unapplied"); ++} ++ ++CONFIGFS_ATTR(cfs_overlay_item_, path); ++CONFIGFS_ATTR_RO(cfs_overlay_item_, status); ++ ++static struct configfs_attribute *cfs_overlay_attrs[] = { ++ &cfs_overlay_item_attr_path, ++ &cfs_overlay_item_attr_status, ++ NULL, ++}; ++ ++ssize_t cfs_overlay_item_dtbo_read(struct config_item *item, ++ void *buf, size_t max_count) ++{ ++ struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); ++ ++ pr_debug("%s: buf=%p max_count=%zu\n", __func__, ++ buf, max_count); ++ ++ if (overlay->dtbo == NULL) ++ return 0; ++ ++ /* copy if buffer provided */ ++ if (buf != NULL) { ++ /* the buffer must be large enough */ ++ if (overlay->dtbo_size > max_count) ++ return -ENOSPC; ++ ++ memcpy(buf, overlay->dtbo, overlay->dtbo_size); ++ } ++ ++ return overlay->dtbo_size; ++} ++ ++ssize_t cfs_overlay_item_dtbo_write(struct config_item *item, ++ const void *buf, size_t count) ++{ ++ struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); ++ int err; ++ ++ /* if it's set do not allow changes */ ++ if (overlay->path[0] != '\0' || overlay->dtbo_size > 0) ++ return -EPERM; ++ ++ /* copy the contents */ ++ overlay->dtbo = kmemdup(buf, count, GFP_KERNEL); ++ if (overlay->dtbo == NULL) ++ return -ENOMEM; ++ ++ overlay->dtbo_size = count; ++ ++ err = of_overlay_fdt_apply(overlay->dtbo, overlay->dtbo_size, ++ &overlay->ov_id, NULL); ++ if (err != 0) ++ goto out_err; ++ ++ return count; ++ ++out_err: ++ kfree(overlay->dtbo); ++ overlay->dtbo = NULL; ++ overlay->dtbo_size = 0; ++ overlay->ov_id = 0; ++ ++ return err; ++} ++ ++CONFIGFS_BIN_ATTR(cfs_overlay_item_, dtbo, NULL, SZ_1M); ++ ++static struct configfs_bin_attribute *cfs_overlay_bin_attrs[] = { ++ &cfs_overlay_item_attr_dtbo, ++ NULL, ++}; ++ ++static void cfs_overlay_release(struct config_item *item) ++{ ++ struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); ++ ++ if (overlay->ov_id > 0) ++ of_overlay_remove(&overlay->ov_id); ++ if (overlay->fw) ++ release_firmware(overlay->fw); ++ /* kfree with NULL is safe */ ++ kfree(overlay->dtbo); ++ kfree(overlay); ++} ++ ++static struct configfs_item_operations cfs_overlay_item_ops = { ++ .release = cfs_overlay_release, ++}; ++ ++static struct config_item_type cfs_overlay_type = { ++ .ct_item_ops = &cfs_overlay_item_ops, ++ .ct_attrs = cfs_overlay_attrs, ++ .ct_bin_attrs = cfs_overlay_bin_attrs, ++ .ct_owner = THIS_MODULE, ++}; ++ ++static struct config_item *cfs_overlay_group_make_item( ++ struct config_group *group, const char *name) ++{ ++ struct cfs_overlay_item *overlay; ++ ++ overlay = kzalloc(sizeof(*overlay), GFP_KERNEL); ++ if (!overlay) ++ return ERR_PTR(-ENOMEM); ++ ++ config_item_init_type_name(&overlay->item, name, &cfs_overlay_type); ++ return &overlay->item; ++} ++ ++static void cfs_overlay_group_drop_item(struct config_group *group, ++ struct config_item *item) ++{ ++ struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); ++ ++ config_item_put(&overlay->item); ++} ++ ++static struct configfs_group_operations overlays_ops = { ++ .make_item = cfs_overlay_group_make_item, ++ .drop_item = cfs_overlay_group_drop_item, ++}; ++ ++static struct config_item_type overlays_type = { ++ .ct_group_ops = &overlays_ops, ++ .ct_owner = THIS_MODULE, ++}; ++ ++static struct configfs_group_operations of_cfs_ops = { ++ /* empty - we don't allow anything to be created */ ++}; ++ ++static struct config_item_type of_cfs_type = { ++ .ct_group_ops = &of_cfs_ops, ++ .ct_owner = THIS_MODULE, ++}; ++ ++struct config_group of_cfs_overlay_group; ++ ++static struct configfs_subsystem of_cfs_subsys = { ++ .su_group = { ++ .cg_item = { ++ .ci_namebuf = "device-tree", ++ .ci_type = &of_cfs_type, ++ }, ++ }, ++ .su_mutex = __MUTEX_INITIALIZER(of_cfs_subsys.su_mutex), ++}; ++ ++static int __init of_cfs_init(void) ++{ ++ int ret; ++ ++ pr_info("%s\n", __func__); ++ ++ config_group_init(&of_cfs_subsys.su_group); ++ config_group_init_type_name(&of_cfs_overlay_group, "overlays", ++ &overlays_type); ++ configfs_add_default_group(&of_cfs_overlay_group, ++ &of_cfs_subsys.su_group); ++ ++ ret = configfs_register_subsystem(&of_cfs_subsys); ++ if (ret != 0) { ++ pr_err("%s: failed to register subsys\n", __func__); ++ goto out; ++ } ++ pr_info("%s: OK\n", __func__); ++out: ++ return ret; ++} ++late_initcall(of_cfs_init); diff --git a/target/linux/starfive/patches-6.6/0061-usr-Add-gen_initramfs_list.sh.patch b/target/linux/starfive/patches-6.6/0061-usr-Add-gen_initramfs_list.sh.patch new file mode 100644 index 0000000000..ade0f467a0 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0061-usr-Add-gen_initramfs_list.sh.patch @@ -0,0 +1,344 @@ +From 7891826f8c2de9ee0f6459cf969f7b082e29b154 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Thu, 1 Jun 2023 23:10:09 -0700 +Subject: [PATCH 061/116] usr: Add gen_initramfs_list.sh + +Add gen_initramfs_list.sh + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + usr/gen_initramfs_list.sh | 328 ++++++++++++++++++++++++++++++++++++++ + 1 file changed, 328 insertions(+) + create mode 100644 usr/gen_initramfs_list.sh + +--- /dev/null ++++ b/usr/gen_initramfs_list.sh +@@ -0,0 +1,328 @@ ++#!/bin/sh ++# Copyright (C) Martin Schlemmer <azarah@nosferatu.za.org> ++# Copyright (C) 2006 Sam Ravnborg <sam@ravnborg.org> ++# ++# Released under the terms of the GNU GPL ++# ++# Generate a cpio packed initramfs. It uses gen_init_cpio to generate ++# the cpio archive, and then compresses it. ++# The script may also be used to generate the inputfile used for gen_init_cpio ++# This script assumes that gen_init_cpio is located in usr/ directory ++ ++# error out on errors ++set -e ++ ++usage() { ++cat << EOF ++Usage: ++$0 [-o <file>] [-u <uid>] [-g <gid>] {-d | <cpio_source>} ... ++ -o <file> Create compressed initramfs file named <file> using ++ gen_init_cpio and compressor depending on the extension ++ -u <uid> User ID to map to user ID 0 (root). ++ <uid> is only meaningful if <cpio_source> is a ++ directory. "squash" forces all files to uid 0. ++ -g <gid> Group ID to map to group ID 0 (root). ++ <gid> is only meaningful if <cpio_source> is a ++ directory. "squash" forces all files to gid 0. ++ <cpio_source> File list or directory for cpio archive. ++ If <cpio_source> is a .cpio file it will be used ++ as direct input to initramfs. ++ -d Output the default cpio list. ++ ++All options except -o and -l may be repeated and are interpreted ++sequentially and immediately. -u and -g states are preserved across ++<cpio_source> options so an explicit "-u 0 -g 0" is required ++to reset the root/group mapping. ++EOF ++} ++ ++# awk style field access ++# $1 - field number; rest is argument string ++field() { ++ shift $1 ; echo $1 ++} ++ ++list_default_initramfs() { ++ # echo usr/kinit/kinit ++ : ++} ++ ++default_initramfs() { ++ cat <<-EOF >> ${output} ++ # This is a very simple, default initramfs ++ ++ dir /dev 0755 0 0 ++ nod /dev/console 0600 0 0 c 5 1 ++ dir /root 0700 0 0 ++ # file /kinit usr/kinit/kinit 0755 0 0 ++ # slink /init kinit 0755 0 0 ++ EOF ++} ++ ++filetype() { ++ local argv1="$1" ++ ++ # symlink test must come before file test ++ if [ -L "${argv1}" ]; then ++ echo "slink" ++ elif [ -f "${argv1}" ]; then ++ echo "file" ++ elif [ -d "${argv1}" ]; then ++ echo "dir" ++ elif [ -b "${argv1}" -o -c "${argv1}" ]; then ++ echo "nod" ++ elif [ -p "${argv1}" ]; then ++ echo "pipe" ++ elif [ -S "${argv1}" ]; then ++ echo "sock" ++ else ++ echo "invalid" ++ fi ++ return 0 ++} ++ ++list_print_mtime() { ++ : ++} ++ ++print_mtime() { ++ local my_mtime="0" ++ ++ if [ -e "$1" ]; then ++ my_mtime=$(find "$1" -printf "%T@\n" | sort -r | head -n 1) ++ fi ++ ++ echo "# Last modified: ${my_mtime}" >> ${output} ++ echo "" >> ${output} ++} ++ ++list_parse() { ++ if [ -L "$1" ]; then ++ return ++ fi ++ echo "$1" | sed 's/:/\\:/g; s/$/ \\/' ++} ++ ++# for each file print a line in following format ++# <filetype> <name> <path to file> <octal mode> <uid> <gid> ++# for links, devices etc the format differs. See gen_init_cpio for details ++parse() { ++ local location="$1" ++ local name="/${location#${srcdir}}" ++ # change '//' into '/' ++ name=$(echo "$name" | sed -e 's://*:/:g') ++ local mode="$2" ++ local uid="$3" ++ local gid="$4" ++ local ftype=$(filetype "${location}") ++ # remap uid/gid to 0 if necessary ++ [ "$root_uid" = "squash" ] && uid=0 || [ "$uid" -eq "$root_uid" ] && uid=0 ++ [ "$root_gid" = "squash" ] && gid=0 || [ "$gid" -eq "$root_gid" ] && gid=0 ++ local str="${mode} ${uid} ${gid}" ++ ++ [ "${ftype}" = "invalid" ] && return 0 ++ [ "${location}" = "${srcdir}" ] && return 0 ++ ++ case "${ftype}" in ++ "file") ++ str="${ftype} ${name} ${location} ${str}" ++ ;; ++ "nod") ++ local dev=`LC_ALL=C ls -l "${location}"` ++ local maj=`field 5 ${dev}` ++ local min=`field 6 ${dev}` ++ maj=${maj%,} ++ ++ [ -b "${location}" ] && dev="b" || dev="c" ++ ++ str="${ftype} ${name} ${str} ${dev} ${maj} ${min}" ++ ;; ++ "slink") ++ local target=`readlink "${location}"` ++ str="${ftype} ${name} ${target} ${str}" ++ ;; ++ *) ++ str="${ftype} ${name} ${str}" ++ ;; ++ esac ++ ++ echo "${str}" >> ${output} ++ ++ return 0 ++} ++ ++unknown_option() { ++ printf "ERROR: unknown option \"$arg\"\n" >&2 ++ printf "If the filename validly begins with '-', " >&2 ++ printf "then it must be prefixed\n" >&2 ++ printf "by './' so that it won't be interpreted as an option." >&2 ++ printf "\n" >&2 ++ usage >&2 ++ exit 1 ++} ++ ++list_header() { ++ : ++} ++ ++header() { ++ printf "\n#####################\n# $1\n" >> ${output} ++} ++ ++# process one directory (incl sub-directories) ++dir_filelist() { ++ ${dep_list}header "$1" ++ ++ srcdir=$(echo "$1" | sed -e 's://*:/:g') ++ dirlist=$(find "${srcdir}" -printf "%p %m %U %G\n" | LANG=C sort) ++ ++ # If $dirlist is only one line, then the directory is empty ++ if [ "$(echo "${dirlist}" | wc -l)" -gt 1 ]; then ++ ${dep_list}print_mtime "$1" ++ ++ echo "${dirlist}" | \ ++ while read x; do ++ ${dep_list}parse ${x} ++ done ++ fi ++} ++ ++# if only one file is specified and it is .cpio file then use it direct as fs ++# if a directory is specified then add all files in given direcotry to fs ++# if a regular file is specified assume it is in gen_initramfs format ++input_file() { ++ source="$1" ++ if [ -f "$1" ]; then ++ ${dep_list}header "$1" ++ is_cpio="$(echo "$1" | sed 's/^.*\.cpio\(\..*\)\{0,1\}/cpio/')" ++ if [ $2 -eq 0 -a ${is_cpio} = "cpio" ]; then ++ cpio_file=$1 ++ echo "$1" | grep -q '^.*\.cpio\..*' && is_cpio_compressed="compressed" ++ [ ! -z ${dep_list} ] && echo "$1" ++ return 0 ++ fi ++ if [ -z ${dep_list} ]; then ++ print_mtime "$1" >> ${output} ++ cat "$1" >> ${output} ++ else ++ echo "$1 \\" ++ cat "$1" | while read type dir file perm ; do ++ if [ "$type" = "file" ]; then ++ echo "$file \\"; ++ fi ++ done ++ fi ++ elif [ -d "$1" ]; then ++ dir_filelist "$1" ++ else ++ echo " ${prog}: Cannot open '$1'" >&2 ++ exit 1 ++ fi ++} ++ ++prog=$0 ++root_uid=0 ++root_gid=0 ++dep_list= ++cpio_file= ++cpio_list= ++output="/dev/stdout" ++output_file="" ++is_cpio_compressed= ++compr="gzip -n -9 -f" ++ ++arg="$1" ++case "$arg" in ++ "-l") # files included in initramfs - used by kbuild ++ dep_list="list_" ++ echo "deps_initramfs := $0 \\" ++ shift ++ ;; ++ "-o") # generate compressed cpio image named $1 ++ shift ++ output_file="$1" ++ cpio_list="$(mktemp ${TMPDIR:-/tmp}/cpiolist.XXXXXX)" ++ output=${cpio_list} ++ echo "$output_file" | grep -q "\.gz$" \ ++ && [ -x "`which gzip 2> /dev/null`" ] \ ++ && compr="gzip -n -9 -f" ++ echo "$output_file" | grep -q "\.bz2$" \ ++ && [ -x "`which bzip2 2> /dev/null`" ] \ ++ && compr="bzip2 -9 -f" ++ echo "$output_file" | grep -q "\.lzma$" \ ++ && [ -x "`which lzma 2> /dev/null`" ] \ ++ && compr="lzma -9 -f" ++ echo "$output_file" | grep -q "\.xz$" \ ++ && [ -x "`which xz 2> /dev/null`" ] \ ++ && compr="xz --check=crc32 --lzma2=dict=1MiB" ++ echo "$output_file" | grep -q "\.lzo$" \ ++ && [ -x "`which lzop 2> /dev/null`" ] \ ++ && compr="lzop -9 -f" ++ echo "$output_file" | grep -q "\.lz4$" \ ++ && [ -x "`which lz4 2> /dev/null`" ] \ ++ && compr="lz4 -l -9 -f" ++ echo "$output_file" | grep -q "\.cpio$" && compr="cat" ++ shift ++ ;; ++esac ++while [ $# -gt 0 ]; do ++ arg="$1" ++ shift ++ case "$arg" in ++ "-u") # map $1 to uid=0 (root) ++ root_uid="$1" ++ [ "$root_uid" = "-1" ] && root_uid=$(id -u || echo 0) ++ shift ++ ;; ++ "-g") # map $1 to gid=0 (root) ++ root_gid="$1" ++ [ "$root_gid" = "-1" ] && root_gid=$(id -g || echo 0) ++ shift ++ ;; ++ "-d") # display default initramfs list ++ default_list="$arg" ++ ${dep_list}default_initramfs ++ ;; ++ "-h") ++ usage ++ exit 0 ++ ;; ++ *) ++ case "$arg" in ++ "-"*) ++ unknown_option ++ ;; ++ *) # input file/dir - process it ++ input_file "$arg" "$#" ++ ;; ++ esac ++ ;; ++ esac ++done ++ ++# If output_file is set we will generate cpio archive and compress it ++# we are careful to delete tmp files ++if [ ! -z ${output_file} ]; then ++ if [ -z ${cpio_file} ]; then ++ timestamp= ++ if test -n "$KBUILD_BUILD_TIMESTAMP"; then ++ timestamp="$(date -d"$KBUILD_BUILD_TIMESTAMP" +%s || :)" ++ if test -n "$timestamp"; then ++ timestamp="-t $timestamp" ++ fi ++ fi ++ cpio_tfile="$(mktemp ${TMPDIR:-/tmp}/cpiofile.XXXXXX)" ++ usr/gen_init_cpio $timestamp ${cpio_list} > ${cpio_tfile} ++ else ++ cpio_tfile=${cpio_file} ++ fi ++ rm ${cpio_list} ++ if [ "${is_cpio_compressed}" = "compressed" ]; then ++ cat ${cpio_tfile} > ${output_file} ++ else ++ (cat ${cpio_tfile} | ${compr} - > ${output_file}) \ ++ || (rm -f ${output_file} ; false) ++ fi ++ [ -z ${cpio_file} ] && rm ${cpio_tfile} ++fi ++exit 0 diff --git a/target/linux/starfive/patches-6.6/0062-i2c-designware-Delete-SMBus-functionalities.patch b/target/linux/starfive/patches-6.6/0062-i2c-designware-Delete-SMBus-functionalities.patch new file mode 100644 index 0000000000..05561f1c51 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0062-i2c-designware-Delete-SMBus-functionalities.patch @@ -0,0 +1,33 @@ +From dcc2827ed6e701a65731c05b0297745559837217 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Fri, 12 May 2023 17:33:20 +0800 +Subject: [PATCH 062/116] i2c: designware: Delete SMBus functionalities + +The driver didn't implement the smbus interface, +so replace the SMBus functionalities with +I2C_FUNC_SMBUS_EMUL. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/i2c/busses/i2c-designware-core.h | 10 ++++------ + 1 file changed, 4 insertions(+), 6 deletions(-) + +--- a/drivers/i2c/busses/i2c-designware-core.h ++++ b/drivers/i2c/busses/i2c-designware-core.h +@@ -18,12 +18,10 @@ + #include <linux/regmap.h> + #include <linux/types.h> + +-#define DW_IC_DEFAULT_FUNCTIONALITY (I2C_FUNC_I2C | \ +- I2C_FUNC_SMBUS_BYTE | \ +- I2C_FUNC_SMBUS_BYTE_DATA | \ +- I2C_FUNC_SMBUS_WORD_DATA | \ +- I2C_FUNC_SMBUS_BLOCK_DATA | \ +- I2C_FUNC_SMBUS_I2C_BLOCK) ++#define DW_IC_DEFAULT_FUNCTIONALITY (I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL \ ++ & ~I2C_FUNC_SMBUS_QUICK \ ++ & ~I2C_FUNC_SMBUS_PROC_CALL \ ++ & ~I2C_FUNC_SMBUS_PEC)) + + #define DW_IC_CON_MASTER BIT(0) + #define DW_IC_CON_SPEED_STD (1 << 1) diff --git a/target/linux/starfive/patches-6.6/0063-drivers-mtd-gigadevice-add-gd25lq256d-32M-flash-supp.patch b/target/linux/starfive/patches-6.6/0063-drivers-mtd-gigadevice-add-gd25lq256d-32M-flash-supp.patch new file mode 100644 index 0000000000..43a7e5ba0d --- /dev/null +++ b/target/linux/starfive/patches-6.6/0063-drivers-mtd-gigadevice-add-gd25lq256d-32M-flash-supp.patch @@ -0,0 +1,26 @@ +From b61cefc6c785aa8a7177a0b535db746fd0047bd8 Mon Sep 17 00:00:00 2001 +From: Ziv Xu <ziv.xu@starfivetech.com> +Date: Fri, 19 Jan 2024 15:22:55 +0800 +Subject: [PATCH 063/116] drivers: mtd: gigadevice: add gd25lq256d 32M flash + support + +add gd25lq256d 32M flash support + +Signed-off-by: Ziv Xu <ziv.xu@starfivetech.com> +--- + drivers/mtd/spi-nor/gigadevice.c | 4 ++++ + 1 file changed, 4 insertions(+) + +--- a/drivers/mtd/spi-nor/gigadevice.c ++++ b/drivers/mtd/spi-nor/gigadevice.c +@@ -66,6 +66,10 @@ static const struct flash_info gigadevic + FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ | + SPI_NOR_QUAD_READ) }, ++ { "gd25lq256d", INFO(0xc86019, 0, 64 * 1024, 512) ++ FLAGS( SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_QUAD_PP) ++ NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ | ++ SPI_NOR_QUAD_READ) }, + { "gd25q256", INFO(0xc84019, 0, 64 * 1024, 512) + PARSE_SFDP + FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6) diff --git a/target/linux/starfive/patches-6.6/0064-driver-mailbox-Add-mailbox-driver.patch b/target/linux/starfive/patches-6.6/0064-driver-mailbox-Add-mailbox-driver.patch new file mode 100644 index 0000000000..b733110f7d --- /dev/null +++ b/target/linux/starfive/patches-6.6/0064-driver-mailbox-Add-mailbox-driver.patch @@ -0,0 +1,808 @@ +From 76bc13aa12bd111f5da01e107f8d487b20b5a40c Mon Sep 17 00:00:00 2001 +From: "shanlong.li" <shanlong.li@starfivetech.com> +Date: Thu, 8 Jun 2023 00:07:15 -0700 +Subject: [PATCH 064/116] driver: mailbox: Add mailbox driver + +Add mailbox driver. + +Signed-off-by: shanlong.li <shanlong.li@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/mailbox/Kconfig | 13 + + drivers/mailbox/Makefile | 4 + + drivers/mailbox/starfive_mailbox-test.c | 407 ++++++++++++++++++++++++ + drivers/mailbox/starfive_mailbox.c | 347 ++++++++++++++++++++ + 4 files changed, 771 insertions(+) + create mode 100644 drivers/mailbox/starfive_mailbox-test.c + create mode 100644 drivers/mailbox/starfive_mailbox.c + +--- a/drivers/mailbox/Kconfig ++++ b/drivers/mailbox/Kconfig +@@ -295,4 +295,17 @@ config QCOM_IPCC + acts as an interrupt controller for receiving interrupts from clients. + Say Y here if you want to build this driver. + ++config STARFIVE_MBOX ++ tristate "Platform Starfive Mailbox" ++ depends on OF ++ help ++ Say Y here if you want to build a platform specific variant RISCV ++ controller driver. ++ ++config STARFIVE_MBOX_TEST ++ tristate "Starfive Mailbox Test Client" ++ depends on OF ++ depends on HAS_IOMEM ++ help ++ Test client to help with testing new Controller driver implementations. + endif +--- a/drivers/mailbox/Makefile ++++ b/drivers/mailbox/Makefile +@@ -62,3 +62,7 @@ obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox + obj-$(CONFIG_QCOM_IPCC) += qcom-ipcc.o + + obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o ++ ++obj-$(CONFIG_STARFIVE_MBOX) += starfive_mailbox.o ++ ++obj-$(CONFIG_STARFIVE_MBOX_TEST) += starfive_mailbox-test.o +--- /dev/null ++++ b/drivers/mailbox/starfive_mailbox-test.c +@@ -0,0 +1,407 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2015 ST Microelectronics ++ * ++ * Author: Lee Jones <lee.jones@linaro.org> ++ */ ++ ++#include <linux/debugfs.h> ++#include <linux/err.h> ++#include <linux/fs.h> ++#include <linux/io.h> ++#include <linux/kernel.h> ++#include <linux/mailbox_client.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/platform_device.h> ++#include <linux/poll.h> ++#include <linux/slab.h> ++#include <linux/uaccess.h> ++#include <linux/sched/signal.h> ++ ++#include <linux/mailbox_controller.h> ++ ++#define MBOX_MAX_SIG_LEN 8 ++#define MBOX_MAX_MSG_LEN 16 ++#define MBOX_BYTES_PER_LINE 16 ++#define MBOX_HEXDUMP_LINE_LEN ((MBOX_BYTES_PER_LINE * 4) + 2) ++#define MBOX_HEXDUMP_MAX_LEN (MBOX_HEXDUMP_LINE_LEN * (MBOX_MAX_MSG_LEN / MBOX_BYTES_PER_LINE)) ++ ++static bool mbox_data_ready; ++ ++struct mbox_test_device { ++ struct device *dev; ++ void __iomem *tx_mmio; ++ void __iomem *rx_mmio; ++ struct mbox_chan *tx_channel; ++ struct mbox_chan *rx_channel; ++ char *rx_buffer; ++ char *signal; ++ char *message; ++ spinlock_t lock; ++ wait_queue_head_t waitq; ++ struct fasync_struct *async_queue; ++ struct dentry *root_debugfs_dir; ++}; ++ ++static ssize_t mbox_test_signal_write(struct file *filp, ++ const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mbox_test_device *tdev = filp->private_data; ++ ++ if (!tdev->tx_channel) { ++ dev_err(tdev->dev, "Channel cannot do Tx\n"); ++ return -EINVAL; ++ } ++ ++ if (count > MBOX_MAX_SIG_LEN) { ++ dev_err(tdev->dev, ++ "Signal length %zd greater than max allowed %d\n", ++ count, MBOX_MAX_SIG_LEN); ++ return -EINVAL; ++ } ++ ++ /* Only allocate memory if we need to */ ++ if (!tdev->signal) { ++ tdev->signal = kzalloc(MBOX_MAX_SIG_LEN, GFP_KERNEL); ++ if (!tdev->signal) ++ return -ENOMEM; ++ } ++ ++ if (copy_from_user(tdev->signal, userbuf, count)) { ++ kfree(tdev->signal); ++ tdev->signal = NULL; ++ return -EFAULT; ++ } ++ ++ return count; ++} ++ ++static const struct file_operations mbox_test_signal_ops = { ++ .write = mbox_test_signal_write, ++ .open = simple_open, ++ .llseek = generic_file_llseek, ++}; ++ ++static int mbox_test_message_fasync(int fd, struct file *filp, int on) ++{ ++ struct mbox_test_device *tdev = filp->private_data; ++ ++ return fasync_helper(fd, filp, on, &tdev->async_queue); ++} ++ ++static ssize_t mbox_test_message_write(struct file *filp, ++ const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mbox_test_device *tdev = filp->private_data; ++ void *data; ++ int ret; ++ ++ if (!tdev->tx_channel) { ++ dev_err(tdev->dev, "Channel cannot do Tx\n"); ++ return -EINVAL; ++ } ++ ++ if (count > MBOX_MAX_MSG_LEN) { ++ dev_err(tdev->dev, ++ "Message length %zd greater than max allowed %d\n", ++ count, MBOX_MAX_MSG_LEN); ++ return -EINVAL; ++ } ++ ++ tdev->message = kzalloc(MBOX_MAX_MSG_LEN, GFP_KERNEL); ++ if (!tdev->message) ++ return -ENOMEM; ++ ++ ret = copy_from_user(tdev->message, userbuf, count); ++ if (ret) { ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ if (tdev->tx_mmio && tdev->signal) { ++ print_hex_dump_bytes("Client: Sending: Signal: ", DUMP_PREFIX_ADDRESS, ++ tdev->signal, MBOX_MAX_SIG_LEN); ++ ++ data = tdev->signal; ++ } else ++ data = tdev->message; ++ ++ print_hex_dump_bytes("Client: Sending: Message: ", DUMP_PREFIX_ADDRESS, ++ tdev->message, MBOX_MAX_MSG_LEN); ++ ++ ret = mbox_send_message(tdev->tx_channel, data); ++ mbox_chan_txdone(tdev->tx_channel, ret); ++ if (ret < 0) ++ dev_err(tdev->dev, "Failed to send message via mailbox\n"); ++ ++out: ++ kfree(tdev->signal); ++ kfree(tdev->message); ++ tdev->signal = NULL; ++ ++ return ret < 0 ? ret : count; ++} ++ ++static bool mbox_test_message_data_ready(struct mbox_test_device *tdev) ++{ ++ bool data_ready; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&tdev->lock, flags); ++ data_ready = mbox_data_ready; ++ spin_unlock_irqrestore(&tdev->lock, flags); ++ ++ return data_ready; ++} ++ ++static ssize_t mbox_test_message_read(struct file *filp, char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct mbox_test_device *tdev = filp->private_data; ++ unsigned long flags; ++ char *touser, *ptr; ++ int ret; ++ ++ touser = kzalloc(MBOX_HEXDUMP_MAX_LEN + 1, GFP_KERNEL); ++ if (!touser) ++ return -ENOMEM; ++ ++ if (!tdev->rx_channel) { ++ ret = snprintf(touser, 20, "<NO RX CAPABILITY>\n"); ++ ret = simple_read_from_buffer(userbuf, count, ppos, ++ touser, ret); ++ goto kfree_err; ++ } ++ ++ do { ++ if (mbox_test_message_data_ready(tdev)) ++ break; ++ ++ if (filp->f_flags & O_NONBLOCK) { ++ ret = -EAGAIN; ++ goto waitq_err; ++ } ++ ++ if (signal_pending(current)) { ++ ret = -ERESTARTSYS; ++ goto waitq_err; ++ } ++ schedule(); ++ ++ } while (1); ++ ++ spin_lock_irqsave(&tdev->lock, flags); ++ ++ ptr = tdev->rx_buffer; ++ ++ mbox_data_ready = false; ++ ++ spin_unlock_irqrestore(&tdev->lock, flags); ++ if (copy_to_user((void __user *)userbuf, ptr, 4)) ++ ret = -EFAULT; ++ ++waitq_err: ++ __set_current_state(TASK_RUNNING); ++kfree_err: ++ kfree(touser); ++ return ret; ++} ++ ++static __poll_t ++mbox_test_message_poll(struct file *filp, struct poll_table_struct *wait) ++{ ++ struct mbox_test_device *tdev = filp->private_data; ++ ++ poll_wait(filp, &tdev->waitq, wait); ++ ++ if (mbox_test_message_data_ready(tdev)) ++ return EPOLLIN | EPOLLRDNORM; ++ return 0; ++} ++ ++static const struct file_operations mbox_test_message_ops = { ++ .write = mbox_test_message_write, ++ .read = mbox_test_message_read, ++ .fasync = mbox_test_message_fasync, ++ .poll = mbox_test_message_poll, ++ .open = simple_open, ++ .llseek = generic_file_llseek, ++}; ++ ++static int mbox_test_add_debugfs(struct platform_device *pdev, ++ struct mbox_test_device *tdev) ++{ ++ if (!debugfs_initialized()) ++ return 0; ++ ++ tdev->root_debugfs_dir = debugfs_create_dir(dev_name(&pdev->dev), NULL); ++ if (!tdev->root_debugfs_dir) { ++ dev_err(&pdev->dev, "Failed to create Mailbox debugfs\n"); ++ return -EINVAL; ++ } ++ ++ debugfs_create_file("message", 0600, tdev->root_debugfs_dir, ++ tdev, &mbox_test_message_ops); ++ ++ debugfs_create_file("signal", 0200, tdev->root_debugfs_dir, ++ tdev, &mbox_test_signal_ops); ++ ++ return 0; ++} ++ ++static void mbox_test_receive_message(struct mbox_client *client, void *message) ++{ ++ struct mbox_test_device *tdev = dev_get_drvdata(client->dev); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&tdev->lock, flags); ++ if (tdev->rx_mmio) { ++ memcpy_fromio(tdev->rx_buffer, tdev->rx_mmio, MBOX_MAX_MSG_LEN); ++ print_hex_dump_bytes("Client: Received [MMIO]: ", DUMP_PREFIX_ADDRESS, ++ tdev->rx_buffer, MBOX_MAX_MSG_LEN); ++ } else if (message) { ++ print_hex_dump_bytes("Client: Received [API]: ", DUMP_PREFIX_ADDRESS, ++ message, MBOX_MAX_MSG_LEN); ++ memcpy(tdev->rx_buffer, message, MBOX_MAX_MSG_LEN); ++ } ++ mbox_data_ready = true; ++ spin_unlock_irqrestore(&tdev->lock, flags); ++} ++ ++static void mbox_test_prepare_message(struct mbox_client *client, void *message) ++{ ++ struct mbox_test_device *tdev = dev_get_drvdata(client->dev); ++ ++ if (tdev->tx_mmio) { ++ if (tdev->signal) ++ memcpy_toio(tdev->tx_mmio, tdev->message, MBOX_MAX_MSG_LEN); ++ else ++ memcpy_toio(tdev->tx_mmio, message, MBOX_MAX_MSG_LEN); ++ } ++} ++ ++static struct mbox_chan * ++mbox_test_request_channel(struct platform_device *pdev, const char *name) ++{ ++ struct mbox_client *client; ++ struct mbox_chan *channel; ++ ++ client = devm_kzalloc(&pdev->dev, sizeof(*client), GFP_KERNEL); ++ if (!client) ++ return ERR_PTR(-ENOMEM); ++ ++ client->dev = &pdev->dev; ++ client->rx_callback = mbox_test_receive_message; ++ client->tx_prepare = mbox_test_prepare_message; ++ client->tx_block = false; ++ client->knows_txdone = false; ++ client->tx_tout = 500; ++ ++ channel = mbox_request_channel_byname(client, name); ++ if (IS_ERR(channel)) { ++ dev_warn(&pdev->dev, "Failed to request %s channel\n", name); ++ return NULL; ++ } ++ ++ return channel; ++} ++ ++static int mbox_test_probe(struct platform_device *pdev) ++{ ++ struct mbox_test_device *tdev; ++ struct resource *res; ++ resource_size_t size; ++ int ret; ++ ++ tdev = devm_kzalloc(&pdev->dev, sizeof(*tdev), GFP_KERNEL); ++ if (!tdev) ++ return -ENOMEM; ++ ++ /* It's okay for MMIO to be NULL */ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ tdev->tx_mmio = devm_ioremap_resource(&pdev->dev, res); ++ if (PTR_ERR(tdev->tx_mmio) == -EBUSY) { ++ /* if reserved area in SRAM, try just ioremap */ ++ size = resource_size(res); ++ tdev->tx_mmio = devm_ioremap(&pdev->dev, res->start, size); ++ } else if (IS_ERR(tdev->tx_mmio)) { ++ tdev->tx_mmio = NULL; ++ } ++ ++ /* If specified, second reg entry is Rx MMIO */ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 1); ++ tdev->rx_mmio = devm_ioremap_resource(&pdev->dev, res); ++ if (PTR_ERR(tdev->rx_mmio) == -EBUSY) { ++ size = resource_size(res); ++ tdev->rx_mmio = devm_ioremap(&pdev->dev, res->start, size); ++ } else if (IS_ERR(tdev->rx_mmio)) { ++ tdev->rx_mmio = tdev->tx_mmio; ++ } ++ ++ tdev->tx_channel = mbox_test_request_channel(pdev, "tx"); ++ tdev->rx_channel = mbox_test_request_channel(pdev, "rx"); ++ ++ if (!tdev->tx_channel && !tdev->rx_channel) ++ return -EPROBE_DEFER; ++ ++ /* If Rx is not specified but has Rx MMIO, then Rx = Tx */ ++ if (!tdev->rx_channel && (tdev->rx_mmio != tdev->tx_mmio)) ++ tdev->rx_channel = tdev->tx_channel; ++ ++ tdev->dev = &pdev->dev; ++ platform_set_drvdata(pdev, tdev); ++ ++ spin_lock_init(&tdev->lock); ++ ++ if (tdev->rx_channel) { ++ tdev->rx_buffer = devm_kzalloc(&pdev->dev, ++ MBOX_MAX_MSG_LEN, GFP_KERNEL); ++ if (!tdev->rx_buffer) ++ return -ENOMEM; ++ } ++ ++ ret = mbox_test_add_debugfs(pdev, tdev); ++ if (ret) ++ return ret; ++ ++ dev_info(&pdev->dev, "Successfully registered\n"); ++ ++ return 0; ++} ++ ++static int mbox_test_remove(struct platform_device *pdev) ++{ ++ struct mbox_test_device *tdev = platform_get_drvdata(pdev); ++ ++ debugfs_remove_recursive(tdev->root_debugfs_dir); ++ ++ if (tdev->tx_channel) ++ mbox_free_channel(tdev->tx_channel); ++ if (tdev->rx_channel) ++ mbox_free_channel(tdev->rx_channel); ++ ++ return 0; ++} ++ ++static const struct of_device_id mbox_test_match[] = { ++ { .compatible = "starfive,mailbox-test" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, mbox_test_match); ++ ++static struct platform_driver mbox_test_driver = { ++ .driver = { ++ .name = "mailbox_test", ++ .of_match_table = mbox_test_match, ++ }, ++ .probe = mbox_test_probe, ++ .remove = mbox_test_remove, ++}; ++module_platform_driver(mbox_test_driver); ++ ++MODULE_DESCRIPTION("Generic Mailbox Testing Facility"); ++MODULE_AUTHOR("Lee Jones <lee.jones@linaro.org"); ++MODULE_LICENSE("GPL v2"); +--- /dev/null ++++ b/drivers/mailbox/starfive_mailbox.c +@@ -0,0 +1,347 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * mailbox driver for StarFive JH7110 SoC ++ * ++ * Copyright (c) 2021 StarFive Technology Co., Ltd. ++ * Author: Shanlong Li <shanlong.li@starfivetech.com> ++ */ ++ ++#include <linux/bitops.h> ++#include <linux/delay.h> ++#include <linux/device.h> ++#include <linux/err.h> ++#include <linux/interrupt.h> ++#include <linux/io.h> ++#include <linux/iopoll.h> ++#include <linux/mailbox_controller.h> ++#include <linux/module.h> ++#include <linux/platform_device.h> ++#include <linux/slab.h> ++#include <linux/clk.h> ++#include <linux/reset.h> ++#include <linux/pm_runtime.h> ++ ++#include "mailbox.h" ++ ++#define MBOX_CHAN_MAX 4 ++ ++#define MBOX_BASE(mbox, ch) ((mbox)->base + ((ch) * 0x10)) ++#define MBOX_IRQ_REG 0x00 ++#define MBOX_SET_REG 0x04 ++#define MBOX_CLR_REG 0x08 ++#define MBOX_CMD_REG 0x0c ++#define MBC_PEND_SMRY 0x100 ++ ++typedef enum { ++ MAILBOX_CORE_U7 = 0, ++ MAILBOX_CORE_HIFI4, ++ MAILBOX_CORE_E2, ++ MAILBOX_CORE_RSVD0, ++ MAILBOX_CORE_NUM, ++} mailbox_core_t; ++ ++struct mailbox_irq_name_c{ ++ int id; ++ char name[16]; ++}; ++ ++static const struct mailbox_irq_name_c irq_peer_name[MBOX_CHAN_MAX] = { ++ {MAILBOX_CORE_U7, "u74_core"}, ++ {MAILBOX_CORE_HIFI4, "hifi4_core"}, ++ {MAILBOX_CORE_E2, "e24_core"}, ++ {MAILBOX_CORE_RSVD0, "" }, ++}; ++ ++/** ++ * starfive mailbox channel information ++ * ++ * A channel can be used for TX or RX, it can trigger remote ++ * processor interrupt to notify remote processor and can receive ++ * interrupt if has incoming message. ++ * ++ * @dst_irq: Interrupt vector for remote processor ++ * @core_id: id for remote processor ++ */ ++struct starfive_chan_info { ++ unsigned int dst_irq; ++ mailbox_core_t core_id; ++}; ++ ++/** ++ * starfive mailbox controller data ++ * ++ * Mailbox controller includes 4 channels and can allocate ++ * channel for message transferring. ++ * ++ * @dev: Device to which it is attached ++ * @base: Base address of the register mapping region ++ * @chan: Representation of channels in mailbox controller ++ * @mchan: Representation of channel info ++ * @controller: Representation of a communication channel controller ++ */ ++struct starfive_mbox { ++ struct device *dev; ++ void __iomem *base; ++ struct mbox_chan chan[MBOX_CHAN_MAX]; ++ struct starfive_chan_info mchan[MBOX_CHAN_MAX]; ++ struct mbox_controller controller; ++ struct clk *clk; ++ struct reset_control *rst_rresetn; ++}; ++ ++static struct starfive_mbox *to_starfive_mbox(struct mbox_controller *mbox) ++{ ++ return container_of(mbox, struct starfive_mbox, controller); ++} ++ ++static struct mbox_chan * ++starfive_of_mbox_index_xlate(struct mbox_controller *mbox, ++ const struct of_phandle_args *sp) ++{ ++ struct starfive_mbox *sbox; ++ ++ int ind = sp->args[0]; ++ int core_id = sp->args[1]; ++ ++ if (ind >= mbox->num_chans || core_id >= MAILBOX_CORE_NUM) ++ return ERR_PTR(-EINVAL); ++ ++ sbox = to_starfive_mbox(mbox); ++ ++ sbox->mchan[ind].core_id = core_id; ++ ++ return &mbox->chans[ind]; ++} ++ ++static irqreturn_t starfive_rx_irq_handler(int irq, void *p) ++{ ++ struct mbox_chan *chan = p; ++ unsigned long ch = (unsigned long)chan->con_priv; ++ struct starfive_mbox *mbox = to_starfive_mbox(chan->mbox); ++ void __iomem *base = MBOX_BASE(mbox, ch); ++ u32 val; ++ ++ val = readl(base + MBOX_CMD_REG); ++ if (!val) ++ return IRQ_NONE; ++ ++ mbox_chan_received_data(chan, (void *)&val); ++ writel(val, base + MBOX_CLR_REG); ++ return IRQ_HANDLED; ++} ++ ++static int starfive_mbox_check_state(struct mbox_chan *chan) ++{ ++ unsigned long ch = (unsigned long)chan->con_priv; ++ struct starfive_mbox *mbox = to_starfive_mbox(chan->mbox); ++ unsigned long irq_flag = IRQF_SHARED; ++ long ret = 0; ++ ++ pm_runtime_get_sync(mbox->dev); ++ /* MAILBOX should be with IRQF_NO_SUSPEND set */ ++ if (!mbox->dev->pm_domain) ++ irq_flag |= IRQF_NO_SUSPEND; ++ ++ /* Mailbox is idle so directly bail out */ ++ if (readl(mbox->base + MBC_PEND_SMRY) & BIT(ch)) ++ return -EBUSY; ++ ++ if (mbox->mchan[ch].dst_irq > 0) { ++ dev_dbg(mbox->dev, "%s: host IRQ = %d, ch:%ld", __func__, mbox->mchan[ch].dst_irq, ch); ++ ret = devm_request_irq(mbox->dev, mbox->mchan[ch].dst_irq, starfive_rx_irq_handler, ++ irq_flag, irq_peer_name[ch].name, chan); ++ if (ret < 0) ++ dev_err(mbox->dev, "request_irq %d failed\n", mbox->mchan[ch].dst_irq); ++ } ++ ++ return ret; ++} ++ ++static int starfive_mbox_startup(struct mbox_chan *chan) ++{ ++ return starfive_mbox_check_state(chan); ++} ++ ++static void starfive_mbox_shutdown(struct mbox_chan *chan) ++{ ++ struct starfive_mbox *mbox = to_starfive_mbox(chan->mbox); ++ unsigned long ch = (unsigned long)chan->con_priv; ++ void __iomem *base = MBOX_BASE(mbox, ch); ++ ++ writel(0x0, base + MBOX_IRQ_REG); ++ writel(0x0, base + MBOX_CLR_REG); ++ ++ if (mbox->mchan[ch].dst_irq > 0) ++ devm_free_irq(mbox->dev, mbox->mchan[ch].dst_irq, chan); ++ pm_runtime_put_sync(mbox->dev); ++} ++ ++static int starfive_mbox_send_data(struct mbox_chan *chan, void *msg) ++{ ++ unsigned long ch = (unsigned long)chan->con_priv; ++ struct starfive_mbox *mbox = to_starfive_mbox(chan->mbox); ++ struct starfive_chan_info *mchan = &mbox->mchan[ch]; ++ void __iomem *base = MBOX_BASE(mbox, ch); ++ u32 *buf = msg; ++ ++ /* Ensure channel is released */ ++ if (readl(mbox->base + MBC_PEND_SMRY) & BIT(ch)) { ++ pr_debug("%s:%d. busy\n", __func__, __LINE__); ++ return -EBUSY; ++ } ++ ++ /* Clear mask for destination interrupt */ ++ writel(BIT(mchan->core_id), base + MBOX_IRQ_REG); ++ ++ /* Fill message data */ ++ writel(*buf, base + MBOX_SET_REG); ++ return 0; ++} ++ ++static struct mbox_chan_ops starfive_mbox_ops = { ++ .startup = starfive_mbox_startup, ++ .send_data = starfive_mbox_send_data, ++ .shutdown = starfive_mbox_shutdown, ++}; ++ ++static const struct of_device_id starfive_mbox_of_match[] = { ++ { .compatible = "starfive,mail_box",}, ++ {}, ++}; ++ ++MODULE_DEVICE_TABLE(of, starfive_mbox_of_match); ++ ++void starfive_mailbox_init(struct starfive_mbox *mbox) ++{ ++ mbox->clk = devm_clk_get_optional(mbox->dev, "clk_apb"); ++ if (IS_ERR(mbox->clk)) { ++ dev_err(mbox->dev, "failed to get mailbox\n"); ++ return; ++ } ++ ++ mbox->rst_rresetn = devm_reset_control_get_exclusive(mbox->dev, "mbx_rre"); ++ if (IS_ERR(mbox->rst_rresetn)) { ++ dev_err(mbox->dev, "failed to get mailbox reset\n"); ++ return; ++ } ++ ++ clk_prepare_enable(mbox->clk); ++ reset_control_deassert(mbox->rst_rresetn); ++} ++ ++static int starfive_mbox_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct starfive_mbox *mbox; ++ struct mbox_chan *chan; ++ struct resource *res; ++ unsigned long ch; ++ int err; ++ ++ mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); ++ if (!mbox) ++ return -ENOMEM; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ mbox->base = devm_ioremap_resource(dev, res); ++ mbox->dev = dev; ++ ++ if (IS_ERR(mbox->base)) ++ return PTR_ERR(mbox->base); ++ ++ starfive_mailbox_init(mbox); ++ ++ mbox->controller.dev = dev; ++ mbox->controller.chans = mbox->chan; ++ mbox->controller.num_chans = MBOX_CHAN_MAX; ++ mbox->controller.ops = &starfive_mbox_ops; ++ mbox->controller.of_xlate = starfive_of_mbox_index_xlate; ++ mbox->controller.txdone_irq = true; ++ mbox->controller.txdone_poll = false; ++ ++ /* Initialize mailbox channel data */ ++ chan = mbox->chan; ++ for (ch = 0; ch < MBOX_CHAN_MAX; ch++) { ++ mbox->mchan[ch].dst_irq = 0; ++ mbox->mchan[ch].core_id = (mailbox_core_t)ch; ++ chan[ch].con_priv = (void *)ch; ++ } ++ mbox->mchan[MAILBOX_CORE_HIFI4].dst_irq = platform_get_irq(pdev, 0); ++ mbox->mchan[MAILBOX_CORE_E2].dst_irq = platform_get_irq(pdev, 1); ++ ++ err = mbox_controller_register(&mbox->controller); ++ if (err) { ++ dev_err(dev, "Failed to register mailbox %d\n", err); ++ return err; ++ } ++ ++ platform_set_drvdata(pdev, mbox); ++ dev_info(dev, "Mailbox enabled\n"); ++ pm_runtime_set_active(dev); ++ pm_runtime_enable(dev); ++ ++ return 0; ++} ++ ++static int starfive_mbox_remove(struct platform_device *pdev) ++{ ++ struct starfive_mbox *mbox = platform_get_drvdata(pdev); ++ ++ mbox_controller_unregister(&mbox->controller); ++ devm_clk_put(mbox->dev, mbox->clk); ++ pm_runtime_disable(mbox->dev); ++ ++ return 0; ++} ++ ++static int __maybe_unused starfive_mbox_suspend(struct device *dev) ++{ ++ struct starfive_mbox *mbox = dev_get_drvdata(dev); ++ ++ clk_disable_unprepare(mbox->clk); ++ ++ return 0; ++} ++ ++static int __maybe_unused starfive_mbox_resume(struct device *dev) ++{ ++ struct starfive_mbox *mbox = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = clk_prepare_enable(mbox->clk); ++ if (ret) ++ dev_err(dev, "failed to enable clock\n"); ++ ++ return ret; ++} ++ ++static const struct dev_pm_ops starfive_mbox_pm_ops = { ++ .suspend = starfive_mbox_suspend, ++ .resume = starfive_mbox_resume, ++ SET_RUNTIME_PM_OPS(starfive_mbox_suspend, starfive_mbox_resume, NULL) ++}; ++static struct platform_driver starfive_mbox_driver = { ++ .probe = starfive_mbox_probe, ++ .remove = starfive_mbox_remove, ++ .driver = { ++ .name = "mailbox", ++ .of_match_table = starfive_mbox_of_match, ++ .pm = &starfive_mbox_pm_ops, ++ }, ++}; ++ ++static int __init starfive_mbox_init(void) ++{ ++ return platform_driver_register(&starfive_mbox_driver); ++} ++core_initcall(starfive_mbox_init); ++ ++static void __exit starfive_mbox_exit(void) ++{ ++ platform_driver_unregister(&starfive_mbox_driver); ++} ++module_exit(starfive_mbox_exit); ++ ++MODULE_DESCRIPTION("StarFive Mailbox Controller driver"); ++MODULE_AUTHOR("Shanlong Li <shanlong.li@starfivetech.com>"); ++MODULE_LICENSE("GPL"); diff --git a/target/linux/starfive/patches-6.6/0065-driver-rtc-Add-StarFive-JH7110-rtc-driver.patch b/target/linux/starfive/patches-6.6/0065-driver-rtc-Add-StarFive-JH7110-rtc-driver.patch new file mode 100644 index 0000000000..0257cb004b --- /dev/null +++ b/target/linux/starfive/patches-6.6/0065-driver-rtc-Add-StarFive-JH7110-rtc-driver.patch @@ -0,0 +1,789 @@ +From 0f44bd6bec708782f38bba4d03deecf927d1c83d Mon Sep 17 00:00:00 2001 +From: "ziv.xu" <ziv.xu@starfivetech.com> +Date: Fri, 9 Jun 2023 15:31:53 +0800 +Subject: [PATCH 065/116] driver: rtc: Add StarFive JH7110 rtc driver + +Add RTC driver and support for StarFive JH7110 SoC. + +Signed-off-by: ziv.xu <ziv.xu@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/rtc/Kconfig | 8 + + drivers/rtc/Makefile | 1 + + drivers/rtc/rtc-starfive.c | 743 +++++++++++++++++++++++++++++++++++++ + 3 files changed, 752 insertions(+) + create mode 100644 drivers/rtc/rtc-starfive.c + +--- a/drivers/rtc/Kconfig ++++ b/drivers/rtc/Kconfig +@@ -1327,6 +1327,14 @@ config RTC_DRV_NTXEC + embedded controller found in certain e-book readers designed by the + original design manufacturer Netronix. + ++config RTC_DRV_STARFIVE ++ tristate "StarFive 32.768k-RTC" ++ depends on ARCH_STARFIVE ++ depends on OF ++ help ++ If you say Y here you will get support for the RTC found on ++ StarFive SOCS. ++ + comment "on-CPU RTC drivers" + + config RTC_DRV_ASM9260 +--- a/drivers/rtc/Makefile ++++ b/drivers/rtc/Makefile +@@ -163,6 +163,7 @@ obj-$(CONFIG_RTC_DRV_SH) += rtc-sh.o + obj-$(CONFIG_RTC_DRV_SNVS) += rtc-snvs.o + obj-$(CONFIG_RTC_DRV_SPEAR) += rtc-spear.o + obj-$(CONFIG_RTC_DRV_STARFIRE) += rtc-starfire.o ++obj-$(CONFIG_RTC_DRV_STARFIVE) += rtc-starfive.o + obj-$(CONFIG_RTC_DRV_STK17TA8) += rtc-stk17ta8.o + obj-$(CONFIG_RTC_DRV_ST_LPC) += rtc-st-lpc.o + obj-$(CONFIG_RTC_DRV_STM32) += rtc-stm32.o +--- /dev/null ++++ b/drivers/rtc/rtc-starfive.c +@@ -0,0 +1,743 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * RTC driver for the StarFive JH7110 SoC ++ * ++ * Copyright (C) 2021 StarFive Technology Co., Ltd. ++ */ ++ ++#include <asm/delay.h> ++#include <linux/bcd.h> ++#include <linux/bitfield.h> ++#include <linux/clk.h> ++#include <linux/reset.h> ++#include <linux/completion.h> ++#include <linux/interrupt.h> ++#include <linux/io.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/of_irq.h> ++#include <linux/iopoll.h> ++#include <linux/platform_device.h> ++#include <linux/rtc.h> ++ ++/* Registers */ ++#define SFT_RTC_CFG 0x00 ++#define SFT_RTC_SW_CAL_VALUE 0x04 ++#define SFT_RTC_HW_CAL_CFG 0x08 ++#define SFT_RTC_CMP_CFG 0x0C ++#define SFT_RTC_IRQ_EN 0x10 ++#define SFT_RTC_IRQ_EVEVT 0x14 ++#define SFT_RTC_IRQ_STATUS 0x18 ++#define SFT_RTC_CAL_VALUE 0x24 ++#define SFT_RTC_CFG_TIME 0x28 ++#define SFT_RTC_CFG_DATE 0x2C ++#define SFT_RTC_ACT_TIME 0x34 ++#define SFT_RTC_ACT_DATE 0x38 ++#define SFT_RTC_TIME 0x3C ++#define SFT_RTC_DATE 0x40 ++#define SFT_RTC_TIME_LATCH 0x44 ++#define SFT_RTC_DATE_LATCH 0x48 ++ ++/* RTC_CFG */ ++#define RTC_CFG_ENABLE_SHIFT 0 /* RW: RTC Enable. */ ++#define RTC_CFG_CAL_EN_HW_SHIFT 1 /* RW: Enable of hardware calibretion. */ ++#define RTC_CFG_CAL_SEL_SHIFT 2 /* RW: select the hw/sw calibretion mode.*/ ++#define RTC_CFG_HOUR_MODE_SHIFT 3 /* RW: time hour mode. 24h|12h */ ++ ++/* RTC_SW_CAL_VALUE */ ++#define RTC_SW_CAL_VALUE_MASK GENMASK(15, 0) ++#define RTC_SW_CAL_MAX RTC_SW_CAL_VALUE_MASK ++#define RTC_SW_CAL_MIN 0 ++#define RTC_TICKS_PER_SEC 32768 /* Number of ticks per second */ ++#define RTC_PPB_MULT 1000000000LL /* Multiplier for ppb conversions */ ++ ++/* RTC_HW_CAL_CFG */ ++#define RTC_HW_CAL_REF_SEL_SHIFT 0 ++#define RTC_HW_CAL_FRQ_SEL_SHIFT 1 ++ ++/* IRQ_EN/IRQ_EVEVT/IRQ_STATUS */ ++#define RTC_IRQ_CAL_START BIT(0) ++#define RTC_IRQ_CAL_FINISH BIT(1) ++#define RTC_IRQ_CMP BIT(2) ++#define RTC_IRQ_1SEC BIT(3) ++#define RTC_IRQ_ALAEM BIT(4) ++#define RTC_IRQ_EVT_UPDATE_PSE BIT(31) /* WO: Enable of update time&&date, IRQ_EVEVT only */ ++#define RTC_IRQ_ALL (RTC_IRQ_CAL_START \ ++ | RTC_IRQ_CAL_FINISH \ ++ | RTC_IRQ_CMP \ ++ | RTC_IRQ_1SEC \ ++ | RTC_IRQ_ALAEM) ++ ++/* CAL_VALUE */ ++#define RTC_CAL_VALUE_MASK GENMASK(15, 0) ++ ++/* CFG_TIME/ACT_TIME/RTC_TIME */ ++#define TIME_SEC_MASK GENMASK(6, 0) ++#define TIME_MIN_MASK GENMASK(13, 7) ++#define TIME_HOUR_MASK GENMASK(20, 14) ++ ++/* CFG_DATE/ACT_DATE/RTC_DATE */ ++#define DATE_DAY_MASK GENMASK(5, 0) ++#define DATE_MON_MASK GENMASK(10, 6) ++#define DATE_YEAR_MASK GENMASK(18, 11) ++ ++#define INT_TIMEOUT_US 180 ++ ++enum RTC_HOUR_MODE { ++ RTC_HOUR_MODE_12H = 0, ++ RTC_HOUR_MODE_24H = 1 ++}; ++ ++enum RTC_CAL_MODE { ++ RTC_CAL_MODE_SW = 0, ++ RTC_CAL_MODE_HW = 1 ++}; ++ ++enum RTC_HW_CAL_REF_MODE { ++ RTC_CAL_CLK_REF = 0, ++ RTC_CAL_CLK_MARK = 1 ++}; ++ ++static const unsigned long refclk_list[] = { ++ 1000000, ++ 2000000, ++ 4000000, ++ 5927000, ++ 6000000, ++ 7200000, ++ 8000000, ++ 10250000, ++ 11059200, ++ 12000000, ++ 12288000, ++ 13560000, ++ 16000000, ++ 19200000, ++ 20000000, ++ 22118000, ++ 24000000, ++ 24567000, ++ 25000000, ++ 26000000, ++ 27000000, ++ 30000000, ++ 32000000, ++ 33868800, ++ 36000000, ++ 36860000, ++ 40000000, ++ 44000000, ++ 50000000, ++ 54000000, ++ 28224000, ++ 28000000, ++}; ++ ++struct sft_rtc { ++ struct rtc_device *rtc_dev; ++ struct completion cal_done; ++ struct completion onesec_done; ++ struct clk *pclk; ++ struct clk *cal_clk; ++ struct reset_control *rst_array; ++ int hw_cal_map; ++ void __iomem *regs; ++ int rtc_irq; ++ int ms_pulse_irq; ++ int one_sec_pulse_irq; ++}; ++ ++static inline void sft_rtc_set_enabled(struct sft_rtc *srtc, bool enabled) ++{ ++ u32 val; ++ ++ if (enabled) { ++ val = readl(srtc->regs + SFT_RTC_CFG); ++ val |= BIT(RTC_CFG_ENABLE_SHIFT); ++ writel(val, srtc->regs + SFT_RTC_CFG); ++ } else { ++ val = readl(srtc->regs + SFT_RTC_CFG); ++ val &= ~BIT(RTC_CFG_ENABLE_SHIFT); ++ writel(val, srtc->regs + SFT_RTC_CFG); ++ } ++} ++ ++static inline bool sft_rtc_get_enabled(struct sft_rtc *srtc) ++{ ++ return !!(readl(srtc->regs + SFT_RTC_CFG) & BIT(RTC_CFG_ENABLE_SHIFT)); ++} ++ ++static inline void sft_rtc_set_mode(struct sft_rtc *srtc, enum RTC_HOUR_MODE mode) ++{ ++ u32 val; ++ ++ val = readl(srtc->regs + SFT_RTC_CFG); ++ val |= mode << RTC_CFG_HOUR_MODE_SHIFT; ++ writel(val, srtc->regs + SFT_RTC_CFG); ++} ++ ++static inline int sft_rtc_irq_enable(struct sft_rtc *srtc, u32 irq, bool enable) ++{ ++ u32 val; ++ ++ if (!(irq & RTC_IRQ_ALL)) ++ return -EINVAL; ++ ++ if (enable) { ++ val = readl(srtc->regs + SFT_RTC_IRQ_EN); ++ val |= irq; ++ writel(val, srtc->regs + SFT_RTC_IRQ_EN); ++ } else { ++ val = readl(srtc->regs + SFT_RTC_IRQ_EN); ++ val &= ~irq; ++ writel(val, srtc->regs + SFT_RTC_IRQ_EN); ++ } ++ return 0; ++} ++ ++static inline void ++sft_rtc_set_cal_hw_enable(struct sft_rtc *srtc, bool enable) ++{ ++ u32 val; ++ ++ if (enable) { ++ val = readl(srtc->regs + SFT_RTC_CFG); ++ val |= BIT(RTC_CFG_CAL_EN_HW_SHIFT); ++ writel(val, srtc->regs + SFT_RTC_CFG); ++ } else { ++ val = readl(srtc->regs + SFT_RTC_CFG); ++ val &= ~BIT(RTC_CFG_CAL_EN_HW_SHIFT); ++ writel(val, srtc->regs + SFT_RTC_CFG); ++ } ++} ++ ++static inline void ++sft_rtc_set_cal_mode(struct sft_rtc *srtc, enum RTC_CAL_MODE mode) ++{ ++ u32 val; ++ ++ val = readl(srtc->regs + SFT_RTC_CFG); ++ val |= mode << RTC_CFG_CAL_SEL_SHIFT; ++ writel(val, srtc->regs + SFT_RTC_CFG); ++} ++ ++static int sft_rtc_get_hw_calclk(struct device *dev, unsigned long freq) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(refclk_list); i++) ++ if (refclk_list[i] == freq) ++ return i; ++ ++ dev_err(dev, "refclk: %ldHz do not support.\n", freq); ++ return -EINVAL; ++} ++ ++static inline void sft_rtc_reg2time(struct rtc_time *tm, u32 reg) ++{ ++ tm->tm_hour = bcd2bin(FIELD_GET(TIME_HOUR_MASK, reg)); ++ tm->tm_min = bcd2bin(FIELD_GET(TIME_MIN_MASK, reg)); ++ tm->tm_sec = bcd2bin(FIELD_GET(TIME_SEC_MASK, reg)); ++} ++ ++static inline void sft_rtc_reg2date(struct rtc_time *tm, u32 reg) ++{ ++ tm->tm_year = bcd2bin(FIELD_GET(DATE_YEAR_MASK, reg)) + 100; ++ tm->tm_mon = bcd2bin(FIELD_GET(DATE_MON_MASK, reg)) - 1; ++ tm->tm_mday = bcd2bin(FIELD_GET(DATE_DAY_MASK, reg)); ++} ++ ++static inline u32 sft_rtc_time2reg(struct rtc_time *tm) ++{ ++ return FIELD_PREP(TIME_HOUR_MASK, bin2bcd(tm->tm_hour)) | ++ FIELD_PREP(TIME_MIN_MASK, bin2bcd(tm->tm_min)) | ++ FIELD_PREP(TIME_SEC_MASK, bin2bcd(tm->tm_sec)); ++} ++ ++static inline u32 sft_rtc_date2reg(struct rtc_time *tm) ++{ ++ return FIELD_PREP(DATE_YEAR_MASK, bin2bcd(tm->tm_year - 100)) | ++ FIELD_PREP(DATE_MON_MASK, bin2bcd(tm->tm_mon + 1)) | ++ FIELD_PREP(DATE_DAY_MASK, bin2bcd(tm->tm_mday)); ++} ++ ++static inline void sft_rtc_update_pulse(struct sft_rtc *srtc) ++{ ++ u32 val; ++ ++ val = readl(srtc->regs + SFT_RTC_IRQ_EVEVT); ++ val |= RTC_IRQ_EVT_UPDATE_PSE; ++ writel(val, srtc->regs + SFT_RTC_IRQ_EVEVT); ++} ++ ++static irqreturn_t sft_rtc_irq_handler(int irq, void *data) ++{ ++ struct sft_rtc *srtc = data; ++ struct timerqueue_node *next; ++ u32 irq_flags = 0; ++ u32 irq_mask = 0; ++ u32 val; ++ int ret = 0; ++ ++ val = readl(srtc->regs + SFT_RTC_IRQ_EVEVT); ++ if (val & RTC_IRQ_CAL_START) ++ irq_mask |= RTC_IRQ_CAL_START; ++ ++ if (val & RTC_IRQ_CAL_FINISH) { ++ irq_mask |= RTC_IRQ_CAL_FINISH; ++ complete(&srtc->cal_done); ++ } ++ ++ if (val & RTC_IRQ_CMP) ++ irq_mask |= RTC_IRQ_CMP; ++ ++ if (val & RTC_IRQ_1SEC) { ++ irq_flags |= RTC_PF; ++ irq_mask |= RTC_IRQ_1SEC; ++ complete(&srtc->onesec_done); ++ } ++ ++ if (val & RTC_IRQ_ALAEM) { ++ irq_flags |= RTC_AF; ++ irq_mask |= RTC_IRQ_ALAEM; ++ ++ next = timerqueue_getnext(&srtc->rtc_dev->timerqueue); ++ if (next == &srtc->rtc_dev->aie_timer.node) ++ dev_info(&srtc->rtc_dev->dev, "alarm expires"); ++ } ++ ++ writel(irq_mask, srtc->regs + SFT_RTC_IRQ_EVEVT); ++ ++ /* Wait interrupt flag clear */ ++ ret = readl_poll_timeout_atomic(srtc->regs + SFT_RTC_IRQ_EVEVT, val, ++ (val & irq_mask) == 0, 0, INT_TIMEOUT_US); ++ if (ret) ++ dev_warn(&srtc->rtc_dev->dev, "fail to clear rtc interrupt flag\n"); ++ ++ if (irq_flags) ++ rtc_update_irq(srtc->rtc_dev, 1, irq_flags | RTC_IRQF); ++ ++ return IRQ_HANDLED; ++} ++ ++static int sft_rtc_read_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct sft_rtc *srtc = dev_get_drvdata(dev); ++ u32 val; ++ int irq_1sec_state_start, irq_1sec_state_end; ++ ++ /* If the RTC is disabled, assume the values are invalid */ ++ if (!sft_rtc_get_enabled(srtc)) ++ return -EINVAL; ++ ++ irq_1sec_state_start = ++ (readl(srtc->regs + SFT_RTC_IRQ_STATUS) & RTC_IRQ_1SEC) == 0 ? 0 : 1; ++ ++read_again: ++ val = readl(srtc->regs + SFT_RTC_TIME); ++ sft_rtc_reg2time(tm, val); ++ ++ val = readl(srtc->regs + SFT_RTC_DATE); ++ sft_rtc_reg2date(tm, val); ++ ++ if (irq_1sec_state_start == 0) { ++ irq_1sec_state_end = ++ (readl(srtc->regs + SFT_RTC_IRQ_STATUS) & RTC_IRQ_1SEC) == 0 ? 0 : 1; ++ if (irq_1sec_state_end == 1) { ++ irq_1sec_state_start = 1; ++ goto read_again; ++ } ++ } ++ ++ return 0; ++} ++ ++static int sft_rtc_set_time(struct device *dev, struct rtc_time *tm) ++{ ++ struct sft_rtc *srtc = dev_get_drvdata(dev); ++ u32 val; ++ int ret; ++ ++ val = sft_rtc_time2reg(tm); ++ writel(val, srtc->regs + SFT_RTC_CFG_TIME); ++ ++ val = sft_rtc_date2reg(tm); ++ writel(val, srtc->regs + SFT_RTC_CFG_DATE); ++ ++ /* Update pulse */ ++ sft_rtc_update_pulse(srtc); ++ ++ /* Ensure that data is fully written */ ++ ret = wait_for_completion_interruptible_timeout(&srtc->onesec_done, ++ usecs_to_jiffies(120)); ++ if (ret) { ++ dev_warn(dev, ++ "rtc wait for completion interruptible timeout.\n"); ++ } ++ return 0; ++} ++ ++static int sft_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) ++{ ++ struct sft_rtc *srtc = dev_get_drvdata(dev); ++ ++ return sft_rtc_irq_enable(srtc, RTC_IRQ_ALAEM, enabled); ++} ++ ++static int sft_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) ++{ ++ struct sft_rtc *srtc = dev_get_drvdata(dev); ++ u32 val; ++ ++ val = readl(srtc->regs + SFT_RTC_ACT_TIME); ++ sft_rtc_reg2time(&alarm->time, val); ++ ++ val = readl(srtc->regs + SFT_RTC_ACT_DATE); ++ sft_rtc_reg2date(&alarm->time, val); ++ ++ return 0; ++} ++ ++static int sft_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) ++{ ++ struct sft_rtc *srtc = dev_get_drvdata(dev); ++ u32 val; ++ ++ sft_rtc_alarm_irq_enable(dev, 0); ++ ++ val = sft_rtc_time2reg(&alarm->time); ++ writel(val, srtc->regs + SFT_RTC_ACT_TIME); ++ ++ val = sft_rtc_date2reg(&alarm->time); ++ writel(val, srtc->regs + SFT_RTC_ACT_DATE); ++ ++ sft_rtc_alarm_irq_enable(dev, alarm->enabled); ++ ++ return 0; ++} ++ ++static int sft_rtc_get_offset(struct device *dev, long *offset) ++{ ++ struct sft_rtc *srtc = dev_get_drvdata(dev); ++ s64 tmp; ++ u32 val; ++ ++ val = readl(srtc->regs + SFT_RTC_CAL_VALUE) ++ & RTC_SW_CAL_VALUE_MASK; ++ val += 1; ++ /* ++ * the adjust val range is [0x0000-0xffff], ++ * the default val is 0x7fff (32768-1),mapping offset=0 ; ++ */ ++ tmp = (s64)val - RTC_TICKS_PER_SEC; ++ tmp *= RTC_PPB_MULT; ++ tmp = div_s64(tmp, RTC_TICKS_PER_SEC); ++ ++ /* Offset value operates in negative way, so swap sign */ ++ *offset = -tmp; ++ ++ return 0; ++} ++ ++static int sft_rtc_set_offset(struct device *dev, long offset) ++{ ++ struct sft_rtc *srtc = dev_get_drvdata(dev); ++ s64 tmp; ++ u32 val; ++ ++ tmp = offset * RTC_TICKS_PER_SEC; ++ tmp = div_s64(tmp, RTC_PPB_MULT); ++ ++ tmp = RTC_TICKS_PER_SEC - tmp; ++ tmp -= 1; ++ if (tmp > RTC_SW_CAL_MAX || tmp < RTC_SW_CAL_MIN) { ++ dev_err(dev, "offset is out of range.\n"); ++ return -EINVAL; ++ } ++ ++ val = tmp & RTC_SW_CAL_VALUE_MASK; ++ /* set software calibration value */ ++ writel(val, srtc->regs + SFT_RTC_SW_CAL_VALUE); ++ ++ /* set CFG_RTC-cal_sel to select calibretion by software. */ ++ sft_rtc_set_cal_mode(srtc, RTC_CAL_MODE_SW); ++ ++ return 0; ++} ++ ++static __maybe_unused int ++sft_rtc_hw_adjustment(struct device *dev, unsigned int enable) ++{ ++ struct sft_rtc *srtc = dev_get_drvdata(dev); ++ u32 val; ++ ++ if (srtc->hw_cal_map <= 0) { ++ dev_err(dev, "fail to get cal-clock-freq.\n"); ++ return -EFAULT; ++ } ++ ++ if (enable) { ++ sft_rtc_irq_enable(srtc, RTC_IRQ_CAL_FINISH, true); ++ ++ /* Set reference clock frequency value */ ++ val = readl(srtc->regs + SFT_RTC_HW_CAL_CFG); ++ val |= (srtc->hw_cal_map << RTC_HW_CAL_FRQ_SEL_SHIFT); ++ writel(val, srtc->regs + SFT_RTC_HW_CAL_CFG); ++ ++ /* Set CFG_RTC-cal_sel to select calibretion by hardware. */ ++ sft_rtc_set_cal_mode(srtc, RTC_CAL_MODE_HW); ++ ++ /* Set CFG_RTC-cal_en_hw to launch hardware calibretion.*/ ++ sft_rtc_set_cal_hw_enable(srtc, true); ++ ++ wait_for_completion_interruptible_timeout(&srtc->cal_done, ++ usecs_to_jiffies(100)); ++ ++ sft_rtc_irq_enable(srtc, RTC_IRQ_CAL_FINISH, false); ++ } else { ++ sft_rtc_set_cal_mode(srtc, RTC_CAL_MODE_SW); ++ sft_rtc_set_cal_hw_enable(srtc, false); ++ } ++ ++ return 0; ++} ++ ++static int sft_rtc_get_cal_clk(struct device *dev, struct sft_rtc *srtc) ++{ ++ struct device_node *np = dev->of_node; ++ unsigned long cal_clk_freq; ++ u32 freq; ++ int ret; ++ ++ srtc->cal_clk = devm_clk_get(dev, "cal_clk"); ++ if (IS_ERR(srtc->cal_clk)) ++ return PTR_ERR(srtc->cal_clk); ++ ++ clk_prepare_enable(srtc->cal_clk); ++ ++ cal_clk_freq = clk_get_rate(srtc->cal_clk); ++ if (!cal_clk_freq) { ++ dev_warn(dev, ++ "get rate failed, next try to get from dts.\n"); ++ ret = of_property_read_u32(np, "rtc,cal-clock-freq", &freq); ++ if (!ret) { ++ cal_clk_freq = (u64)freq; ++ } else { ++ dev_err(dev, ++ "Need rtc,cal-clock-freq define in dts.\n"); ++ goto err_disable_cal_clk; ++ } ++ } ++ ++ srtc->hw_cal_map = sft_rtc_get_hw_calclk(dev, cal_clk_freq); ++ if (srtc->hw_cal_map < 0) { ++ ret = srtc->hw_cal_map; ++ goto err_disable_cal_clk; ++ } ++ ++ return 0; ++ ++err_disable_cal_clk: ++ clk_disable_unprepare(srtc->cal_clk); ++ ++ return ret; ++} ++ ++static int sft_rtc_get_irq(struct platform_device *pdev, struct sft_rtc *srtc) ++{ ++ int ret; ++ ++ srtc->rtc_irq = platform_get_irq_byname(pdev, "rtc"); ++ if (srtc->rtc_irq < 0) ++ return -EINVAL; ++ ++ ret = devm_request_irq(&pdev->dev, srtc->rtc_irq, ++ sft_rtc_irq_handler, 0, ++ KBUILD_MODNAME, srtc); ++ if (ret) ++ dev_err(&pdev->dev, "Failed to request interrupt, %d\n", ret); ++ ++ return ret; ++} ++ ++static const struct rtc_class_ops starfive_rtc_ops = { ++ .read_time = sft_rtc_read_time, ++ .set_time = sft_rtc_set_time, ++ .read_alarm = sft_rtc_read_alarm, ++ .set_alarm = sft_rtc_set_alarm, ++ .alarm_irq_enable = sft_rtc_alarm_irq_enable, ++ .set_offset = sft_rtc_set_offset, ++ .read_offset = sft_rtc_get_offset, ++}; ++ ++static int sft_rtc_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct sft_rtc *srtc; ++ struct rtc_time tm; ++ struct irq_desc *desc; ++ int ret; ++ ++ srtc = devm_kzalloc(dev, sizeof(*srtc), GFP_KERNEL); ++ if (!srtc) ++ return -ENOMEM; ++ ++ srtc->regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(srtc->regs)) ++ return PTR_ERR(srtc->regs); ++ ++ srtc->pclk = devm_clk_get(dev, "pclk"); ++ if (IS_ERR(srtc->pclk)) { ++ ret = PTR_ERR(srtc->pclk); ++ dev_err(dev, ++ "Failed to retrieve the peripheral clock, %d\n", ret); ++ return ret; ++ } ++ ++ srtc->rst_array = devm_reset_control_array_get_exclusive(dev); ++ if (IS_ERR(srtc->rst_array)) { ++ ret = PTR_ERR(srtc->rst_array); ++ dev_err(dev, ++ "Failed to retrieve the rtc reset, %d\n", ret); ++ return ret; ++ } ++ ++ init_completion(&srtc->cal_done); ++ init_completion(&srtc->onesec_done); ++ ++ ret = clk_prepare_enable(srtc->pclk); ++ if (ret) { ++ dev_err(dev, ++ "Failed to enable the peripheral clock, %d\n", ret); ++ return ret; ++ } ++ ++ ret = sft_rtc_get_cal_clk(dev, srtc); ++ if (ret) ++ goto err_disable_pclk; ++ ++ ret = reset_control_deassert(srtc->rst_array); ++ if (ret) { ++ dev_err(dev, ++ "Failed to deassert rtc resets, %d\n", ret); ++ goto err_disable_cal_clk; ++ } ++ ++ ret = sft_rtc_get_irq(pdev, srtc); ++ if (ret) ++ goto err_disable_cal_clk; ++ ++ srtc->rtc_dev = devm_rtc_allocate_device(dev); ++ if (IS_ERR(srtc->rtc_dev)) ++ return PTR_ERR(srtc->rtc_dev); ++ ++ platform_set_drvdata(pdev, srtc); ++ ++ /* The RTC supports 01.01.2001 - 31.12.2099 */ ++ srtc->rtc_dev->range_min = mktime64(2001, 1, 1, 0, 0, 0); ++ srtc->rtc_dev->range_max = mktime64(2099, 12, 31, 23, 59, 59); ++ ++ srtc->rtc_dev->ops = &starfive_rtc_ops; ++ device_init_wakeup(dev, true); ++ ++ desc = irq_to_desc(srtc->rtc_irq); ++ irq_desc_get_chip(desc)->flags = IRQCHIP_SKIP_SET_WAKE; ++ ++ /* Always use 24-hour mode and keep the RTC values */ ++ sft_rtc_set_mode(srtc, RTC_HOUR_MODE_24H); ++ ++ sft_rtc_set_enabled(srtc, true); ++ ++ if (device_property_read_bool(dev, "rtc,hw-adjustment")) ++ sft_rtc_hw_adjustment(dev, true); ++ ++ /* ++ * If rtc time is out of supported range, reset it to the minimum time. ++ * notice that, actual year = 1900 + tm.tm_year ++ * actual month = 1 + tm.tm_mon ++ */ ++ sft_rtc_read_time(dev, &tm); ++ if (tm.tm_year < 101 || tm.tm_year > 199 || tm.tm_mon < 0 || tm.tm_mon > 11 || ++ tm.tm_mday < 1 || tm.tm_mday > 31 || tm.tm_hour < 0 || tm.tm_hour > 23 || ++ tm.tm_min < 0 || tm.tm_min > 59 || tm.tm_sec < 0 || tm.tm_sec > 59) { ++ rtc_time64_to_tm(srtc->rtc_dev->range_min, &tm); ++ sft_rtc_set_time(dev, &tm); ++ } ++ ++ ret = devm_rtc_register_device(srtc->rtc_dev); ++ if (ret) ++ goto err_disable_wakeup; ++ ++ return 0; ++ ++err_disable_wakeup: ++ device_init_wakeup(dev, false); ++ ++err_disable_cal_clk: ++ clk_disable_unprepare(srtc->cal_clk); ++ ++err_disable_pclk: ++ clk_disable_unprepare(srtc->pclk); ++ ++ return ret; ++} ++ ++static int sft_rtc_remove(struct platform_device *pdev) ++{ ++ struct sft_rtc *srtc = platform_get_drvdata(pdev); ++ ++ sft_rtc_alarm_irq_enable(&pdev->dev, 0); ++ device_init_wakeup(&pdev->dev, 0); ++ ++ clk_disable_unprepare(srtc->pclk); ++ clk_disable_unprepare(srtc->cal_clk); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_PM_SLEEP ++static int sft_rtc_suspend(struct device *dev) ++{ ++ struct sft_rtc *srtc = dev_get_drvdata(dev); ++ ++ if (device_may_wakeup(dev)) ++ enable_irq_wake(srtc->rtc_irq); ++ ++ return 0; ++} ++ ++static int sft_rtc_resume(struct device *dev) ++{ ++ struct sft_rtc *srtc = dev_get_drvdata(dev); ++ ++ if (device_may_wakeup(dev)) ++ disable_irq_wake(srtc->rtc_irq); ++ ++ return 0; ++} ++#endif ++ ++static SIMPLE_DEV_PM_OPS(sft_rtc_pm_ops, sft_rtc_suspend, sft_rtc_resume); ++ ++static const struct of_device_id sft_rtc_of_match[] = { ++ { .compatible = "starfive,jh7110-rtc" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, sft_rtc_of_match); ++ ++static struct platform_driver starfive_rtc_driver = { ++ .driver = { ++ .name = "starfive-rtc", ++ .of_match_table = sft_rtc_of_match, ++ .pm = &sft_rtc_pm_ops, ++ }, ++ .probe = sft_rtc_probe, ++ .remove = sft_rtc_remove, ++}; ++module_platform_driver(starfive_rtc_driver); ++ ++MODULE_AUTHOR("Samin Guo <samin.guo@starfivetech.com>"); ++MODULE_AUTHOR("Hal Feng <hal.feng@starfivetech.com>"); ++MODULE_DESCRIPTION("StarFive RTC driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:starfive-rtc"); diff --git a/target/linux/starfive/patches-6.6/0066-uart-8250-Add-dw-auto-flow-ctrl-support.patch b/target/linux/starfive/patches-6.6/0066-uart-8250-Add-dw-auto-flow-ctrl-support.patch new file mode 100644 index 0000000000..c88008a604 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0066-uart-8250-Add-dw-auto-flow-ctrl-support.patch @@ -0,0 +1,96 @@ +From 552114b8cbbd956ad8466261b5f11b059eba82ca Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Sun, 25 Jun 2023 09:40:29 +0800 +Subject: [PATCH 066/116] uart: 8250: Add dw auto flow ctrl support + +Add designeware 8250 auto flow ctrl support. Enable +it by add auto-flow-control in dts. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +--- + drivers/tty/serial/8250/8250_core.c | 2 ++ + drivers/tty/serial/8250/8250_dw.c | 3 +++ + drivers/tty/serial/8250/8250_port.c | 14 +++++++++++++- + include/linux/serial_8250.h | 1 + + include/uapi/linux/serial_core.h | 2 ++ + 5 files changed, 21 insertions(+), 1 deletion(-) + +--- a/drivers/tty/serial/8250/8250_core.c ++++ b/drivers/tty/serial/8250/8250_core.c +@@ -1129,6 +1129,8 @@ int serial8250_register_8250_port(const + uart->dl_read = up->dl_read; + if (up->dl_write) + uart->dl_write = up->dl_write; ++ if (up->probe) ++ uart->probe = up->probe; + + if (uart->port.type != PORT_8250_CIR) { + if (serial8250_isa_config != NULL) +--- a/drivers/tty/serial/8250/8250_dw.c ++++ b/drivers/tty/serial/8250/8250_dw.c +@@ -612,6 +612,9 @@ static int dw8250_probe(struct platform_ + data->msr_mask_off |= UART_MSR_TERI; + } + ++ if (device_property_read_bool(dev, "auto-flow-control")) ++ up->probe |= UART_PROBE_AFE; ++ + /* If there is separate baudclk, get the rate from it. */ + data->clk = devm_clk_get_optional(dev, "baudclk"); + if (data->clk == NULL) +--- a/drivers/tty/serial/8250/8250_port.c ++++ b/drivers/tty/serial/8250/8250_port.c +@@ -330,6 +330,14 @@ static const struct serial8250_config ua + .rxtrig_bytes = {1, 8, 16, 30}, + .flags = UART_CAP_FIFO | UART_CAP_AFE, + }, ++ [PORT_16550A_AFE] = { ++ .name = "16550A_AFE", ++ .fifo_size = 16, ++ .tx_loadsz = 16, ++ .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10, ++ .rxtrig_bytes = {1, 4, 8, 14}, ++ .flags = UART_CAP_FIFO | UART_CAP_AFE, ++ }, + }; + + /* Uart divisor latch read */ +@@ -1143,6 +1151,11 @@ static void autoconfig_16550a(struct uar + up->port.type = PORT_U6_16550A; + up->capabilities |= UART_CAP_AFE; + } ++ ++ if ((up->port.type == PORT_16550A) && (up->probe & UART_PROBE_AFE)) { ++ up->port.type = PORT_16550A_AFE; ++ up->capabilities |= UART_CAP_AFE; ++ } + } + + /* +@@ -2813,7 +2826,6 @@ serial8250_do_set_termios(struct uart_po + if (termios->c_cflag & CRTSCTS) + up->mcr |= UART_MCR_AFE; + } +- + /* + * Update the per-port timeout. + */ +--- a/include/linux/serial_8250.h ++++ b/include/linux/serial_8250.h +@@ -141,6 +141,7 @@ struct uart_8250_port { + unsigned char probe; + struct mctrl_gpios *gpios; + #define UART_PROBE_RSA (1 << 0) ++#define UART_PROBE_AFE (1 << 1) + + /* + * Some bits in registers are cleared on a read, so they must +--- a/include/uapi/linux/serial_core.h ++++ b/include/uapi/linux/serial_core.h +@@ -245,4 +245,6 @@ + /* Sunplus UART */ + #define PORT_SUNPLUS 123 + ++#define PORT_16550A_AFE 124 ++ + #endif /* _UAPILINUX_SERIAL_CORE_H */ diff --git a/target/linux/starfive/patches-6.6/0067-driver-uart-fix-up-uart-communicate-fail.patch b/target/linux/starfive/patches-6.6/0067-driver-uart-fix-up-uart-communicate-fail.patch new file mode 100644 index 0000000000..18d735b92d --- /dev/null +++ b/target/linux/starfive/patches-6.6/0067-driver-uart-fix-up-uart-communicate-fail.patch @@ -0,0 +1,29 @@ +From 6edee93a89254f30c3387c88231e7ecec06ba84a Mon Sep 17 00:00:00 2001 +From: "shanlong.li" <shanlong.li@starfivetech.com> +Date: Mon, 10 Jul 2023 03:07:57 -0700 +Subject: [PATCH 067/116] driver:uart: fix up uart communicate fail + +fix up uart communicate fail + +Signed-off-by: shanlong.li <shanlong.li@starfivetech.com> +--- + drivers/tty/serial/8250/8250_dw.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +--- a/drivers/tty/serial/8250/8250_dw.c ++++ b/drivers/tty/serial/8250/8250_dw.c +@@ -652,10 +652,10 @@ static int dw8250_probe(struct platform_ + if (err) + return err; + +- data->rst = devm_reset_control_get_optional_exclusive(dev, NULL); +- if (IS_ERR(data->rst)) +- return PTR_ERR(data->rst); +- ++ data->rst = devm_reset_control_array_get_exclusive(dev); ++ if (IS_ERR(data->rst)) { ++ err = PTR_ERR(data->rst); ++ } + reset_control_deassert(data->rst); + + err = devm_add_action_or_reset(dev, dw8250_reset_control_assert, data->rst); diff --git a/target/linux/starfive/patches-6.6/0068-uart-8250-add-reset-operation-in-runtime-PM.patch b/target/linux/starfive/patches-6.6/0068-uart-8250-add-reset-operation-in-runtime-PM.patch new file mode 100644 index 0000000000..56efebda6c --- /dev/null +++ b/target/linux/starfive/patches-6.6/0068-uart-8250-add-reset-operation-in-runtime-PM.patch @@ -0,0 +1,32 @@ +From 777d288f03a0b350f6c2d4367b01a80d9f25cd6e Mon Sep 17 00:00:00 2001 +From: William Qiu <william.qiu@starfivetech.com> +Date: Wed, 20 Sep 2023 17:19:59 +0800 +Subject: [PATCH 068/116] uart: 8250: add reset operation in runtime PM + +add reset operation in runtime PM + +Signed-off-by: William Qiu <william.qiu@starfivetech.com> +--- + drivers/tty/serial/8250/8250_dw.c | 4 ++++ + 1 file changed, 4 insertions(+) + +--- a/drivers/tty/serial/8250/8250_dw.c ++++ b/drivers/tty/serial/8250/8250_dw.c +@@ -745,6 +745,8 @@ static int dw8250_runtime_suspend(struct + { + struct dw8250_data *data = dev_get_drvdata(dev); + ++ reset_control_assert(data->rst); ++ + clk_disable_unprepare(data->clk); + + clk_disable_unprepare(data->pclk); +@@ -760,6 +762,8 @@ static int dw8250_runtime_resume(struct + + clk_prepare_enable(data->clk); + ++ reset_control_deassert(data->rst); ++ + return 0; + } + diff --git a/target/linux/starfive/patches-6.6/0069-dt-bindings-CAN-Add-StarFive-CAN-module.patch b/target/linux/starfive/patches-6.6/0069-dt-bindings-CAN-Add-StarFive-CAN-module.patch new file mode 100644 index 0000000000..eb46a96314 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0069-dt-bindings-CAN-Add-StarFive-CAN-module.patch @@ -0,0 +1,113 @@ +From 5eda2331a252436756fb40861f01a7a38b1502c7 Mon Sep 17 00:00:00 2001 +From: William Qiu <william.qiu@starfivetech.com> +Date: Thu, 15 Jun 2023 20:14:22 +0800 +Subject: [PATCH 069/116] dt-bindings: CAN: Add StarFive CAN module + +Add documentation to describe StarFive CAN engine. + +Signed-off-by: William Qiu <william.qiu@starfivetech.com> +--- + .../devicetree/bindings/net/can/ipms-can.yaml | 97 +++++++++++++++++++ + 1 file changed, 97 insertions(+) + create mode 100644 Documentation/devicetree/bindings/net/can/ipms-can.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/net/can/ipms-can.yaml +@@ -0,0 +1,97 @@ ++# SPDX-License-Identifier: GPL-2.0 ++%YAML 1.2 ++--- ++#$id: http://devicetree.org/schemas/net/can/ipms-can.yaml# ++#$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: IPMS CAN/CANFD controller Device Tree Bindings ++ ++properties: ++ compatible: ++ const:ipms,can ++ ++ reg: ++ maxItems: 1 ++ items: ++ - description:CAN controller registers ++ ++ interrupts: ++ maxItems: 1 ++ ++ clocks: ++ minItems: 1 ++ items: ++ - description:apb_clk clock ++ - description:core_clk clock ++ - description:timer_clk clock ++ ++ clock-names: ++ minItems: 1 ++ items: ++ - const:apb_clk ++ - const:core_clk ++ - const:timer_clk ++ resets: ++ minItems: 1 ++ items: ++ - description:apb_clk reset ++ - description:core_clk reset ++ - description:timer_clk reset ++ reset-names: ++ minItems: 1 ++ items: ++ - const:rst_apb ++ - const:rst_core ++ - const:rst_timer ++ starfive,sys-syscon: ++ format: ++ starfive,sys-syscon = <&arg0 arg1 arg2 arg3> ++ description: ++ arg0:arg0 is sys_syscon. ++ arg1:arg1 is syscon register offset, used to enable can2.0/canfd function, can0 is 0x10, can1 is 0x88. ++ arg2:arg2 is used to enable the register shift of the can2.0/canfd function, can0 is 0x3, can1 is 0x12. ++ arg3:arg3 is used to enable the register mask of the can2.0/canfd function, can0 is 0x8, can1 is 0x40000 ++ ++ syscon,can_or_canfd: ++ description: ++ IPMS CAN-CTRL core is a serial communications controller that performs serial communication according to the CAN protocol. ++ This CAN bus interface uses the basic CAN principle and meets all constraints of the CAN-specification 2.0B active. ++ Furthermore this CAN core can be configured to meet the specification of CAN with flexible data rate CAN FD. ++ When syscon,can_or_canfd is set to 0, use CAN2.0B. ++ when syscon,can_or_canfd is set to 1, use CAN FD. ++required: ++ - compatible ++ - reg ++ - interrupts ++ - clocks ++ - clock-names ++ - resets ++ - reset-names ++ - starfive,sys-syscon ++ - syscon,can_or_canfd ++additionalProperties:false ++ ++examples: ++ - | ++ can0: can@130d0000{ ++ compatible = "ipms,can"; ++ reg = <0x0 0x130d0000 0x0 0x1000>; ++ interrupts = <112>; ++ interrupt-parent = <&plic>; ++ clocks = <&clkgen JH7110_CAN0_CTRL_CLK_APB>, ++ <&clkgen JH7110_CAN0_CTRL_CLK_CAN>, ++ <&clkgen JH7110_CAN0_CTRL_CLK_TIMER>; ++ clock-names = "apb_clk", ++ "core_clk", ++ "timer_clk"; ++ resets = <&rstgen RSTN_U0_CAN_CTRL_APB>, ++ <&rstgen RSTN_U0_CAN_CTRL_CORE>, ++ <&rstgen RSTN_U0_CAN_CTRL_TIMER>; ++ reset-names = "rst_apb", ++ "rst_core", ++ "rst_timer"; ++ starfive,sys-syscon = <&sys_syscon, 0x10 0x3 0x8>; ++ syscon,can_or_canfd = <0>; ++ }; ++ ++... diff --git a/target/linux/starfive/patches-6.6/0070-CAN-starfive-Add-CAN-engine-support.patch b/target/linux/starfive/patches-6.6/0070-CAN-starfive-Add-CAN-engine-support.patch new file mode 100644 index 0000000000..1bb41dd079 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0070-CAN-starfive-Add-CAN-engine-support.patch @@ -0,0 +1,1317 @@ +From b1fbe15b87be654b1b280a76ec1470917d79f720 Mon Sep 17 00:00:00 2001 +From: William Qiu <william.qiu@starfivetech.com> +Date: Thu, 15 Jun 2023 20:15:25 +0800 +Subject: [PATCH 070/116] CAN: starfive - Add CAN engine support + +Adding device probe StarFive CAN module. + +Signed-off-by: William Qiu <william.qiu@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/net/can/Kconfig | 5 + + drivers/net/can/Makefile | 1 + + drivers/net/can/ipms_canfd.c | 1275 ++++++++++++++++++++++++++++++++++ + 3 files changed, 1281 insertions(+) + create mode 100644 drivers/net/can/ipms_canfd.c + +--- a/drivers/net/can/Kconfig ++++ b/drivers/net/can/Kconfig +@@ -214,6 +214,11 @@ config CAN_XILINXCAN + Xilinx CAN driver. This driver supports both soft AXI CAN IP and + Zynq CANPS IP. + ++config IPMS_CAN ++ tristate "IPMS CAN" ++ help ++ IPMS CANFD driver. This driver supports IPMS CANFD IP. ++ + source "drivers/net/can/c_can/Kconfig" + source "drivers/net/can/cc770/Kconfig" + source "drivers/net/can/ctucanfd/Kconfig" +--- a/drivers/net/can/Makefile ++++ b/drivers/net/can/Makefile +@@ -31,5 +31,6 @@ obj-$(CONFIG_CAN_SJA1000) += sja1000/ + obj-$(CONFIG_CAN_SUN4I) += sun4i_can.o + obj-$(CONFIG_CAN_TI_HECC) += ti_hecc.o + obj-$(CONFIG_CAN_XILINXCAN) += xilinx_can.o ++obj-$(CONFIG_IPMS_CAN) += ipms_canfd.o + + subdir-ccflags-$(CONFIG_CAN_DEBUG_DEVICES) += -DDEBUG +--- /dev/null ++++ b/drivers/net/can/ipms_canfd.c +@@ -0,0 +1,1275 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * StarFive Controller Area Network Host Controller Driver ++ * ++ * Copyright (c) 2022 StarFive Technology Co., Ltd. ++ */ ++ ++#include <linux/clk.h> ++#include <linux/reset.h> ++#include <linux/errno.h> ++#include <linux/init.h> ++#include <linux/interrupt.h> ++#include <linux/io.h> ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/netdevice.h> ++#include <linux/of.h> ++#include <linux/platform_device.h> ++#include <linux/skbuff.h> ++#include <linux/string.h> ++#include <linux/types.h> ++#include <linux/can/dev.h> ++#include <linux/can/error.h> ++#include <linux/pm_runtime.h> ++#include <linux/of_device.h> ++#include <linux/mfd/syscon.h> ++#include <linux/regmap.h> ++ ++#define DRIVER_NAME "ipms_canfd" ++ ++/* CAN registers set */ ++enum canfd_device_reg { ++ CANFD_RUBF_OFFSET = 0x00, /* Receive Buffer Registers 0x00-0x4f */ ++ CANFD_RUBF_ID_OFFSET = 0x00, ++ CANFD_RBUF_CTL_OFFSET = 0x04, ++ CANFD_RBUF_DATA_OFFSET = 0x08, ++ CANFD_TBUF_OFFSET = 0x50, /* Transmit Buffer Registers 0x50-0x97 */ ++ CANFD_TBUF_ID_OFFSET = 0x50, ++ CANFD_TBUF_CTL_OFFSET = 0x54, ++ CANFD_TBUF_DATA_OFFSET = 0x58, ++ CANFD_TTS_OFFSET = 0x98, /* Transmission Time Stamp 0x98-0x9f */ ++ CANFD_CFG_STAT_OFFSET = 0xa0, ++ CANFD_TCMD_OFFSET = 0xa1, ++ CANFD_TCTRL_OFFSET = 0xa2, ++ CANFD_RCTRL_OFFSET = 0xa3, ++ CANFD_RTIE_OFFSET = 0xa4, ++ CANFD_RTIF_OFFSET = 0xa5, ++ CANFD_ERRINT_OFFSET = 0xa6, ++ CANFD_LIMIT_OFFSET = 0xa7, ++ CANFD_S_SEG_1_OFFSET = 0xa8, ++ CANFD_S_SEG_2_OFFSET = 0xa9, ++ CANFD_S_SJW_OFFSET = 0xaa, ++ CANFD_S_PRESC_OFFSET = 0xab, ++ CANFD_F_SEG_1_OFFSET = 0xac, ++ CANFD_F_SEG_2_OFFSET = 0xad, ++ CANFD_F_SJW_OFFSET = 0xae, ++ CANFD_F_PRESC_OFFSET = 0xaf, ++ CANFD_EALCAP_OFFSET = 0xb0, ++ CANFD_RECNT_OFFSET = 0xb2, ++ CANFD_TECNT_OFFSET = 0xb3, ++}; ++ ++enum canfd_reg_bitchange { ++ CAN_FD_SET_RST_MASK = 0x80, /* Set Reset Bit */ ++ CAN_FD_OFF_RST_MASK = 0x7f, /* Reset Off Bit */ ++ CAN_FD_SET_FULLCAN_MASK = 0x10, /* set TTTBM as 1->full TTCAN mode */ ++ CAN_FD_OFF_FULLCAN_MASK = 0xef, /* set TTTBM as 0->separate PTB and STB mode */ ++ CAN_FD_SET_FIFO_MASK = 0x20, /* set TSMODE as 1->FIFO mode */ ++ CAN_FD_OFF_FIFO_MASK = 0xdf, /* set TSMODE as 0->Priority mode */ ++ CAN_FD_SET_TSONE_MASK = 0x04, ++ CAN_FD_OFF_TSONE_MASK = 0xfb, ++ CAN_FD_SET_TSALL_MASK = 0x02, ++ CAN_FD_OFF_TSALL_MASK = 0xfd, ++ CAN_FD_LBMEMOD_MASK = 0x40, /* set loop back mode, external */ ++ CAN_FD_LBMIMOD_MASK = 0x20, /* set loopback internal mode */ ++ CAN_FD_SET_BUSOFF_MASK = 0x01, ++ CAN_FD_OFF_BUSOFF_MASK = 0xfe, ++ CAN_FD_SET_TTSEN_MASK = 0x80, /* set ttsen, tts update enable */ ++ CAN_FD_SET_BRS_MASK = 0x10, /* can fd Bit Rate Switch mask */ ++ CAN_FD_OFF_BRS_MASK = 0xef, ++ CAN_FD_SET_EDL_MASK = 0x20, /* Extended Data Length */ ++ CAN_FD_OFF_EDL_MASK = 0xdf, ++ CAN_FD_SET_DLC_MASK = 0x0f, ++ CAN_FD_SET_TENEXT_MASK = 0x40, ++ CAN_FD_SET_IDE_MASK = 0x80, ++ CAN_FD_OFF_IDE_MASK = 0x7f, ++ CAN_FD_SET_RTR_MASK = 0x40, ++ CAN_FD_OFF_RTR_MASK = 0xbf, ++ CAN_FD_INTR_ALL_MASK = 0xff, /* all interrupts enable mask */ ++ CAN_FD_SET_RIE_MASK = 0x80, ++ CAN_FD_OFF_RIE_MASK = 0x7f, ++ CAN_FD_SET_RFIE_MASK = 0x20, ++ CAN_FD_OFF_RFIE_MASK = 0xdf, ++ CAN_FD_SET_RAFIE_MASK = 0x10, ++ CAN_FD_OFF_RAFIE_MASK = 0xef, ++ CAN_FD_SET_EIE_MASK = 0x02, ++ CAN_FD_OFF_EIE_MASK = 0xfd, ++ CAN_FD_TASCTIVE_MASK = 0x02, ++ CAN_FD_RASCTIVE_MASK = 0x04, ++ CAN_FD_SET_TBSEL_MASK = 0x80, /* message writen in STB */ ++ CAN_FD_OFF_TBSEL_MASK = 0x7f, /* message writen in PTB */ ++ CAN_FD_SET_STBY_MASK = 0x20, ++ CAN_FD_OFF_STBY_MASK = 0xdf, ++ CAN_FD_SET_TPE_MASK = 0x10, /* Transmit primary enable */ ++ CAN_FD_SET_TPA_MASK = 0x08, ++ CAN_FD_SET_SACK_MASK = 0x80, ++ CAN_FD_SET_RREL_MASK = 0x10, ++ CAN_FD_RSTAT_NOT_EMPTY_MASK = 0x03, ++ CAN_FD_SET_RIF_MASK = 0x80, ++ CAN_FD_OFF_RIF_MASK = 0x7f, ++ CAN_FD_SET_RAFIF_MASK = 0x10, ++ CAN_FD_SET_RFIF_MASK = 0x20, ++ CAN_FD_SET_TPIF_MASK = 0x08, /* Transmission Primary Interrupt Flag */ ++ CAN_FD_SET_TSIF_MASK = 0x04, ++ CAN_FD_SET_EIF_MASK = 0x02, ++ CAN_FD_SET_AIF_MASK = 0x01, ++ CAN_FD_SET_EWARN_MASK = 0x80, ++ CAN_FD_SET_EPASS_MASK = 0x40, ++ CAN_FD_SET_EPIE_MASK = 0x20, ++ CAN_FD_SET_EPIF_MASK = 0x10, ++ CAN_FD_SET_ALIE_MASK = 0x08, ++ CAN_FD_SET_ALIF_MASK = 0x04, ++ CAN_FD_SET_BEIE_MASK = 0x02, ++ CAN_FD_SET_BEIF_MASK = 0x01, ++ CAN_FD_OFF_EPIE_MASK = 0xdf, ++ CAN_FD_OFF_BEIE_MASK = 0xfd, ++ CAN_FD_SET_AFWL_MASK = 0x40, ++ CAN_FD_SET_EWL_MASK = 0x0b, ++ CAN_FD_SET_KOER_MASK = 0xe0, ++ CAN_FD_SET_BIT_ERROR_MASK = 0x20, ++ CAN_FD_SET_FORM_ERROR_MASK = 0x40, ++ CAN_FD_SET_STUFF_ERROR_MASK = 0x60, ++ CAN_FD_SET_ACK_ERROR_MASK = 0x80, ++ CAN_FD_SET_CRC_ERROR_MASK = 0xa0, ++ CAN_FD_SET_OTH_ERROR_MASK = 0xc0, ++}; ++ ++/* seg1,seg2,sjw,prescaler all have 8 bits */ ++#define BITS_OF_BITTIMING_REG 8 ++ ++/* in can_bittiming strucure every field has 32 bits---->u32 */ ++#define FBITS_IN_BITTIMING_STR 32 ++#define SEG_1_SHIFT 0 ++#define SEG_2_SHIFT 8 ++#define SJW_SHIFT 16 ++#define PRESC_SHIFT 24 ++ ++/* TTSEN bit used for 32 bit register read or write */ ++#define TTSEN_8_32_SHIFT 24 ++#define RTR_32_8_SHIFT 24 ++ ++/* transmit mode */ ++#define XMIT_FULL 0 ++#define XMIT_SEP_FIFO 1 ++#define XMIT_SEP_PRIO 2 ++#define XMIT_PTB_MODE 3 ++ ++enum IPMS_CAN_TYPE { ++ IPMS_CAN_TYPY_CAN = 0, ++ IPMS_CAN_TYPE_CANFD, ++}; ++ ++struct ipms_canfd_priv { ++ struct can_priv can; ++ struct napi_struct napi; ++ struct device *dev; ++ struct regmap *reg_syscon; ++ void __iomem *reg_base; ++ u32 (*read_reg)(const struct ipms_canfd_priv *priv, enum canfd_device_reg reg); ++ void (*write_reg)(const struct ipms_canfd_priv *priv, enum canfd_device_reg reg, u32 val); ++ struct clk *can_clk; ++ u32 tx_mode; ++ struct reset_control *resets; ++ struct clk_bulk_data *clks; ++ int nr_clks; ++ u32 can_or_canfd; ++}; ++ ++static struct can_bittiming_const canfd_bittiming_const = { ++ .name = DRIVER_NAME, ++ .tseg1_min = 2, ++ .tseg1_max = 16, ++ .tseg2_min = 2, ++ .tseg2_max = 8, ++ .sjw_max = 4, ++ .brp_min = 1, ++ .brp_max = 512, ++ .brp_inc = 1, ++ ++}; ++ ++static struct can_bittiming_const canfd_data_bittiming_const = { ++ .name = DRIVER_NAME, ++ .tseg1_min = 1, ++ .tseg1_max = 16, ++ .tseg2_min = 2, ++ .tseg2_max = 8, ++ .sjw_max = 8, ++ .brp_min = 1, ++ .brp_max = 512, ++ .brp_inc = 1, ++}; ++ ++static void canfd_write_reg_le(const struct ipms_canfd_priv *priv, ++ enum canfd_device_reg reg, u32 val) ++{ ++ iowrite32(val, priv->reg_base + reg); ++} ++ ++static u32 canfd_read_reg_le(const struct ipms_canfd_priv *priv, ++ enum canfd_device_reg reg) ++{ ++ return ioread32(priv->reg_base + reg); ++} ++ ++static inline unsigned char can_ioread8(const void *addr) ++{ ++ void *addr_down; ++ union val { ++ u8 val_8[4]; ++ u32 val_32; ++ } val; ++ u32 offset = 0; ++ ++ addr_down = (void *)ALIGN_DOWN((unsigned long)addr, 4); ++ offset = addr - addr_down; ++ val.val_32 = ioread32(addr_down); ++ return val.val_8[offset]; ++} ++ ++static inline void can_iowrite8(unsigned char value, void *addr) ++{ ++ void *addr_down; ++ union val { ++ u8 val_8[4]; ++ u32 val_32; ++ } val; ++ u8 offset = 0; ++ ++ addr_down = (void *)ALIGN_DOWN((unsigned long)addr, 4); ++ offset = addr - addr_down; ++ val.val_32 = ioread32(addr_down); ++ val.val_8[offset] = value; ++ iowrite32(val.val_32, addr_down); ++} ++ ++static void canfd_reigister_set_bit(const struct ipms_canfd_priv *priv, ++ enum canfd_device_reg reg, ++ enum canfd_reg_bitchange set_mask) ++{ ++ void *addr_down; ++ union val { ++ u8 val_8[4]; ++ u32 val_32; ++ } val; ++ u8 offset = 0; ++ ++ addr_down = (void *)ALIGN_DOWN((unsigned long)(priv->reg_base + reg), 4); ++ offset = (priv->reg_base + reg) - addr_down; ++ val.val_32 = ioread32(addr_down); ++ val.val_8[offset] |= set_mask; ++ iowrite32(val.val_32, addr_down); ++} ++ ++static void canfd_reigister_off_bit(const struct ipms_canfd_priv *priv, ++ enum canfd_device_reg reg, ++ enum canfd_reg_bitchange set_mask) ++{ ++ void *addr_down; ++ union val { ++ u8 val_8[4]; ++ u32 val_32; ++ } val; ++ u8 offset = 0; ++ ++ addr_down = (void *)ALIGN_DOWN((unsigned long)(priv->reg_base + reg), 4); ++ offset = (priv->reg_base + reg) - addr_down; ++ val.val_32 = ioread32(addr_down); ++ val.val_8[offset] &= set_mask; ++ iowrite32(val.val_32, addr_down); ++} ++ ++static int canfd_device_driver_bittime_configuration(struct net_device *ndev) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ struct can_bittiming *bt = &priv->can.bittiming; ++ struct can_bittiming *dbt = &priv->can.data_bittiming; ++ u32 reset_test, bittiming_temp, dat_bittiming; ++ ++ reset_test = can_ioread8(priv->reg_base + CANFD_CFG_STAT_OFFSET); ++ ++ if (!(reset_test & CAN_FD_SET_RST_MASK)) { ++ netdev_alert(ndev, "Not in reset mode, cannot set bit timing\n"); ++ return -EPERM; ++ } ++ ++ bittiming_temp = ((bt->phase_seg1 + bt->prop_seg + 1 - 2) << SEG_1_SHIFT) | ++ ((bt->phase_seg2 - 1) << SEG_2_SHIFT) | ++ ((bt->sjw - 1) << SJW_SHIFT) | ++ ((bt->brp - 1) << PRESC_SHIFT); ++ ++ /* Check the bittime parameter */ ++ if ((((int)(bt->phase_seg1 + bt->prop_seg + 1) - 2) < 0) || ++ (((int)(bt->phase_seg2) - 1) < 0) || ++ (((int)(bt->sjw) - 1) < 0) || ++ (((int)(bt->brp) - 1) < 0)) ++ return -EINVAL; ++ ++ priv->write_reg(priv, CANFD_S_SEG_1_OFFSET, bittiming_temp); ++ ++ if (priv->can_or_canfd == IPMS_CAN_TYPE_CANFD) { ++ dat_bittiming = ((dbt->phase_seg1 + dbt->prop_seg + 1 - 2) << SEG_1_SHIFT) | ++ ((dbt->phase_seg2 - 1) << SEG_2_SHIFT) | ++ ((dbt->sjw - 1) << SJW_SHIFT) | ++ ((dbt->brp - 1) << PRESC_SHIFT); ++ ++ if ((((int)(dbt->phase_seg1 + dbt->prop_seg + 1) - 2) < 0) || ++ (((int)(dbt->phase_seg2) - 1) < 0) || ++ (((int)(dbt->sjw) - 1) < 0) || ++ (((int)(dbt->brp) - 1) < 0)) ++ return -EINVAL; ++ ++ priv->write_reg(priv, CANFD_F_SEG_1_OFFSET, dat_bittiming); ++ } ++ ++ canfd_reigister_off_bit(priv, CANFD_CFG_STAT_OFFSET, CAN_FD_OFF_RST_MASK); ++ ++ netdev_dbg(ndev, "Slow bit rate: %08x\n", priv->read_reg(priv, CANFD_S_SEG_1_OFFSET)); ++ netdev_dbg(ndev, "Fast bit rate: %08x\n", priv->read_reg(priv, CANFD_F_SEG_1_OFFSET)); ++ ++ return 0; ++} ++ ++int canfd_get_freebuffer(struct ipms_canfd_priv *priv) ++{ ++ /* Get next transmit buffer */ ++ canfd_reigister_set_bit(priv, CANFD_TCTRL_OFFSET, CAN_FD_SET_TENEXT_MASK); ++ ++ if (can_ioread8(priv->reg_base + CANFD_TCTRL_OFFSET) & CAN_FD_SET_TENEXT_MASK) ++ return -1; ++ ++ return 0; ++} ++ ++static void canfd_tx_interrupt(struct net_device *ndev, u8 isr) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ ++ /* wait till transmission of the PTB or STB finished */ ++ while (isr & (CAN_FD_SET_TPIF_MASK | CAN_FD_SET_TSIF_MASK)) { ++ if (isr & CAN_FD_SET_TPIF_MASK) ++ canfd_reigister_set_bit(priv, CANFD_RTIF_OFFSET, CAN_FD_SET_TPIF_MASK); ++ ++ if (isr & CAN_FD_SET_TSIF_MASK) ++ canfd_reigister_set_bit(priv, CANFD_RTIF_OFFSET, CAN_FD_SET_TSIF_MASK); ++ ++ isr = can_ioread8(priv->reg_base + CANFD_RTIF_OFFSET); ++ } ++ netif_wake_queue(ndev); ++} ++ ++static int can_rx(struct net_device *ndev) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ struct net_device_stats *stats = &ndev->stats; ++ struct can_frame *cf; ++ struct sk_buff *skb; ++ u32 can_id; ++ u8 dlc, control, rx_status; ++ ++ rx_status = can_ioread8(priv->reg_base + CANFD_RCTRL_OFFSET); ++ ++ if (!(rx_status & CAN_FD_RSTAT_NOT_EMPTY_MASK)) ++ return 0; ++ control = can_ioread8(priv->reg_base + CANFD_RBUF_CTL_OFFSET); ++ can_id = priv->read_reg(priv, CANFD_RUBF_ID_OFFSET); ++ dlc = can_ioread8(priv->reg_base + CANFD_RBUF_CTL_OFFSET) & CAN_FD_SET_DLC_MASK; ++ ++ skb = alloc_can_skb(ndev, (struct can_frame **)&cf); ++ if (!skb) { ++ stats->rx_dropped++; ++ return 0; ++ } ++ cf->can_dlc = can_cc_dlc2len(dlc); ++ ++ /* change the CANFD id into socketcan id format */ ++ if (control & CAN_FD_SET_IDE_MASK) { ++ cf->can_id = can_id; ++ cf->can_id |= CAN_EFF_FLAG; ++ } else { ++ cf->can_id = can_id; ++ cf->can_id &= (~CAN_EFF_FLAG); ++ } ++ ++ if (control & CAN_FD_SET_RTR_MASK) ++ cf->can_id |= CAN_RTR_FLAG; ++ ++ if (!(control & CAN_FD_SET_RTR_MASK)) { ++ *((u32 *)(cf->data + 0)) = priv->read_reg(priv, CANFD_RBUF_DATA_OFFSET); ++ *((u32 *)(cf->data + 4)) = priv->read_reg(priv, CANFD_RBUF_DATA_OFFSET + 4); ++ } ++ ++ canfd_reigister_set_bit(priv, CANFD_RCTRL_OFFSET, CAN_FD_SET_RREL_MASK); ++ stats->rx_bytes += can_fd_dlc2len(cf->can_dlc); ++ stats->rx_packets++; ++ netif_receive_skb(skb); ++ ++ return 1; ++} ++ ++static int canfd_rx(struct net_device *ndev) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ struct net_device_stats *stats = &ndev->stats; ++ struct canfd_frame *cf; ++ struct sk_buff *skb; ++ u32 can_id; ++ u8 dlc, control, rx_status; ++ int i; ++ ++ rx_status = can_ioread8(priv->reg_base + CANFD_RCTRL_OFFSET); ++ ++ if (!(rx_status & CAN_FD_RSTAT_NOT_EMPTY_MASK)) ++ return 0; ++ control = can_ioread8(priv->reg_base + CANFD_RBUF_CTL_OFFSET); ++ can_id = priv->read_reg(priv, CANFD_RUBF_ID_OFFSET); ++ dlc = can_ioread8(priv->reg_base + CANFD_RBUF_CTL_OFFSET) & CAN_FD_SET_DLC_MASK; ++ ++ if (control & CAN_FD_SET_EDL_MASK) ++ /* allocate sk_buffer for canfd frame */ ++ skb = alloc_canfd_skb(ndev, &cf); ++ else ++ /* allocate sk_buffer for can frame */ ++ skb = alloc_can_skb(ndev, (struct can_frame **)&cf); ++ ++ if (!skb) { ++ stats->rx_dropped++; ++ return 0; ++ } ++ ++ /* change the CANFD or CAN2.0 data into socketcan data format */ ++ if (control & CAN_FD_SET_EDL_MASK) ++ cf->len = can_fd_dlc2len(dlc); ++ else ++ cf->len = can_cc_dlc2len(dlc); ++ ++ /* change the CANFD id into socketcan id format */ ++ if (control & CAN_FD_SET_EDL_MASK) { ++ cf->can_id = can_id; ++ if (control & CAN_FD_SET_IDE_MASK) ++ cf->can_id |= CAN_EFF_FLAG; ++ else ++ cf->can_id &= (~CAN_EFF_FLAG); ++ } else { ++ cf->can_id = can_id; ++ if (control & CAN_FD_SET_IDE_MASK) ++ cf->can_id |= CAN_EFF_FLAG; ++ else ++ cf->can_id &= (~CAN_EFF_FLAG); ++ ++ if (control & CAN_FD_SET_RTR_MASK) ++ cf->can_id |= CAN_RTR_FLAG; ++ } ++ ++ /* CANFD frames handed over to SKB */ ++ if (control & CAN_FD_SET_EDL_MASK) { ++ for (i = 0; i < cf->len; i += 4) ++ *((u32 *)(cf->data + i)) = priv->read_reg(priv, CANFD_RBUF_DATA_OFFSET + i); ++ } else { ++ /* skb reads the received datas, if the RTR bit not set */ ++ if (!(control & CAN_FD_SET_RTR_MASK)) { ++ *((u32 *)(cf->data + 0)) = priv->read_reg(priv, CANFD_RBUF_DATA_OFFSET); ++ *((u32 *)(cf->data + 4)) = priv->read_reg(priv, CANFD_RBUF_DATA_OFFSET + 4); ++ } ++ } ++ ++ canfd_reigister_set_bit(priv, CANFD_RCTRL_OFFSET, CAN_FD_SET_RREL_MASK); ++ ++ stats->rx_bytes += cf->len; ++ stats->rx_packets++; ++ netif_receive_skb(skb); ++ ++ return 1; ++} ++ ++static int canfd_rx_poll(struct napi_struct *napi, int quota) ++{ ++ struct net_device *ndev = napi->dev; ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ int work_done = 0; ++ u8 rx_status = 0, control = 0; ++ ++ control = can_ioread8(priv->reg_base + CANFD_RBUF_CTL_OFFSET); ++ rx_status = can_ioread8(priv->reg_base + CANFD_RCTRL_OFFSET); ++ ++ /* clear receive interrupt and deal with all the received frames */ ++ while ((rx_status & CAN_FD_RSTAT_NOT_EMPTY_MASK) && (work_done < quota)) { ++ (control & CAN_FD_SET_EDL_MASK) ? (work_done += canfd_rx(ndev)) : (work_done += can_rx(ndev)); ++ ++ control = can_ioread8(priv->reg_base + CANFD_RBUF_CTL_OFFSET); ++ rx_status = can_ioread8(priv->reg_base + CANFD_RCTRL_OFFSET); ++ } ++ napi_complete(napi); ++ canfd_reigister_set_bit(priv, CANFD_RTIE_OFFSET, CAN_FD_SET_RIE_MASK); ++ return work_done; ++} ++ ++static void canfd_rxfull_interrupt(struct net_device *ndev, u8 isr) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ ++ if (isr & CAN_FD_SET_RAFIF_MASK) ++ canfd_reigister_set_bit(priv, CANFD_RTIF_OFFSET, CAN_FD_SET_RAFIF_MASK); ++ ++ if (isr & (CAN_FD_SET_RAFIF_MASK | CAN_FD_SET_RFIF_MASK)) ++ canfd_reigister_set_bit(priv, CANFD_RTIF_OFFSET, ++ (CAN_FD_SET_RAFIF_MASK | CAN_FD_SET_RFIF_MASK)); ++} ++ ++static int set_canfd_xmit_mode(struct net_device *ndev) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ ++ switch (priv->tx_mode) { ++ case XMIT_FULL: ++ canfd_reigister_set_bit(priv, CANFD_TCTRL_OFFSET, CAN_FD_SET_FULLCAN_MASK); ++ break; ++ case XMIT_SEP_FIFO: ++ canfd_reigister_off_bit(priv, CANFD_TCTRL_OFFSET, CAN_FD_OFF_FULLCAN_MASK); ++ canfd_reigister_set_bit(priv, CANFD_TCTRL_OFFSET, CAN_FD_SET_FIFO_MASK); ++ canfd_reigister_off_bit(priv, CANFD_TCMD_OFFSET, CAN_FD_SET_TBSEL_MASK); ++ break; ++ case XMIT_SEP_PRIO: ++ canfd_reigister_off_bit(priv, CANFD_TCTRL_OFFSET, CAN_FD_OFF_FULLCAN_MASK); ++ canfd_reigister_off_bit(priv, CANFD_TCTRL_OFFSET, CAN_FD_OFF_FIFO_MASK); ++ canfd_reigister_off_bit(priv, CANFD_TCMD_OFFSET, CAN_FD_SET_TBSEL_MASK); ++ break; ++ case XMIT_PTB_MODE: ++ canfd_reigister_off_bit(priv, CANFD_TCMD_OFFSET, CAN_FD_OFF_TBSEL_MASK); ++ break; ++ default: ++ break; ++ } ++ return 0; ++} ++ ++static netdev_tx_t canfd_driver_start_xmit(struct sk_buff *skb, struct net_device *ndev) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ struct canfd_frame *cf = (struct canfd_frame *)skb->data; ++ struct net_device_stats *stats = &ndev->stats; ++ u32 ttsen, id, ctl, addr_off; ++ int i; ++ ++ priv->tx_mode = XMIT_PTB_MODE; ++ ++ if (can_dropped_invalid_skb(ndev, skb)) ++ return NETDEV_TX_OK; ++ ++ switch (priv->tx_mode) { ++ case XMIT_FULL: ++ return NETDEV_TX_BUSY; ++ case XMIT_PTB_MODE: ++ set_canfd_xmit_mode(ndev); ++ canfd_reigister_off_bit(priv, CANFD_TCMD_OFFSET, CAN_FD_OFF_STBY_MASK); ++ ++ if (cf->can_id & CAN_EFF_FLAG) { ++ id = (cf->can_id & CAN_EFF_MASK); ++ ttsen = 0 << TTSEN_8_32_SHIFT; ++ id |= ttsen; ++ } else { ++ id = (cf->can_id & CAN_SFF_MASK); ++ ttsen = 0 << TTSEN_8_32_SHIFT; ++ id |= ttsen; ++ } ++ ++ ctl = can_fd_len2dlc(cf->len); ++ ++ /* transmit can fd frame */ ++ if (priv->can_or_canfd == IPMS_CAN_TYPE_CANFD) { ++ if (can_is_canfd_skb(skb)) { ++ if (cf->can_id & CAN_EFF_FLAG) ++ ctl |= CAN_FD_SET_IDE_MASK; ++ else ++ ctl &= CAN_FD_OFF_IDE_MASK; ++ ++ if (cf->flags & CANFD_BRS) ++ ctl |= CAN_FD_SET_BRS_MASK; ++ ++ ctl |= CAN_FD_SET_EDL_MASK; ++ ++ addr_off = CANFD_TBUF_DATA_OFFSET; ++ ++ for (i = 0; i < cf->len; i += 4) { ++ priv->write_reg(priv, addr_off, ++ *((u32 *)(cf->data + i))); ++ addr_off += 4; ++ } ++ } else { ++ ctl &= CAN_FD_OFF_EDL_MASK; ++ ctl &= CAN_FD_OFF_BRS_MASK; ++ ++ if (cf->can_id & CAN_EFF_FLAG) ++ ctl |= CAN_FD_SET_IDE_MASK; ++ else ++ ctl &= CAN_FD_OFF_IDE_MASK; ++ ++ if (cf->can_id & CAN_RTR_FLAG) { ++ ctl |= CAN_FD_SET_RTR_MASK; ++ priv->write_reg(priv, ++ CANFD_TBUF_ID_OFFSET, id); ++ priv->write_reg(priv, ++ CANFD_TBUF_CTL_OFFSET, ctl); ++ } else { ++ ctl &= CAN_FD_OFF_RTR_MASK; ++ addr_off = CANFD_TBUF_DATA_OFFSET; ++ priv->write_reg(priv, addr_off, ++ *((u32 *)(cf->data + 0))); ++ priv->write_reg(priv, addr_off + 4, ++ *((u32 *)(cf->data + 4))); ++ } ++ } ++ priv->write_reg(priv, CANFD_TBUF_ID_OFFSET, id); ++ priv->write_reg(priv, CANFD_TBUF_CTL_OFFSET, ctl); ++ addr_off = CANFD_TBUF_DATA_OFFSET; ++ } else { ++ ctl &= CAN_FD_OFF_EDL_MASK; ++ ctl &= CAN_FD_OFF_BRS_MASK; ++ ++ if (cf->can_id & CAN_EFF_FLAG) ++ ctl |= CAN_FD_SET_IDE_MASK; ++ else ++ ctl &= CAN_FD_OFF_IDE_MASK; ++ ++ if (cf->can_id & CAN_RTR_FLAG) { ++ ctl |= CAN_FD_SET_RTR_MASK; ++ priv->write_reg(priv, CANFD_TBUF_ID_OFFSET, id); ++ priv->write_reg(priv, CANFD_TBUF_CTL_OFFSET, ctl); ++ } else { ++ ctl &= CAN_FD_OFF_RTR_MASK; ++ priv->write_reg(priv, CANFD_TBUF_ID_OFFSET, id); ++ priv->write_reg(priv, CANFD_TBUF_CTL_OFFSET, ctl); ++ addr_off = CANFD_TBUF_DATA_OFFSET; ++ priv->write_reg(priv, addr_off, ++ *((u32 *)(cf->data + 0))); ++ priv->write_reg(priv, addr_off + 4, ++ *((u32 *)(cf->data + 4))); ++ } ++ } ++ canfd_reigister_set_bit(priv, CANFD_TCMD_OFFSET, CAN_FD_SET_TPE_MASK); ++ stats->tx_bytes += cf->len; ++ break; ++ default: ++ break; ++ } ++ ++ /*Due to cache blocking, we need call dev_kfree_skb() here to free the socket ++ buffer and return NETDEV_TX_OK */ ++ dev_kfree_skb(skb); ++ ++ return NETDEV_TX_OK; ++} ++ ++static int set_reset_mode(struct net_device *ndev) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ u8 ret; ++ ++ ret = can_ioread8(priv->reg_base + CANFD_CFG_STAT_OFFSET); ++ ret |= CAN_FD_SET_RST_MASK; ++ can_iowrite8(ret, priv->reg_base + CANFD_CFG_STAT_OFFSET); ++ ++ return 0; ++} ++ ++static void canfd_driver_stop(struct net_device *ndev) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ int ret; ++ ++ ret = set_reset_mode(ndev); ++ if (ret) ++ netdev_err(ndev, "Mode Resetting Failed!\n"); ++ ++ priv->can.state = CAN_STATE_STOPPED; ++} ++ ++static int canfd_driver_close(struct net_device *ndev) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ ++ netif_stop_queue(ndev); ++ napi_disable(&priv->napi); ++ canfd_driver_stop(ndev); ++ ++ free_irq(ndev->irq, ndev); ++ close_candev(ndev); ++ ++ pm_runtime_put(priv->dev); ++ ++ return 0; ++} ++ ++static enum can_state get_of_chip_status(struct net_device *ndev) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ u8 can_stat, eir; ++ ++ can_stat = can_ioread8(priv->reg_base + CANFD_CFG_STAT_OFFSET); ++ eir = can_ioread8(priv->reg_base + CANFD_ERRINT_OFFSET); ++ ++ if (can_stat & CAN_FD_SET_BUSOFF_MASK) ++ return CAN_STATE_BUS_OFF; ++ ++ if ((eir & CAN_FD_SET_EPASS_MASK) && ~(can_stat & CAN_FD_SET_BUSOFF_MASK)) ++ return CAN_STATE_ERROR_PASSIVE; ++ ++ if (eir & CAN_FD_SET_EWARN_MASK && ~(eir & CAN_FD_SET_EPASS_MASK)) ++ return CAN_STATE_ERROR_WARNING; ++ ++ if (~(eir & CAN_FD_SET_EPASS_MASK)) ++ return CAN_STATE_ERROR_ACTIVE; ++ ++ return CAN_STATE_ERROR_ACTIVE; ++} ++ ++static void canfd_error_interrupt(struct net_device *ndev, u8 isr, u8 eir) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ struct net_device_stats *stats = &ndev->stats; ++ struct can_frame *cf; ++ struct sk_buff *skb; ++ u8 koer, recnt = 0, tecnt = 0, can_stat = 0; ++ ++ skb = alloc_can_err_skb(ndev, &cf); ++ ++ koer = can_ioread8(priv->reg_base + CANFD_EALCAP_OFFSET) & CAN_FD_SET_KOER_MASK; ++ recnt = can_ioread8(priv->reg_base + CANFD_RECNT_OFFSET); ++ tecnt = can_ioread8(priv->reg_base + CANFD_TECNT_OFFSET); ++ ++ /*Read can status*/ ++ can_stat = can_ioread8(priv->reg_base + CANFD_CFG_STAT_OFFSET); ++ ++ /* Bus off --->active error mode */ ++ if ((isr & CAN_FD_SET_EIF_MASK) && priv->can.state == CAN_STATE_BUS_OFF) ++ priv->can.state = get_of_chip_status(ndev); ++ ++ /* State selection */ ++ if (can_stat & CAN_FD_SET_BUSOFF_MASK) { ++ priv->can.state = get_of_chip_status(ndev); ++ priv->can.can_stats.bus_off++; ++ canfd_reigister_set_bit(priv, CANFD_CFG_STAT_OFFSET, CAN_FD_SET_BUSOFF_MASK); ++ can_bus_off(ndev); ++ if (skb) ++ cf->can_id |= CAN_ERR_BUSOFF; ++ ++ } else if ((eir & CAN_FD_SET_EPASS_MASK) && ~(can_stat & CAN_FD_SET_BUSOFF_MASK)) { ++ priv->can.state = get_of_chip_status(ndev); ++ priv->can.can_stats.error_passive++; ++ if (skb) { ++ cf->can_id |= CAN_ERR_CRTL; ++ cf->data[1] |= (recnt > 127) ? CAN_ERR_CRTL_RX_PASSIVE : 0; ++ cf->data[1] |= (tecnt > 127) ? CAN_ERR_CRTL_TX_PASSIVE : 0; ++ cf->data[6] = tecnt; ++ cf->data[7] = recnt; ++ } ++ } else if (eir & CAN_FD_SET_EWARN_MASK && ~(eir & CAN_FD_SET_EPASS_MASK)) { ++ priv->can.state = get_of_chip_status(ndev); ++ priv->can.can_stats.error_warning++; ++ if (skb) { ++ cf->can_id |= CAN_ERR_CRTL; ++ cf->data[1] |= (recnt > 95) ? CAN_ERR_CRTL_RX_WARNING : 0; ++ cf->data[1] |= (tecnt > 95) ? CAN_ERR_CRTL_TX_WARNING : 0; ++ cf->data[6] = tecnt; ++ cf->data[7] = recnt; ++ } ++ } ++ ++ /* Check for in protocol defined error interrupt */ ++ if (eir & CAN_FD_SET_BEIF_MASK) { ++ if (skb) ++ cf->can_id |= CAN_ERR_BUSERROR | CAN_ERR_PROT; ++ ++ /* bit error interrupt */ ++ if (koer == CAN_FD_SET_BIT_ERROR_MASK) { ++ stats->tx_errors++; ++ if (skb) { ++ cf->can_id |= CAN_ERR_PROT; ++ cf->data[2] = CAN_ERR_PROT_BIT; ++ } ++ } ++ /* format error interrupt */ ++ if (koer == CAN_FD_SET_FORM_ERROR_MASK) { ++ stats->rx_errors++; ++ if (skb) { ++ cf->can_id |= CAN_ERR_PROT; ++ cf->data[2] = CAN_ERR_PROT_FORM; ++ } ++ } ++ /* stuffing error interrupt */ ++ if (koer == CAN_FD_SET_STUFF_ERROR_MASK) { ++ stats->rx_errors++; ++ if (skb) { ++ cf->can_id |= CAN_ERR_PROT; ++ cf->data[3] = CAN_ERR_PROT_STUFF; ++ } ++ } ++ /* ack error interrupt */ ++ if (koer == CAN_FD_SET_ACK_ERROR_MASK) { ++ stats->tx_errors++; ++ if (skb) { ++ cf->can_id |= CAN_ERR_PROT; ++ cf->data[2] = CAN_ERR_PROT_LOC_ACK; ++ } ++ } ++ /* crc error interrupt */ ++ if (koer == CAN_FD_SET_CRC_ERROR_MASK) { ++ stats->rx_errors++; ++ if (skb) { ++ cf->can_id |= CAN_ERR_PROT; ++ cf->data[2] = CAN_ERR_PROT_LOC_CRC_SEQ; ++ } ++ } ++ priv->can.can_stats.bus_error++; ++ } ++ if (skb) { ++ stats->rx_packets++; ++ stats->rx_bytes += cf->can_dlc; ++ netif_rx(skb); ++ } ++ ++ netdev_dbg(ndev, "Recnt is 0x%02x", can_ioread8(priv->reg_base + CANFD_RECNT_OFFSET)); ++ netdev_dbg(ndev, "Tecnt is 0x%02x", can_ioread8(priv->reg_base + CANFD_TECNT_OFFSET)); ++} ++ ++static irqreturn_t canfd_interrupt(int irq, void *dev_id) ++{ ++ struct net_device *ndev = (struct net_device *)dev_id; ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ u8 isr, eir; ++ u8 isr_handled = 0, eir_handled = 0; ++ ++ /* read the value of interrupt status register */ ++ isr = can_ioread8(priv->reg_base + CANFD_RTIF_OFFSET); ++ ++ /* read the value of error interrupt register */ ++ eir = can_ioread8(priv->reg_base + CANFD_ERRINT_OFFSET); ++ ++ /* Check for Tx interrupt and Processing it */ ++ if (isr & (CAN_FD_SET_TPIF_MASK | CAN_FD_SET_TSIF_MASK)) { ++ canfd_tx_interrupt(ndev, isr); ++ isr_handled |= (CAN_FD_SET_TPIF_MASK | CAN_FD_SET_TSIF_MASK); ++ } ++ if (isr & (CAN_FD_SET_RAFIF_MASK | CAN_FD_SET_RFIF_MASK)) { ++ canfd_rxfull_interrupt(ndev, isr); ++ isr_handled |= (CAN_FD_SET_RAFIF_MASK | CAN_FD_SET_RFIF_MASK); ++ } ++ /* Check Rx interrupt and Processing the receive interrupt routine */ ++ if (isr & CAN_FD_SET_RIF_MASK) { ++ canfd_reigister_off_bit(priv, CANFD_RTIE_OFFSET, CAN_FD_OFF_RIE_MASK); ++ canfd_reigister_set_bit(priv, CANFD_RTIF_OFFSET, CAN_FD_SET_RIF_MASK); ++ ++ napi_schedule(&priv->napi); ++ isr_handled |= CAN_FD_SET_RIF_MASK; ++ } ++ if ((isr & CAN_FD_SET_EIF_MASK) | (eir & (CAN_FD_SET_EPIF_MASK | CAN_FD_SET_BEIF_MASK))) { ++ /* reset EPIF and BEIF. Reset EIF */ ++ canfd_reigister_set_bit(priv, CANFD_ERRINT_OFFSET, ++ eir & (CAN_FD_SET_EPIF_MASK | CAN_FD_SET_BEIF_MASK)); ++ canfd_reigister_set_bit(priv, CANFD_RTIF_OFFSET, ++ isr & CAN_FD_SET_EIF_MASK); ++ ++ canfd_error_interrupt(ndev, isr, eir); ++ ++ isr_handled |= CAN_FD_SET_EIF_MASK; ++ eir_handled |= (CAN_FD_SET_EPIF_MASK | CAN_FD_SET_BEIF_MASK); ++ } ++ if ((isr_handled == 0) && (eir_handled == 0)) { ++ netdev_err(ndev, "Unhandled interrupt!\n"); ++ return IRQ_NONE; ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static int canfd_chip_start(struct net_device *ndev) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ int err; ++ u8 ret; ++ ++ err = set_reset_mode(ndev); ++ if (err) { ++ netdev_err(ndev, "Mode Resetting Failed!\n"); ++ return err; ++ } ++ ++ err = canfd_device_driver_bittime_configuration(ndev); ++ if (err) { ++ netdev_err(ndev, "Bittime Setting Failed!\n"); ++ return err; ++ } ++ ++ /* Set Almost Full Warning Limit */ ++ canfd_reigister_set_bit(priv, CANFD_LIMIT_OFFSET, CAN_FD_SET_AFWL_MASK); ++ ++ /* Programmable Error Warning Limit = (EWL+1)*8. Set EWL=11->Error Warning=96 */ ++ canfd_reigister_set_bit(priv, CANFD_LIMIT_OFFSET, CAN_FD_SET_EWL_MASK); ++ ++ /* Interrupts enable */ ++ can_iowrite8(CAN_FD_INTR_ALL_MASK, priv->reg_base + CANFD_RTIE_OFFSET); ++ ++ /* Error Interrupts enable(Error Passive and Bus Error) */ ++ canfd_reigister_set_bit(priv, CANFD_ERRINT_OFFSET, CAN_FD_SET_EPIE_MASK); ++ ++ ret = can_ioread8(priv->reg_base + CANFD_CFG_STAT_OFFSET); ++ ++ /* Check whether it is loopback mode or normal mode */ ++ if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) { ++ ret |= CAN_FD_LBMIMOD_MASK; ++ } else { ++ ret &= ~CAN_FD_LBMEMOD_MASK; ++ ret &= ~CAN_FD_LBMIMOD_MASK; ++ } ++ ++ can_iowrite8(ret, priv->reg_base + CANFD_CFG_STAT_OFFSET); ++ ++ priv->can.state = CAN_STATE_ERROR_ACTIVE; ++ ++ return 0; ++} ++ ++static int canfd_do_set_mode(struct net_device *ndev, enum can_mode mode) ++{ ++ int ret; ++ ++ switch (mode) { ++ case CAN_MODE_START: ++ ret = canfd_chip_start(ndev); ++ if (ret) { ++ netdev_err(ndev, "Could Not Start CAN device !!\n"); ++ return ret; ++ } ++ netif_wake_queue(ndev); ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int canfd_driver_open(struct net_device *ndev) ++{ ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ int ret; ++ ++ ret = pm_runtime_get_sync(priv->dev); ++ if (ret < 0) { ++ netdev_err(ndev, "%s: pm_runtime_get failed(%d)\n", ++ __func__, ret); ++ goto err; ++ } ++ ++ /* Set chip into reset mode */ ++ ret = set_reset_mode(ndev); ++ if (ret) { ++ netdev_err(ndev, "Mode Resetting Failed!\n"); ++ return ret; ++ } ++ ++ /* Common open */ ++ ret = open_candev(ndev); ++ if (ret) ++ return ret; ++ ++ /* Register interrupt handler */ ++ ret = request_irq(ndev->irq, canfd_interrupt, IRQF_SHARED, ndev->name, ndev); ++ if (ret) { ++ netdev_err(ndev, "Request_irq err: %d\n", ret); ++ goto exit_irq; ++ } ++ ++ ret = canfd_chip_start(ndev); ++ if (ret) { ++ netdev_err(ndev, "Could Not Start CAN device !\n"); ++ goto exit_can_start; ++ } ++ ++ napi_enable(&priv->napi); ++ netif_start_queue(ndev); ++ ++ return 0; ++ ++exit_can_start: ++ free_irq(ndev->irq, ndev); ++err: ++ pm_runtime_put(priv->dev); ++exit_irq: ++ close_candev(ndev); ++ return ret; ++} ++ ++static int canfd_control_parse_dt(struct ipms_canfd_priv *priv) ++{ ++ struct of_phandle_args args; ++ u32 syscon_mask, syscon_shift; ++ u32 can_or_canfd; ++ u32 syscon_offset, regval; ++ int ret; ++ ++ ret = of_parse_phandle_with_fixed_args(priv->dev->of_node, ++ "starfive,sys-syscon", 3, 0, &args); ++ if (ret) { ++ dev_err(priv->dev, "Failed to parse starfive,sys-syscon\n"); ++ return -EINVAL; ++ } ++ ++ priv->reg_syscon = syscon_node_to_regmap(args.np); ++ of_node_put(args.np); ++ if (IS_ERR(priv->reg_syscon)) ++ return PTR_ERR(priv->reg_syscon); ++ ++ syscon_offset = args.args[0]; ++ syscon_shift = args.args[1]; ++ syscon_mask = args.args[2]; ++ ++ ret = device_property_read_u32(priv->dev, "syscon,can_or_canfd", &can_or_canfd); ++ if (ret) ++ goto exit_parse; ++ ++ priv->can_or_canfd = can_or_canfd; ++ ++ /* enable can2.0/canfd function */ ++ regval = can_or_canfd << syscon_shift; ++ ret = regmap_update_bits(priv->reg_syscon, syscon_offset, syscon_mask, regval); ++ if (ret) ++ return ret; ++ return 0; ++exit_parse: ++ return ret; ++} ++ ++static const struct net_device_ops canfd_netdev_ops = { ++ .ndo_open = canfd_driver_open, ++ .ndo_stop = canfd_driver_close, ++ .ndo_start_xmit = canfd_driver_start_xmit, ++ .ndo_change_mtu = can_change_mtu, ++}; ++ ++static int canfd_driver_probe(struct platform_device *pdev) ++{ ++ struct net_device *ndev; ++ struct ipms_canfd_priv *priv; ++ void __iomem *addr; ++ int ret; ++ u32 frq; ++ ++ addr = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(addr)) { ++ ret = PTR_ERR(addr); ++ goto exit; ++ } ++ ++ ndev = alloc_candev(sizeof(struct ipms_canfd_priv), 1); ++ if (!ndev) { ++ ret = -ENOMEM; ++ goto exit; ++ } ++ ++ priv = netdev_priv(ndev); ++ priv->dev = &pdev->dev; ++ ++ ret = canfd_control_parse_dt(priv); ++ if (ret) ++ goto free_exit; ++ ++ priv->nr_clks = devm_clk_bulk_get_all(priv->dev, &priv->clks); ++ if (priv->nr_clks < 0) { ++ dev_err(priv->dev, "Failed to get can clocks\n"); ++ ret = -ENODEV; ++ goto free_exit; ++ } ++ ++ ret = clk_bulk_prepare_enable(priv->nr_clks, priv->clks); ++ if (ret) { ++ dev_err(priv->dev, "Failed to enable clocks\n"); ++ goto free_exit; ++ } ++ ++ priv->resets = devm_reset_control_array_get_exclusive(priv->dev); ++ if (IS_ERR(priv->resets)) { ++ ret = PTR_ERR(priv->resets); ++ dev_err(priv->dev, "Failed to get can resets"); ++ goto clk_exit; ++ } ++ ++ ret = reset_control_deassert(priv->resets); ++ if (ret) ++ goto clk_exit; ++ priv->can.bittiming_const = &canfd_bittiming_const; ++ priv->can.data_bittiming_const = &canfd_data_bittiming_const; ++ priv->can.do_set_mode = canfd_do_set_mode; ++ ++ /* in user space the execution mode can be chosen */ ++ if (priv->can_or_canfd == IPMS_CAN_TYPE_CANFD) ++ priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_FD; ++ else ++ priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK; ++ priv->reg_base = addr; ++ priv->write_reg = canfd_write_reg_le; ++ priv->read_reg = canfd_read_reg_le; ++ ++ pm_runtime_enable(&pdev->dev); ++ ++ priv->can_clk = devm_clk_get(&pdev->dev, "core_clk"); ++ if (IS_ERR(priv->can_clk)) { ++ dev_err(&pdev->dev, "Device clock not found.\n"); ++ ret = PTR_ERR(priv->can_clk); ++ goto reset_exit; ++ } ++ ++ device_property_read_u32(priv->dev, "frequency", &frq); ++ clk_set_rate(priv->can_clk, frq); ++ ++ priv->can.clock.freq = clk_get_rate(priv->can_clk); ++ ndev->irq = platform_get_irq(pdev, 0); ++ ++ /* we support local echo */ ++ ndev->flags |= IFF_ECHO; ++ ndev->netdev_ops = &canfd_netdev_ops; ++ ++ platform_set_drvdata(pdev, ndev); ++ SET_NETDEV_DEV(ndev, &pdev->dev); ++ ++ netif_napi_add(ndev, &priv->napi, canfd_rx_poll); ++ ret = register_candev(ndev); ++ if (ret) { ++ dev_err(&pdev->dev, "Fail to register failed (err=%d)\n", ret); ++ goto reset_exit; ++ } ++ ++ dev_dbg(&pdev->dev, "Driver registered: regs=%p, irp=%d, clock=%d\n", ++ priv->reg_base, ndev->irq, priv->can.clock.freq); ++ ++ return 0; ++ ++reset_exit: ++ reset_control_assert(priv->resets); ++clk_exit: ++ clk_bulk_disable_unprepare(priv->nr_clks, priv->clks); ++free_exit: ++ free_candev(ndev); ++exit: ++ return ret; ++} ++ ++static int canfd_driver_remove(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ ++ reset_control_assert(priv->resets); ++ clk_bulk_disable_unprepare(priv->nr_clks, priv->clks); ++ pm_runtime_disable(&pdev->dev); ++ ++ unregister_candev(ndev); ++ netif_napi_del(&priv->napi); ++ free_candev(ndev); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_PM_SLEEP ++static int __maybe_unused canfd_suspend(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ ++ if (netif_running(ndev)) { ++ netif_stop_queue(ndev); ++ netif_device_detach(ndev); ++ canfd_driver_stop(ndev); ++ } ++ ++ return pm_runtime_force_suspend(dev); ++} ++ ++static int __maybe_unused canfd_resume(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = pm_runtime_force_resume(dev); ++ if (ret) { ++ dev_err(dev, "pm_runtime_force_resume failed on resume\n"); ++ return ret; ++ } ++ ++ if (netif_running(ndev)) { ++ ret = canfd_chip_start(ndev); ++ if (ret) { ++ dev_err(dev, "canfd_chip_start failed on resume\n"); ++ return ret; ++ } ++ ++ netif_device_attach(ndev); ++ netif_start_queue(ndev); ++ } ++ ++ return 0; ++} ++#endif ++ ++#ifdef CONFIG_PM ++static int canfd_runtime_suspend(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ ++ reset_control_assert(priv->resets); ++ clk_bulk_disable_unprepare(priv->nr_clks, priv->clks); ++ ++ return 0; ++} ++ ++static int canfd_runtime_resume(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct ipms_canfd_priv *priv = netdev_priv(ndev); ++ int ret; ++ ++ ret = clk_bulk_prepare_enable(priv->nr_clks, priv->clks); ++ if (ret) { ++ dev_err(dev, "Failed to prepare_enable clk\n"); ++ return ret; ++ } ++ ++ ret = reset_control_deassert(priv->resets); ++ if (ret) { ++ dev_err(dev, "Failed to deassert reset\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++#endif ++ ++static const struct dev_pm_ops canfd_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(canfd_suspend, canfd_resume) ++ SET_RUNTIME_PM_OPS(canfd_runtime_suspend, ++ canfd_runtime_resume, NULL) ++}; ++ ++static const struct of_device_id canfd_of_match[] = { ++ { .compatible = "ipms,can" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, canfd_of_match); ++ ++static struct platform_driver can_driver = { ++ .probe = canfd_driver_probe, ++ .remove = canfd_driver_remove, ++ .driver = { ++ .name = DRIVER_NAME, ++ .pm = &canfd_pm_ops, ++ .of_match_table = canfd_of_match, ++ }, ++}; ++ ++module_platform_driver(can_driver); ++ ++MODULE_DESCRIPTION("ipms can controller driver for StarFive jh7110 SoC"); ++MODULE_AUTHOR("William Qiu<william.qiu@starfivetech.com"); ++MODULE_LICENSE("GPL"); diff --git a/target/linux/starfive/patches-6.6/0071-regulator-starfive-jh7110-Add-regulator-support-for-.patch b/target/linux/starfive/patches-6.6/0071-regulator-starfive-jh7110-Add-regulator-support-for-.patch new file mode 100644 index 0000000000..cf1e62386d --- /dev/null +++ b/target/linux/starfive/patches-6.6/0071-regulator-starfive-jh7110-Add-regulator-support-for-.patch @@ -0,0 +1,204 @@ +From b12213d474966fbf47e7afa36a6655b5b241cf36 Mon Sep 17 00:00:00 2001 +From: "Kevin.xie" <kevin.xie@starfivetech.com> +Date: Fri, 9 Jun 2023 14:57:13 +0800 +Subject: [PATCH 071/116] regulator: starfive-jh7110: Add regulator support for + JH7110 A type EVB. + +Add 7 regulators base on regulator framework for +JH7110 evb HW design. + +Signed-off-by: Kevin.xie <kevin.xie@starfivetech.com> +--- + drivers/regulator/Kconfig | 10 ++ + drivers/regulator/Makefile | 1 + + drivers/regulator/starfive-jh7110-regulator.c | 126 ++++++++++++++++++ + include/linux/regulator/jh7110.h | 24 ++++ + 4 files changed, 161 insertions(+) + create mode 100644 drivers/regulator/starfive-jh7110-regulator.c + create mode 100644 include/linux/regulator/jh7110.h + +--- a/drivers/regulator/Kconfig ++++ b/drivers/regulator/Kconfig +@@ -1335,6 +1335,16 @@ config REGULATOR_SM5703 + This driver provides support for voltage regulators of SM5703 + multi-function device. + ++config REGULATOR_STARFIVE_JH7110 ++ tristate "Starfive JH7110 PMIC" ++ depends on I2C ++ select REGMAP_I2C ++ help ++ Say y here to select this option to enable the power regulator of ++ Starfive JH7110 PMIC. ++ This driver supports the control of different power rails of device ++ through regulator interface. ++ + config REGULATOR_STM32_BOOSTER + tristate "STMicroelectronics STM32 BOOSTER" + depends on ARCH_STM32 || COMPILE_TEST +--- a/drivers/regulator/Makefile ++++ b/drivers/regulator/Makefile +@@ -156,6 +156,7 @@ obj-$(CONFIG_REGULATOR_SC2731) += sc2731 + obj-$(CONFIG_REGULATOR_SKY81452) += sky81452-regulator.o + obj-$(CONFIG_REGULATOR_SLG51000) += slg51000-regulator.o + obj-$(CONFIG_REGULATOR_SM5703) += sm5703-regulator.o ++obj-$(CONFIG_REGULATOR_STARFIVE_JH7110) += starfive-jh7110-regulator.o + obj-$(CONFIG_REGULATOR_STM32_BOOSTER) += stm32-booster.o + obj-$(CONFIG_REGULATOR_STM32_VREFBUF) += stm32-vrefbuf.o + obj-$(CONFIG_REGULATOR_STM32_PWR) += stm32-pwr.o +--- /dev/null ++++ b/drivers/regulator/starfive-jh7110-regulator.c +@@ -0,0 +1,126 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2022 Starfive Technology Co., Ltd. ++ * Author: Mason Huo <mason.huo@starfivetech.com> ++ */ ++ ++#include <linux/err.h> ++#include <linux/gpio.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <linux/interrupt.h> ++#include <linux/module.h> ++#include <linux/regmap.h> ++#include <linux/regulator/driver.h> ++#include <linux/regulator/machine.h> ++#include <linux/regulator/of_regulator.h> ++#include <linux/regulator/jh7110.h> ++#include <linux/slab.h> ++ ++#define JH7110_PM_POWER_SW_0 0x80 ++#define JH7110_PM_POWER_SW_1 0x81 ++#define ENABLE_MASK(id) BIT(id) ++ ++ ++static const struct regmap_config jh7110_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = JH7110_PM_POWER_SW_1, ++ .cache_type = REGCACHE_FLAT, ++}; ++ ++static const struct regulator_ops jh7110_ldo_ops = { ++ .enable = regulator_enable_regmap, ++ .disable = regulator_disable_regmap, ++ .is_enabled = regulator_is_enabled_regmap, ++}; ++ ++#define JH7110_LDO(_id, _name, en_reg, en_mask) \ ++{\ ++ .name = (_name),\ ++ .ops = &jh7110_ldo_ops,\ ++ .of_match = of_match_ptr(_name),\ ++ .regulators_node = of_match_ptr("regulators"),\ ++ .type = REGULATOR_VOLTAGE,\ ++ .id = JH7110_ID_##_id,\ ++ .owner = THIS_MODULE,\ ++ .enable_reg = JH7110_PM_POWER_SW_##en_reg,\ ++ .enable_mask = ENABLE_MASK(en_mask),\ ++} ++ ++static const struct regulator_desc jh7110_regulators[] = { ++ JH7110_LDO(LDO_REG1, "hdmi_1p8", 0, 0), ++ JH7110_LDO(LDO_REG2, "mipitx_1p8", 0, 1), ++ JH7110_LDO(LDO_REG3, "mipirx_1p8", 0, 2), ++ JH7110_LDO(LDO_REG4, "hdmi_0p9", 0, 3), ++ JH7110_LDO(LDO_REG5, "mipitx_0p9", 0, 4), ++ JH7110_LDO(LDO_REG6, "mipirx_0p9", 0, 5), ++ JH7110_LDO(LDO_REG7, "sdio_vdd", 1, 0), ++}; ++ ++static int jh7110_i2c_probe(struct i2c_client *i2c) ++{ ++ struct regulator_config config = { }; ++ struct regulator_dev *rdev; ++ struct regulator_init_data *init_data; ++ struct regmap *regmap; ++ int i, ret; ++ ++ regmap = devm_regmap_init_i2c(i2c, &jh7110_regmap_config); ++ if (IS_ERR(regmap)) { ++ ret = PTR_ERR(regmap); ++ dev_err(&i2c->dev, "Failed to allocate register map: %d\n", ++ ret); ++ return ret; ++ } ++ ++ init_data = of_get_regulator_init_data(&i2c->dev, i2c->dev.of_node, NULL); ++ if (!init_data) ++ return -ENOMEM; ++ config.init_data = init_data; ++ ++ for (i = 0; i < JH7110_MAX_REGULATORS; i++) { ++ config.dev = &i2c->dev; ++ config.regmap = regmap; ++ ++ rdev = devm_regulator_register(&i2c->dev, ++ &jh7110_regulators[i], &config); ++ if (IS_ERR(rdev)) { ++ dev_err(&i2c->dev, ++ "Failed to register JH7110 regulator\n"); ++ return PTR_ERR(rdev); ++ } ++ } ++ ++ return 0; ++} ++ ++static const struct i2c_device_id jh7110_i2c_id[] = { ++ {"jh7110_evb_reg", 0}, ++ {}, ++}; ++MODULE_DEVICE_TABLE(i2c, jh7110_i2c_id); ++ ++#ifdef CONFIG_OF ++static const struct of_device_id jh7110_dt_ids[] = { ++ { .compatible = "starfive,jh7110-evb-regulator", ++ .data = &jh7110_i2c_id[0] }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, jh7110_dt_ids); ++#endif ++ ++static struct i2c_driver jh7110_regulator_driver = { ++ .driver = { ++ .name = "jh7110-evb-regulator", ++ .of_match_table = of_match_ptr(jh7110_dt_ids), ++ }, ++ .probe = jh7110_i2c_probe, ++ .id_table = jh7110_i2c_id, ++}; ++ ++module_i2c_driver(jh7110_regulator_driver); ++ ++MODULE_AUTHOR("Mason Huo <mason.huo@starfivetech.com>"); ++MODULE_DESCRIPTION("Regulator device driver for Starfive JH7110"); ++MODULE_LICENSE("GPL v2"); +--- /dev/null ++++ b/include/linux/regulator/jh7110.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * Copyright (c) 2022 Starfive Technology Co., Ltd. ++ * Author: Mason Huo <mason.huo@starfivetech.com> ++ */ ++ ++#ifndef __LINUX_REGULATOR_JH7110_H ++#define __LINUX_REGULATOR_JH7110_H ++ ++#define JH7110_MAX_REGULATORS 7 ++ ++ ++enum jh7110_reg_id { ++ JH7110_ID_LDO_REG1 = 0, ++ JH7110_ID_LDO_REG2, ++ JH7110_ID_LDO_REG3, ++ JH7110_ID_LDO_REG4, ++ JH7110_ID_LDO_REG5, ++ JH7110_ID_LDO_REG6, ++ JH7110_ID_LDO_REG7, ++}; ++ ++ ++#endif /* __LINUX_REGULATOR_JH7110_H */ diff --git a/target/linux/starfive/patches-6.6/0072-drivers-nvme-Add-precheck-and-delay-for-CQE-pending-.patch b/target/linux/starfive/patches-6.6/0072-drivers-nvme-Add-precheck-and-delay-for-CQE-pending-.patch new file mode 100644 index 0000000000..d602df9e4c --- /dev/null +++ b/target/linux/starfive/patches-6.6/0072-drivers-nvme-Add-precheck-and-delay-for-CQE-pending-.patch @@ -0,0 +1,40 @@ +From f0b4cffe4d1813305f783d208f260747ecc56c50 Mon Sep 17 00:00:00 2001 +From: "Kevin.xie" <kevin.xie@starfivetech.com> +Date: Thu, 24 Nov 2022 16:59:12 +0800 +Subject: [PATCH 072/116] drivers: nvme: Add precheck and delay for CQE pending + status. + +To workaroud the NVMe I/O timeout problem in bootup S10udev case +which caused by the CQE update lantancy. + +Signed-off-by: Kevin.xie <kevin.xie@starfivetech.com> +--- + drivers/nvme/host/pci.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +--- a/drivers/nvme/host/pci.c ++++ b/drivers/nvme/host/pci.c +@@ -28,6 +28,7 @@ + #include <linux/io-64-nonatomic-hi-lo.h> + #include <linux/sed-opal.h> + #include <linux/pci-p2pdma.h> ++#include <linux/delay.h> + + #include "trace.h" + #include "nvme.h" +@@ -1062,6 +1063,15 @@ static inline int nvme_poll_cq(struct nv + { + int found = 0; + ++ /* ++ * In some cases, such as udev trigger, cqe status may update ++ * a little bit later than MSI, which cause an irq handle missing. ++ * To workaound, here we will prefetch the status first, and wait ++ * 1us if we get nothing. ++ */ ++ if (!nvme_cqe_pending(nvmeq)) ++ udelay(1); ++ + while (nvme_cqe_pending(nvmeq)) { + found++; + /* diff --git a/target/linux/starfive/patches-6.6/0073-RISC-V-Create-unique-identification-for-SoC-PMU.patch b/target/linux/starfive/patches-6.6/0073-RISC-V-Create-unique-identification-for-SoC-PMU.patch new file mode 100644 index 0000000000..fff01c4c8f --- /dev/null +++ b/target/linux/starfive/patches-6.6/0073-RISC-V-Create-unique-identification-for-SoC-PMU.patch @@ -0,0 +1,93 @@ +From eb294df4b9fab46bc5dbf676edf51e28e06d1968 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jo=C3=A3o=20M=C3=A1rio=20Domingos?= + <joao.mario@tecnico.ulisboa.pt> +Date: Tue, 16 Nov 2021 15:48:09 +0000 +Subject: [PATCH 073/116] RISC-V: Create unique identification for SoC PMU +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The SBI PMU platform driver did not provide any identification for +perf events matching. This patch introduces a new sysfs file inside the +platform device (soc:pmu/id) for pmu identification. + +The identification is a 64-bit value generated as: +[63-32]: mvendorid; +[31]: marchid[MSB]; +[30-16]: marchid[15-0]; +[15-0]: mimpid[15MSBs]; + +The CSRs are detailed in the RISC-V privileged spec [1]. +The marchid is split in MSB + 15LSBs, due to the MSB being used for +open-source architecture identification. + +[1] https://github.com/riscv/riscv-isa-manual + +Signed-off-by: João Mário Domingos <joao.mario@tecnico.ulisboa.pt> +--- + drivers/perf/riscv_pmu_sbi.c | 47 ++++++++++++++++++++++++++++++++++++ + 1 file changed, 47 insertions(+) + +--- a/drivers/perf/riscv_pmu_sbi.c ++++ b/drivers/perf/riscv_pmu_sbi.c +@@ -1019,6 +1019,46 @@ static struct ctl_table sbi_pmu_sysctl_t + { } + }; + ++static uint64_t pmu_sbi_get_pmu_id(void) ++{ ++ union sbi_pmu_id { ++ uint64_t value; ++ struct { ++ uint16_t imp:16; ++ uint16_t arch:16; ++ uint32_t vendor:32; ++ }; ++ } pmuid; ++ ++ pmuid.value = 0; ++ pmuid.vendor = (uint32_t) sbi_get_mvendorid(); ++ pmuid.arch = (sbi_get_marchid() >> (63 - 15) & (1 << 15)) | (sbi_get_marchid() & 0x7FFF); ++ pmuid.imp = (sbi_get_mimpid() >> 16); ++ ++ return pmuid.value; ++} ++ ++static ssize_t pmu_sbi_id_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ int len; ++ ++ len = sprintf(buf, "0x%llx\n", pmu_sbi_get_pmu_id()); ++ if (len <= 0) ++ dev_err(dev, "mydrv: Invalid sprintf len: %dn", len); ++ ++ return len; ++} ++ ++static DEVICE_ATTR(id, S_IRUGO | S_IWUSR, pmu_sbi_id_show, 0); ++ ++static struct attribute *pmu_sbi_attrs[] = { ++ &dev_attr_id.attr, ++ NULL ++}; ++ ++ATTRIBUTE_GROUPS(pmu_sbi); ++ + static int pmu_sbi_device_probe(struct platform_device *pdev) + { + struct riscv_pmu *pmu = NULL; +@@ -1067,6 +1107,13 @@ static int pmu_sbi_device_probe(struct p + pmu->event_unmapped = pmu_sbi_event_unmapped; + pmu->csr_index = pmu_sbi_csr_index; + ++ ret = sysfs_create_group(&pdev->dev.kobj, &pmu_sbi_group); ++ if (ret) { ++ dev_err(&pdev->dev, "sysfs creation failed\n"); ++ return ret; ++ } ++ pdev->dev.groups = pmu_sbi_groups; ++ + ret = cpuhp_state_add_instance(CPUHP_AP_PERF_RISCV_STARTING, &pmu->node); + if (ret) + return ret; diff --git a/target/linux/starfive/patches-6.6/0074-RISC-V-Support-CPUID-for-risc-v-in-perf.patch b/target/linux/starfive/patches-6.6/0074-RISC-V-Support-CPUID-for-risc-v-in-perf.patch new file mode 100644 index 0000000000..177b3b32d8 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0074-RISC-V-Support-CPUID-for-risc-v-in-perf.patch @@ -0,0 +1,55 @@ +From 1dc069ffadf4ce7817a716f9df2f480254e9b01d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jo=C3=A3o=20M=C3=A1rio=20Domingos?= + <joao.mario@tecnico.ulisboa.pt> +Date: Tue, 16 Nov 2021 15:48:10 +0000 +Subject: [PATCH 074/116] RISC-V: Support CPUID for risc-v in perf +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This patch creates the header.c file for the risc-v architecture and introduces support for +PMU identification through sysfs. +It is now possible to configure pmu-events in risc-v. + +Depends on patch [1], that introduces the id sysfs file. + +Signed-off-by: João Mário Domingos <joao.mario@tecnico.ulisboa.pt> +Signed-off-by: minda.chen <minda.chen@starfivetech.com> +--- + drivers/perf/riscv_pmu.c | 18 ++++++++++++++++++ + 1 file changed, 18 insertions(+) + +--- a/drivers/perf/riscv_pmu.c ++++ b/drivers/perf/riscv_pmu.c +@@ -18,6 +18,23 @@ + + #include <asm/sbi.h> + ++PMU_FORMAT_ATTR(event, "config:0-63"); ++ ++static struct attribute *riscv_arch_formats_attr[] = { ++ &format_attr_event.attr, ++ NULL, ++}; ++ ++static struct attribute_group riscv_pmu_format_group = { ++ .name = "format", ++ .attrs = riscv_arch_formats_attr, ++}; ++ ++static const struct attribute_group *riscv_pmu_attr_groups[] = { ++ &riscv_pmu_format_group, ++ NULL, ++}; ++ + static bool riscv_perf_user_access(struct perf_event *event) + { + return ((event->attr.type == PERF_TYPE_HARDWARE) || +@@ -410,6 +427,7 @@ struct riscv_pmu *riscv_pmu_alloc(void) + cpuc->events[i] = NULL; + } + pmu->pmu = (struct pmu) { ++ .attr_groups = riscv_pmu_attr_groups, + .event_init = riscv_pmu_event_init, + .event_mapped = riscv_pmu_event_mapped, + .event_unmapped = riscv_pmu_event_unmapped, diff --git a/target/linux/starfive/patches-6.6/0075-RISC-V-Added-generic-pmu-events-mapfile.patch b/target/linux/starfive/patches-6.6/0075-RISC-V-Added-generic-pmu-events-mapfile.patch new file mode 100644 index 0000000000..01bb4f19ca --- /dev/null +++ b/target/linux/starfive/patches-6.6/0075-RISC-V-Added-generic-pmu-events-mapfile.patch @@ -0,0 +1,42 @@ +From 3e6ea12dda276c01a756764fcafa315b19860c33 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jo=C3=A3o=20M=C3=A1rio=20Domingos?= + <joao.mario@tecnico.ulisboa.pt> +Date: Tue, 16 Nov 2021 15:48:11 +0000 +Subject: [PATCH 075/116] RISC-V: Added generic pmu-events mapfile +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The pmu-events now supports custom events for RISC-V, plus the cycle, +time and instret events were defined. + +Signed-off-by: João Mário Domingos <joao.mario@tecnico.ulisboa.pt> +--- + .../pmu-events/arch/riscv/riscv-generic.json | 20 +++++++++++++++++++ + 1 file changed, 20 insertions(+) + create mode 100644 tools/perf/pmu-events/arch/riscv/riscv-generic.json + +--- /dev/null ++++ b/tools/perf/pmu-events/arch/riscv/riscv-generic.json +@@ -0,0 +1,20 @@ ++[ ++ { ++ "PublicDescription": "CPU Cycles", ++ "EventCode": "0x00", ++ "EventName": "riscv_cycles", ++ "BriefDescription": "CPU cycles RISC-V generic counter" ++ }, ++ { ++ "PublicDescription": "CPU Time", ++ "EventCode": "0x01", ++ "EventName": "riscv_time", ++ "BriefDescription": "CPU time RISC-V generic counter" ++ }, ++ { ++ "PublicDescription": "CPU Instructions", ++ "EventCode": "0x02", ++ "EventName": "riscv_instret", ++ "BriefDescription": "CPU retired instructions RISC-V generic counter" ++ } ++] +\ No newline at end of file diff --git a/target/linux/starfive/patches-6.6/0076-perf-sbi-disable-cpu-hotplug-callback.patch b/target/linux/starfive/patches-6.6/0076-perf-sbi-disable-cpu-hotplug-callback.patch new file mode 100644 index 0000000000..152a60cfda --- /dev/null +++ b/target/linux/starfive/patches-6.6/0076-perf-sbi-disable-cpu-hotplug-callback.patch @@ -0,0 +1,30 @@ +From 30e0cdcf9e05faa65ecde4ed8b70039568fdb660 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Thu, 2 Mar 2023 17:16:01 +0800 +Subject: [PATCH 076/116] perf: sbi: disable cpu hotplug callback. + +register cpu hotplug callback will cause dhrystone +and coremark benchmark reduce the scores. this CPU +hotplug ops will do in sbi cpu/on and off. So disable +this no side effect. + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/perf/riscv_pmu_sbi.c | 2 ++ + 1 file changed, 2 insertions(+) + +--- a/drivers/perf/riscv_pmu_sbi.c ++++ b/drivers/perf/riscv_pmu_sbi.c +@@ -1114,9 +1114,11 @@ static int pmu_sbi_device_probe(struct p + } + pdev->dev.groups = pmu_sbi_groups; + ++#ifndef CONFIG_ARCH_STARFIVE + ret = cpuhp_state_add_instance(CPUHP_AP_PERF_RISCV_STARTING, &pmu->node); + if (ret) + return ret; ++#endif + + ret = riscv_pm_pmu_register(pmu); + if (ret) diff --git a/target/linux/starfive/patches-6.6/0077-dmaengine-dw-axi-dmac-Drop-unused-print-message.patch b/target/linux/starfive/patches-6.6/0077-dmaengine-dw-axi-dmac-Drop-unused-print-message.patch new file mode 100644 index 0000000000..cdc6e4ec26 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0077-dmaengine-dw-axi-dmac-Drop-unused-print-message.patch @@ -0,0 +1,24 @@ +From fc4b5c7c27e1b56b1f848e50511c4fd081b1b6c5 Mon Sep 17 00:00:00 2001 +From: Walker Chen <walker.chen@starfivetech.com> +Date: Mon, 12 Jun 2023 21:21:45 +0800 +Subject: [PATCH 077/116] dmaengine: dw-axi-dmac: Drop unused print message + +Removed printing information which is not related to StarFive +platform. + +Signed-off-by: Walker Chen <walker.chen@starfivetech.com> +--- + drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c ++++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +@@ -523,7 +523,7 @@ static void dw_axi_dma_set_hw_channel(st + unsigned long reg_value, val; + + if (!chip->apb_regs) { +- dev_err(chip->dev, "apb_regs not initialized\n"); ++ dev_dbg(chip->dev, "apb_regs not initialized\n"); + return; + } + diff --git a/target/linux/starfive/patches-6.6/0079-ASoC-codecs-Add-AC108-Codec-driver.patch b/target/linux/starfive/patches-6.6/0079-ASoC-codecs-Add-AC108-Codec-driver.patch new file mode 100644 index 0000000000..2a828974ce --- /dev/null +++ b/target/linux/starfive/patches-6.6/0079-ASoC-codecs-Add-AC108-Codec-driver.patch @@ -0,0 +1,4748 @@ +From cd2254c6be9441ebacaa35693ecb5ce116b90622 Mon Sep 17 00:00:00 2001 +From: Xingyu Wu <xingyu.wu@starfivetech.com> +Date: Fri, 16 Jun 2023 16:27:46 +0800 +Subject: [PATCH 079/116] ASoC: codecs: Add AC108 Codec driver + +Add AC108 Codec driver and AC101 driver for AC10x. + +Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + sound/soc/codecs/Kconfig | 5 + + sound/soc/codecs/Makefile | 2 + + sound/soc/codecs/ac101.c | 1716 +++++++++++++++++++++++++++++++++ + sound/soc/codecs/ac101_regs.h | 431 +++++++++ + sound/soc/codecs/ac108.c | 1622 +++++++++++++++++++++++++++++++ + sound/soc/codecs/ac108.h | 749 ++++++++++++++ + sound/soc/codecs/ac10x.h | 152 +++ + 7 files changed, 4677 insertions(+) + create mode 100644 sound/soc/codecs/ac101.c + create mode 100644 sound/soc/codecs/ac101_regs.h + create mode 100644 sound/soc/codecs/ac108.c + create mode 100644 sound/soc/codecs/ac108.h + create mode 100644 sound/soc/codecs/ac10x.h + +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -16,6 +16,7 @@ config SND_SOC_ALL_CODECS + depends on COMPILE_TEST + imply SND_SOC_88PM860X + imply SND_SOC_AB8500_CODEC ++ imply SND_SOC_AC108 + imply SND_SOC_AC97_CODEC + imply SND_SOC_AD1836 + imply SND_SOC_AD193X_SPI +@@ -397,6 +398,10 @@ config SND_SOC_AB8500_CODEC + tristate + depends on ABX500_CORE + ++config SND_SOC_AC108 ++ tristate "AC108" ++ depends on I2C ++ + config SND_SOC_AC97_CODEC + tristate "Build generic ASoC AC97 CODEC driver" + select SND_AC97_CODEC +--- a/sound/soc/codecs/Makefile ++++ b/sound/soc/codecs/Makefile +@@ -2,6 +2,7 @@ + snd-soc-88pm860x-objs := 88pm860x-codec.o + snd-soc-ab8500-codec-objs := ab8500-codec.o + snd-soc-ac97-objs := ac97.o ++snd-soc-ac108-objs := ac108.o ac101.o + snd-soc-ad1836-objs := ad1836.o + snd-soc-ad193x-objs := ad193x.o + snd-soc-ad193x-spi-objs := ad193x-spi.o +@@ -386,6 +387,7 @@ snd-soc-simple-mux-objs := simple-mux.o + + obj-$(CONFIG_SND_SOC_88PM860X) += snd-soc-88pm860x.o + obj-$(CONFIG_SND_SOC_AB8500_CODEC) += snd-soc-ab8500-codec.o ++obj-$(CONFIG_SND_SOC_AC108) += snd-soc-ac108.o + obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o + obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o + obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o +--- /dev/null ++++ b/sound/soc/codecs/ac101.c +@@ -0,0 +1,1716 @@ ++/* ++ * ac101.c ++ * ++ * (C) Copyright 2017-2018 ++ * Seeed Technology Co., Ltd. <www.seeedstudio.com> ++ * ++ * PeterYang <linsheng.yang@seeed.cc> ++ * ++ * (C) Copyright 2014-2017 ++ * Reuuimlla Technology Co., Ltd. <www.reuuimllatech.com> ++ * ++ * huangxin <huangxin@Reuuimllatech.com> ++ * liushaohua <liushaohua@allwinnertech.com> ++ * ++ * X-Powers AC101 codec driver ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of ++ * the License, or (at your option) any later version. ++ * ++ */ ++ ++#include <linux/clk.h> ++#include <linux/delay.h> ++#include <linux/gpio/consumer.h> ++#include <linux/i2c.h> ++#include <linux/input.h> ++#include <linux/irq.h> ++#include <linux/module.h> ++#include <linux/regmap.h> ++#include <sound/tlv.h> ++#include <linux/workqueue.h> ++#include <sound/pcm.h> ++#include <sound/pcm_params.h> ++#include <sound/soc.h> ++#include <sound/soc-dapm.h> ++ ++#include "ac101_regs.h" ++#include "ac10x.h" ++ ++/* #undef AC101_DEBG ++ * use 'make DEBUG=1' to enable debugging ++ */ ++ ++/* ++ * *** To sync channels *** ++ * ++ * 1. disable clock in codec hw_params() ++ * 2. clear fifo in bcm2835 hw_params() ++ * 3. clear fifo in bcm2385 prepare() ++ * 4. enable RX in bcm2835 trigger() ++ * 5. enable clock in machine trigger() ++ */ ++ ++/*Default initialize configuration*/ ++static bool speaker_double_used = 1; ++static int double_speaker_val = 0x1B; ++static int single_speaker_val = 0x19; ++static int headset_val = 0x3B; ++static int mainmic_val = 0x4; ++static int headsetmic_val = 0x4; ++static bool dmic_used = 0; ++static int adc_digital_val = 0xb0b0; ++static bool drc_used = false; ++ ++#define AC101_RATES (SNDRV_PCM_RATE_8000_96000 & \ ++ ~(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_64000 | \ ++ SNDRV_PCM_RATE_88200)) ++#define AC101_FORMATS (/*SNDRV_PCM_FMTBIT_S16_LE | \ ++ SNDRV_PCM_FMTBIT_S24_LE |*/ \ ++ SNDRV_PCM_FMTBIT_S32_LE | \ ++ 0) ++ ++static struct ac10x_priv* static_ac10x; ++ ++ ++int ac101_read(struct snd_soc_codec *codec, unsigned reg) { ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ int r, v = 0; ++ ++ if ((r = regmap_read(ac10x->regmap101, reg, &v)) < 0) { ++ dev_err(codec->dev, "read reg %02X fail\n", ++ reg); ++ return r; ++ } ++ return v; ++} ++ ++int ac101_write(struct snd_soc_codec *codec, unsigned reg, unsigned val) { ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ int v; ++ ++ v = regmap_write(ac10x->regmap101, reg, val); ++ return v; ++} ++ ++int ac101_update_bits(struct snd_soc_codec *codec, unsigned reg, ++ unsigned mask, unsigned value ++) { ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ int v; ++ ++ v = regmap_update_bits(ac10x->regmap101, reg, mask, value); ++ return v; ++} ++ ++ ++ ++#ifdef CONFIG_AC101_SWITCH_DETECT ++/******************************************************************************/ ++/********************************switch****************************************/ ++/******************************************************************************/ ++#define KEY_HEADSETHOOK 226 /* key define */ ++#define HEADSET_FILTER_CNT (10) ++ ++/* ++ * switch_hw_config:config the 53 codec register ++ */ ++static void switch_hw_config(struct snd_soc_codec *codec) ++{ ++ int r; ++ ++ AC101_DBG(); ++ ++ /*HMIC/MMIC BIAS voltage level select:2.5v*/ ++ ac101_update_bits(codec, OMIXER_BST1_CTRL, (0xf<<BIASVOLTAGE), (0xf<<BIASVOLTAGE)); ++ /*debounce when Key down or keyup*/ ++ ac101_update_bits(codec, HMIC_CTRL1, (0xf<<HMIC_M), (0x0<<HMIC_M)); ++ /*debounce when earphone plugin or pullout*/ ++ ac101_update_bits(codec, HMIC_CTRL1, (0xf<<HMIC_N), (0x0<<HMIC_N)); ++ /*Down Sample Setting Select: Downby 4,32Hz*/ ++ ac101_update_bits(codec, HMIC_CTRL2, (0x3<<HMIC_SAMPLE_SELECT), ++ (0x02<<HMIC_SAMPLE_SELECT)); ++ /*Hmic_th2 for detecting Keydown or Keyup.*/ ++ ac101_update_bits(codec, HMIC_CTRL2, (0x1f<<HMIC_TH2), (0x8<<HMIC_TH2)); ++ /*Hmic_th1[4:0],detecting eraphone plugin or pullout*/ ++ ac101_update_bits(codec, HMIC_CTRL2, (0x1f<<HMIC_TH1), (0x1<<HMIC_TH1)); ++ /*Headset microphone BIAS working mode: when HBIASEN = 1 */ ++ ac101_update_bits(codec, ADC_APC_CTRL, (0x1<<HBIASMOD), (0x1<<HBIASMOD)); ++ /*Headset microphone BIAS Enable*/ ++ ac101_update_bits(codec, ADC_APC_CTRL, (0x1<<HBIASEN), (0x1<<HBIASEN)); ++ /*Headset microphone BIAS Current sensor & ADC Enable*/ ++ ac101_update_bits(codec, ADC_APC_CTRL, (0x1<<HBIASADCEN), (0x1<<HBIASADCEN)); ++ /*Earphone Plugin/out Irq Enable*/ ++ ac101_update_bits(codec, HMIC_CTRL1, (0x1<<HMIC_PULLOUT_IRQ), (0x1<<HMIC_PULLOUT_IRQ)); ++ ac101_update_bits(codec, HMIC_CTRL1, (0x1<<HMIC_PLUGIN_IRQ), (0x1<<HMIC_PLUGIN_IRQ)); ++ ++ /*Hmic KeyUp/key down Irq Enable*/ ++ ac101_update_bits(codec, HMIC_CTRL1, (0x1<<HMIC_KEYDOWN_IRQ), (0x1<<HMIC_KEYDOWN_IRQ)); ++ ac101_update_bits(codec, HMIC_CTRL1, (0x1<<HMIC_KEYUP_IRQ), (0x1<<HMIC_KEYUP_IRQ)); ++ ++ /*headphone calibration clock frequency select*/ ++ ac101_update_bits(codec, SPKOUT_CTRL, (0x7<<HPCALICKS), (0x7<<HPCALICKS)); ++ ++ /*clear hmic interrupt */ ++ r = HMIC_PEND_ALL; ++ ac101_write(codec, HMIC_STS, r); ++ ++ return; ++} ++ ++/* ++ * switch_status_update: update the switch state. ++ */ ++static void switch_status_update(struct ac10x_priv *ac10x) ++{ ++ AC101_DBG("ac10x->state:%d\n", ac10x->state); ++ ++ input_report_switch(ac10x->inpdev, SW_HEADPHONE_INSERT, ac10x->state); ++ input_sync(ac10x->inpdev); ++ return; ++} ++ ++/* ++ * work_cb_clear_irq: clear audiocodec pending and Record the interrupt. ++ */ ++static void work_cb_clear_irq(struct work_struct *work) ++{ ++ int reg_val = 0; ++ struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, work_clear_irq); ++ struct snd_soc_codec *codec = ac10x->codec; ++ ++ ac10x->irq_cntr++; ++ ++ reg_val = ac101_read(codec, HMIC_STS); ++ if (BIT(HMIC_PULLOUT_PEND) & reg_val) { ++ ac10x->pullout_cntr++; ++ AC101_DBG("ac10x->pullout_cntr: %d\n", ac10x->pullout_cntr); ++ } ++ ++ reg_val |= HMIC_PEND_ALL; ++ ac101_write(codec, HMIC_STS, reg_val); ++ ++ reg_val = ac101_read(codec, HMIC_STS); ++ if ((reg_val & HMIC_PEND_ALL) != 0){ ++ reg_val |= HMIC_PEND_ALL; ++ ac101_write(codec, HMIC_STS, reg_val); ++ } ++ ++ if (cancel_work_sync(&ac10x->work_switch) != 0) { ++ ac10x->irq_cntr--; ++ } ++ ++ if (0 == schedule_work(&ac10x->work_switch)) { ++ ac10x->irq_cntr--; ++ AC101_DBG("[work_cb_clear_irq] add work struct failed!\n"); ++ } ++} ++ ++enum { ++ HBIAS_LEVEL_1 = 0x02, ++ HBIAS_LEVEL_2 = 0x0B, ++ HBIAS_LEVEL_3 = 0x13, ++ HBIAS_LEVEL_4 = 0x17, ++ HBIAS_LEVEL_5 = 0x19, ++}; ++ ++static int __ac101_get_hmic_data(struct snd_soc_codec *codec) { ++ #ifdef AC101_DEBG ++ static long counter; ++ #endif ++ int r, d; ++ ++ d = GET_HMIC_DATA(ac101_read(codec, HMIC_STS)); ++ ++ r = 0x1 << HMIC_DATA_PEND; ++ ac101_write(codec, HMIC_STS, r); ++ ++ /* prevent i2c accessing too frequently */ ++ usleep_range(1500, 3000); ++ ++ AC101_DBG("HMIC_DATA(%3ld): %02X\n", counter++, d); ++ return d; ++} ++ ++/* ++ * work_cb_earphone_switch: judge the status of the headphone ++ */ ++static void work_cb_earphone_switch(struct work_struct *work) ++{ ++ struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, work_switch); ++ struct snd_soc_codec *codec = ac10x->codec; ++ ++ static int hook_flag1 = 0, hook_flag2 = 0; ++ static int KEY_VOLUME_FLAG = 0; ++ ++ unsigned filter_buf = 0; ++ int filt_index = 0; ++ int t = 0; ++ ++ ac10x->irq_cntr--; ++ ++ /* read HMIC_DATA */ ++ t = __ac101_get_hmic_data(codec); ++ ++ if ((t >= HBIAS_LEVEL_2) && (ac10x->mode == FOUR_HEADPHONE_PLUGIN)) { ++ t = __ac101_get_hmic_data(codec); ++ ++ if (t >= HBIAS_LEVEL_5){ ++ msleep(150); ++ t = __ac101_get_hmic_data(codec); ++ if (((t < HBIAS_LEVEL_2 && t >= HBIAS_LEVEL_1 - 1) || t >= HBIAS_LEVEL_5) ++ && (ac10x->pullout_cntr == 0)) { ++ input_report_key(ac10x->inpdev, KEY_HEADSETHOOK, 1); ++ input_sync(ac10x->inpdev); ++ ++ AC101_DBG("KEY_HEADSETHOOK1\n"); ++ ++ if (hook_flag1 != hook_flag2) ++ hook_flag1 = hook_flag2 = 0; ++ hook_flag1++; ++ } ++ if (ac10x->pullout_cntr) ++ ac10x->pullout_cntr--; ++ } else if (t >= HBIAS_LEVEL_4) { ++ msleep(80); ++ t = __ac101_get_hmic_data(codec); ++ if (t < HBIAS_LEVEL_5 && t >= HBIAS_LEVEL_4 && (ac10x->pullout_cntr == 0)) { ++ KEY_VOLUME_FLAG = 1; ++ input_report_key(ac10x->inpdev, KEY_VOLUMEUP, 1); ++ input_sync(ac10x->inpdev); ++ input_report_key(ac10x->inpdev, KEY_VOLUMEUP, 0); ++ input_sync(ac10x->inpdev); ++ ++ AC101_DBG("HMIC_DATA: %d KEY_VOLUMEUP\n", t); ++ } ++ if (ac10x->pullout_cntr) ++ ac10x->pullout_cntr--; ++ } else if (t >= HBIAS_LEVEL_3){ ++ msleep(80); ++ t = __ac101_get_hmic_data(codec); ++ if (t < HBIAS_LEVEL_4 && t >= HBIAS_LEVEL_3 && (ac10x->pullout_cntr == 0)) { ++ KEY_VOLUME_FLAG = 1; ++ input_report_key(ac10x->inpdev, KEY_VOLUMEDOWN, 1); ++ input_sync(ac10x->inpdev); ++ input_report_key(ac10x->inpdev, KEY_VOLUMEDOWN, 0); ++ input_sync(ac10x->inpdev); ++ AC101_DBG("KEY_VOLUMEDOWN\n"); ++ } ++ if (ac10x->pullout_cntr) ++ ac10x->pullout_cntr--; ++ } ++ } else if ((t < HBIAS_LEVEL_2 && t >= HBIAS_LEVEL_1) && ++ (ac10x->mode == FOUR_HEADPHONE_PLUGIN)) { ++ t = __ac101_get_hmic_data(codec); ++ if (t < HBIAS_LEVEL_2 && t >= HBIAS_LEVEL_1) { ++ if (KEY_VOLUME_FLAG) { ++ KEY_VOLUME_FLAG = 0; ++ } ++ if (hook_flag1 == (++hook_flag2)) { ++ hook_flag1 = hook_flag2 = 0; ++ input_report_key(ac10x->inpdev, KEY_HEADSETHOOK, 0); ++ input_sync(ac10x->inpdev); ++ ++ AC101_DBG("KEY_HEADSETHOOK0\n"); ++ } ++ } ++ } else { ++ while (ac10x->irq_cntr == 0 && ac10x->irq != 0) { ++ msleep(20); ++ ++ t = __ac101_get_hmic_data(codec); ++ ++ if (filt_index <= HEADSET_FILTER_CNT) { ++ if (filt_index++ == 0) { ++ filter_buf = t; ++ } else if (filter_buf != t) { ++ filt_index = 0; ++ } ++ continue; ++ } ++ ++ filt_index = 0; ++ if (filter_buf >= HBIAS_LEVEL_2) { ++ ac10x->mode = THREE_HEADPHONE_PLUGIN; ++ ac10x->state = 2; ++ } else if (filter_buf >= HBIAS_LEVEL_1 - 1) { ++ ac10x->mode = FOUR_HEADPHONE_PLUGIN; ++ ac10x->state = 1; ++ } else { ++ ac10x->mode = HEADPHONE_IDLE; ++ ac10x->state = 0; ++ } ++ switch_status_update(ac10x); ++ ac10x->pullout_cntr = 0; ++ break; ++ } ++ } ++} ++ ++/* ++ * audio_hmic_irq: the interrupt handlers ++ */ ++static irqreturn_t audio_hmic_irq(int irq, void *para) ++{ ++ struct ac10x_priv *ac10x = (struct ac10x_priv *)para; ++ if (ac10x == NULL) { ++ return -EINVAL; ++ } ++ ++ if (0 == schedule_work(&ac10x->work_clear_irq)){ ++ AC101_DBG("[audio_hmic_irq] work already in queue_codec_irq, adding failed!\n"); ++ } ++ return IRQ_HANDLED; ++} ++ ++static int ac101_switch_probe(struct ac10x_priv *ac10x) { ++ struct i2c_client *i2c = ac10x->i2c101; ++ long ret; ++ ++ ac10x->gpiod_irq = devm_gpiod_get_optional(&i2c->dev, "switch-irq", GPIOD_IN); ++ if (IS_ERR(ac10x->gpiod_irq)) { ++ ac10x->gpiod_irq = NULL; ++ dev_err(&i2c->dev, "failed get switch-irq in device tree\n"); ++ goto _err_irq; ++ } ++ ++ gpiod_direction_input(ac10x->gpiod_irq); ++ ++ ac10x->irq = gpiod_to_irq(ac10x->gpiod_irq); ++ if (IS_ERR_VALUE(ac10x->irq)) { ++ pr_info("[ac101] map gpio to irq failed, errno = %ld\n", ac10x->irq); ++ ac10x->irq = 0; ++ goto _err_irq; ++ } ++ ++ /* request irq, set irq type to falling edge trigger */ ++ ret = devm_request_irq(ac10x->codec->dev, ac10x->irq, audio_hmic_irq, ++ IRQF_TRIGGER_FALLING, "SWTICH_EINT", ac10x); ++ if (IS_ERR_VALUE(ret)) { ++ pr_info("[ac101] request virq %ld failed, errno = %ld\n", ac10x->irq, ret); ++ goto _err_irq; ++ } ++ ++ ac10x->mode = HEADPHONE_IDLE; ++ ac10x->state = -1; ++ ++ /*use for judge the state of switch*/ ++ INIT_WORK(&ac10x->work_switch, work_cb_earphone_switch); ++ INIT_WORK(&ac10x->work_clear_irq, work_cb_clear_irq); ++ ++ /********************create input device************************/ ++ ac10x->inpdev = devm_input_allocate_device(ac10x->codec->dev); ++ if (!ac10x->inpdev) { ++ AC101_DBG("input_allocate_device: not enough memory for input device\n"); ++ ret = -ENOMEM; ++ goto _err_input_allocate_device; ++ } ++ ++ ac10x->inpdev->name = "seed-voicecard-headset"; ++ ac10x->inpdev->phys = dev_name(ac10x->codec->dev); ++ ac10x->inpdev->id.bustype = BUS_I2C; ++ ac10x->inpdev->dev.parent = ac10x->codec->dev; ++ input_set_drvdata(ac10x->inpdev, ac10x->codec); ++ ++ ac10x->inpdev->evbit[0] = BIT_MASK(EV_KEY) | BIT(EV_SW); ++ ++ set_bit(KEY_HEADSETHOOK, ac10x->inpdev->keybit); ++ set_bit(KEY_VOLUMEUP, ac10x->inpdev->keybit); ++ set_bit(KEY_VOLUMEDOWN, ac10x->inpdev->keybit); ++ input_set_capability(ac10x->inpdev, EV_SW, SW_HEADPHONE_INSERT); ++ ++ ret = input_register_device(ac10x->inpdev); ++ if (ret) { ++ AC101_DBG("input_register_device: input_register_device failed\n"); ++ goto _err_input_register_device; ++ } ++ ++ /* the first headset state checking */ ++ switch_hw_config(ac10x->codec); ++ ac10x->irq_cntr = 1; ++ schedule_work(&ac10x->work_switch); ++ ++ return 0; ++ ++_err_input_register_device: ++_err_input_allocate_device: ++ ++ if (ac10x->irq) { ++ devm_free_irq(&i2c->dev, ac10x->irq, ac10x); ++ ac10x->irq = 0; ++ } ++_err_irq: ++ return ret; ++} ++/******************************************************************************/ ++/********************************switch****************************************/ ++/******************************************************************************/ ++#endif ++ ++ ++ ++void drc_config(struct snd_soc_codec *codec) ++{ ++ int reg_val; ++ reg_val = ac101_read(codec, 0xa3); ++ reg_val &= ~(0x7ff<<0); ++ reg_val |= 1<<0; ++ ac101_write(codec, 0xa3, reg_val); ++ ac101_write(codec, 0xa4, 0x2baf); ++ ++ reg_val = ac101_read(codec, 0xa5); ++ reg_val &= ~(0x7ff<<0); ++ reg_val |= 1<<0; ++ ac101_write(codec, 0xa5, reg_val); ++ ac101_write(codec, 0xa6, 0x2baf); ++ ++ reg_val = ac101_read(codec, 0xa7); ++ reg_val &= ~(0x7ff<<0); ++ ac101_write(codec, 0xa7, reg_val); ++ ac101_write(codec, 0xa8, 0x44a); ++ ++ reg_val = ac101_read(codec, 0xa9); ++ reg_val &= ~(0x7ff<<0); ++ ac101_write(codec, 0xa9, reg_val); ++ ac101_write(codec, 0xaa, 0x1e06); ++ ++ reg_val = ac101_read(codec, 0xab); ++ reg_val &= ~(0x7ff<<0); ++ reg_val |= (0x352<<0); ++ ac101_write(codec, 0xab, reg_val); ++ ac101_write(codec, 0xac, 0x6910); ++ ++ reg_val = ac101_read(codec, 0xad); ++ reg_val &= ~(0x7ff<<0); ++ reg_val |= (0x77a<<0); ++ ac101_write(codec, 0xad, reg_val); ++ ac101_write(codec, 0xae, 0xaaaa); ++ ++ reg_val = ac101_read(codec, 0xaf); ++ reg_val &= ~(0x7ff<<0); ++ reg_val |= (0x2de<<0); ++ ac101_write(codec, 0xaf, reg_val); ++ ac101_write(codec, 0xb0, 0xc982); ++ ++ ac101_write(codec, 0x16, 0x9f9f); ++ ++} ++ ++void drc_enable(struct snd_soc_codec *codec,bool on) ++{ ++ int reg_val; ++ if (on) { ++ ac101_write(codec, 0xb5, 0xA080); ++ reg_val = ac101_read(codec, MOD_CLK_ENA); ++ reg_val |= (0x1<<6); ++ ac101_write(codec, MOD_CLK_ENA, reg_val); ++ reg_val = ac101_read(codec, MOD_RST_CTRL); ++ reg_val |= (0x1<<6); ++ ac101_write(codec, MOD_RST_CTRL, reg_val); ++ ++ reg_val = ac101_read(codec, 0xa0); ++ reg_val |= (0x7<<0); ++ ac101_write(codec, 0xa0, reg_val); ++ } else { ++ ac101_write(codec, 0xb5, 0x0); ++ reg_val = ac101_read(codec, MOD_CLK_ENA); ++ reg_val &= ~(0x1<<6); ++ ac101_write(codec, MOD_CLK_ENA, reg_val); ++ reg_val = ac101_read(codec, MOD_RST_CTRL); ++ reg_val &= ~(0x1<<6); ++ ac101_write(codec, MOD_RST_CTRL, reg_val); ++ ++ reg_val = ac101_read(codec, 0xa0); ++ reg_val &= ~(0x7<<0); ++ ac101_write(codec, 0xa0, reg_val); ++ } ++} ++ ++void set_configuration(struct snd_soc_codec *codec) ++{ ++ if (speaker_double_used) { ++ ac101_update_bits(codec, SPKOUT_CTRL, (0x1f<<SPK_VOL), ++ (double_speaker_val<<SPK_VOL)); ++ } else { ++ ac101_update_bits(codec, SPKOUT_CTRL, (0x1f<<SPK_VOL), ++ (single_speaker_val<<SPK_VOL)); ++ } ++ ac101_update_bits(codec, HPOUT_CTRL, (0x3f<<HP_VOL), (headset_val<<HP_VOL)); ++ ac101_update_bits(codec, ADC_SRCBST_CTRL, (0x7<<ADC_MIC1G), (mainmic_val<<ADC_MIC1G)); ++ ac101_update_bits(codec, ADC_SRCBST_CTRL, (0x7<<ADC_MIC2G), (headsetmic_val<<ADC_MIC2G)); ++ if (dmic_used) { ++ ac101_write(codec, ADC_VOL_CTRL, adc_digital_val); ++ } ++ if (drc_used) { ++ drc_config(codec); ++ } ++ /*headphone calibration clock frequency select*/ ++ ac101_update_bits(codec, SPKOUT_CTRL, (0x7<<HPCALICKS), (0x7<<HPCALICKS)); ++ ++ /* I2S1 DAC Timeslot 0 data <- I2S1 DAC channel 0 */ ++ // "AIF1IN0L Mux" <= "AIF1DACL" ++ // "AIF1IN0R Mux" <= "AIF1DACR" ++ ac101_update_bits(codec, AIF1_DACDAT_CTRL, 0x3 << AIF1_DA0L_SRC, 0x0 << AIF1_DA0L_SRC); ++ ac101_update_bits(codec, AIF1_DACDAT_CTRL, 0x3 << AIF1_DA0R_SRC, 0x0 << AIF1_DA0R_SRC); ++ /* Timeslot 0 Left & Right Channel enable */ ++ ac101_update_bits(codec, AIF1_DACDAT_CTRL, 0x3 << AIF1_DA0R_ENA, 0x3 << AIF1_DA0R_ENA); ++ ++ /* DAC Digital Mixer Source Select <- I2S1 DA0 */ ++ // "DACL Mixer" += "AIF1IN0L Mux" ++ // "DACR Mixer" += "AIF1IN0R Mux" ++ ac101_update_bits(codec, DAC_MXR_SRC, 0xF << DACL_MXR_ADCL, 0x8 << DACL_MXR_ADCL); ++ ac101_update_bits(codec, DAC_MXR_SRC, 0xF << DACR_MXR_ADCR, 0x8 << DACR_MXR_ADCR); ++ /* Internal DAC Analog Left & Right Channel enable */ ++ ac101_update_bits(codec, OMIXER_DACA_CTRL, 0x3 << DACALEN, 0x3 << DACALEN); ++ ++ /* Output Mixer Source Select */ ++ // "Left Output Mixer" += "DACL Mixer" ++ // "Right Output Mixer" += "DACR Mixer" ++ ac101_update_bits(codec, OMIXER_SR, 0x1 << LMIXMUTEDACL, 0x1 << LMIXMUTEDACL); ++ ac101_update_bits(codec, OMIXER_SR, 0x1 << RMIXMUTEDACR, 0x1 << RMIXMUTEDACR); ++ /* Left & Right Analog Output Mixer enable */ ++ ac101_update_bits(codec, OMIXER_DACA_CTRL, 0x3 << LMIXEN, 0x3 << LMIXEN); ++ ++ /* Headphone Ouput Control */ ++ // "HP_R Mux" <= "DACR Mixer" ++ // "HP_L Mux" <= "DACL Mixer" ++ ac101_update_bits(codec, HPOUT_CTRL, 0x1 << LHPS, 0x0 << LHPS); ++ ac101_update_bits(codec, HPOUT_CTRL, 0x1 << RHPS, 0x0 << RHPS); ++ ++ /* Speaker Output Control */ ++ // "SPK_L Mux" <= "SPK_LR Adder" ++ // "SPK_R Mux" <= "SPK_LR Adder" ++ ac101_update_bits(codec, SPKOUT_CTRL, (0x1 << LSPKS) | (0x1 << RSPKS), ++ (0x1 << LSPKS) | (0x1 << RSPKS)); ++ /* Enable Left & Right Speaker */ ++ ac101_update_bits(codec, SPKOUT_CTRL, (0x1 << LSPK_EN) | (0x1 << RSPK_EN), ++ (0x1 << LSPK_EN) | (0x1 << RSPK_EN)); ++ return; ++} ++ ++static int late_enable_dac(struct snd_soc_codec* codec, int event) { ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ ++ mutex_lock(&ac10x->dac_mutex); ++ switch (event) { ++ case SND_SOC_DAPM_PRE_PMU: ++ AC101_DBG(); ++ if (ac10x->dac_enable == 0){ ++ /*enable dac module clk*/ ++ ac101_update_bits(codec, MOD_CLK_ENA, (0x1<<MOD_CLK_DAC_DIG), ++ (0x1<<MOD_CLK_DAC_DIG)); ++ ac101_update_bits(codec, MOD_RST_CTRL, (0x1<<MOD_RESET_DAC_DIG), ++ (0x1<<MOD_RESET_DAC_DIG)); ++ ac101_update_bits(codec, DAC_DIG_CTRL, (0x1<<ENDA), (0x1<<ENDA)); ++ ac101_update_bits(codec, DAC_DIG_CTRL, (0x1<<ENHPF),(0x1<<ENHPF)); ++ } ++ ac10x->dac_enable++; ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ if (ac10x->dac_enable != 0){ ++ ac10x->dac_enable = 0; ++ ++ ac101_update_bits(codec, DAC_DIG_CTRL, (0x1<<ENHPF),(0x0<<ENHPF)); ++ ac101_update_bits(codec, DAC_DIG_CTRL, (0x1<<ENDA), (0x0<<ENDA)); ++ /*disable dac module clk*/ ++ ac101_update_bits(codec, MOD_CLK_ENA, (0x1<<MOD_CLK_DAC_DIG), ++ (0x0<<MOD_CLK_DAC_DIG)); ++ ac101_update_bits(codec, MOD_RST_CTRL, (0x1<<MOD_RESET_DAC_DIG), ++ (0x0<<MOD_RESET_DAC_DIG)); ++ } ++ break; ++ } ++ mutex_unlock(&ac10x->dac_mutex); ++ return 0; ++} ++ ++static int ac101_headphone_event(struct snd_soc_codec* codec, int event) { ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ /*open*/ ++ AC101_DBG("post:open\n"); ++ ac101_update_bits(codec, OMIXER_DACA_CTRL, (0xf<<HPOUTPUTENABLE), ++ (0xf<<HPOUTPUTENABLE)); ++ msleep(10); ++ ac101_update_bits(codec, HPOUT_CTRL, (0x1<<HPPA_EN), (0x1<<HPPA_EN)); ++ ac101_update_bits(codec, HPOUT_CTRL, (0x3<<LHPPA_MUTE), (0x3<<LHPPA_MUTE)); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ /*close*/ ++ AC101_DBG("pre:close\n"); ++ ac101_update_bits(codec, HPOUT_CTRL, (0x3<<LHPPA_MUTE), (0x0<<LHPPA_MUTE)); ++ msleep(10); ++ ac101_update_bits(codec, OMIXER_DACA_CTRL, (0xf<<HPOUTPUTENABLE), ++ (0x0<<HPOUTPUTENABLE)); ++ ac101_update_bits(codec, HPOUT_CTRL, (0x1<<HPPA_EN), (0x0<<HPPA_EN)); ++ break; ++ } ++ return 0; ++} ++ ++static int ac101_sysclk_started(void) { ++ int reg_val; ++ ++ reg_val = ac101_read(static_ac10x->codec, SYSCLK_CTRL); ++ return (reg_val & (0x1<<SYSCLK_ENA)); ++} ++ ++static int ac101_aif1clk(struct snd_soc_codec* codec, int event, int quick) { ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ int ret = 0; ++ ++ switch (event) { ++ case SND_SOC_DAPM_PRE_PMU: ++ if (ac10x->aif1_clken == 0){ ++ ret = ac101_update_bits(codec, SYSCLK_CTRL, (0x1<<AIF1CLK_ENA), ++ (0x1<<AIF1CLK_ENA)); ++ if(!quick || _MASTER_MULTI_CODEC != _MASTER_AC101) { ++ /* enable aif1clk & sysclk */ ++ ret = ret || ac101_update_bits(codec, MOD_CLK_ENA, ++ (0x1<<MOD_CLK_AIF1), ++ (0x1<<MOD_CLK_AIF1)); ++ ret = ret || ac101_update_bits(codec, MOD_RST_CTRL, ++ (0x1<<MOD_RESET_AIF1), ++ (0x1<<MOD_RESET_AIF1)); ++ } ++ ret = ret || ac101_update_bits(codec, SYSCLK_CTRL, (0x1<<SYSCLK_ENA), ++ (0x1<<SYSCLK_ENA)); ++ ++ if (ret) { ++ AC101_DBG("start sysclk failed\n"); ++ } else { ++ AC101_DBG("hw sysclk enable\n"); ++ ac10x->aif1_clken++; ++ } ++ } ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ if (ac10x->aif1_clken != 0) { ++ /* disable aif1clk & sysclk */ ++ ret = ac101_update_bits(codec, SYSCLK_CTRL, (0x1<<AIF1CLK_ENA), ++ (0x0<<AIF1CLK_ENA)); ++ ret = ret || ac101_update_bits(codec, MOD_CLK_ENA, (0x1<<MOD_CLK_AIF1), ++ (0x0<<MOD_CLK_AIF1)); ++ ret = ret || ac101_update_bits(codec, MOD_RST_CTRL, (0x1<<MOD_RESET_AIF1), ++ (0x0<<MOD_RESET_AIF1)); ++ ret = ret || ac101_update_bits(codec, SYSCLK_CTRL, (0x1<<SYSCLK_ENA), ++ (0x0<<SYSCLK_ENA)); ++ ++ if (ret) { ++ AC101_DBG("stop sysclk failed\n"); ++ } else { ++ AC101_DBG("hw sysclk disable\n"); ++ ac10x->aif1_clken = 0; ++ } ++ break; ++ } ++ } ++ ++ AC101_DBG("event=%d pre_up/%d post_down/%d\n", event, SND_SOC_DAPM_PRE_PMU, ++ SND_SOC_DAPM_POST_PMD); ++ ++ return ret; ++} ++ ++/** ++ * snd_ac101_get_volsw - single mixer get callback ++ * @kcontrol: mixer control ++ * @ucontrol: control element information ++ * ++ * Callback to get the value of a single mixer control, or a double mixer ++ * control that spans 2 registers. ++ * ++ * Returns 0 for success. ++ */ ++static int snd_ac101_get_volsw(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol ++){ ++ struct soc_mixer_control *mc = ++ (struct soc_mixer_control *)kcontrol->private_value; ++ unsigned int val, mask = (1 << fls(mc->max)) - 1; ++ unsigned int invert = mc->invert; ++ int ret; ++ ++ if ((ret = ac101_read(static_ac10x->codec, mc->reg)) < 0) ++ return ret; ++ ++ val = (ret >> mc->shift) & mask; ++ ucontrol->value.integer.value[0] = val - mc->min; ++ if (invert) { ++ ucontrol->value.integer.value[0] = ++ mc->max - ucontrol->value.integer.value[0]; ++ } ++ ++ if (snd_soc_volsw_is_stereo(mc)) { ++ val = (ret >> mc->rshift) & mask; ++ ucontrol->value.integer.value[1] = val - mc->min; ++ if (invert) { ++ ucontrol->value.integer.value[1] = ++ mc->max - ucontrol->value.integer.value[1]; ++ } ++ } ++ return 0; ++} ++ ++/** ++ * snd_ac101_put_volsw - single mixer put callback ++ * @kcontrol: mixer control ++ * @ucontrol: control element information ++ * ++ * Callback to set the value of a single mixer control, or a double mixer ++ * control that spans 2 registers. ++ * ++ * Returns 0 for success. ++ */ ++static int snd_ac101_put_volsw(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol ++){ ++ struct soc_mixer_control *mc = ++ (struct soc_mixer_control *)kcontrol->private_value; ++ unsigned int sign_bit = mc->sign_bit; ++ unsigned int val, mask = (1 << fls(mc->max)) - 1; ++ unsigned int invert = mc->invert; ++ int ret; ++ ++ if (sign_bit) ++ mask = BIT(sign_bit + 1) - 1; ++ ++ val = ((ucontrol->value.integer.value[0] + mc->min) & mask); ++ if (invert) { ++ val = mc->max - val; ++ } ++ ++ ret = ac101_update_bits(static_ac10x->codec, mc->reg, mask << mc->shift, val << mc->shift); ++ ++ if (! snd_soc_volsw_is_stereo(mc)) { ++ return ret; ++ } ++ val = ((ucontrol->value.integer.value[1] + mc->min) & mask); ++ if (invert) { ++ val = mc->max - val; ++ } ++ ++ ret = ac101_update_bits(static_ac10x->codec, mc->reg, mask << mc->rshift, ++ val << mc->rshift); ++ return ret; ++} ++ ++ ++static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -11925, 75, 0); ++static const DECLARE_TLV_DB_SCALE(dac_mix_vol_tlv, -600, 600, 0); ++static const DECLARE_TLV_DB_SCALE(dig_vol_tlv, -7308, 116, 0); ++static const DECLARE_TLV_DB_SCALE(speaker_vol_tlv, -4800, 150, 0); ++static const DECLARE_TLV_DB_SCALE(headphone_vol_tlv, -6300, 100, 0); ++ ++static struct snd_kcontrol_new ac101_controls[] = { ++ /*DAC*/ ++ SOC_DOUBLE_TLV("DAC volume", DAC_VOL_CTRL, DAC_VOL_L, DAC_VOL_R, 0xff, 0, dac_vol_tlv), ++ SOC_DOUBLE_TLV("DAC mixer gain", DAC_MXR_GAIN, DACL_MXR_GAIN, DACR_MXR_GAIN, ++ 0xf, 0, dac_mix_vol_tlv), ++ SOC_SINGLE_TLV("digital volume", DAC_DBG_CTRL, DVC, 0x3f, 1, dig_vol_tlv), ++ SOC_SINGLE_TLV("speaker volume", SPKOUT_CTRL, SPK_VOL, 0x1f, 0, speaker_vol_tlv), ++ SOC_SINGLE_TLV("headphone volume", HPOUT_CTRL, HP_VOL, 0x3f, 0, headphone_vol_tlv), ++}; ++ ++/* PLL divisors */ ++struct pll_div { ++ unsigned int pll_in; ++ unsigned int pll_out; ++ int m; ++ int n_i; ++ int n_f; ++}; ++ ++struct aif1_fs { ++ unsigned samp_rate; ++ int bclk_div; ++ int srbit; ++ #define _SERIES_24_576K 0 ++ #define _SERIES_22_579K 1 ++ int series; ++}; ++ ++struct kv_map { ++ int val; ++ int bit; ++}; ++ ++/* ++ * Note : pll code from original tdm/i2s driver. ++ * freq_out = freq_in * N/(M*(2k+1)) , k=1,N=N_i+N_f,N_f=factor*0.2; ++ * N_i[0,1023], N_f_factor[0,7], m[1,64]=REG_VAL[1-63,0] ++ */ ++static const struct pll_div codec_pll_div[] = { ++ {128000, _FREQ_22_579K, 1, 529, 1}, ++ {192000, _FREQ_22_579K, 1, 352, 4}, ++ {256000, _FREQ_22_579K, 1, 264, 3}, ++ {384000, _FREQ_22_579K, 1, 176, 2}, /*((176+2*0.2)*6000000)/(38*(2*1+1))*/ ++ {1411200, _FREQ_22_579K, 1, 48, 0}, ++ {2822400, _FREQ_22_579K, 1, 24, 0}, /* accurate, 11025 * 256 */ ++ {5644800, _FREQ_22_579K, 1, 12, 0}, /* accurate, 22050 * 256 */ ++ {6000000, _FREQ_22_579K, 38, 429, 0}, /*((429+0*0.2)*6000000)/(38*(2*1+1))*/ ++ {11289600, _FREQ_22_579K, 1, 6, 0}, /* accurate, 44100 * 256 */ ++ {13000000, _FREQ_22_579K, 19, 99, 0}, ++ {19200000, _FREQ_22_579K, 25, 88, 1}, ++ {24000000, _FREQ_22_579K, 63, 177, 4}, /* 22577778 Hz */ ++ ++ {128000, _FREQ_24_576K, 1, 576, 0}, ++ {192000, _FREQ_24_576K, 1, 384, 0}, ++ {256000, _FREQ_24_576K, 1, 288, 0}, ++ {384000, _FREQ_24_576K, 1, 192, 0}, ++ {2048000, _FREQ_24_576K, 1, 36, 0}, /* accurate, 8000 * 256 */ ++ {3072000, _FREQ_24_576K, 1, 24, 0}, /* accurate, 12000 * 256 */ ++ {4096000, _FREQ_24_576K, 1, 18, 0}, /* accurate, 16000 * 256 */ ++ {6000000, _FREQ_24_576K, 25, 307, 1}, ++ {6144000, _FREQ_24_576K, 4, 48, 0}, /* accurate, 24000 * 256 */ ++ {12288000, _FREQ_24_576K, 8, 48, 0}, /* accurate, 48000 * 256 */ ++ {13000000, _FREQ_24_576K, 42, 238, 1}, ++ {19200000, _FREQ_24_576K, 25, 96, 0}, ++ {24000000, _FREQ_24_576K, 25, 76, 4}, /* accurate */ ++ ++ {_FREQ_22_579K, _FREQ_22_579K, 8, 24, 0}, /* accurate, 88200 * 256 */ ++ {_FREQ_24_576K, _FREQ_24_576K, 8, 24, 0}, /* accurate, 96000 * 256 */ ++}; ++ ++static const struct aif1_fs codec_aif1_fs[] = { ++ {8000, 12, 0}, ++ {11025, 8, 1, _SERIES_22_579K}, ++ {12000, 8, 2}, ++ {16000, 6, 3}, ++ {22050, 4, 4, _SERIES_22_579K}, ++ {24000, 4, 5}, ++ /* {32000, 3, 6}, dividing by 3 is not support */ ++ {44100, 2, 7, _SERIES_22_579K}, ++ {48000, 2, 8}, ++ {96000, 1, 9}, ++}; ++ ++static const struct kv_map codec_aif1_lrck[] = { ++ {16, 0}, ++ {32, 1}, ++ {64, 2}, ++ {128, 3}, ++ {256, 4}, ++}; ++ ++static const struct kv_map codec_aif1_wsize[] = { ++ {8, 0}, ++ {16, 1}, ++ {20, 2}, ++ {24, 3}, ++ {32, 3}, ++}; ++ ++static const unsigned ac101_bclkdivs[] = { ++ 1, 2, 4, 6, ++ 8, 12, 16, 24, ++ 32, 48, 64, 96, ++ 128, 192, 0, 0, ++}; ++ ++static int ac101_aif_play(struct ac10x_priv* ac10x) { ++ struct snd_soc_codec * codec = ac10x->codec; ++ ++ late_enable_dac(codec, SND_SOC_DAPM_PRE_PMU); ++ ac101_headphone_event(codec, SND_SOC_DAPM_POST_PMU); ++ if (drc_used) { ++ drc_enable(codec, 1); ++ } ++ ++ /* Enable Left & Right Speaker */ ++ ac101_update_bits(codec, SPKOUT_CTRL, (0x1 << LSPK_EN) | (0x1 << RSPK_EN), ++ (0x1 << LSPK_EN) | (0x1 << RSPK_EN)); ++ if (ac10x->gpiod_spk_amp_gate) { ++ gpiod_set_value(ac10x->gpiod_spk_amp_gate, 1); ++ } ++ return 0; ++} ++ ++static void ac10x_work_aif_play(struct work_struct *work) { ++ struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, dlywork.work); ++ ++ ac101_aif_play(ac10x); ++ return; ++} ++ ++int ac101_aif_mute(struct snd_soc_dai *codec_dai, int mute) ++{ ++ struct snd_soc_codec *codec = codec_dai->codec; ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ ++ AC101_DBG("mute=%d\n", mute); ++ ++ ac101_write(codec, DAC_VOL_CTRL, mute? 0: 0xA0A0); ++ ++ if (!mute) { ++ #if _MASTER_MULTI_CODEC != _MASTER_AC101 ++ /* enable global clock */ ++ ac10x->aif1_clken = 0; ++ ac101_aif1clk(codec, SND_SOC_DAPM_PRE_PMU, 0); ++ ac101_aif_play(ac10x); ++ #else ++ schedule_delayed_work(&ac10x->dlywork, msecs_to_jiffies(50)); ++ #endif ++ } else { ++ #if _MASTER_MULTI_CODEC == _MASTER_AC101 ++ cancel_delayed_work_sync(&ac10x->dlywork); ++ #endif ++ ++ if (ac10x->gpiod_spk_amp_gate) { ++ gpiod_set_value(ac10x->gpiod_spk_amp_gate, 0); ++ } ++ /* Disable Left & Right Speaker */ ++ ac101_update_bits(codec, SPKOUT_CTRL, (0x1 << LSPK_EN) | (0x1 << RSPK_EN), ++ (0x0 << LSPK_EN) | (0x0 << RSPK_EN)); ++ if (drc_used) { ++ drc_enable(codec, 0); ++ } ++ ac101_headphone_event(codec, SND_SOC_DAPM_PRE_PMD); ++ late_enable_dac(codec, SND_SOC_DAPM_POST_PMD); ++ ++ #if _MASTER_MULTI_CODEC != _MASTER_AC101 ++ ac10x->aif1_clken = 1; ++ ac101_aif1clk(codec, SND_SOC_DAPM_POST_PMD, 0); ++ #endif ++ } ++ return 0; ++} ++ ++void ac101_aif_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai) ++{ ++ struct snd_soc_codec *codec = codec_dai->codec; ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ ++ AC101_DBG("stream = %s, play: %d, capt: %d, active: %d\n", ++ snd_pcm_stream_str(substream), ++ codec_dai->stream[SNDRV_PCM_STREAM_PLAYBACK].active, ++ codec_dai->stream[SNDRV_PCM_STREAM_CAPTURE].active, ++ snd_soc_dai_active(codec_dai)); ++ ++ if (!snd_soc_dai_active(codec_dai)) { ++ ac10x->aif1_clken = 1; ++ ac101_aif1clk(codec, SND_SOC_DAPM_POST_PMD, 0); ++ } else { ++ ac101_aif1clk(codec, SND_SOC_DAPM_PRE_PMU, 0); ++ } ++} ++ ++static int ac101_set_pll(struct snd_soc_dai *codec_dai, int pll_id, int source, ++ unsigned int freq_in, unsigned int freq_out) ++{ ++ struct snd_soc_codec *codec = codec_dai->codec; ++ int i, m, n_i, n_f; ++ ++ AC101_DBG("pll_id:%d\n", pll_id); ++ ++ /* clear volatile reserved bits*/ ++ ac101_update_bits(codec, SYSCLK_CTRL, 0xFF & ~(0x1 << SYSCLK_ENA), 0x0); ++ ++ /* select aif1 clk srouce from mclk1 */ ++ ac101_update_bits(codec, SYSCLK_CTRL, (0x3<<AIF1CLK_SRC), (0x0<<AIF1CLK_SRC)); ++ /* disable pll */ ++ ac101_update_bits(codec, PLL_CTRL2, (0x1<<PLL_EN), (0<<PLL_EN)); ++ ++ if (!freq_out) ++ return 0; ++ if ((freq_in < 128000) || (freq_in > _FREQ_24_576K)) { ++ return -EINVAL; ++ } else if ((freq_in == _FREQ_24_576K) || (freq_in == _FREQ_22_579K)) { ++ if (pll_id == AC101_MCLK1) { ++ /*select aif1 clk source from mclk1*/ ++ ac101_update_bits(codec, SYSCLK_CTRL, (0x3<<AIF1CLK_SRC), ++ (0x0<<AIF1CLK_SRC)); ++ return 0; ++ } ++ } ++ ++ switch (pll_id) { ++ case AC101_MCLK1: ++ /*pll source from MCLK1*/ ++ ac101_update_bits(codec, SYSCLK_CTRL, (0x3<<PLLCLK_SRC), (0x0<<PLLCLK_SRC)); ++ break; ++ case AC101_BCLK1: ++ /*pll source from BCLK1*/ ++ ac101_update_bits(codec, SYSCLK_CTRL, (0x3<<PLLCLK_SRC), (0x2<<PLLCLK_SRC)); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ /* freq_out = freq_in * n/(m*(2k+1)) , k=1,N=N_i+N_f */ ++ for (i = m = n_i = n_f = 0; i < ARRAY_SIZE(codec_pll_div); i++) { ++ if ((codec_pll_div[i].pll_in == freq_in) && ++ (codec_pll_div[i].pll_out == freq_out)) { ++ m = codec_pll_div[i].m; ++ n_i = codec_pll_div[i].n_i; ++ n_f = codec_pll_div[i].n_f; ++ break; ++ } ++ } ++ /* config pll m */ ++ if (m == 64) m = 0; ++ ac101_update_bits(codec, PLL_CTRL1, (0x3f<<PLL_POSTDIV_M), (m<<PLL_POSTDIV_M)); ++ /* config pll n */ ++ ac101_update_bits(codec, PLL_CTRL2, (0x3ff<<PLL_PREDIV_NI), (n_i<<PLL_PREDIV_NI)); ++ ac101_update_bits(codec, PLL_CTRL2, (0x7<<PLL_POSTDIV_NF), (n_f<<PLL_POSTDIV_NF)); ++ /* enable pll */ ++ ac101_update_bits(codec, PLL_CTRL2, (0x1<<PLL_EN), (1<<PLL_EN)); ++ ac101_update_bits(codec, SYSCLK_CTRL, (0x1<<PLLCLK_ENA), (0x1<<PLLCLK_ENA)); ++ ac101_update_bits(codec, SYSCLK_CTRL, (0x3<<AIF1CLK_SRC), (0x3<<AIF1CLK_SRC)); ++ ++ return 0; ++} ++ ++int ac101_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *codec_dai) ++{ ++ int i = 0; ++ int AIF_CLK_CTRL = AIF1_CLK_CTRL; ++ int aif1_word_size = 24; ++ int aif1_slot_size = 32; ++ int aif1_lrck_div; ++ struct snd_soc_codec *codec = codec_dai->codec; ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ int reg_val, freq_out; ++ unsigned channels; ++ ++ AC101_DBG("+++\n"); ++ ++ if (_MASTER_MULTI_CODEC == _MASTER_AC101 && ac101_sysclk_started()) { ++ /* not configure hw_param twice if stream is playback, tell the caller it's started */ ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ return 1; ++ } ++ } ++ ++ /* get channels count & slot size */ ++ channels = params_channels(params); ++ ++ switch (params_format(params)) { ++ case SNDRV_PCM_FORMAT_S24_LE: ++ case SNDRV_PCM_FORMAT_S32_LE: ++ aif1_slot_size = 32; ++ break; ++ case SNDRV_PCM_FORMAT_S16_LE: ++ default: ++ aif1_slot_size = 16; ++ break; ++ } ++ ++ /* set LRCK/BCLK ratio */ ++ aif1_lrck_div = aif1_slot_size * channels; ++ for (i = 0; i < ARRAY_SIZE(codec_aif1_lrck); i++) { ++ if (codec_aif1_lrck[i].val == aif1_lrck_div) { ++ break; ++ } ++ } ++ ac101_update_bits(codec, AIF_CLK_CTRL, (0x7 << AIF1_LRCK_DIV), ++ codec_aif1_lrck[i].bit << AIF1_LRCK_DIV); ++ ++ /* set PLL output freq */ ++ freq_out = _FREQ_24_576K; ++ for (i = 0; i < ARRAY_SIZE(codec_aif1_fs); i++) { ++ if (codec_aif1_fs[i].samp_rate == params_rate(params)) { ++ if (codec_dai->stream[SNDRV_PCM_STREAM_CAPTURE].active && dmic_used && ++ codec_aif1_fs[i].samp_rate == 44100) { ++ ac101_update_bits(codec, AIF_SR_CTRL, (0xf<<AIF1_FS), ++ (0x4<<AIF1_FS)); ++ } else { ++ ac101_update_bits(codec, AIF_SR_CTRL, (0xf<<AIF1_FS), ++ ((codec_aif1_fs[i].srbit)<<AIF1_FS)); ++ } ++ if (codec_aif1_fs[i].series == _SERIES_22_579K) ++ freq_out = _FREQ_22_579K; ++ break; ++ } ++ } ++ ++ /* set I2S word size */ ++ for (i = 0; i < ARRAY_SIZE(codec_aif1_wsize); i++) { ++ if (codec_aif1_wsize[i].val == aif1_word_size) { ++ break; ++ } ++ } ++ ac101_update_bits(codec, AIF_CLK_CTRL, (0x3<<AIF1_WORK_SIZ), ++ ((codec_aif1_wsize[i].bit)<<AIF1_WORK_SIZ)); ++ ++ /* set TDM slot size */ ++ if ((reg_val = codec_aif1_wsize[i].bit) > 2) reg_val = 2; ++ ac101_update_bits(codec, AIF1_ADCDAT_CTRL, 0x3 << AIF1_SLOT_SIZ, reg_val << AIF1_SLOT_SIZ); ++ ++ /* setting pll if it's master mode */ ++ reg_val = ac101_read(codec, AIF_CLK_CTRL); ++ if ((reg_val & (0x1 << AIF1_MSTR_MOD)) == 0) { ++ unsigned bclkdiv; ++ ++ ac101_set_pll(codec_dai, AC101_MCLK1, 0, ac10x->sysclk, freq_out); ++ ++ bclkdiv = freq_out / (aif1_lrck_div * params_rate(params)); ++ for (i = 0; i < ARRAY_SIZE(ac101_bclkdivs) - 1; i++) { ++ if (ac101_bclkdivs[i] >= bclkdiv) { ++ break; ++ } ++ } ++ ac101_update_bits(codec, AIF_CLK_CTRL, (0xf<<AIF1_BCLK_DIV), i<<AIF1_BCLK_DIV); ++ } else { ++ /* set pll clock source to BCLK if slave mode */ ++ ac101_set_pll(codec_dai, AC101_BCLK1, 0, aif1_lrck_div * params_rate(params), ++ freq_out); ++ } ++ ++ #if _MASTER_MULTI_CODEC == _MASTER_AC101 ++ /* Master mode, to clear cpu_dai fifos, disable output bclk & lrck */ ++ ac101_aif1clk(codec, SND_SOC_DAPM_POST_PMD, 0); ++ #endif ++ ++ AC101_DBG("rate: %d , channels: %d , samp_res: %d", ++ params_rate(params), channels, aif1_slot_size); ++ ++ AC101_DBG("---\n"); ++ return 0; ++} ++ ++int ac101_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) ++{ ++ int reg_val; ++ int AIF_CLK_CTRL = AIF1_CLK_CTRL; ++ struct snd_soc_codec *codec = codec_dai->codec; ++ ++ AC101_DBG(); ++ ++ /* ++ * master or slave selection ++ * 0 = Master mode ++ * 1 = Slave mode ++ */ ++ reg_val = ac101_read(codec, AIF_CLK_CTRL); ++ reg_val &= ~(0x1<<AIF1_MSTR_MOD); ++ switch(fmt & SND_SOC_DAIFMT_MASTER_MASK) { ++ case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master, ap is slave*/ ++ #if _MASTER_MULTI_CODEC == _MASTER_AC101 ++ pr_warn("AC101 as Master\n"); ++ reg_val |= (0x0<<AIF1_MSTR_MOD); ++ break; ++ #else ++ pr_warn("AC108 as Master\n"); ++ #endif ++ case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave, ap is master*/ ++ pr_warn("AC101 as Slave\n"); ++ reg_val |= (0x1<<AIF1_MSTR_MOD); ++ break; ++ default: ++ pr_err("unknwon master/slave format\n"); ++ return -EINVAL; ++ } ++ ++ /* ++ * Enable TDM mode ++ */ ++ reg_val |= (0x1 << AIF1_TDMM_ENA); ++ ac101_write(codec, AIF_CLK_CTRL, reg_val); ++ ++ /* i2s mode selection */ ++ reg_val = ac101_read(codec, AIF_CLK_CTRL); ++ reg_val&=~(3<<AIF1_DATA_FMT); ++ switch(fmt & SND_SOC_DAIFMT_FORMAT_MASK){ ++ case SND_SOC_DAIFMT_I2S: /* I2S1 mode */ ++ reg_val |= (0x0<<AIF1_DATA_FMT); ++ break; ++ case SND_SOC_DAIFMT_RIGHT_J: /* Right Justified mode */ ++ reg_val |= (0x2<<AIF1_DATA_FMT); ++ break; ++ case SND_SOC_DAIFMT_LEFT_J: /* Left Justified mode */ ++ reg_val |= (0x1<<AIF1_DATA_FMT); ++ break; ++ case SND_SOC_DAIFMT_DSP_A: /* L reg_val msb after FRM LRC */ ++ reg_val |= (0x3<<AIF1_DATA_FMT); ++ break; ++ case SND_SOC_DAIFMT_DSP_B: ++ /* TODO: data offset set to 0 */ ++ reg_val |= (0x3<<AIF1_DATA_FMT); ++ break; ++ default: ++ pr_err("%s, line:%d\n", __func__, __LINE__); ++ return -EINVAL; ++ } ++ ac101_write(codec, AIF_CLK_CTRL, reg_val); ++ ++ /* DAI signal inversions */ ++ reg_val = ac101_read(codec, AIF_CLK_CTRL); ++ switch(fmt & SND_SOC_DAIFMT_INV_MASK){ ++ case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + nor frame */ ++ reg_val &= ~(0x1<<AIF1_LRCK_INV); ++ reg_val &= ~(0x1<<AIF1_BCLK_INV); ++ break; ++ case SND_SOC_DAIFMT_NB_IF: /* normal bclk + inv frm */ ++ reg_val |= (0x1<<AIF1_LRCK_INV); ++ reg_val &= ~(0x1<<AIF1_BCLK_INV); ++ break; ++ case SND_SOC_DAIFMT_IB_NF: /* invert bclk + nor frm */ ++ reg_val &= ~(0x1<<AIF1_LRCK_INV); ++ reg_val |= (0x1<<AIF1_BCLK_INV); ++ break; ++ case SND_SOC_DAIFMT_IB_IF: /* invert bclk + inv frm */ ++ reg_val |= (0x1<<AIF1_LRCK_INV); ++ reg_val |= (0x1<<AIF1_BCLK_INV); ++ break; ++ } ++ ac101_write(codec, AIF_CLK_CTRL, reg_val); ++ ++ return 0; ++} ++ ++int ac101_audio_startup(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *codec_dai) ++{ ++ // struct snd_soc_codec *codec = codec_dai->codec; ++ ++ AC101_DBG("\n\n\n"); ++ ++ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { ++ } ++ return 0; ++} ++ ++int ac101_trigger(struct snd_pcm_substream *substream, int cmd, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_codec *codec = dai->codec; ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ int ret = 0; ++ ++ AC101_DBG("stream=%s cmd=%d\n", ++ snd_pcm_stream_str(substream), ++ cmd); ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ #if _MASTER_MULTI_CODEC == _MASTER_AC101 ++ if (ac10x->aif1_clken == 0){ ++ /* ++ * enable aif1clk, it' here due to reduce time between 'AC108 Sysclk Enable' and 'AC101 Sysclk Enable' ++ * Or else the two AC108 chips lost the sync. ++ */ ++ ret = 0; ++ ret = ret || ac101_update_bits(codec, MOD_CLK_ENA, ++ (0x1<<MOD_CLK_AIF1), (0x1<<MOD_CLK_AIF1)); ++ ret = ret || ac101_update_bits(codec, MOD_RST_CTRL, (0x1<<MOD_RESET_AIF1), ++ (0x1<<MOD_RESET_AIF1)); ++ } ++ #endif ++ break; ++ case SNDRV_PCM_TRIGGER_STOP: ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ return ret; ++} ++ ++#if 0 ++static int ac101_set_dai_sysclk(struct snd_soc_dai *codec_dai, ++ int clk_id, unsigned int freq, int dir) ++{ ++ struct snd_soc_codec *codec = codec_dai->codec; ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ ++ AC101_DBG("id=%d freq=%d, dir=%d\n", ++ clk_id, freq, dir); ++ ++ ac10x->sysclk = freq; ++ ++ return 0; ++} ++ ++static const struct snd_soc_dai_ops ac101_aif1_dai_ops = { ++ //.startup = ac101_audio_startup, ++ //.shutdown = ac101_aif_shutdown, ++ //.set_sysclk = ac101_set_dai_sysclk, ++ //.set_pll = ac101_set_pll, ++ //.set_fmt = ac101_set_dai_fmt, ++ //.hw_params = ac101_hw_params, ++ //.trigger = ac101_trigger, ++ //.digital_mute = ac101_aif_mute, ++}; ++ ++static struct snd_soc_dai_driver ac101_dai[] = { ++ { ++ .name = "ac10x-aif1", ++ .id = AIF1_CLK, ++ .playback = { ++ .stream_name = "Playback", ++ .channels_min = 1, ++ .channels_max = 8, ++ .rates = AC101_RATES, ++ .formats = AC101_FORMATS, ++ }, ++ #if 0 ++ .capture = { ++ .stream_name = "Capture", ++ .channels_min = 1, ++ .channels_max = 8, ++ .rates = AC101_RATES, ++ .formats = AC101_FORMATS, ++ }, ++ #endif ++ .ops = &ac101_aif1_dai_ops, ++ } ++}; ++#endif ++ ++static void codec_resume_work(struct work_struct *work) ++{ ++ struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, codec_resume); ++ struct snd_soc_codec *codec = ac10x->codec; ++ ++ AC101_DBG("+++\n"); ++ ++ set_configuration(codec); ++ if (drc_used) { ++ drc_config(codec); ++ } ++ /*enable this bit to prevent leakage from ldoin*/ ++ ac101_update_bits(codec, ADDA_TUNE3, (0x1<<OSCEN), (0x1<<OSCEN)); ++ ++ AC101_DBG("---\n"); ++ return; ++} ++ ++int ac101_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) ++{ ++ switch (level) { ++ case SND_SOC_BIAS_ON: ++ AC101_DBG("SND_SOC_BIAS_ON\n"); ++ break; ++ case SND_SOC_BIAS_PREPARE: ++ AC101_DBG("SND_SOC_BIAS_PREPARE\n"); ++ break; ++ case SND_SOC_BIAS_STANDBY: ++ AC101_DBG("SND_SOC_BIAS_STANDBY\n"); ++ #ifdef CONFIG_AC101_SWITCH_DETECT ++ switch_hw_config(codec); ++ #endif ++ break; ++ case SND_SOC_BIAS_OFF: ++ #ifdef CONFIG_AC101_SWITCH_DETECT ++ ac101_update_bits(codec, ADC_APC_CTRL, (0x1<<HBIASEN), (0<<HBIASEN)); ++ ac101_update_bits(codec, ADC_APC_CTRL, (0x1<<HBIASADCEN), (0<<HBIASADCEN)); ++ #endif ++ ac101_update_bits(codec, OMIXER_DACA_CTRL, (0xf<<HPOUTPUTENABLE), ++ (0<<HPOUTPUTENABLE)); ++ ac101_update_bits(codec, ADDA_TUNE3, (0x1<<OSCEN), (0<<OSCEN)); ++ AC101_DBG("SND_SOC_BIAS_OFF\n"); ++ break; ++ } ++ snd_soc_codec_get_dapm(codec)->bias_level = level; ++ return 0; ++} ++ ++int ac101_codec_probe(struct snd_soc_codec *codec) ++{ ++ int ret = 0; ++ struct ac10x_priv *ac10x; ++ ++ ac10x = dev_get_drvdata(codec->dev); ++ if (ac10x == NULL) { ++ AC101_DBG("not set client data!\n"); ++ return -ENOMEM; ++ } ++ ac10x->codec = codec; ++ ++ INIT_DELAYED_WORK(&ac10x->dlywork, ac10x_work_aif_play); ++ INIT_WORK(&ac10x->codec_resume, codec_resume_work); ++ ac10x->dac_enable = 0; ++ ac10x->aif1_clken = 0; ++ mutex_init(&ac10x->dac_mutex); ++ ++ set_configuration(ac10x->codec); ++ ++ /*enable this bit to prevent leakage from ldoin*/ ++ ac101_update_bits(codec, ADDA_TUNE3, (0x1<<OSCEN), (0x1<<OSCEN)); ++ ac101_write(codec, DAC_VOL_CTRL, 0); ++ ++ /* customized get/put inteface */ ++ for (ret = 0; ret < ARRAY_SIZE(ac101_controls); ret++) { ++ struct snd_kcontrol_new* skn = &ac101_controls[ret]; ++ ++ skn->get = snd_ac101_get_volsw; ++ skn->put = snd_ac101_put_volsw; ++ } ++ ret = snd_soc_add_codec_controls(codec, ac101_controls, ARRAY_SIZE(ac101_controls)); ++ if (ret) { ++ pr_err("[ac10x] Failed to register audio mode control, " ++ "will continue without it.\n"); ++ } ++ ++ #ifdef CONFIG_AC101_SWITCH_DETECT ++ ret = ac101_switch_probe(ac10x); ++ if (ret) { ++ // not care the switch return value ++ } ++ #endif ++ ++ return 0; ++} ++ ++/* power down chip */ ++int ac101_codec_remove(struct snd_soc_codec *codec) ++{ ++ #ifdef CONFIG_AC101_SWITCH_DETECT ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ ++ if (ac10x->irq) { ++ devm_free_irq(codec->dev, ac10x->irq, ac10x); ++ ac10x->irq = 0; ++ } ++ ++ if (cancel_work_sync(&ac10x->work_switch) != 0) { ++ } ++ ++ if (cancel_work_sync(&ac10x->work_clear_irq) != 0) { ++ } ++ ++ if (ac10x->inpdev) { ++ input_unregister_device(ac10x->inpdev); ++ ac10x->inpdev = NULL; ++ } ++ #endif ++ ++ return 0; ++} ++ ++int ac101_codec_suspend(struct snd_soc_codec *codec) ++{ ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ ++ AC101_DBG("[codec]:suspend\n"); ++ regcache_cache_only(ac10x->regmap101, true); ++ return 0; ++} ++ ++int ac101_codec_resume(struct snd_soc_codec *codec) ++{ ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ int ret; ++ ++ AC101_DBG("[codec]:resume"); ++ ++ /* Sync reg_cache with the hardware */ ++ regcache_cache_only(ac10x->regmap101, false); ++ ret = regcache_sync(ac10x->regmap101); ++ if (ret != 0) { ++ dev_err(codec->dev, "Failed to sync register cache: %d\n", ret); ++ regcache_cache_only(ac10x->regmap101, true); ++ return ret; ++ } ++ ++ #ifdef CONFIG_AC101_SWITCH_DETECT ++ ac10x->mode = HEADPHONE_IDLE; ++ ac10x->state = -1; ++ #endif ++ ++ ac101_set_bias_level(codec, SND_SOC_BIAS_STANDBY); ++ schedule_work(&ac10x->codec_resume); ++ return 0; ++} ++ ++/***************************************************************************/ ++static ssize_t ac101_debug_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct ac10x_priv *ac10x = dev_get_drvdata(dev); ++ int val = 0, flag = 0; ++ u16 value_w, value_r; ++ u8 reg, num, i=0; ++ ++ val = simple_strtol(buf, NULL, 16); ++ flag = (val >> 24) & 0xF; ++ if (flag) { ++ reg = (val >> 16) & 0xFF; ++ value_w = val & 0xFFFF; ++ ac101_write(ac10x->codec, reg, value_w); ++ printk("write 0x%x to reg:0x%x\n", value_w, reg); ++ } else { ++ reg = (val >> 8) & 0xFF; ++ num = val & 0xff; ++ printk("\n"); ++ printk("read:start add:0x%x,count:0x%x\n", reg, num); ++ ++ regcache_cache_bypass(ac10x->regmap101, true); ++ do { ++ value_r = ac101_read(ac10x->codec, reg); ++ printk("0x%x: 0x%04x ", reg++, value_r); ++ if (++i % 4 == 0 || i == num) ++ printk("\n"); ++ } while (i < num); ++ regcache_cache_bypass(ac10x->regmap101, false); ++ } ++ return count; ++} ++static ssize_t ac101_debug_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ printk("echo flag|reg|val > ac10x\n"); ++ printk("eg read star addres=0x06,count 0x10:echo 0610 >ac10x\n"); ++ printk("eg write value:0x13fe to address:0x06 :echo 10613fe > ac10x\n"); ++ return 0; ++} ++static DEVICE_ATTR(ac10x, 0644, ac101_debug_show, ac101_debug_store); ++ ++static struct attribute *audio_debug_attrs[] = { ++ &dev_attr_ac10x.attr, ++ NULL, ++}; ++ ++static struct attribute_group audio_debug_attr_group = { ++ .name = "ac101_debug", ++ .attrs = audio_debug_attrs, ++}; ++/***************************************************************************/ ++ ++/************************************************************/ ++static bool ac101_volatile_reg(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case PLL_CTRL2: ++ case HMIC_STS: ++ return true; ++ } ++ return false; ++} ++ ++static const struct regmap_config ac101_regmap = { ++ .reg_bits = 8, ++ .val_bits = 16, ++ .reg_stride = 1, ++ .max_register = 0xB5, ++ .cache_type = REGCACHE_FLAT, ++ .volatile_reg = ac101_volatile_reg, ++}; ++ ++/* Sync reg_cache from the hardware */ ++int ac10x_fill_regcache(struct device* dev, struct regmap* map) { ++ int r, i, n; ++ int v; ++ ++ n = regmap_get_max_register(map); ++ for (i = 0; i < n; i++) { ++ regcache_cache_bypass(map, true); ++ r = regmap_read(map, i, &v); ++ if (r) { ++ dev_dbg(dev, "failed to read register %d\n", i); ++ continue; ++ } ++ regcache_cache_bypass(map, false); ++ ++ regcache_cache_only(map, true); ++ r = regmap_write(map, i, v); ++ regcache_cache_only(map, false); ++ } ++ regcache_cache_bypass(map, false); ++ regcache_cache_only(map, false); ++ ++ return 0; ++} ++ ++int ac101_probe(struct i2c_client *i2c, const struct i2c_device_id *id) ++{ ++ struct ac10x_priv *ac10x = i2c_get_clientdata(i2c); ++ int ret = 0; ++ unsigned v = 0; ++ ++ AC101_DBG(); ++ ++ static_ac10x = ac10x; ++ ++ ac10x->regmap101 = devm_regmap_init_i2c(i2c, &ac101_regmap); ++ if (IS_ERR(ac10x->regmap101)) { ++ ret = PTR_ERR(ac10x->regmap101); ++ dev_err(&i2c->dev, "Fail to initialize I/O: %d\n", ret); ++ return ret; ++ } ++ ++ /* Chip reset */ ++ regcache_cache_only(ac10x->regmap101, false); ++ ret = regmap_write(ac10x->regmap101, CHIP_AUDIO_RST, 0); ++ msleep(50); ++ ++ /* sync regcache for FLAT type */ ++ ac10x_fill_regcache(&i2c->dev, ac10x->regmap101); ++ ++ ret = regmap_read(ac10x->regmap101, CHIP_AUDIO_RST, &v); ++ if (ret < 0) { ++ dev_err(&i2c->dev, "failed to read vendor ID: %d\n", ret); ++ return ret; ++ } ++ ++ if (v != AC101_CHIP_ID) { ++ dev_err(&i2c->dev, "chip is not AC101 (%X)\n", v); ++ dev_err(&i2c->dev, "Expected %X\n", AC101_CHIP_ID); ++ return -ENODEV; ++ } ++ ++ ret = sysfs_create_group(&i2c->dev.kobj, &audio_debug_attr_group); ++ if (ret) { ++ pr_err("failed to create attr group\n"); ++ } ++ ++ ac10x->gpiod_spk_amp_gate = devm_gpiod_get_optional(&i2c->dev, "spk-amp-switch", ++ GPIOD_OUT_LOW); ++ if (IS_ERR(ac10x->gpiod_spk_amp_gate)) { ++ ac10x->gpiod_spk_amp_gate = NULL; ++ dev_err(&i2c->dev, "failed get spk-amp-switch in device tree\n"); ++ } ++ ++ return 0; ++} ++ ++void ac101_shutdown(struct i2c_client *i2c) ++{ ++ struct ac10x_priv *ac10x = i2c_get_clientdata(i2c); ++ struct snd_soc_codec *codec = ac10x->codec; ++ int reg_val; ++ ++ if (codec == NULL) { ++ pr_err(": no sound card.\n"); ++ return; ++ } ++ ++ /*set headphone volume to 0*/ ++ reg_val = ac101_read(codec, HPOUT_CTRL); ++ reg_val &= ~(0x3f<<HP_VOL); ++ ac101_write(codec, HPOUT_CTRL, reg_val); ++ ++ /*disable pa*/ ++ reg_val = ac101_read(codec, HPOUT_CTRL); ++ reg_val &= ~(0x1<<HPPA_EN); ++ ac101_write(codec, HPOUT_CTRL, reg_val); ++ ++ /*hardware xzh support*/ ++ reg_val = ac101_read(codec, OMIXER_DACA_CTRL); ++ reg_val &= ~(0xf<<HPOUTPUTENABLE); ++ ac101_write(codec, OMIXER_DACA_CTRL, reg_val); ++ ++ /*unmute l/r headphone pa*/ ++ reg_val = ac101_read(codec, HPOUT_CTRL); ++ reg_val &= ~((0x1<<RHPPA_MUTE)|(0x1<<LHPPA_MUTE)); ++ ac101_write(codec, HPOUT_CTRL, reg_val); ++ return; ++} ++ ++int ac101_remove(struct i2c_client *i2c) ++{ ++ sysfs_remove_group(&i2c->dev.kobj, &audio_debug_attr_group); ++ return 0; ++} ++ ++MODULE_DESCRIPTION("ASoC ac10x driver"); ++MODULE_AUTHOR("huangxin,liushaohua"); ++MODULE_AUTHOR("PeterYang<linsheng.yang@seeed.cc>"); +--- /dev/null ++++ b/sound/soc/codecs/ac101_regs.h +@@ -0,0 +1,431 @@ ++/* ++ * ac101_regs.h ++ * ++ * (C) Copyright 2017-2018 ++ * Seeed Technology Co., Ltd. <www.seeedstudio.com> ++ * ++ * PeterYang <linsheng.yang@seeed.cc> ++ * ++ * (C) Copyright 2010-2017 ++ * Reuuimlla Technology Co., Ltd. <www.reuuimllatech.com> ++ * huangxin <huangxin@reuuimllatech.com> ++ * ++ * some simple description for this code ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of ++ * the License, or (at your option) any later version. ++ * ++ */ ++#ifndef __AC101_REGS_H__ ++#define __AC101_REGS_H__ ++ ++/*pll source*/ ++#define AC101_MCLK1 1 ++#define AC101_MCLK2 2 ++#define AC101_BCLK1 3 ++#define AC101_BCLK2 4 ++ ++#define AIF1_CLK 1 ++#define AIF2_CLK 2 ++ ++#define CHIP_AUDIO_RST 0x0 ++#define PLL_CTRL1 0x1 ++#define PLL_CTRL2 0x2 ++#define SYSCLK_CTRL 0x3 ++#define MOD_CLK_ENA 0x4 ++#define MOD_RST_CTRL 0x5 ++#define AIF_SR_CTRL 0x6 ++ ++#define AIF1_CLK_CTRL 0x10 ++#define AIF1_ADCDAT_CTRL 0x11 ++#define AIF1_DACDAT_CTRL 0x12 ++#define AIF1_MXR_SRC 0x13 ++#define AIF1_VOL_CTRL1 0x14 ++#define AIF1_VOL_CTRL2 0x15 ++#define AIF1_VOL_CTRL3 0x16 ++#define AIF1_VOL_CTRL4 0x17 ++#define AIF1_MXR_GAIN 0x18 ++#define AIF1_RXD_CTRL 0x19 ++#define ADC_DIG_CTRL 0x40 ++#define ADC_VOL_CTRL 0x41 ++#define ADC_DBG_CTRL 0x42 ++ ++#define HMIC_CTRL1 0x44 ++#define HMIC_CTRL2 0x45 ++#define HMIC_STS 0x46 ++ ++#define DAC_DIG_CTRL 0x48 ++#define DAC_VOL_CTRL 0x49 ++#define DAC_DBG_CTRL 0x4a ++#define DAC_MXR_SRC 0x4c ++#define DAC_MXR_GAIN 0x4d ++ ++#define ADC_APC_CTRL 0x50 ++#define ADC_SRC 0x51 ++#define ADC_SRCBST_CTRL 0x52 ++#define OMIXER_DACA_CTRL 0x53 ++#define OMIXER_SR 0x54 ++#define OMIXER_BST1_CTRL 0x55 ++#define HPOUT_CTRL 0x56 ++#define ESPKOUT_CTRL 0x57 ++#define SPKOUT_CTRL 0x58 ++#define LOUT_CTRL 0x59 ++#define ADDA_TUNE1 0x5a ++#define ADDA_TUNE2 0x5b ++#define ADDA_TUNE3 0x5c ++#define HPOUT_STR 0x5d ++ ++/*CHIP_AUDIO_RST*/ ++#define AC101_CHIP_ID 0x0101 ++ ++/*PLL_CTRL1*/ ++#define DPLL_DAC_BIAS 14 ++#define PLL_POSTDIV_M 8 ++#define CLOSE_LOOP 6 ++#define INT 0 ++ ++/*PLL_CTRL2*/ ++#define PLL_EN 15 ++#define PLL_LOCK_STATUS 14 ++#define PLL_PREDIV_NI 4 ++#define PLL_POSTDIV_NF 0 ++ ++/*SYSCLK_CTRL*/ ++#define PLLCLK_ENA 15 ++#define PLLCLK_SRC 12 ++#define AIF1CLK_ENA 11 ++#define AIF1CLK_SRC 8 ++#define AIF2CLK_ENA 7 ++#define AIF2CLK_SRC 4 ++#define SYSCLK_ENA 3 ++#define SYSCLK_SRC 0 ++ ++/*MOD_CLK_ENA*/ ++#define MOD_CLK_AIF1 15 ++#define MOD_CLK_AIF2 14 ++#define MOD_CLK_AIF3 13 ++#define MOD_CLK_SRC1 11 ++#define MOD_CLK_SRC2 10 ++#define MOD_CLK_HPF_AGC 7 ++#define MOD_CLK_HPF_DRC 6 ++#define MOD_CLK_ADC_DIG 3 ++#define MOD_CLK_DAC_DIG 2 ++ ++/*MOD_RST_CTRL*/ ++#define MOD_RESET_CTL 0 ++#define MOD_RESET_AIF1 15 ++#define MOD_RESET_AIF2 14 ++#define MOD_RESET_AIF3 13 ++#define MOD_RESET_SRC1 11 ++#define MOD_RESET_SRC2 10 ++#define MOD_RESET_HPF_AGC 7 ++#define MOD_RESET_HPF_DRC 6 ++#define MOD_RESET_ADC_DIG 3 ++#define MOD_RESET_DAC_DIG 2 ++ ++/*AIF_SR_CTRL*/ ++#define AIF1_FS 12 //AIF1 Sample Rate ++#define AIF2_FS 8 //AIF2 Sample Rate ++#define SRC1_ENA 3 ++#define SRC1_SRC 2 ++#define SRC2_ENA 1 ++#define SRC2_SRC 0 ++ ++/*AIF1LCK_CTRL*/ ++#define AIF1_MSTR_MOD 15 ++#define AIF1_BCLK_INV 14 ++#define AIF1_LRCK_INV 13 ++#define AIF1_BCLK_DIV 9 ++#define AIF1_LRCK_DIV 6 ++#define AIF1_WORK_SIZ 4 ++#define AIF1_DATA_FMT 2 ++#define DSP_MONO_PCM 1 ++#define AIF1_TDMM_ENA 0 ++ ++/*AIF1_ADCDAT_CTRL*/ ++#define AIF1_AD0L_ENA 15 ++#define AIF1_AD0R_ENA 14 ++#define AIF1_AD1L_ENA 13 ++#define AIF1_AD1R_ENA 12 ++#define AIF1_AD0L_SRC 10 ++#define AIF1_AD0R_SRC 8 ++#define AIF1_AD1L_SRC 6 ++#define AIF1_AD1R_SRC 4 ++#define AIF1_ADCP_ENA 3 ++#define AIF1_ADUL_ENA 2 ++#define AIF1_SLOT_SIZ 0 ++ ++/*AIF1_DACDAT_CTRL*/ ++#define AIF1_DA0L_ENA 15 ++#define AIF1_DA0R_ENA 14 ++#define AIF1_DA1L_ENA 13 ++#define AIF1_DA1R_ENA 12 ++#define AIF1_DA0L_SRC 10 ++#define AIF1_DA0R_SRC 8 ++#define AIF1_DA1L_SRC 6 ++#define AIF1_DA1R_SRC 4 ++#define AIF1_DACP_ENA 3 ++#define AIF1_DAUL_ENA 2 ++#define AIF1_SLOT_SIZ 0 ++ ++/*AIF1_MXR_SRC*/ ++#define AIF1_AD0L_AIF1_DA0L_MXR 15 ++#define AIF1_AD0L_AIF2_DACL_MXR 14 ++#define AIF1_AD0L_ADCL_MXR 13 ++#define AIF1_AD0L_AIF2_DACR_MXR 12 ++#define AIF1_AD0R_AIF1_DA0R_MXR 11 ++#define AIF1_AD0R_AIF2_DACR_MXR 10 ++#define AIF1_AD0R_ADCR_MXR 9 ++#define AIF1_AD0R_AIF2_DACL_MXR 8 ++#define AIF1_AD1L_AIF2_DACL_MXR 7 ++#define AIF1_AD1L_ADCL_MXR 6 ++#define AIF1_AD1L_MXR_SRC 6 ++#define AIF1_AD1R_AIF2_DACR_MXR 3 ++#define AIF1_AD1R_ADCR_MXR 2 ++#define AIF1_AD1R_MXR_SRC 2 ++ ++/*AIF1_VOL_CTRL1*/ ++#define AIF1_AD0L_VOL 8 ++#define AIF1_AD0R_VOL 0 ++ ++/*AIF1_VOL_CTRL2*/ ++#define AIF1_AD1L_VOL 8 ++#define AIF1_AD1R_VOL 0 ++ ++/*AIF1_VOL_CTRL3*/ ++#define AIF1_DA0L_VOL 8 ++#define AIF1_DA0R_VOL 0 ++ ++/*AIF1_VOL_CTRL4*/ ++#define AIF1_DA1L_VOL 8 ++#define AIF1_DA1R_VOL 0 ++ ++/*AIF1_MXR_GAIN*/ ++#define AIF1_AD0L_MXR_GAIN 12 ++#define AIF1_AD0R_MXR_GAIN 8 ++#define AIF1_AD1L_MXR_GAIN 6 ++#define AIF1_AD1R_MXR_GAIN 2 ++ ++/*AIF1_RXD_CTRL*/ ++#define AIF1_N_DATA_DISCARD 8 ++ ++/*ADC_DIG_CTRL*/ ++#define ENAD 15 ++#define ENDM 14 ++#define ADFIR32 13 ++#define ADOUT_DTS 2 ++#define ADOUT_DLY 1 ++ ++/*ADC_VOL_CTRL*/ ++#define ADC_VOL_L 8 ++#define ADC_VOL_R 0 ++ ++/*ADC_DBG_CTRL*/ ++#define ADSW 15 ++#define DMIC_CLK_PIN_CTRL 12 ++ ++/*HMIC_CTRL1*/ ++#define HMIC_M 12 ++#define HMIC_N 8 ++#define HMIC_DATA_IRQ_MODE 7 ++#define HMIC_TH1_HYSTERESIS 5 ++#define HMIC_PULLOUT_IRQ 4 ++#define HMIC_PLUGIN_IRQ 3 ++#define HMIC_KEYUP_IRQ 2 ++#define HMIC_KEYDOWN_IRQ 1 ++#define HMIC_DATA_IRQ_EN 0 ++ ++/*HMIC_CTRL2*/ ++#define HMIC_SAMPLE_SELECT 14 ++#define HMIC_TH2_HYSTERESIS 13 ++#define HMIC_TH2 8 ++#define HMIC_SF 6 ++#define KEYUP_CLEAR 5 ++#define HMIC_TH1 0 ++ ++/*HMIC_STS*/ ++#define HMIC_DATA 8 ++#define GET_HMIC_DATA(r) (((r) >> HMIC_DATA) & 0x1F) ++#define HMIC_PULLOUT_PEND 4 ++#define HMIC_PLUGIN_PEND 3 ++#define HMIC_KEYUP_PEND 2 ++#define HMKC_KEYDOWN_PEND 1 ++#define HMIC_DATA_PEND 0 ++#define HMIC_PEND_ALL (0x1F) ++ ++/*DAC_DIG_CTRL*/ ++#define ENDA 15 ++#define ENHPF 14 ++#define DAFIR32 13 ++#define MODQU 8 ++ ++/*DAC_VOL_CTRL*/ ++#define DAC_VOL_L 8 ++#define DAC_VOL_R 0 ++ ++/*DAC_DBG_CTRL*/ ++#define DASW 15 ++#define ENDWA_N 14 ++#define DAC_MOD_DBG 13 ++#define DAC_PTN_SEL 6 ++#define DVC 0 ++ ++/*DAC_MXR_SRC*/ ++#define DACL_MXR_AIF1_DA0L 15 ++#define DACL_MXR_AIF1_DA1L 14 ++#define DACL_MXR_AIF2_DACL 13 ++#define DACL_MXR_ADCL 12 ++#define DACL_MXR_SRC 12 ++#define DACR_MXR_AIF1_DA0R 11 ++#define DACR_MXR_AIF1_DA1R 10 ++#define DACR_MXR_AIF2_DACR 9 ++#define DACR_MXR_ADCR 8 ++#define DACR_MXR_SRC 8 ++ ++/*DAC_MXR_GAIN*/ ++#define DACL_MXR_GAIN 12 ++#define DACR_MXR_GAIN 8 ++ ++/*ADC_APC_CTRL*/ ++#define ADCREN 15 ++#define ADCRG 12 ++#define ADCLEN 11 ++#define ADCLG 8 ++#define MBIASEN 7 ++#define MMIC_BIAS_CHOP_EN 6 ++#define MMIC_BIAS_CHOP_CKS 4 ++#define HBIASMOD 2 ++#define HBIASEN 1 ++#define HBIASADCEN 0 ++ ++/*ADC_SRC*/ ++#define RADCMIXMUTEMIC1BOOST (13) ++#define RADCMIXMUTEMIC2BOOST (12) ++#define RADCMIXMUTELINEINLR (11) ++#define RADCMIXMUTELINEINR (10) ++#define RADCMIXMUTEAUXINR (9) ++#define RADCMIXMUTEROUTPUT (8) ++#define RADCMIXMUTELOUTPUT (7) ++#define LADCMIXMUTEMIC1BOOST (6) ++#define LADCMIXMUTEMIC2BOOST (5) ++#define LADCMIXMUTELINEINLR (4) ++#define LADCMIXMUTELINEINL (3) ++#define LADCMIXMUTEAUXINL (2) ++#define LADCMIXMUTELOUTPUT (1) ++#define LADCMIXMUTEROUTPUT (0) ++ ++/*ADC_SRCBST_CTRL*/ ++#define MIC1AMPEN 15 ++#define ADC_MIC1G 12 ++#define MIC2AMPEN 11 ++#define ADC_MIC2G 8 ++#define MIC2SLT 7 ++#define LINEIN_PREG 4 ++#define AUXI_PREG 0 ++ ++/*OMIXER_DACA_CTRL*/ ++#define DACAREN 15 ++#define DACALEN 14 ++#define RMIXEN 13 ++#define LMIXEN 12 ++#define HPOUTPUTENABLE 8 ++ ++/*OMIXER_SR*/ ++#define RMIXMUTEMIC1BOOST (13) ++#define RMIXMUTEMIC2BOOST (12) ++#define RMIXMUTELINEINLR (11) ++#define RMIXMUTELINEINR (10) ++#define RMIXMUTEAUXINR (9) ++#define RMIXMUTEDACR (8) ++#define RMIXMUTEDACL (7) ++#define LMIXMUTEMIC1BOOST (6) ++#define LMIXMUTEMIC2BOOST (5) ++#define LMIXMUTELINEINLR (4) ++#define LMIXMUTELINEINL (3) ++#define LMIXMUTEAUXINL (2) ++#define LMIXMUTEDACL (1) ++#define LMIXMUTEDACR (0) ++ ++/*OMIXER_BST1_CTRL*/ ++#define BIASVOLTAGE 12 ++#define AXG 9 ++#define OMIXER_MIC1G 6 ++#define OMIXER_MIC2G 3 ++#define LINEING 0 ++ ++/*HPOUT_CTRL*/ ++#define RHPS 15 ++#define LHPS 14 ++#define RHPPA_MUTE 13 ++#define LHPPA_MUTE 12 ++#define HPPA_EN 11 ++#define HP_VOL 4 ++#define HPPA_DEL 2 ++#define HPPA_IS 0 ++ ++/*ESPKOUT_CTRL*/ ++#define EAR_RAMP_TIME 11 ++#define ESPA_OUT_CURRENT 9 ++#define ESPSR 7 ++#define ESPPA_MUTE 6 ++#define ESPPA_EN 5 ++#define ESP_VOL 0 ++ ++/*SPKOUT_CTRL*/ ++#define HPCALICKS 13 ++#define RSPKS 12 ++#define RSPKINVEN 11 ++#define RSPK_EN 9 ++#define LSPKS 8 ++#define LSPKINVEN 7 ++#define LSPK_EN 5 ++#define SPK_VOL 0 ++ ++/*LOUT_CTRL*/ ++#define LINEOUTG 5 ++#define LINEOUTEN 4 ++#define LINEOUTS0 3 ++#define LINEOUTS1 2 ++#define LINEOUTS2 1 ++#define LINEOUTS3 0 ++ ++/*ADDA_TUNE1*/ ++#define CURRENT_TEST_SELECT 14 ++#define BIHE_CTRL 12 ++#define DITHER 11 ++#define DITHER_CLK 9 ++#define ZERO_CROSSOVER_EN 8 ++#define ZERO_CROSSOVER_TIME 7 ++#define EAR_SPEED_SELECT 6 ++#define REF_CHOPPEN_CKS 4 ++#define OPMIC_BIAS_CUR 0 ++ ++/*ADDA_TUNE2*/ ++#define OPDAC_BIAS_CUR 14 ++#define OPDRV_BIAS_CUR 12 ++#define OPMIX_BIAS_CUR 10 ++#define OPEAR_BIAS_CUR 8 ++#define OPVR_BIAS_CUR 6 ++#define OPAAF_BIAS_CUR 4 ++#define OPADC1_BIAS_CUR 2 ++#define OPADC2_BIAS_CUR 0 ++ ++/*ADDA_TUNE3*/ ++#define LDOEN 15 ++#define LDO_SEL 12 ++#define BIASCALIVERIFY 11 ++#define BIASMODE 10 ++#define BIASCALIDATA 9 ++#define OSCS 1 ++#define OSCEN 0 ++ ++/*HPOUT_STR*/ ++#define HPVL_SOFT_MOD 14 ++#define HPVL_STEP_CTRL 8 ++#define DACA_CHND_ENA 7 ++#define HPPA_MXRD_ENA 6 ++#define HPVL_CTRL_OUT 0 ++ ++#endif//__AC101_REGS_H__ +--- /dev/null ++++ b/sound/soc/codecs/ac108.c +@@ -0,0 +1,1622 @@ ++/* ++ * ac10x.c -- ac10x ALSA SoC Audio driver ++ * ++ * ++ * Author: Baozhu Zuo<zuobaozhu@gmail.com> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++#include <linux/clk.h> ++#include <linux/delay.h> ++#include <linux/gpio/consumer.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/moduleparam.h> ++#include <linux/pm.h> ++#include <linux/regmap.h> ++#include <linux/slab.h> ++#include <linux/workqueue.h> ++#include <sound/core.h> ++#include <sound/initval.h> ++#include <sound/pcm.h> ++#include <sound/pcm_params.h> ++#include <sound/soc.h> ++#include <sound/tlv.h> ++ ++#include "ac108.h" ++#include "ac10x.h" ++ ++#define _USE_CAPTURE 1 ++#define _MASTER_INDEX 0 ++ ++/* #undef DEBUG ++ * use 'make DEBUG=1' to enable debugging ++ */ ++ ++/** ++ * TODO: ++ * 1, add PM API: ac108_suspend,ac108_resume ++ * 2,0x65-0x6a ++ * 3,0x76-0x79 high 4bit ++ */ ++struct pll_div { ++ unsigned int freq_in; ++ unsigned int freq_out; ++ unsigned int m1; ++ unsigned int m2; ++ unsigned int n; ++ unsigned int k1; ++ unsigned int k2; ++}; ++ ++static struct ac10x_priv *ac10x; ++ ++struct real_val_to_reg_val { ++ unsigned int real_val; ++ unsigned int reg_val; ++}; ++ ++static const struct real_val_to_reg_val ac108_sample_rate[] = { ++ { 8000, 0 }, ++ { 11025, 1 }, ++ { 12000, 2 }, ++ { 16000, 3 }, ++ { 22050, 4 }, ++ { 24000, 5 }, ++ { 32000, 6 }, ++ { 44100, 7 }, ++ { 48000, 8 }, ++ { 96000, 9 }, ++}; ++ ++/* Sample resolution */ ++static const struct real_val_to_reg_val ac108_samp_res[] = { ++ { 8, 1 }, ++ { 12, 2 }, ++ { 16, 3 }, ++ { 20, 4 }, ++ { 24, 5 }, ++ { 28, 6 }, ++ { 32, 7 }, ++}; ++ ++static const unsigned int ac108_bclkdivs[] = { ++ 0, 1, 2, 4, ++ 6, 8, 12, 16, ++ 24, 32, 48, 64, ++ 96, 128, 176, 192, ++}; ++ ++/* FOUT =(FIN * N) / [(M1+1) * (M2+1)*(K1+1)*(K2+1)] ; M1[0,31], M2[0,1], N[0,1023], K1[0,31], K2[0,1] */ ++static const struct pll_div ac108_pll_div_list[] = { ++ { 400000, _FREQ_24_576K, 0, 0, 614, 4, 1 }, ++ { 512000, _FREQ_24_576K, 0, 0, 960, 9, 1 }, /* _FREQ_24_576K/48 */ ++ { 768000, _FREQ_24_576K, 0, 0, 640, 9, 1 }, /* _FREQ_24_576K/32 */ ++ { 800000, _FREQ_24_576K, 0, 0, 614, 9, 1 }, ++ { 1024000, _FREQ_24_576K, 0, 0, 480, 9, 1 }, /* _FREQ_24_576K/24 */ ++ { 1600000, _FREQ_24_576K, 0, 0, 307, 9, 1 }, ++ { 2048000, _FREQ_24_576K, 0, 0, 240, 9, 1 }, /* accurate, 8000 * 256 */ ++ { 3072000, _FREQ_24_576K, 0, 0, 160, 9, 1 }, /* accurate, 12000 * 256 */ ++ { 4096000, _FREQ_24_576K, 2, 0, 360, 9, 1 }, /* accurate, 16000 * 256 */ ++ { 6000000, _FREQ_24_576K, 4, 0, 410, 9, 1 }, ++ { 12000000, _FREQ_24_576K, 9, 0, 410, 9, 1 }, ++ { 13000000, _FREQ_24_576K, 8, 0, 340, 9, 1 }, ++ { 15360000, _FREQ_24_576K, 12, 0, 415, 9, 1 }, ++ { 16000000, _FREQ_24_576K, 12, 0, 400, 9, 1 }, ++ { 19200000, _FREQ_24_576K, 15, 0, 410, 9, 1 }, ++ { 19680000, _FREQ_24_576K, 15, 0, 400, 9, 1 }, ++ { 24000000, _FREQ_24_576K, 9, 0, 256,24, 0 }, /* accurate, 24M -> 24.576M */ ++ ++ { 400000, _FREQ_22_579K, 0, 0, 566, 4, 1 }, ++ { 512000, _FREQ_22_579K, 0, 0, 880, 9, 1 }, ++ { 768000, _FREQ_22_579K, 0, 0, 587, 9, 1 }, ++ { 800000, _FREQ_22_579K, 0, 0, 567, 9, 1 }, ++ { 1024000, _FREQ_22_579K, 0, 0, 440, 9, 1 }, ++ { 1600000, _FREQ_22_579K, 1, 0, 567, 9, 1 }, ++ { 2048000, _FREQ_22_579K, 0, 0, 220, 9, 1 }, ++ { 3072000, _FREQ_22_579K, 0, 0, 148, 9, 1 }, ++ { 4096000, _FREQ_22_579K, 2, 0, 330, 9, 1 }, ++ { 6000000, _FREQ_22_579K, 2, 0, 227, 9, 1 }, ++ { 12000000, _FREQ_22_579K, 8, 0, 340, 9, 1 }, ++ { 13000000, _FREQ_22_579K, 9, 0, 350, 9, 1 }, ++ { 15360000, _FREQ_22_579K, 10, 0, 325, 9, 1 }, ++ { 16000000, _FREQ_22_579K, 11, 0, 340, 9, 1 }, ++ { 19200000, _FREQ_22_579K, 13, 0, 330, 9, 1 }, ++ { 19680000, _FREQ_22_579K, 14, 0, 345, 9, 1 }, ++ { 24000000, _FREQ_22_579K, 24, 0, 588,24, 0 }, /* accurate, 24M -> 22.5792M */ ++ ++ ++ { _FREQ_24_576K / 1, _FREQ_24_576K, 9, 0, 200, 9, 1 }, /* _FREQ_24_576K */ ++ { _FREQ_24_576K / 2, _FREQ_24_576K, 9, 0, 400, 9, 1 }, /* 12288000,accurate, 48000 * 256 */ ++ { _FREQ_24_576K / 4, _FREQ_24_576K, 4, 0, 400, 9, 1 }, /* 6144000, accurate, 24000 * 256 */ ++ { _FREQ_24_576K / 16, _FREQ_24_576K, 0, 0, 320, 9, 1 }, /* 1536000 */ ++ { _FREQ_24_576K / 64, _FREQ_24_576K, 0, 0, 640, 4, 1 }, /* 384000 */ ++ { _FREQ_24_576K / 96, _FREQ_24_576K, 0, 0, 960, 4, 1 }, /* 256000 */ ++ { _FREQ_24_576K / 128, _FREQ_24_576K, 0, 0, 512, 1, 1 }, /* 192000 */ ++ { _FREQ_24_576K / 176, _FREQ_24_576K, 0, 0, 880, 4, 0 }, /* 140000 */ ++ { _FREQ_24_576K / 192, _FREQ_24_576K, 0, 0, 960, 4, 0 }, /* 128000 */ ++ ++ { _FREQ_22_579K / 1, _FREQ_22_579K, 9, 0, 200, 9, 1 }, /* _FREQ_22_579K */ ++ { _FREQ_22_579K / 2, _FREQ_22_579K, 9, 0, 400, 9, 1 }, /* 11289600,accurate, 44100 * 256 */ ++ { _FREQ_22_579K / 4, _FREQ_22_579K, 4, 0, 400, 9, 1 }, /* 5644800, accurate, 22050 * 256 */ ++ { _FREQ_22_579K / 16, _FREQ_22_579K, 0, 0, 320, 9, 1 }, /* 1411200 */ ++ { _FREQ_22_579K / 64, _FREQ_22_579K, 0, 0, 640, 4, 1 }, /* 352800 */ ++ { _FREQ_22_579K / 96, _FREQ_22_579K, 0, 0, 960, 4, 1 }, /* 235200 */ ++ { _FREQ_22_579K / 128, _FREQ_22_579K, 0, 0, 512, 1, 1 }, /* 176400 */ ++ { _FREQ_22_579K / 176, _FREQ_22_579K, 0, 0, 880, 4, 0 }, /* 128290 */ ++ { _FREQ_22_579K / 192, _FREQ_22_579K, 0, 0, 960, 4, 0 }, /* 117600 */ ++ ++ { _FREQ_22_579K / 6, _FREQ_22_579K, 2, 0, 360, 9, 1 }, /* 3763200 */ ++ { _FREQ_22_579K / 8, _FREQ_22_579K, 0, 0, 160, 9, 1 }, /* 2822400, accurate, 11025 * 256 */ ++ { _FREQ_22_579K / 12, _FREQ_22_579K, 0, 0, 240, 9, 1 }, /* 1881600 */ ++ { _FREQ_22_579K / 24, _FREQ_22_579K, 0, 0, 480, 9, 1 }, /* 940800 */ ++ { _FREQ_22_579K / 32, _FREQ_22_579K, 0, 0, 640, 9, 1 }, /* 705600 */ ++ { _FREQ_22_579K / 48, _FREQ_22_579K, 0, 0, 960, 9, 1 }, /* 470400 */ ++}; ++ ++ ++/* AC108 definition */ ++#define AC108_CHANNELS_MAX 8 /* range[1, 16] */ ++#define AC108_RATES (SNDRV_PCM_RATE_8000_96000 & \ ++ ~(SNDRV_PCM_RATE_64000 | \ ++ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)) ++#define AC108_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ ++ /*SNDRV_PCM_FMTBIT_S20_3LE | \ ++ SNDRV_PCM_FMTBIT_S24_LE |*/ \ ++ SNDRV_PCM_FMTBIT_S32_LE) ++ ++static const DECLARE_TLV_DB_SCALE(tlv_adc_pga_gain, 0, 100, 0); ++static const DECLARE_TLV_DB_SCALE(tlv_ch_digital_vol, -11925, 75, 0); ++ ++int ac10x_read(u8 reg, u8* rt_val, struct regmap* i2cm) ++{ ++ int r, v = 0; ++ ++ if ((r = regmap_read(i2cm, reg, &v)) < 0) ++ pr_info("ac10x_read info->[REG-0x%02x]\n", reg); ++ else ++ *rt_val = v; ++ return r; ++} ++ ++int ac10x_write(u8 reg, u8 val, struct regmap* i2cm) ++{ ++ int r; ++ ++ if ((r = regmap_write(i2cm, reg, val)) < 0) ++ pr_info("ac10x_write info->[REG-0x%02x,val-0x%02x]\n", reg, val); ++ return r; ++} ++ ++int ac10x_update_bits(u8 reg, u8 mask, u8 val, struct regmap* i2cm) ++{ ++ int r; ++ ++ if ((r = regmap_update_bits(i2cm, reg, mask, val)) < 0) ++ pr_info("%s() info->[REG-0x%02x,val-0x%02x]\n", __func__, reg, val); ++ return r; ++} ++ ++/** ++ * snd_ac108_get_volsw - single mixer get callback ++ * @kcontrol: mixer control ++ * @ucontrol: control element information ++ * ++ * Callback to get the value of a single mixer control, or a double mixer ++ * control that spans 2 registers. ++ * ++ * Returns 0 for success. ++ */ ++static int snd_ac108_get_volsw(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct soc_mixer_control *mc = ++ (struct soc_mixer_control *)kcontrol->private_value; ++ unsigned int mask = (1 << fls(mc->max)) - 1; ++ unsigned int invert = mc->invert; ++ int ret, chip = mc->autodisable; ++ u8 val; ++ ++ if ((ret = ac10x_read(mc->reg, &val, ac10x->i2cmap[chip])) < 0) ++ return ret; ++ ++ val = ((val >> mc->shift) & mask) - mc->min; ++ if (invert) { ++ val = mc->max - val; ++ } ++ ucontrol->value.integer.value[0] = val; ++ return 0; ++} ++ ++/** ++ * snd_ac108_put_volsw - single mixer put callback ++ * @kcontrol: mixer control ++ * @ucontrol: control element information ++ * ++ * Callback to set the value of a single mixer control, or a double mixer ++ * control that spans 2 registers. ++ * ++ * Returns 0 for success. ++ */ ++static int snd_ac108_put_volsw(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct soc_mixer_control *mc = ++ (struct soc_mixer_control *)kcontrol->private_value; ++ unsigned int sign_bit = mc->sign_bit; ++ unsigned int val, mask = (1 << fls(mc->max)) - 1; ++ unsigned int invert = mc->invert; ++ int ret, chip = mc->autodisable; ++ ++ if (sign_bit) ++ mask = BIT(sign_bit + 1) - 1; ++ ++ val = ((ucontrol->value.integer.value[0] + mc->min) & mask); ++ if (invert) { ++ val = mc->max - val; ++ } ++ ++ mask = mask << mc->shift; ++ val = val << mc->shift; ++ ++ ret = ac10x_update_bits(mc->reg, mask, val, ac10x->i2cmap[chip]); ++ return ret; ++} ++ ++#define SOC_AC108_SINGLE_TLV(xname, reg, shift, max, invert, chip, tlv_array) \ ++{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ ++ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ ++ SNDRV_CTL_ELEM_ACCESS_READWRITE,\ ++ .tlv.p = (tlv_array), \ ++ .info = snd_soc_info_volsw, .get = snd_ac108_get_volsw,\ ++ .put = snd_ac108_put_volsw, \ ++ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, chip) } ++ ++/* single ac108 */ ++static const struct snd_kcontrol_new ac108_snd_controls[] = { ++ /* ### chip 0 ### */ ++ /*0x70: ADC1 Digital Channel Volume Control Register*/ ++ SOC_AC108_SINGLE_TLV("CH1 digital volume", ADC1_DVOL_CTRL, ++ 0, 0xff, 0, 0, tlv_ch_digital_vol), ++ /*0x71: ADC2 Digital Channel Volume Control Register*/ ++ SOC_AC108_SINGLE_TLV("CH2 digital volume", ADC2_DVOL_CTRL, ++ 0, 0xff, 0, 0, tlv_ch_digital_vol), ++ /*0x72: ADC3 Digital Channel Volume Control Register*/ ++ SOC_AC108_SINGLE_TLV("CH3 digital volume", ADC3_DVOL_CTRL, ++ 0, 0xff, 0, 0, tlv_ch_digital_vol), ++ /*0x73: ADC4 Digital Channel Volume Control Register*/ ++ SOC_AC108_SINGLE_TLV("CH4 digital volume", ADC4_DVOL_CTRL, ++ 0, 0xff, 0, 0, tlv_ch_digital_vol), ++ ++ /*0x90: Analog PGA1 Control Register*/ ++ SOC_AC108_SINGLE_TLV("ADC1 PGA gain", ANA_PGA1_CTRL, ++ ADC1_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), ++ /*0x91: Analog PGA2 Control Register*/ ++ SOC_AC108_SINGLE_TLV("ADC2 PGA gain", ANA_PGA2_CTRL, ++ ADC2_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), ++ /*0x92: Analog PGA3 Control Register*/ ++ SOC_AC108_SINGLE_TLV("ADC3 PGA gain", ANA_PGA3_CTRL, ++ ADC3_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), ++ /*0x93: Analog PGA4 Control Register*/ ++ SOC_AC108_SINGLE_TLV("ADC4 PGA gain", ANA_PGA4_CTRL, ++ ADC4_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), ++}; ++/* multiple ac108s */ ++static const struct snd_kcontrol_new ac108tdm_snd_controls[] = { ++ /* ### chip 1 ### */ ++ /*0x70: ADC1 Digital Channel Volume Control Register*/ ++ SOC_AC108_SINGLE_TLV("CH1 digital volume", ADC1_DVOL_CTRL, ++ 0, 0xff, 0, 1, tlv_ch_digital_vol), ++ /*0x71: ADC2 Digital Channel Volume Control Register*/ ++ SOC_AC108_SINGLE_TLV("CH2 digital volume", ADC2_DVOL_CTRL, ++ 0, 0xff, 0, 1, tlv_ch_digital_vol), ++ /*0x72: ADC3 Digital Channel Volume Control Register*/ ++ SOC_AC108_SINGLE_TLV("CH3 digital volume", ADC3_DVOL_CTRL, ++ 0, 0xff, 0, 1, tlv_ch_digital_vol), ++ /*0x73: ADC4 Digital Channel Volume Control Register*/ ++ SOC_AC108_SINGLE_TLV("CH4 digital volume", ADC4_DVOL_CTRL, ++ 0, 0xff, 0, 1, tlv_ch_digital_vol), ++ ++ /*0x90: Analog PGA1 Control Register*/ ++ SOC_AC108_SINGLE_TLV("ADC1 PGA gain", ANA_PGA1_CTRL, ++ ADC1_ANALOG_PGA, 0x1f, 0, 1, tlv_adc_pga_gain), ++ /*0x91: Analog PGA2 Control Register*/ ++ SOC_AC108_SINGLE_TLV("ADC2 PGA gain", ANA_PGA2_CTRL, ++ ADC2_ANALOG_PGA, 0x1f, 0, 1, tlv_adc_pga_gain), ++ /*0x92: Analog PGA3 Control Register*/ ++ SOC_AC108_SINGLE_TLV("ADC3 PGA gain", ANA_PGA3_CTRL, ++ ADC3_ANALOG_PGA, 0x1f, 0, 1, tlv_adc_pga_gain), ++ /*0x93: Analog PGA4 Control Register*/ ++ SOC_AC108_SINGLE_TLV("ADC4 PGA gain", ANA_PGA4_CTRL, ++ ADC4_ANALOG_PGA, 0x1f, 0, 1, tlv_adc_pga_gain), ++ ++ /* ### chip 0 ### */ ++ /*0x70: ADC1 Digital Channel Volume Control Register*/ ++ SOC_AC108_SINGLE_TLV("CH5 digital volume", ADC1_DVOL_CTRL, ++ 0, 0xff, 0, 0, tlv_ch_digital_vol), ++ /*0x71: ADC2 Digital Channel Volume Control Register*/ ++ SOC_AC108_SINGLE_TLV("CH6 digital volume", ADC2_DVOL_CTRL, ++ 0, 0xff, 0, 0, tlv_ch_digital_vol), ++ /*0x72: ADC3 Digital Channel Volume Control Register*/ ++ SOC_AC108_SINGLE_TLV("CH7 digital volume", ADC3_DVOL_CTRL, ++ 0, 0xff, 0, 0, tlv_ch_digital_vol), ++ /*0x73: ADC4 Digital Channel Volume Control Register*/ ++ SOC_AC108_SINGLE_TLV("CH8 digital volume", ADC4_DVOL_CTRL, ++ 0, 0xff, 0, 0, tlv_ch_digital_vol), ++ ++ /*0x90: Analog PGA1 Control Register*/ ++ SOC_AC108_SINGLE_TLV("ADC5 PGA gain", ANA_PGA1_CTRL, ++ ADC1_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), ++ /*0x91: Analog PGA2 Control Register*/ ++ SOC_AC108_SINGLE_TLV("ADC6 PGA gain", ANA_PGA2_CTRL, ++ ADC2_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), ++ /*0x92: Analog PGA3 Control Register*/ ++ SOC_AC108_SINGLE_TLV("ADC7 PGA gain", ANA_PGA3_CTRL, ++ ADC3_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), ++ /*0x93: Analog PGA4 Control Register*/ ++ SOC_AC108_SINGLE_TLV("ADC8 PGA gain", ANA_PGA4_CTRL, ++ ADC4_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), ++}; ++ ++ ++static const struct snd_soc_dapm_widget ac108_dapm_widgets[] = { ++ /* input widgets */ ++ SND_SOC_DAPM_INPUT("MIC1P"), ++ SND_SOC_DAPM_INPUT("MIC1N"), ++ ++ SND_SOC_DAPM_INPUT("MIC2P"), ++ SND_SOC_DAPM_INPUT("MIC2N"), ++ ++ SND_SOC_DAPM_INPUT("MIC3P"), ++ SND_SOC_DAPM_INPUT("MIC3N"), ++ ++ SND_SOC_DAPM_INPUT("MIC4P"), ++ SND_SOC_DAPM_INPUT("MIC4N"), ++ ++ SND_SOC_DAPM_INPUT("DMIC1"), ++ SND_SOC_DAPM_INPUT("DMIC2"), ++ ++ /*0xa0: ADC1 Analog Control 1 Register*/ ++ /*0xa1-0xa6:use the defualt value*/ ++ SND_SOC_DAPM_AIF_IN("Channel 1 AAF", "Capture", 0, ANA_ADC1_CTRL1, ADC1_DSM_ENABLE, 1), ++ SND_SOC_DAPM_SUPPLY("Channel 1 EN", ANA_ADC1_CTRL1, ADC1_PGA_ENABLE, 1, NULL, 0), ++ SND_SOC_DAPM_MICBIAS("MIC1BIAS", ANA_ADC1_CTRL1, ADC1_MICBIAS_EN, 1), ++ ++ /*0xa7: ADC2 Analog Control 1 Register*/ ++ /*0xa8-0xad:use the defualt value*/ ++ SND_SOC_DAPM_AIF_IN("Channel 2 AAF", "Capture", 0, ANA_ADC2_CTRL1, ADC2_DSM_ENABLE, 1), ++ SND_SOC_DAPM_SUPPLY("Channel 2 EN", ANA_ADC2_CTRL1, ADC2_PGA_ENABLE, 1, NULL, 0), ++ SND_SOC_DAPM_MICBIAS("MIC2BIAS", ANA_ADC2_CTRL1, ADC2_MICBIAS_EN, 1), ++ ++ /*0xae: ADC3 Analog Control 1 Register*/ ++ /*0xaf-0xb4:use the defualt value*/ ++ SND_SOC_DAPM_AIF_IN("Channel 3 AAF", "Capture", 0, ANA_ADC3_CTRL1, ADC3_DSM_ENABLE, 1), ++ SND_SOC_DAPM_SUPPLY("Channel 3 EN", ANA_ADC3_CTRL1, ADC3_PGA_ENABLE, 1, NULL, 0), ++ SND_SOC_DAPM_MICBIAS("MIC3BIAS", ANA_ADC3_CTRL1, ADC3_MICBIAS_EN, 1), ++ ++ /*0xb5: ADC4 Analog Control 1 Register*/ ++ /*0xb6-0xbb:use the defualt value*/ ++ SND_SOC_DAPM_AIF_IN("Channel 4 AAF", "Capture", 0, ANA_ADC4_CTRL1, ADC4_DSM_ENABLE, 1), ++ SND_SOC_DAPM_SUPPLY("Channel 4 EN", ANA_ADC4_CTRL1, ADC4_PGA_ENABLE, 1, NULL, 0), ++ SND_SOC_DAPM_MICBIAS("MIC4BIAS", ANA_ADC4_CTRL1, ADC4_MICBIAS_EN, 1), ++ ++ ++ /*0x61: ADC Digital Part Enable Register*/ ++ SND_SOC_DAPM_SUPPLY("ADC EN", ADC_DIG_EN, 4, 1, NULL, 0), ++ SND_SOC_DAPM_ADC("ADC1", "Capture", ADC_DIG_EN, 0, 1), ++ SND_SOC_DAPM_ADC("ADC2", "Capture", ADC_DIG_EN, 1, 1), ++ SND_SOC_DAPM_ADC("ADC3", "Capture", ADC_DIG_EN, 2, 1), ++ SND_SOC_DAPM_ADC("ADC4", "Capture", ADC_DIG_EN, 3, 1), ++ ++ SND_SOC_DAPM_SUPPLY("ADC1 CLK", ANA_ADC4_CTRL7, ADC1_CLK_GATING, 1, NULL, 0), ++ SND_SOC_DAPM_SUPPLY("ADC2 CLK", ANA_ADC4_CTRL7, ADC2_CLK_GATING, 1, NULL, 0), ++ SND_SOC_DAPM_SUPPLY("ADC3 CLK", ANA_ADC4_CTRL7, ADC3_CLK_GATING, 1, NULL, 0), ++ SND_SOC_DAPM_SUPPLY("ADC4 CLK", ANA_ADC4_CTRL7, ADC4_CLK_GATING, 1, NULL, 0), ++ ++ SND_SOC_DAPM_SUPPLY("DSM EN", ANA_ADC4_CTRL6, DSM_DEMOFF, 1, NULL, 0), ++ ++ /*0x62:Digital MIC Enable Register*/ ++ SND_SOC_DAPM_MICBIAS("DMIC1 enable", DMIC_EN, 0, 0), ++ SND_SOC_DAPM_MICBIAS("DMIC2 enable", DMIC_EN, 1, 0), ++}; ++ ++static const struct snd_soc_dapm_route ac108_dapm_routes[] = { ++ ++ { "ADC1", NULL, "Channel 1 AAF" }, ++ { "ADC2", NULL, "Channel 2 AAF" }, ++ { "ADC3", NULL, "Channel 3 AAF" }, ++ { "ADC4", NULL, "Channel 4 AAF" }, ++ ++ { "Channel 1 AAF", NULL, "MIC1BIAS" }, ++ { "Channel 2 AAF", NULL, "MIC2BIAS" }, ++ { "Channel 3 AAF", NULL, "MIC3BIAS" }, ++ { "Channel 4 AAF", NULL, "MIC4BIAS" }, ++ ++ { "MIC1BIAS", NULL, "ADC1 CLK" }, ++ { "MIC2BIAS", NULL, "ADC2 CLK" }, ++ { "MIC3BIAS", NULL, "ADC3 CLK" }, ++ { "MIC4BIAS", NULL, "ADC4 CLK" }, ++ ++ ++ { "ADC1 CLK", NULL, "DSM EN" }, ++ { "ADC2 CLK", NULL, "DSM EN" }, ++ { "ADC3 CLK", NULL, "DSM EN" }, ++ { "ADC4 CLK", NULL, "DSM EN" }, ++ ++ ++ { "DSM EN", NULL, "ADC EN" }, ++ ++ { "Channel 1 EN", NULL, "DSM EN" }, ++ { "Channel 2 EN", NULL, "DSM EN" }, ++ { "Channel 3 EN", NULL, "DSM EN" }, ++ { "Channel 4 EN", NULL, "DSM EN" }, ++ ++ ++ { "MIC1P", NULL, "Channel 1 EN" }, ++ { "MIC1N", NULL, "Channel 1 EN" }, ++ ++ { "MIC2P", NULL, "Channel 2 EN" }, ++ { "MIC2N", NULL, "Channel 2 EN" }, ++ ++ { "MIC3P", NULL, "Channel 3 EN" }, ++ { "MIC3N", NULL, "Channel 3 EN" }, ++ ++ { "MIC4P", NULL, "Channel 4 EN" }, ++ { "MIC4N", NULL, "Channel 4 EN" }, ++ ++}; ++ ++static int ac108_multi_write(u8 reg, u8 val, struct ac10x_priv *ac10x) ++{ ++ u8 i; ++ for (i = 0; i < ac10x->codec_cnt; i++) ++ ac10x_write(reg, val, ac10x->i2cmap[i]); ++ return 0; ++} ++ ++static int ac108_multi_update_bits(u8 reg, u8 mask, u8 val, struct ac10x_priv *ac10x) ++{ ++ int r = 0; ++ u8 i; ++ for (i = 0; i < ac10x->codec_cnt; i++) ++ r |= ac10x_update_bits(reg, mask, val, ac10x->i2cmap[i]); ++ return r; ++} ++ ++static unsigned int ac108_codec_read(struct snd_soc_codec *codec, unsigned int reg) ++{ ++ unsigned char val_r; ++ struct ac10x_priv *ac10x = dev_get_drvdata(codec->dev); ++ /*read one chip is fine*/ ++ ac10x_read(reg, &val_r, ac10x->i2cmap[_MASTER_INDEX]); ++ return val_r; ++} ++ ++static int ac108_codec_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int val) ++{ ++ struct ac10x_priv *ac10x = dev_get_drvdata(codec->dev); ++ ac108_multi_write(reg, val, ac10x); ++ return 0; ++} ++ ++/** ++ * The Power management related registers are Reg01h~Reg09h ++ * 0x01-0x05,0x08,use the default value ++ * @author baozhu (17-6-21) ++ * ++ * @param ac10x ++ */ ++static void ac108_configure_power(struct ac10x_priv *ac10x) ++{ ++ /** ++ * 0x06:Enable Analog LDO ++ */ ++ ac108_multi_update_bits(PWR_CTRL6, 0x01 << LDO33ANA_ENABLE, 0x01 << LDO33ANA_ENABLE, ac10x); ++ /** ++ * 0x07: ++ * Control VREF output and micbias voltage ? ++ * REF faststart disable, enable Enable VREF (needed for Analog ++ * LDO and MICBIAS) ++ */ ++ ac108_multi_update_bits(PWR_CTRL7, ++ 0x1f << VREF_SEL | 0x01 << VREF_FASTSTART_ENABLE | ++ 0x01 << VREF_ENABLE, ++ 0x13 << VREF_SEL | 0x00 << VREF_FASTSTART_ENABLE | ++ 0x01 << VREF_ENABLE, ++ ac10x); ++ /** ++ * 0x09: ++ * Disable fast-start circuit on VREFP ++ * VREFP_RESCTRL=00=1 MOhm ++ * IGEN_TRIM=100=+25% ++ * Enable VREFP (needed by all audio input channels) ++ */ ++ ac108_multi_update_bits(PWR_CTRL9, ++ 0x01 << VREFP_FASTSTART_ENABLE | 0x03 << VREFP_RESCTRL | ++ 0x07 << IGEN_TRIM | 0x01 << VREFP_ENABLE, ++ 0x00 << VREFP_FASTSTART_ENABLE | 0x00 << VREFP_RESCTRL | ++ 0x04 << IGEN_TRIM | 0x01 << VREFP_ENABLE, ++ ac10x); ++} ++ ++/** ++ * The clock management related registers are Reg20h~Reg25h ++ * The PLL management related registers are Reg10h~Reg18h. ++ * @author baozhu (17-6-20) ++ * ++ * @param ac10x ++ * @param rate : sample rate ++ * ++ * @return int : fail or success ++ */ ++static int ac108_config_pll(struct ac10x_priv *ac10x, unsigned rate, unsigned lrck_ratio) ++{ ++ unsigned int i = 0; ++ struct pll_div ac108_pll_div = { 0 }; ++ ++ if (ac10x->clk_id == SYSCLK_SRC_PLL) { ++ unsigned pll_src, pll_freq_in; ++ ++ if (lrck_ratio == 0) { ++ /* PLL clock source from MCLK */ ++ pll_freq_in = ac10x->sysclk; ++ pll_src = 0x0; ++ } else { ++ /* PLL clock source from BCLK */ ++ pll_freq_in = rate * lrck_ratio; ++ pll_src = 0x1; ++ } ++ ++ /* FOUT =(FIN * N) / [(M1+1) * (M2+1)*(K1+1)*(K2+1)] */ ++ for (i = 0; i < ARRAY_SIZE(ac108_pll_div_list); i++) { ++ if (ac108_pll_div_list[i].freq_in == pll_freq_in && ++ ac108_pll_div_list[i].freq_out % rate == 0) { ++ ac108_pll_div = ac108_pll_div_list[i]; ++ dev_info(&ac10x->i2c[_MASTER_INDEX]->dev, ++ "AC108 PLL freq_in match:%u, freq_out:%u\n\n", ++ ac108_pll_div.freq_in, ac108_pll_div.freq_out); ++ break; ++ } ++ } ++ /* 0x11,0x12,0x13,0x14: Config PLL DIV param M1/M2/N/K1/K2 */ ++ ac108_multi_update_bits(PLL_CTRL5, ++ 0x1f << PLL_POSTDIV1 | 0x01 << PLL_POSTDIV2, ++ ac108_pll_div.k1 << PLL_POSTDIV1 | ++ ac108_pll_div.k2 << PLL_POSTDIV2, ac10x); ++ ac108_multi_update_bits(PLL_CTRL4, 0xff << PLL_LOOPDIV_LSB, ++ (unsigned char)ac108_pll_div.n << PLL_LOOPDIV_LSB, ac10x); ++ ac108_multi_update_bits(PLL_CTRL3, 0x03 << PLL_LOOPDIV_MSB, ++ (ac108_pll_div.n >> 8) << PLL_LOOPDIV_MSB, ac10x); ++ ac108_multi_update_bits(PLL_CTRL2, 0x1f << PLL_PREDIV1 | 0x01 << PLL_PREDIV2, ++ ac108_pll_div.m1 << PLL_PREDIV1 | ++ ac108_pll_div.m2 << PLL_PREDIV2, ac10x); ++ ++ /*0x18: PLL clk lock enable*/ ++ ac108_multi_update_bits(PLL_LOCK_CTRL, 0x1 << PLL_LOCK_EN, ++ 0x1 << PLL_LOCK_EN, ac10x); ++ ++ /** ++ * 0x20: enable pll, pll source from mclk/bclk, sysclk source from pll, enable sysclk ++ */ ++ ac108_multi_update_bits(SYSCLK_CTRL, ++ 0x01 << PLLCLK_EN | 0x03 << PLLCLK_SRC | ++ 0x01 << SYSCLK_SRC | 0x01 << SYSCLK_EN, ++ 0x01 << PLLCLK_EN | pll_src << PLLCLK_SRC | ++ 0x01 << SYSCLK_SRC | 0x01 << SYSCLK_EN, ac10x); ++ ac10x->mclk = ac108_pll_div.freq_out; ++ } ++ if (ac10x->clk_id == SYSCLK_SRC_MCLK) { ++ /** ++ *0x20: sysclk source from mclk, enable sysclk ++ */ ++ ac108_multi_update_bits(SYSCLK_CTRL, ++ 0x01 << PLLCLK_EN | 0x01 << SYSCLK_SRC | 0x01 << SYSCLK_EN, ++ 0x00 << PLLCLK_EN | 0x00 << SYSCLK_SRC | 0x01 << SYSCLK_EN, ++ ac10x); ++ ac10x->mclk = ac10x->sysclk; ++ } ++ ++ return 0; ++} ++ ++/* ++ * support no more than 16 slots. ++ */ ++static int ac108_multi_chips_slots(struct ac10x_priv *ac, int slots) ++{ ++ int i; ++ ++ /* ++ * codec0 enable slots 2,3,0,1 when 1 codec ++ * ++ * codec0 enable slots 6,7,0,1 when 2 codec ++ * codec1 enable slots 2,3,4,5 ++ * ++ * ... ++ */ ++ for (i = 0; i < ac->codec_cnt; i++) { ++ /* rotate map, due to channels rotated by CPU_DAI */ ++ const unsigned vec_mask[] = { ++ 0x3 << 6 | 0x3, // slots 6,7,0,1 ++ 0xF << 2, // slots 2,3,4,5 ++ 0, ++ 0, ++ }; ++ const unsigned vec_maps[] = { ++ /* ++ * chip 0, ++ * mic 0 sample -> slot 6 ++ * mic 1 sample -> slot 7 ++ * mic 2 sample -> slot 0 ++ * mic 3 sample -> slot 1 ++ */ ++ 0x0 << 12 | 0x1 << 14 | 0x2 << 0 | 0x3 << 2, ++ /* ++ * chip 1, ++ * mic 0 sample -> slot 2 ++ * mic 1 sample -> slot 3 ++ * mic 2 sample -> slot 4 ++ * mic 3 sample -> slot 5 ++ */ ++ 0x0 << 4 | 0x1 << 6 | 0x2 << 8 | 0x3 << 10, ++ 0, ++ 0, ++ }; ++ unsigned vec; ++ ++ /* 0x38-0x3A I2S_TX1_CTRLx */ ++ if (ac->codec_cnt == 1) { ++ vec = 0xFUL; ++ } else { ++ vec = vec_mask[i]; ++ } ++ ac10x_write(I2S_TX1_CTRL1, slots - 1, ac->i2cmap[i]); ++ ac10x_write(I2S_TX1_CTRL2, (vec >> 0) & 0xFF, ac->i2cmap[i]); ++ ac10x_write(I2S_TX1_CTRL3, (vec >> 8) & 0xFF, ac->i2cmap[i]); ++ ++ /* 0x3C-0x3F I2S_TX1_CHMP_CTRLx */ ++ if (ac->codec_cnt == 1) { ++ vec = (0x2 << 0 | 0x3 << 2 | 0x0 << 4 | 0x1 << 6); ++ } else if (ac->codec_cnt == 2) { ++ vec = vec_maps[i]; ++ } ++ ++ ac10x_write(I2S_TX1_CHMP_CTRL1, (vec >> 0) & 0xFF, ac->i2cmap[i]); ++ ac10x_write(I2S_TX1_CHMP_CTRL2, (vec >> 8) & 0xFF, ac->i2cmap[i]); ++ ac10x_write(I2S_TX1_CHMP_CTRL3, (vec >> 16) & 0xFF, ac->i2cmap[i]); ++ ac10x_write(I2S_TX1_CHMP_CTRL4, (vec >> 24) & 0xFF, ac->i2cmap[i]); ++ } ++ return 0; ++} ++ ++static int ac108_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) ++{ ++ unsigned int i, channels, samp_res, rate; ++ struct snd_soc_codec *codec = dai->codec; ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ unsigned bclkdiv; ++ int ret = 0; ++ u8 v; ++ ++ dev_dbg(dai->dev, "%s() stream=%s play:%d capt:%d +++\n", __func__, ++ snd_pcm_stream_str(substream), ++ dai->stream[SNDRV_PCM_STREAM_PLAYBACK].active, ++ dai->stream[SNDRV_PCM_STREAM_CAPTURE].active); ++ ++ if (ac10x->i2c101) { ++ ret = ac101_hw_params(substream, params, dai); ++ if (ret > 0) { ++ dev_dbg(dai->dev, "%s() L%d returned\n", __func__, __LINE__); ++ /* not configure hw_param twice */ ++ return 0; ++ } ++ } ++ ++ if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE && ++ dai->stream[SNDRV_PCM_STREAM_PLAYBACK].active) ++ || (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && ++ dai->stream[SNDRV_PCM_STREAM_CAPTURE].active)) { ++ /* not configure hw_param twice */ ++ /* return 0; */ ++ } ++ ++ channels = params_channels(params); ++ ++ /* Master mode, to clear cpu_dai fifos, output bclk without lrck */ ++ ac10x_read(I2S_CTRL, &v, ac10x->i2cmap[_MASTER_INDEX]); ++ if (v & (0x01 << BCLK_IOEN)) { ++ ac10x_update_bits(I2S_CTRL, 0x1 << LRCK_IOEN, ++ 0x0 << LRCK_IOEN, ac10x->i2cmap[_MASTER_INDEX]); ++ } ++ ++ switch (params_format(params)) { ++ case SNDRV_PCM_FORMAT_S8: ++ samp_res = 0; ++ break; ++ case SNDRV_PCM_FORMAT_S16_LE: ++ samp_res = 2; ++ break; ++ case SNDRV_PCM_FORMAT_S20_3LE: ++ samp_res = 3; ++ break; ++ case SNDRV_PCM_FORMAT_S24_LE: ++ samp_res = 4; ++ break; ++ case SNDRV_PCM_FORMAT_S32_LE: ++ samp_res = 6; ++ break; ++ default: ++ dev_err(dai->dev, "AC108 don't supported the sample resolution: %u\n", ++ params_format(params)); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(ac108_sample_rate); i++) { ++ if (ac108_sample_rate[i].real_val == params_rate(params) / ++ (ac10x->data_protocol + 1UL)) { ++ rate = i; ++ break; ++ } ++ } ++ if (i >= ARRAY_SIZE(ac108_sample_rate)) { ++ return -EINVAL; ++ } ++ ++ if (channels == 8 && ac108_sample_rate[rate].real_val == 96000) { ++ /* 24.576M bit clock is not support by ac108 */ ++ return -EINVAL; ++ } ++ ++ dev_dbg(dai->dev, "rate: %d , channels: %d , samp_res: %d", ++ ac108_sample_rate[rate].real_val, ++ channels, ++ ac108_samp_res[samp_res].real_val); ++ ++ /** ++ * 0x33: ++ * The 8-Low bit of LRCK period value. It is used to program ++ * the number of BCLKs per channel of sample frame. This value ++ * is interpreted as follow: ++ * The 8-Low bit of LRCK period value. It is used to program ++ * the number of BCLKs per channel of sample frame. This value ++ * is interpreted as follow: PCM mode: Number of BCLKs within ++ * (Left + Right) channel width I2S / Left-Justified / ++ * Right-Justified mode: Number of BCLKs within each individual ++ * channel width (Left or Right) N+1 ++ * For example: ++ * n = 7: 8 BCLK width ++ * … ++ * n = 1023: 1024 BCLKs width ++ * 0X32[0:1]: ++ * The 2-High bit of LRCK period value. ++ */ ++ if (ac10x->i2s_mode != PCM_FORMAT) { ++ if (ac10x->data_protocol) { ++ ac108_multi_write(I2S_LRCK_CTRL2, ac108_samp_res[samp_res].real_val - 1, ++ ac10x); ++ /*encoding mode, the max LRCK period value < 32,so the 2-High bit is zero*/ ++ ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x03 << 0, 0x00, ac10x); ++ } else { ++ /*TDM mode or normal mode*/ ++ //ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x03 << 0, 0x00, ac10x); ++ ac108_multi_write(I2S_LRCK_CTRL2, ac108_samp_res[samp_res].real_val - 1, ++ ac10x); ++ ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x03 << 0, 0x00, ac10x); ++ } ++ } else { ++ unsigned div; ++ ++ /*TDM mode or normal mode*/ ++ div = ac108_samp_res[samp_res].real_val * channels - 1; ++ ac108_multi_write(I2S_LRCK_CTRL2, (div & 0xFF), ac10x); ++ ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x03 << 0, (div >> 8) << 0, ac10x); ++ } ++ ++ /** ++ * 0x35: ++ * TX Encoding mode will add 4bits to mark channel number ++ * TODO: need a chat to explain this ++ */ ++ ac108_multi_update_bits(I2S_FMT_CTRL2, 0x07 << SAMPLE_RESOLUTION | 0x07 << SLOT_WIDTH_SEL, ++ ac108_samp_res[samp_res].reg_val << SAMPLE_RESOLUTION | ++ ac108_samp_res[samp_res].reg_val << SLOT_WIDTH_SEL, ac10x); ++ ++ /** ++ * 0x60: ++ * ADC Sample Rate synchronised with I2S1 clock zone ++ */ ++ ac108_multi_update_bits(ADC_SPRC, 0x0f << ADC_FS_I2S1, ++ ac108_sample_rate[rate].reg_val << ADC_FS_I2S1, ac10x); ++ ac108_multi_write(HPF_EN, 0x0F, ac10x); ++ ++ if (ac10x->i2c101 && _MASTER_MULTI_CODEC == _MASTER_AC101) { ++ ac108_config_pll(ac10x, ac108_sample_rate[rate].real_val, ++ ac108_samp_res[samp_res].real_val * channels); ++ } else { ++ ac108_config_pll(ac10x, ac108_sample_rate[rate].real_val, 0); ++ } ++ ++ /* ++ * master mode only ++ */ ++ bclkdiv = ac10x->mclk / (ac108_sample_rate[rate].real_val * channels * ++ ac108_samp_res[samp_res].real_val); ++ for (i = 0; i < ARRAY_SIZE(ac108_bclkdivs) - 1; i++) { ++ if (ac108_bclkdivs[i] >= bclkdiv) { ++ break; ++ } ++ } ++ ac108_multi_update_bits(I2S_BCLK_CTRL, 0x0F << BCLKDIV, i << BCLKDIV, ac10x); ++ ++ /* ++ * slots allocation for each chip ++ */ ++ ac108_multi_chips_slots(ac10x, channels); ++ ++ /*0x21: Module clock enable<I2S, ADC digital, MIC offset Calibration, ADC analog>*/ ++ ac108_multi_write(MOD_CLK_EN, 1 << I2S | 1 << ADC_DIGITAL | ++ 1 << MIC_OFFSET_CALIBRATION | 1 << ADC_ANALOG, ac10x); ++ /*0x22: Module reset de-asserted<I2S, ADC digital, MIC offset Calibration, ADC analog>*/ ++ ac108_multi_write(MOD_RST_CTRL, 1 << I2S | 1 << ADC_DIGITAL | ++ 1 << MIC_OFFSET_CALIBRATION | 1 << ADC_ANALOG, ac10x); ++ ++ ac108_multi_write(I2S_TX1_CHMP_CTRL1, 0xE4, ac10x); ++ ac108_multi_write(I2S_TX1_CHMP_CTRL2, 0xE4, ac10x); ++ ac108_multi_write(I2S_TX1_CHMP_CTRL3, 0xE4, ac10x); ++ ac108_multi_write(I2S_TX1_CHMP_CTRL4, 0xE4, ac10x); ++ ++ ac108_multi_write(I2S_TX2_CHMP_CTRL1, 0xE4, ac10x); ++ ac108_multi_write(I2S_TX2_CHMP_CTRL2, 0xE4, ac10x); ++ ac108_multi_write(I2S_TX2_CHMP_CTRL3, 0xE4, ac10x); ++ ac108_multi_write(I2S_TX2_CHMP_CTRL4, 0xE4, ac10x); ++ ++ dev_dbg(dai->dev, "%s() stream=%s ---\n", __func__, ++ snd_pcm_stream_str(substream)); ++ ++ return 0; ++} ++ ++static int ac108_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) ++{ ++ ++ struct ac10x_priv *ac10x = snd_soc_dai_get_drvdata(dai); ++ ++ freq = 24000000; ++ clk_id = SYSCLK_SRC_PLL; ++ ++ switch (clk_id) { ++ case SYSCLK_SRC_MCLK: ++ ac108_multi_update_bits(SYSCLK_CTRL, 0x1 << SYSCLK_SRC, ++ SYSCLK_SRC_MCLK << SYSCLK_SRC, ac10x); ++ break; ++ case SYSCLK_SRC_PLL: ++ ac108_multi_update_bits(SYSCLK_CTRL, 0x1 << SYSCLK_SRC, ++ SYSCLK_SRC_PLL << SYSCLK_SRC, ac10x); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ac10x->sysclk = freq; ++ ac10x->clk_id = clk_id; ++ ++ return 0; ++} ++ ++/** ++ * The i2s format management related registers are Reg ++ * 30h~Reg36h ++ * 33h,35h will be set in ac108_hw_params, It's BCLK width and ++ * Sample Resolution. ++ * @author baozhu (17-6-20) ++ * ++ * @param dai ++ * @param fmt ++ * ++ * @return int ++ */ ++static int ac108_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) ++{ ++ unsigned char tx_offset, lrck_polarity, brck_polarity; ++ struct ac10x_priv *ac10x = dev_get_drvdata(dai->dev); ++ ++ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { ++ case SND_SOC_DAIFMT_CBM_CFM: /* AC108 Master */ ++ if (! ac10x->i2c101 || _MASTER_MULTI_CODEC == _MASTER_AC108) { ++ /** ++ * 0x30:chip is master mode ,BCLK & LRCK output ++ */ ++ ac108_multi_update_bits(I2S_CTRL, ++ 0x03 << LRCK_IOEN | 0x03 << SDO1_EN | ++ 0x1 << TXEN | 0x1 << GEN, ++ 0x03 << LRCK_IOEN | 0x01 << SDO1_EN | ++ 0x1 << TXEN | 0x1 << GEN, ac10x); ++ /* multi_chips: only one chip set as Master, and the others also need to set as Slave */ ++ ac10x_update_bits(I2S_CTRL, 0x3 << LRCK_IOEN, 0x01 << BCLK_IOEN, ++ ac10x->i2cmap[_MASTER_INDEX]); ++ } else { ++ /* TODO: Both cpu_dai and codec_dai(AC108) be set as slave in DTS */ ++ dev_err(dai->dev, "used as slave when AC101 is master\n"); ++ } ++ break; ++ case SND_SOC_DAIFMT_CBS_CFS: /* AC108 Slave */ ++ /** ++ * 0x30:chip is slave mode, BCLK & LRCK input,enable SDO1_EN and ++ * SDO2_EN, Transmitter Block Enable, Globe Enable ++ */ ++ ac108_multi_update_bits(I2S_CTRL, ++ 0x03 << LRCK_IOEN | 0x03 << SDO1_EN | ++ 0x1 << TXEN | 0x1 << GEN, ++ 0x00 << LRCK_IOEN | 0x03 << SDO1_EN | ++ 0x0 << TXEN | 0x0 << GEN, ac10x); ++ break; ++ default: ++ dev_err(dai->dev, "AC108 Master/Slave mode config error:%u\n\n", ++ (fmt & SND_SOC_DAIFMT_MASTER_MASK) >> 12); ++ return -EINVAL; ++ } ++ ++ /*AC108 config I2S/LJ/RJ/PCM format*/ ++ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { ++ case SND_SOC_DAIFMT_I2S: ++ ac10x->i2s_mode = LEFT_JUSTIFIED_FORMAT; ++ tx_offset = 1; ++ break; ++ case SND_SOC_DAIFMT_RIGHT_J: ++ ac10x->i2s_mode = RIGHT_JUSTIFIED_FORMAT; ++ tx_offset = 0; ++ break; ++ case SND_SOC_DAIFMT_LEFT_J: ++ ac10x->i2s_mode = LEFT_JUSTIFIED_FORMAT; ++ tx_offset = 0; ++ break; ++ case SND_SOC_DAIFMT_DSP_A: ++ ac10x->i2s_mode = PCM_FORMAT; ++ tx_offset = 1; ++ break; ++ case SND_SOC_DAIFMT_DSP_B: ++ ac10x->i2s_mode = PCM_FORMAT; ++ tx_offset = 0; ++ break; ++ default: ++ dev_err(dai->dev, "AC108 I2S format config error:%u\n\n", ++ fmt & SND_SOC_DAIFMT_FORMAT_MASK); ++ return -EINVAL; ++ } ++ ++ /*AC108 config BCLK&LRCK polarity*/ ++ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { ++ case SND_SOC_DAIFMT_NB_NF: ++ brck_polarity = BCLK_NORMAL_DRIVE_N_SAMPLE_P; ++ lrck_polarity = LRCK_LEFT_HIGH_RIGHT_LOW; ++ break; ++ case SND_SOC_DAIFMT_NB_IF: ++ brck_polarity = BCLK_NORMAL_DRIVE_N_SAMPLE_P; ++ lrck_polarity = LRCK_LEFT_LOW_RIGHT_HIGH; ++ break; ++ case SND_SOC_DAIFMT_IB_NF: ++ brck_polarity = BCLK_INVERT_DRIVE_P_SAMPLE_N; ++ lrck_polarity = LRCK_LEFT_HIGH_RIGHT_LOW; ++ break; ++ case SND_SOC_DAIFMT_IB_IF: ++ brck_polarity = BCLK_INVERT_DRIVE_P_SAMPLE_N; ++ lrck_polarity = LRCK_LEFT_LOW_RIGHT_HIGH; ++ break; ++ default: ++ dev_err(dai->dev, "AC108 config BCLK/LRCLK polarity error:%u\n\n", ++ (fmt & SND_SOC_DAIFMT_INV_MASK) >> 8); ++ return -EINVAL; ++ } ++ ++ ac108_configure_power(ac10x); ++ ++ /** ++ *0x31: 0: normal mode, negative edge drive and positive edge sample ++ 1: invert mode, positive edge drive and negative edge sample ++ */ ++ ac108_multi_update_bits(I2S_BCLK_CTRL, 0x01 << BCLK_POLARITY, ++ brck_polarity << BCLK_POLARITY, ac10x); ++ /** ++ * 0x32: same as 0x31 ++ */ ++ ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x01 << LRCK_POLARITY, ++ lrck_polarity << LRCK_POLARITY, ac10x); ++ /** ++ * 0x34:Encoding Mode Selection,Mode ++ * Selection,data is offset by 1 BCLKs to LRCK ++ * normal mode for the last half cycle of BCLK in the slot ? ++ * turn to hi-z state (TDM) when not transferring slot ? ++ */ ++ ac108_multi_update_bits(I2S_FMT_CTRL1, ++ 0x01 << ENCD_SEL | 0x03 << MODE_SEL | 0x01 << TX2_OFFSET | ++ 0x01 << TX1_OFFSET | 0x01 << TX_SLOT_HIZ | 0x01 << TX_STATE, ++ ac10x->data_protocol << ENCD_SEL | ac10x->i2s_mode << MODE_SEL | ++ tx_offset << TX2_OFFSET | tx_offset << TX1_OFFSET | ++ 0x00 << TX_SLOT_HIZ | 0x01 << TX_STATE, ac10x); ++ ++ /** ++ * 0x60: ++ * MSB / LSB First Select: This driver only support MSB First Select . ++ * OUT2_MUTE,OUT1_MUTE shoule be set in widget. ++ * LRCK = 1 BCLK width ++ * Linear PCM ++ * ++ * TODO:pcm mode, bit[0:1] and bit[2] is special ++ */ ++ ac108_multi_update_bits(I2S_FMT_CTRL3, ++ 0x01 << TX_MLS | 0x03 << SEXT | ++ 0x01 << LRCK_WIDTH | 0x03 << TX_PDM, ++ 0x00 << TX_MLS | 0x03 << SEXT | ++ 0x00 << LRCK_WIDTH | 0x00 << TX_PDM, ac10x); ++ ++ ac108_multi_write(HPF_EN, 0x00, ac10x); ++ ++ if (ac10x->i2c101) { ++ return ac101_set_dai_fmt(dai, fmt); ++ } ++ return 0; ++} ++ ++/* ++ * due to miss channels order in cpu_dai, we meed defer the clock starting. ++ */ ++static int ac108_set_clock(int y_start_n_stop) ++{ ++ u8 reg; ++ int ret = 0; ++ ++ dev_dbg(ac10x->codec->dev, "%s() L%d cmd:%d\n", __func__, __LINE__, y_start_n_stop); ++ ++ /* spin_lock move to machine trigger */ ++ ++ if (y_start_n_stop && ac10x->sysclk_en == 0) { ++ /* enable lrck clock */ ++ ac10x_read(I2S_CTRL, ®, ac10x->i2cmap[_MASTER_INDEX]); ++ if (reg & (0x01 << BCLK_IOEN)) { ++ ret = ret || ac10x_update_bits(I2S_CTRL, 0x03 << LRCK_IOEN, ++ 0x03 << LRCK_IOEN, ++ ac10x->i2cmap[_MASTER_INDEX]); ++ } ++ ++ /*0x10: PLL Common voltage enable, PLL enable */ ++ ret = ret || ac108_multi_update_bits(PLL_CTRL1, ++ 0x01 << PLL_EN | 0x01 << PLL_COM_EN, ++ 0x01 << PLL_EN | 0x01 << PLL_COM_EN, ac10x); ++ /* enable global clock */ ++ ret = ret || ac108_multi_update_bits(I2S_CTRL, 0x1 << TXEN | 0x1 << GEN, ++ 0x1 << TXEN | 0x1 << GEN, ac10x); ++ ++ ac10x->sysclk_en = 1UL; ++ } else if (!y_start_n_stop && ac10x->sysclk_en != 0) { ++ /* disable global clock */ ++ ret = ret || ac108_multi_update_bits(I2S_CTRL, 0x1 << TXEN | 0x1 << GEN, ++ 0x0 << TXEN | 0x0 << GEN, ac10x); ++ ++ /*0x10: PLL Common voltage disable, PLL disable */ ++ ret = ret || ac108_multi_update_bits(PLL_CTRL1, 0x01 << PLL_EN | 0x01 << PLL_COM_EN, ++ 0x00 << PLL_EN | 0x00 << PLL_COM_EN, ac10x); ++ ++ /* disable lrck clock if it's enabled */ ++ ac10x_read(I2S_CTRL, ®, ac10x->i2cmap[_MASTER_INDEX]); ++ if (reg & (0x01 << LRCK_IOEN)) { ++ ret = ret || ac10x_update_bits(I2S_CTRL, 0x03 << LRCK_IOEN, ++ 0x01 << BCLK_IOEN, ++ ac10x->i2cmap[_MASTER_INDEX]); ++ } ++ if (!ret) { ++ ac10x->sysclk_en = 0UL; ++ } ++ } ++ ++ return ret; ++} ++ ++static int ac108_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ u8 r; ++ ++ dev_dbg(dai->dev, "%s() stream=%s\n", ++ __func__, ++ snd_pcm_stream_str(substream)); ++ ++ if (ac10x->i2c101 && _MASTER_MULTI_CODEC == _MASTER_AC101) { ++ ac101_trigger(substream, SNDRV_PCM_TRIGGER_START, dai); ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ return 0; ++ } ++ } ++ ++ ac10x_read(I2S_CTRL, &r, ac10x->i2cmap[_MASTER_INDEX]); ++ if ((r & (0x01 << BCLK_IOEN)) && (r & (0x01 << LRCK_IOEN)) == 0) { ++ /* disable global clock */ ++ ac108_multi_update_bits(I2S_CTRL, 0x1 << TXEN | 0x1 << GEN, ++ 0x0 << TXEN | 0x0 << GEN, ac10x); ++ } ++ ++ /* delayed clock starting, move to machine trigger() */ ++ ac108_set_clock(1); ++ ++ return 0; ++} ++ ++int ac108_audio_startup(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_codec *codec = dai->codec; ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ ++ if (ac10x->i2c101) { ++ return ac101_audio_startup(substream, dai); ++ } ++ return 0; ++} ++ ++void ac108_aif_shutdown(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_codec *codec = dai->codec; ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ ++ ac108_set_clock(0); ++ ++ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { ++ /*0x21: Module clock disable <I2S, ADC digital, MIC offset Calibration, ADC analog>*/ ++ ac108_multi_write(MOD_CLK_EN, 0x0, ac10x); ++ /*0x22: Module reset asserted <I2S, ADC digital, MIC offset Calibration, ADC analog>*/ ++ ac108_multi_write(MOD_RST_CTRL, 0x0, ac10x); ++ } ++ ++ if (ac10x->i2c101) { ++ ac101_aif_shutdown(substream, dai); ++ } ++} ++ ++int ac108_aif_mute(struct snd_soc_dai *dai, int mute, int stream) ++{ ++ struct snd_soc_codec *codec = dai->codec; ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ ++ if (ac10x->i2c101) { ++ return ac101_aif_mute(dai, mute); ++ } ++ return 0; ++} ++ ++static const struct snd_soc_dai_ops ac108_dai_ops = { ++ .startup = ac108_audio_startup, ++ .shutdown = ac108_aif_shutdown, ++ ++ /*DAI clocking configuration*/ ++ .set_sysclk = ac108_set_sysclk, ++ ++ /*ALSA PCM audio operations*/ ++ .hw_params = ac108_hw_params, ++ .prepare = ac108_prepare, ++ .mute_stream = ac108_aif_mute, ++ ++ /*DAI format configuration*/ ++ .set_fmt = ac108_set_fmt, ++}; ++ ++static struct snd_soc_dai_driver ac108_dai0 = { ++ .name = "ac10x-codec0", ++ #if _USE_CAPTURE ++ .playback = { ++ .stream_name = "Playback", ++ .channels_min = 1, ++ .channels_max = AC108_CHANNELS_MAX, ++ .rates = AC108_RATES, ++ .formats = AC108_FORMATS, ++ }, ++ #endif ++ .capture = { ++ .stream_name = "Capture", ++ .channels_min = 1, ++ .channels_max = AC108_CHANNELS_MAX, ++ .rates = AC108_RATES, ++ .formats = AC108_FORMATS, ++ }, ++ .ops = &ac108_dai_ops, ++}; ++ ++static struct snd_soc_dai_driver ac108_dai1 = { ++ .name = "ac10x-codec1", ++ #if _USE_CAPTURE ++ .playback = { ++ .stream_name = "Playback", ++ .channels_min = 1, ++ .channels_max = AC108_CHANNELS_MAX, ++ .rates = AC108_RATES, ++ .formats = AC108_FORMATS, ++ }, ++ #endif ++ .capture = { ++ .stream_name = "Capture", ++ .channels_min = 1, ++ .channels_max = AC108_CHANNELS_MAX, ++ .rates = AC108_RATES, ++ .formats = AC108_FORMATS, ++ }, ++ .ops = &ac108_dai_ops, ++}; ++ ++static struct snd_soc_dai_driver *ac108_dai[] = { ++ &ac108_dai0, ++ &ac108_dai1, ++}; ++ ++static int ac108_add_widgets(struct snd_soc_codec *codec) ++{ ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); ++ const struct snd_kcontrol_new* snd_kcntl = ac108_snd_controls; ++ int ctrl_cnt = ARRAY_SIZE(ac108_snd_controls); ++ ++ /* only register controls correspond to exist chips */ ++ if (ac10x->tdm_chips_cnt >= 2) { ++ snd_kcntl = ac108tdm_snd_controls; ++ ctrl_cnt = ARRAY_SIZE(ac108tdm_snd_controls); ++ } ++ snd_soc_add_codec_controls(codec, snd_kcntl, ctrl_cnt); ++ ++ snd_soc_dapm_new_controls(dapm, ac108_dapm_widgets,ARRAY_SIZE(ac108_dapm_widgets)); ++ snd_soc_dapm_add_routes(dapm, ac108_dapm_routes, ARRAY_SIZE(ac108_dapm_routes)); ++ ++ return 0; ++} ++ ++static int ac108_codec_probe(struct snd_soc_codec *codec) ++{ ++ spin_lock_init(&ac10x->lock); ++ ++ ac10x->codec = codec; ++ dev_set_drvdata(codec->dev, ac10x); ++ ac108_add_widgets(codec); ++ ++ if (ac10x->i2c101) { ++ ac101_codec_probe(codec); ++ } ++ ++ /* change default volume */ ++ ac108_multi_update_bits(ADC1_DVOL_CTRL, 0xff, 0xc8, ac10x); ++ ac108_multi_update_bits(ADC2_DVOL_CTRL, 0xff, 0xc8, ac10x); ++ ac108_multi_update_bits(ADC3_DVOL_CTRL, 0xff, 0xc8, ac10x); ++ ac108_multi_update_bits(ADC4_DVOL_CTRL, 0xff, 0xc8, ac10x); ++ ++ return 0; ++} ++ ++static int ac108_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) ++{ ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ ++ dev_dbg(codec->dev, "AC108 level:%d\n", level); ++ ++ switch (level) { ++ case SND_SOC_BIAS_ON: ++ ac108_multi_update_bits(ANA_ADC1_CTRL1, 0x01 << ADC1_MICBIAS_EN, ++ 0x01 << ADC1_MICBIAS_EN, ac10x); ++ ac108_multi_update_bits(ANA_ADC2_CTRL1, 0x01 << ADC2_MICBIAS_EN, ++ 0x01 << ADC2_MICBIAS_EN, ac10x); ++ ac108_multi_update_bits(ANA_ADC3_CTRL1, 0x01 << ADC3_MICBIAS_EN, ++ 0x01 << ADC3_MICBIAS_EN, ac10x); ++ ac108_multi_update_bits(ANA_ADC4_CTRL1, 0x01 << ADC4_MICBIAS_EN, ++ 0x01 << ADC4_MICBIAS_EN, ac10x); ++ break; ++ ++ case SND_SOC_BIAS_PREPARE: ++ /* Put the MICBIASes into regulating mode */ ++ break; ++ ++ case SND_SOC_BIAS_STANDBY: ++ break; ++ ++ case SND_SOC_BIAS_OFF: ++ ac108_multi_update_bits(ANA_ADC1_CTRL1, 0x01 << ADC1_MICBIAS_EN, ++ 0x00 << ADC1_MICBIAS_EN, ac10x); ++ ac108_multi_update_bits(ANA_ADC2_CTRL1, 0x01 << ADC2_MICBIAS_EN, ++ 0x00 << ADC2_MICBIAS_EN, ac10x); ++ ac108_multi_update_bits(ANA_ADC3_CTRL1, 0x01 << ADC3_MICBIAS_EN, ++ 0x00 << ADC3_MICBIAS_EN, ac10x); ++ ac108_multi_update_bits(ANA_ADC4_CTRL1, 0x01 << ADC4_MICBIAS_EN, ++ 0x00 << ADC4_MICBIAS_EN, ac10x); ++ break; ++ } ++ ++ if (ac10x->i2c101) { ++ ac101_set_bias_level(codec, level); ++ } ++ return 0; ++} ++ ++int ac108_codec_remove(struct snd_soc_codec *codec) { ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ ++ if (! ac10x->i2c101) { ++ return 0; ++ } ++ return ac101_codec_remove(codec); ++} ++#if __NO_SND_SOC_CODEC_DRV ++void ac108_codec_remove_void(struct snd_soc_codec *codec) ++{ ++ ac108_codec_remove(codec); ++} ++#define ac108_codec_remove ac108_codec_remove_void ++#endif ++ ++int ac108_codec_suspend(struct snd_soc_codec *codec) ++{ ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ int i; ++ ++ for (i = 0; i < ac10x->codec_cnt; i++) { ++ regcache_cache_only(ac10x->i2cmap[i], true); ++ } ++ ++ if (! ac10x->i2c101) { ++ return 0; ++ } ++ return ac101_codec_suspend(codec); ++} ++ ++int ac108_codec_resume(struct snd_soc_codec *codec) ++{ ++ struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); ++ int i, ret; ++ ++ /* Sync reg_cache with the hardware */ ++ for (i = 0; i < ac10x->codec_cnt; i++) { ++ regcache_cache_only(ac10x->i2cmap[i], false); ++ ret = regcache_sync(ac10x->i2cmap[i]); ++ if (ret != 0) { ++ dev_err(codec->dev, "Failed to sync i2cmap%d register cache: %d\n", ++ i, ret); ++ regcache_cache_only(ac10x->i2cmap[i], true); ++ } ++ } ++ ++ if (! ac10x->i2c101) { ++ return 0; ++ } ++ return ac101_codec_resume(codec); ++} ++ ++static struct snd_soc_codec_driver ac10x_soc_codec_driver = { ++ .probe = ac108_codec_probe, ++ .remove = ac108_codec_remove, ++ .suspend = ac108_codec_suspend, ++ .resume = ac108_codec_resume, ++ .set_bias_level = ac108_set_bias_level, ++ .read = ac108_codec_read, ++ .write = ac108_codec_write, ++}; ++ ++static ssize_t ac108_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int val = 0, flag = 0; ++ u8 i = 0, reg, num, value_w, value_r[4]; ++ ++ val = simple_strtol(buf, NULL, 16); ++ flag = (val >> 16) & 0xF; ++ ++ if (flag) { ++ reg = (val >> 8) & 0xFF; ++ value_w = val & 0xFF; ++ ac108_multi_write(reg, value_w, ac10x); ++ dev_info(dev, "Write 0x%02x to REG:0x%02x\n", value_w, reg); ++ } else { ++ int k; ++ ++ reg = (val >> 8) & 0xFF; ++ num = val & 0xff; ++ dev_info(dev, "\nRead: start REG:0x%02x,count:0x%02x\n", reg, num); ++ ++ for (k = 0; k < ac10x->codec_cnt; k++) ++ regcache_cache_bypass(ac10x->i2cmap[k], true); ++ ++ do { ++ memset(value_r, 0, sizeof value_r); ++ ++ for (k = 0; k < ac10x->codec_cnt; k++) ++ ac10x_read(reg, &value_r[k], ac10x->i2cmap[k]); ++ ++ if (ac10x->codec_cnt >= 2) ++ dev_info(dev, "REG[0x%02x]: 0x%02x 0x%02x", reg, ++ value_r[0], value_r[1]); ++ else ++ dev_info(dev, "REG[0x%02x]: 0x%02x", reg, value_r[0]); ++ reg++; ++ ++ if ((++i == num) || (i % 4 == 0)) ++ dev_info(dev, "\n"); ++ } while (i < num); ++ ++ for (k = 0; k < ac10x->codec_cnt; k++) ++ regcache_cache_bypass(ac10x->i2cmap[k], false); ++ } ++ ++ return count; ++} ++ ++static ssize_t ac108_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ dev_info(dev, "echo flag|reg|val > ac108\n"); ++ dev_info(dev, "eg read star addres=0x06,count 0x10:echo 0610 >ac108\n"); ++ dev_info(dev, "eg write value:0xfe to address:0x06 :echo 106fe > ac108\n"); ++ return 0; ++} ++ ++static DEVICE_ATTR(ac108, 0644, ac108_show, ac108_store); ++static struct attribute *ac108_debug_attrs[] = { ++ &dev_attr_ac108.attr, ++ NULL, ++}; ++static struct attribute_group ac108_debug_attr_group = { ++ .name = "ac108_debug", ++ .attrs = ac108_debug_attrs, ++}; ++ ++static const struct i2c_device_id ac108_i2c_id[] = { ++ { "ac108_0", 0 }, ++ { "ac108_1", 1 }, ++ { "ac108_2", 2 }, ++ { "ac108_3", 3 }, ++ { "ac101", AC101_I2C_ID }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, ac108_i2c_id); ++ ++static const struct regmap_config ac108_regmap = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .reg_stride = 1, ++ .max_register = 0xDF, ++ .cache_type = REGCACHE_FLAT, ++}; ++static int ac108_i2c_probe(struct i2c_client *i2c) ++{ ++ struct device_node *np = i2c->dev.of_node; ++ const struct i2c_device_id *i2c_id; ++ unsigned int val = 0; ++ int ret = 0, index; ++ ++ if (ac10x == NULL) { ++ ac10x = kzalloc(sizeof(struct ac10x_priv), GFP_KERNEL); ++ if (ac10x == NULL) { ++ dev_err(&i2c->dev, "Unable to allocate ac10x private data\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ i2c_id = i2c_match_id(ac108_i2c_id, i2c); ++ index = (int)i2c_id->driver_data; ++ if (index == AC101_I2C_ID) { ++ ac10x->i2c101 = i2c; ++ i2c_set_clientdata(i2c, ac10x); ++ ret = ac101_probe(i2c, i2c_id); ++ if (ret) { ++ ac10x->i2c101 = NULL; ++ return ret; ++ } ++ goto __ret; ++ } ++ ++ ret = of_property_read_u32(np, "data-protocol", &val); ++ if (ret) { ++ dev_err(&i2c->dev, "Please set data-protocol.\n"); ++ return -EINVAL; ++ } ++ ac10x->data_protocol = val; ++ ++ if (of_property_read_u32(np, "tdm-chips-count", &val)) val = 1; ++ ac10x->tdm_chips_cnt = val; ++ ++ dev_info(&i2c->dev, " ac10x i2c_id number: %d\n", index); ++ dev_info(&i2c->dev, " ac10x data protocol: %d\n", ac10x->data_protocol); ++ ++ ac10x->i2c[index] = i2c; ++ ac10x->i2cmap[index] = devm_regmap_init_i2c(i2c, &ac108_regmap); ++ if (IS_ERR(ac10x->i2cmap[index])) { ++ ret = PTR_ERR(ac10x->i2cmap[index]); ++ dev_err(&i2c->dev, "Fail to initialize i2cmap%d I/O: %d\n", index, ret); ++ return ret; ++ } ++ ++ /* ++ * Writing this register with 0x12 ++ * will resets all register to their default state. ++ */ ++ regcache_cache_only(ac10x->i2cmap[index], false); ++ ++ ret = regmap_write(ac10x->i2cmap[index], CHIP_RST, CHIP_RST_VAL); ++ msleep(1); ++ ++ /* sync regcache for FLAT type */ ++ ac10x_fill_regcache(&i2c->dev, ac10x->i2cmap[index]); ++ ++ ac10x->codec_cnt++; ++ dev_info(&i2c->dev, " ac10x codec count : %d\n", ac10x->codec_cnt); ++ ++ ret = sysfs_create_group(&i2c->dev.kobj, &ac108_debug_attr_group); ++ if (ret) { ++ dev_err(&i2c->dev, "failed to create attr group\n"); ++ } ++ ++__ret: ++ /* It's time to bind codec to i2c[_MASTER_INDEX] when all i2c are ready */ ++ if ((ac10x->codec_cnt != 0 && ac10x->tdm_chips_cnt < 2) ++ || (ac10x->i2c[0] && ac10x->i2c[1] && ac10x->i2c101)) { ++ /* no playback stream */ ++ if (! ac10x->i2c101) { ++ memset(&ac108_dai[_MASTER_INDEX]->playback, '\0', ++ sizeof ac108_dai[_MASTER_INDEX]->playback); ++ } ++ ret = snd_soc_register_codec(&ac10x->i2c[_MASTER_INDEX]->dev, ++ &ac10x_soc_codec_driver, ++ ac108_dai[_MASTER_INDEX], 1); ++ if (ret < 0) { ++ dev_err(&i2c->dev, "Failed to register ac10x codec: %d\n", ret); ++ } ++ } ++ return ret; ++} ++ ++static void ac108_i2c_remove(struct i2c_client *i2c) ++{ ++ if (ac10x->codec != NULL) { ++ snd_soc_unregister_codec(&ac10x->i2c[_MASTER_INDEX]->dev); ++ ac10x->codec = NULL; ++ } ++ if (i2c == ac10x->i2c101) { ++ ac101_remove(ac10x->i2c101); ++ ac10x->i2c101 = NULL; ++ goto __ret; ++ } ++ ++ if (i2c == ac10x->i2c[0]) { ++ ac10x->i2c[0] = NULL; ++ } ++ if (i2c == ac10x->i2c[1]) { ++ ac10x->i2c[1] = NULL; ++ } ++ ++ sysfs_remove_group(&i2c->dev.kobj, &ac108_debug_attr_group); ++ ++__ret: ++ if (!ac10x->i2c[0] && !ac10x->i2c[1] && !ac10x->i2c101) { ++ kfree(ac10x); ++ ac10x = NULL; ++ } ++} ++ ++static const struct of_device_id ac108_of_match[] = { ++ { .compatible = "x-power,ac108_0", }, ++ { .compatible = "x-power,ac108_1", }, ++ { .compatible = "x-power,ac108_2", }, ++ { .compatible = "x-power,ac108_3", }, ++ { .compatible = "x-power,ac101", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, ac108_of_match); ++ ++static struct i2c_driver ac108_i2c_driver = { ++ .driver = { ++ .name = "ac10x-codec", ++ .of_match_table = ac108_of_match, ++ }, ++ .probe = ac108_i2c_probe, ++ .remove = ac108_i2c_remove, ++ .id_table = ac108_i2c_id, ++}; ++ ++module_i2c_driver(ac108_i2c_driver); ++ ++MODULE_DESCRIPTION("ASoC AC108 driver"); ++MODULE_AUTHOR("Baozhu Zuo<zuobaozhu@gmail.com>"); ++MODULE_LICENSE("GPL"); +--- /dev/null ++++ b/sound/soc/codecs/ac108.h +@@ -0,0 +1,749 @@ ++/* ++ * ac108.h -- ac108 ALSA Soc Audio driver ++ * ++ * Author: panjunwen ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ * ++ */ ++ ++#ifndef _AC108_H ++#define _AC108_H ++ ++/*** AC108 Codec Register Define***/ ++ ++//Chip Reset ++#define CHIP_RST 0x00 ++#define CHIP_RST_VAL 0x12 ++ ++//Power Control ++#define PWR_CTRL1 0x01 ++#define PWR_CTRL2 0x02 ++#define PWR_CTRL3 0x03 ++#define PWR_CTRL4 0x04 ++#define PWR_CTRL5 0x05 ++#define PWR_CTRL6 0x06 ++#define PWR_CTRL7 0x07 ++#define PWR_CTRL8 0x08 ++#define PWR_CTRL9 0x09 ++ ++//PLL Configure Control ++#define PLL_CTRL1 0x10 ++#define PLL_CTRL2 0x11 ++#define PLL_CTRL3 0x12 ++#define PLL_CTRL4 0x13 ++#define PLL_CTRL5 0x14 ++#define PLL_CTRL6 0x16 ++#define PLL_CTRL7 0x17 ++#define PLL_LOCK_CTRL 0x18 ++ ++//System Clock Control ++#define SYSCLK_CTRL 0x20 ++#define MOD_CLK_EN 0x21 ++#define MOD_RST_CTRL 0x22 ++#define DSM_CLK_CTRL 0x25 ++ ++//I2S Common Control ++#define I2S_CTRL 0x30 ++#define I2S_BCLK_CTRL 0x31 ++#define I2S_LRCK_CTRL1 0x32 ++#define I2S_LRCK_CTRL2 0x33 ++#define I2S_FMT_CTRL1 0x34 ++#define I2S_FMT_CTRL2 0x35 ++#define I2S_FMT_CTRL3 0x36 ++ ++//I2S TX1 Control ++#define I2S_TX1_CTRL1 0x38 ++#define I2S_TX1_CTRL2 0x39 ++#define I2S_TX1_CTRL3 0x3A ++#define I2S_TX1_CHMP_CTRL1 0x3C ++#define I2S_TX1_CHMP_CTRL2 0x3D ++#define I2S_TX1_CHMP_CTRL3 0x3E ++#define I2S_TX1_CHMP_CTRL4 0x3F ++ ++//I2S TX2 Control ++#define I2S_TX2_CTRL1 0x40 ++#define I2S_TX2_CTRL2 0x41 ++#define I2S_TX2_CTRL3 0x42 ++#define I2S_TX2_CHMP_CTRL1 0x44 ++#define I2S_TX2_CHMP_CTRL2 0x45 ++#define I2S_TX2_CHMP_CTRL3 0x46 ++#define I2S_TX2_CHMP_CTRL4 0x47 ++ ++//I2S RX1 Control ++#define I2S_RX1_CTRL1 0x50 ++#define I2S_RX1_CHMP_CTRL1 0x54 ++#define I2S_RX1_CHMP_CTRL2 0x55 ++#define I2S_RX1_CHMP_CTRL3 0x56 ++#define I2S_RX1_CHMP_CTRL4 0x57 ++ ++//I2S Loopback Debug ++#define I2S_LPB_DEBUG 0x58 ++ ++//ADC Common Control ++#define ADC_SPRC 0x60 ++#define ADC_DIG_EN 0x61 ++#define DMIC_EN 0x62 ++#define ADC_DSR 0x63 ++#define ADC_FIR 0x64 ++#define ADC_DDT_CTRL 0x65 ++ ++//HPF Control ++#define HPF_EN 0x66 ++#define HPF_COEF_REGH1 0x67 ++#define HPF_COEF_REGH2 0x68 ++#define HPF_COEF_REGL1 0x69 ++#define HPF_COEF_REGL2 0x6A ++#define HPF_GAIN_REGH1 0x6B ++#define HPF_GAIN_REGH2 0x6C ++#define HPF_GAIN_REGL1 0x6D ++#define HPF_GAIN_REGL2 0x6E ++ ++//ADC Digital Channel Volume Control ++#define ADC1_DVOL_CTRL 0x70 ++#define ADC2_DVOL_CTRL 0x71 ++#define ADC3_DVOL_CTRL 0x72 ++#define ADC4_DVOL_CTRL 0x73 ++ ++//ADC Digital Mixer Source and Gain Control ++#define ADC1_DMIX_SRC 0x76 ++#define ADC2_DMIX_SRC 0x77 ++#define ADC3_DMIX_SRC 0x78 ++#define ADC4_DMIX_SRC 0x79 ++ ++//ADC Digital Debug Control ++#define ADC_DIG_DEBUG 0x7F ++ ++//I2S Pad Drive Control ++#define I2S_DAT_PADDRV_CTRL 0x80 ++#define I2S_CLK_PADDRV_CTRL 0x81 ++ ++//Analog PGA Control ++#define ANA_PGA1_CTRL 0x90 ++#define ANA_PGA2_CTRL 0x91 ++#define ANA_PGA3_CTRL 0x92 ++#define ANA_PGA4_CTRL 0x93 ++ ++//MIC Offset Control ++#define MIC_OFFSET_CTRL1 0x96 ++#define MIC_OFFSET_CTRL2 0x97 ++#define MIC1_OFFSET_STATU1 0x98 ++#define MIC1_OFFSET_STATU2 0x99 ++#define MIC2_OFFSET_STATU1 0x9A ++#define MIC2_OFFSET_STATU2 0x9B ++#define MIC3_OFFSET_STATU1 0x9C ++#define MIC3_OFFSET_STATU2 0x9D ++#define MIC4_OFFSET_STATU1 0x9E ++#define MIC4_OFFSET_STATU2 0x9F ++ ++//ADC1 Analog Control ++#define ANA_ADC1_CTRL1 0xA0 ++#define ANA_ADC1_CTRL2 0xA1 ++#define ANA_ADC1_CTRL3 0xA2 ++#define ANA_ADC1_CTRL4 0xA3 ++#define ANA_ADC1_CTRL5 0xA4 ++#define ANA_ADC1_CTRL6 0xA5 ++#define ANA_ADC1_CTRL7 0xA6 ++ ++//ADC2 Analog Control ++#define ANA_ADC2_CTRL1 0xA7 ++#define ANA_ADC2_CTRL2 0xA8 ++#define ANA_ADC2_CTRL3 0xA9 ++#define ANA_ADC2_CTRL4 0xAA ++#define ANA_ADC2_CTRL5 0xAB ++#define ANA_ADC2_CTRL6 0xAC ++#define ANA_ADC2_CTRL7 0xAD ++ ++//ADC3 Analog Control ++#define ANA_ADC3_CTRL1 0xAE ++#define ANA_ADC3_CTRL2 0xAF ++#define ANA_ADC3_CTRL3 0xB0 ++#define ANA_ADC3_CTRL4 0xB1 ++#define ANA_ADC3_CTRL5 0xB2 ++#define ANA_ADC3_CTRL6 0xB3 ++#define ANA_ADC3_CTRL7 0xB4 ++ ++//ADC4 Analog Control ++#define ANA_ADC4_CTRL1 0xB5 ++#define ANA_ADC4_CTRL2 0xB6 ++#define ANA_ADC4_CTRL3 0xB7 ++#define ANA_ADC4_CTRL4 0xB8 ++#define ANA_ADC4_CTRL5 0xB9 ++#define ANA_ADC4_CTRL6 0xBA ++#define ANA_ADC4_CTRL7 0xBB ++ ++//GPIO Configure ++#define GPIO_CFG1 0xC0 ++#define GPIO_CFG2 0xC1 ++#define GPIO_DAT 0xC2 ++#define GPIO_DRV 0xC3 ++#define GPIO_PULL 0xC4 ++#define GPIO_INT_CFG 0xC5 ++#define GPIO_INT_EN 0xC6 ++#define GPIO_INT_STATUS 0xC7 ++ ++//Misc ++#define BGTC_DAT 0xD1 ++#define BGVC_DAT 0xD2 ++#define PRNG_CLK_CTRL 0xDF ++ ++/*** AC108 Codec Register Bit Define***/ ++ ++/*PWR_CTRL1*/ ++#define CP12_CTRL 4 ++#define CP12_SENSE_SELECT 3 ++ ++/*PWR_CTRL2*/ ++#define CP12_SENSE_FILT 6 ++#define CP12_COMP_FF_EN 3 ++#define CP12_FORCE_ENABLE 2 ++#define CP12_FORCE_RSTB 1 ++ ++/*PWR_CTRL3*/ ++#define LDO33DIG_CTRL 0 ++ ++/*PWR_CTRL6*/ ++#define LDO33ANA_2XHDRM 2 ++#define LDO33ANA_ENABLE 0 ++ ++/*PWR_CTRL7*/ ++#define VREF_SEL 3 ++#define VREF_FASTSTART_ENABLE 1 ++#define VREF_ENABLE 0 ++ ++/*PWR_CTRL9*/ ++#define VREFP_FASTSTART_ENABLE 7 ++#define VREFP_RESCTRL 5 ++#define VREFP_LPMODE 4 ++#define IGEN_TRIM 1 ++#define VREFP_ENABLE 0 ++ ++/*PLL_CTRL1*/ ++#define PLL_IBIAS 4 ++#define PLL_NDET 3 ++#define PLL_LOCKED_STATUS 2 ++#define PLL_COM_EN 1 ++#define PLL_EN 0 ++ ++/*PLL_CTRL2*/ ++#define PLL_PREDIV2 5 ++#define PLL_PREDIV1 0 ++ ++/*PLL_CTRL3*/ ++#define PLL_LOOPDIV_MSB 0 ++ ++/*PLL_CTRL4*/ ++#define PLL_LOOPDIV_LSB 0 ++ ++/*PLL_CTRL5*/ ++#define PLL_POSTDIV2 5 ++#define PLL_POSTDIV1 0 ++ ++/*PLL_CTRL6*/ ++#define PLL_LDO 6 ++#define PLL_CP 0 ++ ++/*PLL_CTRL7*/ ++#define PLL_CAP 6 ++#define PLL_RES 4 ++#define PLL_TEST_EN 0 ++ ++/*PLL_LOCK_CTRL*/ ++#define LOCK_LEVEL1 2 ++#define LOCK_LEVEL2 1 ++#define PLL_LOCK_EN 0 ++ ++/*SYSCLK_CTRL*/ ++#define PLLCLK_EN 7 ++#define PLLCLK_SRC 4 ++#define SYSCLK_SRC 3 ++#define SYSCLK_EN 0 ++ ++/*MOD_CLK_EN & MOD_RST_CTRL*/ ++#define I2S 7 ++#define ADC_DIGITAL 4 ++#define MIC_OFFSET_CALIBRATION 1 ++#define ADC_ANALOG 0 ++ ++/*DSM_CLK_CTRL*/ ++#define MIC_OFFSET_DIV 4 ++#define DSM_CLK_SEL 0 ++ ++/*I2S_CTRL*/ ++#define BCLK_IOEN 7 ++#define LRCK_IOEN 6 ++#define SDO2_EN 5 ++#define SDO1_EN 4 ++#define TXEN 2 ++#define RXEN 1 ++#define GEN 0 ++ ++/*I2S_BCLK_CTRL*/ ++#define EDGE_TRANSFER 5 ++#define BCLK_POLARITY 4 ++#define BCLKDIV 0 ++ ++/*I2S_LRCK_CTRL1*/ ++#define LRCK_POLARITY 4 ++#define LRCK_PERIODH 0 ++ ++/*I2S_LRCK_CTRL2*/ ++#define LRCK_PERIODL 0 ++ ++/*I2S_FMT_CTRL1*/ ++#define ENCD_SEL 6 ++#define MODE_SEL 4 ++#define TX2_OFFSET 3 ++#define TX1_OFFSET 2 ++#define TX_SLOT_HIZ 1 ++#define TX_STATE 0 ++ ++/*I2S_FMT_CTRL2*/ ++#define SLOT_WIDTH_SEL 4 ++#define SAMPLE_RESOLUTION 0 ++ ++/*I2S_FMT_CTRL3*/ ++#define TX_MLS 7 ++#define SEXT 5 ++#define OUT2_MUTE 4 ++#define OUT1_MUTE 3 ++#define LRCK_WIDTH 2 ++#define TX_PDM 0 ++ ++/*I2S_TX1_CTRL1*/ ++#define TX1_CHSEL 0 ++ ++/*I2S_TX1_CTRL2*/ ++#define TX1_CH8_EN 7 ++#define TX1_CH7_EN 6 ++#define TX1_CH6_EN 5 ++#define TX1_CH5_EN 4 ++#define TX1_CH4_EN 3 ++#define TX1_CH3_EN 2 ++#define TX1_CH2_EN 1 ++#define TX1_CH1_EN 0 ++ ++/*I2S_TX1_CTRL3*/ ++#define TX1_CH16_EN 7 ++#define TX1_CH15_EN 6 ++#define TX1_CH14_EN 5 ++#define TX1_CH13_EN 4 ++#define TX1_CH12_EN 3 ++#define TX1_CH11_EN 2 ++#define TX1_CH10_EN 1 ++#define TX1_CH9_EN 0 ++ ++/*I2S_TX1_CHMP_CTRL1*/ ++#define TX1_CH4_MAP 6 ++#define TX1_CH3_MAP 4 ++#define TX1_CH2_MAP 2 ++#define TX1_CH1_MAP 0 ++ ++/*I2S_TX1_CHMP_CTRL2*/ ++#define TX1_CH8_MAP 6 ++#define TX1_CH7_MAP 4 ++#define TX1_CH6_MAP 2 ++#define TX1_CH5_MAP 0 ++ ++/*I2S_TX1_CHMP_CTRL3*/ ++#define TX1_CH12_MAP 6 ++#define TX1_CH11_MAP 4 ++#define TX1_CH10_MAP 2 ++#define TX1_CH9_MAP 0 ++ ++/*I2S_TX1_CHMP_CTRL4*/ ++#define TX1_CH16_MAP 6 ++#define TX1_CH15_MAP 4 ++#define TX1_CH14_MAP 2 ++#define TX1_CH13_MAP 0 ++ ++/*I2S_TX2_CTRL1*/ ++#define TX2_CHSEL 0 ++ ++/*I2S_TX2_CHMP_CTRL1*/ ++#define TX2_CH4_MAP 6 ++#define TX2_CH3_MAP 4 ++#define TX2_CH2_MAP 2 ++#define TX2_CH1_MAP 0 ++ ++/*I2S_TX2_CHMP_CTRL2*/ ++#define TX2_CH8_MAP 6 ++#define TX2_CH7_MAP 4 ++#define TX2_CH6_MAP 2 ++#define TX2_CH5_MAP 0 ++ ++/*I2S_TX2_CHMP_CTRL3*/ ++#define TX2_CH12_MAP 6 ++#define TX2_CH11_MAP 4 ++#define TX2_CH10_MAP 2 ++#define TX2_CH9_MAP 0 ++ ++/*I2S_TX2_CHMP_CTRL4*/ ++#define TX2_CH16_MAP 6 ++#define TX2_CH15_MAP 4 ++#define TX2_CH14_MAP 2 ++#define TX2_CH13_MAP 0 ++ ++/*I2S_RX1_CTRL1*/ ++#define RX1_CHSEL 0 ++ ++/*I2S_RX1_CHMP_CTRL1*/ ++#define RX1_CH4_MAP 6 ++#define RX1_CH3_MAP 4 ++#define RX1_CH2_MAP 2 ++#define RX1_CH1_MAP 0 ++ ++/*I2S_RX1_CHMP_CTRL2*/ ++#define RX1_CH8_MAP 6 ++#define RX1_CH7_MAP 4 ++#define RX1_CH6_MAP 2 ++#define RX1_CH5_MAP 0 ++ ++/*I2S_RX1_CHMP_CTRL3*/ ++#define RX1_CH12_MAP 6 ++#define RX1_CH11_MAP 4 ++#define RX1_CH10_MAP 2 ++#define RX1_CH9_MAP 0 ++ ++/*I2S_RX1_CHMP_CTRL4*/ ++#define RX1_CH16_MAP 6 ++#define RX1_CH15_MAP 4 ++#define RX1_CH14_MAP 2 ++#define RX1_CH13_MAP 0 ++ ++/*I2S_LPB_DEBUG*/ ++#define I2S_LPB_DEBUG_EN 0 ++ ++/*ADC_SPRC*/ ++#define ADC_FS_I2S1 0 ++ ++/*ADC_DIG_EN*/ ++#define DG_EN 4 ++#define ENAD4 3 ++#define ENAD3 2 ++#define ENAD2 1 ++#define ENAD1 0 ++ ++/*DMIC_EN*/ ++#define DMIC2_EN 1 ++#define DMIC1_EN 0 ++ ++/*ADC_DSR*/ ++#define DIG_ADC4_SRS 6 ++#define DIG_ADC3_SRS 4 ++#define DIG_ADC2_SRS 2 ++#define DIG_ADC1_SRS 0 ++ ++/*ADC_DDT_CTRL*/ ++#define ADOUT_DLY_EN 2 ++#define ADOUT_DTS 0 ++ ++/*HPF_EN*/ ++#define DIG_ADC4_HPF_EN 3 ++#define DIG_ADC3_HPF_EN 2 ++#define DIG_ADC2_HPF_EN 1 ++#define DIG_ADC1_HPF_EN 0 ++ ++/*ADC1_DMIX_SRC*/ ++#define ADC1_ADC4_DMXL_GC 7 ++#define ADC1_ADC3_DMXL_GC 6 ++#define ADC1_ADC2_DMXL_GC 5 ++#define ADC1_ADC1_DMXL_GC 4 ++#define ADC1_ADC4_DMXL_SRC 3 ++#define ADC1_ADC3_DMXL_SRC 2 ++#define ADC1_ADC2_DMXL_SRC 1 ++#define ADC1_ADC1_DMXL_SRC 0 ++ ++/*ADC2_DMIX_SRC*/ ++#define ADC2_ADC4_DMXL_GC 7 ++#define ADC2_ADC3_DMXL_GC 6 ++#define ADC2_ADC2_DMXL_GC 5 ++#define ADC2_ADC1_DMXL_GC 4 ++#define ADC2_ADC4_DMXL_SRC 3 ++#define ADC2_ADC3_DMXL_SRC 2 ++#define ADC2_ADC2_DMXL_SRC 1 ++#define ADC2_ADC1_DMXL_SRC 0 ++ ++/*ADC3_DMIX_SRC*/ ++#define ADC3_ADC4_DMXL_GC 7 ++#define ADC3_ADC3_DMXL_GC 6 ++#define ADC3_ADC2_DMXL_GC 5 ++#define ADC3_ADC1_DMXL_GC 4 ++#define ADC3_ADC4_DMXL_SRC 3 ++#define ADC3_ADC3_DMXL_SRC 2 ++#define ADC3_ADC2_DMXL_SRC 1 ++#define ADC3_ADC1_DMXL_SRC 0 ++ ++/*ADC4_DMIX_SRC*/ ++#define ADC4_ADC4_DMXL_GC 7 ++#define ADC4_ADC3_DMXL_GC 6 ++#define ADC4_ADC2_DMXL_GC 5 ++#define ADC4_ADC1_DMXL_GC 4 ++#define ADC4_ADC4_DMXL_SRC 3 ++#define ADC4_ADC3_DMXL_SRC 2 ++#define ADC4_ADC2_DMXL_SRC 1 ++#define ADC4_ADC1_DMXL_SRC 0 ++ ++/*ADC_DIG_DEBUG*/ ++#define ADC_PTN_SEL 0 ++ ++/*I2S_DAT_PADDRV_CTRL*/ ++#define TX2_DAT_DRV 4 ++#define TX1_DAT_DRV 0 ++ ++/*I2S_CLK_PADDRV_CTRL*/ ++#define LRCK_DRV 4 ++#define BCLK_DRV 0 ++ ++/*ANA_PGA1_CTRL*/ ++#define ADC1_ANALOG_PGA 1 ++#define ADC1_ANALOG_PGA_STEP 0 ++ ++/*ANA_PGA2_CTRL*/ ++#define ADC2_ANALOG_PGA 1 ++#define ADC2_ANALOG_PGA_STEP 0 ++ ++/*ANA_PGA3_CTRL*/ ++#define ADC3_ANALOG_PGA 1 ++#define ADC3_ANALOG_PGA_STEP 0 ++ ++/*ANA_PGA4_CTRL*/ ++#define ADC4_ANALOG_PGA 1 ++#define ADC4_ANALOG_PGA_STEP 0 ++ ++ ++/*MIC_OFFSET_CTRL1*/ ++#define MIC_OFFSET_CAL_EN4 3 ++#define MIC_OFFSET_CAL_EN3 2 ++#define MIC_OFFSET_CAL_EN2 1 ++#define MIC_OFFSET_CAL_EN1 0 ++ ++/*MIC_OFFSET_CTRL2*/ ++#define MIC_OFFSET_CAL_GAIN 3 ++#define MIC_OFFSET_CAL_CHANNEL 1 ++#define MIC_OFFSET_CAL_EN_ONCE 0 ++ ++/*MIC1_OFFSET_STATU1*/ ++#define MIC1_OFFSET_CAL_DONE 7 ++#define MIC1_OFFSET_CAL_RUN_STA 6 ++#define MIC1_OFFSET_MSB 0 ++ ++/*MIC1_OFFSET_STATU2*/ ++#define MIC1_OFFSET_LSB 0 ++ ++/*MIC2_OFFSET_STATU1*/ ++#define MIC2_OFFSET_CAL_DONE 7 ++#define MIC2_OFFSET_CAL_RUN_STA 6 ++#define MIC2_OFFSET_MSB 0 ++ ++/*MIC2_OFFSET_STATU2*/ ++#define MIC2_OFFSET_LSB 0 ++ ++/*MIC3_OFFSET_STATU1*/ ++#define MIC3_OFFSET_CAL_DONE 7 ++#define MIC3_OFFSET_CAL_RUN_STA 6 ++#define MIC3_OFFSET_MSB 0 ++ ++/*MIC3_OFFSET_STATU2*/ ++#define MIC3_OFFSET_LSB 0 ++ ++/*MIC4_OFFSET_STATU1*/ ++#define MIC4_OFFSET_CAL_DONE 7 ++#define MIC4_OFFSET_CAL_RUN_STA 6 ++#define MIC4_OFFSET_MSB 0 ++ ++/*MIC4_OFFSET_STATU2*/ ++#define MIC4_OFFSET_LSB 0 ++ ++/*ANA_ADC1_CTRL1*/ ++#define ADC1_PGA_BYPASS 7 ++#define ADC1_PGA_BYP_RCM 6 ++#define ADC1_PGA_CTRL_RCM 4 ++#define ADC1_PGA_MUTE 3 ++#define ADC1_DSM_ENABLE 2 ++#define ADC1_PGA_ENABLE 1 ++#define ADC1_MICBIAS_EN 0 ++ ++/*ANA_ADC1_CTRL3*/ ++#define ADC1_ANA_CAL_EN 5 ++#define ADC1_SEL_OUT_EDGE 3 ++#define ADC1_DSM_DISABLE 2 ++#define ADC1_VREFP_DISABLE 1 ++#define ADC1_AAF_DISABLE 0 ++ ++/*ANA_ADC1_CTRL6*/ ++#define PGA_CTRL_TC 6 ++#define PGA_CTRL_RC 4 ++#define PGA_CTRL_I_LIN 2 ++#define PGA_CTRL_I_IN 0 ++ ++/*ANA_ADC1_CTRL7*/ ++#define PGA_CTRL_HI_Z 7 ++#define PGA_CTRL_SHORT_RF 6 ++#define PGA_CTRL_VCM_VG 4 ++#define PGA_CTRL_VCM_IN 0 ++ ++/*ANA_ADC2_CTRL1*/ ++#define ADC2_PGA_BYPASS 7 ++#define ADC2_PGA_BYP_RCM 6 ++#define ADC2_PGA_CTRL_RCM 4 ++#define ADC2_PGA_MUTE 3 ++#define ADC2_DSM_ENABLE 2 ++#define ADC2_PGA_ENABLE 1 ++#define ADC2_MICBIAS_EN 0 ++ ++/*ANA_ADC2_CTRL3*/ ++#define ADC2_ANA_CAL_EN 5 ++#define ADC2_SEL_OUT_EDGE 3 ++#define ADC2_DSM_DISABLE 2 ++#define ADC2_VREFP_DISABLE 1 ++#define ADC2_AAF_DISABLE 0 ++ ++/*ANA_ADC2_CTRL6*/ ++#define PGA_CTRL_IBOOST 7 ++#define PGA_CTRL_IQCTRL 6 ++#define PGA_CTRL_OABIAS 4 ++#define PGA_CTRL_CMLP_DIS 3 ++#define PGA_CTRL_PDB_RIN 2 ++#define PGA_CTRL_PEAKDET 0 ++ ++/*ANA_ADC2_CTRL7*/ ++#define AAF_LPMODE_EN 7 ++#define AAF_STG2_IB_SEL 4 ++#define AAFDSM_IB_DIV2 3 ++#define AAF_STG1_IB_SEL 0 ++ ++/*ANA_ADC3_CTRL1*/ ++#define ADC3_PGA_BYPASS 7 ++#define ADC3_PGA_BYP_RCM 6 ++#define ADC3_PGA_CTRL_RCM 4 ++#define ADC3_PGA_MUTE 3 ++#define ADC3_DSM_ENABLE 2 ++#define ADC3_PGA_ENABLE 1 ++#define ADC3_MICBIAS_EN 0 ++ ++/*ANA_ADC3_CTRL3*/ ++#define ADC3_ANA_CAL_EN 5 ++#define ADC3_INVERT_CLK 4 ++#define ADC3_SEL_OUT_EDGE 3 ++#define ADC3_DSM_DISABLE 2 ++#define ADC3_VREFP_DISABLE 1 ++#define ADC3_AAF_DISABLE 0 ++ ++/*ANA_ADC3_CTRL7*/ ++#define DSM_COMP_IB_SEL 6 ++#define DSM_OTA_CTRL 4 ++#define DSM_LPMODE 3 ++#define DSM_OTA_IB_SEL 0 ++ ++/*ANA_ADC4_CTRL1*/ ++#define ADC4_PGA_BYPASS 7 ++#define ADC4_PGA_BYP_RCM 6 ++#define ADC4_PGA_CTRL_RCM 4 ++#define ADC4_PGA_MUTE 3 ++#define ADC4_DSM_ENABLE 2 ++#define ADC4_PGA_ENABLE 1 ++#define ADC4_MICBIAS_EN 0 ++ ++/*ANA_ADC4_CTRL3*/ ++#define ADC4_ANA_CAL_EN 5 ++#define ADC4_SEL_OUT_EDGE 3 ++#define ADC4_DSM_DISABLE 2 ++#define ADC4_VREFP_DISABLE 1 ++#define ADC4_AAF_DISABLE 0 ++ ++/*ANA_ADC4_CTRL6*/ ++#define DSM_DEMOFF 5 ++#define DSM_EN_DITHER 4 ++#define DSM_VREFP_LPMODE 2 ++#define DSM_VREFP_OUTCTRL 0 ++ ++/*ANA_ADC4_CTRL7*/ ++#define CK8M_EN 5 ++#define OSC_EN 4 ++#define ADC4_CLK_GATING 3 ++#define ADC3_CLK_GATING 2 ++#define ADC2_CLK_GATING 1 ++#define ADC1_CLK_GATING 0 ++ ++/*GPIO_CFG1*/ ++#define GPIO2_SELECT 4 ++#define GPIO1_SELECT 0 ++ ++/*GPIO_CFG2*/ ++#define GPIO4_SELECT 4 ++#define GPIO3_SELECT 0 ++ ++/*GPIO_DAT*///order??? ++#define GPIO4_DAT 3 ++#define GPIO3_DAT 2 ++#define GPIO2_DAT 1 ++#define GPIO1_DAT 0 ++ ++/*GPIO_DRV*/ ++#define GPIO4_DRV 6 ++#define GPIO3_DRV 4 ++#define GPIO2_DRV 2 ++#define GPIO1_DRV 0 ++ ++/*GPIO_PULL*/ ++#define GPIO4_PULL 6 ++#define GPIO3_PULL 4 ++#define GPIO2_PULL 2 ++#define GPIO1_PULL 0 ++ ++/*GPIO_INT_CFG*/ ++#define GPIO4_EINT_CFG 6 ++#define GPIO3_EINT_CFG 4 ++#define GPIO2_EINT_CFG 2 ++#define GPIO1_EINT_CFG 0 ++ ++/*GPIO_INT_EN*///order??? ++#define GPIO4_EINT_EN 3 ++#define GPIO3_EINT_EN 2 ++#define GPIO2_EINT_EN 1 ++#define GPIO1_EINT_EN 0 ++ ++/*GPIO_INT_STATUS*///order??? ++#define GPIO4_EINT_STA 3 ++#define GPIO3_EINT_STA 2 ++#define GPIO2_EINT_STA 1 ++#define GPIO1_EINT_STA 0 ++ ++/*PRNG_CLK_CTRL*/ ++#define PRNG_CLK_EN 1 ++#define PRNG_CLK_POS 0 ++ ++/*** Some Config Value ***/ ++ ++//[SYSCLK_CTRL]: PLLCLK_SRC ++#define PLLCLK_SRC_MCLK 0 ++#define PLLCLK_SRC_BCLK 1 ++#define PLLCLK_SRC_GPIO2 2 ++#define PLLCLK_SRC_GPIO3 3 ++ ++//[SYSCLK_CTRL]: SYSCLK_SRC ++#define SYSCLK_SRC_MCLK 0 ++#define SYSCLK_SRC_PLL 1 ++ ++//I2S BCLK POLARITY Control ++#define BCLK_NORMAL_DRIVE_N_SAMPLE_P 0 ++#define BCLK_INVERT_DRIVE_P_SAMPLE_N 1 ++ ++//I2S LRCK POLARITY Control ++#define LRCK_LEFT_LOW_RIGHT_HIGH 0 ++#define LRCK_LEFT_HIGH_RIGHT_LOW 1 ++ ++//I2S Format Selection ++#define PCM_FORMAT 0 ++#define LEFT_JUSTIFIED_FORMAT 1 ++#define RIGHT_JUSTIFIED_FORMAT 2 ++ ++//I2S data protocol types ++ ++#define IS_ENCODING_MODE 0 ++ ++#endif ++ +--- /dev/null ++++ b/sound/soc/codecs/ac10x.h +@@ -0,0 +1,152 @@ ++/* ++ * ac10x.h ++ * ++ * (C) Copyright 2017-2018 ++ * Seeed Technology Co., Ltd. <www.seeedstudio.com> ++ * ++ * PeterYang <linsheng.yang@seeed.cc> ++ * ++ * (C) Copyright 2010-2017 ++ * Reuuimlla Technology Co., Ltd. <www.reuuimllatech.com> ++ * huangxin <huangxin@reuuimllatech.com> ++ * ++ * some simple description for this code ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of ++ * the License, or (at your option) any later version. ++ * ++ */ ++#ifndef __AC10X_H__ ++#define __AC10X_H__ ++ ++#define AC101_I2C_ID 4 ++#define _MASTER_AC108 0 ++#define _MASTER_AC101 1 ++#define _MASTER_MULTI_CODEC _MASTER_AC101 ++ ++/* enable headset detecting & headset button pressing */ ++#define CONFIG_AC101_SWITCH_DETECT ++ ++/* obsolete */ ++#define CONFIG_AC10X_TRIG_LOCK 0 ++ ++#ifdef AC101_DEBG ++ #define AC101_DBG(format,args...) printk("[AC101] %s() L%d " format, __func__, __LINE__, ##args) ++#else ++ #define AC101_DBG(...) ++#endif ++ ++#include <linux/version.h> ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,18,0) ++#define __NO_SND_SOC_CODEC_DRV 1 ++#else ++#define __NO_SND_SOC_CODEC_DRV 0 ++#endif ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ++#if __has_attribute(__fallthrough__) ++# define fallthrough __attribute__((__fallthrough__)) ++#else ++# define fallthrough do {} while (0) /* fallthrough */ ++#endif ++#endif ++ ++#if __NO_SND_SOC_CODEC_DRV ++#define codec component ++#define snd_soc_codec snd_soc_component ++#define snd_soc_codec_driver snd_soc_component_driver ++#define snd_soc_codec_get_drvdata snd_soc_component_get_drvdata ++#define snd_soc_codec_get_dapm snd_soc_component_get_dapm ++#define snd_soc_codec_get_bias_level snd_soc_component_get_bias_level ++#define snd_soc_kcontrol_codec snd_soc_kcontrol_component ++#define snd_soc_read snd_soc_component_read32 ++#define snd_soc_register_codec snd_soc_register_component ++#define snd_soc_unregister_codec snd_soc_unregister_component ++#define snd_soc_update_bits snd_soc_component_update_bits ++#define snd_soc_write snd_soc_component_write ++#define snd_soc_add_codec_controls snd_soc_add_component_controls ++#endif ++ ++ ++#ifdef CONFIG_AC101_SWITCH_DETECT ++enum headphone_mode_u { ++ HEADPHONE_IDLE, ++ FOUR_HEADPHONE_PLUGIN, ++ THREE_HEADPHONE_PLUGIN, ++}; ++#endif ++ ++struct ac10x_priv { ++ struct i2c_client *i2c[4]; ++ struct regmap* i2cmap[4]; ++ int codec_cnt; ++ unsigned sysclk; ++#define _FREQ_24_576K 24576000 ++#define _FREQ_22_579K 22579200 ++ unsigned mclk; /* master clock or aif_clock/aclk */ ++ int clk_id; ++ unsigned char i2s_mode; ++ unsigned char data_protocol; ++ struct delayed_work dlywork; ++ int tdm_chips_cnt; ++ int sysclk_en; ++ ++ /* member for ac101 .begin */ ++ struct snd_soc_codec *codec; ++ struct i2c_client *i2c101; ++ struct regmap* regmap101; ++ ++ struct mutex dac_mutex; ++ u8 dac_enable; ++ spinlock_t lock; ++ u8 aif1_clken; ++ ++ struct work_struct codec_resume; ++ struct gpio_desc* gpiod_spk_amp_gate; ++ ++ #ifdef CONFIG_AC101_SWITCH_DETECT ++ struct gpio_desc* gpiod_irq; ++ long irq; ++ volatile int irq_cntr; ++ volatile int pullout_cntr; ++ volatile int state; ++ ++ enum headphone_mode_u mode; ++ struct work_struct work_switch; ++ struct work_struct work_clear_irq; ++ ++ struct input_dev* inpdev; ++ #endif ++ /* member for ac101 .end */ ++}; ++ ++ ++/* AC101 DAI operations */ ++int ac101_audio_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai); ++void ac101_aif_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai); ++int ac101_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt); ++int ac101_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *codec_dai); ++int ac101_trigger(struct snd_pcm_substream *substream, int cmd, ++ struct snd_soc_dai *dai); ++int ac101_aif_mute(struct snd_soc_dai *codec_dai, int mute); ++ ++/* codec driver specific */ ++int ac101_codec_probe(struct snd_soc_codec *codec); ++int ac101_codec_remove(struct snd_soc_codec *codec); ++int ac101_codec_suspend(struct snd_soc_codec *codec); ++int ac101_codec_resume(struct snd_soc_codec *codec); ++int ac101_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level); ++ ++/* i2c device specific */ ++int ac101_probe(struct i2c_client *i2c, const struct i2c_device_id *id); ++void ac101_shutdown(struct i2c_client *i2c); ++int ac101_remove(struct i2c_client *i2c); ++ ++int ac10x_fill_regcache(struct device* dev, struct regmap* map); ++ ++#endif//__AC10X_H__ diff --git a/target/linux/starfive/patches-6.6/0080-ASoC-starfive-Add-SPDIF-and-PCM-driver.patch b/target/linux/starfive/patches-6.6/0080-ASoC-starfive-Add-SPDIF-and-PCM-driver.patch new file mode 100644 index 0000000000..f2fb206947 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0080-ASoC-starfive-Add-SPDIF-and-PCM-driver.patch @@ -0,0 +1,1169 @@ +From f03b7c834baef87e4f740e10a8bbcbfc57bd985a Mon Sep 17 00:00:00 2001 +From: Xingyu Wu <xingyu.wu@starfivetech.com> +Date: Thu, 15 Jun 2023 11:32:50 +0800 +Subject: [PATCH 080/116] ASoC: starfive: Add SPDIF and PCM driver + +Add SPDIF and SPDIF-PCM driver for StarFive JH7110. + +Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + sound/soc/starfive/Kconfig | 17 + + sound/soc/starfive/Makefile | 5 + + sound/soc/starfive/jh7110_spdif.c | 568 ++++++++++++++++++++++++++ + sound/soc/starfive/jh7110_spdif.h | 196 +++++++++ + sound/soc/starfive/jh7110_spdif_pcm.c | 339 +++++++++++++++ + 5 files changed, 1125 insertions(+) + create mode 100644 sound/soc/starfive/jh7110_spdif.c + create mode 100644 sound/soc/starfive/jh7110_spdif.h + create mode 100644 sound/soc/starfive/jh7110_spdif_pcm.c + +--- a/sound/soc/starfive/Kconfig ++++ b/sound/soc/starfive/Kconfig +@@ -16,6 +16,23 @@ config SND_SOC_JH7110_PWMDAC + Say Y or M if you want to add support for StarFive JH7110 + PWM-DAC driver. + ++config SND_SOC_JH7110_SPDIF ++ tristate "JH7110 SPDIF module" ++ depends on HAVE_CLK && SND_SOC_STARFIVE ++ select SND_SOC_GENERIC_DMAENGINE_PCM ++ select REGMAP_MMIO ++ help ++ Say Y or M if you want to add support for SPDIF driver of StarFive ++ JH7110 SoC. ++ ++config SND_SOC_JH7110_SPDIF_PCM ++ bool "PCM PIO extension for JH7110 SPDIF" ++ depends on SND_SOC_JH7110_SPDIF ++ default y if SND_SOC_JH7110_SPDIF ++ help ++ Say Y or N if you want to add a custom ALSA extension that registers ++ a PCM and uses PIO to transfer data. ++ + config SND_SOC_JH7110_TDM + tristate "JH7110 TDM device driver" + depends on HAVE_CLK && SND_SOC_STARFIVE +--- a/sound/soc/starfive/Makefile ++++ b/sound/soc/starfive/Makefile +@@ -1,3 +1,8 @@ + # StarFive Platform Support + obj-$(CONFIG_SND_SOC_JH7110_PWMDAC) += jh7110_pwmdac.o ++ ++obj-$(CONFIG_SND_SOC_JH7110_SPDIF) += spdif.o ++spdif-y := jh7110_spdif.o ++spdif-$(CONFIG_SND_SOC_JH7110_SPDIF_PCM) += jh7110_spdif_pcm.o ++ + obj-$(CONFIG_SND_SOC_JH7110_TDM) += jh7110_tdm.o +--- /dev/null ++++ b/sound/soc/starfive/jh7110_spdif.c +@@ -0,0 +1,568 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * SPDIF driver for the StarFive JH7110 SoC ++ * ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ */ ++ ++#include <linux/clk.h> ++#include <linux/init.h> ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/platform_device.h> ++#include <linux/pm_runtime.h> ++#include <linux/regmap.h> ++#include <linux/reset.h> ++#include <linux/slab.h> ++#include <sound/core.h> ++#include <sound/dmaengine_pcm.h> ++#include <sound/initval.h> ++#include <sound/pcm.h> ++#include <sound/pcm_params.h> ++#include <sound/soc.h> ++ ++#include "jh7110_spdif.h" ++ ++static irqreturn_t spdif_irq_handler(int irq, void *dev_id) ++{ ++ struct sf_spdif_dev *dev = dev_id; ++ bool irq_valid = false; ++ unsigned int intr; ++ unsigned int stat; ++ ++ regmap_read(dev->regmap, SPDIF_INT_REG, &intr); ++ regmap_read(dev->regmap, SPDIF_STAT_REG, &stat); ++ regmap_update_bits(dev->regmap, SPDIF_CTRL, SPDIF_MASK_ENABLE, 0); ++ regmap_update_bits(dev->regmap, SPDIF_INT_REG, SPDIF_INT_REG_BIT, 0); ++ ++ if ((stat & SPDIF_EMPTY_FLAG) || (stat & SPDIF_AEMPTY_FLAG)) { ++ sf_spdif_pcm_push_tx(dev); ++ irq_valid = true; ++ } ++ ++ if ((stat & SPDIF_FULL_FLAG) || (stat & SPDIF_AFULL_FLAG)) { ++ sf_spdif_pcm_pop_rx(dev); ++ irq_valid = true; ++ } ++ ++ if (stat & SPDIF_PARITY_FLAG) ++ irq_valid = true; ++ ++ if (stat & SPDIF_UNDERR_FLAG) ++ irq_valid = true; ++ ++ if (stat & SPDIF_OVRERR_FLAG) ++ irq_valid = true; ++ ++ if (stat & SPDIF_SYNCERR_FLAG) ++ irq_valid = true; ++ ++ if (stat & SPDIF_LOCK_FLAG) ++ irq_valid = true; ++ ++ if (stat & SPDIF_BEGIN_FLAG) ++ irq_valid = true; ++ ++ if (stat & SPDIF_RIGHT_LEFT) ++ irq_valid = true; ++ ++ regmap_update_bits(dev->regmap, SPDIF_CTRL, ++ SPDIF_MASK_ENABLE, SPDIF_MASK_ENABLE); ++ ++ if (irq_valid) ++ return IRQ_HANDLED; ++ else ++ return IRQ_NONE; ++} ++ ++static int sf_spdif_trigger(struct snd_pcm_substream *substream, int cmd, ++ struct snd_soc_dai *dai) ++{ ++ struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); ++ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; ++ ++ if (tx) { ++ /* tx mode */ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_TR_MODE, SPDIF_TR_MODE); ++ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_MASK_FIFO, SPDIF_EMPTY_MASK | SPDIF_AEMPTY_MASK); ++ } else { ++ /* rx mode */ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_TR_MODE, 0); ++ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_MASK_FIFO, SPDIF_FULL_MASK | SPDIF_AFULL_MASK); ++ } ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ /* clock recovery form the SPDIF data stream 0:clk_enable */ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_CLK_ENABLE, 0); ++ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_ENABLE, SPDIF_ENABLE); ++ break; ++ case SNDRV_PCM_TRIGGER_STOP: ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ /* clock recovery form the SPDIF data stream 1:power save mode */ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE); ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_ENABLE, 0); ++ break; ++ default: ++ dev_err(dai->dev, "%s L.%d cmd:%d\n", __func__, __LINE__, cmd); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int sf_spdif_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) ++{ ++ struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); ++ unsigned int channels = params_channels(params); ++ unsigned int rate = params_rate(params); ++ unsigned int format = params_format(params); ++ unsigned int tsamplerate; ++ unsigned int mclk; ++ unsigned int audio_root; ++ int ret; ++ ++ switch (channels) { ++ case 1: ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_CHANNEL_MODE, SPDIF_CHANNEL_MODE); ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_DUPLICATE, SPDIF_DUPLICATE); ++ spdif->channels = false; ++ break; ++ case 2: ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_CHANNEL_MODE, 0); ++ spdif->channels = true; ++ break; ++ default: ++ dev_err(dai->dev, "invalid channels number\n"); ++ return -EINVAL; ++ } ++ ++ switch (format) { ++ case SNDRV_PCM_FORMAT_S16_LE: ++ case SNDRV_PCM_FORMAT_S24_LE: ++ case SNDRV_PCM_FORMAT_S24_3LE: ++ case SNDRV_PCM_FORMAT_S32_LE: ++ break; ++ default: ++ dev_err(dai->dev, "invalid format\n"); ++ return -EINVAL; ++ } ++ ++ switch (rate) { ++ case 8000: ++ break; ++ case 11025: ++ audio_root = 148500000; ++ /* 11025 * 512 = 5644800 */ ++ /* But now pll2 is 1188m and mclk should be 5711539 closely. */ ++ mclk = 5711539; ++ break; ++ case 16000: ++ break; ++ case 22050: ++ audio_root = 148500000; ++ mclk = 11423077; ++ break; ++ default: ++ dev_err(dai->dev, "channel:%d sample rate:%d\n", channels, rate); ++ return -EINVAL; ++ } ++ ++ /* use mclk_inner clock from 1188m PLL2 will be better about 11k and 22k*/ ++ if ((rate == 11025) || (rate == 22050)) { ++ ret = clk_set_parent(spdif->mclk, spdif->mclk_inner); ++ if (ret) { ++ dev_err(dai->dev, ++ "failed to set parent to mclk_inner ret=%d\n", ret); ++ goto fail_ext; ++ } ++ ++ ret = clk_set_rate(spdif->audio_root, audio_root); ++ if (ret) { ++ dev_err(dai->dev, "failed to set audio_root rate :%d\n", ret); ++ goto fail_ext; ++ } ++ ++ ret = clk_set_rate(spdif->mclk_inner, mclk); ++ if (ret) { ++ dev_err(dai->dev, "failed to set mclk_inner rate :%d\n", ret); ++ goto fail_ext; ++ } ++ ++ mclk = clk_get_rate(spdif->mclk_inner); ++ } else { ++ ret = clk_set_parent(spdif->mclk, spdif->mclk_ext); ++ if (ret) { ++ dev_err(dai->dev, ++ "failed to set parent to mclk_ext ret=%d\n", ret); ++ goto fail_ext; ++ } ++ ++ mclk = clk_get_rate(spdif->mclk_ext); ++ } ++ ++ /* (FCLK)4096000/128=32000 */ ++ tsamplerate = (mclk / 128 + rate / 2) / rate - 1; ++ if (tsamplerate < 3) ++ tsamplerate = 3; ++ ++ /* transmission sample rate */ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, 0xFF, tsamplerate); ++ ++ return 0; ++ ++fail_ext: ++ return ret; ++} ++ ++static int sf_spdif_clks_get(struct platform_device *pdev, ++ struct sf_spdif_dev *spdif) ++{ ++ static struct clk_bulk_data clks[] = { ++ { .id = "apb" }, /* clock-names in dts file */ ++ { .id = "core" }, ++ { .id = "audroot" }, ++ { .id = "mclk_inner"}, ++ { .id = "mclk_ext"}, ++ { .id = "mclk"}, ++ }; ++ int ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(clks), clks); ++ ++ spdif->spdif_apb = clks[0].clk; ++ spdif->spdif_core = clks[1].clk; ++ spdif->audio_root = clks[2].clk; ++ spdif->mclk_inner = clks[3].clk; ++ spdif->mclk_ext = clks[4].clk; ++ spdif->mclk = clks[5].clk; ++ ++ return ret; ++} ++ ++static int sf_spdif_resets_get(struct platform_device *pdev, ++ struct sf_spdif_dev *spdif) ++{ ++ struct reset_control_bulk_data resets[] = { ++ { .id = "apb" }, ++ }; ++ int ret = devm_reset_control_bulk_get_exclusive(&pdev->dev, ARRAY_SIZE(resets), resets); ++ ++ if (ret) ++ return ret; ++ ++ spdif->rst_apb = resets[0].rstc; ++ ++ return 0; ++} ++ ++static int starfive_spdif_crg_enable(struct sf_spdif_dev *spdif, bool enable) ++{ ++ int ret; ++ ++ dev_dbg(spdif->dev, "starfive_spdif clk&rst %sable.\n", enable ? "en":"dis"); ++ if (enable) { ++ ret = clk_prepare_enable(spdif->spdif_apb); ++ if (ret) { ++ dev_err(spdif->dev, "failed to prepare enable spdif_apb\n"); ++ goto failed_apb_clk; ++ } ++ ++ ret = clk_prepare_enable(spdif->spdif_core); ++ if (ret) { ++ dev_err(spdif->dev, "failed to prepare enable spdif_core\n"); ++ goto failed_core_clk; ++ } ++ ++ ret = reset_control_deassert(spdif->rst_apb); ++ if (ret) { ++ dev_err(spdif->dev, "failed to deassert apb\n"); ++ goto failed_rst; ++ } ++ } else { ++ clk_disable_unprepare(spdif->spdif_core); ++ clk_disable_unprepare(spdif->spdif_apb); ++ } ++ ++ return 0; ++ ++failed_rst: ++ clk_disable_unprepare(spdif->spdif_core); ++failed_core_clk: ++ clk_disable_unprepare(spdif->spdif_apb); ++failed_apb_clk: ++ return ret; ++} ++ ++static int sf_spdif_dai_probe(struct snd_soc_dai *dai) ++{ ++ struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); ++ ++ pm_runtime_get_sync(spdif->dev); ++ ++ /* reset */ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_ENABLE | SPDIF_SFR_ENABLE | SPDIF_FIFO_ENABLE, 0); ++ ++ /* clear irq */ ++ regmap_update_bits(spdif->regmap, SPDIF_INT_REG, ++ SPDIF_INT_REG_BIT, 0); ++ ++ /* power save mode */ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE); ++ ++ /* power save mode */ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE); ++ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_PARITCHECK|SPDIF_VALIDITYCHECK|SPDIF_DUPLICATE, ++ SPDIF_PARITCHECK|SPDIF_VALIDITYCHECK|SPDIF_DUPLICATE); ++ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_SETPREAMBB, SPDIF_SETPREAMBB); ++ ++ regmap_update_bits(spdif->regmap, SPDIF_INT_REG, ++ BIT8TO20MASK<<SPDIF_PREAMBLEDEL, 0x3<<SPDIF_PREAMBLEDEL); ++ ++ regmap_update_bits(spdif->regmap, SPDIF_FIFO_CTRL, ++ ALLBITMASK, 0x20|(0x20<<SPDIF_AFULL_THRESHOLD)); ++ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_PARITYGEN, SPDIF_PARITYGEN); ++ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_MASK_ENABLE, SPDIF_MASK_ENABLE); ++ ++ /* APB access to FIFO enable, disable if use DMA/FIFO */ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_USE_FIFO_IF, 0); ++ ++ /* two channel */ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ SPDIF_CHANNEL_MODE, 0); ++ ++ pm_runtime_put_sync(spdif->dev); ++ return 0; ++} ++ ++static const struct snd_soc_dai_ops sf_spdif_dai_ops = { ++ .probe = sf_spdif_dai_probe, ++ .trigger = sf_spdif_trigger, ++ .hw_params = sf_spdif_hw_params, ++}; ++ ++#ifdef CONFIG_PM_SLEEP ++static int spdif_system_suspend(struct device *dev) ++{ ++ struct sf_spdif_dev *spdif = dev_get_drvdata(dev); ++ ++ /* save the register value */ ++ regmap_read(spdif->regmap, SPDIF_CTRL, &spdif->reg_spdif_ctrl); ++ regmap_read(spdif->regmap, SPDIF_INT_REG, &spdif->reg_spdif_int); ++ regmap_read(spdif->regmap, SPDIF_FIFO_CTRL, &spdif->reg_spdif_fifo_ctrl); ++ ++ return pm_runtime_force_suspend(dev); ++} ++ ++static int spdif_system_resume(struct device *dev) ++{ ++ struct sf_spdif_dev *spdif = dev_get_drvdata(dev); ++ int ret = pm_runtime_force_resume(dev); ++ ++ if (ret) ++ return ret; ++ ++ /* restore the register value */ ++ regmap_update_bits(spdif->regmap, SPDIF_CTRL, ++ ALLBITMASK, spdif->reg_spdif_ctrl); ++ regmap_update_bits(spdif->regmap, SPDIF_INT_REG, ++ ALLBITMASK, spdif->reg_spdif_int); ++ regmap_update_bits(spdif->regmap, SPDIF_FIFO_CTRL, ++ ALLBITMASK, spdif->reg_spdif_fifo_ctrl); ++ ++ return 0; ++} ++#endif ++ ++#ifdef CONFIG_PM ++static int spdif_runtime_suspend(struct device *dev) ++{ ++ struct sf_spdif_dev *spdif = dev_get_drvdata(dev); ++ ++ return starfive_spdif_crg_enable(spdif, false); ++} ++ ++static int spdif_runtime_resume(struct device *dev) ++{ ++ struct sf_spdif_dev *spdif = dev_get_drvdata(dev); ++ ++ return starfive_spdif_crg_enable(spdif, true); ++} ++#endif ++ ++static const struct dev_pm_ops spdif_pm_ops = { ++ SET_RUNTIME_PM_OPS(spdif_runtime_suspend, spdif_runtime_resume, NULL) ++ SET_SYSTEM_SLEEP_PM_OPS(spdif_system_suspend, spdif_system_resume) ++}; ++ ++#define SF_PCM_RATE_44100_192000 (SNDRV_PCM_RATE_44100 | \ ++ SNDRV_PCM_RATE_48000 | \ ++ SNDRV_PCM_RATE_96000 | \ ++ SNDRV_PCM_RATE_192000) ++ ++#define SF_PCM_RATE_8000_22050 (SNDRV_PCM_RATE_8000 | \ ++ SNDRV_PCM_RATE_11025 | \ ++ SNDRV_PCM_RATE_16000 | \ ++ SNDRV_PCM_RATE_22050) ++ ++static struct snd_soc_dai_driver sf_spdif_dai = { ++ .name = "spdif", ++ .id = 0, ++ .playback = { ++ .stream_name = "Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = SF_PCM_RATE_8000_22050, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | ++ SNDRV_PCM_FMTBIT_S24_LE | ++ SNDRV_PCM_FMTBIT_S24_3LE | ++ SNDRV_PCM_FMTBIT_S32_LE, ++ }, ++ .ops = &sf_spdif_dai_ops, ++ .symmetric_rate = 1, ++}; ++ ++static const struct snd_soc_component_driver sf_spdif_component = { ++ .name = "starfive-spdif", ++}; ++ ++static const struct regmap_config sf_spdif_regmap_config = { ++ .reg_bits = 32, ++ .reg_stride = 4, ++ .val_bits = 32, ++ .max_register = 0x200, ++}; ++ ++static int sf_spdif_probe(struct platform_device *pdev) ++{ ++ struct sf_spdif_dev *spdif; ++ struct resource *res; ++ void __iomem *base; ++ int ret; ++ int irq; ++ ++ spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL); ++ if (!spdif) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, spdif); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(base)) ++ return PTR_ERR(base); ++ ++ spdif->spdif_base = base; ++ spdif->regmap = devm_regmap_init_mmio(&pdev->dev, spdif->spdif_base, ++ &sf_spdif_regmap_config); ++ if (IS_ERR(spdif->regmap)) ++ return PTR_ERR(spdif->regmap); ++ ++ spdif->dev = &pdev->dev; ++ ++ ret = sf_spdif_clks_get(pdev, spdif); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to get audio clock\n"); ++ return ret; ++ } ++ ++ ret = sf_spdif_resets_get(pdev, spdif); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to get audio reset controls\n"); ++ return ret; ++ } ++ ++ ret = starfive_spdif_crg_enable(spdif, true); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to enable audio clock\n"); ++ return ret; ++ } ++ ++ spdif->fifo_th = 16; ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq >= 0) { ++ ret = devm_request_irq(&pdev->dev, irq, spdif_irq_handler, 0, ++ pdev->name, spdif); ++ if (ret < 0) { ++ dev_err(&pdev->dev, "failed to request irq\n"); ++ return ret; ++ } ++ } ++ ++ ret = devm_snd_soc_register_component(&pdev->dev, &sf_spdif_component, ++ &sf_spdif_dai, 1); ++ if (ret) ++ goto err_clk_disable; ++ ++ if (irq >= 0) { ++ ret = sf_spdif_pcm_register(pdev); ++ spdif->use_pio = true; ++ } else { ++ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, ++ 0); ++ spdif->use_pio = false; ++ } ++ ++ if (ret) ++ goto err_clk_disable; ++ ++ starfive_spdif_crg_enable(spdif, false); ++ pm_runtime_enable(&pdev->dev); ++ dev_dbg(&pdev->dev, "spdif register done.\n"); ++ ++ return 0; ++ ++err_clk_disable: ++ return ret; ++} ++ ++static const struct of_device_id sf_spdif_of_match[] = { ++ { .compatible = "starfive,jh7110-spdif", }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, sf_spdif_of_match); ++ ++static struct platform_driver sf_spdif_driver = { ++ .driver = { ++ .name = "starfive-spdif", ++ .of_match_table = sf_spdif_of_match, ++ .pm = &spdif_pm_ops, ++ }, ++ .probe = sf_spdif_probe, ++}; ++module_platform_driver(sf_spdif_driver); ++ ++MODULE_AUTHOR("curry.zhang <curry.zhang@starfive.com>"); ++MODULE_AUTHOR("Xingyu Wu <xingyu.wu@starfivetech.com>"); ++MODULE_DESCRIPTION("starfive SPDIF driver"); ++MODULE_LICENSE("GPL v2"); +--- /dev/null ++++ b/sound/soc/starfive/jh7110_spdif.h +@@ -0,0 +1,196 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * SPDIF driver for the StarFive JH7110 SoC ++ * ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ */ ++ ++#ifndef __SND_SOC_JH7110_SPDIF_H ++#define __SND_SOC_JH7110_SPDIF_H ++ ++#include <linux/clk.h> ++#include <linux/device.h> ++#include <linux/dmaengine.h> ++#include <linux/types.h> ++#include <sound/dmaengine_pcm.h> ++#include <sound/pcm.h> ++ ++#define SPDIF_CTRL 0x0 ++#define SPDIF_INT_REG 0x4 ++#define SPDIF_FIFO_CTRL 0x8 ++#define SPDIF_STAT_REG 0xC ++ ++#define SPDIF_FIFO_ADDR 0x100 ++#define DMAC_SPDIF_POLLING_LEN 256 ++ ++/* ctrl: sampled on the rising clock edge */ ++#define SPDIF_TSAMPLERATE 0 /* [SRATEW-1:0] */ ++/* 0:SFR reg reset to defualt value; auto set back to '1' after reset */ ++#define SPDIF_SFR_ENABLE (1<<8) ++/* 0:reset of SPDIF block, SRF bits are unchanged; 1:enables SPDIF module */ ++#define SPDIF_ENABLE (1<<9) ++/* 0:FIFO pointers are reset to zero,threshold levels for FIFO are unchaned; auto set back to 1 */ ++#define SPDIF_FIFO_ENABLE (1<<10) ++/* 1:blocked and the modules are in power save mode; 0:block feeds the modules */ ++#define SPDIF_CLK_ENABLE (1<<11) ++#define SPDIF_TR_MODE (1<<12) /* 0:rx; 1:tx */ ++/* 0:party bit rx in a sub-frame is repeated on the parity; 1:check on a parity error */ ++#define SPDIF_PARITCHECK (1<<13) ++/* ++ * 0:parity bit from FIFO is transmitted in sub-frame; ++ * 1:parity bit generated inside the core and added to a transmitted sub-frame ++ */ ++#define SPDIF_PARITYGEN (1<<14) ++/* 0:validity bit in frame isn't checked and all frame are written; 1:validity bit rx is checked */ ++#define SPDIF_VALIDITYCHECK (1<<15) ++#define SPDIF_CHANNEL_MODE (1<<16) /* 0:two-channel; 1:single-channel */ ++/* only tx -single-channel mode; 0:secondary channel; 1: left(primary) channel */ ++#define SPDIF_DUPLICATE (1<<17) ++/* ++ * only tx; ++ * 0:first preamble B after reset tx valid sub-frame; ++ * 1:first preamble B is tx after preambleddel(INT_REG) ++ */ ++#define SPDIF_SETPREAMBB (1<<18) ++/* 0:FIFO disabled ,APB accese FIFO; 1:FIFO enable, APB access to FIFO disable; */ ++#define SPDIF_USE_FIFO_IF (1<<19) ++#define SPDIF_PARITY_MASK (1<<21) ++#define SPDIF_UNDERR_MASK (1<<22) ++#define SPDIF_OVRERR_MASK (1<<23) ++#define SPDIF_EMPTY_MASK (1<<24) ++#define SPDIF_AEMPTY_MASK (1<<25) ++#define SPDIF_FULL_MASK (1<<26) ++#define SPDIF_AFULL_MASK (1<<27) ++#define SPDIF_SYNCERR_MASK (1<<28) ++#define SPDIF_LOCK_MASK (1<<29) ++#define SPDIF_BEGIN_MASK (1<<30) ++#define SPDIF_INTEREQ_MAKS (1<<31) ++ ++#define SPDIF_MASK_ENABLE (SPDIF_PARITY_MASK | SPDIF_UNDERR_MASK | \ ++ SPDIF_OVRERR_MASK | SPDIF_EMPTY_MASK | \ ++ SPDIF_AEMPTY_MASK | SPDIF_FULL_MASK | \ ++ SPDIF_AFULL_MASK | SPDIF_SYNCERR_MASK | \ ++ SPDIF_LOCK_MASK | SPDIF_BEGIN_MASK | \ ++ SPDIF_INTEREQ_MAKS) ++ ++#define SPDIF_MASK_FIFO (SPDIF_EMPTY_MASK | SPDIF_AEMPTY_MASK | \ ++ SPDIF_FULL_MASK | SPDIF_AFULL_MASK) ++ ++/* INT_REG */ ++#define SPDIF_RSAMPLERATE 0 /* [SRATEW-1:0] */ ++#define SPDIF_PREAMBLEDEL 8 /* [PDELAYW+7:8] first B delay */ ++#define SPDIF_PARITYO (1<<21) /* 0:clear parity error */ ++#define SPDIF_TDATA_UNDERR (1<<22) /* tx data underrun error;0:clear */ ++#define SPDIF_RDATA_OVRERR (1<<23) /* rx data overrun error; 0:clear */ ++#define SPDIF_FIFO_EMPTY (1<<24) /* empty; 0:clear */ ++#define SPDIF_FIOF_AEMPTY (1<<25) /* almost empty; 0:clear */ ++#define SPDIF_FIFO_FULL (1<<26) /* FIFO full; 0:clear */ ++#define SPDIF_FIFO_AFULL (1<<27) /* FIFO almost full; 0:clear */ ++#define SPDIF_SYNCERR (1<<28) /* sync error; 0:clear */ ++#define SPDIF_LOCK (1<<29) /* sync; 0:clear */ ++#define SPDIF_BLOCK_BEGIN (1<<30) /* new start block rx data */ ++ ++#define SPDIF_INT_REG_BIT (SPDIF_PARITYO | SPDIF_TDATA_UNDERR | \ ++ SPDIF_RDATA_OVRERR | SPDIF_FIFO_EMPTY | \ ++ SPDIF_FIOF_AEMPTY | SPDIF_FIFO_FULL | \ ++ SPDIF_FIFO_AFULL | SPDIF_SYNCERR | \ ++ SPDIF_LOCK | SPDIF_BLOCK_BEGIN) ++ ++#define SPDIF_ERROR_INT_STATUS (SPDIF_PARITYO | \ ++ SPDIF_TDATA_UNDERR | SPDIF_RDATA_OVRERR) ++#define SPDIF_FIFO_INT_STATUS (SPDIF_FIFO_EMPTY | SPDIF_FIOF_AEMPTY | \ ++ SPDIF_FIFO_FULL | SPDIF_FIFO_AFULL) ++ ++#define SPDIF_INT_PARITY_ERROR (-1) ++#define SPDIF_INT_TDATA_UNDERR (-2) ++#define SPDIF_INT_RDATA_OVRERR (-3) ++#define SPDIF_INT_FIFO_EMPTY 1 ++#define SPDIF_INT_FIFO_AEMPTY 2 ++#define SPDIF_INT_FIFO_FULL 3 ++#define SPDIF_INT_FIFO_AFULL 4 ++#define SPDIF_INT_SYNCERR (-4) ++#define SPDIF_INT_LOCK 5 /* reciever has become synchronized with input data stream */ ++#define SPDIF_INT_BLOCK_BEGIN 6 /* start a new block in recieve data, written into FIFO */ ++ ++/* FIFO_CTRL */ ++#define SPDIF_AEMPTY_THRESHOLD 0 /* [depth-1:0] */ ++#define SPDIF_AFULL_THRESHOLD 16 /* [depth+15:16] */ ++ ++/* STAT_REG */ ++#define SPDIF_FIFO_LEVEL (1<<0) ++#define SPDIF_PARITY_FLAG (1<<21) /* 1:error; 0:repeated */ ++#define SPDIF_UNDERR_FLAG (1<<22) /* 1:error */ ++#define SPDIF_OVRERR_FLAG (1<<23) /* 1:error */ ++#define SPDIF_EMPTY_FLAG (1<<24) /* 1:fifo empty */ ++#define SPDIF_AEMPTY_FLAG (1<<25) /* 1:fifo almost empty */ ++#define SPDIF_FULL_FLAG (1<<26) /* 1:fifo full */ ++#define SPDIF_AFULL_FLAG (1<<27) /* 1:fifo almost full */ ++#define SPDIF_SYNCERR_FLAG (1<<28) /* 1:rx sync error */ ++#define SPDIF_LOCK_FLAG (1<<29) /* 1:RX sync */ ++#define SPDIF_BEGIN_FLAG (1<<30) /* 1:start a new block */ ++/* 1:left channel received and tx into FIFO; 0:right channel received and tx into FIFO */ ++#define SPDIF_RIGHT_LEFT (1<<31) ++ ++#define BIT8TO20MASK 0x1FFF ++#define ALLBITMASK 0xFFFFFFFF ++ ++#define SPDIF_STAT (SPDIF_PARITY_FLAG | SPDIF_UNDERR_FLAG | \ ++ SPDIF_OVRERR_FLAG | SPDIF_EMPTY_FLAG | \ ++ SPDIF_AEMPTY_FLAG | SPDIF_FULL_FLAG | \ ++ SPDIF_AFULL_FLAG | SPDIF_SYNCERR_FLAG | \ ++ SPDIF_LOCK_FLAG | SPDIF_BEGIN_FLAG | \ ++ SPDIF_RIGHT_LEFT) ++struct sf_spdif_dev { ++ void __iomem *spdif_base; ++ struct regmap *regmap; ++ struct device *dev; ++ u32 fifo_th; ++ int active; ++ ++ /* data related to DMA transfers b/w i2s and DMAC */ ++ struct snd_dmaengine_dai_dma_data play_dma_data; ++ struct snd_dmaengine_dai_dma_data capture_dma_data; ++ ++ bool use_pio; ++ struct snd_pcm_substream __rcu *tx_substream; ++ struct snd_pcm_substream __rcu *rx_substream; ++ ++ unsigned int (*tx_fn)(struct sf_spdif_dev *dev, ++ struct snd_pcm_runtime *runtime, unsigned int tx_ptr, ++ bool *period_elapsed, snd_pcm_format_t format); ++ unsigned int (*rx_fn)(struct sf_spdif_dev *dev, ++ struct snd_pcm_runtime *runtime, unsigned int rx_ptr, ++ bool *period_elapsed, snd_pcm_format_t format); ++ ++ snd_pcm_format_t format; ++ bool channels; ++ unsigned int tx_ptr; ++ unsigned int rx_ptr; ++ struct clk *spdif_apb; ++ struct clk *spdif_core; ++ struct clk *audio_root; ++ struct clk *mclk_inner; ++ struct clk *mclk; ++ struct clk *mclk_ext; ++ struct reset_control *rst_apb; ++ unsigned int reg_spdif_ctrl; ++ unsigned int reg_spdif_int; ++ unsigned int reg_spdif_fifo_ctrl; ++ ++ struct snd_dmaengine_dai_dma_data dma_data; ++}; ++ ++#if IS_ENABLED(CONFIG_SND_SOC_JH7110_SPDIF_PCM) ++void sf_spdif_pcm_push_tx(struct sf_spdif_dev *dev); ++void sf_spdif_pcm_pop_rx(struct sf_spdif_dev *dev); ++int sf_spdif_pcm_register(struct platform_device *pdev); ++#else ++void sf_spdif_pcm_push_tx(struct sf_spdif_dev *dev) { } ++void sf_spdif_pcm_pop_rx(struct sf_spdif_dev *dev) { } ++int sf_spdif_pcm_register(struct platform_device *pdev) ++{ ++ return -EINVAL; ++} ++#endif ++ ++#endif /* __SND_SOC_JH7110_SPDIF_H */ +--- /dev/null ++++ b/sound/soc/starfive/jh7110_spdif_pcm.c +@@ -0,0 +1,339 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * SPDIF PCM driver for the StarFive JH7110 SoC ++ * ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ */ ++ ++#include <linux/io.h> ++#include <linux/rcupdate.h> ++#include <sound/pcm.h> ++#include <sound/pcm_params.h> ++ ++#include "jh7110_spdif.h" ++ ++#define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN) ++#define PERIOD_BYTES_MIN 4096 ++#define PERIODS_MIN 2 ++ ++static unsigned int sf_spdif_pcm_tx(struct sf_spdif_dev *dev, ++ struct snd_pcm_runtime *runtime, unsigned int tx_ptr, ++ bool *period_elapsed, snd_pcm_format_t format) ++{ ++ unsigned int period_pos = tx_ptr % runtime->period_size; ++ u32 data[2]; ++ int i; ++ ++ /* two- channel and signal-channel mode */ ++ if (dev->channels) { ++ const u16 (*p16)[2] = (void *)runtime->dma_area; ++ const u32 (*p32)[2] = (void *)runtime->dma_area; ++ ++ for (i = 0; i < dev->fifo_th; i++) { ++ if (format == SNDRV_PCM_FORMAT_S16_LE) { ++ data[0] = p16[tx_ptr][0]; ++ data[0] = data[0]<<8; ++ data[0] &= 0x00ffff00; ++ data[1] = p16[tx_ptr][1]; ++ data[1] = data[1]<<8; ++ data[1] &= 0x00ffff00; ++ } else if (format == SNDRV_PCM_FORMAT_S24_LE) { ++ data[0] = p32[tx_ptr][0]; ++ data[1] = p32[tx_ptr][1]; ++ ++ /* ++ * To adapt S24_3LE and ALSA pass parameter of S24_LE. ++ * operation of S24_LE should be same to S24_3LE. ++ * So it would wrong when playback S24_LE file. ++ * when want to playback S24_LE file, should add in there: ++ * data[0] = data[0]>>8; ++ * data[1] = data[1]>>8; ++ */ ++ ++ data[0] &= 0x00ffffff; ++ data[1] &= 0x00ffffff; ++ } else if (format == SNDRV_PCM_FORMAT_S24_3LE) { ++ data[0] = p32[tx_ptr][0]; ++ data[1] = p32[tx_ptr][1]; ++ data[0] &= 0x00ffffff; ++ data[1] &= 0x00ffffff; ++ } else if (format == SNDRV_PCM_FORMAT_S32_LE) { ++ data[0] = p32[tx_ptr][0]; ++ data[0] = data[0]>>8; ++ data[1] = p32[tx_ptr][1]; ++ data[1] = data[1]>>8; ++ } ++ ++ iowrite32(data[0], dev->spdif_base + SPDIF_FIFO_ADDR); ++ iowrite32(data[1], dev->spdif_base + SPDIF_FIFO_ADDR); ++ period_pos++; ++ if (++tx_ptr >= runtime->buffer_size) ++ tx_ptr = 0; ++ } ++ } else { ++ const u16 (*p16) = (void *)runtime->dma_area; ++ const u32 (*p32) = (void *)runtime->dma_area; ++ ++ for (i = 0; i < dev->fifo_th; i++) { ++ if (format == SNDRV_PCM_FORMAT_S16_LE) { ++ data[0] = p16[tx_ptr]; ++ data[0] = data[0]<<8; ++ data[0] &= 0x00ffff00; ++ } else if (format == SNDRV_PCM_FORMAT_S24_LE || ++ format == SNDRV_PCM_FORMAT_S24_3LE) { ++ data[0] = p32[tx_ptr]; ++ data[0] &= 0x00ffffff; ++ } else if (format == SNDRV_PCM_FORMAT_S32_LE) { ++ data[0] = p32[tx_ptr]; ++ data[0] = data[0]>>8; ++ } ++ ++ iowrite32(data[0], dev->spdif_base + SPDIF_FIFO_ADDR); ++ period_pos++; ++ if (++tx_ptr >= runtime->buffer_size) ++ tx_ptr = 0; ++ } ++ } ++ ++ *period_elapsed = period_pos >= runtime->period_size; ++ return tx_ptr; ++} ++ ++static unsigned int sf_spdif_pcm_rx(struct sf_spdif_dev *dev, ++ struct snd_pcm_runtime *runtime, unsigned int rx_ptr, ++ bool *period_elapsed, snd_pcm_format_t format) ++{ ++ u16 (*p16)[2] = (void *)runtime->dma_area; ++ u32 (*p32)[2] = (void *)runtime->dma_area; ++ unsigned int period_pos = rx_ptr % runtime->period_size; ++ u32 data[2]; ++ int i; ++ ++ for (i = 0; i < dev->fifo_th; i++) { ++ data[0] = ioread32(dev->spdif_base + SPDIF_FIFO_ADDR); ++ data[1] = ioread32(dev->spdif_base + SPDIF_FIFO_ADDR); ++ if (format == SNDRV_PCM_FORMAT_S16_LE) { ++ p16[rx_ptr][0] = data[0]>>8; ++ p16[rx_ptr][1] = data[1]>>8; ++ } else if (format == SNDRV_PCM_FORMAT_S24_LE) { ++ p32[rx_ptr][0] = data[0]; ++ p32[rx_ptr][1] = data[1]; ++ } else if (format == SNDRV_PCM_FORMAT_S32_LE) { ++ p32[rx_ptr][0] = data[0]<<8; ++ p32[rx_ptr][1] = data[1]<<8; ++ } ++ ++ period_pos++; ++ if (++rx_ptr >= runtime->buffer_size) ++ rx_ptr = 0; ++ } ++ ++ *period_elapsed = period_pos >= runtime->period_size; ++ return rx_ptr; ++} ++ ++static const struct snd_pcm_hardware sf_pcm_hardware = { ++ .info = SNDRV_PCM_INFO_INTERLEAVED | ++ SNDRV_PCM_INFO_MMAP | ++ SNDRV_PCM_INFO_MMAP_VALID | ++ SNDRV_PCM_INFO_BLOCK_TRANSFER | ++ SNDRV_PCM_INFO_PAUSE | ++ SNDRV_PCM_INFO_RESUME, ++ .rates = SNDRV_PCM_RATE_8000 | ++ SNDRV_PCM_RATE_11025 | ++ SNDRV_PCM_RATE_16000 | ++ SNDRV_PCM_RATE_22050 | ++ SNDRV_PCM_RATE_32000 | ++ SNDRV_PCM_RATE_44100 | ++ SNDRV_PCM_RATE_48000, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | ++ SNDRV_PCM_FMTBIT_S24_LE | ++ SNDRV_PCM_FMTBIT_S24_3LE | ++ SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 1, ++ .channels_max = 2, ++ .buffer_bytes_max = BUFFER_BYTES_MAX, ++ .period_bytes_min = PERIOD_BYTES_MIN, ++ .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, ++ .periods_min = PERIODS_MIN, ++ .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, ++ .fifo_size = 16, ++}; ++ ++static void sf_spdif_pcm_transfer(struct sf_spdif_dev *dev, bool push) ++{ ++ struct snd_pcm_substream *substream; ++ bool active, period_elapsed; ++ ++ rcu_read_lock(); ++ if (push) ++ substream = rcu_dereference(dev->tx_substream); ++ else ++ substream = rcu_dereference(dev->rx_substream); ++ ++ active = substream && snd_pcm_running(substream); ++ if (active) { ++ unsigned int ptr; ++ unsigned int new_ptr; ++ ++ if (push) { ++ ptr = READ_ONCE(dev->tx_ptr); ++ new_ptr = dev->tx_fn(dev, substream->runtime, ptr, ++ &period_elapsed, dev->format); ++ cmpxchg(&dev->tx_ptr, ptr, new_ptr); ++ } else { ++ ptr = READ_ONCE(dev->rx_ptr); ++ new_ptr = dev->rx_fn(dev, substream->runtime, ptr, ++ &period_elapsed, dev->format); ++ cmpxchg(&dev->rx_ptr, ptr, new_ptr); ++ } ++ ++ if (period_elapsed) ++ snd_pcm_period_elapsed(substream); ++ } ++ rcu_read_unlock(); ++} ++ ++void sf_spdif_pcm_push_tx(struct sf_spdif_dev *dev) ++{ ++ sf_spdif_pcm_transfer(dev, true); ++} ++ ++void sf_spdif_pcm_pop_rx(struct sf_spdif_dev *dev) ++{ ++ sf_spdif_pcm_transfer(dev, false); ++} ++ ++static int sf_pcm_open(struct snd_soc_component *component, ++ struct snd_pcm_substream *substream) ++{ ++ struct snd_pcm_runtime *runtime = substream->runtime; ++ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); ++ struct sf_spdif_dev *dev = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); ++ ++ snd_soc_set_runtime_hwparams(substream, &sf_pcm_hardware); ++ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); ++ runtime->private_data = dev; ++ ++ return 0; ++} ++ ++static int sf_pcm_close(struct snd_soc_component *component, ++ struct snd_pcm_substream *substream) ++{ ++ synchronize_rcu(); ++ return 0; ++} ++ ++static int sf_pcm_hw_params(struct snd_soc_component *component, ++ struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *hw_params) ++{ ++ struct snd_pcm_runtime *runtime = substream->runtime; ++ struct sf_spdif_dev *dev = runtime->private_data; ++ ++ switch (params_channels(hw_params)) { ++ case 1: ++ case 2: ++ break; ++ default: ++ dev_err(dev->dev, "invalid channels number\n"); ++ return -EINVAL; ++ } ++ ++ dev->format = params_format(hw_params); ++ switch (dev->format) { ++ case SNDRV_PCM_FORMAT_S16_LE: ++ case SNDRV_PCM_FORMAT_S24_LE: ++ case SNDRV_PCM_FORMAT_S24_3LE: ++ case SNDRV_PCM_FORMAT_S32_LE: ++ break; ++ default: ++ dev_err(dev->dev, "invalid format\n"); ++ return -EINVAL; ++ } ++ ++ dev->tx_fn = sf_spdif_pcm_tx; ++ dev->rx_fn = sf_spdif_pcm_rx; ++ ++ return 0; ++} ++ ++static int sf_pcm_trigger(struct snd_soc_component *component, ++ struct snd_pcm_substream *substream, int cmd) ++{ ++ struct snd_pcm_runtime *runtime = substream->runtime; ++ struct sf_spdif_dev *dev = runtime->private_data; ++ int ret = 0; ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ WRITE_ONCE(dev->tx_ptr, 0); ++ rcu_assign_pointer(dev->tx_substream, substream); ++ } else { ++ WRITE_ONCE(dev->rx_ptr, 0); ++ rcu_assign_pointer(dev->rx_substream, substream); ++ } ++ break; ++ case SNDRV_PCM_TRIGGER_STOP: ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ++ rcu_assign_pointer(dev->tx_substream, NULL); ++ else ++ rcu_assign_pointer(dev->rx_substream, NULL); ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static snd_pcm_uframes_t sf_pcm_pointer(struct snd_soc_component *component, ++ struct snd_pcm_substream *substream) ++{ ++ struct snd_pcm_runtime *runtime = substream->runtime; ++ struct sf_spdif_dev *dev = runtime->private_data; ++ snd_pcm_uframes_t pos; ++ ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ++ pos = READ_ONCE(dev->tx_ptr); ++ else ++ pos = READ_ONCE(dev->rx_ptr); ++ ++ return pos < runtime->buffer_size ? pos : 0; ++} ++ ++static int sf_pcm_new(struct snd_soc_component *component, ++ struct snd_soc_pcm_runtime *rtd) ++{ ++ size_t size = sf_pcm_hardware.buffer_bytes_max; ++ ++ snd_pcm_set_managed_buffer_all(rtd->pcm, ++ SNDRV_DMA_TYPE_CONTINUOUS, ++ NULL, size, size); ++ ++ return 0; ++} ++ ++static const struct snd_soc_component_driver sf_pcm_component = { ++ .open = sf_pcm_open, ++ .close = sf_pcm_close, ++ .hw_params = sf_pcm_hw_params, ++ .trigger = sf_pcm_trigger, ++ .pointer = sf_pcm_pointer, ++ .pcm_construct = sf_pcm_new, ++}; ++ ++int sf_spdif_pcm_register(struct platform_device *pdev) ++{ ++ return devm_snd_soc_register_component(&pdev->dev, &sf_pcm_component, ++ NULL, 0); ++} diff --git a/target/linux/starfive/patches-6.6/0081-ASoC-starfive-Add-JH7110-PDM-driver.patch b/target/linux/starfive/patches-6.6/0081-ASoC-starfive-Add-JH7110-PDM-driver.patch new file mode 100644 index 0000000000..006760d754 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0081-ASoC-starfive-Add-JH7110-PDM-driver.patch @@ -0,0 +1,537 @@ +From 9c4858f9fe4d8f8fe5cf347b3ca727016b7ba492 Mon Sep 17 00:00:00 2001 +From: Walker Chen <walker.chen@starfivetech.com> +Date: Tue, 20 Jun 2023 15:57:53 +0800 +Subject: [PATCH 081/116] ASoC: starfive: Add JH7110 PDM driver + +Add pdm driver support for the StarFive JH7110 SoC. + +Signed-off-by: Walker Chen <walker.chen@starfivetech.com> +--- + sound/soc/starfive/Kconfig | 8 + + sound/soc/starfive/Makefile | 2 + + sound/soc/starfive/jh7110_pdm.c | 493 ++++++++++++++++++++++++++++++++ + 3 files changed, 503 insertions(+) + create mode 100644 sound/soc/starfive/jh7110_pdm.c + +--- a/sound/soc/starfive/Kconfig ++++ b/sound/soc/starfive/Kconfig +@@ -7,6 +7,14 @@ config SND_SOC_STARFIVE + the Starfive SoCs' Audio interfaces. You will also need to + select the audio interfaces to support below. + ++config SND_SOC_JH7110_PDM ++ tristate "JH7110 PDM device driver" ++ depends on HAVE_CLK && SND_SOC_STARFIVE ++ select SND_SOC_JH7110_I2S ++ select REGMAP_MMIO ++ help ++ Say Y or M if you want to add support for StarFive pdm driver. ++ + config SND_SOC_JH7110_PWMDAC + tristate "JH7110 PWM-DAC device driver" + depends on HAVE_CLK && SND_SOC_STARFIVE +--- a/sound/soc/starfive/Makefile ++++ b/sound/soc/starfive/Makefile +@@ -1,4 +1,6 @@ + # StarFive Platform Support ++obj-$(CONFIG_SND_SOC_JH7110_PDM) += jh7110_pdm.o ++ + obj-$(CONFIG_SND_SOC_JH7110_PWMDAC) += jh7110_pwmdac.o + + obj-$(CONFIG_SND_SOC_JH7110_SPDIF) += spdif.o +--- /dev/null ++++ b/sound/soc/starfive/jh7110_pdm.c +@@ -0,0 +1,493 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * PDM driver for the StarFive JH7110 SoC ++ * ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ */ ++#include <linux/clk.h> ++#include <linux/device.h> ++#include <linux/dmaengine.h> ++#include <linux/reset.h> ++#include <linux/module.h> ++#include <linux/of_irq.h> ++#include <linux/of_platform.h> ++#include <linux/regmap.h> ++#include <linux/pm_runtime.h> ++#include <linux/types.h> ++#include <sound/dmaengine_pcm.h> ++#include <sound/initval.h> ++#include <sound/pcm.h> ++#include <sound/pcm_params.h> ++#include <sound/soc.h> ++#include <sound/soc-dai.h> ++#include <sound/tlv.h> ++ ++#define PDM_DMIC_CTRL0 0x00 ++#define PDM_DC_SCALE0 0x04 ++#define PDM_DMIC_CTRL1 0x10 ++#define PDM_DC_SCALE1 0x14 ++ ++/* PDM CTRL OFFSET */ ++#define PDM_DMIC_MSB_SHIFT 1 ++#define PDM_DMIC_MSB_MASK (0x7 << PDM_DMIC_MSB_SHIFT) ++#define PDM_DMIC_VOL_SHIFT 16 ++#define PDM_DMIC_VOL_MASK (0x3f << PDM_DMIC_VOL_SHIFT) ++#define PDM_VOL_DB_MUTE (0x3f << PDM_DMIC_VOL_SHIFT) ++#define PDM_VOL_DB_MAX 0 ++ ++#define PDM_DMIC_RVOL_MASK BIT(22) ++#define PDM_DMIC_LVOL_MASK BIT(23) ++#define PDM_DMIC_I2S_SLAVE BIT(24) ++#define PDM_DMIC_HPF_EN BIT(28) ++#define PDM_DMIC_FASTMODE_MASK BIT(29) ++#define PDM_DMIC_DC_BYPASS_MASK BIT(30) ++#define PDM_SW_RST_MASK BIT(31) ++#define PDM_SW_RST_RELEASE BIT(31) ++ ++/* PDM SCALE OFFSET */ ++#define DMIC_DCOFF3_SHIFT 24 ++#define DMIC_DCOFF2_SHIFT 16 ++#define DMIC_DCOFF1_SHIFT 8 ++ ++#define DMIC_DCOFF3_MASK (0xf << DMIC_DCOFF3_SHIFT) ++#define DMIC_DCOFF3_VAL (0xc << DMIC_DCOFF3_SHIFT) ++#define DMIC_DCOFF1_MASK (0xff << DMIC_DCOFF1_SHIFT) ++#define DMIC_DCOFF1_VAL (0x5 << DMIC_DCOFF1_SHIFT) ++#define DMIC_SCALE_MASK 0x3f ++#define DMIC_SCALE_DEF_VAL 0x8 ++ ++enum PDM_MSB_SHIFT { ++ PDM_MSB_SHIFT_NONE = 0, ++ PDM_MSB_SHIFT_1, ++ PDM_MSB_SHIFT_2, ++ PDM_MSB_SHIFT_3, ++ PDM_MSB_SHIFT_4, ++ PDM_MSB_SHIFT_5, ++ PDM_MSB_SHIFT_6, ++ PDM_MSB_SHIFT_7, ++}; ++ ++struct sf_pdm { ++ struct regmap *pdm_map; ++ struct device *dev; ++ struct clk *clk_pdm_apb; ++ struct clk *clk_pdm_mclk; ++ struct clk *clk_mclk; ++ struct clk *clk_mclk_ext; ++ struct reset_control *rst_pdm_dmic; ++ struct reset_control *rst_pdm_apb; ++ unsigned char flag_first; ++ unsigned int saved_ctrl0; ++ unsigned int saved_scale0; ++}; ++ ++static const DECLARE_TLV_DB_SCALE(volume_tlv, -9450, 150, 0); ++ ++static const struct snd_kcontrol_new sf_pdm_snd_controls[] = { ++ SOC_SINGLE("DC compensation Control", PDM_DMIC_CTRL0, 30, 1, 0), ++ SOC_SINGLE("High Pass Filter Control", PDM_DMIC_CTRL0, 28, 1, 0), ++ SOC_SINGLE("Left Channel Volume Control", PDM_DMIC_CTRL0, 23, 1, 0), ++ SOC_SINGLE("Right Channel Volume Control", PDM_DMIC_CTRL0, 22, 1, 0), ++ SOC_SINGLE_TLV("Volume", PDM_DMIC_CTRL0, 16, 0x3F, 1, volume_tlv), ++ SOC_SINGLE("Data MSB Shift", PDM_DMIC_CTRL0, 1, 7, 0), ++ SOC_SINGLE("SCALE", PDM_DC_SCALE0, 0, 0x3F, 0), ++ SOC_SINGLE("DC offset", PDM_DC_SCALE0, 8, 0xFFFFF, 0), ++}; ++ ++static void sf_pdm_enable(struct regmap *map) ++{ ++ /* Left and Right Channel Volume Control Enable */ ++ regmap_update_bits(map, PDM_DMIC_CTRL0, PDM_DMIC_RVOL_MASK, 0); ++ regmap_update_bits(map, PDM_DMIC_CTRL0, PDM_DMIC_LVOL_MASK, 0); ++} ++ ++static void sf_pdm_disable(struct regmap *map) ++{ ++ /* Left and Right Channel Volume Control Disable */ ++ regmap_update_bits(map, PDM_DMIC_CTRL0, ++ PDM_DMIC_RVOL_MASK, PDM_DMIC_RVOL_MASK); ++ regmap_update_bits(map, PDM_DMIC_CTRL0, ++ PDM_DMIC_LVOL_MASK, PDM_DMIC_LVOL_MASK); ++} ++ ++static int sf_pdm_trigger(struct snd_pcm_substream *substream, int cmd, ++ struct snd_soc_dai *dai) ++{ ++ struct sf_pdm *priv = snd_soc_dai_get_drvdata(dai); ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ if (priv->flag_first) { ++ priv->flag_first = 0; ++ mdelay(200); ++ } ++ ++ sf_pdm_enable(priv->pdm_map); ++ return 0; ++ ++ case SNDRV_PCM_TRIGGER_STOP: ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ sf_pdm_disable(priv->pdm_map); ++ return 0; ++ ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int sf_pdm_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct sf_pdm *priv = snd_soc_dai_get_drvdata(dai); ++ unsigned int sample_rate; ++ unsigned int data_width; ++ int ret; ++ const int pdm_mul = 128; ++ ++ sample_rate = params_rate(params); ++ switch (sample_rate) { ++ case 8000: ++ case 11025: ++ case 16000: ++ break; ++ default: ++ dev_err(priv->dev, "can't support sample rate:%d\n", sample_rate); ++ return -EINVAL; ++ } ++ ++ data_width = params_width(params); ++ switch (data_width) { ++ case 16: ++ case 32: ++ break; ++ default: ++ dev_err(priv->dev, "can't support bit width %d\n", data_width); ++ return -EINVAL; ++ } ++ ++ /* set pdm_mclk, PDM MCLK = 128 * LRCLK */ ++ ret = clk_set_rate(priv->clk_pdm_mclk, pdm_mul * sample_rate); ++ if (ret) { ++ dev_err(priv->dev, "Can't set pdm_mclk: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct snd_soc_dai_ops sf_pdm_dai_ops = { ++ .trigger = sf_pdm_trigger, ++ .hw_params = sf_pdm_hw_params, ++}; ++ ++static void sf_pdm_module_init(struct sf_pdm *priv) ++{ ++ /* Reset */ ++ regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0, ++ PDM_SW_RST_MASK, 0x00); ++ regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0, ++ PDM_SW_RST_MASK, PDM_SW_RST_RELEASE); ++ ++ /* Make sure the device is initially disabled */ ++ sf_pdm_disable(priv->pdm_map); ++ ++ /* MUTE */ ++ regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0, ++ PDM_DMIC_VOL_MASK, PDM_VOL_DB_MUTE); ++ ++ /* UNMUTE */ ++ regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0, ++ PDM_DMIC_VOL_MASK, PDM_VOL_DB_MAX); ++ ++ /* enable high pass filter */ ++ regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0, ++ PDM_DMIC_HPF_EN, PDM_DMIC_HPF_EN); ++ ++ /* PDM work as slave mode */ ++ regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0, ++ PDM_DMIC_I2S_SLAVE, PDM_DMIC_I2S_SLAVE); ++ ++ /* disable fast mode */ ++ regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0, ++ PDM_DMIC_FASTMODE_MASK, 0); ++ ++ /* dmic msb shift 0 */ ++ regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0, ++ PDM_DMIC_MSB_MASK, 0); ++ ++ /* scale: 0x8 */ ++ regmap_update_bits(priv->pdm_map, PDM_DC_SCALE0, ++ DMIC_SCALE_MASK, DMIC_SCALE_DEF_VAL); ++ ++ regmap_update_bits(priv->pdm_map, PDM_DC_SCALE0, ++ DMIC_DCOFF1_MASK, DMIC_DCOFF1_VAL); ++ ++ regmap_update_bits(priv->pdm_map, PDM_DC_SCALE0, ++ DMIC_DCOFF3_MASK, DMIC_DCOFF3_VAL); ++ ++ /* scale: 0x3f */ ++ regmap_update_bits(priv->pdm_map, PDM_DC_SCALE0, ++ DMIC_SCALE_MASK, DMIC_SCALE_MASK); ++ ++ /* dmic msb shift 2 */ ++ regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0, ++ PDM_DMIC_MSB_MASK, PDM_MSB_SHIFT_4); ++} ++ ++#define SF_PDM_RATES (SNDRV_PCM_RATE_8000 | \ ++ SNDRV_PCM_RATE_11025 | \ ++ SNDRV_PCM_RATE_16000) ++ ++#define SF_PDM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ ++ SNDRV_PCM_FMTBIT_S32_LE) ++ ++static struct snd_soc_dai_driver sf_pdm_dai_drv = { ++ .name = "PDM", ++ .id = 0, ++ .capture = { ++ .stream_name = "Capture", ++ .channels_min = 2, ++ .channels_max = 2, ++ .rates = SF_PDM_RATES, ++ .formats = SF_PDM_FORMATS, ++ }, ++ .ops = &sf_pdm_dai_ops, ++ .symmetric_rate = 1, ++}; ++ ++static int sf_pdm_component_probe(struct snd_soc_component *component) ++{ ++ struct sf_pdm *priv = snd_soc_component_get_drvdata(component); ++ ++ snd_soc_component_init_regmap(component, priv->pdm_map); ++ snd_soc_add_component_controls(component, sf_pdm_snd_controls, ++ ARRAY_SIZE(sf_pdm_snd_controls)); ++ ++ return 0; ++} ++ ++static int sf_pdm_clock_enable(struct sf_pdm *priv) ++{ ++ int ret; ++ ++ ret = clk_prepare_enable(priv->clk_pdm_mclk); ++ if (ret) { ++ dev_err(priv->dev, "failed to prepare enable clk_pdm_mclk\n"); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(priv->clk_pdm_apb); ++ if (ret) { ++ dev_err(priv->dev, "failed to prepare enable clk_pdm_apb\n"); ++ goto disable_pdm_mclk; ++ } ++ ++ ret = reset_control_deassert(priv->rst_pdm_dmic); ++ if (ret) { ++ dev_err(priv->dev, "failed to deassert pdm_dmic\n"); ++ goto disable_pdm_apb; ++ } ++ ++ ret = reset_control_deassert(priv->rst_pdm_apb); ++ if (ret) { ++ dev_err(priv->dev, "failed to deassert pdm_apb\n"); ++ goto disable_pdm_apb; ++ } ++ ++ ret = clk_set_parent(priv->clk_mclk, priv->clk_mclk_ext); ++ if (ret) { ++ dev_err(priv->dev, "failed to set parent clk_mclk ret=%d\n", ret); ++ goto disable_pdm_apb; ++ } ++ ++ return 0; ++ ++disable_pdm_apb: ++ clk_disable_unprepare(priv->clk_pdm_apb); ++disable_pdm_mclk: ++ clk_disable_unprepare(priv->clk_pdm_mclk); ++ ++ return ret; ++} ++ ++#ifdef CONFIG_PM ++static int sf_pdm_runtime_suspend(struct device *dev) ++{ ++ struct sf_pdm *priv = dev_get_drvdata(dev); ++ ++ clk_disable_unprepare(priv->clk_pdm_apb); ++ clk_disable_unprepare(priv->clk_pdm_mclk); ++ ++ return 0; ++} ++ ++static int sf_pdm_runtime_resume(struct device *dev) ++{ ++ struct sf_pdm *priv = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = sf_pdm_clock_enable(priv); ++ if (!ret) ++ sf_pdm_module_init(priv); ++ ++ return ret; ++} ++#endif ++ ++#ifdef CONFIG_PM_SLEEP ++static int sf_pdm_suspend(struct snd_soc_component *component) ++{ ++ return pm_runtime_force_suspend(component->dev); ++} ++ ++static int sf_pdm_resume(struct snd_soc_component *component) ++{ ++ return pm_runtime_force_resume(component->dev); ++} ++ ++#else ++#define sf_pdm_suspend NULL ++#define sf_pdm_resume NULL ++#endif ++ ++static const struct snd_soc_component_driver sf_pdm_component_drv = { ++ .name = "jh7110-pdm", ++ .probe = sf_pdm_component_probe, ++ .suspend = sf_pdm_suspend, ++ .resume = sf_pdm_resume, ++}; ++ ++static const struct regmap_config sf_pdm_regmap_cfg = { ++ .reg_bits = 32, ++ .val_bits = 32, ++ .reg_stride = 4, ++ .max_register = 0x20, ++}; ++ ++static int sf_pdm_clock_get(struct platform_device *pdev, struct sf_pdm *priv) ++{ ++ int ret; ++ ++ static struct clk_bulk_data clks[] = { ++ { .id = "pdm_mclk" }, ++ { .id = "pdm_apb" }, ++ { .id = "clk_mclk" }, ++ { .id = "mclk_ext" }, ++ }; ++ ++ ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(clks), clks); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to get pdm clocks\n"); ++ goto exit; ++ } ++ ++ priv->clk_pdm_mclk = clks[0].clk; ++ priv->clk_pdm_apb = clks[1].clk; ++ priv->clk_mclk = clks[2].clk; ++ priv->clk_mclk_ext = clks[3].clk; ++ ++ priv->rst_pdm_dmic = devm_reset_control_get_exclusive(&pdev->dev, "pdm_dmic"); ++ if (IS_ERR(priv->rst_pdm_dmic)) { ++ dev_err(&pdev->dev, "failed to get pdm_dmic reset control\n"); ++ ret = PTR_ERR(priv->rst_pdm_dmic); ++ goto exit; ++ } ++ ++ priv->rst_pdm_apb = devm_reset_control_get_exclusive(&pdev->dev, "pdm_apb"); ++ if (IS_ERR(priv->rst_pdm_apb)) { ++ dev_err(&pdev->dev, "failed to get pdm_apb reset control\n"); ++ ret = PTR_ERR(priv->rst_pdm_apb); ++ goto exit; ++ } ++ ++ /* ++ * pdm clock must always be enabled as hardware issue that ++ * no data in the first 4 seconds of the first recording ++ */ ++ ret = sf_pdm_clock_enable(priv); ++ ++exit: ++ return ret; ++} ++ ++static int sf_pdm_probe(struct platform_device *pdev) ++{ ++ struct sf_pdm *priv; ++ struct resource *res; ++ void __iomem *regs; ++ int ret; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ platform_set_drvdata(pdev, priv); ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pdm"); ++ regs = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); ++ ++ priv->pdm_map = devm_regmap_init_mmio(&pdev->dev, regs, &sf_pdm_regmap_cfg); ++ if (IS_ERR(priv->pdm_map)) { ++ dev_err(&pdev->dev, "failed to init regmap: %ld\n", ++ PTR_ERR(priv->pdm_map)); ++ return PTR_ERR(priv->pdm_map); ++ } ++ ++ priv->dev = &pdev->dev; ++ priv->flag_first = 1; ++ ++ ret = sf_pdm_clock_get(pdev, priv); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to enable audio-pdm clock\n"); ++ return ret; ++ } ++ ++ dev_set_drvdata(&pdev->dev, priv); ++ ++ ret = devm_snd_soc_register_component(&pdev->dev, &sf_pdm_component_drv, ++ &sf_pdm_dai_drv, 1); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to register pdm dai\n"); ++ return ret; ++ } ++ pm_runtime_enable(&pdev->dev); ++ ++ return 0; ++} ++ ++static int sf_pdm_dev_remove(struct platform_device *pdev) ++{ ++ pm_runtime_disable(&pdev->dev); ++ return 0; ++} ++ ++static const struct of_device_id sf_pdm_of_match[] = { ++ {.compatible = "starfive,jh7110-pdm",}, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, sf_pdm_of_match); ++ ++static const struct dev_pm_ops sf_pdm_pm_ops = { ++ SET_RUNTIME_PM_OPS(sf_pdm_runtime_suspend, ++ sf_pdm_runtime_resume, NULL) ++}; ++ ++static struct platform_driver sf_pdm_driver = { ++ .driver = { ++ .name = "jh7110-pdm", ++ .of_match_table = sf_pdm_of_match, ++ .pm = &sf_pdm_pm_ops, ++ }, ++ .probe = sf_pdm_probe, ++ .remove = sf_pdm_dev_remove, ++}; ++module_platform_driver(sf_pdm_driver); ++ ++MODULE_AUTHOR("Walker Chen <walker.chen@starfivetech.com>"); ++MODULE_DESCRIPTION("Starfive PDM Controller Driver"); ++MODULE_LICENSE("GPL v2"); diff --git a/target/linux/starfive/patches-6.6/0082-dt-binding-input-Add-tink_ft5406.patch b/target/linux/starfive/patches-6.6/0082-dt-binding-input-Add-tink_ft5406.patch new file mode 100644 index 0000000000..ed8a3af308 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0082-dt-binding-input-Add-tink_ft5406.patch @@ -0,0 +1,55 @@ +From 59cbdfeee0fc1ad382a0bc8f7fa897a9f5d03df0 Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Fri, 9 Jun 2023 16:54:36 +0800 +Subject: [PATCH 082/116] dt-binding: input: Add tink_ft5406 + +Add tink_ft5406. + +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +--- + .../bindings/input/tinker_ft5406.yaml | 39 +++++++++++++++++++ + 1 file changed, 39 insertions(+) + create mode 100644 Documentation/devicetree/bindings/input/tinker_ft5406.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/input/tinker_ft5406.yaml +@@ -0,0 +1,39 @@ ++# SPDX-License-Identifier: GPL-2.0 ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/input/touchscreen/goodix.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Tinker FT5406 touchscreen controller Bindings ++ ++maintainers: ++ - Changhuang Liang <changhuang.liang@starfivetech.com> ++ ++allOf: ++ - $ref: touchscreen.yaml# ++ ++properties: ++ compatible: ++ const: tinker_ft5406 ++ ++ reg: ++ const: 0x38 ++ ++additionalProperties: false ++ ++required: ++ - compatible ++ - reg ++ ++examples: ++ - | ++ i2c { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ tinker_ft5406@38 { ++ compatible = "tinker_ft5406"; ++ reg = <0x38>; ++ }; ++ }; ++ ++... diff --git a/target/linux/starfive/patches-6.6/0083-input-touchscreen-Add-tinker_ft5406-driver-support.patch b/target/linux/starfive/patches-6.6/0083-input-touchscreen-Add-tinker_ft5406-driver-support.patch new file mode 100644 index 0000000000..f45085f495 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0083-input-touchscreen-Add-tinker_ft5406-driver-support.patch @@ -0,0 +1,444 @@ +From 4800b6e0f2190d991cd4e5352167a9422841f195 Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Wed, 21 Dec 2022 16:20:04 +0800 +Subject: [PATCH 083/116] input: touchscreen: Add tinker_ft5406 driver support + +Add tinker_ft5406 driver support + +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +--- + drivers/input/touchscreen/Kconfig | 6 + + drivers/input/touchscreen/Makefile | 1 + + drivers/input/touchscreen/tinker_ft5406.c | 406 ++++++++++++++++++++++ + 3 files changed, 413 insertions(+) + create mode 100644 drivers/input/touchscreen/tinker_ft5406.c + +--- a/drivers/input/touchscreen/Kconfig ++++ b/drivers/input/touchscreen/Kconfig +@@ -1399,4 +1399,10 @@ config TOUCHSCREEN_HIMAX_HX83112B + To compile this driver as a module, choose M here: the + module will be called himax_hx83112b. + ++config TOUCHSCREEN_TINKER_FT5406 ++ tristate "tinker ft5406" ++ depends on I2C ++ help ++ Control ft5406 touch ic. ++ + endif +--- a/drivers/input/touchscreen/Makefile ++++ b/drivers/input/touchscreen/Makefile +@@ -118,3 +118,4 @@ obj-$(CONFIG_TOUCHSCREEN_IQS5XX) += iqs5 + obj-$(CONFIG_TOUCHSCREEN_IQS7211) += iqs7211.o + obj-$(CONFIG_TOUCHSCREEN_ZINITIX) += zinitix.o + obj-$(CONFIG_TOUCHSCREEN_HIMAX_HX83112B) += himax_hx83112b.o ++obj-$(CONFIG_TOUCHSCREEN_TINKER_FT5406) += tinker_ft5406.o +--- /dev/null ++++ b/drivers/input/touchscreen/tinker_ft5406.c +@@ -0,0 +1,406 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * ++ * TINKER BOARD FT5406 touch driver. ++ * ++ * Copyright (c) 2016 ASUSTek Computer Inc. ++ * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. ++ * ++ * This software is licensed under the terms of the GNU General Public ++ * License version 2, as published by the Free Software Foundation, and ++ * may be copied, distributed, and modified under those terms. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ */ ++ ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/delay.h> ++#include <linux/i2c.h> ++#include <linux/input.h> ++#include <linux/input/mt.h> ++#include <linux/module.h> ++#include <linux/workqueue.h> ++ ++#define RETRY_COUNT 10 ++#define FT_ONE_TCH_LEN 6 ++ ++#define FT_REG_FW_VER 0xA6 ++#define FT_REG_FW_MIN_VER 0xB2 ++#define FT_REG_FW_SUB_MIN_VER 0xB3 ++ ++#define VALID_TD_STATUS_VAL 10 ++#define MAX_TOUCH_POINTS 5 ++ ++#define FT_PRESS 0x7F ++#define FT_MAX_ID 0x0F ++ ++#define FT_TOUCH_X_H 0 ++#define FT_TOUCH_X_L 1 ++#define FT_TOUCH_Y_H 2 ++#define FT_TOUCH_Y_L 3 ++#define FT_TOUCH_EVENT 0 ++#define FT_TOUCH_ID 2 ++ ++#define FT_TOUCH_X_H_REG 3 ++#define FT_TOUCH_X_L_REG 4 ++#define FT_TOUCH_Y_H_REG 5 ++#define FT_TOUCH_Y_L_REG 6 ++#define FT_TD_STATUS_REG 2 ++#define FT_TOUCH_EVENT_REG 3 ++#define FT_TOUCH_ID_REG 5 ++ ++#define FT_TOUCH_DOWN 0 ++#define FT_TOUCH_CONTACT 2 ++ ++struct ts_event { ++ u16 au16_x[MAX_TOUCH_POINTS]; /*x coordinate */ ++ u16 au16_y[MAX_TOUCH_POINTS]; /*y coordinate */ ++ u8 au8_touch_event[MAX_TOUCH_POINTS]; /*touch event: 0:down; 1:up; 2:contact */ ++ u8 au8_finger_id[MAX_TOUCH_POINTS]; /*touch ID */ ++ u16 pressure; ++ u8 touch_point; ++ u8 point_num; ++}; ++ ++struct tinker_ft5406_data { ++ struct device *dev; ++ struct i2c_client *client; ++ struct input_dev *input_dev; ++ struct ts_event event; ++ struct work_struct ft5406_work; ++ ++ int screen_width; ++ int screen_height; ++ int xy_reverse; ++ int known_ids; ++ int retry_count; ++ bool finish_work; ++}; ++ ++struct tinker_ft5406_data *g_ts_data; ++ ++static int fts_i2c_read(struct i2c_client *client, char *writebuf, ++ int writelen, char *readbuf, int readlen) ++{ ++ int ret; ++ ++ if (writelen > 0) { ++ struct i2c_msg msgs[] = { ++ { ++ .addr = client->addr, ++ .flags = 0, ++ .len = writelen, ++ .buf = writebuf, ++ }, ++ { ++ .addr = client->addr, ++ .flags = I2C_M_RD, ++ .len = readlen, ++ .buf = readbuf, ++ }, ++ }; ++ ret = i2c_transfer(client->adapter, msgs, 2); ++ if (ret < 0) ++ dev_err(&client->dev, "i2c read error, %d\n", ret); ++ } else { ++ struct i2c_msg msgs[] = { ++ { ++ .addr = client->addr, ++ .flags = I2C_M_RD, ++ .len = readlen, ++ .buf = readbuf, ++ }, ++ }; ++ ret = i2c_transfer(client->adapter, msgs, 1); ++ if (ret < 0) ++ dev_err(&client->dev, "i2c read error, %d\n", ret); ++ } ++ ++ return ret; ++} ++ ++static int fts_read_reg(struct i2c_client *client, u8 addr, u8 *val) ++{ ++ return fts_i2c_read(client, &addr, 1, val, 1); ++} ++ ++static int fts_check_fw_ver(struct i2c_client *client) ++{ ++ u8 reg_addr, fw_ver[3]; ++ int ret; ++ ++ reg_addr = FT_REG_FW_VER; ++ ret = fts_i2c_read(client, ®_addr, 1, &fw_ver[0], 1); ++ if (ret < 0) ++ goto error; ++ ++ reg_addr = FT_REG_FW_MIN_VER; ++ ret = fts_i2c_read(client, ®_addr, 1, &fw_ver[1], 1); ++ if (ret < 0) ++ goto error; ++ ++ reg_addr = FT_REG_FW_SUB_MIN_VER; ++ ret = fts_i2c_read(client, ®_addr, 1, &fw_ver[2], 1); ++ if (ret < 0) ++ goto error; ++ ++ dev_info(&client->dev, "Firmware version = %d.%d.%d\n", ++ fw_ver[0], fw_ver[1], fw_ver[2]); ++ return 0; ++ ++error: ++ return ret; ++} ++ ++static int fts_read_td_status(struct tinker_ft5406_data *ts_data) ++{ ++ u8 td_status; ++ int ret = -1; ++ ++ ret = fts_read_reg(ts_data->client, FT_TD_STATUS_REG, &td_status); ++ if (ret < 0) { ++ dev_err(&ts_data->client->dev, ++ "Get reg td_status failed, %d\n", ret); ++ return ret; ++ } ++ return (int)td_status; ++} ++ ++static int fts_read_touchdata(struct tinker_ft5406_data *ts_data) ++{ ++ struct ts_event *event = &ts_data->event; ++ int ret = -1, i; ++ u8 buf[FT_ONE_TCH_LEN-2] = { 0 }; ++ u8 reg_addr, pointid = FT_MAX_ID; ++ ++ for (i = 0; i < event->touch_point && i < MAX_TOUCH_POINTS; i++) { ++ reg_addr = FT_TOUCH_X_H_REG + (i * FT_ONE_TCH_LEN); ++ ret = fts_i2c_read(ts_data->client, ®_addr, 1, buf, FT_ONE_TCH_LEN-2); ++ if (ret < 0) { ++ dev_err(&ts_data->client->dev, "Read touchdata failed.\n"); ++ return ret; ++ } ++ ++ pointid = (buf[FT_TOUCH_ID]) >> 4; ++ if (pointid >= MAX_TOUCH_POINTS) ++ break; ++ event->au8_finger_id[i] = pointid; ++ event->au16_x[i] = (s16) (buf[FT_TOUCH_X_H] & 0x0F) << 8 | (s16) buf[FT_TOUCH_X_L]; ++ event->au16_y[i] = (s16) (buf[FT_TOUCH_Y_H] & 0x0F) << 8 | (s16) buf[FT_TOUCH_Y_L]; ++ event->au8_touch_event[i] = buf[FT_TOUCH_EVENT] >> 6; ++ ++ if (ts_data->xy_reverse) { ++ event->au16_x[i] = ts_data->screen_width - event->au16_x[i] - 1; ++ event->au16_y[i] = ts_data->screen_height - event->au16_y[i] - 1; ++ } ++ } ++ event->pressure = FT_PRESS; ++ ++ return 0; ++} ++ ++static void fts_report_value(struct tinker_ft5406_data *ts_data) ++{ ++ struct ts_event *event = &ts_data->event; ++ int i, modified_ids = 0, released_ids; ++ ++ for (i = 0; i < event->touch_point && i < MAX_TOUCH_POINTS; i++) { ++ if (event->au8_touch_event[i] == FT_TOUCH_DOWN || ++ event->au8_touch_event[i] == FT_TOUCH_CONTACT) { ++ modified_ids |= 1 << event->au8_finger_id[i]; ++ input_mt_slot(ts_data->input_dev, event->au8_finger_id[i]); ++ input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_FINGER, ++ true); ++ input_report_abs(ts_data->input_dev, ABS_MT_TOUCH_MAJOR, ++ event->pressure); ++ input_report_abs(ts_data->input_dev, ABS_MT_POSITION_X, ++ event->au16_x[i]); ++ input_report_abs(ts_data->input_dev, ABS_MT_POSITION_Y, ++ event->au16_y[i]); ++ ++ if (!((1 << event->au8_finger_id[i]) & ts_data->known_ids)) ++ dev_dbg(&ts_data->client->dev, "Touch id-%d: x = %d, y = %d\n", ++ event->au8_finger_id[i], ++ event->au16_x[i], ++ event->au16_y[i]); ++ } ++ } ++ ++ released_ids = ts_data->known_ids & ~modified_ids; ++ for (i = 0; released_ids && i < MAX_TOUCH_POINTS; i++) { ++ if (released_ids & (1<<i)) { ++ dev_dbg(&ts_data->client->dev, "Release id-%d, known = %x modified = %x\n", ++ i, ts_data->known_ids, modified_ids); ++ input_mt_slot(ts_data->input_dev, i); ++ input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_FINGER, false); ++ modified_ids &= ~(1 << i); ++ } ++ } ++ ts_data->known_ids = modified_ids; ++ input_mt_report_pointer_emulation(ts_data->input_dev, true); ++ input_sync(ts_data->input_dev); ++} ++ ++static void fts_retry_clear(struct tinker_ft5406_data *ts_data) ++{ ++ if (ts_data->retry_count != 0) ++ ts_data->retry_count = 0; ++} ++ ++static int fts_retry_wait(struct tinker_ft5406_data *ts_data) ++{ ++ if (ts_data->retry_count < RETRY_COUNT) { ++ dev_info(&ts_data->client->dev, ++ "Wait and retry, count = %d\n", ts_data->retry_count); ++ ts_data->retry_count++; ++ msleep(1000); ++ return 1; ++ } ++ dev_err(&ts_data->client->dev, "Attach retry count\n"); ++ return 0; ++} ++ ++static void tinker_ft5406_work(struct work_struct *work) ++{ ++ struct ts_event *event = &g_ts_data->event; ++ int ret = 0, td_status; ++ ++ /* polling 60fps */ ++ while (!g_ts_data->finish_work) { ++ td_status = fts_read_td_status(g_ts_data); ++ if (td_status < 0) { ++ ret = fts_retry_wait(g_ts_data); ++ if (ret == 0) { ++ dev_err(&g_ts_data->client->dev, "Stop touch polling\n"); ++ break; ++ } ++ } else if (td_status < VALID_TD_STATUS_VAL + 1 && ++ (td_status > 0 || g_ts_data->known_ids != 0)) { ++ fts_retry_clear(g_ts_data); ++ memset(event, -1, sizeof(struct ts_event)); ++ event->touch_point = td_status; ++ ret = fts_read_touchdata(g_ts_data); ++ if (ret == 0) ++ fts_report_value(g_ts_data); ++ } ++ msleep_interruptible(17); ++ } ++} ++ ++static int tinker_ft5406_open(struct input_dev *dev) ++{ ++ schedule_work(&g_ts_data->ft5406_work); ++ return 0; ++} ++ ++static void tinker_ft5406_close(struct input_dev *dev) ++{ ++ g_ts_data->finish_work = true; ++ cancel_work_sync(&g_ts_data->ft5406_work); ++ g_ts_data->finish_work = false; ++} ++ ++static int tinker_ft5406_probe(struct i2c_client *client) ++{ ++ struct input_dev *input_dev; ++ int ret = 0; ++ ++ dev_info(&client->dev, "Address = 0x%x\n", client->addr); ++ ++ g_ts_data = kzalloc(sizeof(struct tinker_ft5406_data), GFP_KERNEL); ++ if (g_ts_data == NULL) { ++ dev_err(&client->dev, "No memory for device\n"); ++ return -ENOMEM; ++ } ++ ++ g_ts_data->client = client; ++ i2c_set_clientdata(client, g_ts_data); ++ ++ g_ts_data->screen_width = 800; ++ g_ts_data->screen_height = 480; ++ g_ts_data->xy_reverse = 1; ++ ++ dev_info(&client->dev, "width = %d, height = %d, reverse = %d\n", ++ g_ts_data->screen_width, g_ts_data->screen_height, g_ts_data->xy_reverse); ++ ++ ret = fts_check_fw_ver(g_ts_data->client); ++ if (ret) { ++ dev_err(&client->dev, "Checking touch ic failed\n"); ++ goto check_fw_err; ++ } ++ ++ input_dev = input_allocate_device(); ++ if (!input_dev) { ++ dev_err(&client->dev, "Failed to allocate input device\n"); ++ goto input_allocate_failed; ++ } ++ input_dev->name = "fts_ts"; ++ input_dev->id.bustype = BUS_I2C; ++ input_dev->dev.parent = &g_ts_data->client->dev; ++ input_dev->open = tinker_ft5406_open; ++ input_dev->close = tinker_ft5406_close; ++ ++ g_ts_data->input_dev = input_dev; ++ input_set_drvdata(input_dev, g_ts_data); ++ ++ __set_bit(EV_SYN, input_dev->evbit); ++ __set_bit(EV_KEY, input_dev->evbit); ++ __set_bit(EV_ABS, input_dev->evbit); ++ __set_bit(BTN_TOUCH, input_dev->keybit); ++ ++ input_mt_init_slots(input_dev, MAX_TOUCH_POINTS, 0); ++ input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, g_ts_data->screen_width, 0, 0); ++ input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, g_ts_data->screen_height, 0, 0); ++ ++ ret = input_register_device(input_dev); ++ if (ret) { ++ dev_err(&client->dev, "Input device registration failed\n"); ++ goto input_register_failed; ++ } ++ ++ INIT_WORK(&g_ts_data->ft5406_work, tinker_ft5406_work); ++ ++ return 0; ++ ++input_register_failed: ++ input_free_device(input_dev); ++input_allocate_failed: ++check_fw_err: ++ kfree(g_ts_data); ++ g_ts_data = NULL; ++ return ret; ++} ++ ++static void tinker_ft5406_remove(struct i2c_client *client) ++{ ++ cancel_work_sync(&g_ts_data->ft5406_work); ++ if (g_ts_data->input_dev) { ++ input_unregister_device(g_ts_data->input_dev); ++ input_free_device(g_ts_data->input_dev); ++ } ++ kfree(g_ts_data); ++ g_ts_data = NULL; ++} ++ ++static const struct i2c_device_id tinker_ft5406_id[] = { ++ {"tinker_ft5406", 0}, ++ {}, ++}; ++ ++static struct i2c_driver tinker_ft5406_driver = { ++ .driver = { ++ .name = "tinker_ft5406", ++ }, ++ .probe = tinker_ft5406_probe, ++ .remove = tinker_ft5406_remove, ++ .id_table = tinker_ft5406_id, ++}; ++module_i2c_driver(tinker_ft5406_driver); ++ ++MODULE_DESCRIPTION("TINKER BOARD FT5406 Touch driver"); ++MODULE_LICENSE("GPL v2"); diff --git a/target/linux/starfive/patches-6.6/0084-dt-binding-media-Add-JH7110-Camera-Subsystem.patch b/target/linux/starfive/patches-6.6/0084-dt-binding-media-Add-JH7110-Camera-Subsystem.patch new file mode 100644 index 0000000000..69d7c7f494 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0084-dt-binding-media-Add-JH7110-Camera-Subsystem.patch @@ -0,0 +1,246 @@ +From b477a1a53553336edcfeb83be1b35817928daed8 Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Mon, 5 Jun 2023 14:46:16 +0800 +Subject: [PATCH 084/116] dt-binding: media: Add JH7110 Camera Subsystem. + +Add the bindings documentation for Starfive JH7110 Camera Subsystem +which is used for handing image sensor data. + +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +Signed-off-by: Jack Zhu <jack.zhu@starfivetech.com> +--- + .../bindings/media/starfive,jh7110-camss.yaml | 228 ++++++++++++++++++ + 1 file changed, 228 insertions(+) + create mode 100644 Documentation/devicetree/bindings/media/starfive,jh7110-camss.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/media/starfive,jh7110-camss.yaml +@@ -0,0 +1,228 @@ ++# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/media/starfive,jh7110-camss.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Starfive SoC CAMSS ISP ++ ++maintainers: ++ - Jack Zhu <jack.zhu@starfivetech.com> ++ - Changhuang Liang <changhuang.liang@starfivetech.com> ++ ++description: ++ The Starfive CAMSS ISP is a Camera interface for Starfive JH7110 SoC. It ++ consists of a VIN controller (Video In Controller, a top-level control unit) ++ and an ISP. ++ ++properties: ++ compatible: ++ const: starfive,jh7110-vin ++ ++ reg: ++ maxItems: 8 ++ ++ reg-names: ++ items: ++ - const: csi2rx ++ - const: vclk ++ - const: vrst ++ - const: sctrl ++ - const: isp ++ - const: trst ++ - const: pmu ++ - const: syscrg ++ ++ clocks: ++ maxItems: 16 ++ ++ clock-names: ++ items: ++ - const: clk_apb_func ++ - const: clk_pclk ++ - const: clk_sys_clk ++ - const: clk_wrapper_clk_c ++ - const: clk_dvp_inv ++ - const: clk_axiwr ++ - const: clk_mipi_rx0_pxl ++ - const: clk_pixel_clk_if0 ++ - const: clk_pixel_clk_if1 ++ - const: clk_pixel_clk_if2 ++ - const: clk_pixel_clk_if3 ++ - const: clk_m31dphy_cfgclk_in ++ - const: clk_m31dphy_refclk_in ++ - const: clk_m31dphy_txclkesc_lan0 ++ - const: clk_ispcore_2x ++ - const: clk_isp_axi ++ ++ resets: ++ maxItems: 14 ++ ++ reset-names: ++ items: ++ - const: rst_wrapper_p ++ - const: rst_wrapper_c ++ - const: rst_pclk ++ - const: rst_sys_clk ++ - const: rst_axird ++ - const: rst_axiwr ++ - const: rst_pixel_clk_if0 ++ - const: rst_pixel_clk_if1 ++ - const: rst_pixel_clk_if2 ++ - const: rst_pixel_clk_if3 ++ - const: rst_m31dphy_hw ++ - const: rst_m31dphy_b09_always_on ++ - const: rst_isp_top_n ++ - const: rst_isp_top_axi ++ ++ power-domains: ++ items: ++ - description: JH7110 ISP Power Domain Switch Controller. ++ ++ interrupts: ++ maxItems: 5 ++ ++ ports: ++ $ref: /schemas/graph.yaml#/properties/ports ++ ++ properties: ++ port@0: ++ $ref: /schemas/graph.yaml#/$defs/port-base ++ unevaluatedProperties: false ++ description: Input port for receiving DVP data. ++ ++ properties: ++ endpoint: ++ $ref: video-interfaces.yaml# ++ unevaluatedProperties: false ++ ++ properties: ++ bus-type: ++ enum: [5, 6] ++ ++ bus-width: ++ enum: [8, 10, 12] ++ ++ data-shift: ++ enum: [0, 2] ++ default: 0 ++ ++ hsync-active: ++ enum: [0, 1] ++ default: 1 ++ ++ vsync-active: ++ enum: [0, 1] ++ default: 1 ++ ++ required: ++ - bus-type ++ - bus-width ++ ++ port@1: ++ $ref: /schemas/graph.yaml#/properties/port ++ description: Input port for receiving CSI data. ++ ++ required: ++ - port@0 ++ - port@1 ++ ++required: ++ - compatible ++ - reg ++ - reg-names ++ - clocks ++ - clock-names ++ - resets ++ - reset-names ++ - power-domains ++ - interrupts ++ - ports ++ ++additionalProperties: false ++ ++examples: ++ - | ++ vin_sysctl: vin_sysctl@19800000 { ++ compatible = "starfive,jh7110-vin"; ++ reg = <0x0 0x19800000 0x0 0x10000>, ++ <0x0 0x19810000 0x0 0x10000>, ++ <0x0 0x19820000 0x0 0x10000>, ++ <0x0 0x19840000 0x0 0x10000>, ++ <0x0 0x19870000 0x0 0x30000>, ++ <0x0 0x11840000 0x0 0x10000>, ++ <0x0 0x17030000 0x0 0x10000>, ++ <0x0 0x13020000 0x0 0x10000>; ++ reg-names = "csi2rx", "vclk", "vrst", "sctrl", ++ "isp", "trst", "pmu", "syscrg"; ++ clocks = <&clkisp JH7110_DOM4_APB_FUNC>, ++ <&clkisp JH7110_U0_VIN_PCLK>, ++ <&clkisp JH7110_U0_VIN_SYS_CLK>, ++ <&clkisp JH7110_U0_ISPV2_TOP_WRAPPER_CLK_C>, ++ <&clkisp JH7110_DVP_INV>, ++ <&clkisp JH7110_U0_VIN_CLK_P_AXIWR>, ++ <&clkisp JH7110_MIPI_RX0_PXL>, ++ <&clkisp JH7110_U0_VIN_PIXEL_CLK_IF0>, ++ <&clkisp JH7110_U0_VIN_PIXEL_CLK_IF1>, ++ <&clkisp JH7110_U0_VIN_PIXEL_CLK_IF2>, ++ <&clkisp JH7110_U0_VIN_PIXEL_CLK_IF3>, ++ <&clkisp JH7110_U0_M31DPHY_CFGCLK_IN>, ++ <&clkisp JH7110_U0_M31DPHY_REFCLK_IN>, ++ <&clkisp JH7110_U0_M31DPHY_TXCLKESC_LAN0>, ++ <&clkgen JH7110_ISP_TOP_CLK_ISPCORE_2X>, ++ <&clkgen JH7110_ISP_TOP_CLK_ISP_AXI>; ++ clock-names = "clk_apb_func", "clk_pclk", "clk_sys_clk", ++ "clk_wrapper_clk_c", "clk_dvp_inv", "clk_axiwr", ++ "clk_mipi_rx0_pxl", "clk_pixel_clk_if0", ++ "clk_pixel_clk_if1", "clk_pixel_clk_if2", ++ "clk_pixel_clk_if3", "clk_m31dphy_cfgclk_in", ++ "clk_m31dphy_refclk_in", "clk_m31dphy_txclkesc_lan0", ++ "clk_ispcore_2x", "clk_isp_axi"; ++ resets = <&rstgen RSTN_U0_ISPV2_TOP_WRAPPER_P>, ++ <&rstgen RSTN_U0_ISPV2_TOP_WRAPPER_C>, ++ <&rstgen RSTN_U0_VIN_N_PCLK>, ++ <&rstgen RSTN_U0_VIN_N_SYS_CLK>, ++ <&rstgen RSTN_U0_VIN_P_AXIRD>, ++ <&rstgen RSTN_U0_VIN_P_AXIWR>, ++ <&rstgen RSTN_U0_VIN_N_PIXEL_CLK_IF0>, ++ <&rstgen RSTN_U0_VIN_N_PIXEL_CLK_IF1>, ++ <&rstgen RSTN_U0_VIN_N_PIXEL_CLK_IF2>, ++ <&rstgen RSTN_U0_VIN_N_PIXEL_CLK_IF3>, ++ <&rstgen RSTN_U0_M31DPHY_HW>, ++ <&rstgen RSTN_U0_M31DPHY_B09_ALWAYS_ON>, ++ <&rstgen RSTN_U0_DOM_ISP_TOP_N>, ++ <&rstgen RSTN_U0_DOM_ISP_TOP_AXI>; ++ reset-names = "rst_wrapper_p", "rst_wrapper_c", "rst_pclk", ++ "rst_sys_clk", "rst_axird", "rst_axiwr", "rst_pixel_clk_if0", ++ "rst_pixel_clk_if1", "rst_pixel_clk_if2", "rst_pixel_clk_if3", ++ "rst_m31dphy_hw", "rst_m31dphy_b09_always_on", ++ "rst_isp_top_n", "rst_isp_top_axi"; ++ starfive,aon-syscon = <&aon_syscon 0x00>; ++ power-domains = <&pwrc JH7110_PD_ISP>; ++ /* irq nr: vin, isp, isp_csi, isp_scd, isp_csiline */ ++ interrupts = <92>, <87>, <88>, <89>, <90>; ++ ++ ports { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ port@0 { ++ reg = <0>; ++ vin_from_sc2235: endpoint { ++ remote-endpoint = <&sc2235_to_vin>; ++ bus-type = <5>; ++ bus-width = <8>; ++ data-shift = <2>; ++ hsync-active = <1>; ++ vsync-active = <0>; ++ pclk-sample = <1>; ++ }; ++ }; ++ ++ port@1 { ++ reg = <1>; ++ vin_from_csi2rx: endpoint { ++ remote-endpoint = <&csi2rx_to_vin>; ++ }; ++ }; ++ }; ++ }; diff --git a/target/linux/starfive/patches-6.6/0085-media-starfive-Add-vin-driver-support.patch b/target/linux/starfive/patches-6.6/0085-media-starfive-Add-vin-driver-support.patch new file mode 100644 index 0000000000..b5d5f73f9d --- /dev/null +++ b/target/linux/starfive/patches-6.6/0085-media-starfive-Add-vin-driver-support.patch @@ -0,0 +1,24014 @@ +From 908b10ebc95eb29caae8c4737b23a29af5c6298f Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Mon, 5 Jun 2023 13:54:16 +0800 +Subject: [PATCH 085/116] media: starfive: Add vin driver support + +Add vin driver support. + +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +--- + drivers/media/platform/Kconfig | 1 + + drivers/media/platform/Makefile | 1 + + drivers/media/platform/starfive/Kconfig | 56 + + drivers/media/platform/starfive/Makefile | 24 + + .../platform/starfive/v4l2_driver/Readme.txt | 11 + + .../starfive/v4l2_driver/imx219_mipi.c | 1583 ++++++++ + .../starfive/v4l2_driver/ov13850_mipi.c | 1921 ++++++++++ + .../starfive/v4l2_driver/ov4689_mipi.c | 2975 +++++++++++++++ + .../platform/starfive/v4l2_driver/ov5640.c | 3227 +++++++++++++++++ + .../platform/starfive/v4l2_driver/sc2235.c | 1914 ++++++++++ + .../starfive/v4l2_driver/stf_common.h | 185 + + .../platform/starfive/v4l2_driver/stf_csi.c | 465 +++ + .../platform/starfive/v4l2_driver/stf_csi.h | 61 + + .../starfive/v4l2_driver/stf_csi_hw_ops.c | 310 ++ + .../starfive/v4l2_driver/stf_csiphy.c | 357 ++ + .../starfive/v4l2_driver/stf_csiphy.h | 188 + + .../starfive/v4l2_driver/stf_csiphy_hw_ops.c | 335 ++ + .../starfive/v4l2_driver/stf_dmabuf.c | 123 + + .../starfive/v4l2_driver/stf_dmabuf.h | 12 + + .../platform/starfive/v4l2_driver/stf_dvp.c | 385 ++ + .../platform/starfive/v4l2_driver/stf_dvp.h | 67 + + .../starfive/v4l2_driver/stf_dvp_hw_ops.c | 187 + + .../platform/starfive/v4l2_driver/stf_event.c | 36 + + .../platform/starfive/v4l2_driver/stf_isp.c | 1521 ++++++++ + .../platform/starfive/v4l2_driver/stf_isp.h | 222 ++ + .../starfive/v4l2_driver/stf_isp_hw_ops.c | 1550 ++++++++ + .../starfive/v4l2_driver/stf_isp_ioctl.h | 133 + + .../platform/starfive/v4l2_driver/stf_video.c | 1552 ++++++++ + .../platform/starfive/v4l2_driver/stf_video.h | 83 + + .../platform/starfive/v4l2_driver/stf_vin.c | 1515 ++++++++ + .../platform/starfive/v4l2_driver/stf_vin.h | 182 + + .../starfive/v4l2_driver/stf_vin_hw_ops.c | 433 +++ + .../platform/starfive/v4l2_driver/stfcamss.c | 1369 +++++++ + .../platform/starfive/v4l2_driver/stfcamss.h | 117 + + include/uapi/linux/jh7110-isp.h | 253 ++ + include/uapi/linux/v4l2-controls.h | 6 + + include/video/stf-vin.h | 443 +++ + 37 files changed, 23803 insertions(+) + create mode 100644 drivers/media/platform/starfive/Kconfig + create mode 100644 drivers/media/platform/starfive/Makefile + create mode 100644 drivers/media/platform/starfive/v4l2_driver/Readme.txt + create mode 100644 drivers/media/platform/starfive/v4l2_driver/imx219_mipi.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/ov13850_mipi.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/ov4689_mipi.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/ov5640.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/sc2235.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_common.h + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_csi.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_csi.h + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_csi_hw_ops.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_csiphy.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_csiphy.h + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_csiphy_hw_ops.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_dmabuf.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_dmabuf.h + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_dvp.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_dvp.h + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_dvp_hw_ops.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_event.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_isp.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_isp.h + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_isp_hw_ops.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_isp_ioctl.h + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_video.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_video.h + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_vin.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_vin.h + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stf_vin_hw_ops.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stfcamss.c + create mode 100644 drivers/media/platform/starfive/v4l2_driver/stfcamss.h + create mode 100644 include/uapi/linux/jh7110-isp.h + create mode 100644 include/video/stf-vin.h + +--- a/drivers/media/platform/Kconfig ++++ b/drivers/media/platform/Kconfig +@@ -80,6 +80,7 @@ source "drivers/media/platform/renesas/K + source "drivers/media/platform/rockchip/Kconfig" + source "drivers/media/platform/samsung/Kconfig" + source "drivers/media/platform/st/Kconfig" ++source "drivers/media/platform/starfive/Kconfig" + source "drivers/media/platform/sunxi/Kconfig" + source "drivers/media/platform/ti/Kconfig" + source "drivers/media/platform/verisilicon/Kconfig" +--- a/drivers/media/platform/Makefile ++++ b/drivers/media/platform/Makefile +@@ -23,6 +23,7 @@ obj-y += renesas/ + obj-y += rockchip/ + obj-y += samsung/ + obj-y += st/ ++obj-y += starfive/ + obj-y += sunxi/ + obj-y += ti/ + obj-y += verisilicon/ +--- /dev/null ++++ b/drivers/media/platform/starfive/Kconfig +@@ -0,0 +1,56 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++ ++comment "Starfive media platform drivers" ++ ++config VIN_SENSOR_OV5640 ++ tristate "VIN SENSOR support OV5640" ++ depends on VIDEO_STF_VIN ++ select V4L2_FWNODE ++ default n ++ help ++ Say Y here if you want to have support for VIN sensor OV5640 ++ ++config VIN_SENSOR_SC2235 ++ tristate "VIN SENSOR support SC2235" ++ depends on VIDEO_STF_VIN ++ select V4L2_FWNODE ++ default n ++ help ++ Say Y here if you want to have support for VIN sensor SC2235 ++ ++config VIN_SENSOR_OV4689 ++ tristate "VIN SENSOR support OV4689" ++ depends on VIDEO_STF_VIN ++ select V4L2_FWNODE ++ default n ++ ++ help ++ Say Y here if you want to have support for VIN sensor OV4689 ++ ++config VIN_SENSOR_OV13850 ++ bool "VIN SENSOR support OV13850" ++ depends on VIDEO_STF_VIN ++ select V4L2_FWNODE ++ default n ++ help ++ Say Y here if you want to have support for VIN sensor OV13850 ++ ++config VIN_SENSOR_IMX219 ++ tristate "VIN SENSOR support IMX219" ++ depends on VIDEO_STF_VIN ++ select V4L2_FWNODE ++ default n ++ help ++ Say Y here if you want to have support for VIN sensor IMX219 ++ ++config VIDEO_STF_VIN ++ tristate "starfive VIC video in support" ++ depends on V4L_PLATFORM_DRIVERS ++ depends on VIDEO_DEV ++ select MEDIA_CONTROLLER ++ select VIDEOBUF2_DMA_CONTIG ++ select VIDEO_V4L2_SUBDEV_API ++ select V4L2_FWNODE ++ help ++ To compile this driver as a module, choose M here: the module ++ will be called stf-vin. +--- /dev/null ++++ b/drivers/media/platform/starfive/Makefile +@@ -0,0 +1,24 @@ ++# SPDX-License-Identifier: GPL-2.0 ++ ++obj-$(CONFIG_VIN_SENSOR_OV5640) += v4l2_driver/ov5640.o ++obj-$(CONFIG_VIN_SENSOR_SC2235) += v4l2_driver/sc2235.o ++obj-$(CONFIG_VIN_SENSOR_OV4689) += v4l2_driver/ov4689_mipi.o ++obj-$(CONFIG_VIN_SENSOR_OV13850) += v4l2_driver/ov13850_mipi.o ++obj-$(CONFIG_VIN_SENSOR_IMX219) += v4l2_driver/imx219_mipi.o ++ ++starfivecamss-objs += v4l2_driver/stfcamss.o \ ++ v4l2_driver/stf_event.o \ ++ v4l2_driver/stf_dvp.o \ ++ v4l2_driver/stf_csi.o \ ++ v4l2_driver/stf_csiphy.o \ ++ v4l2_driver/stf_isp.o \ ++ v4l2_driver/stf_video.o \ ++ v4l2_driver/stf_vin.o \ ++ v4l2_driver/stf_vin_hw_ops.o \ ++ v4l2_driver/stf_csi_hw_ops.o \ ++ v4l2_driver/stf_csiphy_hw_ops.o \ ++ v4l2_driver/stf_isp_hw_ops.o \ ++ v4l2_driver/stf_dvp_hw_ops.o \ ++ v4l2_driver/stf_dmabuf.o ++ ++obj-$(CONFIG_VIDEO_STF_VIN) += starfivecamss.o \ +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/Readme.txt +@@ -0,0 +1,11 @@ ++ ++/dev/video0: Output the camera data directly. ++/dev/video1: Output the data of the camera converted by isp. ++ ++ensure linux/arch/riscv/configs/starfive_jh7110_defconfig: ++CONFIG_VIDEO_STF_VIN=y ++CONFIG_VIN_SENSOR_SC2235=y ++CONFIG_VIN_SENSOR_OV4689=y ++ ++Only support the lane0/lane5 of dphy as clock lane, lane1/lane2/lane3/lane4 ++as data lane. +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/imx219_mipi.c +@@ -0,0 +1,1583 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * A V4L2 driver for Sony IMX219 cameras. ++ * Copyright (C) 2019, Raspberry Pi (Trading) Ltd ++ * ++ * Based on Sony imx258 camera driver ++ * Copyright (C) 2018 Intel Corporation ++ * ++ * DT / fwnode changes, and regulator / GPIO control taken from imx214 driver ++ * Copyright 2018 Qtechnology A/S ++ * ++ * Flip handling taken from the Sony IMX319 driver. ++ * Copyright (C) 2018 Intel Corporation ++ * ++ */ ++ ++#include <linux/clk.h> ++#include <linux/delay.h> ++#include <linux/gpio/consumer.h> ++#include <linux/i2c.h> ++#include <linux/module.h> ++#include <linux/pm_runtime.h> ++#include <linux/regulator/consumer.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-event.h> ++#include <media/v4l2-fwnode.h> ++#include <media/v4l2-mediabus.h> ++#include <asm/unaligned.h> ++ ++#define IMX219_REG_VALUE_08BIT 1 ++#define IMX219_REG_VALUE_16BIT 2 ++ ++#define IMX219_REG_MODE_SELECT 0x0100 ++#define IMX219_MODE_STANDBY 0x00 ++#define IMX219_MODE_STREAMING 0x01 ++ ++/* Chip ID */ ++#define IMX219_REG_CHIP_ID 0x0000 ++#define IMX219_CHIP_ID 0x0219 ++ ++/* External clock frequency is 24.0M */ ++#define IMX219_XCLK_FREQ 24000000 ++ ++/* Pixel rate is fixed at 182.4M for all the modes */ ++#define IMX219_PIXEL_RATE 182400000 ++ ++#define IMX219_DEFAULT_LINK_FREQ 456000000 ++ ++/* V_TIMING internal */ ++#define IMX219_REG_VTS 0x0160 ++#define IMX219_VTS_15FPS 0x0dc6 ++#define IMX219_VTS_30FPS_1080P 0x06e3 ++#define IMX219_VTS_30FPS_BINNED 0x06e3 ++#define IMX219_VTS_30FPS_1280x720 0x06e3 ++#define IMX219_VTS_30FPS_640x480 0x06e3 ++#define IMX219_VTS_MAX 0xffff ++ ++#define IMX219_VBLANK_MIN 4 ++ ++/*Frame Length Line*/ ++#define IMX219_FLL_MIN 0x08a6 ++#define IMX219_FLL_MAX 0xffff ++#define IMX219_FLL_STEP 1 ++#define IMX219_FLL_DEFAULT 0x0c98 ++ ++/* HBLANK control - read only */ ++#define IMX219_PPL_DEFAULT 3448 ++ ++/* Exposure control */ ++#define IMX219_REG_EXPOSURE 0x015a ++#define IMX219_EXPOSURE_MIN 4 ++#define IMX219_EXPOSURE_STEP 1 ++#define IMX219_EXPOSURE_DEFAULT 0x640 ++#define IMX219_EXPOSURE_MAX 65535 ++ ++/* Analog gain control */ ++#define IMX219_REG_ANALOG_GAIN 0x0157 ++#define IMX219_ANA_GAIN_MIN 0 ++#define IMX219_ANA_GAIN_MAX 232 ++#define IMX219_ANA_GAIN_STEP 1 ++#define IMX219_ANA_GAIN_DEFAULT 0xd0 ++ ++/* Digital gain control */ ++#define IMX219_REG_DIGITAL_GAIN 0x0158 ++#define IMX219_DGTL_GAIN_MIN 0x0100 ++#define IMX219_DGTL_GAIN_MAX 0x0fff ++#define IMX219_DGTL_GAIN_DEFAULT 0x0100 ++#define IMX219_DGTL_GAIN_STEP 1 ++ ++#define IMX219_REG_ORIENTATION 0x0172 ++ ++/* Test Pattern Control */ ++#define IMX219_REG_TEST_PATTERN 0x0600 ++#define IMX219_TEST_PATTERN_DISABLE 0 ++#define IMX219_TEST_PATTERN_SOLID_COLOR 1 ++#define IMX219_TEST_PATTERN_COLOR_BARS 2 ++#define IMX219_TEST_PATTERN_GREY_COLOR 3 ++#define IMX219_TEST_PATTERN_PN9 4 ++ ++/* Test pattern colour components */ ++#define IMX219_REG_TESTP_RED 0x0602 ++#define IMX219_REG_TESTP_GREENR 0x0604 ++#define IMX219_REG_TESTP_BLUE 0x0606 ++#define IMX219_REG_TESTP_GREENB 0x0608 ++#define IMX219_TESTP_COLOUR_MIN 0 ++#define IMX219_TESTP_COLOUR_MAX 0x03ff ++#define IMX219_TESTP_COLOUR_STEP 1 ++#define IMX219_TESTP_RED_DEFAULT IMX219_TESTP_COLOUR_MAX ++#define IMX219_TESTP_GREENR_DEFAULT 0 ++#define IMX219_TESTP_BLUE_DEFAULT 0 ++#define IMX219_TESTP_GREENB_DEFAULT 0 ++ ++/* IMX219 native and active pixel array size. */ ++#define IMX219_NATIVE_WIDTH 3296U ++#define IMX219_NATIVE_HEIGHT 2480U ++#define IMX219_PIXEL_ARRAY_LEFT 8U ++#define IMX219_PIXEL_ARRAY_TOP 8U ++#define IMX219_PIXEL_ARRAY_WIDTH 3280U ++#define IMX219_PIXEL_ARRAY_HEIGHT 2464U ++ ++struct imx219_reg { ++ u16 address; ++ u8 val; ++}; ++ ++struct imx219_reg_list { ++ unsigned int num_of_regs; ++ const struct imx219_reg *regs; ++}; ++ ++/* Mode : resolution and related config&values */ ++struct imx219_mode { ++ /* Frame width */ ++ unsigned int width; ++ /* Frame height */ ++ unsigned int height; ++ ++ unsigned int fps; ++ ++ /* Analog crop rectangle. */ ++ struct v4l2_rect crop; ++ ++ /* V-timing */ ++ unsigned int vts_def; ++ ++ /* Default register values */ ++ struct imx219_reg_list reg_list; ++}; ++ ++/* ++ * Register sets lifted off the i2C interface from the Raspberry Pi firmware ++ * driver. ++ * 3280x2464 = mode 2, 1920x1080 = mode 1, 1640x1232 = mode 4, 640x480 = mode 7. ++ */ ++ ++static const struct imx219_reg mode_1920_1080_regs[] = { ++ {0x0100, 0x00}, ++ {0x30eb, 0x05}, ++ {0x30eb, 0x0c}, ++ {0x300a, 0xff}, ++ {0x300b, 0xff}, ++ {0x30eb, 0x05}, ++ {0x30eb, 0x09}, ++ {0x0114, 0x01}, ++ {0x0128, 0x00}, ++ {0x012a, 0x18}, ++ {0x012b, 0x00}, ++ {0x0162, 0x0d}, ++ {0x0163, 0x78}, ++ {0x0164, 0x02}, ++ {0x0165, 0xa8}, ++ {0x0166, 0x0a}, ++ {0x0167, 0x27}, ++ {0x0168, 0x02}, ++ {0x0169, 0xb4}, ++ {0x016a, 0x06}, ++ {0x016b, 0xeb}, ++ {0x016c, 0x07}, ++ {0x016d, 0x80}, ++ {0x016e, 0x04}, ++ {0x016f, 0x38}, ++ {0x0170, 0x01}, ++ {0x0171, 0x01}, ++ {0x0174, 0x00}, ++ {0x0175, 0x00}, ++ {0x0301, 0x05}, ++ {0x0303, 0x01}, ++ {0x0304, 0x03}, ++ {0x0305, 0x03}, ++ {0x0306, 0x00}, ++ {0x0307, 0x39}, ++ {0x030b, 0x01}, ++ {0x030c, 0x00}, ++ {0x030d, 0x72}, ++ {0x0624, 0x07}, ++ {0x0625, 0x80}, ++ {0x0626, 0x04}, ++ {0x0627, 0x38}, ++ {0x455e, 0x00}, ++ {0x471e, 0x4b}, ++ {0x4767, 0x0f}, ++ {0x4750, 0x14}, ++ {0x4540, 0x00}, ++ {0x47b4, 0x14}, ++ {0x4713, 0x30}, ++ {0x478b, 0x10}, ++ {0x478f, 0x10}, ++ {0x4793, 0x10}, ++ {0x4797, 0x0e}, ++ {0x479b, 0x0e}, ++}; ++ ++static const struct imx219_reg mode_1280_720_regs[] = { ++ {0x0100, 0x00}, ++ {0x30eb, 0x05}, ++ {0x30eb, 0x0c}, ++ {0x300a, 0xff}, ++ {0x300b, 0xff}, ++ {0x30eb, 0x05}, ++ {0x30eb, 0x09}, ++ {0x0114, 0x01}, ++ {0x0128, 0x00}, ++ {0x012a, 0x18}, ++ {0x012b, 0x00}, ++ {0x0162, 0x0d}, ++ {0x0163, 0x78}, ++ {0x0164, 0x01}, ++ {0x0165, 0x68}, ++ {0x0166, 0x0b}, ++ {0x0167, 0x67}, ++ {0x0168, 0x02}, ++ {0x0169, 0x00}, ++ {0x016a, 0x07}, ++ {0x016b, 0x9f}, ++ {0x016c, 0x05}, ++ {0x016d, 0x00}, ++ {0x016e, 0x02}, ++ {0x016f, 0xd0}, ++ {0x0170, 0x01}, ++ {0x0171, 0x01}, ++ {0x0174, 0x01}, ++ {0x0175, 0x01}, ++ {0x0301, 0x05}, ++ {0x0303, 0x01}, ++ {0x0304, 0x03}, ++ {0x0305, 0x03}, ++ {0x0306, 0x00}, ++ {0x0307, 0x39}, ++ {0x030b, 0x01}, ++ {0x030c, 0x00}, ++ {0x030d, 0x72}, ++ {0x0624, 0x06}, ++ {0x0625, 0x68}, ++ {0x0626, 0x04}, ++ {0x0627, 0xd0}, ++ {0x455e, 0x00}, ++ {0x471e, 0x4b}, ++ {0x4767, 0x0f}, ++ {0x4750, 0x14}, ++ {0x4540, 0x00}, ++ {0x47b4, 0x14}, ++ {0x4713, 0x30}, ++ {0x478b, 0x10}, ++ {0x478f, 0x10}, ++ {0x4793, 0x10}, ++ {0x4797, 0x0e}, ++ {0x479b, 0x0e}, ++}; ++ ++static const struct imx219_reg mode_640_480_regs[] = { ++ {0x0100, 0x00}, ++ {0x30eb, 0x05}, ++ {0x30eb, 0x0c}, ++ {0x300a, 0xff}, ++ {0x300b, 0xff}, ++ {0x30eb, 0x05}, ++ {0x30eb, 0x09}, ++ {0x0114, 0x01}, ++ {0x0128, 0x00}, ++ {0x012a, 0x18}, ++ {0x012b, 0x00}, ++ {0x0162, 0x0d}, ++ {0x0163, 0x78}, ++ {0x0164, 0x03}, ++ {0x0165, 0xe8}, ++ {0x0166, 0x08}, ++ {0x0167, 0xe7}, ++ {0x0168, 0x02}, ++ {0x0169, 0xf0}, ++ {0x016a, 0x06}, ++ {0x016b, 0xaf}, ++ {0x016c, 0x02}, ++ {0x016d, 0x80}, ++ {0x016e, 0x01}, ++ {0x016f, 0xe0}, ++ {0x0170, 0x01}, ++ {0x0171, 0x01}, ++ {0x0174, 0x03}, ++ {0x0175, 0x03}, ++ {0x0301, 0x05}, ++ {0x0303, 0x01}, ++ {0x0304, 0x03}, ++ {0x0305, 0x03}, ++ {0x0306, 0x00}, ++ {0x0307, 0x39}, ++ {0x030b, 0x01}, ++ {0x030c, 0x00}, ++ {0x030d, 0x72}, ++ {0x0624, 0x06}, ++ {0x0625, 0x68}, ++ {0x0626, 0x04}, ++ {0x0627, 0xd0}, ++ {0x455e, 0x00}, ++ {0x471e, 0x4b}, ++ {0x4767, 0x0f}, ++ {0x4750, 0x14}, ++ {0x4540, 0x00}, ++ {0x47b4, 0x14}, ++ {0x4713, 0x30}, ++ {0x478b, 0x10}, ++ {0x478f, 0x10}, ++ {0x4793, 0x10}, ++ {0x4797, 0x0e}, ++ {0x479b, 0x0e}, ++}; ++ ++static const struct imx219_reg raw8_framefmt_regs[] = { ++ {0x018c, 0x08}, ++ {0x018d, 0x08}, ++ {0x0309, 0x08}, ++}; ++ ++static const struct imx219_reg raw10_framefmt_regs[] = { ++ {0x018c, 0x0a}, ++ {0x018d, 0x0a}, ++ {0x0309, 0x0a}, ++}; ++ ++static const s64 imx219_link_freq_menu[] = { ++ IMX219_DEFAULT_LINK_FREQ, ++}; ++ ++static const char * const imx219_test_pattern_menu[] = { ++ "Disabled", ++ "Color Bars", ++ "Solid Color", ++ "Grey Color Bars", ++ "PN9" ++}; ++ ++static const int imx219_test_pattern_val[] = { ++ IMX219_TEST_PATTERN_DISABLE, ++ IMX219_TEST_PATTERN_COLOR_BARS, ++ IMX219_TEST_PATTERN_SOLID_COLOR, ++ IMX219_TEST_PATTERN_GREY_COLOR, ++ IMX219_TEST_PATTERN_PN9, ++}; ++ ++/* regulator supplies */ ++static const char * const imx219_supply_name[] = { ++ /* Supplies can be enabled in any order */ ++ "VANA", /* Analog (2.8V) supply */ ++ "VDIG", /* Digital Core (1.8V) supply */ ++ "VDDL", /* IF (1.2V) supply */ ++}; ++ ++#define IMX219_NUM_SUPPLIES ARRAY_SIZE(imx219_supply_name) ++ ++/* ++ * The supported formats. ++ * This table MUST contain 4 entries per format, to cover the various flip ++ * combinations in the order ++ * - no flip ++ * - h flip ++ * - v flip ++ * - h&v flips ++ */ ++static const u32 codes[] = { ++ MEDIA_BUS_FMT_SRGGB10_1X10, ++ MEDIA_BUS_FMT_SGRBG10_1X10, ++ MEDIA_BUS_FMT_SGBRG10_1X10, ++ MEDIA_BUS_FMT_SBGGR10_1X10, ++ ++ MEDIA_BUS_FMT_SRGGB8_1X8, ++ MEDIA_BUS_FMT_SGRBG8_1X8, ++ MEDIA_BUS_FMT_SGBRG8_1X8, ++ MEDIA_BUS_FMT_SBGGR8_1X8, ++}; ++ ++/* ++ * Initialisation delay between XCLR low->high and the moment when the sensor ++ * can start capture (i.e. can leave software stanby) must be not less than: ++ * t4 + max(t5, t6 + <time to initialize the sensor register over I2C>) ++ * where ++ * t4 is fixed, and is max 200uS, ++ * t5 is fixed, and is 6000uS, ++ * t6 depends on the sensor external clock, and is max 32000 clock periods. ++ * As per sensor datasheet, the external clock must be from 6MHz to 27MHz. ++ * So for any acceptable external clock t6 is always within the range of ++ * 1185 to 5333 uS, and is always less than t5. ++ * For this reason this is always safe to wait (t4 + t5) = 6200 uS, then ++ * initialize the sensor over I2C, and then exit the software standby. ++ * ++ * This start-up time can be optimized a bit more, if we start the writes ++ * over I2C after (t4+t6), but before (t4+t5) expires. But then sensor ++ * initialization over I2C may complete before (t4+t5) expires, and we must ++ * ensure that capture is not started before (t4+t5). ++ * ++ * This delay doesn't account for the power supply startup time. If needed, ++ * this should be taken care of via the regulator framework. E.g. in the ++ * case of DT for regulator-fixed one should define the startup-delay-us ++ * property. ++ */ ++#define IMX219_XCLR_MIN_DELAY_US 6200 ++#define IMX219_XCLR_DELAY_RANGE_US 1000 ++ ++/* Mode configs */ ++static const struct imx219_mode supported_modes[] = { ++ { ++ /* 1080P 30fps cropped */ ++ .width = 1920, ++ .height = 1080, ++ .fps = 30, ++ .crop = { ++ .left = 688, ++ .top = 700, ++ .width = 1920, ++ .height = 1080 ++ }, ++ .vts_def = IMX219_VTS_30FPS_1080P, ++ .reg_list = { ++ .num_of_regs = ARRAY_SIZE(mode_1920_1080_regs), ++ .regs = mode_1920_1080_regs, ++ }, ++ }, ++ { ++ /* 1280x720 30fps mode */ ++ .width = 1280, ++ .height = 720, ++ .fps = 30, ++ .crop = { ++ .left = 360, ++ .top = 512, ++ .width = 2560, ++ .height = 1440 ++ }, ++ .vts_def = IMX219_VTS_30FPS_1280x720, ++ .reg_list = { ++ .num_of_regs = ARRAY_SIZE(mode_1280_720_regs), ++ .regs = mode_1280_720_regs, ++ }, ++ }, ++ { ++ /* 640x480 30fps mode */ ++ .width = 640, ++ .height = 480, ++ .fps = 30, ++ .crop = { ++ .left = 1008, ++ .top = 760, ++ .width = 1280, ++ .height = 960 ++ }, ++ .vts_def = IMX219_VTS_30FPS_640x480, ++ .reg_list = { ++ .num_of_regs = ARRAY_SIZE(mode_640_480_regs), ++ .regs = mode_640_480_regs, ++ }, ++ }, ++}; ++ ++struct imx219 { ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ //struct v4l2_fwnode_endpoint ep; /* the parsed DT endpoint info */ ++ ++ struct v4l2_mbus_framefmt fmt; ++ ++ struct clk *xclk; /* system clock to IMX219 */ ++ u32 xclk_freq; ++ ++ struct gpio_desc *reset_gpio; ++ struct regulator_bulk_data supplies[IMX219_NUM_SUPPLIES]; ++ ++ struct v4l2_ctrl_handler ctrl_handler; ++ /* V4L2 Controls */ ++ struct v4l2_ctrl *pixel_rate; ++ struct v4l2_ctrl *link_freq; ++ struct v4l2_ctrl *exposure; ++ struct v4l2_ctrl *vflip; ++ struct v4l2_ctrl *hflip; ++ struct v4l2_ctrl *vblank; ++ struct v4l2_ctrl *hblank; ++ ++ /* Current mode */ ++ const struct imx219_mode *mode; ++ struct v4l2_fract frame_interval; ++ ++ /* ++ * Mutex for serialized access: ++ * Protect sensor module set pad format and start/stop streaming safely. ++ */ ++ struct mutex mutex; ++ ++ /* Streaming on/off */ ++ int streaming; ++}; ++ ++static inline struct imx219 *to_imx219(struct v4l2_subdev *_sd) ++{ ++ return container_of(_sd, struct imx219, sd); ++} ++ ++/* Read registers up to 2 at a time */ ++static int imx219_read_reg(struct imx219 *imx219, u16 reg, u32 len, u32 *val) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); ++ struct i2c_msg msgs[2]; ++ u8 addr_buf[2] = { reg >> 8, reg & 0xff }; ++ u8 data_buf[4] = { 0, }; ++ int ret; ++ ++ if (len > 4) ++ return -EINVAL; ++ ++ /* Write register address */ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = 0; ++ msgs[0].len = ARRAY_SIZE(addr_buf); ++ msgs[0].buf = addr_buf; ++ ++ /* Read data from register */ ++ msgs[1].addr = client->addr; ++ msgs[1].flags = I2C_M_RD; ++ msgs[1].len = len; ++ msgs[1].buf = &data_buf[4 - len]; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret != ARRAY_SIZE(msgs)) ++ return -EIO; ++ ++ *val = get_unaligned_be32(data_buf); ++ ++ return 0; ++} ++ ++/* Write registers up to 2 at a time */ ++static int imx219_write_reg(struct imx219 *imx219, u16 reg, u32 len, u32 val) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); ++ u8 buf[6]; ++ ++ if (len > 4) ++ return -EINVAL; ++ ++ put_unaligned_be16(reg, buf); ++ put_unaligned_be32(val << (8 * (4 - len)), buf + 2); ++ if (i2c_master_send(client, buf, len + 2) != len + 2) ++ return -EIO; ++ ++ return 0; ++} ++ ++/* Write a list of registers */ ++static int imx219_write_regs(struct imx219 *imx219, ++ const struct imx219_reg *regs, u32 len) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); ++ unsigned int i; ++ int ret; ++ ++ for (i = 0; i < len; i++) { ++ ret = imx219_write_reg(imx219, regs[i].address, 1, regs[i].val); ++ if (ret) { ++ dev_err_ratelimited(&client->dev, ++ "Failed to write reg 0x%4.4x. error = %d\n", ++ regs[i].address, ret); ++ ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++/* Get bayer order based on flip setting. */ ++static u32 imx219_get_format_code(struct imx219 *imx219, u32 code) ++{ ++ unsigned int i; ++ ++ lockdep_assert_held(&imx219->mutex); ++ ++ for (i = 0; i < ARRAY_SIZE(codes); i++) ++ if (codes[i] == code) ++ break; ++ ++ if (i >= ARRAY_SIZE(codes)) ++ i = 0; ++ ++ i = (i & ~3) | (imx219->vflip->val ? 2 : 0) | ++ (imx219->hflip->val ? 1 : 0); ++ ++ return codes[i]; ++} ++ ++static void imx219_set_default_format(struct imx219 *imx219) ++{ ++ struct v4l2_mbus_framefmt *fmt; ++ ++ fmt = &imx219->fmt; ++ fmt->code = MEDIA_BUS_FMT_SRGGB10_1X10; ++ 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); ++ fmt->width = supported_modes[0].width; ++ fmt->height = supported_modes[0].height; ++ fmt->field = V4L2_FIELD_NONE; ++} ++ ++static int imx219_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) ++{ ++ struct imx219 *imx219 = to_imx219(sd); ++ struct v4l2_mbus_framefmt *try_fmt = ++ v4l2_subdev_get_try_format(sd, fh->state, 0); ++ struct v4l2_rect *try_crop; ++ ++ mutex_lock(&imx219->mutex); ++ ++ /* Initialize try_fmt */ ++ try_fmt->width = supported_modes[0].width; ++ try_fmt->height = supported_modes[0].height; ++ try_fmt->code = imx219_get_format_code(imx219, MEDIA_BUS_FMT_SRGGB10_1X10); ++ try_fmt->field = V4L2_FIELD_NONE; ++ ++ /* Initialize try_crop rectangle. */ ++ try_crop = v4l2_subdev_get_try_crop(sd, fh->state, 0); ++ try_crop->top = IMX219_PIXEL_ARRAY_TOP; ++ try_crop->left = IMX219_PIXEL_ARRAY_LEFT; ++ try_crop->width = IMX219_PIXEL_ARRAY_WIDTH; ++ try_crop->height = IMX219_PIXEL_ARRAY_HEIGHT; ++ ++ mutex_unlock(&imx219->mutex); ++ ++ return 0; ++} ++ ++static int imx219_set_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct imx219 *imx219 = ++ container_of(ctrl->handler, struct imx219, ctrl_handler); ++ struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); ++ int ret; ++ ++ if (ctrl->id == V4L2_CID_VBLANK) { ++ int exposure_max, exposure_def; ++ ++ /* Update max exposure while meeting expected vblanking */ ++ exposure_max = imx219->mode->height + ctrl->val - 4; ++ exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ? ++ exposure_max : IMX219_EXPOSURE_DEFAULT; ++ __v4l2_ctrl_modify_range(imx219->exposure, imx219->exposure->minimum, ++ exposure_max, imx219->exposure->step, exposure_def); ++ } ++ ++ /* ++ * Applying V4L2 control value only happens ++ * when power is up for streaming ++ */ ++ if (pm_runtime_get_if_in_use(&client->dev) == 0) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = imx219_write_reg(imx219, IMX219_REG_ANALOG_GAIN, ++ IMX219_REG_VALUE_08BIT, ctrl->val); ++ break; ++ case V4L2_CID_EXPOSURE: ++ ret = imx219_write_reg(imx219, IMX219_REG_EXPOSURE, ++ IMX219_REG_VALUE_16BIT, ctrl->val); ++ break; ++ case V4L2_CID_DIGITAL_GAIN: ++ ret = imx219_write_reg(imx219, IMX219_REG_DIGITAL_GAIN, ++ IMX219_REG_VALUE_16BIT, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN: ++ ret = imx219_write_reg(imx219, IMX219_REG_TEST_PATTERN, ++ IMX219_REG_VALUE_16BIT, imx219_test_pattern_val[ctrl->val]); ++ break; ++ case V4L2_CID_HFLIP: ++ case V4L2_CID_VFLIP: ++ ret = imx219_write_reg(imx219, IMX219_REG_ORIENTATION, 1, ++ imx219->hflip->val | imx219->vflip->val << 1); ++ break; ++ case V4L2_CID_VBLANK: ++ ret = imx219_write_reg(imx219, IMX219_REG_VTS, IMX219_REG_VALUE_16BIT, ++ imx219->mode->height + ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN_RED: ++ ret = imx219_write_reg(imx219, IMX219_REG_TESTP_RED, ++ IMX219_REG_VALUE_16BIT, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN_GREENR: ++ ret = imx219_write_reg(imx219, IMX219_REG_TESTP_GREENR, ++ IMX219_REG_VALUE_16BIT, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN_BLUE: ++ ret = imx219_write_reg(imx219, IMX219_REG_TESTP_BLUE, ++ IMX219_REG_VALUE_16BIT, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN_GREENB: ++ ret = imx219_write_reg(imx219, IMX219_REG_TESTP_GREENB, ++ IMX219_REG_VALUE_16BIT, ctrl->val); ++ break; ++ default: ++ dev_info(&client->dev, ++ "ctrl(id:0x%x,val:0x%x) is not handled\n", ctrl->id, ctrl->val); ++ ret = -EINVAL; ++ break; ++ } ++ ++ pm_runtime_put(&client->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops imx219_ctrl_ops = { ++ .s_ctrl = imx219_set_ctrl, ++}; ++ ++static int imx219_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct imx219 *imx219 = to_imx219(sd); ++ ++ if (code->index >= (ARRAY_SIZE(codes) / 4)) ++ return -EINVAL; ++ ++ mutex_lock(&imx219->mutex); ++ code->code = imx219_get_format_code(imx219, codes[code->index * 4]); ++ mutex_unlock(&imx219->mutex); ++ ++ return 0; ++} ++ ++static int imx219_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ struct imx219 *imx219 = to_imx219(sd); ++ u32 code; ++ ++ if (fse->index >= ARRAY_SIZE(supported_modes)) ++ return -EINVAL; ++ ++ mutex_lock(&imx219->mutex); ++ code = imx219_get_format_code(imx219, fse->code); ++ mutex_unlock(&imx219->mutex); ++ if (fse->code != code) ++ return -EINVAL; ++ ++ fse->min_width = supported_modes[fse->index].width; ++ fse->max_width = fse->min_width; ++ fse->min_height = supported_modes[fse->index].height; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static int imx219_try_frame_interval(struct imx219 *imx219, ++ struct v4l2_fract *fi, ++ u32 w, u32 h) ++{ ++ const struct imx219_mode *mode; ++ ++ mode = v4l2_find_nearest_size(supported_modes, ARRAY_SIZE(supported_modes), ++ width, height, w, h); ++ if (!mode || (mode->width != w || mode->height != h)) ++ return -EINVAL; ++ ++ fi->numerator = 1; ++ fi->denominator = mode->fps; ++ ++ return mode->fps; ++} ++ ++static int imx219_enum_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_interval_enum *fie) ++{ ++ struct imx219 *imx219 = to_imx219(sd); ++ struct v4l2_fract tpf; ++ u32 code; ++ int ret; ++ ++ if (fie->index >= ARRAY_SIZE(supported_modes)) ++ return -EINVAL; ++ ++ mutex_lock(&imx219->mutex); ++ code = imx219_get_format_code(imx219, fie->code); ++ if (fie->code != code) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ ret = imx219_try_frame_interval(imx219, &tpf, ++ fie->width, fie->height); ++ if (ret < 0) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ mutex_unlock(&imx219->mutex); ++ fie->interval = tpf; ++ ++ return 0; ++ ++out: ++ mutex_unlock(&imx219->mutex); ++ return ret; ++} ++ ++static int imx219_g_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_frame_interval *fi) ++{ ++ struct imx219 *imx219 = to_imx219(sd); ++ ++ mutex_lock(&imx219->mutex); ++ fi->interval = imx219->frame_interval; ++ mutex_unlock(&imx219->mutex); ++ ++ return 0; ++} ++ ++static int imx219_s_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_frame_interval *fi) ++{ ++ struct imx219 *imx219 = to_imx219(sd); ++ const struct imx219_mode *mode = imx219->mode; ++ int frame_rate, ret = 0; ++ ++ if (fi->pad != 0) ++ return -EINVAL; ++ ++ mutex_lock(&imx219->mutex); ++ ++ if (imx219->streaming) { ++ ret = -EBUSY; ++ goto out; ++ } ++ ++ frame_rate = imx219_try_frame_interval(imx219, &fi->interval, ++ mode->width, mode->height); ++ if (frame_rate < 0) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ imx219->frame_interval = fi->interval; ++ ++out: ++ mutex_unlock(&imx219->mutex); ++ return ret; ++} ++ ++static void imx219_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 imx219_update_pad_format(struct imx219 *imx219, ++ const struct imx219_mode *mode, ++ struct v4l2_subdev_format *fmt) ++{ ++ fmt->format.width = mode->width; ++ fmt->format.height = mode->height; ++ fmt->format.field = V4L2_FIELD_NONE; ++ imx219_reset_colorspace(&fmt->format); ++} ++ ++static int __imx219_get_pad_format(struct imx219 *imx219, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { ++ struct v4l2_mbus_framefmt *try_fmt = ++ v4l2_subdev_get_try_format(&imx219->sd, state, fmt->pad); ++ /* update the code which could change due to vflip or hflip: */ ++ try_fmt->code = imx219_get_format_code(imx219, try_fmt->code); ++ fmt->format = *try_fmt; ++ } else { ++ imx219_update_pad_format(imx219, imx219->mode, fmt); ++ fmt->format.code = imx219_get_format_code(imx219, imx219->fmt.code); ++ } ++ ++ return 0; ++} ++ ++static int imx219_get_pad_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct imx219 *imx219 = to_imx219(sd); ++ int ret; ++ ++ mutex_lock(&imx219->mutex); ++ ret = __imx219_get_pad_format(imx219, state, fmt); ++ mutex_unlock(&imx219->mutex); ++ ++ return ret; ++} ++ ++static int imx219_set_pad_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct imx219 *imx219 = to_imx219(sd); ++ const struct imx219_mode *mode; ++ struct v4l2_mbus_framefmt *framefmt; ++ int exposure_max, exposure_def, hblank; ++ unsigned int i; ++ ++ mutex_lock(&imx219->mutex); ++ ++ for (i = 0; i < ARRAY_SIZE(codes); i++) ++ if (codes[i] == fmt->format.code) ++ break; ++ if (i >= ARRAY_SIZE(codes)) ++ i = 0; ++ ++ /* Bayer order varies with flips */ ++ fmt->format.code = imx219_get_format_code(imx219, codes[i]); ++ ++ mode = v4l2_find_nearest_size(supported_modes, ARRAY_SIZE(supported_modes), ++ width, height, fmt->format.width, fmt->format.height); ++ imx219_update_pad_format(imx219, mode, fmt); ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { ++ framefmt = v4l2_subdev_get_try_format(sd, state, fmt->pad); ++ *framefmt = fmt->format; ++ } else if (imx219->mode != mode || ++ imx219->fmt.code != fmt->format.code) { ++ imx219->fmt = fmt->format; ++ imx219->mode = mode; ++ /* Update limits and set FPS to default */ ++ __v4l2_ctrl_modify_range(imx219->vblank, IMX219_VBLANK_MIN, ++ IMX219_VTS_MAX - mode->height, 1, ++ mode->vts_def - mode->height); ++ __v4l2_ctrl_s_ctrl(imx219->vblank, mode->vts_def - mode->height); ++ /* Update max exposure while meeting expected vblanking */ ++ exposure_max = mode->vts_def - 4; ++ exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ? ++ exposure_max : IMX219_EXPOSURE_DEFAULT; ++ __v4l2_ctrl_modify_range(imx219->exposure, imx219->exposure->minimum, ++ exposure_max, imx219->exposure->step, exposure_def); ++ /* ++ * Currently PPL is fixed to IMX219_PPL_DEFAULT, so hblank ++ * depends on mode->width only, and is not changeble in any ++ * way other than changing the mode. ++ */ ++ hblank = IMX219_PPL_DEFAULT - mode->width; ++ __v4l2_ctrl_modify_range(imx219->hblank, hblank, hblank, 1, hblank); ++ } ++ ++ mutex_unlock(&imx219->mutex); ++ ++ return 0; ++} ++ ++static int imx219_set_framefmt(struct imx219 *imx219) ++{ ++ switch (imx219->fmt.code) { ++ case MEDIA_BUS_FMT_SRGGB8_1X8: ++ case MEDIA_BUS_FMT_SGRBG8_1X8: ++ case MEDIA_BUS_FMT_SGBRG8_1X8: ++ case MEDIA_BUS_FMT_SBGGR8_1X8: ++ return imx219_write_regs(imx219, raw8_framefmt_regs, ++ ARRAY_SIZE(raw8_framefmt_regs)); ++ ++ case MEDIA_BUS_FMT_SRGGB10_1X10: ++ case MEDIA_BUS_FMT_SGRBG10_1X10: ++ case MEDIA_BUS_FMT_SGBRG10_1X10: ++ case MEDIA_BUS_FMT_SBGGR10_1X10: ++ return imx219_write_regs(imx219, raw10_framefmt_regs, ++ ARRAY_SIZE(raw10_framefmt_regs)); ++ } ++ ++ return -EINVAL; ++} ++ ++static const struct v4l2_rect * ++__imx219_get_pad_crop(struct imx219 *imx219, struct v4l2_subdev_state *state, ++ unsigned int pad, enum v4l2_subdev_format_whence which) ++{ ++ switch (which) { ++ case V4L2_SUBDEV_FORMAT_TRY: ++ return v4l2_subdev_get_try_crop(&imx219->sd, state, pad); ++ case V4L2_SUBDEV_FORMAT_ACTIVE: ++ return &imx219->mode->crop; ++ } ++ ++ return NULL; ++} ++ ++static int imx219_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_selection *sel) ++{ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: { ++ struct imx219 *imx219 = to_imx219(sd); ++ ++ mutex_lock(&imx219->mutex); ++ sel->r = *__imx219_get_pad_crop(imx219, state, sel->pad, sel->which); ++ mutex_unlock(&imx219->mutex); ++ return 0; ++ } ++ ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = 0; ++ sel->r.left = 0; ++ sel->r.width = IMX219_NATIVE_WIDTH; ++ sel->r.height = IMX219_NATIVE_HEIGHT; ++ return 0; ++ ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ sel->r.top = IMX219_PIXEL_ARRAY_TOP; ++ sel->r.left = IMX219_PIXEL_ARRAY_LEFT; ++ sel->r.width = IMX219_PIXEL_ARRAY_WIDTH; ++ sel->r.height = IMX219_PIXEL_ARRAY_HEIGHT; ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++static int imx219_start_streaming(struct imx219 *imx219) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); ++ const struct imx219_reg_list *reg_list; ++ int ret; ++ ++ /* Apply default values of current mode */ ++ reg_list = &imx219->mode->reg_list; ++ ret = imx219_write_regs(imx219, reg_list->regs, reg_list->num_of_regs); ++ if (ret) { ++ dev_err(&client->dev, "%s failed to set mode\n", __func__); ++ goto err; ++ } ++ ++ ret = imx219_set_framefmt(imx219); ++ if (ret) { ++ dev_err(&client->dev, "%s failed to set frame format: %d\n", ++ __func__, ret); ++ goto err; ++ } ++ ++ /* Apply customized values from user */ ++ ret = __v4l2_ctrl_handler_setup(imx219->sd.ctrl_handler); ++ if (ret) ++ goto err; ++ ++ /* set stream on register */ ++ ret = imx219_write_reg(imx219, IMX219_REG_MODE_SELECT, ++ IMX219_REG_VALUE_08BIT, IMX219_MODE_STREAMING); ++ if (ret) ++ goto err; ++ ++ /* vflip and hflip cannot change during streaming */ ++ __v4l2_ctrl_grab(imx219->vflip, true); ++ __v4l2_ctrl_grab(imx219->hflip, true); ++ ++ return 0; ++ ++err: ++ return ret; ++} ++ ++static void imx219_stop_streaming(struct imx219 *imx219) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); ++ int ret; ++ ++ /* set stream off register */ ++ ret = imx219_write_reg(imx219, IMX219_REG_MODE_SELECT, ++ IMX219_REG_VALUE_08BIT, IMX219_MODE_STANDBY); ++ if (ret) ++ dev_err(&client->dev, "%s failed to set stream\n", __func__); ++ ++ __v4l2_ctrl_grab(imx219->vflip, false); ++ __v4l2_ctrl_grab(imx219->hflip, false); ++} ++ ++static int imx219_set_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct imx219 *imx219 = to_imx219(sd); ++ struct i2c_client *client = v4l2_get_subdevdata(sd); ++ int ret = 0; ++ ++ mutex_lock(&imx219->mutex); ++ ++ if (enable) { ++ ret = pm_runtime_get_sync(&client->dev); ++ if (ret < 0) { ++ pm_runtime_put_noidle(&client->dev); ++ mutex_unlock(&imx219->mutex); ++ return ret; ++ } ++ ++ if (imx219->streaming) ++ goto unlock; ++ ++ /* ++ * Apply default & customized values ++ * and then start streaming. ++ */ ++ ret = imx219_start_streaming(imx219); ++ if (ret) ++ goto err_unlock; ++ } else { ++ imx219_stop_streaming(imx219); ++ pm_runtime_put(&client->dev); ++ } ++ ++unlock: ++ imx219->streaming += enable ? 1 : -1; ++ WARN_ON(imx219->streaming < 0); ++ ++ mutex_unlock(&imx219->mutex); ++ ++ return ret; ++ ++err_unlock: ++ pm_runtime_put(&client->dev); ++ mutex_unlock(&imx219->mutex); ++ ++ return ret; ++} ++ ++/* Power/clock management functions */ ++static int imx219_power_on(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct imx219 *imx219 = to_imx219(sd); ++ int ret; ++ ++ ret = regulator_bulk_enable(IMX219_NUM_SUPPLIES, imx219->supplies); ++ if (ret) { ++ dev_err(dev, "%s: failed to enable regulators\n", ++ __func__); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(imx219->xclk); ++ if (ret) { ++ dev_err(dev, "%s: failed to enable clock\n", __func__); ++ goto reg_off; ++ } ++ ++ gpiod_set_value_cansleep(imx219->reset_gpio, 1); ++ usleep_range(IMX219_XCLR_MIN_DELAY_US, ++ IMX219_XCLR_MIN_DELAY_US + IMX219_XCLR_DELAY_RANGE_US); ++ ++ return 0; ++ ++reg_off: ++ regulator_bulk_disable(IMX219_NUM_SUPPLIES, imx219->supplies); ++ ++ return ret; ++} ++ ++static int imx219_power_off(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct imx219 *imx219 = to_imx219(sd); ++ ++ gpiod_set_value_cansleep(imx219->reset_gpio, 0); ++ regulator_bulk_disable(IMX219_NUM_SUPPLIES, imx219->supplies); ++ clk_disable_unprepare(imx219->xclk); ++ ++ return 0; ++} ++ ++static int imx219_get_regulators(struct imx219 *imx219) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); ++ unsigned int i; ++ ++ for (i = 0; i < IMX219_NUM_SUPPLIES; i++) ++ imx219->supplies[i].supply = imx219_supply_name[i]; ++ ++ return devm_regulator_bulk_get(&client->dev, ++ IMX219_NUM_SUPPLIES, imx219->supplies); ++} ++ ++/* Verify chip ID */ ++static int imx219_identify_module(struct imx219 *imx219) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); ++ int ret; ++ u32 val; ++ ++ ret = imx219_read_reg(imx219, IMX219_REG_CHIP_ID, ++ IMX219_REG_VALUE_16BIT, &val); ++ if (ret) { ++ dev_err(&client->dev, "failed to read chip id %x\n", ++ IMX219_CHIP_ID); ++ return ret; ++ } ++ ++ if (val != IMX219_CHIP_ID) { ++ dev_err(&client->dev, "chip id mismatch: %x!=%x\n", ++ IMX219_CHIP_ID, val); ++ return -EIO; ++ } ++ ++ dev_err(&client->dev, "%s: chip identifier, got 0x%x\n", ++ __func__, IMX219_CHIP_ID); ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_core_ops imx219_core_ops = { ++ .subscribe_event = v4l2_ctrl_subdev_subscribe_event, ++ .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++}; ++ ++static const struct v4l2_subdev_video_ops imx219_video_ops = { ++ .g_frame_interval = imx219_g_frame_interval, ++ .s_frame_interval = imx219_s_frame_interval, ++ .s_stream = imx219_set_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops imx219_pad_ops = { ++ .enum_mbus_code = imx219_enum_mbus_code, ++ .get_fmt = imx219_get_pad_format, ++ .set_fmt = imx219_set_pad_format, ++ .get_selection = imx219_get_selection, ++ .enum_frame_size = imx219_enum_frame_size, ++ .enum_frame_interval = imx219_enum_frame_interval, ++}; ++ ++static const struct v4l2_subdev_ops imx219_subdev_ops = { ++ .core = &imx219_core_ops, ++ .video = &imx219_video_ops, ++ .pad = &imx219_pad_ops, ++}; ++ ++static const struct v4l2_subdev_internal_ops imx219_internal_ops = { ++ .open = imx219_open, ++}; ++ ++/* Initialize control handlers */ ++static int imx219_init_controls(struct imx219 *imx219) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); ++ struct v4l2_ctrl_handler *ctrl_hdlr; ++ unsigned int height = imx219->mode->height; ++ struct v4l2_fwnode_device_properties props; ++ int exposure_max, exposure_def, hblank; ++ int i, ret; ++ ++ ctrl_hdlr = &imx219->ctrl_handler; ++ ret = v4l2_ctrl_handler_init(ctrl_hdlr, 12); ++ if (ret) ++ return ret; ++ ++ mutex_init(&imx219->mutex); ++ ctrl_hdlr->lock = &imx219->mutex; ++ ++ /* By default, PIXEL_RATE is read only */ ++ imx219->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, ++ V4L2_CID_PIXEL_RATE, IMX219_PIXEL_RATE, ++ IMX219_PIXEL_RATE, 1, IMX219_PIXEL_RATE); ++ ++ imx219->link_freq = ++ v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx219_ctrl_ops, V4L2_CID_LINK_FREQ, ++ ARRAY_SIZE(imx219_link_freq_menu) - 1, 0, imx219_link_freq_menu); ++ if (imx219->link_freq) ++ imx219->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ++ /* Initial vblank/hblank/exposure parameters based on current mode */ ++ imx219->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, ++ V4L2_CID_VBLANK, IMX219_VBLANK_MIN, ++ IMX219_VTS_MAX - height, 1, ++ imx219->mode->vts_def - height); ++ hblank = IMX219_PPL_DEFAULT - imx219->mode->width; ++ imx219->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, ++ V4L2_CID_HBLANK, hblank, hblank, 1, hblank); ++ if (imx219->hblank) ++ imx219->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ exposure_max = imx219->mode->vts_def - 4; ++ exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ? ++ exposure_max : IMX219_EXPOSURE_DEFAULT; ++ imx219->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, ++ V4L2_CID_EXPOSURE, IMX219_EXPOSURE_MIN, exposure_max, ++ IMX219_EXPOSURE_STEP, exposure_def); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, ++ IMX219_ANA_GAIN_MIN, IMX219_ANA_GAIN_MAX, ++ IMX219_ANA_GAIN_STEP, IMX219_ANA_GAIN_DEFAULT); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, V4L2_CID_DIGITAL_GAIN, ++ IMX219_DGTL_GAIN_MIN, IMX219_DGTL_GAIN_MAX, ++ IMX219_DGTL_GAIN_STEP, IMX219_DGTL_GAIN_DEFAULT); ++ ++ imx219->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, ++ V4L2_CID_HFLIP, 0, 1, 1, 0); ++ if (imx219->hflip) ++ imx219->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ++ ++ imx219->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, ++ V4L2_CID_VFLIP, 0, 1, 1, 0); ++ if (imx219->vflip) ++ imx219->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ++ ++ v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx219_ctrl_ops, V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(imx219_test_pattern_menu) - 1, ++ 0, 0, imx219_test_pattern_menu); ++ for (i = 0; i < 4; i++) { ++ /* ++ * The assumption is that ++ * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1 ++ * V4L2_CID_TEST_PATTERN_BLUE == V4L2_CID_TEST_PATTERN_RED + 2 ++ * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3 ++ */ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, ++ V4L2_CID_TEST_PATTERN_RED + i, ++ IMX219_TESTP_COLOUR_MIN, ++ IMX219_TESTP_COLOUR_MAX, ++ IMX219_TESTP_COLOUR_STEP, ++ IMX219_TESTP_COLOUR_MAX); ++ /* The "Solid color" pattern is white by default */ ++ } ++ ++ if (ctrl_hdlr->error) { ++ ret = ctrl_hdlr->error; ++ dev_err(&client->dev, "%s control init failed (%d)\n", ++ __func__, ret); ++ goto error; ++ } ++ ++ ret = v4l2_fwnode_device_parse(&client->dev, &props); ++ if (ret) ++ goto error; ++ ++ ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx219_ctrl_ops, &props); ++ if (ret) ++ goto error; ++ ++ imx219->sd.ctrl_handler = ctrl_hdlr; ++ ++ return 0; ++ ++error: ++ v4l2_ctrl_handler_free(ctrl_hdlr); ++ mutex_destroy(&imx219->mutex); ++ ++ return ret; ++} ++ ++static void imx219_free_controls(struct imx219 *imx219) ++{ ++ v4l2_ctrl_handler_free(imx219->sd.ctrl_handler); ++ mutex_destroy(&imx219->mutex); ++} ++ ++static int imx219_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; ++ } ++ ++ /* Check the link frequency set in device tree */ ++ if (!ep_cfg.nr_of_link_frequencies) { ++ dev_err(dev, "link-frequency property not found in DT\n"); ++ goto error_out; ++ } ++ ++ if (ep_cfg.nr_of_link_frequencies != 1 || ++ ep_cfg.link_frequencies[0] != IMX219_DEFAULT_LINK_FREQ) { ++ dev_err(dev, "Link frequency not supported: %lld\n", ++ ep_cfg.link_frequencies[0]); ++ goto error_out; ++ } ++ ++ ret = 0; ++ ++error_out: ++ v4l2_fwnode_endpoint_free(&ep_cfg); ++ fwnode_handle_put(endpoint); ++ ++ return ret; ++} ++ ++static int imx219_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct imx219 *imx219; ++ int ret; ++ ++ imx219 = devm_kzalloc(&client->dev, sizeof(*imx219), GFP_KERNEL); ++ if (!imx219) ++ return -ENOMEM; ++ ++ v4l2_i2c_subdev_init(&imx219->sd, client, &imx219_subdev_ops); ++ ++ /* Check the hardware configuration in device tree */ ++ if (imx219_check_hwcfg(dev)) ++ return -EINVAL; ++ ++ /* Get system clock (xclk) */ ++ imx219->xclk = devm_clk_get(dev, NULL); ++ if (IS_ERR(imx219->xclk)) { ++ dev_err(dev, "failed to get xclk\n"); ++ return PTR_ERR(imx219->xclk); ++ } ++ ++ imx219->xclk_freq = clk_get_rate(imx219->xclk); ++ if (imx219->xclk_freq != IMX219_XCLK_FREQ) { ++ dev_err(dev, "xclk frequency not supported: %d Hz\n", ++ imx219->xclk_freq); ++ return -EINVAL; ++ } ++ ++ ret = imx219_get_regulators(imx219); ++ if (ret) { ++ dev_err(dev, "failed to get regulators\n"); ++ return ret; ++ } ++ ++ /* Request optional enable pin */ ++ imx219->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); ++ usleep_range(IMX219_XCLR_MIN_DELAY_US, ++ IMX219_XCLR_MIN_DELAY_US + IMX219_XCLR_DELAY_RANGE_US); ++ ++ /* ++ * The sensor must be powered for imx219_identify_module() ++ * to be able to read the CHIP_ID register ++ */ ++ ret = imx219_power_on(dev); ++ if (ret) ++ return ret; ++ ++ ret = imx219_identify_module(imx219); ++ if (ret) ++ goto error_power_off; ++ ++ /* Set default mode to max resolution */ ++ imx219->mode = &supported_modes[0]; ++ imx219->frame_interval.numerator = 1; ++ imx219->frame_interval.denominator = supported_modes[0].fps; ++ ++ /* sensor doesn't enter LP-11 state upon power up until and unless ++ * streaming is started, so upon power up switch the modes to: ++ * streaming -> standby ++ */ ++ ret = imx219_write_reg(imx219, IMX219_REG_MODE_SELECT, ++ IMX219_REG_VALUE_08BIT, IMX219_MODE_STREAMING); ++ if (ret < 0) ++ goto error_power_off; ++ usleep_range(100, 110); ++ ++ /* put sensor back to standby mode */ ++ ret = imx219_write_reg(imx219, IMX219_REG_MODE_SELECT, ++ IMX219_REG_VALUE_08BIT, IMX219_MODE_STANDBY); ++ if (ret < 0) ++ goto error_power_off; ++ usleep_range(100, 110); ++ ++ ret = imx219_init_controls(imx219); ++ if (ret) ++ goto error_power_off; ++ ++ /* Initialize subdev */ ++ imx219->sd.internal_ops = &imx219_internal_ops; ++ imx219->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; ++ imx219->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ++ /* Initialize source pad */ ++ imx219->pad.flags = MEDIA_PAD_FL_SOURCE; ++ ++ /* Initialize default format */ ++ imx219_set_default_format(imx219); ++ ++ ret = media_entity_pads_init(&imx219->sd.entity, 1, &imx219->pad); ++ if (ret) { ++ dev_err(dev, "failed to init entity pads: %d\n", ret); ++ goto error_handler_free; ++ } ++ ++ ret = v4l2_async_register_subdev_sensor(&imx219->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(&imx219->sd.entity); ++ ++error_handler_free: ++ imx219_free_controls(imx219); ++ ++error_power_off: ++ imx219_power_off(dev); ++ ++ return ret; ++} ++ ++static void imx219_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct imx219 *imx219 = to_imx219(sd); ++ ++ v4l2_async_unregister_subdev(sd); ++ media_entity_cleanup(&sd->entity); ++ imx219_free_controls(imx219); ++ ++ pm_runtime_disable(&client->dev); ++ if (!pm_runtime_status_suspended(&client->dev)) ++ imx219_power_off(&client->dev); ++ pm_runtime_set_suspended(&client->dev); ++} ++ ++static const struct of_device_id imx219_dt_ids[] = { ++ { .compatible = "sony,imx219" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, imx219_dt_ids); ++ ++static const struct dev_pm_ops imx219_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) ++ SET_RUNTIME_PM_OPS(imx219_power_off, imx219_power_on, NULL) ++}; ++ ++static struct i2c_driver imx219_i2c_driver = { ++ .driver = { ++ .name = "imx219", ++ .of_match_table = imx219_dt_ids, ++ .pm = &imx219_pm_ops, ++ }, ++ .probe = imx219_probe, ++ .remove = imx219_remove, ++}; ++ ++module_i2c_driver(imx219_i2c_driver); ++ ++MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com"); ++MODULE_DESCRIPTION("Sony IMX219 sensor driver"); ++MODULE_LICENSE("GPL v2"); +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/ov13850_mipi.c +@@ -0,0 +1,1921 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include <linux/clk.h> ++#include <linux/clk-provider.h> ++#include <linux/clkdev.h> ++#include <linux/ctype.h> ++#include <linux/delay.h> ++#include <linux/device.h> ++#include <linux/gpio/consumer.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/of_device.h> ++#include <linux/regulator/consumer.h> ++#include <linux/slab.h> ++#include <linux/types.h> ++#include <media/v4l2-async.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-event.h> ++#include <media/v4l2-fwnode.h> ++#include <media/v4l2-subdev.h> ++#include "stfcamss.h" ++ ++#define OV13850_XCLK_MIN 6000000 ++#define OV13850_XCLK_MAX 54000000 ++ ++#define OV13850_LINK_FREQ_500MHZ 500000000LL ++ ++/** ++ *OV13850 PLL ++ * ++ *PLL1: ++ * ++ *REF_CLK -> /PREDIVP[0] -> /PREDIV[2:0] -> *DIVP[9:8,7:0] -> /DIVM[3:0] -> /DIV_MIPI[1:0] -> PCLK ++ *(6-64M) 0x030A 0x0300 0x0301,0x302 0x0303 0x0304 ++ * `-> MIPI_PHY_CLK ++ * ++ * ++ *PLL2: ++ * 000: 1 ++ * 001: 1.5 ++ * 010: 2 ++ * 011: 2.5 ++ * 100: 3 ++ * 101: 4 ++ * 0: /1 110: 6 ++ * 1: /2 111: 8 ++ *REF_CLK -> /PREDIVP[3] -> /PREDIV[2:0] -> /DIVP[9:0] -> /DIVDAC[3:0] -> DAC_CLK = ++ *(6~64M) 0x3611 ++ * -> /DIVSP[3:0] -> /DIVS[2:0] -> SCLK ++ * ++ * -> /(1+DIVSRAM[3:0]) -> SRAM_CLK ++ */ ++ ++// PREDIVP ++#define OV13850_REG_PLL1_PREDIVP 0x030a ++#define OV13850_PREDIVP_1 0 ++#define OV13850_PREDIVP_2 1 ++ ++// PREDIV ++#define OV13850_REG_PLL1_PREDIV 0x0300 ++#define OV13850_PREDIV_1 0 ++#define OV13850_PREDIV_1_5 1 ++#define OV13850_PREDIV_2 2 ++#define OV13850_PREDIV_2_5 3 ++#define OV13850_PREDIV_3 4 ++#define OV13850_PREDIV_4 5 ++#define OV13850_PREDIV_6 6 ++#define OV13850_PREDIV_8 7 ++ ++// DIVP ++#define OV13850_REG_PLL1_DIVP_H 0x0301 ++#define OV13850_REG_PLL1_DIVP_L 0x0302 ++#define OV13850_REG_PLL1_DIVP OV13850_REG_PLL1_DIVP_H ++ ++// DIVM ++#define OV13850_REG_PLL1_DIVM 0x0303 ++#define OV13850_DIVM(n) ((n)-1) // n=1~16 ++ ++// DIV_MIPI ++#define OV13850_REG_PLL1_DIV_MIPI 0x0304 ++#define OV13850_DIV_MIPI_4 0 ++#define OV13850_DIV_MIPI_5 1 ++#define OV13850_DIV_MIPI_6 2 ++#define OV13850_DIV_MIPI_8 3 ++ ++// system control ++#define OV13850_STREAM_CTRL 0x0100 ++#define OV13850_REG_MIPI_SC 0x300f ++#define OV13850_MIPI_SC_8_BIT 0x0 ++#define OV13850_MIPI_SC_10_BIT 0x1 ++#define OV13850_MIPI_SC_12_BIT 0x2 ++#define OV13850_GET_MIPI_SC_MIPI_BIT(v) ((v) & 0x3) ++#define OV13850_REG_MIPI_SC_CTRL0 0x3012 ++#define OV13850_GET_MIPI_SC_CTRL0_LANE_NUM(v) ((v)>>4 & 0xf) ++ ++// timing ++#define OV13850_REG_H_CROP_START_H 0x3800 ++#define OV13850_REG_H_CROP_START_L 0x3801 ++#define OV13850_REG_H_CROP_START OV13850_REG_H_CROP_START_H ++#define OV13850_REG_V_CROP_START_H 0x3802 ++#define OV13850_REG_V_CROP_START_L 0x3803 ++#define OV13850_REG_V_CROP_START OV13850_REG_V_CROP_START_H ++ ++#define OV13850_REG_H_CROP_END_H 0x3804 ++#define OV13850_REG_H_CROP_END_L 0x3805 ++#define OV13850_REG_H_CROP_END OV13850_REG_H_CROP_END_H ++#define OV13850_REG_V_CROP_END_H 0x3806 ++#define OV13850_REG_V_CROP_END_L 0x3807 ++#define OV13850_REG_V_CROP_END OV13850_REG_V_CROP_END_H ++ ++#define OV13850_REG_H_OUTPUT_SIZE_H 0x3808 ++#define OV13850_REG_H_OUTPUT_SIZE_L 0x3809 ++#define OV13850_REG_H_OUTPUT_SIZE OV13850_REG_H_OUTPUT_SIZE_H ++#define OV13850_REG_V_OUTPUT_SIZE_H 0x380a ++#define OV13850_REG_V_OUTPUT_SIZE_L 0x380b ++#define OV13850_REG_V_OUTPUT_SIZE OV13850_REG_V_OUTPUT_SIZE_H ++ ++#define OV13850_REG_TIMING_HTS_H 0x380c ++#define OV13850_REG_TIMING_HTS_L 0x380d ++#define OV13850_REG_TIMING_HTS OV13850_REG_TIMING_HTS_H ++#define OV13850_REG_TIMING_VTS_H 0x380e ++#define OV13850_REG_TIMING_VTS_L 0x380f ++#define OV13850_REG_TIMING_VTS OV13850_REG_TIMING_VTS_H ++ ++ ++#define OV13850_REG_H_WIN_OFF_H 0x3810 ++#define OV13850_REG_H_WIN_OFF_L 0x3811 ++#define OV13850_REG_V_WIN_OFF_H 0x3812 ++#define OV13850_REG_V_WIN_OFF_L 0x3813 ++ ++#define OV13850_REG_H_INC 0x3814 ++#define OV13850_REG_V_INC 0x3815 ++ ++enum ov13850_mode_id { ++ OV13850_MODE_1080P_1920_1080 = 0, ++ OV13850_NUM_MODES, ++}; ++ ++enum ov13850_frame_rate { ++ OV13850_15_FPS = 0, ++ OV13850_30_FPS, ++ OV13850_60_FPS, ++ OV13850_NUM_FRAMERATES, ++}; ++ ++static const int ov13850_framerates[] = { ++ [OV13850_15_FPS] = 15, ++ [OV13850_30_FPS] = 30, ++ [OV13850_60_FPS] = 60, ++}; ++ ++struct ov13850_pixfmt { ++ u32 code; ++ u32 colorspace; ++}; ++ ++static const struct ov13850_pixfmt ov13850_formats[] = { ++ { MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_COLORSPACE_SRGB, }, ++ { MEDIA_BUS_FMT_SGBRG10_1X10, V4L2_COLORSPACE_SRGB, }, ++ { MEDIA_BUS_FMT_SGRBG10_1X10, V4L2_COLORSPACE_SRGB, }, ++ { MEDIA_BUS_FMT_SRGGB10_1X10, V4L2_COLORSPACE_SRGB, }, ++}; ++ ++/* regulator supplies */ ++static const char * const ov13850_supply_name[] = { ++ "DOVDD", /* Digital I/O (1.8V) supply */ ++ "AVDD", /* Analog (2.8V) supply */ ++ "DVDD", /* Digital Core (1.5V) supply */ ++}; ++ ++#define OV13850_NUM_SUPPLIES ARRAY_SIZE(ov13850_supply_name) ++ ++/* ++ * Image size under 1280 * 960 are SUBSAMPLING ++ * Image size upper 1280 * 960 are SCALING ++ */ ++enum ov13850_downsize_mode { ++ SUBSAMPLING, ++ SCALING, ++}; ++ ++struct reg_value { ++ u16 reg_addr; ++ u8 val; ++ u8 mask; ++ u32 delay_ms; ++}; ++ ++struct ov13850_mode_info { ++ enum ov13850_mode_id id; ++ enum ov13850_downsize_mode dn_mode; ++ u32 hact; ++ u32 htot; ++ u32 vact; ++ u32 vtot; ++ const struct reg_value *reg_data; ++ u32 reg_data_size; ++ u32 max_fps; ++}; ++ ++struct ov13850_ctrls { ++ struct v4l2_ctrl_handler handler; ++ struct v4l2_ctrl *pixel_rate; ++ struct { ++ struct v4l2_ctrl *exposure; ++ }; ++ struct { ++ struct v4l2_ctrl *auto_wb; ++ struct v4l2_ctrl *blue_balance; ++ struct v4l2_ctrl *red_balance; ++ }; ++ struct { ++ struct v4l2_ctrl *anal_gain; ++ }; ++ struct v4l2_ctrl *brightness; ++ struct v4l2_ctrl *light_freq; ++ struct v4l2_ctrl *link_freq; ++ struct v4l2_ctrl *saturation; ++ struct v4l2_ctrl *contrast; ++ struct v4l2_ctrl *hue; ++ struct v4l2_ctrl *test_pattern; ++ struct v4l2_ctrl *hflip; ++ struct v4l2_ctrl *vflip; ++}; ++ ++struct ov13850_dev { ++ struct i2c_client *i2c_client; ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ struct v4l2_fwnode_endpoint ep; /* the parsed DT endpoint info */ ++ struct clk *xclk; /* system clock to OV13850 */ ++ u32 xclk_freq; ++ ++ struct regulator_bulk_data supplies[OV13850_NUM_SUPPLIES]; ++ struct gpio_desc *reset_gpio; ++ struct gpio_desc *pwdn_gpio; ++ bool upside_down; ++ ++ /* lock to protect all members below */ ++ struct mutex lock; ++ ++ int power_count; ++ ++ struct v4l2_mbus_framefmt fmt; ++ bool pending_fmt_change; ++ ++ const struct ov13850_mode_info *current_mode; ++ const struct ov13850_mode_info *last_mode; ++ enum ov13850_frame_rate current_fr; ++ struct v4l2_fract frame_interval; ++ ++ struct ov13850_ctrls ctrls; ++ ++ u32 prev_sysclk, prev_hts; ++ u32 ae_low, ae_high, ae_target; ++ ++ bool pending_mode_change; ++ bool streaming; ++}; ++ ++static inline struct ov13850_dev *to_ov13850_dev(struct v4l2_subdev *sd) ++{ ++ return container_of(sd, struct ov13850_dev, sd); ++} ++ ++static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) ++{ ++ return &container_of(ctrl->handler, struct ov13850_dev, ++ ctrls.handler)->sd; ++} ++ ++/* ov13850 initial register */ ++static const struct reg_value ov13850_init_setting_30fps_1080P[] = { ++ ++}; ++ ++static const struct reg_value ov13850_setting_1080P_1920_1080[] = { ++//;XVCLK=24Mhz, SCLK=4x120Mhz, MIPI 640Mbps, DACCLK=240Mhz ++/* ++ * using quarter size to scale down ++ */ ++ {0x0103, 0x01, 0, 0}, // ; software reset ++ ++ {0x0300, 0x01, 0, 0}, //; PLL ++ {0x0301, 0x00, 0, 0}, //; PLL1_DIVP_hi ++ {0x0302, 0x28, 0, 0}, //; PLL1_DIVP_lo ++ {0x0303, 0x00, 0, 0}, // ; PLL ++ {0x030a, 0x00, 0, 0}, // ; PLL ++ //{0xffff, 20, 0, 0}, ++ {0x300f, 0x11, 0, 0}, // SFC modified, MIPI_SRC, [1:0] 00-8bit, 01-10bit, 10-12bit ++ {0x3010, 0x01, 0, 0}, // ; MIPI PHY ++ {0x3011, 0x76, 0, 0}, // ; MIPI PHY ++ {0x3012, 0x41, 0, 0}, // ; MIPI 4 lane ++ {0x3013, 0x12, 0, 0}, // ; MIPI control ++ {0x3014, 0x11, 0, 0}, // ; MIPI control ++ {0x301f, 0x03, 0, 0}, // ++ {0x3106, 0x00, 0, 0}, // ++ {0x3210, 0x47, 0, 0}, // ++ {0x3500, 0x00, 0, 0}, // ; exposure HH ++ {0x3501, 0x67, 0, 0}, // ; exposure H ++ {0x3502, 0x80, 0, 0}, // ; exposure L ++ {0x3506, 0x00, 0, 0}, // ; short exposure HH ++ {0x3507, 0x02, 0, 0}, // ; short exposure H ++ {0x3508, 0x00, 0, 0}, // ; shour exposure L ++ {0x3509, 0x10, 0, 0},//00},//8}, ++ {0x350a, 0x00, 0, 0}, // ; gain H ++ {0x350b, 0x10, 0, 0}, // ; gain L ++ {0x350e, 0x00, 0, 0}, // ; short gain H ++ {0x350f, 0x10, 0, 0}, // ; short gain L ++ {0x3600, 0x40, 0, 0}, // ; analog control ++ {0x3601, 0xfc, 0, 0}, // ; analog control ++ {0x3602, 0x02, 0, 0}, // ; analog control ++ {0x3603, 0x48, 0, 0}, // ; analog control ++ {0x3604, 0xa5, 0, 0}, // ; analog control ++ {0x3605, 0x9f, 0, 0}, // ; analog control ++ {0x3607, 0x00, 0, 0}, // ; analog control ++ {0x360a, 0x40, 0, 0}, // ; analog control ++ {0x360b, 0x91, 0, 0}, // ; analog control ++ {0x360c, 0x49, 0, 0}, // ; analog control ++ {0x360f, 0x8a, 0, 0}, // ++ {0x3611, 0x10, 0, 0}, // ; PLL2 ++ //{0x3612, 0x23, 0, 0}, // ; PLL2 ++ {0x3612, 0x13, 0, 0}, // ; PLL2 ++ //{0x3613, 0x33, 0, 0}, // ; PLL2 ++ {0x3613, 0x22, 0, 0}, // ; PLL2 ++ //{0xffff, 50, 0, 0}, ++ {0x3614, 0x28, 0, 0}, //[7:0] PLL2_DIVP lo ++ {0x3615, 0x08, 0, 0}, //[7:6] Debug mode, [5:4] N_pump clock div, [3:2] P_pump clock div, [1:0] PLL2_DIVP hi ++ {0x3641, 0x02, 0, 0}, ++ {0x3660, 0x82, 0, 0}, ++ {0x3668, 0x54, 0, 0}, ++ {0x3669, 0x40, 0, 0}, ++ {0x3667, 0xa0, 0, 0}, ++ {0x3702, 0x40, 0, 0}, ++ {0x3703, 0x44, 0, 0}, ++ {0x3704, 0x2c, 0, 0}, ++ {0x3705, 0x24, 0, 0}, ++ {0x3706, 0x50, 0, 0}, ++ {0x3707, 0x44, 0, 0}, ++ {0x3708, 0x3c, 0, 0}, ++ {0x3709, 0x1f, 0, 0}, ++ {0x370a, 0x26, 0, 0}, ++ {0x370b, 0x3c, 0, 0}, ++ {0x3720, 0x66, 0, 0}, ++ {0x3722, 0x84, 0, 0}, ++ {0x3728, 0x40, 0, 0}, ++ {0x372a, 0x00, 0, 0}, ++ {0x372f, 0x90, 0, 0}, ++ {0x3710, 0x28, 0, 0}, ++ {0x3716, 0x03, 0, 0}, ++ {0x3718, 0x10, 0, 0}, ++ {0x3719, 0x08, 0, 0}, ++ {0x371c, 0xfc, 0, 0}, ++ {0x3760, 0x13, 0, 0}, ++ {0x3761, 0x34, 0, 0}, ++ {0x3767, 0x24, 0, 0}, ++ {0x3768, 0x06, 0, 0}, ++ {0x3769, 0x45, 0, 0}, ++ {0x376c, 0x23, 0, 0}, ++ {0x3d84, 0x00, 0, 0}, // ; OTP program disable ++ {0x3d85, 0x17, 0, 0}, // ; OTP power up load data enable, power load setting enable, software load setting ++ {0x3d8c, 0x73, 0, 0}, // ; OTP start address H ++ {0x3d8d, 0xbf, 0, 0}, // ; OTP start address L ++ {0x3800, 0x00, 0, 0}, // ; H crop start H ++ {0x3801, 0x08, 0, 0}, // ; H crop start L ++ {0x3802, 0x00, 0, 0}, // ; V crop start H ++ {0x3803, 0x04, 0, 0}, // ; V crop start L ++ {0x3804, 0x10, 0, 0}, // ; H crop end H ++ {0x3805, 0x97, 0, 0}, // ; H crop end L ++ {0x3806, 0x0c, 0, 0}, // ; V crop end H ++ {0x3807, 0x4b, 0, 0}, // ; V crop end L ++ {0x3808, 0x08, 0, 0}, // ; H output size H ++ {0x3809, 0x40, 0, 0}, // ; H output size L ++ {0x380a, 0x06, 0, 0}, // ; V output size H ++ {0x380b, 0x20, 0, 0}, // ; V output size L ++ {0x380c, 0x25, 0, 0}, // ; HTS H ++ {0x380d, 0x80, 0, 0}, // ; HTS L ++ {0x380e, 0x06, 0, 0}, // ; VTS H ++ {0x380f, 0x80, 0, 0}, // ; VTS L ++ {0x3810, 0x00, 0, 0}, // ; H win off H ++ {0x3811, 0x04, 0, 0}, // ; H win off L ++ {0x3812, 0x00, 0, 0}, // ; V win off H ++ {0x3813, 0x02, 0, 0}, // ; V win off L ++ {0x3814, 0x31, 0, 0}, // ; H inc ++ {0x3815, 0x31, 0, 0}, // ; V inc ++ {0x3820, 0x02, 0, 0}, // ; V flip off, V bin on ++ {0x3821, 0x05, 0, 0}, // ; H mirror on, H bin on ++ {0x3834, 0x00, 0, 0}, // ++ {0x3835, 0x1c, 0, 0}, // ; cut_en, vts_auto, blk_col_dis ++ {0x3836, 0x08, 0, 0}, // ++ {0x3837, 0x02, 0, 0}, // ++ {0x4000, 0xf1, 0, 0},//c1}, // ; BLC offset trig en, format change trig en, gain trig en, exp trig en, median en ++ {0x4001, 0x00, 0, 0}, // ; BLC ++ {0x400b, 0x0c, 0, 0}, // ; BLC ++ {0x4011, 0x00, 0, 0}, // ; BLC ++ {0x401a, 0x00, 0, 0}, // ; BLC ++ {0x401b, 0x00, 0, 0}, // ; BLC ++ {0x401c, 0x00, 0, 0}, // ; BLC ++ {0x401d, 0x00, 0, 0}, // ; BLC ++ {0x4020, 0x00, 0, 0}, // ; BLC ++ {0x4021, 0xe4, 0, 0}, // ; BLC ++ {0x4022, 0x07, 0, 0}, // ; BLC ++ {0x4023, 0x5f, 0, 0}, // ; BLC ++ {0x4024, 0x08, 0, 0}, // ; BLC ++ {0x4025, 0x44, 0, 0}, // ; BLC ++ {0x4026, 0x08, 0, 0}, // ; BLC ++ {0x4027, 0x47, 0, 0}, // ; BLC ++ {0x4028, 0x00, 0, 0}, // ; BLC ++ {0x4029, 0x02, 0, 0}, // ; BLC ++ {0x402a, 0x04, 0, 0}, // ; BLC ++ {0x402b, 0x08, 0, 0}, // ; BLC ++ {0x402c, 0x02, 0, 0}, // ; BLC ++ {0x402d, 0x02, 0, 0}, // ; BLC ++ {0x402e, 0x0c, 0, 0}, // ; BLC ++ {0x402f, 0x08, 0, 0}, // ; BLC ++ {0x403d, 0x2c, 0, 0}, // ++ {0x403f, 0x7f, 0, 0}, // ++ {0x4500, 0x82, 0, 0}, // ; BLC ++ {0x4501, 0x38, 0, 0}, // ; BLC ++ {0x4601, 0x04, 0, 0}, // ++ {0x4602, 0x22, 0, 0}, // ++ {0x4603, 0x01, 0, 0}, //; VFIFO ++ {0x4837, 0x19, 0, 0}, //; MIPI global timing ++ {0x4d00, 0x04, 0, 0}, // ; temperature monitor ++ {0x4d01, 0x42, 0, 0}, // ; temperature monitor ++ {0x4d02, 0xd1, 0, 0}, // ; temperature monitor ++ {0x4d03, 0x90, 0, 0}, // ; temperature monitor ++ {0x4d04, 0x66, 0, 0}, // ; temperature monitor ++ {0x4d05, 0x65, 0, 0}, // ; temperature monitor ++ {0x5000, 0x0e, 0, 0}, // ; windowing enable, BPC on, WPC on, Lenc on ++ {0x5001, 0x03, 0, 0}, // ; BLC enable, MWB on ++ {0x5002, 0x07, 0, 0}, // ++ {0x5013, 0x40, 0, 0}, ++ {0x501c, 0x00, 0, 0}, ++ {0x501d, 0x10, 0, 0}, ++ //{0x5057, 0x56, 0, 0},//add ++ {0x5056, 0x08, 0, 0}, ++ {0x5058, 0x08, 0, 0}, ++ {0x505a, 0x08, 0, 0}, ++ {0x5242, 0x00, 0, 0}, ++ {0x5243, 0xb8, 0, 0}, ++ {0x5244, 0x00, 0, 0}, ++ {0x5245, 0xf9, 0, 0}, ++ {0x5246, 0x00, 0, 0}, ++ {0x5247, 0xf6, 0, 0}, ++ {0x5248, 0x00, 0, 0}, ++ {0x5249, 0xa6, 0, 0}, ++ {0x5300, 0xfc, 0, 0}, ++ {0x5301, 0xdf, 0, 0}, ++ {0x5302, 0x3f, 0, 0}, ++ {0x5303, 0x08, 0, 0}, ++ {0x5304, 0x0c, 0, 0}, ++ {0x5305, 0x10, 0, 0}, ++ {0x5306, 0x20, 0, 0}, ++ {0x5307, 0x40, 0, 0}, ++ {0x5308, 0x08, 0, 0}, ++ {0x5309, 0x08, 0, 0}, ++ {0x530a, 0x02, 0, 0}, ++ {0x530b, 0x01, 0, 0}, ++ {0x530c, 0x01, 0, 0}, ++ {0x530d, 0x0c, 0, 0}, ++ {0x530e, 0x02, 0, 0}, ++ {0x530f, 0x01, 0, 0}, ++ {0x5310, 0x01, 0, 0}, ++ {0x5400, 0x00, 0, 0}, ++ {0x5401, 0x61, 0, 0}, ++ {0x5402, 0x00, 0, 0}, ++ {0x5403, 0x00, 0, 0}, ++ {0x5404, 0x00, 0, 0}, ++ {0x5405, 0x40, 0, 0}, ++ {0x540c, 0x05, 0, 0}, ++ {0x5b00, 0x00, 0, 0}, ++ {0x5b01, 0x00, 0, 0}, ++ {0x5b02, 0x01, 0, 0}, ++ {0x5b03, 0xff, 0, 0}, ++ {0x5b04, 0x02, 0, 0}, ++ {0x5b05, 0x6c, 0, 0}, ++ {0x5b09, 0x02, 0, 0}, // ++ //{0x5e00, 0x00, 0, 0}, // ; test pattern disable ++ //{0x5e00, 0x80, 0, 0}, // ; test pattern enable ++ {0x5e10, 0x1c, 0, 0}, // ; ISP test disable ++ ++ //{0x0300, 0x01, 0, 0},// ; PLL ++ //{0x0302, 0x28, 0, 0},// ; PLL ++ //{0xffff, 50, 0, 0}, ++ {0x3501, 0x67, 0, 0},// ; Exposure H ++ {0x370a, 0x26, 0, 0},// ++ {0x372a, 0x00, 0, 0}, ++ {0x372f, 0x90, 0, 0}, ++ {0x3801, 0x08, 0, 0}, //; H crop start L ++ {0x3803, 0x04, 0, 0}, //; V crop start L ++ {0x3805, 0x97, 0, 0}, //; H crop end L ++ {0x3807, 0x4b, 0, 0}, //; V crop end L ++ {0x3808, 0x08, 0, 0}, //; H output size H ++ {0x3809, 0x40, 0, 0}, //; H output size L ++ {0x380a, 0x06, 0, 0}, //; V output size H ++ {0x380b, 0x20, 0, 0}, //; V output size L ++ {0x380c, 0x25, 0, 0}, //; HTS H ++ {0x380d, 0x80, 0, 0}, //; HTS L ++ {0x380e, 0x0a, 0, 0},//6}, //; VTS H ++ {0x380f, 0x80, 0, 0}, //; VTS L ++ {0x3813, 0x02, 0, 0}, //; V win off ++ {0x3814, 0x31, 0, 0}, //; H inc ++ {0x3815, 0x31, 0, 0}, //; V inc ++ {0x3820, 0x02, 0, 0}, //; V flip off, V bin on ++ {0x3821, 0x05, 0, 0}, //; H mirror on, H bin on ++ {0x3836, 0x08, 0, 0}, // ++ {0x3837, 0x02, 0, 0}, // ++ {0x4020, 0x00, 0, 0}, // ++ {0x4021, 0xe4, 0, 0}, // ++ {0x4022, 0x07, 0, 0}, // ++ {0x4023, 0x5f, 0, 0}, // ++ {0x4024, 0x08, 0, 0}, // ++ {0x4025, 0x44, 0, 0}, // ++ {0x4026, 0x08, 0, 0}, // ++ {0x4027, 0x47, 0, 0}, // ++ {0x4603, 0x01, 0, 0}, //; VFIFO ++ {0x4837, 0x19, 0, 0}, //; MIPI global timing ++ {0x4802, 0x42, 0, 0}, //default 0x00 ++ {0x481a, 0x00, 0, 0}, ++ {0x481b, 0x1c, 0, 0}, //default 0x3c prepare ++ {0x4826, 0x12, 0, 0}, //default 0x32 trail ++ {0x5401, 0x61, 0, 0}, // ++ {0x5405, 0x40, 0, 0}, // ++ ++ //{0xffff, 200, 0, 0}, ++ //{0xffff, 200, 0, 0}, ++ //{0xffff, 200, 0, 0}, ++ ++ //{0x0100, 0x01, 0, 0}, //; wake up, streaming ++}; ++ ++/* power-on sensor init reg table */ ++static const struct ov13850_mode_info ov13850_mode_init_data = { ++ OV13850_MODE_1080P_1920_1080, SCALING, ++ 1920, 0x6e0, 1080, 0x470, ++ ov13850_init_setting_30fps_1080P, ++ ARRAY_SIZE(ov13850_init_setting_30fps_1080P), ++ OV13850_30_FPS, ++}; ++ ++static const struct ov13850_mode_info ++ov13850_mode_data[OV13850_NUM_MODES] = { ++ {OV13850_MODE_1080P_1920_1080, SCALING, ++ 1920, 0x6e0, 1080, 0x470, ++ ov13850_setting_1080P_1920_1080, ++ ARRAY_SIZE(ov13850_setting_1080P_1920_1080), ++ OV13850_30_FPS}, ++}; ++ ++static int ov13850_write_reg(struct ov13850_dev *sensor, u16 reg, u8 val) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ struct i2c_msg msg; ++ u8 buf[3]; ++ int ret; ++ ++ buf[0] = reg >> 8; ++ buf[1] = reg & 0xff; ++ buf[2] = val; ++ ++ msg.addr = client->addr; ++ msg.flags = client->flags; ++ msg.buf = buf; ++ msg.len = sizeof(buf); ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ if (ret < 0) { ++ dev_err(&client->dev, "%s: error: reg=%x, val=%x\n", ++ __func__, reg, val); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ov13850_read_reg(struct ov13850_dev *sensor, u16 reg, u8 *val) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ struct i2c_msg msg[2]; ++ u8 buf[2]; ++ int ret; ++ ++ buf[0] = reg >> 8; ++ buf[1] = reg & 0xff; ++ ++ msg[0].addr = client->addr; ++ msg[0].flags = client->flags; ++ msg[0].buf = buf; ++ msg[0].len = sizeof(buf); ++ ++ msg[1].addr = client->addr; ++ msg[1].flags = client->flags | I2C_M_RD; ++ msg[1].buf = buf; ++ msg[1].len = 1; ++ ++ ret = i2c_transfer(client->adapter, msg, 2); ++ if (ret < 0) { ++ dev_err(&client->dev, "%s: error: reg=%x\n", ++ __func__, reg); ++ return ret; ++ } ++ ++ *val = buf[0]; ++ return 0; ++} ++ ++static int ov13850_read_reg16(struct ov13850_dev *sensor, u16 reg, u16 *val) ++{ ++ u8 hi, lo; ++ int ret; ++ ++ ret = ov13850_read_reg(sensor, reg, &hi); ++ if (ret) ++ return ret; ++ ret = ov13850_read_reg(sensor, reg + 1, &lo); ++ if (ret) ++ return ret; ++ ++ *val = ((u16)hi << 8) | (u16)lo; ++ return 0; ++} ++ ++static int ov13850_write_reg16(struct ov13850_dev *sensor, u16 reg, u16 val) ++{ ++ int ret; ++ ++ ret = ov13850_write_reg(sensor, reg, val >> 8); ++ if (ret) ++ return ret; ++ ++ return ov13850_write_reg(sensor, reg + 1, val & 0xff); ++} ++ ++static int ov13850_mod_reg(struct ov13850_dev *sensor, u16 reg, ++ u8 mask, u8 val) ++{ ++ u8 readval; ++ int ret; ++ ++ ret = ov13850_read_reg(sensor, reg, &readval); ++ if (ret) ++ return ret; ++ ++ readval &= ~mask; ++ val &= mask; ++ val |= readval; ++ ++ return ov13850_write_reg(sensor, reg, val); ++} ++ ++static int ov13850_set_timings(struct ov13850_dev *sensor, ++ const struct ov13850_mode_info *mode) ++{ ++ int ret; ++ ++ ret = ov13850_write_reg16(sensor, OV13850_REG_H_OUTPUT_SIZE, mode->hact); ++ if (ret < 0) ++ return ret; ++ ++ ret = ov13850_write_reg16(sensor, OV13850_REG_V_OUTPUT_SIZE, mode->vact); ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++static int ov13850_load_regs(struct ov13850_dev *sensor, ++ const struct ov13850_mode_info *mode) ++{ ++ const struct reg_value *regs = mode->reg_data; ++ unsigned int i; ++ u32 delay_ms; ++ u16 reg_addr; ++ u8 mask, val; ++ int ret = 0; ++ ++ st_info(ST_SENSOR, "%s, mode = 0x%x\n", __func__, mode->id); ++ for (i = 0; i < mode->reg_data_size; ++i, ++regs) { ++ delay_ms = regs->delay_ms; ++ reg_addr = regs->reg_addr; ++ val = regs->val; ++ mask = regs->mask; ++ ++ if (mask) ++ ret = ov13850_mod_reg(sensor, reg_addr, mask, val); ++ else ++ ret = ov13850_write_reg(sensor, reg_addr, val); ++ if (ret) ++ break; ++ ++ if (delay_ms) ++ usleep_range(1000 * delay_ms, 1000 * delay_ms + 100); ++ } ++ ++ return ov13850_set_timings(sensor, mode); ++} ++ ++ ++ ++static int ov13850_get_gain(struct ov13850_dev *sensor) ++{ ++ u32 gain = 0; ++ return gain; ++} ++ ++static int ov13850_set_gain(struct ov13850_dev *sensor, int gain) ++{ ++ return 0; ++} ++ ++static int ov13850_set_stream_mipi(struct ov13850_dev *sensor, bool on) ++{ ++ return 0; ++} ++ ++static int ov13850_get_sysclk(struct ov13850_dev *sensor) ++{ ++ return 0; ++} ++ ++static int ov13850_set_night_mode(struct ov13850_dev *sensor) ++{ ++ return 0; ++} ++ ++static int ov13850_get_hts(struct ov13850_dev *sensor) ++{ ++ /* read HTS from register settings */ ++ u16 hts; ++ int ret; ++ ++ ret = ov13850_read_reg16(sensor, OV13850_REG_TIMING_HTS, &hts); ++ if (ret) ++ return ret; ++ return hts; ++} ++ ++static int ov13850_set_hts(struct ov13850_dev *sensor, int hts) ++{ ++ return ov13850_write_reg16(sensor, OV13850_REG_TIMING_HTS, hts); ++} ++ ++ ++static int ov13850_get_vts(struct ov13850_dev *sensor) ++{ ++ u16 vts; ++ int ret; ++ ++ ret = ov13850_read_reg16(sensor, OV13850_REG_TIMING_VTS, &vts); ++ if (ret) ++ return ret; ++ return vts; ++} ++ ++static int ov13850_set_vts(struct ov13850_dev *sensor, int vts) ++{ ++ return ov13850_write_reg16(sensor, OV13850_REG_TIMING_VTS, vts); ++} ++ ++static int ov13850_get_light_freq(struct ov13850_dev *sensor) ++{ ++ return 0; ++} ++ ++static int ov13850_set_bandingfilter(struct ov13850_dev *sensor) ++{ ++ return 0; ++} ++ ++static int ov13850_set_ae_target(struct ov13850_dev *sensor, int target) ++{ ++ return 0; ++} ++ ++static int ov13850_get_binning(struct ov13850_dev *sensor) ++{ ++ return 0; ++} ++ ++static int ov13850_set_binning(struct ov13850_dev *sensor, bool enable) ++{ ++ return 0; ++} ++ ++static const struct ov13850_mode_info * ++ov13850_find_mode(struct ov13850_dev *sensor, enum ov13850_frame_rate fr, ++ int width, int height, bool nearest) ++{ ++ const struct ov13850_mode_info *mode; ++ ++ mode = v4l2_find_nearest_size(ov13850_mode_data, ++ ARRAY_SIZE(ov13850_mode_data), ++ hact, vact, ++ width, height); ++ ++ if (!mode || ++ (!nearest && (mode->hact != width || mode->vact != height))) ++ return NULL; ++ ++ /* Check to see if the current mode exceeds the max frame rate */ ++ if (ov13850_framerates[fr] > ov13850_framerates[mode->max_fps]) ++ return NULL; ++ ++ return mode; ++} ++ ++static u64 ov13850_calc_pixel_rate(struct ov13850_dev *sensor) ++{ ++ u64 rate; ++ ++ rate = sensor->current_mode->vact * sensor->current_mode->hact; ++ rate *= ov13850_framerates[sensor->current_fr]; ++ ++ return rate; ++} ++ ++/* ++ * After trying the various combinations, reading various ++ * documentations spread around the net, and from the various ++ * feedback, the clock tree is probably as follows: ++ * ++ * +--------------+ ++ * | Ext. Clock | ++ * +-+------------+ ++ * | +----------+ ++ * +->| PLL1 | - reg 0x030a, bit0 for the pre-dividerp ++ * +-+--------+ - reg 0x0300, bits 0-2 for the pre-divider ++ * +-+--------+ - reg 0x0301~0x0302, for the multiplier ++ * | +--------------+ ++ * +->| MIPI Divider | - reg 0x0303, bits 0-3 for the pre-divider ++ * | +---------> MIPI PHY CLK ++ * | +-----+ ++ * | +->| PLL1_DIV_MIPI | - reg 0x0304, bits 0-1 for the divider ++ * | +----------------> PCLK ++ * | +-----+ ++ * ++ * +--------------+ ++ * | Ext. Clock | ++ * +-+------------+ ++ * | +----------+ ++ * +->| PLL2 | - reg 0x0311, bit0 for the pre-dividerp ++ * +-+--------+ - reg 0x030b, bits 0-2 for the pre-divider ++ * +-+--------+ - reg 0x030c~0x030d, for the multiplier ++ * | +--------------+ ++ * +->| SCLK Divider | - reg 0x030F, bits 0-3 for the pre-divider ++ * +-+--------+ - reg 0x030E, bits 0-2 for the divider ++ * | +---------> SCLK ++ * ++ * | +-----+ ++ * +->| DAC Divider | - reg 0x0312, bits 0-3 for the divider ++ * | +----------------> DACCLK ++ ** ++ */ ++ ++/* ++ * ov13850_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: ++ * ++ * - mipi_pclk = bpl / 2; ( / 2 is for CSI-2 DDR) ++ * - mipi_phy_clk = mipi_pclk * PLL1_DIV_MIPI; ++ * ++ * with these fixed parameters: ++ * PLL1_PREDIVP = 1; ++ * PLL1_PREDIV = 1; (MIPI_BIT_MODE == 8 ? 2 : 2,5); ++ * PLL1_DIVM = 1; ++ * PLL1_DIV_MIPI = 4; ++ * ++ * FIXME: this have been tested with 10-bit raw 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 ov13850_set_mipi_pclk(struct ov13850_dev *sensor, ++ unsigned long rate) ++{ ++ ++ return 0; ++} ++ ++/* ++ * if sensor changes inside scaling or subsampling ++ * change mode directly ++ */ ++static int ov13850_set_mode_direct(struct ov13850_dev *sensor, ++ const struct ov13850_mode_info *mode) ++{ ++ if (!mode->reg_data) ++ return -EINVAL; ++ ++ /* Write capture setting */ ++ return ov13850_load_regs(sensor, mode); ++} ++ ++static int ov13850_set_mode(struct ov13850_dev *sensor) ++{ ++ const struct ov13850_mode_info *mode = sensor->current_mode; ++ const struct ov13850_mode_info *orig_mode = sensor->last_mode; ++ int ret = 0; ++ ++ ret = ov13850_set_mode_direct(sensor, mode); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * we support have 10 bits raw RGB(mipi) ++ */ ++ if (sensor->ep.bus_type == V4L2_MBUS_CSI2_DPHY) ++ ret = ov13850_set_mipi_pclk(sensor, 0); ++ ++ if (ret < 0) ++ return 0; ++ ++ sensor->pending_mode_change = false; ++ sensor->last_mode = mode; ++ return 0; ++} ++ ++static int ov13850_set_framefmt(struct ov13850_dev *sensor, ++ struct v4l2_mbus_framefmt *format); ++ ++/* restore the last set video mode after chip power-on */ ++static int ov13850_restore_mode(struct ov13850_dev *sensor) ++{ ++ int ret; ++ ++ /* first load the initial register values */ ++ ret = ov13850_load_regs(sensor, &ov13850_mode_init_data); ++ if (ret < 0) ++ return ret; ++ sensor->last_mode = &ov13850_mode_init_data; ++ ++ /* now restore the last capture mode */ ++ ret = ov13850_set_mode(sensor); ++ if (ret < 0) ++ return ret; ++ ++ return ov13850_set_framefmt(sensor, &sensor->fmt); ++} ++ ++static void ov13850_power(struct ov13850_dev *sensor, bool enable) ++{ ++ if (!sensor->pwdn_gpio) ++ return; ++ if (enable) { ++ gpiod_set_value_cansleep(sensor->pwdn_gpio, 0); ++ gpiod_set_value_cansleep(sensor->pwdn_gpio, 1); ++ } else { ++ gpiod_set_value_cansleep(sensor->pwdn_gpio, 0); ++ } ++ ++ mdelay(100); ++} ++ ++static void ov13850_reset(struct ov13850_dev *sensor) ++{ ++ if (!sensor->reset_gpio) ++ return; ++ ++ gpiod_set_value_cansleep(sensor->reset_gpio, 0); ++ gpiod_set_value_cansleep(sensor->reset_gpio, 1); ++ mdelay(100); ++} ++ ++static int ov13850_set_power_on(struct ov13850_dev *sensor) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ int ret; ++ ++ ret = clk_prepare_enable(sensor->xclk); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to enable clock\n", ++ __func__); ++ return ret; ++ } ++ ++ ret = regulator_bulk_enable(OV13850_NUM_SUPPLIES, ++ sensor->supplies); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to enable regulators\n", ++ __func__); ++ goto xclk_off; ++ } ++ ++ ov13850_reset(sensor); ++ ov13850_power(sensor, true); ++ ++ return 0; ++ ++xclk_off: ++ clk_disable_unprepare(sensor->xclk); ++ return ret; ++} ++ ++static void ov13850_set_power_off(struct ov13850_dev *sensor) ++{ ++ ov13850_power(sensor, false); ++ regulator_bulk_disable(OV13850_NUM_SUPPLIES, sensor->supplies); ++ clk_disable_unprepare(sensor->xclk); ++} ++ ++static int ov13850_set_power_mipi(struct ov13850_dev *sensor, bool on) ++{ ++ return 0; ++} ++ ++static int ov13850_set_power(struct ov13850_dev *sensor, bool on) ++{ ++ int ret = 0; ++ u16 chip_id; ++ ++ if (on) { ++ ret = ov13850_set_power_on(sensor); ++ if (ret) ++ return ret; ++ ++#ifdef UNUSED_CODE ++ ret = ov13850_read_reg16(sensor, OV13850_REG_CHIP_ID, &chip_id); ++ if (ret) { ++ dev_err(&sensor->i2c_client->dev, "%s: failed to read chip identifier\n", ++ __func__); ++ ret = -ENODEV; ++ goto power_off; ++ } ++ ++ if (chip_id != OV13850_CHIP_ID) { ++ dev_err(&sensor->i2c_client->dev, ++ "%s: wrong chip identifier, expected 0x%x, got 0x%x\n", ++ __func__, OV13850_CHIP_ID, chip_id); ++ ret = -ENXIO; ++ goto power_off; ++ } ++ dev_err(&sensor->i2c_client->dev, "%s: chip identifier, got 0x%x\n", ++ __func__, chip_id); ++#endif ++ ++ ret = ov13850_restore_mode(sensor); ++ if (ret) ++ goto power_off; ++ } ++ ++ if (sensor->ep.bus_type == V4L2_MBUS_CSI2_DPHY) ++ ret = ov13850_set_power_mipi(sensor, on); ++ if (ret) ++ goto power_off; ++ ++ if (!on) ++ ov13850_set_power_off(sensor); ++ ++ return 0; ++ ++power_off: ++ ov13850_set_power_off(sensor); ++ return ret; ++} ++ ++static int ov13850_s_power(struct v4l2_subdev *sd, int on) ++{ ++ struct ov13850_dev *sensor = to_ov13850_dev(sd); ++ int ret = 0; ++ ++ mutex_lock(&sensor->lock); ++ ++ /* ++ * If the power count is modified from 0 to != 0 or from != 0 to 0, ++ * update the power state. ++ */ ++ if (sensor->power_count == !on) { ++ ret = ov13850_set_power(sensor, !!on); ++ if (ret) ++ goto out; ++ } ++ ++ /* Update the power count. */ ++ sensor->power_count += on ? 1 : -1; ++ WARN_ON(sensor->power_count < 0); ++out: ++ mutex_unlock(&sensor->lock); ++ ++ if (on && !ret && sensor->power_count == 1) { ++ /* restore controls */ ++ ret = v4l2_ctrl_handler_setup(&sensor->ctrls.handler); ++ } ++ ++ return ret; ++} ++ ++static int ov13850_try_frame_interval(struct ov13850_dev *sensor, ++ struct v4l2_fract *fi, ++ u32 width, u32 height) ++{ ++ const struct ov13850_mode_info *mode; ++ enum ov13850_frame_rate rate = OV13850_15_FPS; ++ int minfps, maxfps, best_fps, fps; ++ int i; ++ ++ minfps = ov13850_framerates[OV13850_15_FPS]; ++ maxfps = ov13850_framerates[OV13850_NUM_FRAMERATES - 1]; ++ ++ if (fi->numerator == 0) { ++ fi->denominator = maxfps; ++ fi->numerator = 1; ++ rate = OV13850_60_FPS; ++ goto find_mode; ++ } ++ ++ fps = clamp_val(DIV_ROUND_CLOSEST(fi->denominator, fi->numerator), ++ minfps, maxfps); ++ ++ best_fps = minfps; ++ for (i = 0; i < ARRAY_SIZE(ov13850_framerates); i++) { ++ int curr_fps = ov13850_framerates[i]; ++ ++ if (abs(curr_fps - fps) < abs(best_fps - fps)) { ++ best_fps = curr_fps; ++ rate = i; ++ } ++ } ++ st_info(ST_SENSOR, "best_fps = %d, fps = %d\n", best_fps, fps); ++ ++ fi->numerator = 1; ++ fi->denominator = best_fps; ++ ++find_mode: ++ mode = ov13850_find_mode(sensor, rate, width, height, false); ++ return mode ? rate : -EINVAL; ++} ++ ++static int ov13850_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ if (code->pad != 0) ++ return -EINVAL; ++ ++ if (code->index >= ARRAY_SIZE(ov13850_formats)) ++ return -EINVAL; ++ ++ code->code = ov13850_formats[code->index].code; ++ return 0; ++} ++ ++static int ov13850_get_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *format) ++{ ++ struct ov13850_dev *sensor = to_ov13850_dev(sd); ++ struct v4l2_mbus_framefmt *fmt; ++ ++ if (format->pad != 0) ++ return -EINVAL; ++ ++ mutex_lock(&sensor->lock); ++ ++ if (format->which == V4L2_SUBDEV_FORMAT_TRY) ++ fmt = v4l2_subdev_get_try_format(&sensor->sd, state, ++ format->pad); ++ else ++ fmt = &sensor->fmt; ++ ++ format->format = *fmt; ++ ++ mutex_unlock(&sensor->lock); ++ ++ return 0; ++} ++ ++static int ov13850_try_fmt_internal(struct v4l2_subdev *sd, ++ struct v4l2_mbus_framefmt *fmt, ++ enum ov13850_frame_rate fr, ++ const struct ov13850_mode_info **new_mode) ++{ ++ struct ov13850_dev *sensor = to_ov13850_dev(sd); ++ const struct ov13850_mode_info *mode; ++ int i; ++ ++ mode = ov13850_find_mode(sensor, fr, fmt->width, fmt->height, true); ++ if (!mode) ++ return -EINVAL; ++ fmt->width = mode->hact; ++ fmt->height = mode->vact; ++ ++ if (new_mode) ++ *new_mode = mode; ++ ++ for (i = 0; i < ARRAY_SIZE(ov13850_formats); i++) ++ if (ov13850_formats[i].code == fmt->code) ++ break; ++ if (i >= ARRAY_SIZE(ov13850_formats)) ++ i = 0; ++ ++ fmt->code = ov13850_formats[i].code; ++ fmt->colorspace = ov13850_formats[i].colorspace; ++ 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); ++ ++ return 0; ++} ++ ++static int ov13850_set_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *format) ++{ ++ struct ov13850_dev *sensor = to_ov13850_dev(sd); ++ const struct ov13850_mode_info *new_mode; ++ struct v4l2_mbus_framefmt *mbus_fmt = &format->format; ++ struct v4l2_mbus_framefmt *fmt; ++ int ret; ++ ++ if (format->pad != 0) ++ return -EINVAL; ++ ++ mutex_lock(&sensor->lock); ++ ++ if (sensor->streaming) { ++ ret = -EBUSY; ++ goto out; ++ } ++ ++ ret = ov13850_try_fmt_internal(sd, mbus_fmt, 0, &new_mode); ++ if (ret) ++ goto out; ++ ++ if (format->which == V4L2_SUBDEV_FORMAT_TRY) ++ fmt = v4l2_subdev_get_try_format(sd, state, 0); ++ else ++ fmt = &sensor->fmt; ++ ++ if (mbus_fmt->code != sensor->fmt.code) ++ sensor->pending_fmt_change = true; ++ ++ *fmt = *mbus_fmt; ++ ++ if (new_mode != sensor->current_mode) { ++ sensor->current_mode = new_mode; ++ sensor->pending_mode_change = true; ++ } ++ ++ if (new_mode->max_fps < sensor->current_fr) { ++ sensor->current_fr = new_mode->max_fps; ++ sensor->frame_interval.numerator = 1; ++ sensor->frame_interval.denominator = ++ ov13850_framerates[sensor->current_fr]; ++ sensor->current_mode = new_mode; ++ sensor->pending_mode_change = true; ++ } ++ ++ __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, ++ ov13850_calc_pixel_rate(sensor)); ++out: ++ mutex_unlock(&sensor->lock); ++ return ret; ++} ++ ++static int ov13850_set_framefmt(struct ov13850_dev *sensor, ++ struct v4l2_mbus_framefmt *format) ++{ ++ u8 fmt; ++ ++ switch (format->code) { ++ /* Raw, BGBG... / GRGR... */ ++ case MEDIA_BUS_FMT_SBGGR8_1X8: ++ case MEDIA_BUS_FMT_SGBRG8_1X8: ++ case MEDIA_BUS_FMT_SGRBG8_1X8: ++ case MEDIA_BUS_FMT_SRGGB8_1X8: ++ fmt = 0x0; ++ break; ++ case MEDIA_BUS_FMT_SBGGR10_1X10: ++ case MEDIA_BUS_FMT_SGBRG10_1X10: ++ case MEDIA_BUS_FMT_SGRBG10_1X10: ++ case MEDIA_BUS_FMT_SRGGB10_1X10: ++ fmt = 0x1; ++ case MEDIA_BUS_FMT_SBGGR12_1X12: ++ case MEDIA_BUS_FMT_SGBRG12_1X12: ++ case MEDIA_BUS_FMT_SGRBG12_1X12: ++ case MEDIA_BUS_FMT_SRGGB12_1X12: ++ fmt = 0x2; ++ default: ++ return -EINVAL; ++ } ++ ++ return ov13850_mod_reg(sensor, OV13850_REG_MIPI_SC, ++ BIT(1) | BIT(0), fmt); ++} ++ ++/* ++ * Sensor Controls. ++ */ ++ ++static int ov13850_set_ctrl_hue(struct ov13850_dev *sensor, int value) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int ov13850_set_ctrl_contrast(struct ov13850_dev *sensor, int value) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int ov13850_set_ctrl_saturation(struct ov13850_dev *sensor, int value) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int ov13850_set_ctrl_white_balance(struct ov13850_dev *sensor, int awb) ++{ ++ struct ov13850_ctrls *ctrls = &sensor->ctrls; ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int ov13850_set_ctrl_exposure(struct ov13850_dev *sensor, ++ enum v4l2_exposure_auto_type auto_exposure) ++{ ++ struct ov13850_ctrls *ctrls = &sensor->ctrls; ++ int ret = 0; ++ ++ return ret; ++} ++ ++static const s64 link_freq_menu_items[] = { ++ OV13850_LINK_FREQ_500MHZ ++}; ++ ++static const char * const test_pattern_menu[] = { ++ "Disabled", ++ "Color bars", ++ "Color bars w/ rolling bar", ++ "Color squares", ++ "Color squares w/ rolling bar", ++}; ++ ++static int ov13850_set_ctrl_test_pattern(struct ov13850_dev *sensor, int value) ++{ ++ return 0; ++} ++ ++static int ov13850_set_ctrl_light_freq(struct ov13850_dev *sensor, int value) ++{ ++ return 0; ++} ++ ++static int ov13850_set_ctrl_hflip(struct ov13850_dev *sensor, int value) ++{ ++ return 0; ++} ++ ++static int ov13850_set_ctrl_vflip(struct ov13850_dev *sensor, int value) ++{ ++ return 0; ++} ++ ++static int ov13850_g_volatile_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct v4l2_subdev *sd = ctrl_to_sd(ctrl); ++ struct ov13850_dev *sensor = to_ov13850_dev(sd); ++ int val; ++ ++ /* v4l2_ctrl_lock() locks our own mutex */ ++ ++ switch (ctrl->id) { ++ case V4L2_CID_ANALOGUE_GAIN: ++ val = ov13850_get_gain(sensor); ++ break; ++ } ++ ++ return 0; ++} ++ ++static int ov13850_s_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct v4l2_subdev *sd = ctrl_to_sd(ctrl); ++ struct ov13850_dev *sensor = to_ov13850_dev(sd); ++ int ret; ++ ++ /* v4l2_ctrl_lock() locks our own mutex */ ++ ++ /* ++ * If the device is not powered up by the host driver do ++ * not apply any controls to H/W at this time. Instead ++ * the controls will be restored right after power-up. ++ */ ++ if (sensor->power_count == 0) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = ov13850_set_gain(sensor, ctrl->val); ++ break; ++ case V4L2_CID_EXPOSURE: ++ ret = ov13850_set_ctrl_exposure(sensor, V4L2_EXPOSURE_MANUAL); ++ break; ++ case V4L2_CID_AUTO_WHITE_BALANCE: ++ ret = ov13850_set_ctrl_white_balance(sensor, ctrl->val); ++ break; ++ case V4L2_CID_HUE: ++ ret = ov13850_set_ctrl_hue(sensor, ctrl->val); ++ break; ++ case V4L2_CID_CONTRAST: ++ ret = ov13850_set_ctrl_contrast(sensor, ctrl->val); ++ break; ++ case V4L2_CID_SATURATION: ++ ret = ov13850_set_ctrl_saturation(sensor, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN: ++ ret = ov13850_set_ctrl_test_pattern(sensor, ctrl->val); ++ break; ++ case V4L2_CID_POWER_LINE_FREQUENCY: ++ ret = ov13850_set_ctrl_light_freq(sensor, ctrl->val); ++ break; ++ case V4L2_CID_HFLIP: ++ ret = ov13850_set_ctrl_hflip(sensor, ctrl->val); ++ break; ++ case V4L2_CID_VFLIP: ++ ret = ov13850_set_ctrl_vflip(sensor, ctrl->val); ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops ov13850_ctrl_ops = { ++ .g_volatile_ctrl = ov13850_g_volatile_ctrl, ++ .s_ctrl = ov13850_s_ctrl, ++}; ++ ++static int ov13850_init_controls(struct ov13850_dev *sensor) ++{ ++ const struct v4l2_ctrl_ops *ops = &ov13850_ctrl_ops; ++ struct ov13850_ctrls *ctrls = &sensor->ctrls; ++ struct v4l2_ctrl_handler *hdl = &ctrls->handler; ++ int ret; ++ ++ v4l2_ctrl_handler_init(hdl, 32); ++ ++ /* we can use our own mutex for the ctrl lock */ ++ hdl->lock = &sensor->lock; ++ ++ /* Clock related controls */ ++ ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, ++ 0, INT_MAX, 1, ++ ov13850_calc_pixel_rate(sensor)); ++ ++ /* Auto/manual white balance */ ++ ctrls->auto_wb = v4l2_ctrl_new_std(hdl, ops, ++ V4L2_CID_AUTO_WHITE_BALANCE, ++ 0, 1, 1, 0); ++ ctrls->blue_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BLUE_BALANCE, ++ 0, 4095, 1, 1024); ++ ctrls->red_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE, ++ 0, 4095, 1, 1024); ++ ++ ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, ++ 4, 0xfff8, 1, 0x4c00); ++ ctrls->anal_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, ++ 0x10, 0xfff8, 1, 0x0080); ++ ctrls->test_pattern = ++ v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(test_pattern_menu) - 1, ++ 0, 0, test_pattern_menu); ++ ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, ++ 0, 1, 1, 0); ++ ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, ++ 0, 1, 1, 0); ++ ctrls->light_freq = ++ v4l2_ctrl_new_std_menu(hdl, ops, ++ V4L2_CID_POWER_LINE_FREQUENCY, ++ V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, ++ V4L2_CID_POWER_LINE_FREQUENCY_50HZ); ++ ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, ++ 0, 0, link_freq_menu_items); ++ if (hdl->error) { ++ ret = hdl->error; ++ goto free_ctrls; ++ } ++ ++ ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ // ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; ++ // ctrls->anal_gain->flags |= V4L2_CTRL_FLAG_VOLATILE; ++ ++ v4l2_ctrl_auto_cluster(3, &ctrls->auto_wb, 0, false); ++ ++ sensor->sd.ctrl_handler = hdl; ++ return 0; ++ ++free_ctrls: ++ v4l2_ctrl_handler_free(hdl); ++ return ret; ++} ++ ++static int ov13850_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ if (fse->pad != 0) ++ return -EINVAL; ++ if (fse->index >= OV13850_NUM_MODES) ++ return -EINVAL; ++ ++ fse->min_width = ++ ov13850_mode_data[fse->index].hact; ++ fse->max_width = fse->min_width; ++ fse->min_height = ++ ov13850_mode_data[fse->index].vact; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static int ov13850_enum_frame_interval( ++ struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_interval_enum *fie) ++{ ++ struct ov13850_dev *sensor = to_ov13850_dev(sd); ++ struct v4l2_fract tpf; ++ int ret; ++ ++ if (fie->pad != 0) ++ return -EINVAL; ++ if (fie->index >= OV13850_NUM_FRAMERATES) ++ return -EINVAL; ++ ++ tpf.numerator = 1; ++ tpf.denominator = ov13850_framerates[fie->index]; ++ ++/* ret = ov13850_try_frame_interval(sensor, &tpf, ++ * fie->width, fie->height); ++ * if (ret < 0) ++ * return -EINVAL; ++ */ ++ fie->interval = tpf; ++ ++ return 0; ++} ++ ++static int ov13850_g_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_frame_interval *fi) ++{ ++ struct ov13850_dev *sensor = to_ov13850_dev(sd); ++ ++ mutex_lock(&sensor->lock); ++ fi->interval = sensor->frame_interval; ++ mutex_unlock(&sensor->lock); ++ ++ return 0; ++} ++ ++static int ov13850_s_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_frame_interval *fi) ++{ ++ struct ov13850_dev *sensor = to_ov13850_dev(sd); ++ const struct ov13850_mode_info *mode; ++ int frame_rate, ret = 0; ++ ++ if (fi->pad != 0) ++ return -EINVAL; ++ ++ mutex_lock(&sensor->lock); ++ ++ if (sensor->streaming) { ++ ret = -EBUSY; ++ goto out; ++ } ++ ++ mode = sensor->current_mode; ++ ++ frame_rate = ov13850_try_frame_interval(sensor, &fi->interval, ++ mode->hact, mode->vact); ++ if (frame_rate < 0) { ++ /* Always return a valid frame interval value */ ++ fi->interval = sensor->frame_interval; ++ goto out; ++ } ++ ++ mode = ov13850_find_mode(sensor, frame_rate, mode->hact, ++ mode->vact, true); ++ if (!mode) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ if (mode != sensor->current_mode || ++ frame_rate != sensor->current_fr) { ++ 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, ++ ov13850_calc_pixel_rate(sensor)); ++ } ++out: ++ mutex_unlock(&sensor->lock); ++ return ret; ++} ++ ++static int ov13850_stream_start(struct ov13850_dev *sensor, int enable) ++{ ++ int ret; ++ ++ if (enable) { //stream on ++ mdelay(1000); ++ ret = ov13850_write_reg(sensor, OV13850_STREAM_CTRL, enable); ++ } else { //stream off ++ ret = ov13850_write_reg(sensor, OV13850_STREAM_CTRL, enable); ++ mdelay(100); ++ } ++ ++ return ret; ++} ++ ++static int ov13850_s_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct ov13850_dev *sensor = to_ov13850_dev(sd); ++ int ret = 0; ++ ++ mutex_lock(&sensor->lock); ++ ++ if (sensor->streaming == !enable) { ++ if (enable && sensor->pending_mode_change) { ++ ret = ov13850_set_mode(sensor); ++ if (ret) ++ goto out; ++ } ++ ++ if (enable && sensor->pending_fmt_change) { ++ ret = ov13850_set_framefmt(sensor, &sensor->fmt); ++ if (ret) ++ goto out; ++ sensor->pending_fmt_change = false; ++ } ++ ++ if (sensor->ep.bus_type == V4L2_MBUS_CSI2_DPHY) ++ ret = ov13850_set_stream_mipi(sensor, enable); ++ ++ ret = ov13850_stream_start(sensor, enable); ++ ++ if (!ret) ++ sensor->streaming = enable; ++ } ++out: ++ mutex_unlock(&sensor->lock); ++ return ret; ++} ++ ++static const struct v4l2_subdev_core_ops ov13850_core_ops = { ++ .s_power = ov13850_s_power, ++ .log_status = v4l2_ctrl_subdev_log_status, ++ .subscribe_event = v4l2_ctrl_subdev_subscribe_event, ++ .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++}; ++ ++static const struct v4l2_subdev_video_ops ov13850_video_ops = { ++ .g_frame_interval = ov13850_g_frame_interval, ++ .s_frame_interval = ov13850_s_frame_interval, ++ .s_stream = ov13850_s_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops ov13850_pad_ops = { ++ .enum_mbus_code = ov13850_enum_mbus_code, ++ .get_fmt = ov13850_get_fmt, ++ .set_fmt = ov13850_set_fmt, ++ .enum_frame_size = ov13850_enum_frame_size, ++ .enum_frame_interval = ov13850_enum_frame_interval, ++}; ++ ++static const struct v4l2_subdev_ops ov13850_subdev_ops = { ++ .core = &ov13850_core_ops, ++ .video = &ov13850_video_ops, ++ .pad = &ov13850_pad_ops, ++}; ++ ++static int ov13850_get_regulators(struct ov13850_dev *sensor) ++{ ++ int i; ++ ++ for (i = 0; i < OV13850_NUM_SUPPLIES; i++) ++ sensor->supplies[i].supply = ov13850_supply_name[i]; ++ ++ return devm_regulator_bulk_get(&sensor->i2c_client->dev, ++ OV13850_NUM_SUPPLIES, ++ sensor->supplies); ++} ++ ++static int ov13850_check_chip_id(struct ov13850_dev *sensor) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ int ret = 0; ++ u16 chip_id; ++ ++ ret = ov13850_set_power_on(sensor); ++ if (ret) ++ return ret; ++ ++#ifdef UNUSED_CODE ++ ret = ov13850_read_reg16(sensor, OV13850_REG_CHIP_ID, &chip_id); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to read chip identifier\n", ++ __func__); ++ goto power_off; ++ } ++ ++ if (chip_id != OV13850_CHIP_ID) { ++ dev_err(&client->dev, "%s: wrong chip identifier, expected 0x%x, got 0x%x\n", ++ __func__, OV13850_CHIP_ID, chip_id); ++ ret = -ENXIO; ++ } ++ dev_err(&client->dev, "%s: chip identifier, got 0x%x\n", ++ __func__, chip_id); ++#endif ++ ++power_off: ++ ov13850_set_power_off(sensor); ++ return ret; ++} ++ ++static int ov13850_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct fwnode_handle *endpoint; ++ struct ov13850_dev *sensor; ++ struct v4l2_mbus_framefmt *fmt; ++ u32 rotation; ++ int ret; ++ u8 chip_id_high, chip_id_low; ++ ++ sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); ++ if (!sensor) ++ return -ENOMEM; ++ ++ sensor->i2c_client = client; ++ ++ fmt = &sensor->fmt; ++ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; ++ fmt->colorspace = V4L2_COLORSPACE_SRGB; ++ 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 = 1920; ++ fmt->height = 1080; ++ fmt->field = V4L2_FIELD_NONE; ++ sensor->frame_interval.numerator = 1; ++ sensor->frame_interval.denominator = ov13850_framerates[OV13850_30_FPS]; ++ sensor->current_fr = OV13850_30_FPS; ++ sensor->current_mode = ++ &ov13850_mode_data[OV13850_MODE_1080P_1920_1080]; ++ sensor->last_mode = sensor->current_mode; ++ ++ sensor->ae_target = 52; ++ ++ /* optional indication of physical rotation of sensor */ ++ ret = fwnode_property_read_u32(dev_fwnode(&client->dev), "rotation", ++ &rotation); ++ if (!ret) { ++ switch (rotation) { ++ case 180: ++ sensor->upside_down = true; ++ fallthrough; ++ case 0: ++ break; ++ default: ++ dev_warn(dev, "%u degrees rotation is not supported, ignoring...\n", ++ rotation); ++ } ++ } ++ ++ endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), ++ NULL); ++ if (!endpoint) { ++ dev_err(dev, "endpoint node not found\n"); ++ return -EINVAL; ++ } ++ ++ ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep); ++ fwnode_handle_put(endpoint); ++ if (ret) { ++ dev_err(dev, "Could not parse endpoint\n"); ++ return ret; ++ } ++ ++ if (sensor->ep.bus_type != V4L2_MBUS_PARALLEL && ++ sensor->ep.bus_type != V4L2_MBUS_CSI2_DPHY && ++ sensor->ep.bus_type != V4L2_MBUS_BT656) { ++ dev_err(dev, "Unsupported bus type %d\n", sensor->ep.bus_type); ++ return -EINVAL; ++ } ++ ++ /* get system clock (xclk) */ ++ sensor->xclk = devm_clk_get(dev, "xclk"); ++ if (IS_ERR(sensor->xclk)) { ++ dev_err(dev, "failed to get xclk\n"); ++ return PTR_ERR(sensor->xclk); ++ } ++ ++ sensor->xclk_freq = clk_get_rate(sensor->xclk); ++ if (sensor->xclk_freq < OV13850_XCLK_MIN || ++ sensor->xclk_freq > OV13850_XCLK_MAX) { ++ dev_err(dev, "xclk frequency out of range: %d Hz\n", ++ sensor->xclk_freq); ++ return -EINVAL; ++ } ++ ++ /* request optional power down pin */ ++ sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(sensor->pwdn_gpio)) ++ return PTR_ERR(sensor->pwdn_gpio); ++ ++ /* request optional reset pin */ ++ sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(sensor->reset_gpio)) ++ return PTR_ERR(sensor->reset_gpio); ++ ++ v4l2_i2c_subdev_init(&sensor->sd, client, &ov13850_subdev_ops); ++ ++ sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | ++ V4L2_SUBDEV_FL_HAS_EVENTS; ++ sensor->pad.flags = MEDIA_PAD_FL_SOURCE; ++ sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); ++ if (ret) ++ return ret; ++ ++ ret = ov13850_get_regulators(sensor); ++ if (ret) ++ return ret; ++ ++ mutex_init(&sensor->lock); ++ ++ ret = ov13850_check_chip_id(sensor); ++ if (ret) ++ goto entity_cleanup; ++ ++ ret = ov13850_init_controls(sensor); ++ if (ret) ++ goto entity_cleanup; ++ ++ ret = v4l2_async_register_subdev_sensor(&sensor->sd); ++ if (ret) ++ goto free_ctrls; ++ ++ return 0; ++ ++free_ctrls: ++ v4l2_ctrl_handler_free(&sensor->ctrls.handler); ++entity_cleanup: ++ media_entity_cleanup(&sensor->sd.entity); ++ mutex_destroy(&sensor->lock); ++ return ret; ++} ++ ++static int ov13850_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov13850_dev *sensor = to_ov13850_dev(sd); ++ ++ v4l2_async_unregister_subdev(&sensor->sd); ++ media_entity_cleanup(&sensor->sd.entity); ++ v4l2_ctrl_handler_free(&sensor->ctrls.handler); ++ mutex_destroy(&sensor->lock); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id ov13850_id[] = { ++ {"ov13850", 0}, ++ {}, ++}; ++MODULE_DEVICE_TABLE(i2c, ov13850_id); ++ ++static const struct of_device_id ov13850_dt_ids[] = { ++ { .compatible = "ovti,ov13850" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, ov13850_dt_ids); ++ ++static struct i2c_driver ov13850_i2c_driver = { ++ .driver = { ++ .name = "ov13850", ++ .of_match_table = ov13850_dt_ids, ++ }, ++ .id_table = ov13850_id, ++ .probe_new = ov13850_probe, ++ .remove = ov13850_remove, ++}; ++ ++module_i2c_driver(ov13850_i2c_driver); ++ ++MODULE_DESCRIPTION("OV13850 MIPI Camera Subdev Driver"); ++MODULE_LICENSE("GPL"); +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/ov4689_mipi.c +@@ -0,0 +1,2975 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include <linux/clk.h> ++#include <linux/clk-provider.h> ++#include <linux/clkdev.h> ++#include <linux/ctype.h> ++#include <linux/delay.h> ++#include <linux/device.h> ++#include <linux/gpio/consumer.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/pm_runtime.h> ++#include <linux/of_device.h> ++#include <linux/regulator/consumer.h> ++#include <linux/slab.h> ++#include <linux/types.h> ++#include <media/v4l2-async.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-event.h> ++#include <media/v4l2-fwnode.h> ++#include <media/v4l2-subdev.h> ++#include "stfcamss.h" ++ ++ ++#define OV4689_LANES 4 ++ ++#define OV4689_LINK_FREQ_500MHZ 500000000LL ++ ++/* min/typical/max system clock (xclk) frequencies */ ++#define OV4689_XCLK_MIN 6000000 ++#define OV4689_XCLK_MAX 64000000 ++ ++#define OV4689_CHIP_ID (0x4688) ++ ++#define OV4689_CHIP_ID_HIGH_BYTE 0x300a // max should be 0x46 ++#define OV4689_CHIP_ID_LOW_BYTE 0x300b // max should be 0x88 ++#define OV4689_REG_CHIP_ID 0x300a ++ ++#define OV4689_REG_H_OUTPUT_SIZE 0x3808 ++#define OV4689_REG_V_OUTPUT_SIZE 0x380a ++#define OV4689_REG_TIMING_HTS 0x380c ++#define OV4689_REG_TIMING_VTS 0x380e ++ ++#define OV4689_REG_EXPOSURE_HI 0x3500 ++#define OV4689_REG_EXPOSURE_MED 0x3501 ++#define OV4689_REG_EXPOSURE_LO 0x3502 ++#define OV4689_REG_GAIN_H 0x3507 ++#define OV4689_REG_GAIN_M 0x3508 ++#define OV4689_REG_GAIN_L 0x3509 ++#define OV4689_REG_TEST_PATTERN 0x5040 ++#define OV4689_REG_TIMING_TC_REG20 0x3820 ++#define OV4689_REG_TIMING_TC_REG21 0x3821 ++ ++#define OV4689_REG_AWB_R_GAIN 0x500C ++#define OV4689_REG_AWB_B_GAIN 0x5010 ++#define OV4689_REG_STREAM_ON 0x0100 ++ ++#define OV4689_REG_MIPI_SC_CTRL_HI 0x3018 ++#define OV4689_REG_MIPI_SC_CTRL_LOW 0x3019 ++ ++enum ov4689_mode_id { ++ //OV4689_MODE_720P_1280_720 = 0, ++ OV4689_MODE_1080P_1920_1080 = 0, ++ //OV4689_MODE_4M_2688_1520, ++ OV4689_NUM_MODES, ++}; ++ ++enum ov4689_frame_rate { ++ OV4689_15_FPS = 0, ++ OV4689_30_FPS, ++ OV4689_45_FPS, ++ OV4689_60_FPS, ++ OV4689_90_FPS, ++ OV4689_120_FPS, ++ OV4689_150_FPS, ++ OV4689_180_FPS, ++ OV4689_330_FPS, ++ OV4689_NUM_FRAMERATES, ++}; ++ ++enum ov4689_format_mux { ++ OV4689_FMT_MUX_RAW, ++}; ++ ++static const int ov4689_framerates[] = { ++ [OV4689_15_FPS] = 15, ++ [OV4689_30_FPS] = 30, ++ [OV4689_45_FPS] = 45, ++ [OV4689_60_FPS] = 60, ++ [OV4689_90_FPS] = 90, ++ [OV4689_120_FPS] = 120, ++ [OV4689_150_FPS] = 150, ++ [OV4689_180_FPS] = 180, ++ [OV4689_330_FPS] = 330, ++}; ++ ++/* regulator supplies */ ++static const char * const ov4689_supply_name[] = { ++ "DOVDD", /* Digital I/O (1.8V) supply */ ++ "AVDD", /* Analog (2.8V) supply */ ++ "DVDD", /* Digital Core (1.5V) supply */ ++}; ++ ++#define OV4689_NUM_SUPPLIES ARRAY_SIZE(ov4689_supply_name) ++ ++/* ++ * Image size under 1280 * 960 are SUBSAMPLING ++ * Image size upper 1280 * 960 are SCALING ++ */ ++enum ov4689_downsize_mode { ++ SUBSAMPLING, ++ SCALING, ++}; ++ ++struct reg_value { ++ u16 reg_addr; ++ u8 val; ++ u8 mask; ++ u32 delay_ms; ++}; ++ ++struct ov4689_mode_info { ++ enum ov4689_mode_id id; ++ enum ov4689_downsize_mode dn_mode; ++ u32 hact; ++ u32 htot; ++ u32 vact; ++ u32 vtot; ++ const struct reg_value *reg_data; ++ u32 reg_data_size; ++ u32 max_fps; ++}; ++ ++struct ov4689_ctrls { ++ struct v4l2_ctrl_handler handler; ++ struct v4l2_ctrl *pixel_rate; ++ struct { ++ struct v4l2_ctrl *exposure; ++ }; ++ struct { ++ struct v4l2_ctrl *auto_wb; ++ struct v4l2_ctrl *blue_balance; ++ struct v4l2_ctrl *red_balance; ++ }; ++ struct { ++ struct v4l2_ctrl *anal_gain; ++ }; ++ struct v4l2_ctrl *brightness; ++ struct v4l2_ctrl *light_freq; ++ struct v4l2_ctrl *link_freq; ++ struct v4l2_ctrl *saturation; ++ struct v4l2_ctrl *contrast; ++ struct v4l2_ctrl *hue; ++ struct v4l2_ctrl *test_pattern; ++ struct v4l2_ctrl *hflip; ++ struct v4l2_ctrl *vflip; ++}; ++ ++struct ov4689_dev { ++ struct i2c_client *i2c_client; ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ struct v4l2_fwnode_endpoint ep; /* the parsed DT endpoint info */ ++ struct clk *xclk; /* system clock to OV4689 */ ++ u32 xclk_freq; ++ ++ struct regulator_bulk_data supplies[OV4689_NUM_SUPPLIES]; ++ struct gpio_desc *reset_gpio; ++ struct gpio_desc *pwdn_gpio; ++ bool upside_down; ++ ++ /* lock to protect all members below */ ++ struct mutex lock; ++ ++ struct v4l2_mbus_framefmt fmt; ++ ++ const struct ov4689_mode_info *current_mode; ++ const struct ov4689_mode_info *last_mode; ++ enum ov4689_frame_rate current_fr; ++ struct v4l2_fract frame_interval; ++ ++ struct ov4689_ctrls ctrls; ++ ++ bool pending_mode_change; ++ int streaming; ++}; ++ ++static inline struct ov4689_dev *to_ov4689_dev(struct v4l2_subdev *sd) ++{ ++ return container_of(sd, struct ov4689_dev, sd); ++} ++ ++static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) ++{ ++ return &container_of(ctrl->handler, struct ov4689_dev, ++ ctrls.handler)->sd; ++} ++ ++/* ov4689 initial register */ ++static const struct reg_value ov4689_init_setting_30fps_1080P[] = { ++/* ov4689_1080p_30fps_4d */ ++ {0x0103, 0x01, 0, 0}, ++ {0x3638, 0x00, 0, 0}, ++ {0x0300, 0x02, 0, 0}, ++ {0x0302, 0x32, 0, 0}, ++ {0x0303, 0x00, 0, 0}, ++ {0x0304, 0x03, 0, 0}, ++ {0x030b, 0x00, 0, 0}, ++ {0x030d, 0x1e, 0, 0}, ++ {0x030e, 0x04, 0, 0}, ++ {0x030f, 0x01, 0, 0}, ++ {0x0312, 0x01, 0, 0}, ++ {0x031e, 0x00, 0, 0}, ++ {0x3000, 0x20, 0, 0}, ++ {0x3002, 0x00, 0, 0}, ++ {0x3020, 0x93, 0, 0}, ++ {0x3021, 0x03, 0, 0}, ++ {0x3022, 0x01, 0, 0}, ++ {0x3031, 0x0a, 0, 0}, ++ {0x3305, 0xf1, 0, 0}, ++ {0x3307, 0x04, 0, 0}, ++ {0x3309, 0x29, 0, 0}, ++ {0x3500, 0x00, 0, 0}, ++ {0x3501, 0x4c, 0, 0}, ++ {0x3502, 0x00, 0, 0}, ++ {0x3503, 0x04, 0, 0}, ++ {0x3504, 0x00, 0, 0}, ++ {0x3505, 0x00, 0, 0}, ++ {0x3506, 0x00, 0, 0}, ++ {0x3507, 0x00, 0, 0}, ++ {0x3508, 0x00, 0, 0}, ++ {0x3509, 0x80, 0, 0}, ++ {0x350a, 0x00, 0, 0}, ++ {0x350b, 0x00, 0, 0}, ++ {0x350c, 0x00, 0, 0}, ++ {0x350d, 0x00, 0, 0}, ++ {0x350e, 0x00, 0, 0}, ++ {0x350f, 0x80, 0, 0}, ++ {0x3510, 0x00, 0, 0}, ++ {0x3511, 0x00, 0, 0}, ++ {0x3512, 0x00, 0, 0}, ++ {0x3513, 0x00, 0, 0}, ++ {0x3514, 0x00, 0, 0}, ++ {0x3515, 0x80, 0, 0}, ++ {0x3516, 0x00, 0, 0}, ++ {0x3517, 0x00, 0, 0}, ++ {0x3518, 0x00, 0, 0}, ++ {0x3519, 0x00, 0, 0}, ++ {0x351a, 0x00, 0, 0}, ++ {0x351b, 0x80, 0, 0}, ++ {0x351c, 0x00, 0, 0}, ++ {0x351d, 0x00, 0, 0}, ++ {0x351e, 0x00, 0, 0}, ++ {0x351f, 0x00, 0, 0}, ++ {0x3520, 0x00, 0, 0}, ++ {0x3521, 0x80, 0, 0}, ++ {0x3522, 0x08, 0, 0}, ++ {0x3524, 0x08, 0, 0}, ++ {0x3526, 0x08, 0, 0}, ++ {0x3528, 0x08, 0, 0}, ++ {0x352a, 0x08, 0, 0}, ++ {0x3602, 0x00, 0, 0}, ++ {0x3603, 0x40, 0, 0}, ++ {0x3604, 0x02, 0, 0}, ++ {0x3605, 0x00, 0, 0}, ++ {0x3606, 0x00, 0, 0}, ++ {0x3607, 0x00, 0, 0}, ++ {0x3609, 0x12, 0, 0}, ++ {0x360a, 0x40, 0, 0}, ++ {0x360c, 0x08, 0, 0}, ++ {0x360f, 0xe5, 0, 0}, ++ {0x3608, 0x8f, 0, 0}, ++ {0x3611, 0x00, 0, 0}, ++ {0x3613, 0xf7, 0, 0}, ++ {0x3616, 0x58, 0, 0}, ++ {0x3619, 0x99, 0, 0}, ++ {0x361b, 0x60, 0, 0}, ++ {0x361c, 0x7a, 0, 0}, ++ {0x361e, 0x79, 0, 0}, ++ {0x361f, 0x02, 0, 0}, ++ {0x3632, 0x00, 0, 0}, ++ {0x3633, 0x10, 0, 0}, ++ {0x3634, 0x10, 0, 0}, ++ {0x3635, 0x10, 0, 0}, ++ {0x3636, 0x15, 0, 0}, ++ {0x3646, 0x86, 0, 0}, ++ {0x364a, 0x0b, 0, 0}, ++ {0x3700, 0x17, 0, 0}, ++ {0x3701, 0x22, 0, 0}, ++ {0x3703, 0x10, 0, 0}, ++ {0x370a, 0x37, 0, 0}, ++ {0x3705, 0x00, 0, 0}, ++ {0x3706, 0x63, 0, 0}, ++ {0x3709, 0x3c, 0, 0}, ++ {0x370b, 0x01, 0, 0}, ++ {0x370c, 0x30, 0, 0}, ++ {0x3710, 0x24, 0, 0}, ++ {0x3711, 0x0c, 0, 0}, ++ {0x3716, 0x00, 0, 0}, ++ {0x3720, 0x28, 0, 0}, ++ {0x3729, 0x7b, 0, 0}, ++ {0x372a, 0x84, 0, 0}, ++ {0x372b, 0xbd, 0, 0}, ++ {0x372c, 0xbc, 0, 0}, ++ {0x372e, 0x52, 0, 0}, ++ {0x373c, 0x0e, 0, 0}, ++ {0x373e, 0x33, 0, 0}, ++ {0x3743, 0x10, 0, 0}, ++ {0x3744, 0x88, 0, 0}, ++ {0x3745, 0xc0, 0, 0}, ++ {0x374a, 0x43, 0, 0}, ++ {0x374c, 0x00, 0, 0}, ++ {0x374e, 0x23, 0, 0}, ++ {0x3751, 0x7b, 0, 0}, ++ {0x3752, 0x84, 0, 0}, ++ {0x3753, 0xbd, 0, 0}, ++ {0x3754, 0xbc, 0, 0}, ++ {0x3756, 0x52, 0, 0}, ++ {0x375c, 0x00, 0, 0}, ++ {0x3760, 0x00, 0, 0}, ++ {0x3761, 0x00, 0, 0}, ++ {0x3762, 0x00, 0, 0}, ++ {0x3763, 0x00, 0, 0}, ++ {0x3764, 0x00, 0, 0}, ++ {0x3767, 0x04, 0, 0}, ++ {0x3768, 0x04, 0, 0}, ++ {0x3769, 0x08, 0, 0}, ++ {0x376a, 0x08, 0, 0}, ++ {0x376b, 0x20, 0, 0}, ++ {0x376c, 0x00, 0, 0}, ++ {0x376d, 0x00, 0, 0}, ++ {0x376e, 0x00, 0, 0}, ++ {0x3773, 0x00, 0, 0}, ++ {0x3774, 0x51, 0, 0}, ++ {0x3776, 0xbd, 0, 0}, ++ {0x3777, 0xbd, 0, 0}, ++ {0x3781, 0x18, 0, 0}, ++ {0x3783, 0x25, 0, 0}, ++ {0x3798, 0x1b, 0, 0}, ++ {0x3800, 0x01, 0, 0}, ++ {0x3801, 0x88, 0, 0}, ++ {0x3802, 0x00, 0, 0}, ++ {0x3803, 0xe0, 0, 0}, ++ {0x3804, 0x09, 0, 0}, ++ {0x3805, 0x17, 0, 0}, ++ {0x3806, 0x05, 0, 0}, ++ {0x3807, 0x1f, 0, 0}, ++ {0x3808, 0x07, 0, 0}, ++ {0x3809, 0x80, 0, 0}, ++ {0x380a, 0x04, 0, 0}, ++ {0x380b, 0x38, 0, 0}, ++ {0x380c, 0x0d, 0, 0}, ++ {0x380d, 0x70, 0, 0}, ++ {0x380e, 0x04, 0, 0}, ++ {0x380f, 0x8A, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x08, 0, 0}, ++ {0x3812, 0x00, 0, 0}, ++ {0x3813, 0x04, 0, 0}, ++ {0x3814, 0x01, 0, 0}, ++ {0x3815, 0x01, 0, 0}, ++ {0x3819, 0x01, 0, 0}, ++ {0x3820, 0x06, 0, 0}, ++ {0x3821, 0x00, 0, 0}, ++ {0x3829, 0x00, 0, 0}, ++ {0x382a, 0x01, 0, 0}, ++ {0x382b, 0x01, 0, 0}, ++ {0x382d, 0x7f, 0, 0}, ++ {0x3830, 0x04, 0, 0}, ++ {0x3836, 0x01, 0, 0}, ++ {0x3837, 0x00, 0, 0}, ++ {0x3841, 0x02, 0, 0}, ++ {0x3846, 0x08, 0, 0}, ++ {0x3847, 0x07, 0, 0}, ++ {0x3d85, 0x36, 0, 0}, ++ {0x3d8c, 0x71, 0, 0}, ++ {0x3d8d, 0xcb, 0, 0}, ++ {0x3f0a, 0x00, 0, 0}, ++ {0x4000, 0xf1, 0, 0}, ++ {0x4001, 0x40, 0, 0}, ++ {0x4002, 0x04, 0, 0}, ++ {0x4003, 0x14, 0, 0}, ++ {0x400e, 0x00, 0, 0}, ++ {0x4011, 0x00, 0, 0}, ++ {0x401a, 0x00, 0, 0}, ++ {0x401b, 0x00, 0, 0}, ++ {0x401c, 0x00, 0, 0}, ++ {0x401d, 0x00, 0, 0}, ++ {0x401f, 0x00, 0, 0}, ++ {0x4020, 0x00, 0, 0}, ++ {0x4021, 0x10, 0, 0}, ++ {0x4022, 0x06, 0, 0}, ++ {0x4023, 0x13, 0, 0}, ++ {0x4024, 0x07, 0, 0}, ++ {0x4025, 0x40, 0, 0}, ++ {0x4026, 0x07, 0, 0}, ++ {0x4027, 0x50, 0, 0}, ++ {0x4028, 0x00, 0, 0}, ++ {0x4029, 0x02, 0, 0}, ++ {0x402a, 0x06, 0, 0}, ++ {0x402b, 0x04, 0, 0}, ++ {0x402c, 0x02, 0, 0}, ++ {0x402d, 0x02, 0, 0}, ++ {0x402e, 0x0e, 0, 0}, ++ {0x402f, 0x04, 0, 0}, ++ {0x4302, 0xff, 0, 0}, ++ {0x4303, 0xff, 0, 0}, ++ {0x4304, 0x00, 0, 0}, ++ {0x4305, 0x00, 0, 0}, ++ {0x4306, 0x00, 0, 0}, ++ {0x4308, 0x02, 0, 0}, ++ {0x4500, 0x6c, 0, 0}, ++ {0x4501, 0xc4, 0, 0}, ++ {0x4502, 0x40, 0, 0}, ++ {0x4503, 0x01, 0, 0}, ++ {0x4601, 0x77, 0, 0}, ++ {0x4800, 0x04, 0, 0}, ++ {0x4813, 0x08, 0, 0}, ++ {0x481f, 0x40, 0, 0}, ++ {0x4829, 0x78, 0, 0}, ++ {0x4837, 0x10, 0, 0}, ++ {0x4b00, 0x2a, 0, 0}, ++ {0x4b0d, 0x00, 0, 0}, ++ {0x4d00, 0x04, 0, 0}, ++ {0x4d01, 0x42, 0, 0}, ++ {0x4d02, 0xd1, 0, 0}, ++ {0x4d03, 0x93, 0, 0}, ++ {0x4d04, 0xf5, 0, 0}, ++ {0x4d05, 0xc1, 0, 0}, ++ {0x5000, 0xf3, 0, 0}, ++ {0x5001, 0x11, 0, 0}, ++ {0x5004, 0x00, 0, 0}, ++ {0x500a, 0x00, 0, 0}, ++ {0x500b, 0x00, 0, 0}, ++ {0x5032, 0x00, 0, 0}, ++ {0x5040, 0x00, 0, 0}, ++ {0x5050, 0x0c, 0, 0}, ++ {0x5500, 0x00, 0, 0}, ++ {0x5501, 0x10, 0, 0}, ++ {0x5502, 0x01, 0, 0}, ++ {0x5503, 0x0f, 0, 0}, ++ {0x8000, 0x00, 0, 0}, ++ {0x8001, 0x00, 0, 0}, ++ {0x8002, 0x00, 0, 0}, ++ {0x8003, 0x00, 0, 0}, ++ {0x8004, 0x00, 0, 0}, ++ {0x8005, 0x00, 0, 0}, ++ {0x8006, 0x00, 0, 0}, ++ {0x8007, 0x00, 0, 0}, ++ {0x8008, 0x00, 0, 0}, ++ {0x3638, 0x00, 0, 0}, ++}; ++ ++static const struct reg_value ov4689_setting_VGA_640_480[] = { ++ //@@ RES_640x480_2x_Bin_330fps_816Mbps ++ //OV4689_AM01B_640x480_24M_2lane_816Mbps_330fps_20140210.txt ++ {0x0103, 0x01, 0, 0}, ++ {0x3638, 0x00, 0, 0}, ++ {0x0300, 0x00, 0, 0}, // 00 ++ {0x0302, 0x22, 0, 0}, // 816Mbps 5a ; 64 ; 5a ; 78 ; 78 ; 2a ++ {0x0303, 0x00, 0, 0}, // 03 ; 01 ; 02 ; ++ {0x0304, 0x03, 0, 0}, ++ {0x030b, 0x00, 0, 0}, ++ {0x030d, 0x1e, 0, 0}, ++ {0x030e, 0x04, 0, 0}, ++ {0x030f, 0x01, 0, 0}, ++ {0x0312, 0x01, 0, 0}, ++ {0x031e, 0x00, 0, 0}, ++ {0x3000, 0x20, 0, 0}, ++ {0x3002, 0x00, 0, 0}, ++ {0x3020, 0x93, 0, 0}, ++ {0x3021, 0x03, 0, 0}, ++ {0x3022, 0x01, 0, 0}, ++ {0x3031, 0x0a, 0, 0}, ++ {0x303f, 0x0c, 0, 0}, ++ {0x3305, 0xf1, 0, 0}, ++ {0x3307, 0x04, 0, 0}, ++ {0x3309, 0x29, 0, 0}, ++ {0x3500, 0x00, 0, 0}, ++ {0x3501, 0x4c, 0, 0}, ++ {0x3502, 0x00, 0, 0}, ++ {0x3503, 0x04, 0, 0}, ++ {0x3504, 0x00, 0, 0}, ++ {0x3505, 0x00, 0, 0}, ++ {0x3506, 0x00, 0, 0}, ++ {0x3507, 0x00, 0, 0}, ++ {0x3508, 0x00, 0, 0}, ++ {0x3509, 0x80, 0, 0}, // 8X ++ {0x350a, 0x00, 0, 0}, ++ {0x350b, 0x00, 0, 0}, ++ {0x350c, 0x00, 0, 0}, ++ {0x350d, 0x00, 0, 0}, ++ {0x350e, 0x00, 0, 0}, ++ {0x350f, 0x80, 0, 0}, ++ {0x3510, 0x00, 0, 0}, ++ {0x3511, 0x00, 0, 0}, ++ {0x3512, 0x00, 0, 0}, ++ {0x3513, 0x00, 0, 0}, ++ {0x3514, 0x00, 0, 0}, ++ {0x3515, 0x80, 0, 0}, ++ {0x3516, 0x00, 0, 0}, ++ {0x3517, 0x00, 0, 0}, ++ {0x3518, 0x00, 0, 0}, ++ {0x3519, 0x00, 0, 0}, ++ {0x351a, 0x00, 0, 0}, ++ {0x351b, 0x80, 0, 0}, ++ {0x351c, 0x00, 0, 0}, ++ {0x351d, 0x00, 0, 0}, ++ {0x351e, 0x00, 0, 0}, ++ {0x351f, 0x00, 0, 0}, ++ {0x3520, 0x00, 0, 0}, ++ {0x3521, 0x80, 0, 0}, ++ {0x3522, 0x08, 0, 0}, ++ {0x3524, 0x08, 0, 0}, ++ {0x3526, 0x08, 0, 0}, ++ {0x3528, 0x08, 0, 0}, ++ {0x352a, 0x08, 0, 0}, ++ {0x3602, 0x00, 0, 0}, ++ {0x3603, 0x40, 0, 0}, ++ {0x3604, 0x02, 0, 0}, ++ {0x3605, 0x00, 0, 0}, ++ {0x3606, 0x00, 0, 0}, ++ {0x3607, 0x00, 0, 0}, ++ {0x3609, 0x12, 0, 0}, ++ {0x360a, 0x40, 0, 0}, ++ {0x360c, 0x08, 0, 0}, ++ {0x360f, 0xe5, 0, 0}, ++ {0x3608, 0x8f, 0, 0}, ++ {0x3611, 0x00, 0, 0}, ++ {0x3613, 0xf7, 0, 0}, ++ {0x3616, 0x58, 0, 0}, ++ {0x3619, 0x99, 0, 0}, ++ {0x361b, 0x60, 0, 0}, ++ {0x361c, 0x7a, 0, 0}, ++ {0x361e, 0x79, 0, 0}, ++ {0x361f, 0x02, 0, 0}, ++ {0x3632, 0x05, 0, 0}, ++ {0x3633, 0x10, 0, 0}, ++ {0x3634, 0x10, 0, 0}, ++ {0x3635, 0x10, 0, 0}, ++ {0x3636, 0x15, 0, 0}, ++ {0x3646, 0x86, 0, 0}, ++ {0x364a, 0x0b, 0, 0}, ++ {0x3700, 0x17, 0, 0}, ++ {0x3701, 0x22, 0, 0}, ++ {0x3703, 0x10, 0, 0}, ++ {0x370a, 0x37, 0, 0}, ++ {0x3705, 0x00, 0, 0}, ++ {0x3706, 0x63, 0, 0}, ++ {0x3709, 0x3c, 0, 0}, ++ {0x370b, 0x01, 0, 0}, ++ {0x370c, 0x30, 0, 0}, ++ {0x3710, 0x24, 0, 0}, ++ {0x3711, 0x0c, 0, 0}, ++ {0x3716, 0x00, 0, 0}, ++ {0x3720, 0x28, 0, 0}, ++ {0x3729, 0x7b, 0, 0}, ++ {0x372a, 0x84, 0, 0}, ++ {0x372b, 0xbd, 0, 0}, ++ {0x372c, 0xbc, 0, 0}, ++ {0x372e, 0x52, 0, 0}, ++ {0x373c, 0x0e, 0, 0}, ++ {0x373e, 0x33, 0, 0}, ++ {0x3743, 0x10, 0, 0}, ++ {0x3744, 0x88, 0, 0}, ++ {0x3745, 0xc0, 0, 0}, ++ {0x374a, 0x43, 0, 0}, ++ {0x374c, 0x00, 0, 0}, ++ {0x374e, 0x23, 0, 0}, ++ {0x3751, 0x7b, 0, 0}, ++ {0x3752, 0x84, 0, 0}, ++ {0x3753, 0xbd, 0, 0}, ++ {0x3754, 0xbc, 0, 0}, ++ {0x3756, 0x52, 0, 0}, ++ {0x375c, 0x00, 0, 0}, ++ {0x3760, 0x00, 0, 0}, ++ {0x3761, 0x00, 0, 0}, ++ {0x3762, 0x00, 0, 0}, ++ {0x3763, 0x00, 0, 0}, ++ {0x3764, 0x00, 0, 0}, ++ {0x3767, 0x04, 0, 0}, ++ {0x3768, 0x04, 0, 0}, ++ {0x3769, 0x08, 0, 0}, ++ {0x376a, 0x08, 0, 0}, ++ {0x376b, 0x40, 0, 0}, ++ {0x376c, 0x00, 0, 0}, ++ {0x376d, 0x00, 0, 0}, ++ {0x376e, 0x00, 0, 0}, ++ {0x3773, 0x00, 0, 0}, ++ {0x3774, 0x51, 0, 0}, ++ {0x3776, 0xbd, 0, 0}, ++ {0x3777, 0xbd, 0, 0}, ++ {0x3781, 0x18, 0, 0}, ++ {0x3783, 0x25, 0, 0}, ++ {0x3798, 0x1b, 0, 0}, ++ {0x3800, 0x00, 0, 0}, ++ {0x3801, 0x48, 0, 0}, ++ {0x3802, 0x00, 0, 0}, ++ {0x3803, 0x2C, 0, 0}, ++ {0x3804, 0x0a, 0, 0}, ++ {0x3805, 0x57, 0, 0}, ++ {0x3806, 0x05, 0, 0}, ++ {0x3807, 0xD3, 0, 0}, ++ {0x3808, 0x02, 0, 0}, ++ {0x3809, 0x80, 0, 0}, ++ {0x380a, 0x01, 0, 0}, ++ {0x380b, 0xe0, 0, 0}, ++ ++ {0x380c, 0x02, 0, 0}, // 0a ; 03 ++ {0x380d, 0x04, 0, 0}, // 1c ; 5C ++ ++ {0x380e, 0x03, 0, 0}, ++ {0x380f, 0x05, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x04, 0, 0}, ++ {0x3812, 0x00, 0, 0}, ++ {0x3813, 0x02, 0, 0}, ++ {0x3814, 0x03, 0, 0}, ++ {0x3815, 0x01, 0, 0}, ++ {0x3819, 0x01, 0, 0}, ++ {0x3820, 0x06, 0, 0}, ++ {0x3821, 0x00, 0, 0}, ++ {0x3829, 0x00, 0, 0}, ++ {0x382a, 0x03, 0, 0}, ++ {0x382b, 0x01, 0, 0}, ++ {0x382d, 0x7f, 0, 0}, ++ {0x3830, 0x08, 0, 0}, ++ {0x3836, 0x02, 0, 0}, ++ {0x3837, 0x00, 0, 0}, ++ {0x3841, 0x02, 0, 0}, ++ {0x3846, 0x08, 0, 0}, ++ {0x3847, 0x07, 0, 0}, ++ {0x3d85, 0x36, 0, 0}, ++ {0x3d8c, 0x71, 0, 0}, ++ {0x3d8d, 0xcb, 0, 0}, ++ {0x3f0a, 0x00, 0, 0}, ++ {0x4000, 0x71, 0, 0}, ++ {0x4001, 0x50, 0, 0}, ++ {0x4002, 0x04, 0, 0}, ++ {0x4003, 0x14, 0, 0}, ++ {0x400e, 0x00, 0, 0}, ++ {0x4011, 0x00, 0, 0}, ++ {0x401a, 0x00, 0, 0}, ++ {0x401b, 0x00, 0, 0}, ++ {0x401c, 0x00, 0, 0}, ++ {0x401d, 0x00, 0, 0}, ++ {0x401f, 0x00, 0, 0}, ++ {0x4020, 0x00, 0, 0}, ++ {0x4021, 0x10, 0, 0}, ++ {0x4022, 0x03, 0, 0}, ++ {0x4023, 0x93, 0, 0}, ++ {0x4024, 0x04, 0, 0}, ++ {0x4025, 0xC0, 0, 0}, ++ {0x4026, 0x04, 0, 0}, ++ {0x4027, 0xD0, 0, 0}, ++ {0x4028, 0x00, 0, 0}, ++ {0x4029, 0x02, 0, 0}, ++ {0x402a, 0x06, 0, 0}, ++ {0x402b, 0x04, 0, 0}, ++ {0x402c, 0x02, 0, 0}, ++ {0x402d, 0x02, 0, 0}, ++ {0x402e, 0x0e, 0, 0}, ++ {0x402f, 0x04, 0, 0}, ++ {0x4302, 0xff, 0, 0}, ++ {0x4303, 0xff, 0, 0}, ++ {0x4304, 0x00, 0, 0}, ++ {0x4305, 0x00, 0, 0}, ++ {0x4306, 0x00, 0, 0}, ++ {0x4308, 0x02, 0, 0}, ++ {0x4500, 0x6c, 0, 0}, ++ {0x4501, 0xc4, 0, 0}, ++ {0x4502, 0x44, 0, 0}, ++ {0x4503, 0x01, 0, 0}, ++ {0x4600, 0x00, 0, 0}, ++ {0x4601, 0x4F, 0, 0}, ++ {0x4800, 0x04, 0, 0}, ++ {0x4813, 0x08, 0, 0}, ++ {0x481f, 0x40, 0, 0}, ++ {0x4829, 0x78, 0, 0}, ++ {0x4837, 0x10, 0, 0}, // 20 ; 10 ++ {0x4b00, 0x2a, 0, 0}, ++ {0x4b0d, 0x00, 0, 0}, ++ {0x4d00, 0x04, 0, 0}, ++ {0x4d01, 0x42, 0, 0}, ++ {0x4d02, 0xd1, 0, 0}, ++ {0x4d03, 0x93, 0, 0}, ++ {0x4d04, 0xf5, 0, 0}, ++ {0x4d05, 0xc1, 0, 0}, ++ {0x5000, 0xf3, 0, 0}, ++ {0x5001, 0x11, 0, 0}, ++ {0x5004, 0x00, 0, 0}, ++ {0x500a, 0x00, 0, 0}, ++ {0x500b, 0x00, 0, 0}, ++ {0x5032, 0x00, 0, 0}, ++ {0x5040, 0x00, 0, 0}, ++ {0x5050, 0x3c, 0, 0}, ++ {0x5500, 0x00, 0, 0}, ++ {0x5501, 0x10, 0, 0}, ++ {0x5502, 0x01, 0, 0}, ++ {0x5503, 0x0f, 0, 0}, ++ {0x8000, 0x00, 0, 0}, ++ {0x8001, 0x00, 0, 0}, ++ {0x8002, 0x00, 0, 0}, ++ {0x8003, 0x00, 0, 0}, ++ {0x8004, 0x00, 0, 0}, ++ {0x8005, 0x00, 0, 0}, ++ {0x8006, 0x00, 0, 0}, ++ {0x8007, 0x00, 0, 0}, ++ {0x8008, 0x00, 0, 0}, ++ {0x3638, 0x00, 0, 0}, ++}; ++ ++static const struct reg_value ov4689_setting_720P_1280_720[] = { ++ //@@ RES_1280x720_2x_Bin_150fps_816Mbps ++ //OV4689_AM01B_1280x720_24M_2lane_816Mbps_150fps_20140210.txt ++ {0x0103, 0x01, 0, 0}, ++ {0x3638, 0x00, 0, 0}, ++ {0x0300, 0x00, 0, 0}, // 00 ++ {0x0302, 0x22, 0, 0}, // 816Mbps 5a ; 64 ; 5a ; 78 ; 78 ; 2a ++ {0x0303, 0x00, 0, 0}, // 03 ; 01 ; 02 ; ++ {0x0304, 0x03, 0, 0}, ++ {0x030b, 0x00, 0, 0}, ++ {0x030d, 0x1e, 0, 0}, ++ {0x030e, 0x04, 0, 0}, ++ {0x030f, 0x01, 0, 0}, ++ {0x0312, 0x01, 0, 0}, ++ {0x031e, 0x00, 0, 0}, ++ {0x3000, 0x20, 0, 0}, ++ {0x3002, 0x00, 0, 0}, ++ {0x3020, 0x93, 0, 0}, ++ {0x3021, 0x03, 0, 0}, ++ {0x3022, 0x01, 0, 0}, ++ {0x3031, 0x0a, 0, 0}, ++ {0x303f, 0x0c, 0, 0}, ++ {0x3305, 0xf1, 0, 0}, ++ {0x3307, 0x04, 0, 0}, ++ {0x3309, 0x29, 0, 0}, ++ {0x3500, 0x00, 0, 0}, ++ {0x3501, 0x30, 0, 0}, ++ {0x3502, 0x00, 0, 0}, ++ {0x3503, 0x04, 0, 0}, ++ {0x3504, 0x00, 0, 0}, ++ {0x3505, 0x00, 0, 0}, ++ {0x3506, 0x00, 0, 0}, ++ {0x3507, 0x00, 0, 0}, ++ {0x3508, 0x07, 0, 0}, ++ {0x3509, 0x78, 0, 0}, // 8X ++ {0x350a, 0x00, 0, 0}, ++ {0x350b, 0x00, 0, 0}, ++ {0x350c, 0x00, 0, 0}, ++ {0x350d, 0x00, 0, 0}, ++ {0x350e, 0x00, 0, 0}, ++ {0x350f, 0x80, 0, 0}, ++ {0x3510, 0x00, 0, 0}, ++ {0x3511, 0x00, 0, 0}, ++ {0x3512, 0x00, 0, 0}, ++ {0x3513, 0x00, 0, 0}, ++ {0x3514, 0x00, 0, 0}, ++ {0x3515, 0x80, 0, 0}, ++ {0x3516, 0x00, 0, 0}, ++ {0x3517, 0x00, 0, 0}, ++ {0x3518, 0x00, 0, 0}, ++ {0x3519, 0x00, 0, 0}, ++ {0x351a, 0x00, 0, 0}, ++ {0x351b, 0x80, 0, 0}, ++ {0x351c, 0x00, 0, 0}, ++ {0x351d, 0x00, 0, 0}, ++ {0x351e, 0x00, 0, 0}, ++ {0x351f, 0x00, 0, 0}, ++ {0x3520, 0x00, 0, 0}, ++ {0x3521, 0x80, 0, 0}, ++ {0x3522, 0x08, 0, 0}, ++ {0x3524, 0x08, 0, 0}, ++ {0x3526, 0x08, 0, 0}, ++ {0x3528, 0x08, 0, 0}, ++ {0x352a, 0x08, 0, 0}, ++ {0x3602, 0x00, 0, 0}, ++ {0x3603, 0x40, 0, 0}, ++ {0x3604, 0x02, 0, 0}, ++ {0x3605, 0x00, 0, 0}, ++ {0x3606, 0x00, 0, 0}, ++ {0x3607, 0x00, 0, 0}, ++ {0x3609, 0x12, 0, 0}, ++ {0x360a, 0x40, 0, 0}, ++ {0x360c, 0x08, 0, 0}, ++ {0x360f, 0xe5, 0, 0}, ++ {0x3608, 0x8f, 0, 0}, ++ {0x3611, 0x00, 0, 0}, ++ {0x3613, 0xf7, 0, 0}, ++ {0x3616, 0x58, 0, 0}, ++ {0x3619, 0x99, 0, 0}, ++ {0x361b, 0x60, 0, 0}, ++ {0x361c, 0x7a, 0, 0}, ++ {0x361e, 0x79, 0, 0}, ++ {0x361f, 0x02, 0, 0}, ++ {0x3632, 0x05, 0, 0}, ++ {0x3633, 0x10, 0, 0}, ++ {0x3634, 0x10, 0, 0}, ++ {0x3635, 0x10, 0, 0}, ++ {0x3636, 0x15, 0, 0}, ++ {0x3646, 0x86, 0, 0}, ++ {0x364a, 0x0b, 0, 0}, ++ {0x3700, 0x17, 0, 0}, ++ {0x3701, 0x22, 0, 0}, ++ {0x3703, 0x10, 0, 0}, ++ {0x370a, 0x37, 0, 0}, ++ {0x3705, 0x00, 0, 0}, ++ {0x3706, 0x63, 0, 0}, ++ {0x3709, 0x3c, 0, 0}, ++ {0x370b, 0x01, 0, 0}, ++ {0x370c, 0x30, 0, 0}, ++ {0x3710, 0x24, 0, 0}, ++ {0x3711, 0x0c, 0, 0}, ++ {0x3716, 0x00, 0, 0}, ++ {0x3720, 0x28, 0, 0}, ++ {0x3729, 0x7b, 0, 0}, ++ {0x372a, 0x84, 0, 0}, ++ {0x372b, 0xbd, 0, 0}, ++ {0x372c, 0xbc, 0, 0}, ++ {0x372e, 0x52, 0, 0}, ++ {0x373c, 0x0e, 0, 0}, ++ {0x373e, 0x33, 0, 0}, ++ {0x3743, 0x10, 0, 0}, ++ {0x3744, 0x88, 0, 0}, ++ {0x3745, 0xc0, 0, 0}, ++ {0x374a, 0x43, 0, 0}, ++ {0x374c, 0x00, 0, 0}, ++ {0x374e, 0x23, 0, 0}, ++ {0x3751, 0x7b, 0, 0}, ++ {0x3752, 0x84, 0, 0}, ++ {0x3753, 0xbd, 0, 0}, ++ {0x3754, 0xbc, 0, 0}, ++ {0x3756, 0x52, 0, 0}, ++ {0x375c, 0x00, 0, 0}, ++ {0x3760, 0x00, 0, 0}, ++ {0x3761, 0x00, 0, 0}, ++ {0x3762, 0x00, 0, 0}, ++ {0x3763, 0x00, 0, 0}, ++ {0x3764, 0x00, 0, 0}, ++ {0x3767, 0x04, 0, 0}, ++ {0x3768, 0x04, 0, 0}, ++ {0x3769, 0x08, 0, 0}, ++ {0x376a, 0x08, 0, 0}, ++ {0x376b, 0x40, 0, 0}, ++ {0x376c, 0x00, 0, 0}, ++ {0x376d, 0x00, 0, 0}, ++ {0x376e, 0x00, 0, 0}, ++ {0x3773, 0x00, 0, 0}, ++ {0x3774, 0x51, 0, 0}, ++ {0x3776, 0xbd, 0, 0}, ++ {0x3777, 0xbd, 0, 0}, ++ {0x3781, 0x18, 0, 0}, ++ {0x3783, 0x25, 0, 0}, ++ {0x3798, 0x1b, 0, 0}, ++ {0x3800, 0x00, 0, 0}, ++ {0x3801, 0x48, 0, 0}, ++ {0x3802, 0x00, 0, 0}, ++ {0x3803, 0x2C, 0, 0}, ++ {0x3804, 0x0a, 0, 0}, ++ {0x3805, 0x57, 0, 0}, ++ {0x3806, 0x05, 0, 0}, ++ {0x3807, 0xD3, 0, 0}, ++ {0x3808, 0x05, 0, 0}, ++ {0x3809, 0x00, 0, 0}, ++ {0x380a, 0x02, 0, 0}, ++ {0x380b, 0xD0, 0, 0}, ++#ifndef UNUSED_CODE ++ {0x380c, 0x04, 0, 0}, // 0a ; 03 ++ {0x380d, 0x08, 0, 0}, // 1c ; 5C ++#else ++ {0x380c, 0x05, 0, 0}, // 120fps ++ {0x380d, 0x0A, 0, 0}, ++#endif ++ {0x380e, 0x03, 0, 0}, ++ {0x380f, 0x05, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x04, 0, 0}, ++ {0x3812, 0x00, 0, 0}, ++ {0x3813, 0x02, 0, 0}, ++ {0x3814, 0x03, 0, 0}, ++ {0x3815, 0x01, 0, 0}, ++ {0x3819, 0x01, 0, 0}, ++ {0x3820, 0x06, 0, 0}, ++ {0x3821, 0x00, 0, 0}, ++ {0x3829, 0x00, 0, 0}, ++ {0x382a, 0x03, 0, 0}, ++ {0x382b, 0x01, 0, 0}, ++ {0x382d, 0x7f, 0, 0}, ++ {0x3830, 0x08, 0, 0}, ++ {0x3836, 0x02, 0, 0}, ++ {0x3837, 0x00, 0, 0}, ++ {0x3841, 0x02, 0, 0}, ++ {0x3846, 0x08, 0, 0}, ++ {0x3847, 0x07, 0, 0}, ++ {0x3d85, 0x36, 0, 0}, ++ {0x3d8c, 0x71, 0, 0}, ++ {0x3d8d, 0xcb, 0, 0}, ++ {0x3f0a, 0x00, 0, 0}, ++ {0x4000, 0x71, 0, 0}, ++ {0x4001, 0x50, 0, 0}, ++ {0x4002, 0x04, 0, 0}, ++ {0x4003, 0x14, 0, 0}, ++ {0x400e, 0x00, 0, 0}, ++ {0x4011, 0x00, 0, 0}, ++ {0x401a, 0x00, 0, 0}, ++ {0x401b, 0x00, 0, 0}, ++ {0x401c, 0x00, 0, 0}, ++ {0x401d, 0x00, 0, 0}, ++ {0x401f, 0x00, 0, 0}, ++ {0x4020, 0x00, 0, 0}, ++ {0x4021, 0x10, 0, 0}, ++ {0x4022, 0x03, 0, 0}, ++ {0x4023, 0x93, 0, 0}, ++ {0x4024, 0x04, 0, 0}, ++ {0x4025, 0xC0, 0, 0}, ++ {0x4026, 0x04, 0, 0}, ++ {0x4027, 0xD0, 0, 0}, ++ {0x4028, 0x00, 0, 0}, ++ {0x4029, 0x02, 0, 0}, ++ {0x402a, 0x06, 0, 0}, ++ {0x402b, 0x04, 0, 0}, ++ {0x402c, 0x02, 0, 0}, ++ {0x402d, 0x02, 0, 0}, ++ {0x402e, 0x0e, 0, 0}, ++ {0x402f, 0x04, 0, 0}, ++ {0x4302, 0xff, 0, 0}, ++ {0x4303, 0xff, 0, 0}, ++ {0x4304, 0x00, 0, 0}, ++ {0x4305, 0x00, 0, 0}, ++ {0x4306, 0x00, 0, 0}, ++ {0x4308, 0x02, 0, 0}, ++ {0x4500, 0x6c, 0, 0}, ++ {0x4501, 0xc4, 0, 0}, ++ {0x4502, 0x44, 0, 0}, ++ {0x4503, 0x01, 0, 0}, ++ {0x4600, 0x00, 0, 0}, ++ {0x4601, 0x4F, 0, 0}, ++ {0x4800, 0x04, 0, 0}, ++ {0x4813, 0x08, 0, 0}, ++ {0x481f, 0x40, 0, 0}, ++ {0x4829, 0x78, 0, 0}, ++ {0x4837, 0x10, 0, 0}, // 20 ; 10 ++ {0x4b00, 0x2a, 0, 0}, ++ {0x4b0d, 0x00, 0, 0}, ++ {0x4d00, 0x04, 0, 0}, ++ {0x4d01, 0x42, 0, 0}, ++ {0x4d02, 0xd1, 0, 0}, ++ {0x4d03, 0x93, 0, 0}, ++ {0x4d04, 0xf5, 0, 0}, ++ {0x4d05, 0xc1, 0, 0}, ++ {0x5000, 0xf3, 0, 0}, ++ {0x5001, 0x11, 0, 0}, ++ {0x5004, 0x00, 0, 0}, ++ {0x500a, 0x00, 0, 0}, ++ {0x500b, 0x00, 0, 0}, ++ {0x5032, 0x00, 0, 0}, ++ {0x5040, 0x00, 0, 0}, ++ {0x5050, 0x3c, 0, 0}, ++ {0x5500, 0x00, 0, 0}, ++ {0x5501, 0x10, 0, 0}, ++ {0x5502, 0x01, 0, 0}, ++ {0x5503, 0x0f, 0, 0}, ++ {0x8000, 0x00, 0, 0}, ++ {0x8001, 0x00, 0, 0}, ++ {0x8002, 0x00, 0, 0}, ++ {0x8003, 0x00, 0, 0}, ++ {0x8004, 0x00, 0, 0}, ++ {0x8005, 0x00, 0, 0}, ++ {0x8006, 0x00, 0, 0}, ++ {0x8007, 0x00, 0, 0}, ++ {0x8008, 0x00, 0, 0}, ++ {0x3638, 0x00, 0, 0}, ++}; ++ ++static const struct reg_value ov4689_setting_1080P_1920_1080[] = { ++ //@@ RES_1920x1080_60fps_816Mbps 2lanes ++ {0x0103, 0x01, 0, 0}, ++ {0x3638, 0x00, 0, 0}, ++ {0x0300, 0x00, 0, 0}, // clk ++ {0x0302, 0x22, 0, 0}, ++ {0x0303, 0x00, 0, 0}, ++ {0x0304, 0x03, 0, 0}, ++ {0x030b, 0x00, 0, 0}, ++ {0x030d, 0x1e, 0, 0}, ++ {0x030e, 0x04, 0, 0}, ++ {0x030f, 0x01, 0, 0}, ++ {0x0312, 0x01, 0, 0}, ++ {0x031e, 0x00, 0, 0}, ++ {0x3000, 0x20, 0, 0}, ++ {0x3002, 0x00, 0, 0}, ++ {0x3020, 0x93, 0, 0}, ++ {0x3021, 0x03, 0, 0}, ++ {0x3022, 0x01, 0, 0}, ++ {0x3031, 0x0a, 0, 0}, ++ {0x303f, 0x0c, 0, 0}, ++ {0x3305, 0xf1, 0, 0}, ++ {0x3307, 0x04, 0, 0}, ++ {0x3309, 0x29, 0, 0}, ++ {0x3500, 0x00, 0, 0}, // AEC ++ {0x3501, 0x4c, 0, 0}, ++ {0x3502, 0x00, 0, 0}, ++ {0x3503, 0x04, 0, 0}, ++ {0x3504, 0x00, 0, 0}, ++ {0x3505, 0x00, 0, 0}, ++ {0x3506, 0x00, 0, 0}, ++ {0x3507, 0x00, 0, 0}, ++ {0x3508, 0x00, 0, 0}, ++ {0x3509, 0x80, 0, 0}, ++ {0x350a, 0x00, 0, 0}, ++ {0x350b, 0x00, 0, 0}, ++ {0x350c, 0x00, 0, 0}, ++ {0x350d, 0x00, 0, 0}, ++ {0x350e, 0x00, 0, 0}, ++ {0x350f, 0x80, 0, 0}, ++ {0x3510, 0x00, 0, 0}, ++ {0x3511, 0x00, 0, 0}, ++ {0x3512, 0x00, 0, 0}, ++ {0x3513, 0x00, 0, 0}, ++ {0x3514, 0x00, 0, 0}, ++ {0x3515, 0x80, 0, 0}, ++ {0x3516, 0x00, 0, 0}, ++ {0x3517, 0x00, 0, 0}, ++ {0x3518, 0x00, 0, 0}, ++ {0x3519, 0x00, 0, 0}, ++ {0x351a, 0x00, 0, 0}, ++ {0x351b, 0x80, 0, 0}, ++ {0x351c, 0x00, 0, 0}, ++ {0x351d, 0x00, 0, 0}, ++ {0x351e, 0x00, 0, 0}, ++ {0x351f, 0x00, 0, 0}, ++ {0x3520, 0x00, 0, 0}, ++ {0x3521, 0x80, 0, 0}, ++ {0x3522, 0x08, 0, 0}, ++ {0x3524, 0x08, 0, 0}, ++ {0x3526, 0x08, 0, 0}, ++ {0x3528, 0x08, 0, 0}, ++ {0x352a, 0x08, 0, 0}, ++ {0x3602, 0x00, 0, 0}, ++ {0x3603, 0x40, 0, 0}, ++ {0x3604, 0x02, 0, 0}, ++ {0x3605, 0x00, 0, 0}, ++ {0x3606, 0x00, 0, 0}, ++ {0x3607, 0x00, 0, 0}, ++ {0x3609, 0x12, 0, 0}, ++ {0x360a, 0x40, 0, 0}, ++ {0x360c, 0x08, 0, 0}, ++ {0x360f, 0xe5, 0, 0}, ++ {0x3608, 0x8f, 0, 0}, ++ {0x3611, 0x00, 0, 0}, ++ {0x3613, 0xf7, 0, 0}, ++ {0x3616, 0x58, 0, 0}, ++ {0x3619, 0x99, 0, 0}, ++ {0x361b, 0x60, 0, 0}, ++ {0x361c, 0x7a, 0, 0}, ++ {0x361e, 0x79, 0, 0}, ++ {0x361f, 0x02, 0, 0}, ++ {0x3632, 0x00, 0, 0}, ++ {0x3633, 0x10, 0, 0}, ++ {0x3634, 0x10, 0, 0}, ++ {0x3635, 0x10, 0, 0}, ++ {0x3636, 0x15, 0, 0}, ++ {0x3646, 0x86, 0, 0}, ++ {0x364a, 0x0b, 0, 0}, ++ {0x3700, 0x17, 0, 0}, ++ {0x3701, 0x22, 0, 0}, ++ {0x3703, 0x10, 0, 0}, ++ {0x370a, 0x37, 0, 0}, ++ {0x3705, 0x00, 0, 0}, ++ {0x3706, 0x63, 0, 0}, ++ {0x3709, 0x3c, 0, 0}, ++ {0x370b, 0x01, 0, 0}, ++ {0x370c, 0x30, 0, 0}, ++ {0x3710, 0x24, 0, 0}, ++ {0x3711, 0x0c, 0, 0}, ++ {0x3716, 0x00, 0, 0}, ++ {0x3720, 0x28, 0, 0}, ++ {0x3729, 0x7b, 0, 0}, ++ {0x372a, 0x84, 0, 0}, ++ {0x372b, 0xbd, 0, 0}, ++ {0x372c, 0xbc, 0, 0}, ++ {0x372e, 0x52, 0, 0}, ++ {0x373c, 0x0e, 0, 0}, ++ {0x373e, 0x33, 0, 0}, ++ {0x3743, 0x10, 0, 0}, ++ {0x3744, 0x88, 0, 0}, ++ {0x3745, 0xc0, 0, 0}, ++ {0x374a, 0x43, 0, 0}, ++ {0x374c, 0x00, 0, 0}, ++ {0x374e, 0x23, 0, 0}, ++ {0x3751, 0x7b, 0, 0}, ++ {0x3752, 0x84, 0, 0}, ++ {0x3753, 0xbd, 0, 0}, ++ {0x3754, 0xbc, 0, 0}, ++ {0x3756, 0x52, 0, 0}, ++ {0x375c, 0x00, 0, 0}, ++ {0x3760, 0x00, 0, 0}, ++ {0x3761, 0x00, 0, 0}, ++ {0x3762, 0x00, 0, 0}, ++ {0x3763, 0x00, 0, 0}, ++ {0x3764, 0x00, 0, 0}, ++ {0x3767, 0x04, 0, 0}, ++ {0x3768, 0x04, 0, 0}, ++ {0x3769, 0x08, 0, 0}, ++ {0x376a, 0x08, 0, 0}, ++ {0x376b, 0x20, 0, 0}, ++ {0x376c, 0x00, 0, 0}, ++ {0x376d, 0x00, 0, 0}, ++ {0x376e, 0x00, 0, 0}, ++ {0x3773, 0x00, 0, 0}, ++ {0x3774, 0x51, 0, 0}, ++ {0x3776, 0xbd, 0, 0}, ++ {0x3777, 0xbd, 0, 0}, ++ {0x3781, 0x18, 0, 0}, ++ {0x3783, 0x25, 0, 0}, ++ {0x3798, 0x1b, 0, 0}, ++ {0x3800, 0x01, 0, 0}, // timings ++ {0x3801, 0x88, 0, 0}, ++ {0x3802, 0x00, 0, 0}, ++ {0x3803, 0xe0, 0, 0}, ++ {0x3804, 0x09, 0, 0}, ++ {0x3805, 0x17, 0, 0}, ++ {0x3806, 0x05, 0, 0}, ++ {0x3807, 0x1f, 0, 0}, ++ {0x3808, 0x07, 0, 0}, ++ {0x3809, 0x80, 0, 0}, ++ {0x380a, 0x04, 0, 0}, ++ {0x380b, 0x38, 0, 0}, ++ {0x380c, 0x06, 0, 0}, ++ {0x380d, 0xe0, 0, 0}, ++ {0x380e, 0x04, 0, 0}, ++ {0x380f, 0x70, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x08, 0, 0}, ++ {0x3812, 0x00, 0, 0}, ++ {0x3813, 0x04, 0, 0}, ++ {0x3814, 0x01, 0, 0}, ++ {0x3815, 0x01, 0, 0}, ++ {0x3819, 0x01, 0, 0}, ++ {0x3820, 0x06, 0, 0}, ++ {0x3821, 0x00, 0, 0}, ++ {0x3829, 0x00, 0, 0}, ++ {0x382a, 0x01, 0, 0}, ++ {0x382b, 0x01, 0, 0}, ++ {0x382d, 0x7f, 0, 0}, ++ {0x3830, 0x04, 0, 0}, ++ {0x3836, 0x01, 0, 0}, ++ {0x3837, 0x00, 0, 0}, ++ {0x3841, 0x02, 0, 0}, ++ {0x3846, 0x08, 0, 0}, ++ {0x3847, 0x07, 0, 0}, ++ {0x3d85, 0x36, 0, 0}, ++ {0x3d8c, 0x71, 0, 0}, ++ {0x3d8d, 0xcb, 0, 0}, ++ {0x3f0a, 0x00, 0, 0}, ++ {0x4000, 0xf1, 0, 0}, ++ {0x4001, 0x40, 0, 0}, ++ {0x4002, 0x04, 0, 0}, ++ {0x4003, 0x14, 0, 0}, ++ {0x400e, 0x00, 0, 0}, ++ {0x4011, 0x00, 0, 0}, ++ {0x401a, 0x00, 0, 0}, ++ {0x401b, 0x00, 0, 0}, ++ {0x401c, 0x00, 0, 0}, ++ {0x401d, 0x00, 0, 0}, ++ {0x401f, 0x00, 0, 0}, ++ {0x4020, 0x00, 0, 0}, ++ {0x4021, 0x10, 0, 0}, ++ {0x4022, 0x06, 0, 0}, ++ {0x4023, 0x13, 0, 0}, ++ {0x4024, 0x07, 0, 0}, ++ {0x4025, 0x40, 0, 0}, ++ {0x4026, 0x07, 0, 0}, ++ {0x4027, 0x50, 0, 0}, ++ {0x4028, 0x00, 0, 0}, ++ {0x4029, 0x02, 0, 0}, ++ {0x402a, 0x06, 0, 0}, ++ {0x402b, 0x04, 0, 0}, ++ {0x402c, 0x02, 0, 0}, ++ {0x402d, 0x02, 0, 0}, ++ {0x402e, 0x0e, 0, 0}, ++ {0x402f, 0x04, 0, 0}, ++ {0x4302, 0xff, 0, 0}, ++ {0x4303, 0xff, 0, 0}, ++ {0x4304, 0x00, 0, 0}, ++ {0x4305, 0x00, 0, 0}, ++ {0x4306, 0x00, 0, 0}, ++ {0x4308, 0x02, 0, 0}, ++ {0x4500, 0x6c, 0, 0}, ++ {0x4501, 0xc4, 0, 0}, ++ {0x4502, 0x40, 0, 0}, ++ {0x4503, 0x01, 0, 0}, ++ {0x4601, 0x77, 0, 0}, ++ {0x4800, 0x04, 0, 0}, ++ {0x4813, 0x08, 0, 0}, ++ {0x481f, 0x40, 0, 0}, ++ {0x4829, 0x78, 0, 0}, ++ {0x4837, 0x10, 0, 0}, ++ {0x4b00, 0x2a, 0, 0}, ++ {0x4b0d, 0x00, 0, 0}, ++ {0x4d00, 0x04, 0, 0}, ++ {0x4d01, 0x42, 0, 0}, ++ {0x4d02, 0xd1, 0, 0}, ++ {0x4d03, 0x93, 0, 0}, ++ {0x4d04, 0xf5, 0, 0}, ++ {0x4d05, 0xc1, 0, 0}, ++ {0x5000, 0xf3, 0, 0}, ++ {0x5001, 0x11, 0, 0}, ++ {0x5004, 0x00, 0, 0}, ++ {0x500a, 0x00, 0, 0}, ++ {0x500b, 0x00, 0, 0}, ++ {0x5032, 0x00, 0, 0}, ++ {0x5040, 0x00, 0, 0}, ++ {0x5050, 0x0c, 0, 0}, ++ {0x5500, 0x00, 0, 0}, ++ {0x5501, 0x10, 0, 0}, ++ {0x5502, 0x01, 0, 0}, ++ {0x5503, 0x0f, 0, 0}, ++ {0x8000, 0x00, 0, 0}, ++ {0x8001, 0x00, 0, 0}, ++ {0x8002, 0x00, 0, 0}, ++ {0x8003, 0x00, 0, 0}, ++ {0x8004, 0x00, 0, 0}, ++ {0x8005, 0x00, 0, 0}, ++ {0x8006, 0x00, 0, 0}, ++ {0x8007, 0x00, 0, 0}, ++ {0x8008, 0x00, 0, 0}, ++ {0x3638, 0x00, 0, 0}, ++}; ++ ++static const struct reg_value ov4689_setting_4M_2688_1520[] = { ++ //@@ 0 10 RES_2688x1520_default(60fps) ++ //102 2630 960 ++ {0x0103, 0x01, 0, 0}, ++ {0x3638, 0x00, 0, 0}, ++ {0x0300, 0x00, 0, 0}, ++ {0x0302, 0x22, 0, 0}, // 2a ;1008Mbps,23 ;; 840Mbps ++ {0x0304, 0x03, 0, 0}, ++ {0x030b, 0x00, 0, 0}, ++ {0x030d, 0x1e, 0, 0}, ++ {0x030e, 0x04, 0, 0}, ++ {0x030f, 0x01, 0, 0}, ++ {0x0312, 0x01, 0, 0}, ++ {0x031e, 0x00, 0, 0}, ++ {0x3000, 0x20, 0, 0}, ++ {0x3002, 0x00, 0, 0}, ++ {0x3020, 0x93, 0, 0}, ++ {0x3021, 0x03, 0, 0}, ++ {0x3022, 0x01, 0, 0}, ++ {0x3031, 0x0a, 0, 0}, ++ {0x303f, 0x0c, 0, 0}, ++ {0x3305, 0xf1, 0, 0}, ++ {0x3307, 0x04, 0, 0}, ++ {0x3309, 0x29, 0, 0}, ++ {0x3500, 0x00, 0, 0}, ++ {0x3501, 0x60, 0, 0}, ++ {0x3502, 0x00, 0, 0}, ++ {0x3503, 0x04, 0, 0}, ++ {0x3504, 0x00, 0, 0}, ++ {0x3505, 0x00, 0, 0}, ++ {0x3506, 0x00, 0, 0}, ++ {0x3507, 0x00, 0, 0}, ++ {0x3508, 0x00, 0, 0}, ++ {0x3509, 0x80, 0, 0}, ++ {0x350a, 0x00, 0, 0}, ++ {0x350b, 0x00, 0, 0}, ++ {0x350c, 0x00, 0, 0}, ++ {0x350d, 0x00, 0, 0}, ++ {0x350e, 0x00, 0, 0}, ++ {0x350f, 0x80, 0, 0}, ++ {0x3510, 0x00, 0, 0}, ++ {0x3511, 0x00, 0, 0}, ++ {0x3512, 0x00, 0, 0}, ++ {0x3513, 0x00, 0, 0}, ++ {0x3514, 0x00, 0, 0}, ++ {0x3515, 0x80, 0, 0}, ++ {0x3516, 0x00, 0, 0}, ++ {0x3517, 0x00, 0, 0}, ++ {0x3518, 0x00, 0, 0}, ++ {0x3519, 0x00, 0, 0}, ++ {0x351a, 0x00, 0, 0}, ++ {0x351b, 0x80, 0, 0}, ++ {0x351c, 0x00, 0, 0}, ++ {0x351d, 0x00, 0, 0}, ++ {0x351e, 0x00, 0, 0}, ++ {0x351f, 0x00, 0, 0}, ++ {0x3520, 0x00, 0, 0}, ++ {0x3521, 0x80, 0, 0}, ++ {0x3522, 0x08, 0, 0}, ++ {0x3524, 0x08, 0, 0}, ++ {0x3526, 0x08, 0, 0}, ++ {0x3528, 0x08, 0, 0}, ++ {0x352a, 0x08, 0, 0}, ++ {0x3602, 0x00, 0, 0}, ++ {0x3603, 0x40, 0, 0}, ++ {0x3604, 0x02, 0, 0}, ++ {0x3605, 0x00, 0, 0}, ++ {0x3606, 0x00, 0, 0}, ++ {0x3607, 0x00, 0, 0}, ++ {0x3609, 0x12, 0, 0}, ++ {0x360a, 0x40, 0, 0}, ++ {0x360c, 0x08, 0, 0}, ++ {0x360f, 0xe5, 0, 0}, ++ {0x3608, 0x8f, 0, 0}, ++ {0x3611, 0x00, 0, 0}, ++ {0x3613, 0xf7, 0, 0}, ++ {0x3616, 0x58, 0, 0}, ++ {0x3619, 0x99, 0, 0}, ++ {0x361b, 0x60, 0, 0}, ++ {0x361c, 0x7a, 0, 0}, ++ {0x361e, 0x79, 0, 0}, ++ {0x361f, 0x02, 0, 0}, ++ {0x3632, 0x00, 0, 0}, ++ {0x3633, 0x10, 0, 0}, ++ {0x3634, 0x10, 0, 0}, ++ {0x3635, 0x10, 0, 0}, ++ {0x3636, 0x15, 0, 0}, ++ {0x3646, 0x86, 0, 0}, ++ {0x364a, 0x0b, 0, 0}, ++ {0x3700, 0x17, 0, 0}, ++ {0x3701, 0x22, 0, 0}, ++ {0x3703, 0x10, 0, 0}, ++ {0x370a, 0x37, 0, 0}, ++ {0x3705, 0x00, 0, 0}, ++ {0x3706, 0x63, 0, 0}, ++ {0x3709, 0x3c, 0, 0}, ++ {0x370b, 0x01, 0, 0}, ++ {0x370c, 0x30, 0, 0}, ++ {0x3710, 0x24, 0, 0}, ++ {0x3711, 0x0c, 0, 0}, ++ {0x3716, 0x00, 0, 0}, ++ {0x3720, 0x28, 0, 0}, ++ {0x3729, 0x7b, 0, 0}, ++ {0x372a, 0x84, 0, 0}, ++ {0x372b, 0xbd, 0, 0}, ++ {0x372c, 0xbc, 0, 0}, ++ {0x372e, 0x52, 0, 0}, ++ {0x373c, 0x0e, 0, 0}, ++ {0x373e, 0x33, 0, 0}, ++ {0x3743, 0x10, 0, 0}, ++ {0x3744, 0x88, 0, 0}, ++ {0x3745, 0xc0, 0, 0}, ++ {0x374a, 0x43, 0, 0}, ++ {0x374c, 0x00, 0, 0}, ++ {0x374e, 0x23, 0, 0}, ++ {0x3751, 0x7b, 0, 0}, ++ {0x3752, 0x84, 0, 0}, ++ {0x3753, 0xbd, 0, 0}, ++ {0x3754, 0xbc, 0, 0}, ++ {0x3756, 0x52, 0, 0}, ++ {0x375c, 0x00, 0, 0}, ++ {0x3760, 0x00, 0, 0}, ++ {0x3761, 0x00, 0, 0}, ++ {0x3762, 0x00, 0, 0}, ++ {0x3763, 0x00, 0, 0}, ++ {0x3764, 0x00, 0, 0}, ++ {0x3767, 0x04, 0, 0}, ++ {0x3768, 0x04, 0, 0}, ++ {0x3769, 0x08, 0, 0}, ++ {0x376a, 0x08, 0, 0}, ++ {0x376b, 0x20, 0, 0}, ++ {0x376c, 0x00, 0, 0}, ++ {0x376d, 0x00, 0, 0}, ++ {0x376e, 0x00, 0, 0}, ++ {0x3773, 0x00, 0, 0}, ++ {0x3774, 0x51, 0, 0}, ++ {0x3776, 0xbd, 0, 0}, ++ {0x3777, 0xbd, 0, 0}, ++ {0x3781, 0x18, 0, 0}, ++ {0x3783, 0x25, 0, 0}, ++ {0x3798, 0x1b, 0, 0}, ++ {0x3800, 0x00, 0, 0}, ++ {0x3801, 0x08, 0, 0}, ++ {0x3802, 0x00, 0, 0}, ++ {0x3803, 0x04, 0, 0}, ++ {0x3804, 0x0a, 0, 0}, ++ {0x3805, 0x97, 0, 0}, ++ {0x3806, 0x05, 0, 0}, ++ {0x3807, 0xfb, 0, 0}, ++ {0x3808, 0x0a, 0, 0}, ++ {0x3809, 0x80, 0, 0}, ++ {0x380a, 0x05, 0, 0}, ++ {0x380b, 0xf0, 0, 0}, ++ {0x380c, 0x03, 0, 0}, ++ {0x380d, 0x5c, 0, 0}, ++ {0x380e, 0x06, 0, 0}, ++ {0x380f, 0x12, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x08, 0, 0}, ++ {0x3812, 0x00, 0, 0}, ++ {0x3813, 0x04, 0, 0}, ++ {0x3814, 0x01, 0, 0}, ++ {0x3815, 0x01, 0, 0}, ++ {0x3819, 0x01, 0, 0}, ++ {0x3820, 0x00, 0, 0}, ++ {0x3821, 0x06, 0, 0}, ++ {0x3829, 0x00, 0, 0}, ++ {0x382a, 0x01, 0, 0}, ++ {0x382b, 0x01, 0, 0}, ++ {0x382d, 0x7f, 0, 0}, ++ {0x3830, 0x04, 0, 0}, ++ {0x3836, 0x01, 0, 0}, ++ {0x3837, 0x00, 0, 0}, ++ {0x3841, 0x02, 0, 0}, ++ {0x3846, 0x08, 0, 0}, ++ {0x3847, 0x07, 0, 0}, ++ {0x3d85, 0x36, 0, 0}, ++ {0x3d8c, 0x71, 0, 0}, ++ {0x3d8d, 0xcb, 0, 0}, ++ {0x3f0a, 0x00, 0, 0}, ++ {0x4000, 0x71, 0, 0}, ++ {0x4001, 0x40, 0, 0}, ++ {0x4002, 0x04, 0, 0}, ++ {0x4003, 0x14, 0, 0}, ++ {0x400e, 0x00, 0, 0}, ++ {0x4011, 0x00, 0, 0}, ++ {0x401a, 0x00, 0, 0}, ++ {0x401b, 0x00, 0, 0}, ++ {0x401c, 0x00, 0, 0}, ++ {0x401d, 0x00, 0, 0}, ++ {0x401f, 0x00, 0, 0}, ++ {0x4020, 0x00, 0, 0}, ++ {0x4021, 0x10, 0, 0}, ++ {0x4022, 0x07, 0, 0}, ++ {0x4023, 0xcf, 0, 0}, ++ {0x4024, 0x09, 0, 0}, ++ {0x4025, 0x60, 0, 0}, ++ {0x4026, 0x09, 0, 0}, ++ {0x4027, 0x6f, 0, 0}, ++ {0x4028, 0x00, 0, 0}, ++ {0x4029, 0x02, 0, 0}, ++ {0x402a, 0x06, 0, 0}, ++ {0x402b, 0x04, 0, 0}, ++ {0x402c, 0x02, 0, 0}, ++ {0x402d, 0x02, 0, 0}, ++ {0x402e, 0x0e, 0, 0}, ++ {0x402f, 0x04, 0, 0}, ++ {0x4302, 0xff, 0, 0}, ++ {0x4303, 0xff, 0, 0}, ++ {0x4304, 0x00, 0, 0}, ++ {0x4305, 0x00, 0, 0}, ++ {0x4306, 0x00, 0, 0}, ++ {0x4308, 0x02, 0, 0}, ++ {0x4500, 0x6c, 0, 0}, ++ {0x4501, 0xc4, 0, 0}, ++ {0x4502, 0x40, 0, 0}, ++ {0x4503, 0x01, 0, 0}, ++ {0x4601, 0x04, 0, 0}, ++ {0x4800, 0x04, 0, 0}, ++ {0x4813, 0x08, 0, 0}, ++ {0x481f, 0x40, 0, 0}, ++ {0x4829, 0x78, 0, 0}, ++ {0x4837, 0x14, 0, 0}, // 10 ++ {0x4b00, 0x2a, 0, 0}, ++ {0x4b0d, 0x00, 0, 0}, ++ {0x4d00, 0x04, 0, 0}, ++ {0x4d01, 0x42, 0, 0}, ++ {0x4d02, 0xd1, 0, 0}, ++ {0x4d03, 0x93, 0, 0}, ++ {0x4d04, 0xf5, 0, 0}, ++ {0x4d05, 0xc1, 0, 0}, ++ {0x5000, 0xf3, 0, 0}, ++ {0x5001, 0x11, 0, 0}, ++ {0x5004, 0x00, 0, 0}, ++ {0x500a, 0x00, 0, 0}, ++ {0x500b, 0x00, 0, 0}, ++ {0x5032, 0x00, 0, 0}, ++ {0x5040, 0x00, 0, 0}, ++ {0x5050, 0x0c, 0, 0}, ++ {0x5500, 0x00, 0, 0}, ++ {0x5501, 0x10, 0, 0}, ++ {0x5502, 0x01, 0, 0}, ++ {0x5503, 0x0f, 0, 0}, ++ {0x8000, 0x00, 0, 0}, ++ {0x8001, 0x00, 0, 0}, ++ {0x8002, 0x00, 0, 0}, ++ {0x8003, 0x00, 0, 0}, ++ {0x8004, 0x00, 0, 0}, ++ {0x8005, 0x00, 0, 0}, ++ {0x8006, 0x00, 0, 0}, ++ {0x8007, 0x00, 0, 0}, ++ {0x8008, 0x00, 0, 0}, ++ {0x3638, 0x00, 0, 0}, ++// {0x0100, 0x01, 0, 0}, ++ ++// {0x0100, 0x00, 0, 0}, ++ {0x380c, 0x0A, 0, 0}, // 05 ++ {0x380d, 0x0A, 0, 0}, // 10 ++ {0x380e, 0x06, 0, 0}, ++ {0x380f, 0x12, 0, 0}, ++// {0x0100, 0x01, 0, 0}, ++ {0x3105, 0x31, 0, 0}, ++ {0x301a, 0xf9, 0, 0}, ++ {0x3508, 0x07, 0, 0}, ++ {0x484b, 0x05, 0, 0}, ++ {0x4805, 0x03, 0, 0}, ++ {0x3601, 0x01, 0, 0}, ++ {0x3745, 0xc0, 0, 0}, ++ {0x3798, 0x1b, 0, 0}, ++// {0x0100, 0x01, 0, 0}, ++ {0xffff, 0x0a, 0, 0}, ++ {0x3105, 0x11, 0, 0}, ++ {0x301a, 0xf1, 0, 0}, ++ {0x4805, 0x00, 0, 0}, ++ {0x301a, 0xf0, 0, 0}, ++ {0x3208, 0x00, 0, 0}, ++ {0x302a, 0x00, 0, 0}, ++ {0x302a, 0x00, 0, 0}, ++ {0x302a, 0x00, 0, 0}, ++ {0x302a, 0x00, 0, 0}, ++ {0x302a, 0x00, 0, 0}, ++ {0x3601, 0x00, 0, 0}, ++ {0x3638, 0x00, 0, 0}, ++ {0x3208, 0x10, 0, 0}, ++ {0x3208, 0xa0, 0, 0}, ++}; ++ ++/* power-on sensor init reg table */ ++static const struct ov4689_mode_info ov4689_mode_init_data = { ++ ++}; ++ ++static const struct ov4689_mode_info ++ov4689_mode_data[OV4689_NUM_MODES] = { ++ // {OV4689_MODE_720P_1280_720, SUBSAMPLING, ++ // 1280, 0x408, 720, 0x305, ++ // ov4689_setting_720P_1280_720, ++ // ARRAY_SIZE(ov4689_setting_720P_1280_720), ++ // OV4689_150_FPS}, ++ // {OV4689_MODE_1080P_1920_1080, SCALING, ++ // 1920, 0x6e0, 1080, 0x470, ++ // ov4689_setting_1080P_1920_1080, ++ // ARRAY_SIZE(ov4689_setting_1080P_1920_1080), ++ // OV4689_60_FPS}, ++ // {OV4689_MODE_4M_2688_1520, SCALING, ++ // 2688, 0xa0a, 1520, 0x612, ++ // ov4689_setting_4M_2688_1520, ++ // ARRAY_SIZE(ov4689_setting_4M_2688_1520), ++ // OV4689_60_FPS}, ++ ++ {OV4689_MODE_1080P_1920_1080, SCALING, ++ 1920, 0x6e0, 1080, 0x470, ++ ov4689_init_setting_30fps_1080P, ++ ARRAY_SIZE(ov4689_init_setting_30fps_1080P), ++ OV4689_60_FPS}, ++}; ++ ++static int ov4689_write_reg(struct ov4689_dev *sensor, u16 reg, u8 val) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ struct i2c_msg msg; ++ u8 buf[3]; ++ int ret; ++ ++ buf[0] = reg >> 8; ++ buf[1] = reg & 0xff; ++ buf[2] = val; ++ ++ msg.addr = client->addr; ++ msg.flags = client->flags; ++ msg.buf = buf; ++ msg.len = sizeof(buf); ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ if (ret < 0) { ++ dev_err(&client->dev, "%s: error: reg=%x, val=%x\n", ++ __func__, reg, val); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ov4689_read_reg(struct ov4689_dev *sensor, u16 reg, u8 *val) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ struct i2c_msg msg[2]; ++ u8 buf[2]; ++ int ret; ++ ++ buf[0] = reg >> 8; ++ buf[1] = reg & 0xff; ++ ++ msg[0].addr = client->addr; ++ msg[0].flags = client->flags; ++ msg[0].buf = buf; ++ msg[0].len = sizeof(buf); ++ ++ msg[1].addr = client->addr; ++ msg[1].flags = client->flags | I2C_M_RD; ++ msg[1].buf = buf; ++ msg[1].len = 1; ++ ++ ret = i2c_transfer(client->adapter, msg, 2); ++ if (ret < 0) { ++ dev_err(&client->dev, "%s: error: reg=%x\n", ++ __func__, reg); ++ return ret; ++ } ++ ++ *val = buf[0]; ++ return 0; ++} ++ ++static int ov4689_read_reg16(struct ov4689_dev *sensor, u16 reg, u16 *val) ++{ ++ u8 hi, lo; ++ int ret; ++ ++ ret = ov4689_read_reg(sensor, reg, &hi); ++ if (ret) ++ return ret; ++ ret = ov4689_read_reg(sensor, reg + 1, &lo); ++ if (ret) ++ return ret; ++ ++ *val = ((u16)hi << 8) | (u16)lo; ++ return 0; ++} ++ ++static int ov4689_write_reg16(struct ov4689_dev *sensor, u16 reg, u16 val) ++{ ++ int ret; ++ ++ ret = ov4689_write_reg(sensor, reg, val >> 8); ++ if (ret) ++ return ret; ++ ++ return ov4689_write_reg(sensor, reg + 1, val & 0xff); ++} ++ ++static int ov4689_mod_reg(struct ov4689_dev *sensor, u16 reg, ++ u8 mask, u8 val) ++{ ++ u8 readval; ++ int ret; ++ ++ ret = ov4689_read_reg(sensor, reg, &readval); ++ if (ret) ++ return ret; ++ ++ readval &= ~mask; ++ val &= mask; ++ val |= readval; ++ ++ return ov4689_write_reg(sensor, reg, val); ++} ++ ++static int ov4689_set_timings(struct ov4689_dev *sensor, ++ const struct ov4689_mode_info *mode) ++{ ++ return 0; ++} ++ ++static int ov4689_load_regs(struct ov4689_dev *sensor, ++ const struct ov4689_mode_info *mode) ++{ ++ const struct reg_value *regs = mode->reg_data; ++ unsigned int i; ++ u32 delay_ms; ++ u16 reg_addr; ++ u8 mask, val; ++ int ret = 0; ++ ++ st_info(ST_SENSOR, "%s, mode = 0x%x\n", __func__, mode->id); ++ for (i = 0; i < mode->reg_data_size; ++i, ++regs) { ++ delay_ms = regs->delay_ms; ++ reg_addr = regs->reg_addr; ++ val = regs->val; ++ mask = regs->mask; ++ ++ if (mask) ++ ret = ov4689_mod_reg(sensor, reg_addr, mask, val); ++ else ++ ret = ov4689_write_reg(sensor, reg_addr, val); ++ if (ret) ++ break; ++ ++ if (delay_ms) ++ usleep_range(1000 * delay_ms, 1000 * delay_ms + 100); ++ } ++ ++ return ov4689_set_timings(sensor, mode); ++} ++ ++#ifdef UNUSED_CODE ++static int ov4689_get_exposure(struct ov4689_dev *sensor) ++{ ++ int exp, ret; ++ u8 temp; ++ ++ ret = ov4689_read_reg(sensor, OV4689_REG_EXPOSURE_HI, &temp); ++ if (ret) ++ return ret; ++ exp = ((int)temp & 0x0f) << 16; ++ ret = ov4689_read_reg(sensor, OV4689_REG_EXPOSURE_MED, &temp); ++ if (ret) ++ return ret; ++ exp |= ((int)temp << 8); ++ ret = ov4689_read_reg(sensor, OV4689_REG_EXPOSURE_LO, &temp); ++ if (ret) ++ return ret; ++ exp |= (int)temp; ++ ++ return exp >> 4; ++} ++#endif ++ ++static int ov4689_set_exposure(struct ov4689_dev *sensor, u32 exposure) ++{ ++ int ret; ++ ++ st_info(ST_SENSOR, "%s, exposure = 0x%x\n", __func__, exposure); ++ exposure <<= 4; ++ ++ ret = ov4689_write_reg(sensor, ++ OV4689_REG_EXPOSURE_LO, ++ exposure & 0xff); ++ if (ret) ++ return ret; ++ ret = ov4689_write_reg(sensor, ++ OV4689_REG_EXPOSURE_MED, ++ (exposure >> 8) & 0xff); ++ if (ret) ++ return ret; ++ return ov4689_write_reg(sensor, ++ OV4689_REG_EXPOSURE_HI, ++ (exposure >> 16) & 0x0f); ++} ++ ++static int ov4689_get_gain(struct ov4689_dev *sensor) ++{ ++ u32 gain = 0; ++ u8 val; ++ ++ ov4689_read_reg(sensor, OV4689_REG_GAIN_H, &val); ++ gain = (val & 0x3) << 16; ++ ov4689_read_reg(sensor, OV4689_REG_GAIN_M, &val); ++ gain |= val << 8; ++ ov4689_read_reg(sensor, OV4689_REG_GAIN_L, &val); ++ gain |= val; ++ ++ return gain; ++} ++ ++static int ov4689_set_gain(struct ov4689_dev *sensor, int gain) ++{ ++ ov4689_write_reg(sensor, OV4689_REG_GAIN_H, ++ (gain >> 16) & 0x3); ++ ov4689_write_reg(sensor, OV4689_REG_GAIN_M, ++ (gain >> 8) & 0xff); ++ ov4689_write_reg(sensor, OV4689_REG_GAIN_L, ++ gain & 0xff); ++ return 0; ++} ++ ++#ifdef UNUSED_CODE ++static int ov4689_get_sysclk(struct ov4689_dev *sensor) ++{ ++ return 0; ++} ++ ++static int ov4689_set_night_mode(struct ov4689_dev *sensor) ++{ ++ return 0; ++} ++ ++static int ov4689_get_hts(struct ov4689_dev *sensor) ++{ ++ /* read HTS from register settings */ ++ u16 hts; ++ int ret; ++ ++ ret = ov4689_read_reg16(sensor, OV4689_REG_TIMING_HTS, &hts); ++ if (ret) ++ return ret; ++ return hts; ++} ++#endif ++ ++static int ov4689_get_vts(struct ov4689_dev *sensor) ++{ ++ u16 vts; ++ int ret; ++ ++ ret = ov4689_read_reg16(sensor, OV4689_REG_TIMING_VTS, &vts); ++ if (ret) ++ return ret; ++ return vts; ++} ++ ++#ifdef UNUSED_CODE ++static int ov4689_set_vts(struct ov4689_dev *sensor, int vts) ++{ ++ return ov4689_write_reg16(sensor, OV4689_REG_TIMING_VTS, vts); ++} ++ ++static int ov4689_get_light_freq(struct ov4689_dev *sensor) ++{ ++ return 0; ++} ++ ++static int ov4689_set_bandingfilter(struct ov4689_dev *sensor) ++{ ++ return 0; ++} ++ ++static int ov4689_set_ae_target(struct ov4689_dev *sensor, int target) ++{ ++ return 0; ++} ++ ++static int ov4689_get_binning(struct ov4689_dev *sensor) ++{ ++ return 0; ++} ++ ++static int ov4689_set_binning(struct ov4689_dev *sensor, bool enable) ++{ ++ return 0; ++} ++#endif ++ ++static const struct ov4689_mode_info * ++ov4689_find_mode(struct ov4689_dev *sensor, enum ov4689_frame_rate fr, ++ int width, int height, bool nearest) ++{ ++ const struct ov4689_mode_info *mode; ++ ++ mode = v4l2_find_nearest_size(ov4689_mode_data, ++ ARRAY_SIZE(ov4689_mode_data), ++ hact, vact, ++ width, height); ++ ++ if (!mode || ++ (!nearest && (mode->hact != width || mode->vact != height))) ++ return NULL; ++ ++ /* Check to see if the current mode exceeds the max frame rate */ ++ if (ov4689_framerates[fr] > ov4689_framerates[mode->max_fps]) ++ return NULL; ++ ++ return mode; ++} ++ ++static u64 ov4689_calc_pixel_rate(struct ov4689_dev *sensor) ++{ ++ u64 rate; ++ ++ rate = sensor->current_mode->vact * sensor->current_mode->hact; ++ rate *= ov4689_framerates[sensor->current_fr]; ++ ++ return rate; ++} ++ ++/* ++ * After trying the various combinations, reading various ++ * documentations spread around the net, and from the various ++ * feedback, the clock tree is probably as follows: ++ * ++ * +--------------+ ++ * | Ext. Clock | ++ * +-+------------+ ++ * | +----------+ ++ * +->| PLL1 | - reg 0x030a, bit0 for the pre-dividerp ++ * +-+--------+ - reg 0x0300, bits 0-2 for the pre-divider ++ * +-+--------+ - reg 0x0301~0x0302, for the multiplier ++ * | +--------------+ ++ * +->| MIPI Divider | - reg 0x0303, bits 0-3 for the pre-divider ++ * | +---------> MIPI PHY CLK ++ * | +-----+ ++ * | +->| PLL1_DIV_MIPI | - reg 0x0304, bits 0-1 for the divider ++ * | +----------------> PCLK ++ * | +-----+ ++ * ++ * +--------------+ ++ * | Ext. Clock | ++ * +-+------------+ ++ * | +----------+ ++ * +->| PLL2 | - reg 0x0311, bit0 for the pre-dividerp ++ * +-+--------+ - reg 0x030b, bits 0-2 for the pre-divider ++ * +-+--------+ - reg 0x030c~0x030d, for the multiplier ++ * | +--------------+ ++ * +->| SCLK Divider | - reg 0x030F, bits 0-3 for the pre-divider ++ * +-+--------+ - reg 0x030E, bits 0-2 for the divider ++ * | +---------> SCLK ++ * ++ * | +-----+ ++ * +->| DAC Divider | - reg 0x0312, bits 0-3 for the divider ++ * | +----------------> DACCLK ++ ** ++ */ ++ ++/* ++ * ov4689_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: ++ * ++ * - mipi_pclk = bpl / 2; ( / 2 is for CSI-2 DDR) ++ * - mipi_phy_clk = mipi_pclk * PLL1_DIV_MIPI; ++ * ++ * with these fixed parameters: ++ * PLL1_PREDIVP = 1; ++ * PLL1_PREDIV = 1; (MIPI_BIT_MODE == 8 ? 2 : 2,5); ++ * PLL1_DIVM = 1; ++ * PLL1_DIV_MIPI = 4; ++ * ++ * FIXME: this have been tested with 10-bit raw 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. ++ */ ++ ++#define PLL1_PREDIVP 1 // bypass ++#define PLL1_PREDIV 1 // bypass ++#define PLL1_DIVM 1 // bypass ++#define PLL1_DIV_MIPI 3 // div ++#define PLL1_DIV_MIPI_BASE 1 // div ++ ++#define PLL1_DIVSP 1 // no use ++#define PLL1_DIVS 1 // no use ++ ++#define PLL2_PREDIVP 0 ++#define PLL2_PREDIV 0 ++#define PLL2_DIVSP 1 ++#define PLL2_DIVS 4 ++#define PLL2_DIVDAC 1 ++ ++#define OV4689_PLL1_PREDIVP 0x030a // bits[0] ++#define OV4689_PLL1_PREDIV 0x0300 // bits[2:0] ++#define OV4689_PLL1_MULTIPLIER 0x0301 // bits[9:8] 0x0302 bits[7:0] ++#define OV4689_PLL1_DIVM 0x0303 // bits[3:0] ++#define OV4689_PLL1_DIV_MIPI 0x0304 // bits[1:0] ++ ++#define OV4689_PLL1_DIVSP 0x0305 //bits[1:0] ++#define OV4689_PLL1_DIVS 0x0306 // bits[0] ++ ++#define OV4689_PLL2_PREDIVP 0x0311 // bits[0] ++#define OV4689_PLL2_PREDIV 0x030b // bits[2:0] ++#define OV4689_PLL2_MULTIPLIER 0x030c // bits[9:8] 0x030d bits[7:0] ++#define OV4689_PLL2_DIVSP 0x030f // bits[3:0] ++#define OV4689_PLL2_DIVS 0x030e // bits[2:0] ++#define OV4689_PLL2_DIVDAC 0x0312 // bits[3:0] ++ ++static int ov4689_set_mipi_pclk(struct ov4689_dev *sensor, ++ unsigned long rate) ++{ ++ const struct ov4689_mode_info *mode = sensor->current_mode; ++ //const struct ov4689_mode_info *orig_mode = sensor->last_mode; ++ u8 val; ++ int ret = 0; ++ int fps = ov4689_framerates[sensor->current_fr]; ++ u16 htot, val16; ++ ++ htot = mode->htot * ov4689_framerates[mode->max_fps] / fps; ++ ++ ret = ov4689_write_reg16(sensor, OV4689_REG_TIMING_HTS, htot); ++ ++ ret = ov4689_read_reg(sensor, OV4689_REG_TIMING_HTS, &val); ++ val16 = val << 8; ++ ret = ov4689_read_reg(sensor, OV4689_REG_TIMING_HTS + 1, &val); ++ val16 |= val; ++ ++ st_info(ST_SENSOR, "fps = %d, max_fps = %d\n", fps, mode->max_fps); ++ st_info(ST_SENSOR, "mode->htot = 0x%x, htot = 0x%x\n", mode->htot, ++ htot); ++ st_info(ST_SENSOR, "reg: 0x%x = 0x%x\n", OV4689_REG_TIMING_HTS, val16); ++ ++ return 0; ++} ++ ++/* ++ * if sensor changes inside scaling or subsampling ++ * change mode directly ++ */ ++static int ov4689_set_mode_direct(struct ov4689_dev *sensor, ++ const struct ov4689_mode_info *mode) ++{ ++ if (!mode->reg_data) ++ return -EINVAL; ++ ++ /* Write capture setting */ ++ return ov4689_load_regs(sensor, mode); ++} ++ ++static int ov4689_set_mode(struct ov4689_dev *sensor) ++{ ++ const struct ov4689_mode_info *mode = sensor->current_mode; ++ ++ int ret = 0; ++ ++ ret = ov4689_set_mode_direct(sensor, mode); ++ if (ret < 0) ++ return ret; ++ ++ ret = ov4689_set_mipi_pclk(sensor, 0); ++ if (ret < 0) ++ return 0; ++ ++ sensor->pending_mode_change = false; ++ sensor->last_mode = mode; ++ return 0; ++} ++ ++/* restore the last set video mode after chip power-on */ ++static int ov4689_restore_mode(struct ov4689_dev *sensor) ++{ ++ int ret; ++ ++ /* first load the initial register values */ ++ ret = ov4689_load_regs(sensor, &ov4689_mode_init_data); ++ if (ret < 0) ++ return ret; ++ sensor->last_mode = &ov4689_mode_init_data; ++ ++ /* now restore the last capture mode */ ++ ret = ov4689_set_mode(sensor); ++ if (ret < 0) ++ return ret; ++ ++ return ret; ++} ++ ++static void ov4689_power(struct ov4689_dev *sensor, bool enable) ++{ ++ if (!sensor->pwdn_gpio) ++ return; ++ gpiod_set_value_cansleep(sensor->pwdn_gpio, enable ? 0 : 1); ++} ++ ++static void ov4689_reset(struct ov4689_dev *sensor) ++{ ++ if (!sensor->reset_gpio) ++ return; ++ ++ gpiod_set_value_cansleep(sensor->reset_gpio, 0); ++ ++ usleep_range(5000, 25000); ++ ++ gpiod_set_value_cansleep(sensor->reset_gpio, 1); ++ usleep_range(1000, 2000); ++} ++ ++static int ov4689_set_power_on(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov4689_dev *sensor = to_ov4689_dev(sd); ++ int ret; ++ ++ ret = clk_prepare_enable(sensor->xclk); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to enable clock\n", ++ __func__); ++ return ret; ++ } ++ ++ ret = regulator_bulk_enable(OV4689_NUM_SUPPLIES, ++ sensor->supplies); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to enable regulators\n", ++ __func__); ++ goto xclk_off; ++ } ++ ++ ov4689_reset(sensor); ++ ov4689_power(sensor, true); ++ ++ return 0; ++ ++xclk_off: ++ clk_disable_unprepare(sensor->xclk); ++ return ret; ++} ++ ++static int ov4689_set_power_off(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov4689_dev *sensor = to_ov4689_dev(sd); ++ ++ ov4689_power(sensor, false); ++ regulator_bulk_disable(OV4689_NUM_SUPPLIES, sensor->supplies); ++ clk_disable_unprepare(sensor->xclk); ++ ++ return 0; ++} ++ ++static int ov4689_try_frame_interval(struct ov4689_dev *sensor, ++ struct v4l2_fract *fi, ++ u32 width, u32 height) ++{ ++ const struct ov4689_mode_info *mode; ++ enum ov4689_frame_rate rate = OV4689_15_FPS; ++ int minfps, maxfps, best_fps, fps; ++ int i; ++ ++ minfps = ov4689_framerates[OV4689_15_FPS]; ++ maxfps = ov4689_framerates[OV4689_NUM_FRAMERATES - 1]; ++ ++ if (fi->numerator == 0) { ++ fi->denominator = maxfps; ++ fi->numerator = 1; ++ rate = OV4689_60_FPS; ++ goto find_mode; ++ } ++ ++ fps = clamp_val(DIV_ROUND_CLOSEST(fi->denominator, fi->numerator), ++ minfps, maxfps); ++ ++ best_fps = minfps; ++ for (i = 0; i < ARRAY_SIZE(ov4689_framerates); i++) { ++ int curr_fps = ov4689_framerates[i]; ++ ++ if (abs(curr_fps - fps) < abs(best_fps - fps)) { ++ best_fps = curr_fps; ++ rate = i; ++ } ++ } ++ st_info(ST_SENSOR, "best_fps = %d, fps = %d\n", best_fps, fps); ++ ++ fi->numerator = 1; ++ fi->denominator = best_fps; ++ ++find_mode: ++ mode = ov4689_find_mode(sensor, rate, width, height, false); ++ return mode ? rate : -EINVAL; ++} ++ ++static int ov4689_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ if (code->pad != 0) ++ return -EINVAL; ++ ++ if (code->index) ++ return -EINVAL; ++ ++ code->code = MEDIA_BUS_FMT_SBGGR10_1X10; ++ return 0; ++} ++ ++static int ov4689_get_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *format) ++{ ++ struct ov4689_dev *sensor = to_ov4689_dev(sd); ++ struct v4l2_mbus_framefmt *fmt; ++ ++ if (format->pad != 0) ++ return -EINVAL; ++ ++ mutex_lock(&sensor->lock); ++ ++ if (format->which == V4L2_SUBDEV_FORMAT_TRY) ++ fmt = v4l2_subdev_get_try_format(&sensor->sd, state, ++ format->pad); ++ else ++ fmt = &sensor->fmt; ++ ++ format->format = *fmt; ++ ++ mutex_unlock(&sensor->lock); ++ ++ return 0; ++} ++ ++static int ov4689_try_fmt_internal(struct v4l2_subdev *sd, ++ struct v4l2_mbus_framefmt *fmt, ++ enum ov4689_frame_rate fr, ++ const struct ov4689_mode_info **new_mode) ++{ ++ struct ov4689_dev *sensor = to_ov4689_dev(sd); ++ const struct ov4689_mode_info *mode; ++ ++ mode = ov4689_find_mode(sensor, fr, fmt->width, fmt->height, true); ++ if (!mode) ++ return -EINVAL; ++ fmt->width = mode->hact; ++ fmt->height = mode->vact; ++ ++ if (new_mode) ++ *new_mode = mode; ++ ++ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; ++ ++ return 0; ++} ++ ++static int ov4689_set_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *format) ++{ ++ struct ov4689_dev *sensor = to_ov4689_dev(sd); ++ const struct ov4689_mode_info *new_mode; ++ struct v4l2_mbus_framefmt *mbus_fmt = &format->format; ++ struct v4l2_mbus_framefmt *fmt; ++ int ret; ++ ++ if (format->pad != 0) ++ return -EINVAL; ++ ++ mutex_lock(&sensor->lock); ++ ++ if (sensor->streaming) { ++ ret = -EBUSY; ++ goto out; ++ } ++ ++ ret = ov4689_try_fmt_internal(sd, mbus_fmt, 0, &new_mode); ++ if (ret) ++ goto out; ++ ++ if (format->which == V4L2_SUBDEV_FORMAT_TRY) ++ fmt = v4l2_subdev_get_try_format(sd, state, 0); ++ else ++ fmt = &sensor->fmt; ++ ++ *fmt = *mbus_fmt; ++ ++ if (new_mode != sensor->current_mode) { ++ sensor->current_mode = new_mode; ++ sensor->pending_mode_change = true; ++ } ++ if (new_mode->max_fps < sensor->current_fr) { ++ sensor->current_fr = new_mode->max_fps; ++ sensor->frame_interval.numerator = 1; ++ sensor->frame_interval.denominator = ++ ov4689_framerates[sensor->current_fr]; ++ sensor->current_mode = new_mode; ++ sensor->pending_mode_change = true; ++ } ++ ++ __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, ++ ov4689_calc_pixel_rate(sensor)); ++out: ++ mutex_unlock(&sensor->lock); ++ return ret; ++} ++ ++/* ++ * Sensor Controls. ++ */ ++ ++static int ov4689_set_ctrl_hue(struct ov4689_dev *sensor, int value) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int ov4689_set_ctrl_contrast(struct ov4689_dev *sensor, int value) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int ov4689_set_ctrl_saturation(struct ov4689_dev *sensor, int value) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int ov4689_set_ctrl_white_balance(struct ov4689_dev *sensor, int awb) ++{ ++ struct ov4689_ctrls *ctrls = &sensor->ctrls; ++ int ret = 0; ++ ++ if (!awb && (ctrls->red_balance->is_new ++ || ctrls->blue_balance->is_new)) { ++ u16 red = (u16)ctrls->red_balance->val; ++ u16 blue = (u16)ctrls->blue_balance->val; ++ ++ st_info(ST_SENSOR, "red = 0x%x, blue = 0x%x\n", red, blue); ++ ret = ov4689_write_reg16(sensor, OV4689_REG_AWB_R_GAIN, red); ++ if (ret) ++ return ret; ++ ret = ov4689_write_reg16(sensor, OV4689_REG_AWB_B_GAIN, blue); ++ } ++ return ret; ++} ++ ++static int ov4689_set_ctrl_exposure(struct ov4689_dev *sensor, ++ enum v4l2_exposure_auto_type auto_exposure) ++{ ++ struct ov4689_ctrls *ctrls = &sensor->ctrls; ++ bool auto_exp = (auto_exposure == V4L2_EXPOSURE_AUTO); ++ int ret = 0; ++ ++ if (!auto_exp && ctrls->exposure->is_new) { ++ u16 max_exp = 0; ++ ++ ret = ov4689_read_reg16(sensor, OV4689_REG_V_OUTPUT_SIZE, ++ &max_exp); ++ ++ ret = ov4689_get_vts(sensor); ++ if (ret < 0) ++ return ret; ++ max_exp += ret; ++ ret = 0; ++ ++ st_info(ST_SENSOR, "%s, max_exp = 0x%x\n", __func__, max_exp); ++ if (ctrls->exposure->val < max_exp) ++ ret = ov4689_set_exposure(sensor, ctrls->exposure->val); ++ } ++ ++ return ret; ++} ++ ++static const s64 link_freq_menu_items[] = { ++ OV4689_LINK_FREQ_500MHZ ++}; ++ ++static const char * const test_pattern_menu[] = { ++ "Disabled", ++ "Color bars", ++ "Color bars w/ rolling bar", ++ "Color squares", ++ "Color squares w/ rolling bar", ++}; ++ ++#define OV4689_TEST_ENABLE BIT(7) ++#define OV4689_TEST_ROLLING BIT(6) /* rolling horizontal bar */ ++#define OV4689_TEST_TRANSPARENT BIT(5) ++#define OV4689_TEST_SQUARE_BW BIT(4) /* black & white squares */ ++#define OV4689_TEST_BAR_STANDARD (0 << 2) ++#define OV4689_TEST_BAR_DARKER_1 (1 << 2) ++#define OV4689_TEST_BAR_DARKER_2 (2 << 2) ++#define OV4689_TEST_BAR_DARKER_3 (3 << 2) ++#define OV4689_TEST_BAR (0 << 0) ++#define OV4689_TEST_RANDOM (1 << 0) ++#define OV4689_TEST_SQUARE (2 << 0) ++#define OV4689_TEST_BLACK (3 << 0) ++ ++static const u8 test_pattern_val[] = { ++ 0, ++ OV4689_TEST_ENABLE | OV4689_TEST_BAR_STANDARD | ++ OV4689_TEST_BAR, ++ OV4689_TEST_ENABLE | OV4689_TEST_ROLLING | ++ OV4689_TEST_BAR_DARKER_1 | OV4689_TEST_BAR, ++ OV4689_TEST_ENABLE | OV4689_TEST_SQUARE, ++ OV4689_TEST_ENABLE | OV4689_TEST_ROLLING | OV4689_TEST_SQUARE, ++}; ++ ++static int ov4689_set_ctrl_test_pattern(struct ov4689_dev *sensor, int value) ++{ ++ return ov4689_write_reg(sensor, OV4689_REG_TEST_PATTERN, ++ test_pattern_val[value]); ++} ++ ++static int ov4689_set_ctrl_light_freq(struct ov4689_dev *sensor, int value) ++{ ++ return 0; ++} ++ ++static int ov4689_set_ctrl_hflip(struct ov4689_dev *sensor, int value) ++{ ++ /* ++ * TIMING TC REG21: ++ * - [2]: Digital mirror ++ * - [1]: Array mirror ++ */ ++ return ov4689_mod_reg(sensor, OV4689_REG_TIMING_TC_REG21, ++ BIT(2) | BIT(1), ++ (!(value ^ sensor->upside_down)) ? ++ (BIT(2) | BIT(1)) : 0); ++} ++ ++static int ov4689_set_ctrl_vflip(struct ov4689_dev *sensor, int value) ++{ ++ /* ++ * TIMING TC REG20: ++ * - [2]: Digital vflip ++ * - [1]: Array vflip ++ */ ++ return ov4689_mod_reg(sensor, OV4689_REG_TIMING_TC_REG20, ++ BIT(2) | BIT(1), ++ (value ^ sensor->upside_down) ? ++ (BIT(2) | BIT(1)) : 0); ++} ++ ++static int ov4689_g_volatile_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct v4l2_subdev *sd = ctrl_to_sd(ctrl); ++ struct ov4689_dev *sensor = to_ov4689_dev(sd); ++ int val; ++ ++ /* v4l2_ctrl_lock() locks our own mutex */ ++ ++ if (!pm_runtime_get_if_in_use(&sensor->i2c_client->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_ANALOGUE_GAIN: ++ val = ov4689_get_gain(sensor); ++ break; ++ } ++ ++ pm_runtime_put(&sensor->i2c_client->dev); ++ ++ return 0; ++} ++ ++static int ov4689_s_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct v4l2_subdev *sd = ctrl_to_sd(ctrl); ++ struct ov4689_dev *sensor = to_ov4689_dev(sd); ++ int ret; ++ ++ /* v4l2_ctrl_lock() locks our own mutex */ ++ ++ /* ++ * If the device is not powered up by the host driver do ++ * not apply any controls to H/W at this time. Instead ++ * the controls will be restored at start streaming time. ++ */ ++ if (!pm_runtime_get_if_in_use(&sensor->i2c_client->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = ov4689_set_gain(sensor, ctrl->val); ++ break; ++ case V4L2_CID_EXPOSURE: ++ ret = ov4689_set_ctrl_exposure(sensor, V4L2_EXPOSURE_MANUAL); ++ break; ++ case V4L2_CID_AUTO_WHITE_BALANCE: ++ ret = ov4689_set_ctrl_white_balance(sensor, ctrl->val); ++ break; ++ case V4L2_CID_HUE: ++ ret = ov4689_set_ctrl_hue(sensor, ctrl->val); ++ break; ++ case V4L2_CID_CONTRAST: ++ ret = ov4689_set_ctrl_contrast(sensor, ctrl->val); ++ break; ++ case V4L2_CID_SATURATION: ++ ret = ov4689_set_ctrl_saturation(sensor, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN: ++ ret = ov4689_set_ctrl_test_pattern(sensor, ctrl->val); ++ break; ++ case V4L2_CID_POWER_LINE_FREQUENCY: ++ ret = ov4689_set_ctrl_light_freq(sensor, ctrl->val); ++ break; ++ case V4L2_CID_HFLIP: ++ ret = ov4689_set_ctrl_hflip(sensor, ctrl->val); ++ break; ++ case V4L2_CID_VFLIP: ++ ret = ov4689_set_ctrl_vflip(sensor, ctrl->val); ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ pm_runtime_put(&sensor->i2c_client->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops ov4689_ctrl_ops = { ++ .g_volatile_ctrl = ov4689_g_volatile_ctrl, ++ .s_ctrl = ov4689_s_ctrl, ++}; ++ ++static int ov4689_init_controls(struct ov4689_dev *sensor) ++{ ++ const struct v4l2_ctrl_ops *ops = &ov4689_ctrl_ops; ++ struct ov4689_ctrls *ctrls = &sensor->ctrls; ++ struct v4l2_ctrl_handler *hdl = &ctrls->handler; ++ int ret; ++ ++ v4l2_ctrl_handler_init(hdl, 32); ++ ++ /* we can use our own mutex for the ctrl lock */ ++ hdl->lock = &sensor->lock; ++ ++ /* Clock related controls */ ++ ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, ++ 0, INT_MAX, 1, ++ ov4689_calc_pixel_rate(sensor)); ++ ++ /* Auto/manual white balance */ ++ ctrls->auto_wb = v4l2_ctrl_new_std(hdl, ops, ++ V4L2_CID_AUTO_WHITE_BALANCE, ++ 0, 1, 1, 0); ++ ctrls->blue_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BLUE_BALANCE, ++ 0, 4095, 1, 1024); ++ ctrls->red_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE, ++ 0, 4095, 1, 1024); ++ ++ ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, ++ 4, 0xfff8, 1, 0x4c00); ++ ctrls->anal_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, ++ 0x10, 0xfff8, 1, 0x0080); ++ ctrls->test_pattern = ++ v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(test_pattern_menu) - 1, ++ 0, 0, test_pattern_menu); ++ ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, ++ 0, 1, 1, 0); ++ ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, ++ 0, 1, 1, 0); ++ ctrls->light_freq = ++ v4l2_ctrl_new_std_menu(hdl, ops, ++ V4L2_CID_POWER_LINE_FREQUENCY, ++ V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, ++ V4L2_CID_POWER_LINE_FREQUENCY_50HZ); ++ ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, ++ 0, 0, link_freq_menu_items); ++ if (hdl->error) { ++ ret = hdl->error; ++ goto free_ctrls; ++ } ++ ++ ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ // ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; ++ // ctrls->anal_gain->flags |= V4L2_CTRL_FLAG_VOLATILE; ++ ++ v4l2_ctrl_auto_cluster(3, &ctrls->auto_wb, 0, false); ++ ++ sensor->sd.ctrl_handler = hdl; ++ return 0; ++ ++free_ctrls: ++ v4l2_ctrl_handler_free(hdl); ++ return ret; ++} ++ ++static int ov4689_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ if (fse->pad != 0) ++ return -EINVAL; ++ if (fse->index >= OV4689_NUM_MODES) ++ return -EINVAL; ++ ++ fse->min_width = ++ ov4689_mode_data[fse->index].hact; ++ fse->max_width = fse->min_width; ++ fse->min_height = ++ ov4689_mode_data[fse->index].vact; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static int ov4689_enum_frame_interval( ++ struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_interval_enum *fie) ++{ ++ //struct ov4689_dev *sensor = to_ov4689_dev(sd); ++ struct v4l2_fract tpf; ++ int i = 0; ++ ++ if (fie->pad != 0) ++ return -EINVAL; ++ if (fie->index >= OV4689_NUM_FRAMERATES) ++ return -EINVAL; ++ ++ tpf.numerator = 1; ++ tpf.denominator = ov4689_framerates[fie->index]; ++ ++ // ret = ov4689_try_frame_interval(sensor, &tpf, ++ // fie->width, fie->height); ++ // if (ret < 0) ++ // return -EINVAL; ++ ++ pr_debug("fie->width = %d, fie->height = %d\n", fie->width, fie->height); ++ for (i = 0; i < OV4689_NUM_MODES; i++) { ++ if (fie->width == ov4689_mode_data[i].hact && ++ fie->height == ov4689_mode_data[i].vact) ++ break; ++ } ++ if (i == OV4689_NUM_MODES) ++ return -ENOTTY; ++ ++ fie->interval = tpf; ++ fie->width = ov4689_mode_data[i].hact; ++ fie->height = ov4689_mode_data[i].vact; ++ ++ return 0; ++} ++ ++static int ov4689_g_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_frame_interval *fi) ++{ ++ struct ov4689_dev *sensor = to_ov4689_dev(sd); ++ ++ mutex_lock(&sensor->lock); ++ fi->interval = sensor->frame_interval; ++ mutex_unlock(&sensor->lock); ++ ++ return 0; ++} ++ ++static int ov4689_s_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_frame_interval *fi) ++{ ++ struct ov4689_dev *sensor = to_ov4689_dev(sd); ++ const struct ov4689_mode_info *mode; ++ int frame_rate, ret = 0; ++ ++ if (fi->pad != 0) ++ return -EINVAL; ++ ++ mutex_lock(&sensor->lock); ++ ++ if (sensor->streaming) { ++ ret = -EBUSY; ++ goto out; ++ } ++ ++ mode = sensor->current_mode; ++ ++ frame_rate = ov4689_try_frame_interval(sensor, &fi->interval, ++ mode->hact, mode->vact); ++ if (frame_rate < 0) { ++ /* Always return a valid frame interval value */ ++ fi->interval = sensor->frame_interval; ++ goto out; ++ } ++ ++ mode = ov4689_find_mode(sensor, frame_rate, mode->hact, ++ mode->vact, true); ++ if (!mode) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ if (mode != sensor->current_mode || ++ frame_rate != sensor->current_fr) { ++ 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, ++ ov4689_calc_pixel_rate(sensor)); ++ } ++out: ++ mutex_unlock(&sensor->lock); ++ return ret; ++} ++ ++static int ov4689_stream_start(struct ov4689_dev *sensor, int enable) ++{ ++ return ov4689_write_reg(sensor, OV4689_REG_STREAM_ON, enable); ++} ++ ++static int ov4689_s_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct ov4689_dev *sensor = to_ov4689_dev(sd); ++ int ret = 0; ++ ++ if (enable) { ++ pm_runtime_get_sync(&sensor->i2c_client->dev); ++ ++ ret = v4l2_ctrl_handler_setup(&sensor->ctrls.handler); ++ if (ret) { ++ pm_runtime_put_sync(&sensor->i2c_client->dev); ++ return ret; ++ } ++ } ++ ++ mutex_lock(&sensor->lock); ++ ++ if (sensor->streaming == !enable) { ++ if (enable) { ++ ret = ov4689_restore_mode(sensor); ++ if (ret) ++ goto out; ++ } ++ ++ if (enable && sensor->pending_mode_change) { ++ ret = ov4689_set_mode(sensor); ++ if (ret) ++ goto out; ++ } ++ ++ if (sensor->ep.bus.mipi_csi2.num_data_lanes == 2) { ++ ov4689_write_reg(sensor, OV4689_REG_MIPI_SC_CTRL_HI, 0x32); ++ ov4689_write_reg(sensor, OV4689_REG_MIPI_SC_CTRL_LOW, 0x0c); ++ } else if (sensor->ep.bus.mipi_csi2.num_data_lanes == 4) { ++ ov4689_write_reg(sensor, OV4689_REG_MIPI_SC_CTRL_HI, 0x72); ++ ov4689_write_reg(sensor, OV4689_REG_MIPI_SC_CTRL_LOW, 0x00); ++ } else { ++ dev_err(&sensor->i2c_client->dev, "Unsupport lane num\n"); ++ } ++ ++ ret = ov4689_stream_start(sensor, enable); ++ if (ret) ++ goto out; ++ } ++ sensor->streaming += enable ? 1 : -1; ++ WARN_ON(sensor->streaming < 0); ++out: ++ mutex_unlock(&sensor->lock); ++ ++ if (!enable || ret) ++ pm_runtime_put_sync(&sensor->i2c_client->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_subdev_core_ops ov4689_core_ops = { ++ .log_status = v4l2_ctrl_subdev_log_status, ++ .subscribe_event = v4l2_ctrl_subdev_subscribe_event, ++ .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++}; ++ ++static const struct v4l2_subdev_video_ops ov4689_video_ops = { ++ .g_frame_interval = ov4689_g_frame_interval, ++ .s_frame_interval = ov4689_s_frame_interval, ++ .s_stream = ov4689_s_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops ov4689_pad_ops = { ++ .enum_mbus_code = ov4689_enum_mbus_code, ++ .get_fmt = ov4689_get_fmt, ++ .set_fmt = ov4689_set_fmt, ++ .enum_frame_size = ov4689_enum_frame_size, ++ .enum_frame_interval = ov4689_enum_frame_interval, ++}; ++ ++static const struct v4l2_subdev_ops ov4689_subdev_ops = { ++ .core = &ov4689_core_ops, ++ .video = &ov4689_video_ops, ++ .pad = &ov4689_pad_ops, ++}; ++ ++static int ov4689_get_regulators(struct ov4689_dev *sensor) ++{ ++ int i; ++ ++ for (i = 0; i < OV4689_NUM_SUPPLIES; i++) ++ sensor->supplies[i].supply = ov4689_supply_name[i]; ++ ++ return devm_regulator_bulk_get(&sensor->i2c_client->dev, ++ OV4689_NUM_SUPPLIES, ++ sensor->supplies); ++} ++ ++static int ov4689_check_chip_id(struct ov4689_dev *sensor) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ int ret = 0; ++ u16 chip_id; ++ ++ ret = ov4689_read_reg16(sensor, OV4689_REG_CHIP_ID, &chip_id); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to read chip identifier\n", ++ __func__); ++ return ret; ++ } ++ ++ if (chip_id != OV4689_CHIP_ID) { ++ dev_err(&client->dev, "%s: wrong chip identifier, expected 0x%x, got 0x%x\n", ++ __func__, OV4689_CHIP_ID, chip_id); ++ return -ENXIO; ++ } ++ dev_err(&client->dev, "%s: chip identifier, got 0x%x\n", ++ __func__, chip_id); ++ ++ return 0; ++} ++ ++static int ov4689_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct fwnode_handle *endpoint; ++ struct ov4689_dev *sensor; ++ struct v4l2_mbus_framefmt *fmt; ++ u32 rotation; ++ int ret; ++ ++ sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); ++ if (!sensor) ++ return -ENOMEM; ++ ++ sensor->i2c_client = client; ++ ++ fmt = &sensor->fmt; ++ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; ++ fmt->colorspace = V4L2_COLORSPACE_SRGB; ++ 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 = 1920; ++ fmt->height = 1080; ++ fmt->field = V4L2_FIELD_NONE; ++ sensor->frame_interval.numerator = 1; ++ sensor->frame_interval.denominator = ov4689_framerates[OV4689_30_FPS]; ++ sensor->current_fr = OV4689_30_FPS; ++ sensor->current_mode = ++ &ov4689_mode_data[OV4689_MODE_1080P_1920_1080]; ++ sensor->last_mode = sensor->current_mode; ++ ++ ++ /* optional indication of physical rotation of sensor */ ++ ret = fwnode_property_read_u32(dev_fwnode(&client->dev), "rotation", ++ &rotation); ++ if (!ret) { ++ switch (rotation) { ++ case 180: ++ sensor->upside_down = true; ++ fallthrough; ++ case 0: ++ break; ++ default: ++ dev_warn(dev, "%u degrees rotation is not supported, ignoring...\n", ++ rotation); ++ } ++ } ++ ++ endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), ++ NULL); ++ if (!endpoint) { ++ dev_err(dev, "endpoint node not found\n"); ++ return -EINVAL; ++ } ++ ++ ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep); ++ fwnode_handle_put(endpoint); ++ if (ret) { ++ dev_err(dev, "Could not parse endpoint\n"); ++ return ret; ++ } ++ ++ if (sensor->ep.bus_type != V4L2_MBUS_CSI2_DPHY) { ++ dev_err(dev, "Unsupported bus type %d\n", sensor->ep.bus_type); ++ return -EINVAL; ++ } ++ ++ /* get system clock (xclk) */ ++ sensor->xclk = devm_clk_get(dev, "xclk"); ++ if (IS_ERR(sensor->xclk)) { ++ dev_err(dev, "failed to get xclk\n"); ++ return PTR_ERR(sensor->xclk); ++ } ++ ++ sensor->xclk_freq = clk_get_rate(sensor->xclk); ++ if (sensor->xclk_freq < OV4689_XCLK_MIN || ++ sensor->xclk_freq > OV4689_XCLK_MAX) { ++ dev_err(dev, "xclk frequency out of range: %d Hz\n", ++ sensor->xclk_freq); ++ return -EINVAL; ++ } ++ ++ /* request optional power down pin */ ++ sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(sensor->pwdn_gpio)) ++ return PTR_ERR(sensor->pwdn_gpio); ++ ++ /* request optional reset pin */ ++ sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(sensor->reset_gpio)) ++ return PTR_ERR(sensor->reset_gpio); ++ ++ v4l2_i2c_subdev_init(&sensor->sd, client, &ov4689_subdev_ops); ++ ++ sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | ++ V4L2_SUBDEV_FL_HAS_EVENTS; ++ sensor->pad.flags = MEDIA_PAD_FL_SOURCE; ++ sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); ++ if (ret) ++ return ret; ++ ++ ret = ov4689_get_regulators(sensor); ++ if (ret) ++ return ret; ++ ++ mutex_init(&sensor->lock); ++ ++ ret = ov4689_set_power_on(dev); ++ if (ret) { ++ dev_err(dev, "failed to power on\n"); ++ goto entity_cleanup; ++ } ++ ++ ret = ov4689_check_chip_id(sensor); ++ if (ret) ++ goto error_power_off; ++ ++ ret = ov4689_init_controls(sensor); ++ if (ret) ++ goto error_power_off; ++ ++ ret = v4l2_async_register_subdev_sensor(&sensor->sd); ++ if (ret) ++ goto free_ctrls; ++ ++ pm_runtime_set_active(dev); ++ pm_runtime_enable(dev); ++ ++ return 0; ++ ++free_ctrls: ++ v4l2_ctrl_handler_free(&sensor->ctrls.handler); ++error_power_off: ++ ov4689_set_power_off(dev); ++entity_cleanup: ++ media_entity_cleanup(&sensor->sd.entity); ++ mutex_destroy(&sensor->lock); ++ return ret; ++} ++ ++static void ov4689_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov4689_dev *sensor = to_ov4689_dev(sd); ++ ++ v4l2_async_unregister_subdev(&sensor->sd); ++ media_entity_cleanup(&sensor->sd.entity); ++ v4l2_ctrl_handler_free(&sensor->ctrls.handler); ++ mutex_destroy(&sensor->lock); ++ ++ pm_runtime_disable(&client->dev); ++ if (!pm_runtime_status_suspended(&client->dev)) ++ ov4689_set_power_off(&client->dev); ++ pm_runtime_set_suspended(&client->dev); ++} ++ ++static const struct i2c_device_id ov4689_id[] = { ++ { "ov4689", 0 }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(i2c, ov4689_id); ++ ++static const struct of_device_id ov4689_dt_ids[] = { ++ { .compatible = "ovti,ov4689" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, ov4689_dt_ids); ++ ++static const struct dev_pm_ops ov4689_pm_ops = { ++ SET_RUNTIME_PM_OPS(ov4689_set_power_off, ov4689_set_power_on, NULL) ++}; ++ ++static struct i2c_driver ov4689_i2c_driver = { ++ .driver = { ++ .name = "ov4689", ++ .of_match_table = ov4689_dt_ids, ++ .pm = &ov4689_pm_ops, ++ }, ++ .id_table = ov4689_id, ++ .probe = ov4689_probe, ++ .remove = ov4689_remove, ++}; ++ ++module_i2c_driver(ov4689_i2c_driver); ++ ++MODULE_DESCRIPTION("OV4689 MIPI Camera Subdev Driver"); ++MODULE_LICENSE("GPL"); +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/ov5640.c +@@ -0,0 +1,3227 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved. ++ * Copyright (C) 2014-2017 Mentor Graphics Inc. ++ * ++ */ ++ ++#include <linux/clk.h> ++#include <linux/clk-provider.h> ++#include <linux/clkdev.h> ++#include <linux/ctype.h> ++#include <linux/delay.h> ++#include <linux/device.h> ++#include <linux/gpio/consumer.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/of_device.h> ++#include <linux/regulator/consumer.h> ++#include <linux/slab.h> ++#include <linux/types.h> ++#include <media/v4l2-async.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-event.h> ++#include <media/v4l2-fwnode.h> ++#include <media/v4l2-subdev.h> ++#include "stfcamss.h" ++ ++/* min/typical/max system clock (xclk) frequencies */ ++#define OV5640_XCLK_MIN 6000000 ++#define OV5640_XCLK_MAX 54000000 ++ ++#define OV5640_SKIP_FRAMES 4 ++ ++#define OV5640_CHIP_ID 0x5640 ++#define OV5640_DEFAULT_SLAVE_ID 0x3c ++ ++#define OV5640_REG_SYS_RESET02 0x3002 ++#define OV5640_REG_SYS_CLOCK_ENABLE02 0x3006 ++#define OV5640_REG_SYS_CTRL0 0x3008 ++#define OV5640_REG_SYS_CTRL0_SW_PWDN 0x42 ++#define OV5640_REG_SYS_CTRL0_SW_PWUP 0x02 ++#define OV5640_REG_CHIP_ID 0x300a ++#define OV5640_REG_IO_MIPI_CTRL00 0x300e ++#define OV5640_REG_PAD_OUTPUT_ENABLE01 0x3017 ++#define OV5640_REG_PAD_OUTPUT_ENABLE02 0x3018 ++#define OV5640_REG_PAD_OUTPUT00 0x3019 ++#define OV5640_REG_SYSTEM_CONTROL1 0x302e ++#define OV5640_REG_SC_PLL_CTRL0 0x3034 ++#define OV5640_REG_SC_PLL_CTRL1 0x3035 ++#define OV5640_REG_SC_PLL_CTRL2 0x3036 ++#define OV5640_REG_SC_PLL_CTRL3 0x3037 ++#define OV5640_REG_SLAVE_ID 0x3100 ++#define OV5640_REG_SCCB_SYS_CTRL1 0x3103 ++#define OV5640_REG_SYS_ROOT_DIVIDER 0x3108 ++#define OV5640_REG_AWB_R_GAIN 0x3400 ++#define OV5640_REG_AWB_G_GAIN 0x3402 ++#define OV5640_REG_AWB_B_GAIN 0x3404 ++#define OV5640_REG_AWB_MANUAL_CTRL 0x3406 ++#define OV5640_REG_AEC_PK_EXPOSURE_HI 0x3500 ++#define OV5640_REG_AEC_PK_EXPOSURE_MED 0x3501 ++#define OV5640_REG_AEC_PK_EXPOSURE_LO 0x3502 ++#define OV5640_REG_AEC_PK_MANUAL 0x3503 ++#define OV5640_REG_AEC_PK_REAL_GAIN 0x350a ++#define OV5640_REG_AEC_PK_VTS 0x350c ++#define OV5640_REG_TIMING_DVPHO 0x3808 ++#define OV5640_REG_TIMING_DVPVO 0x380a ++#define OV5640_REG_TIMING_HTS 0x380c ++#define OV5640_REG_TIMING_VTS 0x380e ++#define OV5640_REG_TIMING_TC_REG20 0x3820 ++#define OV5640_REG_TIMING_TC_REG21 0x3821 ++#define OV5640_REG_AEC_CTRL00 0x3a00 ++#define OV5640_REG_AEC_B50_STEP 0x3a08 ++#define OV5640_REG_AEC_B60_STEP 0x3a0a ++#define OV5640_REG_AEC_CTRL0D 0x3a0d ++#define OV5640_REG_AEC_CTRL0E 0x3a0e ++#define OV5640_REG_AEC_CTRL0F 0x3a0f ++#define OV5640_REG_AEC_CTRL10 0x3a10 ++#define OV5640_REG_AEC_CTRL11 0x3a11 ++#define OV5640_REG_AEC_CTRL1B 0x3a1b ++#define OV5640_REG_AEC_CTRL1E 0x3a1e ++#define OV5640_REG_AEC_CTRL1F 0x3a1f ++#define OV5640_REG_HZ5060_CTRL00 0x3c00 ++#define OV5640_REG_HZ5060_CTRL01 0x3c01 ++#define OV5640_REG_SIGMADELTA_CTRL0C 0x3c0c ++#define OV5640_REG_FRAME_CTRL01 0x4202 ++#define OV5640_REG_FORMAT_CONTROL00 0x4300 ++#define OV5640_REG_VFIFO_HSIZE 0x4602 ++#define OV5640_REG_VFIFO_VSIZE 0x4604 ++#define OV5640_REG_JPG_MODE_SELECT 0x4713 ++#define OV5640_REG_CCIR656_CTRL00 0x4730 ++#define OV5640_REG_POLARITY_CTRL00 0x4740 ++#define OV5640_REG_MIPI_CTRL00 0x4800 ++#define OV5640_REG_DEBUG_MODE 0x4814 ++#define OV5640_REG_ISP_FORMAT_MUX_CTRL 0x501f ++#define OV5640_REG_PRE_ISP_TEST_SET1 0x503d ++#define OV5640_REG_SDE_CTRL0 0x5580 ++#define OV5640_REG_SDE_CTRL1 0x5581 ++#define OV5640_REG_SDE_CTRL3 0x5583 ++#define OV5640_REG_SDE_CTRL4 0x5584 ++#define OV5640_REG_SDE_CTRL5 0x5585 ++#define OV5640_REG_AVG_READOUT 0x56a1 ++ ++enum ov5640_mode_id { ++ OV5640_MODE_QCIF_176_144 = 0, ++ OV5640_MODE_QVGA_320_240, ++ OV5640_MODE_VGA_640_480, ++ OV5640_MODE_NTSC_720_480, ++ OV5640_MODE_PAL_720_576, ++ OV5640_MODE_XGA_1024_768, ++ OV5640_MODE_720P_1280_720, ++ OV5640_MODE_1080P_1920_1080, ++ OV5640_MODE_QSXGA_2592_1944, ++ OV5640_NUM_MODES, ++}; ++ ++enum ov5640_frame_rate { ++ OV5640_15_FPS = 0, ++ OV5640_30_FPS, ++ OV5640_60_FPS, ++ OV5640_NUM_FRAMERATES, ++}; ++ ++enum ov5640_format_mux { ++ OV5640_FMT_MUX_YUV422 = 0, ++ OV5640_FMT_MUX_RGB, ++ OV5640_FMT_MUX_DITHER, ++ OV5640_FMT_MUX_RAW_DPC, ++ OV5640_FMT_MUX_SNR_RAW, ++ OV5640_FMT_MUX_RAW_CIP, ++}; ++ ++struct ov5640_pixfmt { ++ u32 code; ++ u32 colorspace; ++}; ++ ++static const struct ov5640_pixfmt ov5640_formats[] = { ++ { MEDIA_BUS_FMT_JPEG_1X8, V4L2_COLORSPACE_JPEG, }, ++ { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_COLORSPACE_SRGB, }, ++ { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_SRGB, }, ++ { MEDIA_BUS_FMT_RGB565_2X8_LE, V4L2_COLORSPACE_SRGB, }, ++ { MEDIA_BUS_FMT_RGB565_2X8_BE, V4L2_COLORSPACE_SRGB, }, ++ { MEDIA_BUS_FMT_SBGGR8_1X8, V4L2_COLORSPACE_SRGB, }, ++ { MEDIA_BUS_FMT_SGBRG8_1X8, V4L2_COLORSPACE_SRGB, }, ++ { MEDIA_BUS_FMT_SGRBG8_1X8, V4L2_COLORSPACE_SRGB, }, ++ { MEDIA_BUS_FMT_SRGGB8_1X8, V4L2_COLORSPACE_SRGB, }, ++}; ++ ++/* ++ * FIXME: remove this when a subdev API becomes available ++ * to set the MIPI CSI-2 virtual channel. ++ */ ++static unsigned int virtual_channel; ++module_param(virtual_channel, uint, 0444); ++MODULE_PARM_DESC(virtual_channel, ++ "MIPI CSI-2 virtual channel (0..3), default 0"); ++ ++static const int ov5640_framerates[] = { ++ [OV5640_15_FPS] = 15, ++ [OV5640_30_FPS] = 30, ++ [OV5640_60_FPS] = 60, ++}; ++ ++/* regulator supplies */ ++static const char * const ov5640_supply_name[] = { ++ "DOVDD", /* Digital I/O (1.8V) supply */ ++ "AVDD", /* Analog (2.8V) supply */ ++ "DVDD", /* Digital Core (1.5V) supply */ ++}; ++ ++#define OV5640_NUM_SUPPLIES ARRAY_SIZE(ov5640_supply_name) ++ ++/* ++ * Image size under 1280 * 960 are SUBSAMPLING ++ * Image size upper 1280 * 960 are SCALING ++ */ ++enum ov5640_downsize_mode { ++ SUBSAMPLING, ++ SCALING, ++}; ++ ++struct reg_value { ++ u16 reg_addr; ++ u8 val; ++ u8 mask; ++ u32 delay_ms; ++}; ++ ++struct ov5640_mode_info { ++ enum ov5640_mode_id id; ++ enum ov5640_downsize_mode dn_mode; ++ u32 hact; ++ u32 htot; ++ u32 vact; ++ u32 vtot; ++ const struct reg_value *reg_data; ++ u32 reg_data_size; ++ u32 max_fps; ++}; ++ ++struct ov5640_ctrls { ++ struct v4l2_ctrl_handler handler; ++ struct v4l2_ctrl *pixel_rate; ++ struct { ++ struct v4l2_ctrl *auto_exp; ++ struct v4l2_ctrl *exposure; ++ }; ++ struct { ++ struct v4l2_ctrl *auto_wb; ++ struct v4l2_ctrl *blue_balance; ++ struct v4l2_ctrl *red_balance; ++ }; ++ struct { ++ struct v4l2_ctrl *auto_gain; ++ struct v4l2_ctrl *gain; ++ }; ++ struct v4l2_ctrl *brightness; ++ struct v4l2_ctrl *light_freq; ++ struct v4l2_ctrl *saturation; ++ struct v4l2_ctrl *contrast; ++ struct v4l2_ctrl *hue; ++ struct v4l2_ctrl *test_pattern; ++ struct v4l2_ctrl *hflip; ++ struct v4l2_ctrl *vflip; ++}; ++ ++struct ov5640_dev { ++ struct i2c_client *i2c_client; ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ struct v4l2_fwnode_endpoint ep; /* the parsed DT endpoint info */ ++ struct clk *xclk; /* system clock to OV5640 */ ++ u32 xclk_freq; ++ ++ struct regulator_bulk_data supplies[OV5640_NUM_SUPPLIES]; ++ struct gpio_desc *reset_gpio; ++ struct gpio_desc *pwdn_gpio; ++ bool upside_down; ++ ++ /* lock to protect all members below */ ++ struct mutex lock; ++ ++ int power_count; ++ ++ struct v4l2_mbus_framefmt fmt; ++ bool pending_fmt_change; ++ ++ const struct ov5640_mode_info *current_mode; ++ const struct ov5640_mode_info *last_mode; ++ enum ov5640_frame_rate current_fr; ++ struct v4l2_fract frame_interval; ++ ++ struct ov5640_ctrls ctrls; ++ ++ u32 prev_sysclk, prev_hts; ++ u32 ae_low, ae_high, ae_target; ++ ++ bool pending_mode_change; ++ int streaming; ++}; ++ ++static inline struct ov5640_dev *to_ov5640_dev(struct v4l2_subdev *sd) ++{ ++ return container_of(sd, struct ov5640_dev, sd); ++} ++ ++static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) ++{ ++ return &container_of(ctrl->handler, struct ov5640_dev, ++ ctrls.handler)->sd; ++} ++ ++/* ++ * FIXME: all of these register tables are likely filled with ++ * entries that set the register to their power-on default values, ++ * and which are otherwise not touched by this driver. Those entries ++ * should be identified and removed to speed register load time ++ * over i2c. ++ */ ++/* YUV422 UYVY VGA@30fps */ ++static const struct reg_value ov5640_init_setting_30fps_VGA[] = { ++ {0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 5}, {0x3008, 0x42, 0, 0}, ++ {0x3103, 0x03, 0, 0}, {0x3630, 0x36, 0, 0}, ++ {0x3631, 0x0e, 0, 0}, {0x3632, 0xe2, 0, 0}, {0x3633, 0x12, 0, 0}, ++ {0x3621, 0xe0, 0, 0}, {0x3704, 0xa0, 0, 0}, {0x3703, 0x5a, 0, 0}, ++ {0x3715, 0x78, 0, 0}, {0x3717, 0x01, 0, 0}, {0x370b, 0x60, 0, 0}, ++ {0x3705, 0x1a, 0, 0}, {0x3905, 0x02, 0, 0}, {0x3906, 0x10, 0, 0}, ++ {0x3901, 0x0a, 0, 0}, {0x3731, 0x12, 0, 0}, {0x3600, 0x08, 0, 0}, ++ {0x3601, 0x33, 0, 0}, {0x302d, 0x60, 0, 0}, {0x3620, 0x52, 0, 0}, ++ {0x371b, 0x20, 0, 0}, {0x471c, 0x50, 0, 0}, {0x3a13, 0x43, 0, 0}, ++ {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, {0x3635, 0x13, 0, 0}, ++ {0x3636, 0x03, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3622, 0x01, 0, 0}, ++ {0x3c01, 0xa4, 0, 0}, {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, ++ {0x3c06, 0x00, 0, 0}, {0x3c07, 0x08, 0, 0}, {0x3c08, 0x00, 0, 0}, ++ {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, ++ {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, ++ {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, ++ {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, ++ {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, ++ {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, ++ {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, ++ {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, ++ {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, ++ {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, ++ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x3000, 0x00, 0, 0}, ++ {0x3002, 0x1c, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3006, 0xc3, 0, 0}, ++ {0x302e, 0x08, 0, 0}, {0x4300, 0x3f, 0, 0}, ++ {0x501f, 0x00, 0, 0}, {0x4407, 0x04, 0, 0}, ++ {0x440e, 0x00, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, ++ {0x4837, 0x0a, 0, 0}, {0x3824, 0x02, 0, 0}, ++ {0x5000, 0xa7, 0, 0}, {0x5001, 0xa3, 0, 0}, {0x5180, 0xff, 0, 0}, ++ {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0}, {0x5183, 0x14, 0, 0}, ++ {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0}, {0x5186, 0x09, 0, 0}, ++ {0x5187, 0x09, 0, 0}, {0x5188, 0x09, 0, 0}, {0x5189, 0x88, 0, 0}, ++ {0x518a, 0x54, 0, 0}, {0x518b, 0xee, 0, 0}, {0x518c, 0xb2, 0, 0}, ++ {0x518d, 0x50, 0, 0}, {0x518e, 0x34, 0, 0}, {0x518f, 0x6b, 0, 0}, ++ {0x5190, 0x46, 0, 0}, {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, ++ {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, ++ {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, ++ {0x5199, 0x6c, 0, 0}, {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, ++ {0x519c, 0x09, 0, 0}, {0x519d, 0x2b, 0, 0}, {0x519e, 0x38, 0, 0}, ++ {0x5381, 0x1e, 0, 0}, {0x5382, 0x5b, 0, 0}, {0x5383, 0x08, 0, 0}, ++ {0x5384, 0x0a, 0, 0}, {0x5385, 0x7e, 0, 0}, {0x5386, 0x88, 0, 0}, ++ {0x5387, 0x7c, 0, 0}, {0x5388, 0x6c, 0, 0}, {0x5389, 0x10, 0, 0}, ++ {0x538a, 0x01, 0, 0}, {0x538b, 0x98, 0, 0}, {0x5300, 0x08, 0, 0}, ++ {0x5301, 0x30, 0, 0}, {0x5302, 0x10, 0, 0}, {0x5303, 0x00, 0, 0}, ++ {0x5304, 0x08, 0, 0}, {0x5305, 0x30, 0, 0}, {0x5306, 0x08, 0, 0}, ++ {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0}, {0x530a, 0x30, 0, 0}, ++ {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0}, {0x5480, 0x01, 0, 0}, ++ {0x5481, 0x08, 0, 0}, {0x5482, 0x14, 0, 0}, {0x5483, 0x28, 0, 0}, ++ {0x5484, 0x51, 0, 0}, {0x5485, 0x65, 0, 0}, {0x5486, 0x71, 0, 0}, ++ {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0}, {0x5489, 0x91, 0, 0}, ++ {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0}, {0x548c, 0xb8, 0, 0}, ++ {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0}, {0x548f, 0xea, 0, 0}, ++ {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5583, 0x40, 0, 0}, ++ {0x5584, 0x10, 0, 0}, {0x5589, 0x10, 0, 0}, {0x558a, 0x00, 0, 0}, ++ {0x558b, 0xf8, 0, 0}, {0x5800, 0x23, 0, 0}, {0x5801, 0x14, 0, 0}, ++ {0x5802, 0x0f, 0, 0}, {0x5803, 0x0f, 0, 0}, {0x5804, 0x12, 0, 0}, ++ {0x5805, 0x26, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0}, ++ {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x08, 0, 0}, ++ {0x580b, 0x0d, 0, 0}, {0x580c, 0x08, 0, 0}, {0x580d, 0x03, 0, 0}, ++ {0x580e, 0x00, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x03, 0, 0}, ++ {0x5811, 0x09, 0, 0}, {0x5812, 0x07, 0, 0}, {0x5813, 0x03, 0, 0}, ++ {0x5814, 0x00, 0, 0}, {0x5815, 0x01, 0, 0}, {0x5816, 0x03, 0, 0}, ++ {0x5817, 0x08, 0, 0}, {0x5818, 0x0d, 0, 0}, {0x5819, 0x08, 0, 0}, ++ {0x581a, 0x05, 0, 0}, {0x581b, 0x06, 0, 0}, {0x581c, 0x08, 0, 0}, ++ {0x581d, 0x0e, 0, 0}, {0x581e, 0x29, 0, 0}, {0x581f, 0x17, 0, 0}, ++ {0x5820, 0x11, 0, 0}, {0x5821, 0x11, 0, 0}, {0x5822, 0x15, 0, 0}, ++ {0x5823, 0x28, 0, 0}, {0x5824, 0x46, 0, 0}, {0x5825, 0x26, 0, 0}, ++ {0x5826, 0x08, 0, 0}, {0x5827, 0x26, 0, 0}, {0x5828, 0x64, 0, 0}, ++ {0x5829, 0x26, 0, 0}, {0x582a, 0x24, 0, 0}, {0x582b, 0x22, 0, 0}, ++ {0x582c, 0x24, 0, 0}, {0x582d, 0x24, 0, 0}, {0x582e, 0x06, 0, 0}, ++ {0x582f, 0x22, 0, 0}, {0x5830, 0x40, 0, 0}, {0x5831, 0x42, 0, 0}, ++ {0x5832, 0x24, 0, 0}, {0x5833, 0x26, 0, 0}, {0x5834, 0x24, 0, 0}, ++ {0x5835, 0x22, 0, 0}, {0x5836, 0x22, 0, 0}, {0x5837, 0x26, 0, 0}, ++ {0x5838, 0x44, 0, 0}, {0x5839, 0x24, 0, 0}, {0x583a, 0x26, 0, 0}, ++ {0x583b, 0x28, 0, 0}, {0x583c, 0x42, 0, 0}, {0x583d, 0xce, 0, 0}, ++ {0x5025, 0x00, 0, 0}, {0x3a0f, 0x30, 0, 0}, {0x3a10, 0x28, 0, 0}, ++ {0x3a1b, 0x30, 0, 0}, {0x3a1e, 0x26, 0, 0}, {0x3a11, 0x60, 0, 0}, ++ {0x3a1f, 0x14, 0, 0}, {0x3008, 0x02, 0, 0}, {0x3c00, 0x04, 0, 300}, ++}; ++ ++static const struct reg_value ov5640_setting_VGA_640_480[] = { ++ {0x3c07, 0x08, 0, 0}, ++ {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, ++ {0x3814, 0x31, 0, 0}, ++ {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, ++ {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, ++ {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, ++ {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, ++ {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, ++ {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, ++ {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, ++ {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, ++ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, ++ {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, ++ {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, ++}; ++ ++static const struct reg_value ov5640_setting_XGA_1024_768[] = { ++ {0x3c07, 0x08, 0, 0}, ++ {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, ++ {0x3814, 0x31, 0, 0}, ++ {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, ++ {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, ++ {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, ++ {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, ++ {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, ++ {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, ++ {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, ++ {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, ++ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, ++ {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, ++ {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, ++}; ++ ++static const struct reg_value ov5640_setting_QVGA_320_240[] = { ++ {0x3c07, 0x08, 0, 0}, ++ {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, ++ {0x3814, 0x31, 0, 0}, ++ {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, ++ {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, ++ {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, ++ {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, ++ {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, ++ {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, ++ {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, ++ {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, ++ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, ++ {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, ++ {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, ++}; ++ ++static const struct reg_value ov5640_setting_QCIF_176_144[] = { ++ {0x3c07, 0x08, 0, 0}, ++ {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, ++ {0x3814, 0x31, 0, 0}, ++ {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, ++ {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, ++ {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, ++ {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, ++ {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, ++ {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, ++ {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, ++ {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, ++ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, ++ {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, ++ {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, ++}; ++ ++static const struct reg_value ov5640_setting_NTSC_720_480[] = { ++ {0x3c07, 0x08, 0, 0}, ++ {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, ++ {0x3814, 0x31, 0, 0}, ++ {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, ++ {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, ++ {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x3c, 0, 0}, ++ {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, ++ {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, ++ {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, ++ {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, ++ {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, ++ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, ++ {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, ++ {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, ++}; ++ ++static const struct reg_value ov5640_setting_PAL_720_576[] = { ++ {0x3c07, 0x08, 0, 0}, ++ {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, ++ {0x3814, 0x31, 0, 0}, ++ {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, ++ {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, ++ {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x38, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, ++ {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, ++ {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, ++ {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, ++ {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, ++ {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, ++ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, ++ {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, ++ {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, ++}; ++ ++static const struct reg_value ov5640_setting_720P_1280_720[] = { ++ {0x3c07, 0x07, 0, 0}, ++ {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, ++ {0x3814, 0x31, 0, 0}, ++ {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, ++ {0x3802, 0x00, 0, 0}, {0x3803, 0xfa, 0, 0}, {0x3804, 0x0a, 0, 0}, ++ {0x3805, 0x3f, 0, 0}, {0x3806, 0x06, 0, 0}, {0x3807, 0xa9, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, ++ {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, ++ {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x02, 0, 0}, ++ {0x3a03, 0xe4, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0xbc, 0, 0}, ++ {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x72, 0, 0}, {0x3a0e, 0x01, 0, 0}, ++ {0x3a0d, 0x02, 0, 0}, {0x3a14, 0x02, 0, 0}, {0x3a15, 0xe4, 0, 0}, ++ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, ++ {0x4407, 0x04, 0, 0}, {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, ++ {0x3824, 0x04, 0, 0}, {0x5001, 0x83, 0, 0}, ++}; ++ ++static const struct reg_value ov5640_setting_1080P_1920_1080[] = { ++ {0x3c07, 0x08, 0, 0}, ++ {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, ++ {0x3814, 0x11, 0, 0}, ++ {0x3815, 0x11, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, ++ {0x3802, 0x00, 0, 0}, {0x3803, 0x00, 0, 0}, {0x3804, 0x0a, 0, 0}, ++ {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9f, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, ++ {0x3618, 0x04, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x21, 0, 0}, ++ {0x3709, 0x12, 0, 0}, {0x370c, 0x00, 0, 0}, {0x3a02, 0x03, 0, 0}, ++ {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, ++ {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, ++ {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, ++ {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0}, ++ {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, ++ {0x3824, 0x02, 0, 0}, {0x5001, 0x83, 0, 0}, ++ {0x3c07, 0x07, 0, 0}, {0x3c08, 0x00, 0, 0}, ++ {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, ++ {0x3800, 0x01, 0, 0}, {0x3801, 0x50, 0, 0}, {0x3802, 0x01, 0, 0}, ++ {0x3803, 0xb2, 0, 0}, {0x3804, 0x08, 0, 0}, {0x3805, 0xef, 0, 0}, ++ {0x3806, 0x05, 0, 0}, {0x3807, 0xf1, 0, 0}, ++ {0x3612, 0x2b, 0, 0}, {0x3708, 0x64, 0, 0}, ++ {0x3a02, 0x04, 0, 0}, {0x3a03, 0x60, 0, 0}, {0x3a08, 0x01, 0, 0}, ++ {0x3a09, 0x50, 0, 0}, {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x18, 0, 0}, ++ {0x3a0e, 0x03, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x04, 0, 0}, ++ {0x3a15, 0x60, 0, 0}, {0x4407, 0x04, 0, 0}, ++ {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, {0x3824, 0x04, 0, 0}, ++ {0x4005, 0x1a, 0, 0}, ++}; ++ ++static const struct reg_value ov5640_setting_QSXGA_2592_1944[] = { ++ {0x3c07, 0x08, 0, 0}, ++ {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, ++ {0x3814, 0x11, 0, 0}, ++ {0x3815, 0x11, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, ++ {0x3802, 0x00, 0, 0}, {0x3803, 0x00, 0, 0}, {0x3804, 0x0a, 0, 0}, ++ {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9f, 0, 0}, ++ {0x3810, 0x00, 0, 0}, ++ {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, ++ {0x3618, 0x04, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x21, 0, 0}, ++ {0x3709, 0x12, 0, 0}, {0x370c, 0x00, 0, 0}, {0x3a02, 0x03, 0, 0}, ++ {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, ++ {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, ++ {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, ++ {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0}, ++ {0x4407, 0x04, 0, 0}, {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, ++ {0x3824, 0x04, 0, 0}, {0x5001, 0x83, 0, 70}, ++}; ++ ++/* power-on sensor init reg table */ ++static const struct ov5640_mode_info ov5640_mode_init_data = { ++ 0, SUBSAMPLING, 640, 1896, 480, 984, ++ ov5640_init_setting_30fps_VGA, ++ ARRAY_SIZE(ov5640_init_setting_30fps_VGA), ++ OV5640_30_FPS, ++}; ++ ++static const struct ov5640_mode_info ++ov5640_mode_data[OV5640_NUM_MODES] = { ++ {OV5640_MODE_QCIF_176_144, SUBSAMPLING, ++ 176, 1896, 144, 984, ++ ov5640_setting_QCIF_176_144, ++ ARRAY_SIZE(ov5640_setting_QCIF_176_144), ++ OV5640_30_FPS}, ++ {OV5640_MODE_QVGA_320_240, SUBSAMPLING, ++ 320, 1896, 240, 984, ++ ov5640_setting_QVGA_320_240, ++ ARRAY_SIZE(ov5640_setting_QVGA_320_240), ++ OV5640_30_FPS}, ++ {OV5640_MODE_VGA_640_480, SUBSAMPLING, ++ 640, 1896, 480, 1080, ++ ov5640_setting_VGA_640_480, ++ ARRAY_SIZE(ov5640_setting_VGA_640_480), ++ OV5640_60_FPS}, ++ {OV5640_MODE_NTSC_720_480, SUBSAMPLING, ++ 720, 1896, 480, 984, ++ ov5640_setting_NTSC_720_480, ++ ARRAY_SIZE(ov5640_setting_NTSC_720_480), ++ OV5640_30_FPS}, ++ {OV5640_MODE_PAL_720_576, SUBSAMPLING, ++ 720, 1896, 576, 984, ++ ov5640_setting_PAL_720_576, ++ ARRAY_SIZE(ov5640_setting_PAL_720_576), ++ OV5640_30_FPS}, ++ {OV5640_MODE_XGA_1024_768, SUBSAMPLING, ++ 1024, 1896, 768, 1080, ++ ov5640_setting_XGA_1024_768, ++ ARRAY_SIZE(ov5640_setting_XGA_1024_768), ++ OV5640_30_FPS}, ++ {OV5640_MODE_720P_1280_720, SUBSAMPLING, ++ 1280, 1892, 720, 740, ++ ov5640_setting_720P_1280_720, ++ ARRAY_SIZE(ov5640_setting_720P_1280_720), ++ OV5640_30_FPS}, ++ {OV5640_MODE_1080P_1920_1080, SCALING, ++ 1920, 2500, 1080, 1120, ++ ov5640_setting_1080P_1920_1080, ++ ARRAY_SIZE(ov5640_setting_1080P_1920_1080), ++ OV5640_30_FPS}, ++ {OV5640_MODE_QSXGA_2592_1944, SCALING, ++ 2592, 2844, 1944, 1968, ++ ov5640_setting_QSXGA_2592_1944, ++ ARRAY_SIZE(ov5640_setting_QSXGA_2592_1944), ++ OV5640_15_FPS}, ++}; ++ ++static int ov5640_init_slave_id(struct ov5640_dev *sensor) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ struct i2c_msg msg; ++ u8 buf[3]; ++ int ret; ++ ++ if (client->addr == OV5640_DEFAULT_SLAVE_ID) ++ return 0; ++ ++ buf[0] = OV5640_REG_SLAVE_ID >> 8; ++ buf[1] = OV5640_REG_SLAVE_ID & 0xff; ++ buf[2] = client->addr << 1; ++ ++ msg.addr = OV5640_DEFAULT_SLAVE_ID; ++ msg.flags = 0; ++ msg.buf = buf; ++ msg.len = sizeof(buf); ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ if (ret < 0) { ++ dev_err(&client->dev, "%s: failed with %d\n", __func__, ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ov5640_write_reg(struct ov5640_dev *sensor, u16 reg, u8 val) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ struct i2c_msg msg; ++ u8 buf[3]; ++ int ret; ++ ++ buf[0] = reg >> 8; ++ buf[1] = reg & 0xff; ++ buf[2] = val; ++ ++ msg.addr = client->addr; ++ msg.flags = client->flags; ++ msg.buf = buf; ++ msg.len = sizeof(buf); ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ if (ret < 0) { ++ dev_err(&client->dev, "%s: error: reg=%x, val=%x\n", ++ __func__, reg, val); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ov5640_read_reg(struct ov5640_dev *sensor, u16 reg, u8 *val) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ struct i2c_msg msg[2]; ++ u8 buf[2]; ++ int ret; ++ ++ buf[0] = reg >> 8; ++ buf[1] = reg & 0xff; ++ ++ msg[0].addr = client->addr; ++ msg[0].flags = client->flags; ++ msg[0].buf = buf; ++ msg[0].len = sizeof(buf); ++ ++ msg[1].addr = client->addr; ++ msg[1].flags = client->flags | I2C_M_RD; ++ msg[1].buf = buf; ++ msg[1].len = 1; ++ ++ ret = i2c_transfer(client->adapter, msg, 2); ++ if (ret < 0) { ++ dev_err(&client->dev, "%s: error: reg=%x\n", ++ __func__, reg); ++ return ret; ++ } ++ ++ *val = buf[0]; ++ return 0; ++} ++ ++static int ov5640_read_reg16(struct ov5640_dev *sensor, u16 reg, u16 *val) ++{ ++ u8 hi, lo; ++ int ret; ++ ++ ret = ov5640_read_reg(sensor, reg, &hi); ++ if (ret) ++ return ret; ++ ret = ov5640_read_reg(sensor, reg + 1, &lo); ++ if (ret) ++ return ret; ++ ++ *val = ((u16)hi << 8) | (u16)lo; ++ return 0; ++} ++ ++static int ov5640_write_reg16(struct ov5640_dev *sensor, u16 reg, u16 val) ++{ ++ int ret; ++ ++ ret = ov5640_write_reg(sensor, reg, val >> 8); ++ if (ret) ++ return ret; ++ ++ return ov5640_write_reg(sensor, reg + 1, val & 0xff); ++} ++ ++static int ov5640_mod_reg(struct ov5640_dev *sensor, u16 reg, ++ u8 mask, u8 val) ++{ ++ u8 readval; ++ int ret; ++ ++ ret = ov5640_read_reg(sensor, reg, &readval); ++ if (ret) ++ return ret; ++ ++ readval &= ~mask; ++ val &= mask; ++ val |= readval; ++ ++ return ov5640_write_reg(sensor, reg, val); ++} ++ ++/* ++ * After trying the various combinations, reading various ++ * documentations spread around the net, and from the various ++ * feedback, the clock tree is probably as follows: ++ * ++ * +--------------+ ++ * | Ext. Clock | ++ * +-+------------+ ++ * | +----------+ ++ * +->| PLL1 | - reg 0x3036, for the multiplier ++ * +-+--------+ - reg 0x3037, bits 0-3 for the pre-divider ++ * | +--------------+ ++ * +->| System Clock | - reg 0x3035, bits 4-7 ++ * +-+------------+ ++ * | +--------------+ ++ * +->| MIPI Divider | - reg 0x3035, bits 0-3 ++ * | +-+------------+ ++ * | +----------------> MIPI SCLK ++ * | + +-----+ ++ * | +->| / 2 |-------> MIPI BIT CLK ++ * | +-----+ ++ * | +--------------+ ++ * +->| PLL Root Div | - reg 0x3037, bit 4 ++ * +-+------------+ ++ * | +---------+ ++ * +->| Bit Div | - reg 0x3034, bits 0-3 ++ * +-+-------+ ++ * | +-------------+ ++ * +->| SCLK Div | - reg 0x3108, bits 0-1 ++ * | +-+-----------+ ++ * | +---------------> SCLK ++ * | +-------------+ ++ * +->| SCLK 2X Div | - reg 0x3108, bits 2-3 ++ * | +-+-----------+ ++ * | +---------------> SCLK 2X ++ * | +-------------+ ++ * +->| PCLK Div | - reg 0x3108, bits 4-5 ++ * ++------------+ ++ * + +-----------+ ++ * +->| P_DIV | - reg 0x3035, bits 0-3 ++ * +-----+-----+ ++ * +------------> PCLK ++ * ++ * This is deviating from the datasheet at least for the register ++ * 0x3108, since it's said here that the PCLK would be clocked from ++ * the PLL. ++ * ++ * There seems to be also (unverified) constraints: ++ * - the PLL pre-divider output rate should be in the 4-27MHz range ++ * - the PLL multiplier output rate should be in the 500-1000MHz range ++ * - PCLK >= SCLK * 2 in YUV, >= SCLK in Raw or JPEG ++ * ++ * In the two latter cases, these constraints are met since our ++ * factors are hardcoded. If we were to change that, we would need to ++ * take this into account. The only varying parts are the PLL ++ * multiplier and the system clock divider, which are shared between ++ * all these clocks so won't cause any issue. ++ */ ++ ++/* ++ * This is supposed to be ranging from 1 to 8, but the value is always ++ * set to 3 in the vendor kernels. ++ */ ++#define OV5640_PLL_PREDIV 3 ++ ++#define OV5640_PLL_MULT_MIN 4 ++#define OV5640_PLL_MULT_MAX 252 ++ ++/* ++ * This is supposed to be ranging from 1 to 16, but the value is ++ * always set to either 1 or 2 in the vendor kernels. ++ */ ++#define OV5640_SYSDIV_MIN 1 ++#define OV5640_SYSDIV_MAX 16 ++ ++/* ++ * Hardcode these values for scaler and non-scaler modes. ++ * FIXME: to be re-calcualted for 1 data lanes setups ++ */ ++#define OV5640_MIPI_DIV_PCLK 2 ++#define OV5640_MIPI_DIV_SCLK 1 ++ ++/* ++ * This is supposed to be ranging from 1 to 2, but the value is always ++ * set to 2 in the vendor kernels. ++ */ ++#define OV5640_PLL_ROOT_DIV 2 ++#define OV5640_PLL_CTRL3_PLL_ROOT_DIV_2 BIT(4) ++ ++/* ++ * We only supports 8-bit formats at the moment ++ */ ++#define OV5640_BIT_DIV 2 ++#define OV5640_PLL_CTRL0_MIPI_MODE_8BIT 0x08 ++ ++/* ++ * This is supposed to be ranging from 1 to 8, but the value is always ++ * set to 2 in the vendor kernels. ++ */ ++#define OV5640_SCLK_ROOT_DIV 2 ++ ++/* ++ * This is hardcoded so that the consistency is maintained between SCLK and ++ * SCLK 2x. ++ */ ++#define OV5640_SCLK2X_ROOT_DIV (OV5640_SCLK_ROOT_DIV / 2) ++ ++/* ++ * This is supposed to be ranging from 1 to 8, but the value is always ++ * set to 1 in the vendor kernels. ++ */ ++#define OV5640_PCLK_ROOT_DIV 1 ++#define OV5640_PLL_SYS_ROOT_DIVIDER_BYPASS 0x00 ++ ++static unsigned long ov5640_compute_sys_clk(struct ov5640_dev *sensor, ++ u8 pll_prediv, u8 pll_mult, ++ u8 sysdiv) ++{ ++ unsigned long sysclk = sensor->xclk_freq / pll_prediv * pll_mult; ++ ++ /* PLL1 output cannot exceed 1GHz. */ ++ if (sysclk / 1000000 > 1000) ++ return 0; ++ ++ return sysclk / sysdiv; ++} ++ ++static unsigned long ov5640_calc_sys_clk(struct ov5640_dev *sensor, ++ unsigned long rate, ++ u8 *pll_prediv, u8 *pll_mult, ++ u8 *sysdiv) ++{ ++ unsigned long best = ~0; ++ u8 best_sysdiv = 1, best_mult = 1; ++ u8 _sysdiv, _pll_mult; ++ ++ for (_sysdiv = OV5640_SYSDIV_MIN; ++ _sysdiv <= OV5640_SYSDIV_MAX; ++ _sysdiv++) { ++ for (_pll_mult = OV5640_PLL_MULT_MIN; ++ _pll_mult <= OV5640_PLL_MULT_MAX; ++ _pll_mult++) { ++ unsigned long _rate; ++ ++ /* ++ * The PLL multiplier cannot be odd if above ++ * 127. ++ */ ++ if (_pll_mult > 127 && (_pll_mult % 2)) ++ continue; ++ ++ _rate = ov5640_compute_sys_clk(sensor, ++ OV5640_PLL_PREDIV, ++ _pll_mult, _sysdiv); ++ ++ /* ++ * We have reached the maximum allowed PLL1 output, ++ * increase sysdiv. ++ */ ++ if (!_rate) ++ break; ++ ++ /* ++ * Prefer rates above the expected clock rate than ++ * below, even if that means being less precise. ++ */ ++ if (_rate < rate) ++ continue; ++ ++ if (abs(rate - _rate) < abs(rate - best)) { ++ best = _rate; ++ best_sysdiv = _sysdiv; ++ best_mult = _pll_mult; ++ } ++ ++ if (_rate == rate) ++ goto out; ++ } ++ } ++ ++out: ++ *sysdiv = best_sysdiv; ++ *pll_prediv = OV5640_PLL_PREDIV; ++ *pll_mult = best_mult; ++ ++ return best; ++} ++ ++/* ++ * 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) ++{ ++ const struct ov5640_mode_info *mode = sensor->current_mode; ++ u8 prediv, mult, sysdiv; ++ u8 mipi_div; ++ int ret; ++ ++ /* ++ * 1280x720 is reported to use 'SUBSAMPLING' only, ++ * but according to the sensor manual it goes through the ++ * scaler before subsampling. ++ */ ++ if (mode->dn_mode == SCALING || ++ (mode->id == OV5640_MODE_720P_1280_720)) ++ mipi_div = OV5640_MIPI_DIV_SCLK; ++ else ++ mipi_div = OV5640_MIPI_DIV_PCLK; ++ ++ ov5640_calc_sys_clk(sensor, rate, &prediv, &mult, &sysdiv); ++ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL0, ++ 0x0f, OV5640_PLL_CTRL0_MIPI_MODE_8BIT); ++ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL1, ++ 0xff, sysdiv << 4 | mipi_div); ++ if (ret) ++ return ret; ++ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL2, 0xff, mult); ++ if (ret) ++ return ret; ++ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL3, ++ 0x1f, OV5640_PLL_CTRL3_PLL_ROOT_DIV_2 | prediv); ++ if (ret) ++ return ret; ++ ++ return ov5640_mod_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, ++ 0x30, OV5640_PLL_SYS_ROOT_DIVIDER_BYPASS); ++} ++ ++static unsigned long ov5640_calc_pclk(struct ov5640_dev *sensor, ++ unsigned long rate, ++ u8 *pll_prediv, u8 *pll_mult, u8 *sysdiv, ++ u8 *pll_rdiv, u8 *bit_div, u8 *pclk_div) ++{ ++ unsigned long _rate = rate * OV5640_PLL_ROOT_DIV * OV5640_BIT_DIV * ++ OV5640_PCLK_ROOT_DIV; ++ ++ _rate = ov5640_calc_sys_clk(sensor, _rate, pll_prediv, pll_mult, ++ sysdiv); ++ *pll_rdiv = OV5640_PLL_ROOT_DIV; ++ *bit_div = OV5640_BIT_DIV; ++ *pclk_div = OV5640_PCLK_ROOT_DIV; ++ ++ return _rate / *pll_rdiv / *bit_div / *pclk_div; ++} ++ ++static int ov5640_set_dvp_pclk(struct ov5640_dev *sensor, unsigned long rate) ++{ ++ u8 prediv, mult, sysdiv, pll_rdiv, bit_div, pclk_div; ++ int ret; ++ ++ ov5640_calc_pclk(sensor, rate, &prediv, &mult, &sysdiv, &pll_rdiv, ++ &bit_div, &pclk_div); ++ ++#ifndef CONFIG_VIN_SENSOR_OV5640 ++ if (bit_div == 2) ++ bit_div = 8; ++#else ++ bit_div = 0xa; ++#endif ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL0, ++ 0x0f, bit_div); ++ if (ret) ++ return ret; ++ ++ /* ++ * We need to set sysdiv according to the clock, and to clear ++ * the MIPI divider. ++ */ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL1, ++ 0xff, sysdiv << 4); ++ if (ret) ++ return ret; ++ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL2, ++ 0xff, mult); ++ if (ret) ++ return ret; ++ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL3, ++ 0x1f, prediv | ((pll_rdiv - 1) << 4)); ++ if (ret) ++ return ret; ++ ++ return ov5640_mod_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, 0x30, ++ (ilog2(pclk_div) << 4)); ++} ++ ++/* set JPEG framing sizes */ ++static int ov5640_set_jpeg_timings(struct ov5640_dev *sensor, ++ const struct ov5640_mode_info *mode) ++{ ++ int ret; ++ ++ /* ++ * compression mode 3 timing ++ * ++ * Data is transmitted with programmable width (VFIFO_HSIZE). ++ * No padding done. Last line may have less data. Varying ++ * number of lines per frame, depending on amount of data. ++ */ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_JPG_MODE_SELECT, 0x7, 0x3); ++ if (ret < 0) ++ return ret; ++ ++ ret = ov5640_write_reg16(sensor, OV5640_REG_VFIFO_HSIZE, mode->hact); ++ if (ret < 0) ++ return ret; ++ ++ return ov5640_write_reg16(sensor, OV5640_REG_VFIFO_VSIZE, mode->vact); ++} ++ ++/* 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 (sensor->fmt.code == MEDIA_BUS_FMT_JPEG_1X8) { ++ ret = ov5640_set_jpeg_timings(sensor, mode); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_DVPHO, mode->hact); ++ if (ret < 0) ++ return ret; ++ ++ ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_DVPVO, mode->vact); ++ if (ret < 0) ++ return ret; ++ ++ ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_HTS, mode->htot); ++ if (ret < 0) ++ return ret; ++ ++ return ov5640_write_reg16(sensor, OV5640_REG_TIMING_VTS, mode->vtot); ++} ++ ++static int ov5640_load_regs(struct ov5640_dev *sensor, ++ const struct ov5640_mode_info *mode) ++{ ++ const struct reg_value *regs = mode->reg_data; ++ unsigned int i; ++ u32 delay_ms; ++ u16 reg_addr; ++ u8 mask, val; ++ int ret = 0; ++ ++ st_info(ST_SENSOR, "%s, mode = 0x%x\n", __func__, mode->id); ++ for (i = 0; i < mode->reg_data_size; ++i, ++regs) { ++ delay_ms = regs->delay_ms; ++ reg_addr = regs->reg_addr; ++ val = regs->val; ++ mask = regs->mask; ++ ++ /* remain in power down mode for DVP */ ++ if (regs->reg_addr == OV5640_REG_SYS_CTRL0 && ++ val == OV5640_REG_SYS_CTRL0_SW_PWUP && ++ sensor->ep.bus_type != V4L2_MBUS_CSI2_DPHY) ++ continue; ++ ++ if (mask) ++ ret = ov5640_mod_reg(sensor, reg_addr, mask, val); ++ else ++ ret = ov5640_write_reg(sensor, reg_addr, val); ++ if (ret) ++ break; ++ ++ if (delay_ms) ++ usleep_range(1000 * delay_ms, 1000 * delay_ms + 100); ++ } ++ ++ return ov5640_set_timings(sensor, mode); ++} ++ ++static int ov5640_set_autoexposure(struct ov5640_dev *sensor, bool on) ++{ ++ return ov5640_mod_reg(sensor, OV5640_REG_AEC_PK_MANUAL, ++ BIT(0), on ? 0 : BIT(0)); ++} ++ ++/* read exposure, in number of line periods */ ++static int ov5640_get_exposure(struct ov5640_dev *sensor) ++{ ++ int exp, ret; ++ u8 temp; ++ ++ ret = ov5640_read_reg(sensor, OV5640_REG_AEC_PK_EXPOSURE_HI, &temp); ++ if (ret) ++ return ret; ++ exp = ((int)temp & 0x0f) << 16; ++ ret = ov5640_read_reg(sensor, OV5640_REG_AEC_PK_EXPOSURE_MED, &temp); ++ if (ret) ++ return ret; ++ exp |= ((int)temp << 8); ++ ret = ov5640_read_reg(sensor, OV5640_REG_AEC_PK_EXPOSURE_LO, &temp); ++ if (ret) ++ return ret; ++ exp |= (int)temp; ++ ++ return exp >> 4; ++} ++ ++/* write exposure, given number of line periods */ ++static int ov5640_set_exposure(struct ov5640_dev *sensor, u32 exposure) ++{ ++ int ret; ++ ++ exposure <<= 4; ++ ++ ret = ov5640_write_reg(sensor, ++ OV5640_REG_AEC_PK_EXPOSURE_LO, ++ exposure & 0xff); ++ if (ret) ++ return ret; ++ ret = ov5640_write_reg(sensor, ++ OV5640_REG_AEC_PK_EXPOSURE_MED, ++ (exposure >> 8) & 0xff); ++ if (ret) ++ return ret; ++ return ov5640_write_reg(sensor, ++ OV5640_REG_AEC_PK_EXPOSURE_HI, ++ (exposure >> 16) & 0x0f); ++} ++ ++static int ov5640_get_gain(struct ov5640_dev *sensor) ++{ ++ u16 gain; ++ int ret; ++ ++ ret = ov5640_read_reg16(sensor, OV5640_REG_AEC_PK_REAL_GAIN, &gain); ++ if (ret) ++ return ret; ++ ++ return gain & 0x3ff; ++} ++ ++static int ov5640_set_gain(struct ov5640_dev *sensor, int gain) ++{ ++ return ov5640_write_reg16(sensor, OV5640_REG_AEC_PK_REAL_GAIN, ++ (u16)gain & 0x3ff); ++} ++ ++static int ov5640_set_autogain(struct ov5640_dev *sensor, bool on) ++{ ++ return ov5640_mod_reg(sensor, OV5640_REG_AEC_PK_MANUAL, ++ BIT(1), on ? 0 : BIT(1)); ++} ++ ++static int ov5640_set_stream_dvp(struct ov5640_dev *sensor, bool on) ++{ ++ return ov5640_write_reg(sensor, OV5640_REG_SYS_CTRL0, on ? ++ OV5640_REG_SYS_CTRL0_SW_PWUP : ++ OV5640_REG_SYS_CTRL0_SW_PWDN); ++} ++ ++static int ov5640_set_stream_mipi(struct ov5640_dev *sensor, bool on) ++{ ++ int ret; ++ ++ /* ++ * Enable/disable the MIPI interface ++ * ++ * 0x300e = on ? 0x45 : 0x40 ++ * ++ * FIXME: the sensor manual (version 2.03) reports ++ * [7:5] = 000 : 1 data lane mode ++ * [7:5] = 001 : 2 data lanes mode ++ * But this settings do not work, while the following ones ++ * have been validated for 2 data lanes mode. ++ * ++ * [7:5] = 010 : 2 data lanes mode ++ * [4] = 0 : Power up MIPI HS Tx ++ * [3] = 0 : Power up MIPI LS Rx ++ * [2] = 1/0 : MIPI interface enable/disable ++ * [1:0] = 01/00: FIXME: 'debug' ++ */ ++ ret = ov5640_write_reg(sensor, OV5640_REG_IO_MIPI_CTRL00, ++ on ? 0x45 : 0x40); ++ if (ret) ++ return ret; ++ ++ return ov5640_write_reg(sensor, OV5640_REG_FRAME_CTRL01, ++ on ? 0x00 : 0x0f); ++} ++ ++static int ov5640_get_sysclk(struct ov5640_dev *sensor) ++{ ++ /* calculate sysclk */ ++ u32 xvclk = sensor->xclk_freq / 10000; ++ u32 multiplier, prediv, VCO, sysdiv, pll_rdiv; ++ u32 sclk_rdiv_map[] = {1, 2, 4, 8}; ++ u32 bit_div2x = 1, sclk_rdiv, sysclk; ++ u8 temp1, temp2; ++ int ret; ++ ++ ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL0, &temp1); ++ if (ret) ++ return ret; ++ temp2 = temp1 & 0x0f; ++ if (temp2 == 8 || temp2 == 10) ++ bit_div2x = temp2 / 2; ++ ++ ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL1, &temp1); ++ if (ret) ++ return ret; ++ sysdiv = temp1 >> 4; ++ if (sysdiv == 0) ++ sysdiv = 16; ++ ++ ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL2, &temp1); ++ if (ret) ++ return ret; ++ multiplier = temp1; ++ ++ ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL3, &temp1); ++ if (ret) ++ return ret; ++ prediv = temp1 & 0x0f; ++ pll_rdiv = ((temp1 >> 4) & 0x01) + 1; ++ ++ ret = ov5640_read_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, &temp1); ++ if (ret) ++ return ret; ++ temp2 = temp1 & 0x03; ++ sclk_rdiv = sclk_rdiv_map[temp2]; ++ ++ if (!prediv || !sysdiv || !pll_rdiv || !bit_div2x) ++ return -EINVAL; ++ ++ VCO = xvclk * multiplier / prediv; ++ ++ sysclk = VCO / sysdiv / pll_rdiv * 2 / bit_div2x / sclk_rdiv; ++ ++ return sysclk; ++} ++ ++static int ov5640_set_night_mode(struct ov5640_dev *sensor) ++{ ++ /* read HTS from register settings */ ++ u8 mode; ++ int ret; ++ ++ ret = ov5640_read_reg(sensor, OV5640_REG_AEC_CTRL00, &mode); ++ if (ret) ++ return ret; ++ mode &= 0xfb; ++ return ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL00, mode); ++} ++ ++static int ov5640_get_hts(struct ov5640_dev *sensor) ++{ ++ /* read HTS from register settings */ ++ u16 hts; ++ int ret; ++ ++ ret = ov5640_read_reg16(sensor, OV5640_REG_TIMING_HTS, &hts); ++ if (ret) ++ return ret; ++ return hts; ++} ++ ++static int ov5640_get_vts(struct ov5640_dev *sensor) ++{ ++ u16 vts; ++ int ret; ++ ++ ret = ov5640_read_reg16(sensor, OV5640_REG_TIMING_VTS, &vts); ++ if (ret) ++ return ret; ++ return vts; ++} ++ ++static int ov5640_set_vts(struct ov5640_dev *sensor, int vts) ++{ ++ return ov5640_write_reg16(sensor, OV5640_REG_TIMING_VTS, vts); ++} ++ ++static int ov5640_get_light_freq(struct ov5640_dev *sensor) ++{ ++ /* get banding filter value */ ++ int ret, light_freq = 0; ++ u8 temp, temp1; ++ ++ ret = ov5640_read_reg(sensor, OV5640_REG_HZ5060_CTRL01, &temp); ++ if (ret) ++ return ret; ++ ++ if (temp & 0x80) { ++ /* manual */ ++ ret = ov5640_read_reg(sensor, OV5640_REG_HZ5060_CTRL00, ++ &temp1); ++ if (ret) ++ return ret; ++ if (temp1 & 0x04) { ++ /* 50Hz */ ++ light_freq = 50; ++ } else { ++ /* 60Hz */ ++ light_freq = 60; ++ } ++ } else { ++ /* auto */ ++ ret = ov5640_read_reg(sensor, OV5640_REG_SIGMADELTA_CTRL0C, ++ &temp1); ++ if (ret) ++ return ret; ++ ++ if (temp1 & 0x01) { ++ /* 50Hz */ ++ light_freq = 50; ++ } else { ++ /* 60Hz */ ++ } ++ } ++ ++ return light_freq; ++} ++ ++static int ov5640_set_bandingfilter(struct ov5640_dev *sensor) ++{ ++ u32 band_step60, max_band60, band_step50, max_band50, prev_vts; ++ int ret; ++ ++ /* read preview PCLK */ ++ ret = ov5640_get_sysclk(sensor); ++ if (ret < 0) ++ return ret; ++ if (ret == 0) ++ return -EINVAL; ++ sensor->prev_sysclk = ret; ++ /* read preview HTS */ ++ ret = ov5640_get_hts(sensor); ++ if (ret < 0) ++ return ret; ++ if (ret == 0) ++ return -EINVAL; ++ sensor->prev_hts = ret; ++ ++ /* read preview VTS */ ++ ret = ov5640_get_vts(sensor); ++ if (ret < 0) ++ return ret; ++ prev_vts = ret; ++ ++ /* calculate banding filter */ ++ /* 60Hz */ ++ band_step60 = sensor->prev_sysclk * 100 / sensor->prev_hts * 100 / 120; ++ ret = ov5640_write_reg16(sensor, OV5640_REG_AEC_B60_STEP, band_step60); ++ if (ret) ++ return ret; ++ if (!band_step60) ++ return -EINVAL; ++ max_band60 = (int)((prev_vts - 4) / band_step60); ++ ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL0D, max_band60); ++ if (ret) ++ return ret; ++ ++ /* 50Hz */ ++ band_step50 = sensor->prev_sysclk * 100 / sensor->prev_hts; ++ ret = ov5640_write_reg16(sensor, OV5640_REG_AEC_B50_STEP, band_step50); ++ if (ret) ++ return ret; ++ if (!band_step50) ++ return -EINVAL; ++ max_band50 = (int)((prev_vts - 4) / band_step50); ++ return ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL0E, max_band50); ++} ++ ++static int ov5640_set_ae_target(struct ov5640_dev *sensor, int target) ++{ ++ /* stable in high */ ++ u32 fast_high, fast_low; ++ int ret; ++ ++ sensor->ae_low = target * 23 / 25; /* 0.92 */ ++ sensor->ae_high = target * 27 / 25; /* 1.08 */ ++ ++ fast_high = sensor->ae_high << 1; ++ if (fast_high > 255) ++ fast_high = 255; ++ ++ fast_low = sensor->ae_low >> 1; ++ ++ ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL0F, sensor->ae_high); ++ if (ret) ++ return ret; ++ ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL10, sensor->ae_low); ++ if (ret) ++ return ret; ++ ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL1B, sensor->ae_high); ++ if (ret) ++ return ret; ++ ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL1E, sensor->ae_low); ++ if (ret) ++ return ret; ++ ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL11, fast_high); ++ if (ret) ++ return ret; ++ return ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL1F, fast_low); ++} ++ ++static int ov5640_get_binning(struct ov5640_dev *sensor) ++{ ++ u8 temp; ++ int ret; ++ ++ ret = ov5640_read_reg(sensor, OV5640_REG_TIMING_TC_REG21, &temp); ++ if (ret) ++ return ret; ++ ++ return temp & BIT(0); ++} ++ ++static int ov5640_set_binning(struct ov5640_dev *sensor, bool enable) ++{ ++ int ret; ++ ++ /* ++ * TIMING TC REG21: ++ * - [0]: Horizontal binning enable ++ */ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_TIMING_TC_REG21, ++ BIT(0), enable ? BIT(0) : 0); ++ if (ret) ++ return ret; ++ /* ++ * TIMING TC REG20: ++ * - [0]: Undocumented, but hardcoded init sequences ++ * are always setting REG21/REG20 bit 0 to same value... ++ */ ++ return ov5640_mod_reg(sensor, OV5640_REG_TIMING_TC_REG20, ++ BIT(0), enable ? BIT(0) : 0); ++} ++ ++static int ov5640_set_virtual_channel(struct ov5640_dev *sensor) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ u8 temp, channel = virtual_channel; ++ int ret; ++ ++ if (channel > 3) { ++ dev_err(&client->dev, ++ "%s: wrong virtual_channel parameter, expected (0..3), got %d\n", ++ __func__, channel); ++ return -EINVAL; ++ } ++ ++ ret = ov5640_read_reg(sensor, OV5640_REG_DEBUG_MODE, &temp); ++ if (ret) ++ return ret; ++ temp &= ~(3 << 6); ++ temp |= (channel << 6); ++ return ov5640_write_reg(sensor, OV5640_REG_DEBUG_MODE, temp); ++} ++ ++static const struct ov5640_mode_info * ++ov5640_find_mode(struct ov5640_dev *sensor, enum ov5640_frame_rate fr, ++ int width, int height, bool nearest) ++{ ++ const struct ov5640_mode_info *mode; ++ ++ mode = v4l2_find_nearest_size(ov5640_mode_data, ++ ARRAY_SIZE(ov5640_mode_data), ++ hact, vact, ++ width, height); ++ ++ if (!mode || ++ (!nearest && (mode->hact != width || mode->vact != height))) ++ return NULL; ++ ++ /* Check to see if the current mode exceeds the max frame rate */ ++ if (ov5640_framerates[fr] > ov5640_framerates[mode->max_fps]) ++ return NULL; ++ ++ 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 ++ */ ++static int ov5640_set_mode_exposure_calc(struct ov5640_dev *sensor, ++ const struct ov5640_mode_info *mode) ++{ ++ u32 prev_shutter, prev_gain16; ++ u32 cap_shutter, cap_gain16; ++ u32 cap_sysclk, cap_hts, cap_vts; ++ u32 light_freq, cap_bandfilt, cap_maxband; ++ u32 cap_gain16_shutter; ++ u8 average; ++ int ret; ++ ++ if (!mode->reg_data) ++ return -EINVAL; ++ ++ /* read preview shutter */ ++ ret = ov5640_get_exposure(sensor); ++ if (ret < 0) ++ return ret; ++ prev_shutter = ret; ++ ret = ov5640_get_binning(sensor); ++ if (ret < 0) ++ return ret; ++ if (ret && mode->id != OV5640_MODE_720P_1280_720 && ++ mode->id != OV5640_MODE_1080P_1920_1080) ++ prev_shutter *= 2; ++ ++ /* read preview gain */ ++ ret = ov5640_get_gain(sensor); ++ if (ret < 0) ++ return ret; ++ prev_gain16 = ret; ++ ++ /* get average */ ++ ret = ov5640_read_reg(sensor, OV5640_REG_AVG_READOUT, &average); ++ if (ret) ++ return ret; ++ ++ /* turn off night mode for capture */ ++ ret = ov5640_set_night_mode(sensor); ++ if (ret < 0) ++ return ret; ++ ++ /* Write capture setting */ ++ ret = ov5640_load_regs(sensor, mode); ++ if (ret < 0) ++ return ret; ++ ++ /* read capture VTS */ ++ ret = ov5640_get_vts(sensor); ++ if (ret < 0) ++ return ret; ++ cap_vts = ret; ++ ret = ov5640_get_hts(sensor); ++ if (ret < 0) ++ return ret; ++ if (ret == 0) ++ return -EINVAL; ++ cap_hts = ret; ++ ++ ret = ov5640_get_sysclk(sensor); ++ if (ret < 0) ++ return ret; ++ if (ret == 0) ++ return -EINVAL; ++ cap_sysclk = ret; ++ ++ /* calculate capture banding filter */ ++ ret = ov5640_get_light_freq(sensor); ++ if (ret < 0) ++ return ret; ++ light_freq = ret; ++ ++ if (light_freq == 60) { ++ /* 60Hz */ ++ cap_bandfilt = cap_sysclk * 100 / cap_hts * 100 / 120; ++ } else { ++ /* 50Hz */ ++ cap_bandfilt = cap_sysclk * 100 / cap_hts; ++ } ++ ++ if (!sensor->prev_sysclk) { ++ ret = ov5640_get_sysclk(sensor); ++ if (ret < 0) ++ return ret; ++ if (ret == 0) ++ return -EINVAL; ++ sensor->prev_sysclk = ret; ++ } ++ ++ if (!cap_bandfilt) ++ return -EINVAL; ++ ++ cap_maxband = (int)((cap_vts - 4) / cap_bandfilt); ++ ++ /* calculate capture shutter/gain16 */ ++ if (average > sensor->ae_low && average < sensor->ae_high) { ++ /* in stable range */ ++ cap_gain16_shutter = ++ prev_gain16 * prev_shutter * ++ cap_sysclk / sensor->prev_sysclk * ++ sensor->prev_hts / cap_hts * ++ sensor->ae_target / average; ++ } else { ++ cap_gain16_shutter = ++ prev_gain16 * prev_shutter * ++ cap_sysclk / sensor->prev_sysclk * ++ sensor->prev_hts / cap_hts; ++ } ++ ++ /* gain to shutter */ ++ if (cap_gain16_shutter < (cap_bandfilt * 16)) { ++ /* shutter < 1/100 */ ++ cap_shutter = cap_gain16_shutter / 16; ++ if (cap_shutter < 1) ++ cap_shutter = 1; ++ ++ cap_gain16 = cap_gain16_shutter / cap_shutter; ++ if (cap_gain16 < 16) ++ cap_gain16 = 16; ++ } else { ++ if (cap_gain16_shutter > (cap_bandfilt * cap_maxband * 16)) { ++ /* exposure reach max */ ++ cap_shutter = cap_bandfilt * cap_maxband; ++ if (!cap_shutter) ++ return -EINVAL; ++ ++ cap_gain16 = cap_gain16_shutter / cap_shutter; ++ } else { ++ /* 1/100 < (cap_shutter = n/100) =< max */ ++ cap_shutter = ++ ((int)(cap_gain16_shutter / 16 / cap_bandfilt)) ++ * cap_bandfilt; ++ if (!cap_shutter) ++ return -EINVAL; ++ ++ cap_gain16 = cap_gain16_shutter / cap_shutter; ++ } ++ } ++ ++ /* set capture gain */ ++ ret = ov5640_set_gain(sensor, cap_gain16); ++ if (ret) ++ return ret; ++ ++ /* write capture shutter */ ++ if (cap_shutter > (cap_vts - 4)) { ++ cap_vts = cap_shutter + 4; ++ ret = ov5640_set_vts(sensor, cap_vts); ++ if (ret < 0) ++ return ret; ++ } ++ ++ /* set exposure */ ++ return ov5640_set_exposure(sensor, cap_shutter); ++} ++ ++/* ++ * if sensor changes inside scaling or subsampling ++ * change mode directly ++ */ ++static int ov5640_set_mode_direct(struct ov5640_dev *sensor, ++ const struct ov5640_mode_info *mode) ++{ ++ if (!mode->reg_data) ++ return -EINVAL; ++ ++ /* Write capture setting */ ++ return ov5640_load_regs(sensor, mode); ++} ++ ++static int ov5640_set_mode(struct ov5640_dev *sensor) ++{ ++ const struct ov5640_mode_info *mode = sensor->current_mode; ++ const struct ov5640_mode_info *orig_mode = sensor->last_mode; ++ 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; ++ orig_dn_mode = orig_mode->dn_mode; ++ ++ /* auto gain and exposure must be turned off when changing modes */ ++ if (auto_gain) { ++ ret = ov5640_set_autogain(sensor, false); ++ if (ret) ++ return ret; ++ } ++ ++ if (auto_exp) { ++ ret = ov5640_set_autoexposure(sensor, false); ++ if (ret) ++ 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); ++ } else { ++ rate = rate / sensor->ep.bus.parallel.bus_width; ++ ret = ov5640_set_dvp_pclk(sensor, rate); ++ } ++ ++ if (ret < 0) ++ return 0; ++ ++ if ((dn_mode == SUBSAMPLING && orig_dn_mode == SCALING) || ++ (dn_mode == SCALING && orig_dn_mode == SUBSAMPLING)) { ++ /* ++ * change between subsampling and scaling ++ * go through exposure calculation ++ */ ++ ret = ov5640_set_mode_exposure_calc(sensor, mode); ++ } else { ++ /* ++ * change inside subsampling or scaling ++ * download firmware directly ++ */ ++ ret = ov5640_set_mode_direct(sensor, mode); ++ } ++ if (ret < 0) ++ goto restore_auto_exp_gain; ++ ++ /* restore auto gain and exposure */ ++ if (auto_gain) ++ ov5640_set_autogain(sensor, true); ++ if (auto_exp) ++ ov5640_set_autoexposure(sensor, true); ++ ++ ret = ov5640_set_binning(sensor, dn_mode != SCALING); ++ if (ret < 0) ++ return ret; ++ ret = ov5640_set_ae_target(sensor, sensor->ae_target); ++ if (ret < 0) ++ return ret; ++ ret = ov5640_get_light_freq(sensor); ++ if (ret < 0) ++ return ret; ++ ret = ov5640_set_bandingfilter(sensor); ++ if (ret < 0) ++ return ret; ++ ret = ov5640_set_virtual_channel(sensor); ++ if (ret < 0) ++ return ret; ++ ++ sensor->pending_mode_change = false; ++ sensor->last_mode = mode; ++ ++ return 0; ++ ++restore_auto_exp_gain: ++ if (auto_exp) ++ ov5640_set_autoexposure(sensor, true); ++restore_auto_gain: ++ if (auto_gain) ++ ov5640_set_autogain(sensor, true); ++ ++ return ret; ++} ++ ++static int ov5640_set_framefmt(struct ov5640_dev *sensor, ++ struct v4l2_mbus_framefmt *format); ++ ++/* restore the last set video mode after chip power-on */ ++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); ++ if (ret < 0) ++ return ret; ++ sensor->last_mode = &ov5640_mode_init_data; ++ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, 0x3f, ++ (ilog2(OV5640_SCLK2X_ROOT_DIV) << 2) | ++ ilog2(OV5640_SCLK_ROOT_DIV)); ++ if (ret) ++ return ret; ++ ++ /* now restore the last capture mode */ ++ ret = ov5640_set_mode(sensor); ++ if (ret < 0) ++ return ret; ++ ++ return ov5640_set_framefmt(sensor, &sensor->fmt); ++} ++ ++static void ov5640_power(struct ov5640_dev *sensor, bool enable) ++{ ++ if (!sensor->pwdn_gpio) ++ return; ++ gpiod_set_value_cansleep(sensor->pwdn_gpio, enable ? 0 : 1); ++} ++ ++static void ov5640_reset(struct ov5640_dev *sensor) ++{ ++ if (!sensor->reset_gpio) ++ return; ++ ++ gpiod_set_value_cansleep(sensor->reset_gpio, 0); ++ ++ /* camera power cycle */ ++ ov5640_power(sensor, false); ++ usleep_range(5000, 10000); ++ ov5640_power(sensor, true); ++ usleep_range(5000, 10000); ++ ++ gpiod_set_value_cansleep(sensor->reset_gpio, 1); ++ usleep_range(1000, 2000); ++ ++ gpiod_set_value_cansleep(sensor->reset_gpio, 0); ++ usleep_range(20000, 25000); ++} ++ ++static int ov5640_set_power_on(struct ov5640_dev *sensor) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ int ret; ++ ++ ret = clk_prepare_enable(sensor->xclk); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to enable clock\n", ++ __func__); ++ return ret; ++ } ++ ++ ret = regulator_bulk_enable(OV5640_NUM_SUPPLIES, ++ sensor->supplies); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to enable regulators\n", ++ __func__); ++ goto xclk_off; ++ } ++ ++ ov5640_reset(sensor); ++ ov5640_power(sensor, true); ++ ++ ret = ov5640_init_slave_id(sensor); ++ if (ret) ++ goto power_off; ++ ++ return 0; ++ ++power_off: ++ ov5640_power(sensor, false); ++ regulator_bulk_disable(OV5640_NUM_SUPPLIES, sensor->supplies); ++xclk_off: ++ clk_disable_unprepare(sensor->xclk); ++ return ret; ++} ++ ++static void ov5640_set_power_off(struct ov5640_dev *sensor) ++{ ++ ov5640_power(sensor, false); ++ regulator_bulk_disable(OV5640_NUM_SUPPLIES, sensor->supplies); ++ clk_disable_unprepare(sensor->xclk); ++} ++ ++static int ov5640_set_power_mipi(struct ov5640_dev *sensor, bool on) ++{ ++ int ret; ++ ++ if (!on) { ++ /* Reset MIPI bus settings to their default values. */ ++ ov5640_write_reg(sensor, OV5640_REG_IO_MIPI_CTRL00, 0x58); ++ ov5640_write_reg(sensor, OV5640_REG_MIPI_CTRL00, 0x04); ++ ov5640_write_reg(sensor, OV5640_REG_PAD_OUTPUT00, 0x00); ++ return 0; ++ } ++ ++ /* ++ * Power up MIPI HS Tx and LS Rx; 2 data lanes mode ++ * ++ * 0x300e = 0x40 ++ * [7:5] = 010 : 2 data lanes mode (see FIXME note in ++ * "ov5640_set_stream_mipi()") ++ * [4] = 0 : Power up MIPI HS Tx ++ * [3] = 0 : Power up MIPI LS Rx ++ * [2] = 0 : MIPI interface disabled ++ */ ++ ret = ov5640_write_reg(sensor, OV5640_REG_IO_MIPI_CTRL00, 0x40); ++ if (ret) ++ return ret; ++ ++ /* ++ * Gate clock and set LP11 in 'no packets mode' (idle) ++ * ++ * 0x4800 = 0x24 ++ * [5] = 1 : Gate clock when 'no packets' ++ * [2] = 1 : MIPI bus in LP11 when 'no packets' ++ */ ++ ret = ov5640_write_reg(sensor, OV5640_REG_MIPI_CTRL00, 0x24); ++ if (ret) ++ return ret; ++ ++ /* ++ * Set data lanes and clock in LP11 when 'sleeping' ++ * ++ * 0x3019 = 0x70 ++ * [6] = 1 : MIPI data lane 2 in LP11 when 'sleeping' ++ * [5] = 1 : MIPI data lane 1 in LP11 when 'sleeping' ++ * [4] = 1 : MIPI clock lane in LP11 when 'sleeping' ++ */ ++ ret = ov5640_write_reg(sensor, OV5640_REG_PAD_OUTPUT00, 0x70); ++ if (ret) ++ return ret; ++ ++ /* Give lanes some time to coax into LP11 state. */ ++ usleep_range(500, 1000); ++ ++ return 0; ++} ++ ++static int ov5640_set_power_dvp(struct ov5640_dev *sensor, bool on) ++{ ++ unsigned int flags = sensor->ep.bus.parallel.flags; ++ bool bt656 = sensor->ep.bus_type == V4L2_MBUS_BT656; ++ u8 polarities = 0; ++ int ret; ++ ++ if (!on) { ++ /* Reset settings to their default values. */ ++ ov5640_write_reg(sensor, OV5640_REG_CCIR656_CTRL00, 0x00); ++ ov5640_write_reg(sensor, OV5640_REG_IO_MIPI_CTRL00, 0x58); ++ ov5640_write_reg(sensor, OV5640_REG_POLARITY_CTRL00, 0x20); ++ ov5640_write_reg(sensor, OV5640_REG_PAD_OUTPUT_ENABLE01, 0x00); ++ ov5640_write_reg(sensor, OV5640_REG_PAD_OUTPUT_ENABLE02, 0x00); ++ return 0; ++ } ++ ++ /* ++ * Note about parallel port configuration. ++ * ++ * When configured in parallel mode, the OV5640 will ++ * output 10 bits data on DVP data lines [9:0]. ++ * If only 8 bits data are wanted, the 8 bits data lines ++ * of the camera interface must be physically connected ++ * on the DVP data lines [9:2]. ++ * ++ * Control lines polarity can be configured through ++ * devicetree endpoint control lines properties. ++ * If no endpoint control lines properties are set, ++ * polarity will be as below: ++ * - VSYNC: active high ++ * - HREF: active low ++ * - PCLK: active low ++ * ++ * VSYNC & HREF are not configured if BT656 bus mode is selected ++ */ ++ ++ /* ++ * BT656 embedded synchronization configuration ++ * ++ * CCIR656 CTRL00 ++ * - [7]: SYNC code selection (0: auto generate sync code, ++ * 1: sync code from regs 0x4732-0x4735) ++ * - [6]: f value in CCIR656 SYNC code when fixed f value ++ * - [5]: Fixed f value ++ * - [4:3]: Blank toggle data options (00: data=1'h040/1'h200, ++ * 01: data from regs 0x4736-0x4738, 10: always keep 0) ++ * - [1]: Clip data disable ++ * - [0]: CCIR656 mode enable ++ * ++ * Default CCIR656 SAV/EAV mode with default codes ++ * SAV=0xff000080 & EAV=0xff00009d is enabled here with settings: ++ * - CCIR656 mode enable ++ * - auto generation of sync codes ++ * - blank toggle data 1'h040/1'h200 ++ * - clip reserved data (0x00 & 0xff changed to 0x01 & 0xfe) ++ */ ++ ret = ov5640_write_reg(sensor, OV5640_REG_CCIR656_CTRL00, ++ bt656 ? 0x01 : 0x00); ++ if (ret) ++ return ret; ++ ++ /* ++ * configure parallel port control lines polarity ++ * ++ * POLARITY CTRL0 ++ * - [5]: PCLK polarity (0: active low, 1: active high) ++ * - [1]: HREF polarity (0: active low, 1: active high) ++ * - [0]: VSYNC polarity (mismatch here between ++ * datasheet and hardware, 0 is active high ++ * and 1 is active low...) ++ */ ++ if (!bt656) { ++ if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) ++ polarities |= BIT(1); ++ if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) ++ polarities |= BIT(0); ++ } ++ if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) ++ polarities |= BIT(5); ++ ++ ret = ov5640_write_reg(sensor, OV5640_REG_POLARITY_CTRL00, polarities); ++ if (ret) ++ return ret; ++ ++ /* ++ * powerdown MIPI TX/RX PHY & enable DVP ++ * ++ * MIPI CONTROL 00 ++ * [4] = 1 : Power down MIPI HS Tx ++ * [3] = 1 : Power down MIPI LS Rx ++ * [2] = 0 : DVP enable (MIPI disable) ++ */ ++ ret = ov5640_write_reg(sensor, OV5640_REG_IO_MIPI_CTRL00, 0x58); ++ if (ret) ++ return ret; ++ ++ /* ++ * enable VSYNC/HREF/PCLK DVP control lines ++ * & D[9:6] DVP data lines ++ * ++ * PAD OUTPUT ENABLE 01 ++ * - 6: VSYNC output enable ++ * - 5: HREF output enable ++ * - 4: PCLK output enable ++ * - [3:0]: D[9:6] output enable ++ */ ++ ret = ov5640_write_reg(sensor, OV5640_REG_PAD_OUTPUT_ENABLE01, ++ bt656 ? 0x1f : 0x7f); ++ if (ret) ++ return ret; ++ ++ /* ++ * enable D[5:0] DVP data lines ++ * ++ * PAD OUTPUT ENABLE 02 ++ * - [7:2]: D[5:0] output enable ++ */ ++ return ov5640_write_reg(sensor, OV5640_REG_PAD_OUTPUT_ENABLE02, 0xfc); ++} ++ ++static int ov5640_set_power(struct ov5640_dev *sensor, bool on) ++{ ++ int ret = 0; ++ ++ if (on) { ++ ret = ov5640_set_power_on(sensor); ++ if (ret) ++ return ret; ++ ++ ret = ov5640_restore_mode(sensor); ++ if (ret) ++ goto power_off; ++ } ++ ++ if (sensor->ep.bus_type == V4L2_MBUS_CSI2_DPHY) ++ ret = ov5640_set_power_mipi(sensor, on); ++ else ++ ret = ov5640_set_power_dvp(sensor, on); ++ if (ret) ++ goto power_off; ++ ++ if (!on) ++ ov5640_set_power_off(sensor); ++ ++ return 0; ++ ++power_off: ++ ov5640_set_power_off(sensor); ++ return ret; ++} ++ ++/* --------------- Subdev Operations --------------- */ ++ ++static int ov5640_s_power(struct v4l2_subdev *sd, int on) ++{ ++ struct ov5640_dev *sensor = to_ov5640_dev(sd); ++ int ret = 0; ++ ++ mutex_lock(&sensor->lock); ++ ++ /* ++ * If the power count is modified from 0 to != 0 or from != 0 to 0, ++ * update the power state. ++ */ ++ if (sensor->power_count == !on) { ++ ret = ov5640_set_power(sensor, !!on); ++ if (ret) ++ goto out; ++ } ++ ++ /* Update the power count. */ ++ sensor->power_count += on ? 1 : -1; ++ WARN_ON(sensor->power_count < 0); ++out: ++ mutex_unlock(&sensor->lock); ++ ++ if (on && !ret && sensor->power_count == 1) { ++ /* restore controls */ ++ ret = v4l2_ctrl_handler_setup(&sensor->ctrls.handler); ++ } ++ ++ return ret; ++} ++ ++static int ov5640_try_frame_interval(struct ov5640_dev *sensor, ++ struct v4l2_fract *fi, ++ u32 width, u32 height) ++{ ++ const struct ov5640_mode_info *mode; ++ enum ov5640_frame_rate rate = OV5640_15_FPS; ++ int minfps, maxfps, best_fps, fps; ++ int i; ++ ++ minfps = ov5640_framerates[OV5640_15_FPS]; ++ maxfps = ov5640_framerates[OV5640_60_FPS]; ++ ++ if (fi->numerator == 0) { ++ fi->denominator = maxfps; ++ fi->numerator = 1; ++ rate = OV5640_60_FPS; ++ goto find_mode; ++ } ++ ++ fps = clamp_val(DIV_ROUND_CLOSEST(fi->denominator, fi->numerator), ++ minfps, maxfps); ++ ++ best_fps = minfps; ++ for (i = 0; i < ARRAY_SIZE(ov5640_framerates); i++) { ++ int curr_fps = ov5640_framerates[i]; ++ ++ if (abs(curr_fps - fps) < abs(best_fps - fps)) { ++ best_fps = curr_fps; ++ rate = i; ++ } ++ } ++ ++ fi->numerator = 1; ++ fi->denominator = best_fps; ++ ++find_mode: ++ mode = ov5640_find_mode(sensor, rate, width, height, false); ++ return mode ? rate : -EINVAL; ++} ++ ++static int ov5640_get_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *format) ++{ ++ struct ov5640_dev *sensor = to_ov5640_dev(sd); ++ struct v4l2_mbus_framefmt *fmt; ++ ++ if (format->pad != 0) ++ return -EINVAL; ++ ++ mutex_lock(&sensor->lock); ++ ++ if (format->which == V4L2_SUBDEV_FORMAT_TRY) ++ fmt = v4l2_subdev_get_try_format(&sensor->sd, state, ++ format->pad); ++ else ++ fmt = &sensor->fmt; ++ ++ format->format = *fmt; ++ ++ mutex_unlock(&sensor->lock); ++ ++ return 0; ++} ++ ++static int ov5640_try_fmt_internal(struct v4l2_subdev *sd, ++ struct v4l2_mbus_framefmt *fmt, ++ enum ov5640_frame_rate fr, ++ const struct ov5640_mode_info **new_mode) ++{ ++ struct ov5640_dev *sensor = to_ov5640_dev(sd); ++ const struct ov5640_mode_info *mode; ++ int i; ++ ++ mode = ov5640_find_mode(sensor, fr, fmt->width, fmt->height, true); ++ if (!mode) ++ return -EINVAL; ++ fmt->width = mode->hact; ++ fmt->height = mode->vact; ++ ++ if (new_mode) ++ *new_mode = mode; ++ ++ for (i = 0; i < ARRAY_SIZE(ov5640_formats); i++) ++ if (ov5640_formats[i].code == fmt->code) ++ break; ++ if (i >= ARRAY_SIZE(ov5640_formats)) ++ i = 0; ++ ++ fmt->code = ov5640_formats[i].code; ++ fmt->colorspace = ov5640_formats[i].colorspace; ++ 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); ++ ++ return 0; ++} ++ ++static int ov5640_set_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *format) ++{ ++ struct ov5640_dev *sensor = to_ov5640_dev(sd); ++ const struct ov5640_mode_info *new_mode; ++ struct v4l2_mbus_framefmt *mbus_fmt = &format->format; ++ struct v4l2_mbus_framefmt *fmt; ++ int ret; ++ ++ if (format->pad != 0) ++ return -EINVAL; ++ ++ mutex_lock(&sensor->lock); ++ ++ if (sensor->streaming) { ++ ret = -EBUSY; ++ goto out; ++ } ++ ++ ret = ov5640_try_fmt_internal(sd, mbus_fmt, 0, &new_mode); ++ if (ret) ++ goto out; ++ ++ if (format->which == V4L2_SUBDEV_FORMAT_TRY) ++ fmt = v4l2_subdev_get_try_format(sd, state, 0); ++ else ++ fmt = &sensor->fmt; ++ ++ if (mbus_fmt->code != sensor->fmt.code) ++ sensor->pending_fmt_change = true; ++ ++ *fmt = *mbus_fmt; ++ ++ if (new_mode != sensor->current_mode) { ++ sensor->current_mode = new_mode; ++ sensor->pending_mode_change = true; ++ } ++ if (new_mode->max_fps < sensor->current_fr) { ++ sensor->current_fr = new_mode->max_fps; ++ sensor->frame_interval.numerator = 1; ++ sensor->frame_interval.denominator = ++ ov5640_framerates[sensor->current_fr]; ++ sensor->current_mode = new_mode; ++ sensor->pending_mode_change = true; ++ } ++ ++ __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, ++ ov5640_calc_pixel_rate(sensor)); ++out: ++ mutex_unlock(&sensor->lock); ++ return ret; ++} ++ ++static int ov5640_set_framefmt(struct ov5640_dev *sensor, ++ struct v4l2_mbus_framefmt *format) ++{ ++ int ret = 0; ++ bool is_jpeg = false; ++ u8 fmt, mux; ++ ++ switch (format->code) { ++ case MEDIA_BUS_FMT_UYVY8_2X8: ++ /* YUV422, UYVY */ ++ fmt = 0x3f; ++ mux = OV5640_FMT_MUX_YUV422; ++ break; ++ case MEDIA_BUS_FMT_YUYV8_2X8: ++ /* YUV422, YUYV */ ++ fmt = 0x30; ++ mux = OV5640_FMT_MUX_YUV422; ++ break; ++ case MEDIA_BUS_FMT_RGB565_2X8_LE: ++ /* RGB565 {g[2:0],b[4:0]},{r[4:0],g[5:3]} */ ++ fmt = 0x6F; ++ mux = OV5640_FMT_MUX_RGB; ++ break; ++ case MEDIA_BUS_FMT_RGB565_2X8_BE: ++ /* RGB565 {r[4:0],g[5:3]},{g[2:0],b[4:0]} */ ++ fmt = 0x61; ++ mux = OV5640_FMT_MUX_RGB; ++ break; ++ case MEDIA_BUS_FMT_JPEG_1X8: ++ /* YUV422, YUYV */ ++ fmt = 0x30; ++ mux = OV5640_FMT_MUX_YUV422; ++ is_jpeg = true; ++ break; ++ case MEDIA_BUS_FMT_SBGGR8_1X8: ++ /* Raw, BGBG... / GRGR... */ ++ fmt = 0x00; ++ mux = OV5640_FMT_MUX_RAW_DPC; ++ break; ++ case MEDIA_BUS_FMT_SGBRG8_1X8: ++ /* Raw bayer, GBGB... / RGRG... */ ++ fmt = 0x01; ++ mux = OV5640_FMT_MUX_RAW_DPC; ++ break; ++ case MEDIA_BUS_FMT_SGRBG8_1X8: ++ /* Raw bayer, GRGR... / BGBG... */ ++ fmt = 0x02; ++ mux = OV5640_FMT_MUX_RAW_DPC; ++ break; ++ case MEDIA_BUS_FMT_SRGGB8_1X8: ++ /* Raw bayer, RGRG... / GBGB... */ ++ fmt = 0x03; ++ mux = OV5640_FMT_MUX_RAW_DPC; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ /* FORMAT CONTROL00: YUV and RGB formatting */ ++ ret = ov5640_write_reg(sensor, OV5640_REG_FORMAT_CONTROL00, fmt); ++ if (ret) ++ return ret; ++ ++ /* FORMAT MUX CONTROL: ISP YUV or RGB */ ++ ret = ov5640_write_reg(sensor, OV5640_REG_ISP_FORMAT_MUX_CTRL, mux); ++ if (ret) ++ return ret; ++ ++ /* ++ * TIMING TC REG21: ++ * - [5]: JPEG enable ++ */ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_TIMING_TC_REG21, ++ BIT(5), is_jpeg ? BIT(5) : 0); ++ if (ret) ++ return ret; ++ ++ /* ++ * SYSTEM RESET02: ++ * - [4]: Reset JFIFO ++ * - [3]: Reset SFIFO ++ * - [2]: Reset JPEG ++ */ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SYS_RESET02, ++ BIT(4) | BIT(3) | BIT(2), ++ is_jpeg ? 0 : (BIT(4) | BIT(3) | BIT(2))); ++ if (ret) ++ return ret; ++ ++ /* ++ * CLOCK ENABLE02: ++ * - [5]: Enable JPEG 2x clock ++ * - [3]: Enable JPEG clock ++ */ ++ return ov5640_mod_reg(sensor, OV5640_REG_SYS_CLOCK_ENABLE02, ++ BIT(5) | BIT(3), ++ is_jpeg ? (BIT(5) | BIT(3)) : 0); ++} ++ ++/* ++ * Sensor Controls. ++ */ ++ ++static int ov5640_set_ctrl_hue(struct ov5640_dev *sensor, int value) ++{ ++ int ret; ++ ++ if (value) { ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, ++ BIT(0), BIT(0)); ++ if (ret) ++ return ret; ++ ret = ov5640_write_reg16(sensor, OV5640_REG_SDE_CTRL1, value); ++ } else { ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, BIT(0), 0); ++ } ++ ++ return ret; ++} ++ ++static int ov5640_set_ctrl_contrast(struct ov5640_dev *sensor, int value) ++{ ++ int ret; ++ ++ if (value) { ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, ++ BIT(2), BIT(2)); ++ if (ret) ++ return ret; ++ ret = ov5640_write_reg(sensor, OV5640_REG_SDE_CTRL5, ++ value & 0xff); ++ } else { ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, BIT(2), 0); ++ } ++ ++ return ret; ++} ++ ++static int ov5640_set_ctrl_saturation(struct ov5640_dev *sensor, int value) ++{ ++ int ret; ++ ++ if (value) { ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, ++ BIT(1), BIT(1)); ++ if (ret) ++ return ret; ++ ret = ov5640_write_reg(sensor, OV5640_REG_SDE_CTRL3, ++ value & 0xff); ++ if (ret) ++ return ret; ++ ret = ov5640_write_reg(sensor, OV5640_REG_SDE_CTRL4, ++ value & 0xff); ++ } else { ++ ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, BIT(1), 0); ++ } ++ ++ return ret; ++} ++ ++static int ov5640_set_ctrl_white_balance(struct ov5640_dev *sensor, int awb) ++{ ++ int ret; ++ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_AWB_MANUAL_CTRL, ++ BIT(0), awb ? 0 : 1); ++ if (ret) ++ return ret; ++ ++ if (!awb) { ++ u16 red = (u16)sensor->ctrls.red_balance->val; ++ u16 blue = (u16)sensor->ctrls.blue_balance->val; ++ ++ ret = ov5640_write_reg16(sensor, OV5640_REG_AWB_R_GAIN, red); ++ if (ret) ++ return ret; ++ ret = ov5640_write_reg16(sensor, OV5640_REG_AWB_B_GAIN, blue); ++ } ++ ++ return ret; ++} ++ ++static int ov5640_set_ctrl_exposure(struct ov5640_dev *sensor, ++ enum v4l2_exposure_auto_type auto_exposure) ++{ ++ struct ov5640_ctrls *ctrls = &sensor->ctrls; ++ bool auto_exp = (auto_exposure == V4L2_EXPOSURE_AUTO); ++ int ret = 0; ++ ++ if (ctrls->auto_exp->is_new) { ++ ret = ov5640_set_autoexposure(sensor, auto_exp); ++ if (ret) ++ return ret; ++ } ++ ++ if (!auto_exp && ctrls->exposure->is_new) { ++ u16 max_exp; ++ ++ ret = ov5640_read_reg16(sensor, OV5640_REG_AEC_PK_VTS, ++ &max_exp); ++ if (ret) ++ return ret; ++ ret = ov5640_get_vts(sensor); ++ if (ret < 0) ++ return ret; ++ max_exp += ret; ++ ret = 0; ++ ++ if (ctrls->exposure->val < max_exp) ++ ret = ov5640_set_exposure(sensor, ctrls->exposure->val); ++ } ++ ++ return ret; ++} ++ ++static int ov5640_set_ctrl_gain(struct ov5640_dev *sensor, bool auto_gain) ++{ ++ struct ov5640_ctrls *ctrls = &sensor->ctrls; ++ int ret = 0; ++ ++ if (ctrls->auto_gain->is_new) { ++ ret = ov5640_set_autogain(sensor, auto_gain); ++ if (ret) ++ return ret; ++ } ++ ++ if (!auto_gain && ctrls->gain->is_new) ++ ret = ov5640_set_gain(sensor, ctrls->gain->val); ++ ++ return ret; ++} ++ ++static const char * const test_pattern_menu[] = { ++ "Disabled", ++ "Color bars", ++ "Color bars w/ rolling bar", ++ "Color squares", ++ "Color squares w/ rolling bar", ++}; ++ ++#define OV5640_TEST_ENABLE BIT(7) ++#define OV5640_TEST_ROLLING BIT(6) /* rolling horizontal bar */ ++#define OV5640_TEST_TRANSPARENT BIT(5) ++#define OV5640_TEST_SQUARE_BW BIT(4) /* black & white squares */ ++#define OV5640_TEST_BAR_STANDARD (0 << 2) ++#define OV5640_TEST_BAR_VERT_CHANGE_1 (1 << 2) ++#define OV5640_TEST_BAR_HOR_CHANGE (2 << 2) ++#define OV5640_TEST_BAR_VERT_CHANGE_2 (3 << 2) ++#define OV5640_TEST_BAR (0 << 0) ++#define OV5640_TEST_RANDOM (1 << 0) ++#define OV5640_TEST_SQUARE (2 << 0) ++#define OV5640_TEST_BLACK (3 << 0) ++ ++static const u8 test_pattern_val[] = { ++ 0, ++ OV5640_TEST_ENABLE | OV5640_TEST_BAR_VERT_CHANGE_1 | ++ OV5640_TEST_BAR, ++ OV5640_TEST_ENABLE | OV5640_TEST_ROLLING | ++ OV5640_TEST_BAR_VERT_CHANGE_1 | OV5640_TEST_BAR, ++ OV5640_TEST_ENABLE | OV5640_TEST_SQUARE, ++ OV5640_TEST_ENABLE | OV5640_TEST_ROLLING | OV5640_TEST_SQUARE, ++}; ++ ++static int ov5640_set_ctrl_test_pattern(struct ov5640_dev *sensor, int value) ++{ ++ return ov5640_write_reg(sensor, OV5640_REG_PRE_ISP_TEST_SET1, ++ test_pattern_val[value]); ++} ++ ++static int ov5640_set_ctrl_light_freq(struct ov5640_dev *sensor, int value) ++{ ++ int ret; ++ ++ ret = ov5640_mod_reg(sensor, OV5640_REG_HZ5060_CTRL01, BIT(7), ++ (value == V4L2_CID_POWER_LINE_FREQUENCY_AUTO) ? ++ 0 : BIT(7)); ++ if (ret) ++ return ret; ++ ++ return ov5640_mod_reg(sensor, OV5640_REG_HZ5060_CTRL00, BIT(2), ++ (value == V4L2_CID_POWER_LINE_FREQUENCY_50HZ) ? ++ BIT(2) : 0); ++} ++ ++static int ov5640_set_ctrl_hflip(struct ov5640_dev *sensor, int value) ++{ ++ /* ++ * If sensor is mounted upside down, mirror logic is inversed. ++ * ++ * Sensor is a BSI (Back Side Illuminated) one, ++ * so image captured is physically mirrored. ++ * This is why mirror logic is inversed in ++ * order to cancel this mirror effect. ++ */ ++ ++ /* ++ * TIMING TC REG21: ++ * - [2]: ISP mirror ++ * - [1]: Sensor mirror ++ */ ++ return ov5640_mod_reg(sensor, OV5640_REG_TIMING_TC_REG21, ++ BIT(2) | BIT(1), ++ (!(value ^ sensor->upside_down)) ? ++ (BIT(2) | BIT(1)) : 0); ++} ++ ++static int ov5640_set_ctrl_vflip(struct ov5640_dev *sensor, int value) ++{ ++ /* If sensor is mounted upside down, flip logic is inversed */ ++ ++ /* ++ * TIMING TC REG20: ++ * - [2]: ISP vflip ++ * - [1]: Sensor vflip ++ */ ++ return ov5640_mod_reg(sensor, OV5640_REG_TIMING_TC_REG20, ++ BIT(2) | BIT(1), ++ (value ^ sensor->upside_down) ? ++ (BIT(2) | BIT(1)) : 0); ++} ++ ++static int ov5640_g_volatile_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct v4l2_subdev *sd = ctrl_to_sd(ctrl); ++ struct ov5640_dev *sensor = to_ov5640_dev(sd); ++ int val; ++ ++ /* v4l2_ctrl_lock() locks our own mutex */ ++ ++ switch (ctrl->id) { ++ case V4L2_CID_AUTOGAIN: ++ val = ov5640_get_gain(sensor); ++ if (val < 0) ++ return val; ++ sensor->ctrls.gain->val = val; ++ break; ++ case V4L2_CID_EXPOSURE_AUTO: ++ val = ov5640_get_exposure(sensor); ++ if (val < 0) ++ return val; ++ sensor->ctrls.exposure->val = val; ++ break; ++ } ++ ++ return 0; ++} ++ ++static int ov5640_s_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct v4l2_subdev *sd = ctrl_to_sd(ctrl); ++ struct ov5640_dev *sensor = to_ov5640_dev(sd); ++ int ret; ++ ++ /* v4l2_ctrl_lock() locks our own mutex */ ++ ++ /* ++ * If the device is not powered up by the host driver do ++ * not apply any controls to H/W at this time. Instead ++ * the controls will be restored right after power-up. ++ */ ++ if (sensor->power_count == 0) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_AUTOGAIN: ++ ret = ov5640_set_ctrl_gain(sensor, ctrl->val); ++ break; ++ case V4L2_CID_EXPOSURE_AUTO: ++ ret = ov5640_set_ctrl_exposure(sensor, ctrl->val); ++ break; ++ case V4L2_CID_AUTO_WHITE_BALANCE: ++ ret = ov5640_set_ctrl_white_balance(sensor, ctrl->val); ++ break; ++ case V4L2_CID_HUE: ++ ret = ov5640_set_ctrl_hue(sensor, ctrl->val); ++ break; ++ case V4L2_CID_CONTRAST: ++ ret = ov5640_set_ctrl_contrast(sensor, ctrl->val); ++ break; ++ case V4L2_CID_SATURATION: ++ ret = ov5640_set_ctrl_saturation(sensor, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN: ++ ret = ov5640_set_ctrl_test_pattern(sensor, ctrl->val); ++ break; ++ case V4L2_CID_POWER_LINE_FREQUENCY: ++ ret = ov5640_set_ctrl_light_freq(sensor, ctrl->val); ++ break; ++ case V4L2_CID_HFLIP: ++ ret = ov5640_set_ctrl_hflip(sensor, ctrl->val); ++ break; ++ case V4L2_CID_VFLIP: ++ ret = ov5640_set_ctrl_vflip(sensor, ctrl->val); ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops ov5640_ctrl_ops = { ++ .g_volatile_ctrl = ov5640_g_volatile_ctrl, ++ .s_ctrl = ov5640_s_ctrl, ++}; ++ ++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; ++ int ret; ++ ++ v4l2_ctrl_handler_init(hdl, 32); ++ ++ /* we can use our own mutex for the ctrl lock */ ++ hdl->lock = &sensor->lock; ++ ++ /* Clock related controls */ ++ ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, ++ 0, INT_MAX, 1, ++ ov5640_calc_pixel_rate(sensor)); ++ ++ /* Auto/manual white balance */ ++ ctrls->auto_wb = v4l2_ctrl_new_std(hdl, ops, ++ V4L2_CID_AUTO_WHITE_BALANCE, ++ 0, 1, 1, 1); ++ ctrls->blue_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BLUE_BALANCE, ++ 0, 4095, 1, 0); ++ ctrls->red_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE, ++ 0, 4095, 1, 0); ++ /* Auto/manual exposure */ ++ ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, ++ V4L2_CID_EXPOSURE_AUTO, ++ V4L2_EXPOSURE_MANUAL, 0, ++ V4L2_EXPOSURE_AUTO); ++ ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, ++ 0, 65535, 1, 0); ++ /* Auto/manual gain */ ++ ctrls->auto_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTOGAIN, ++ 0, 1, 1, 1); ++ ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, ++ 0, 1023, 1, 0); ++ ++ ctrls->saturation = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, ++ 0, 255, 1, 64); ++ ctrls->hue = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HUE, ++ 0, 359, 1, 0); ++ ctrls->contrast = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, ++ 0, 255, 1, 0); ++ ctrls->test_pattern = ++ v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(test_pattern_menu) - 1, ++ 0, 0, test_pattern_menu); ++ ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, ++ 0, 1, 1, 0); ++ ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, ++ 0, 1, 1, 0); ++ ++ ctrls->light_freq = ++ v4l2_ctrl_new_std_menu(hdl, ops, ++ V4L2_CID_POWER_LINE_FREQUENCY, ++ V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, ++ V4L2_CID_POWER_LINE_FREQUENCY_50HZ); ++ ++ if (hdl->error) { ++ ret = hdl->error; ++ goto free_ctrls; ++ } ++ ++ ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ctrls->gain->flags |= V4L2_CTRL_FLAG_VOLATILE; ++ ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; ++ ++ v4l2_ctrl_auto_cluster(3, &ctrls->auto_wb, 0, false); ++ v4l2_ctrl_auto_cluster(2, &ctrls->auto_gain, 0, true); ++ v4l2_ctrl_auto_cluster(2, &ctrls->auto_exp, 1, true); ++ ++ sensor->sd.ctrl_handler = hdl; ++ return 0; ++ ++free_ctrls: ++ v4l2_ctrl_handler_free(hdl); ++ return ret; ++} ++ ++static int ov5640_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ if (fse->pad != 0) ++ return -EINVAL; ++ if (fse->index >= OV5640_NUM_MODES) ++ return -EINVAL; ++ ++ fse->min_width = ++ ov5640_mode_data[fse->index].hact; ++ fse->max_width = fse->min_width; ++ fse->min_height = ++ ov5640_mode_data[fse->index].vact; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static int ov5640_enum_frame_interval( ++ struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_interval_enum *fie) ++{ ++ struct ov5640_dev *sensor = to_ov5640_dev(sd); ++ struct v4l2_fract tpf; ++ int ret; ++ ++ if (fie->pad != 0) ++ return -EINVAL; ++ if (fie->index >= OV5640_NUM_FRAMERATES) ++ return -EINVAL; ++ ++ tpf.numerator = 1; ++ tpf.denominator = ov5640_framerates[fie->index]; ++ ++ ret = ov5640_try_frame_interval(sensor, &tpf, ++ fie->width, fie->height); ++ if (ret < 0) ++ return -EINVAL; ++ ++ fie->interval = tpf; ++ return 0; ++} ++ ++static int ov5640_g_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_frame_interval *fi) ++{ ++ struct ov5640_dev *sensor = to_ov5640_dev(sd); ++ ++ mutex_lock(&sensor->lock); ++ fi->interval = sensor->frame_interval; ++ mutex_unlock(&sensor->lock); ++ ++ return 0; ++} ++ ++static int ov5640_s_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_frame_interval *fi) ++{ ++ struct ov5640_dev *sensor = to_ov5640_dev(sd); ++ const struct ov5640_mode_info *mode; ++ int frame_rate, ret = 0; ++ ++ if (fi->pad != 0) ++ return -EINVAL; ++ ++ mutex_lock(&sensor->lock); ++ ++ if (sensor->streaming) { ++ ret = -EBUSY; ++ goto out; ++ } ++ ++ mode = sensor->current_mode; ++ ++ frame_rate = ov5640_try_frame_interval(sensor, &fi->interval, ++ mode->hact, mode->vact); ++ if (frame_rate < 0) { ++ /* Always return a valid frame interval value */ ++ fi->interval = sensor->frame_interval; ++ goto out; ++ } ++ ++ mode = ov5640_find_mode(sensor, frame_rate, mode->hact, ++ mode->vact, true); ++ if (!mode) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ if (mode != sensor->current_mode || ++ frame_rate != sensor->current_fr) { ++ 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)); ++ } ++out: ++ mutex_unlock(&sensor->lock); ++ return ret; ++} ++ ++static int ov5640_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ if (code->pad != 0) ++ return -EINVAL; ++ if (code->index >= ARRAY_SIZE(ov5640_formats)) ++ return -EINVAL; ++ ++ code->code = ov5640_formats[code->index].code; ++ return 0; ++} ++ ++static int ov5640_s_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct ov5640_dev *sensor = to_ov5640_dev(sd); ++ int ret = 0; ++ ++ mutex_lock(&sensor->lock); ++ ++ if (sensor->streaming == !enable) { ++ if (enable && sensor->pending_mode_change) { ++ ret = ov5640_set_mode(sensor); ++ if (ret) ++ goto out; ++ } ++ ++ if (enable && sensor->pending_fmt_change) { ++ ret = ov5640_set_framefmt(sensor, &sensor->fmt); ++ if (ret) ++ goto out; ++ sensor->pending_fmt_change = false; ++ } ++ ++ if (sensor->ep.bus_type == V4L2_MBUS_CSI2_DPHY) ++ ret = ov5640_set_stream_mipi(sensor, enable); ++ else ++ ret = ov5640_set_stream_dvp(sensor, enable); ++ ++ if (ret) ++ goto out; ++ } ++ sensor->streaming += enable ? 1 : -1; ++ WARN_ON(sensor->streaming < 0); ++out: ++ mutex_unlock(&sensor->lock); ++ return ret; ++} ++ ++int ov5640_skip_frames(struct v4l2_subdev *sd, u32 *frames) ++{ ++ *frames = OV5640_SKIP_FRAMES; ++ return 0; ++} ++ ++static const struct v4l2_subdev_core_ops ov5640_core_ops = { ++ .s_power = ov5640_s_power, ++ .log_status = v4l2_ctrl_subdev_log_status, ++ .subscribe_event = v4l2_ctrl_subdev_subscribe_event, ++ .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++}; ++ ++static const struct v4l2_subdev_video_ops ov5640_video_ops = { ++ .g_frame_interval = ov5640_g_frame_interval, ++ .s_frame_interval = ov5640_s_frame_interval, ++ .s_stream = ov5640_s_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops ov5640_pad_ops = { ++ .enum_mbus_code = ov5640_enum_mbus_code, ++ .get_fmt = ov5640_get_fmt, ++ .set_fmt = ov5640_set_fmt, ++ .enum_frame_size = ov5640_enum_frame_size, ++ .enum_frame_interval = ov5640_enum_frame_interval, ++}; ++ ++static const struct v4l2_subdev_sensor_ops ov5640_sensor_ops = { ++ .g_skip_frames = ov5640_skip_frames, ++}; ++ ++static const struct v4l2_subdev_ops ov5640_subdev_ops = { ++ .core = &ov5640_core_ops, ++ .video = &ov5640_video_ops, ++ .pad = &ov5640_pad_ops, ++ .sensor = &ov5640_sensor_ops, ++}; ++ ++static int ov5640_get_regulators(struct ov5640_dev *sensor) ++{ ++ int i; ++ ++ for (i = 0; i < OV5640_NUM_SUPPLIES; i++) ++ sensor->supplies[i].supply = ov5640_supply_name[i]; ++ ++ return devm_regulator_bulk_get(&sensor->i2c_client->dev, ++ OV5640_NUM_SUPPLIES, ++ sensor->supplies); ++} ++ ++static int ov5640_check_chip_id(struct ov5640_dev *sensor) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ int ret = 0; ++ u16 chip_id; ++ ++ ret = ov5640_set_power_on(sensor); ++ if (ret) ++ return ret; ++ ++ ret = ov5640_read_reg16(sensor, OV5640_REG_CHIP_ID, &chip_id); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to read chip identifier\n", ++ __func__); ++ goto power_off; ++ } ++ ++ if (chip_id != OV5640_CHIP_ID) { ++ dev_err(&client->dev, "%s: wrong chip identifier, expected 0x%x, got 0x%x\n", ++ __func__, OV5640_CHIP_ID, chip_id); ++ ret = -ENXIO; ++ } ++ dev_err(&client->dev, "%s: chip identifier, got 0x%x\n", ++ __func__, chip_id); ++ ++power_off: ++ ov5640_set_power_off(sensor); ++ return ret; ++} ++ ++static int ov5640_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct fwnode_handle *endpoint; ++ struct ov5640_dev *sensor; ++ struct v4l2_mbus_framefmt *fmt; ++ u32 rotation; ++ int ret; ++ ++ sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); ++ if (!sensor) ++ return -ENOMEM; ++ ++ sensor->i2c_client = client; ++ ++ /* ++ * default init sequence initialize sensor to ++ * YUV422 UYVY VGA@30fps ++ */ ++ fmt = &sensor->fmt; ++ fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; ++ fmt->colorspace = V4L2_COLORSPACE_SRGB; ++ 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; ++ ++ /* optional indication of physical rotation of sensor */ ++ ret = fwnode_property_read_u32(dev_fwnode(&client->dev), "rotation", ++ &rotation); ++ if (!ret) { ++ switch (rotation) { ++ case 180: ++ sensor->upside_down = true; ++ fallthrough; ++ case 0: ++ break; ++ default: ++ dev_warn(dev, "%u degrees rotation is not supported, ignoring...\n", ++ rotation); ++ } ++ } ++ ++ endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), ++ NULL); ++ if (!endpoint) { ++ dev_err(dev, "endpoint node not found\n"); ++ return -EINVAL; ++ } ++ ++ ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep); ++ fwnode_handle_put(endpoint); ++ if (ret) { ++ dev_err(dev, "Could not parse endpoint\n"); ++ return ret; ++ } ++ ++ if (sensor->ep.bus_type != V4L2_MBUS_PARALLEL && ++ sensor->ep.bus_type != V4L2_MBUS_CSI2_DPHY && ++ sensor->ep.bus_type != V4L2_MBUS_BT656) { ++ dev_err(dev, "Unsupported bus type %d\n", sensor->ep.bus_type); ++ return -EINVAL; ++ } ++ ++ /* get system clock (xclk) */ ++ sensor->xclk = devm_clk_get(dev, "xclk"); ++ if (IS_ERR(sensor->xclk)) { ++ dev_err(dev, "failed to get xclk\n"); ++ return PTR_ERR(sensor->xclk); ++ } ++ ++ sensor->xclk_freq = clk_get_rate(sensor->xclk); ++ if (sensor->xclk_freq < OV5640_XCLK_MIN || ++ sensor->xclk_freq > OV5640_XCLK_MAX) { ++ dev_err(dev, "xclk frequency out of range: %d Hz\n", ++ sensor->xclk_freq); ++ return -EINVAL; ++ } ++ ++ /* request optional power down pin */ ++ sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(sensor->pwdn_gpio)) ++ return PTR_ERR(sensor->pwdn_gpio); ++ ++ /* request optional reset pin */ ++ sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(sensor->reset_gpio)) ++ return PTR_ERR(sensor->reset_gpio); ++ ++ v4l2_i2c_subdev_init(&sensor->sd, client, &ov5640_subdev_ops); ++ ++ sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | ++ V4L2_SUBDEV_FL_HAS_EVENTS; ++ sensor->pad.flags = MEDIA_PAD_FL_SOURCE; ++ sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); ++ if (ret) ++ return ret; ++ ++ ret = ov5640_get_regulators(sensor); ++ if (ret) ++ return ret; ++ ++ mutex_init(&sensor->lock); ++ ++ ret = ov5640_check_chip_id(sensor); ++ if (ret) ++ goto entity_cleanup; ++ ++ ret = ov5640_init_controls(sensor); ++ if (ret) ++ goto entity_cleanup; ++ ++ ret = v4l2_async_register_subdev_sensor(&sensor->sd); ++ if (ret) ++ goto free_ctrls; ++ ++ return 0; ++ ++free_ctrls: ++ v4l2_ctrl_handler_free(&sensor->ctrls.handler); ++entity_cleanup: ++ media_entity_cleanup(&sensor->sd.entity); ++ mutex_destroy(&sensor->lock); ++ return ret; ++} ++ ++static int ov5640_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov5640_dev *sensor = to_ov5640_dev(sd); ++ ++ v4l2_async_unregister_subdev(&sensor->sd); ++ media_entity_cleanup(&sensor->sd.entity); ++ v4l2_ctrl_handler_free(&sensor->ctrls.handler); ++ mutex_destroy(&sensor->lock); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id ov5640_id[] = { ++ {"ov5640", 0}, ++ {}, ++}; ++MODULE_DEVICE_TABLE(i2c, ov5640_id); ++ ++static const struct of_device_id ov5640_dt_ids[] = { ++ { .compatible = "ovti,ov5640" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, ov5640_dt_ids); ++ ++static struct i2c_driver ov5640_i2c_driver = { ++ .driver = { ++ .name = "ov5640", ++ .of_match_table = ov5640_dt_ids, ++ }, ++ .id_table = ov5640_id, ++ .probe_new = ov5640_probe, ++ .remove = ov5640_remove, ++}; ++ ++module_i2c_driver(ov5640_i2c_driver); ++ ++MODULE_DESCRIPTION("OV5640 MIPI Camera Subdev Driver"); ++MODULE_LICENSE("GPL"); +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/sc2235.c +@@ -0,0 +1,1914 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include <linux/clk.h> ++#include <linux/clk-provider.h> ++#include <linux/clkdev.h> ++#include <linux/ctype.h> ++#include <linux/delay.h> ++#include <linux/device.h> ++#include <linux/gpio/consumer.h> ++#include <linux/i2c.h> ++#include <linux/init.h> ++#include <linux/module.h> ++#include <linux/pm_runtime.h> ++#include <linux/of_device.h> ++#include <linux/regulator/consumer.h> ++#include <linux/slab.h> ++#include <linux/types.h> ++#include <media/v4l2-async.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-event.h> ++#include <media/v4l2-fwnode.h> ++#include <media/v4l2-subdev.h> ++#include "stfcamss.h" ++ ++/* min/typical/max system clock (xclk) frequencies */ ++#define SC2235_XCLK_MIN 6000000 ++#define SC2235_XCLK_MAX 27000000 ++ ++#define SC2235_CHIP_ID (0x2235) ++ ++#define SC2235_REG_CHIP_ID 0x3107 ++#define SC2235_REG_AEC_PK_MANUAL 0x3e03 ++#define SC2235_REG_AEC_PK_EXPOSURE_HI 0x3e01 ++#define SC2235_REG_AEC_PK_EXPOSURE_LO 0x3e02 ++#define SC2235_REG_AEC_PK_REAL_GAIN 0x3e08 ++#define SC2235_REG_TIMING_HTS 0x320c ++#define SC2235_REG_TIMING_VTS 0x320e ++#define SC2235_REG_TEST_SET0 0x4501 ++#define SC2235_REG_TEST_SET1 0x3902 ++#define SC2235_REG_TIMING_TC_REG21 0x3221 ++#define SC2235_REG_SC_PLL_CTRL0 0x3039 ++#define SC2235_REG_SC_PLL_CTRL1 0x303a ++#define SC2235_REG_STREAM_ON 0x0100 ++ ++enum sc2235_mode_id { ++ SC2235_MODE_1080P_1920_1080 = 0, ++ SC2235_NUM_MODES, ++}; ++ ++enum sc2235_frame_rate { ++ SC2235_15_FPS = 0, ++ SC2235_30_FPS, ++ SC2235_NUM_FRAMERATES, ++}; ++ ++struct sc2235_pixfmt { ++ u32 code; ++ u32 colorspace; ++}; ++ ++static const struct sc2235_pixfmt sc2235_formats[] = { ++ { MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_COLORSPACE_SRGB, }, ++}; ++ ++static const int sc2235_framerates[] = { ++ [SC2235_15_FPS] = 15, ++ [SC2235_30_FPS] = 30, ++}; ++ ++/* regulator supplies */ ++static const char * const sc2235_supply_name[] = { ++ "DOVDD", /* Digital I/O (1.8V) supply */ ++ "AVDD", /* Analog (2.8V) supply */ ++ "DVDD", /* Digital Core (1.5V) supply */ ++}; ++ ++#define SC2235_NUM_SUPPLIES ARRAY_SIZE(sc2235_supply_name) ++ ++struct reg_value { ++ u16 reg_addr; ++ u8 val; ++ u8 mask; ++ u32 delay_ms; ++}; ++ ++struct sc2235_mode_info { ++ enum sc2235_mode_id id; ++ u32 hact; ++ u32 htot; ++ u32 vact; ++ u32 vtot; ++ const struct reg_value *reg_data; ++ u32 reg_data_size; ++ u32 max_fps; ++}; ++ ++struct sc2235_ctrls { ++ struct v4l2_ctrl_handler handler; ++ struct v4l2_ctrl *pixel_rate; ++ struct { ++ struct v4l2_ctrl *auto_exp; ++ struct v4l2_ctrl *exposure; ++ }; ++ struct { ++ struct v4l2_ctrl *auto_wb; ++ struct v4l2_ctrl *blue_balance; ++ struct v4l2_ctrl *red_balance; ++ }; ++ struct { ++ struct v4l2_ctrl *auto_gain; ++ struct v4l2_ctrl *gain; ++ }; ++ struct v4l2_ctrl *brightness; ++ struct v4l2_ctrl *light_freq; ++ struct v4l2_ctrl *saturation; ++ struct v4l2_ctrl *contrast; ++ struct v4l2_ctrl *hue; ++ struct v4l2_ctrl *test_pattern; ++ struct v4l2_ctrl *hflip; ++ struct v4l2_ctrl *vflip; ++}; ++ ++struct sc2235_dev { ++ struct i2c_client *i2c_client; ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ struct v4l2_fwnode_endpoint ep; /* the parsed DT endpoint info */ ++ struct clk *xclk; /* system clock to SC2235 */ ++ u32 xclk_freq; ++ ++ struct regulator_bulk_data supplies[SC2235_NUM_SUPPLIES]; ++ struct gpio_desc *reset_gpio; ++ struct gpio_desc *pwdn_gpio; ++ bool upside_down; ++ ++ /* lock to protect all members below */ ++ struct mutex lock; ++ ++ struct v4l2_mbus_framefmt fmt; ++ bool pending_fmt_change; ++ ++ const struct sc2235_mode_info *current_mode; ++ const struct sc2235_mode_info *last_mode; ++ enum sc2235_frame_rate current_fr; ++ struct v4l2_fract frame_interval; ++ ++ struct sc2235_ctrls ctrls; ++ ++ bool pending_mode_change; ++ int streaming; ++}; ++ ++static inline struct sc2235_dev *to_sc2235_dev(struct v4l2_subdev *sd) ++{ ++ return container_of(sd, struct sc2235_dev, sd); ++} ++ ++static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) ++{ ++ return &container_of(ctrl->handler, struct sc2235_dev, ++ ctrls.handler)->sd; ++} ++ ++/* sc2235 initial register 30fps*/ ++static struct reg_value sc2235_init_regs_tbl_1080[] = { ++ {0x0103, 0x01, 0, 50}, ++ {0x0100, 0x00, 0, 0}, ++ {0x3039, 0x80, 0, 0}, ++ {0x3621, 0x28, 0, 0}, ++ ++ {0x3309, 0x60, 0, 0}, ++ {0x331f, 0x4d, 0, 0}, ++ {0x3321, 0x4f, 0, 0}, ++ {0x33b5, 0x10, 0, 0}, ++ ++ {0x3303, 0x20, 0, 0}, ++ {0x331e, 0x0d, 0, 0}, ++ {0x3320, 0x0f, 0, 0}, ++ ++ {0x3622, 0x02, 0, 0}, ++ {0x3633, 0x42, 0, 0}, ++ {0x3634, 0x42, 0, 0}, ++ ++ {0x3306, 0x66, 0, 0}, ++ {0x330b, 0xd1, 0, 0}, ++ ++ {0x3301, 0x0e, 0, 0}, ++ ++ {0x320c, 0x08, 0, 0}, ++ {0x320d, 0x98, 0, 0}, ++ ++ {0x3364, 0x05, 0, 0}, // [2] 1: write at sampling ending ++ ++ {0x363c, 0x28, 0, 0}, //bypass nvdd ++ {0x363b, 0x0a, 0, 0}, //HVDD ++ {0x3635, 0xa0, 0, 0}, //TXVDD ++ ++ {0x4500, 0x59, 0, 0}, ++ {0x3d08, 0x00, 0, 0}, ++ {0x3908, 0x11, 0, 0}, ++ ++ {0x363c, 0x08, 0, 0}, ++ ++ {0x3e03, 0x03, 0, 0}, ++ {0x3e01, 0x46, 0, 0}, ++ ++ //0703 ++ {0x3381, 0x0a, 0, 0}, ++ {0x3348, 0x09, 0, 0}, ++ {0x3349, 0x50, 0, 0}, ++ {0x334a, 0x02, 0, 0}, ++ {0x334b, 0x60, 0, 0}, ++ ++ {0x3380, 0x04, 0, 0}, ++ {0x3340, 0x06, 0, 0}, ++ {0x3341, 0x50, 0, 0}, ++ {0x3342, 0x02, 0, 0}, ++ {0x3343, 0x60, 0, 0}, ++ ++ //0707 ++ ++ {0x3632, 0x88, 0, 0}, //anti sm ++ {0x3309, 0xa0, 0, 0}, ++ {0x331f, 0x8d, 0, 0}, ++ {0x3321, 0x8f, 0, 0}, ++ ++ {0x335e, 0x01, 0, 0}, //ana dithering ++ {0x335f, 0x03, 0, 0}, ++ {0x337c, 0x04, 0, 0}, ++ {0x337d, 0x06, 0, 0}, ++ {0x33a0, 0x05, 0, 0}, ++ {0x3301, 0x05, 0, 0}, ++ ++ {0x337f, 0x03, 0, 0}, ++ {0x3368, 0x02, 0, 0}, ++ {0x3369, 0x00, 0, 0}, ++ {0x336a, 0x00, 0, 0}, ++ {0x336b, 0x00, 0, 0}, ++ {0x3367, 0x08, 0, 0}, ++ {0x330e, 0x30, 0, 0}, ++ ++ {0x3366, 0x7c, 0, 0}, // div_rst gap ++ ++ {0x3635, 0xc1, 0, 0}, ++ {0x363b, 0x09, 0, 0}, ++ {0x363c, 0x07, 0, 0}, ++ ++ {0x391e, 0x00, 0, 0}, ++ ++ {0x3637, 0x14, 0, 0}, //fullwell 7K ++ ++ {0x3306, 0x54, 0, 0}, ++ {0x330b, 0xd8, 0, 0}, ++ {0x366e, 0x08, 0, 0}, // ofs auto en [3] ++ {0x366f, 0x2f, 0, 0}, ++ ++ {0x3631, 0x84, 0, 0}, ++ {0x3630, 0x48, 0, 0}, ++ {0x3622, 0x06, 0, 0}, ++ ++ //ramp by sc ++ {0x3638, 0x1f, 0, 0}, ++ {0x3625, 0x02, 0, 0}, ++ {0x3636, 0x24, 0, 0}, ++ ++ //0714 ++ {0x3348, 0x08, 0, 0}, ++ {0x3e03, 0x0b, 0, 0}, ++ ++ //7.17 fpn ++ {0x3342, 0x03, 0, 0}, ++ {0x3343, 0xa0, 0, 0}, ++ {0x334a, 0x03, 0, 0}, ++ {0x334b, 0xa0, 0, 0}, ++ ++ //0718 ++ {0x3343, 0xb0, 0, 0}, ++ {0x334b, 0xb0, 0, 0}, ++ ++ //0720 ++ //digital ctrl ++ {0x3802, 0x01, 0, 0}, ++ {0x3235, 0x04, 0, 0}, ++ {0x3236, 0x63, 0, 0}, // vts-2 ++ ++ //fpn ++ {0x3343, 0xd0, 0, 0}, ++ {0x334b, 0xd0, 0, 0}, ++ {0x3348, 0x07, 0, 0}, ++ {0x3349, 0x80, 0, 0}, ++ ++ //0724 ++ {0x391b, 0x4d, 0, 0}, ++ ++ {0x3342, 0x04, 0, 0}, ++ {0x3343, 0x20, 0, 0}, ++ {0x334a, 0x04, 0, 0}, ++ {0x334b, 0x20, 0, 0}, ++ ++ //0804 ++ {0x3222, 0x29, 0, 0}, ++ {0x3901, 0x02, 0, 0}, ++ ++ //0808 ++ ++ // auto blc ++ {0x3900, 0xD5, 0, 0}, // Bit[0]: blc_enable ++ {0x3902, 0x45, 0, 0}, // Bit[6]: blc_auto_en ++ ++ // blc target ++ {0x3907, 0x00, 0, 0}, ++ {0x3908, 0x00, 0, 0}, ++ ++ // auto dpc ++ {0x5000, 0x00, 0, 0}, // Bit[2]: white dead pixel cancel enable, Bit[1]: black dead pixel cancel enable ++ ++ //digital ctrl ++ {0x3f00, 0x07, 0, 0}, // bit[2] = 1 ++ {0x3f04, 0x08, 0, 0}, ++ {0x3f05, 0x74, 0, 0}, // hts - { 0x24 ++ ++ //0809 ++ {0x330b, 0xc8, 0, 0}, ++ ++ //0817 ++ {0x3306, 0x4a, 0, 0}, ++ {0x330b, 0xca, 0, 0}, ++ {0x3639, 0x09, 0, 0}, ++ ++ //manual DPC ++ {0x5780, 0xff, 0, 0}, ++ {0x5781, 0x04, 0, 0}, ++ {0x5785, 0x18, 0, 0}, ++ ++ //0822 ++ {0x3039, 0x35, 0, 0}, //fps ++ {0x303a, 0x2e, 0, 0}, ++ {0x3034, 0x05, 0, 0}, ++ {0x3035, 0x2a, 0, 0}, ++ ++ {0x320c, 0x08, 0, 0}, ++ {0x320d, 0xca, 0, 0}, ++ {0x320e, 0x04, 0, 0}, ++ {0x320f, 0xb0, 0, 0}, ++ ++ {0x3f04, 0x08, 0, 0}, ++ {0x3f05, 0xa6, 0, 0}, // hts - { 0x24 ++ ++ {0x3235, 0x04, 0, 0}, ++ {0x3236, 0xae, 0, 0}, // vts-2 ++ ++ //0825 ++ {0x3313, 0x05, 0, 0}, ++ {0x3678, 0x42, 0, 0}, ++ ++ //for AE control per frame ++ {0x3670, 0x00, 0, 0}, ++ {0x3633, 0x42, 0, 0}, ++ ++ {0x3802, 0x00, 0, 0}, ++ ++ //20180126 ++ {0x3677, 0x3f, 0, 0}, ++ {0x3306, 0x44, 0, 0}, //20180126[3c },4a] ++ {0x330b, 0xca, 0, 0}, //20180126[c2 },d3] ++ ++ //20180202 ++ {0x3237, 0x08, 0, 0}, ++ {0x3238, 0x9a, 0, 0}, //hts-0x30 ++ ++ //20180417 ++ {0x3640, 0x01, 0, 0}, ++ {0x3641, 0x02, 0, 0}, ++ ++ {0x3301, 0x12, 0, 0}, //[8 },15]20180126 ++ {0x3631, 0x84, 0, 0}, ++ {0x366f, 0x2f, 0, 0}, ++ {0x3622, 0xc6, 0, 0}, //20180117 ++ ++ {0x3e03, 0x03, 0, 0}, // Bit[3]: AGC table mapping method, Bit[1]: AGC manual, BIt[0]: AEC manual ++ ++ // {0x0100, 0x00, 0, 0}, ++ // {0x4501, 0xc8, 0, 0}, //bar testing ++ // {0x3902, 0x45, 0, 0}, ++}; ++ ++static struct reg_value sc2235_setting_1080P_1920_1080[] = { ++ ++}; ++ ++/* power-on sensor init reg table */ ++static const struct sc2235_mode_info sc2235_mode_init_data = { ++ SC2235_MODE_1080P_1920_1080, ++ 1920, 0x8ca, 1080, 0x4b0, ++ sc2235_init_regs_tbl_1080, ++ ARRAY_SIZE(sc2235_init_regs_tbl_1080), ++ SC2235_30_FPS, ++}; ++ ++static const struct sc2235_mode_info ++sc2235_mode_data[SC2235_NUM_MODES] = { ++ {SC2235_MODE_1080P_1920_1080, ++ 1920, 0x8ca, 1080, 0x4b0, ++ sc2235_setting_1080P_1920_1080, ++ ARRAY_SIZE(sc2235_setting_1080P_1920_1080), ++ SC2235_30_FPS}, ++}; ++ ++static int sc2235_write_reg(struct sc2235_dev *sensor, u16 reg, u8 val) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ struct i2c_msg msg; ++ u8 buf[3]; ++ int ret; ++ ++ buf[0] = reg >> 8; ++ buf[1] = reg & 0xff; ++ buf[2] = val; ++ ++ msg.addr = client->addr; ++ msg.flags = client->flags; ++ msg.buf = buf; ++ msg.len = sizeof(buf); ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ if (ret < 0) { ++ dev_err(&client->dev, "%s: error: reg=%x, val=%x\n", ++ __func__, reg, val); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int sc2235_read_reg(struct sc2235_dev *sensor, u16 reg, u8 *val) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ struct i2c_msg msg[2]; ++ u8 buf[2]; ++ int ret; ++ ++ buf[0] = reg >> 8; ++ buf[1] = reg & 0xff; ++ ++ msg[0].addr = client->addr; ++ msg[0].flags = client->flags; ++ msg[0].buf = buf; ++ msg[0].len = sizeof(buf); ++ ++ msg[1].addr = client->addr; ++ msg[1].flags = client->flags | I2C_M_RD; ++ msg[1].buf = buf; ++ msg[1].len = 1; ++ ++ ret = i2c_transfer(client->adapter, msg, 2); ++ if (ret < 0) { ++ dev_err(&client->dev, "%s: error: reg=%x\n", ++ __func__, reg); ++ return ret; ++ } ++ ++ *val = buf[0]; ++ return 0; ++} ++ ++static int sc2235_read_reg16(struct sc2235_dev *sensor, u16 reg, u16 *val) ++{ ++ u8 hi, lo; ++ int ret; ++ ++ ret = sc2235_read_reg(sensor, reg, &hi); ++ if (ret) ++ return ret; ++ ret = sc2235_read_reg(sensor, reg + 1, &lo); ++ if (ret) ++ return ret; ++ ++ *val = ((u16)hi << 8) | (u16)lo; ++ return 0; ++} ++ ++static int sc2235_write_reg16(struct sc2235_dev *sensor, u16 reg, u16 val) ++{ ++ int ret; ++ ++ ret = sc2235_write_reg(sensor, reg, val >> 8); ++ if (ret) ++ return ret; ++ ++ return sc2235_write_reg(sensor, reg + 1, val & 0xff); ++} ++ ++static int sc2235_mod_reg(struct sc2235_dev *sensor, u16 reg, ++ u8 mask, u8 val) ++{ ++ u8 readval; ++ int ret; ++ ++ ret = sc2235_read_reg(sensor, reg, &readval); ++ if (ret) ++ return ret; ++ ++ readval &= ~mask; ++ val &= mask; ++ val |= readval; ++ ++ return sc2235_write_reg(sensor, reg, val); ++} ++ ++#define SC2235_PLL_PREDIV 3 ++ ++#define SC2235_SYSDIV_MIN 0 ++#define SC2235_SYSDIV_MAX 7 ++ ++#define SC2235_PLL_MULT_MIN 0 ++#define SC2235_PLL_MULT_MAX 63 ++ ++#ifdef UNUSED_CODE ++static unsigned long sc2235_compute_sys_clk(struct sc2235_dev *sensor, ++ u8 pll_pre, u8 pll_mult, ++ u8 sysdiv) ++{ ++ unsigned long sysclk = ++ sensor->xclk_freq * (64 - pll_mult) / (pll_pre * (sysdiv + 1)); ++ ++ /* PLL1 output cannot exceed 1GHz. */ ++ if (sysclk / 1000000 > 1000) ++ return 0; ++ ++ return sysclk; ++} ++ ++static unsigned long sc2235_calc_sys_clk(struct sc2235_dev *sensor, ++ unsigned long rate, ++ u8 *pll_prediv, u8 *pll_mult, ++ u8 *sysdiv) ++{ ++ unsigned long best = ~0; ++ u8 best_sysdiv = 1, best_mult = 1; ++ u8 _sysdiv, _pll_mult; ++ ++ for (_sysdiv = SC2235_SYSDIV_MIN; ++ _sysdiv <= SC2235_SYSDIV_MAX; ++ _sysdiv++) { ++ for (_pll_mult = SC2235_PLL_MULT_MIN; ++ _pll_mult <= SC2235_PLL_MULT_MAX; ++ _pll_mult++) { ++ unsigned long _rate; ++ ++ _rate = sc2235_compute_sys_clk(sensor, ++ SC2235_PLL_PREDIV, ++ _pll_mult, _sysdiv); ++ ++ /* ++ * We have reached the maximum allowed PLL1 output, ++ * increase sysdiv. ++ */ ++ if (!_rate) ++ break; ++ ++ /* ++ * Prefer rates above the expected clock rate than ++ * below, even if that means being less precise. ++ */ ++ if (_rate < rate) ++ continue; ++ ++ if (abs(rate - _rate) < abs(rate - best)) { ++ best = _rate; ++ best_sysdiv = _sysdiv; ++ best_mult = _pll_mult; ++ } ++ ++ if (_rate == rate) ++ goto out; ++ } ++ } ++ ++out: ++ *sysdiv = best_sysdiv; ++ *pll_prediv = SC2235_PLL_PREDIV; ++ *pll_mult = best_mult; ++ ++ return best; ++} ++#endif ++ ++static int sc2235_set_timings(struct sc2235_dev *sensor, ++ const struct sc2235_mode_info *mode) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int sc2235_load_regs(struct sc2235_dev *sensor, ++ const struct sc2235_mode_info *mode) ++{ ++ const struct reg_value *regs = mode->reg_data; ++ unsigned int i; ++ u32 delay_ms; ++ u16 reg_addr; ++ u8 mask, val; ++ int ret = 0; ++ ++ for (i = 0; i < mode->reg_data_size; ++i, ++regs) { ++ delay_ms = regs->delay_ms; ++ reg_addr = regs->reg_addr; ++ val = regs->val; ++ mask = regs->mask; ++ ++ if (mask) ++ ret = sc2235_mod_reg(sensor, reg_addr, mask, val); ++ else ++ ret = sc2235_write_reg(sensor, reg_addr, val); ++ if (ret) ++ break; ++ ++ if (delay_ms) ++ usleep_range(1000 * delay_ms, 1000 * delay_ms + 100); ++ } ++ ++ return sc2235_set_timings(sensor, mode); ++} ++ ++static int sc2235_set_autoexposure(struct sc2235_dev *sensor, bool on) ++{ ++ return sc2235_mod_reg(sensor, SC2235_REG_AEC_PK_MANUAL, ++ BIT(0), on ? 0 : BIT(0)); ++} ++ ++static int sc2235_get_exposure(struct sc2235_dev *sensor) ++{ ++ int exp = 0, ret = 0; ++ u8 temp; ++ ++ ret = sc2235_read_reg(sensor, SC2235_REG_AEC_PK_EXPOSURE_HI, &temp); ++ if (ret) ++ return ret; ++ exp |= (int)temp << 8; ++ ret = sc2235_read_reg(sensor, SC2235_REG_AEC_PK_EXPOSURE_LO, &temp); ++ if (ret) ++ return ret; ++ exp |= (int)temp; ++ ++ return exp >> 4; ++} ++ ++static int sc2235_set_exposure(struct sc2235_dev *sensor, u32 exposure) ++{ ++ int ret; ++ ++ exposure <<= 4; ++ ++ ret = sc2235_write_reg(sensor, ++ SC2235_REG_AEC_PK_EXPOSURE_LO, ++ exposure & 0xff); ++ if (ret) ++ return ret; ++ return sc2235_write_reg(sensor, ++ SC2235_REG_AEC_PK_EXPOSURE_HI, ++ (exposure >> 8) & 0xff); ++} ++ ++static int sc2235_get_gain(struct sc2235_dev *sensor) ++{ ++ u16 gain; ++ int ret; ++ ++ ret = sc2235_read_reg16(sensor, SC2235_REG_AEC_PK_REAL_GAIN, &gain); ++ if (ret) ++ return ret; ++ ++ return gain & 0x1fff; ++} ++ ++static int sc2235_set_gain(struct sc2235_dev *sensor, int gain) ++{ ++ return sc2235_write_reg16(sensor, SC2235_REG_AEC_PK_REAL_GAIN, ++ (u16)gain & 0x1fff); ++} ++ ++static int sc2235_set_autogain(struct sc2235_dev *sensor, bool on) ++{ ++ return sc2235_mod_reg(sensor, SC2235_REG_AEC_PK_MANUAL, ++ BIT(1), on ? 0 : BIT(1)); ++} ++ ++#ifdef UNUSED_CODE ++static int sc2235_get_sysclk(struct sc2235_dev *sensor) ++{ ++ return 0; ++} ++ ++static int sc2235_set_night_mode(struct sc2235_dev *sensor) ++{ ++ return 0; ++} ++ ++static int sc2235_get_hts(struct sc2235_dev *sensor) ++{ ++ u16 hts; ++ int ret; ++ ++ ret = sc2235_read_reg16(sensor, SC2235_REG_TIMING_HTS, &hts); ++ if (ret) ++ return ret; ++ return hts; ++} ++#endif ++ ++static int sc2235_get_vts(struct sc2235_dev *sensor) ++{ ++ u16 vts; ++ int ret; ++ ++ ret = sc2235_read_reg16(sensor, SC2235_REG_TIMING_VTS, &vts); ++ if (ret) ++ return ret; ++ return vts; ++} ++ ++#ifdef UNUSED_CODE ++static int sc2235_set_vts(struct sc2235_dev *sensor, int vts) ++{ ++ return sc2235_write_reg16(sensor, SC2235_REG_TIMING_VTS, vts); ++} ++ ++static int sc2235_get_light_freq(struct sc2235_dev *sensor) ++{ ++ return 0; ++} ++ ++static int sc2235_set_bandingfilter(struct sc2235_dev *sensor) ++{ ++ return 0; ++} ++ ++static int sc2235_set_ae_target(struct sc2235_dev *sensor, int target) ++{ ++ return 0; ++} ++ ++static int sc2235_get_binning(struct sc2235_dev *sensor) ++{ ++ return 0; ++} ++ ++static int sc2235_set_binning(struct sc2235_dev *sensor, bool enable) ++{ ++ return 0; ++} ++ ++#endif ++ ++static const struct sc2235_mode_info * ++sc2235_find_mode(struct sc2235_dev *sensor, enum sc2235_frame_rate fr, ++ int width, int height, bool nearest) ++{ ++ const struct sc2235_mode_info *mode; ++ ++ mode = v4l2_find_nearest_size(sc2235_mode_data, ++ ARRAY_SIZE(sc2235_mode_data), ++ hact, vact, ++ width, height); ++ ++ if (!mode || ++ (!nearest && (mode->hact != width || mode->vact != height))) ++ return NULL; ++ ++ /* Check to see if the current mode exceeds the max frame rate */ ++ if (sc2235_framerates[fr] > sc2235_framerates[mode->max_fps]) ++ return NULL; ++ ++ return mode; ++} ++ ++static u64 sc2235_calc_pixel_rate(struct sc2235_dev *sensor) ++{ ++ u64 rate; ++ ++ rate = sensor->current_mode->vtot * sensor->current_mode->htot; ++ rate *= sc2235_framerates[sensor->current_fr]; ++ ++ return rate; ++} ++ ++#ifdef UNUSED_CODE ++/* ++ * sc2235_set_dvp_pclk() - Calculate the clock tree configuration values ++ * for the dvp output. ++ * ++ * @rate: The requested bandwidth per lane in bytes per second. ++ * 'Bandwidth Per Lane' is calculated as: ++ * rate = HTOT * VTOT * FPS; ++ * ++ * This function use the requested bandwidth to calculate: ++ * - rate = xclk * (64 - M) / (N * (S + 1)); ++ * ++ */ ++ ++#define PLL_PREDIV 1 ++#define PLL_SYSEL 0 ++ ++static int sc2235_set_dvp_pclk(struct sc2235_dev *sensor, ++ unsigned long rate) ++{ ++ u8 prediv, mult, sysdiv; ++ int ret = 0; ++ ++ sc2235_calc_sys_clk(sensor, rate, &prediv, &mult, ++ &sysdiv); ++ ++ ++ return ret; ++} ++ ++/* ++ * if sensor changes inside scaling or subsampling ++ * change mode directly ++ */ ++static int sc2235_set_mode_direct(struct sc2235_dev *sensor, ++ const struct sc2235_mode_info *mode) ++{ ++ if (!mode->reg_data) ++ return -EINVAL; ++ ++ /* Write capture setting */ ++ return sc2235_load_regs(sensor, mode); ++} ++#endif ++ ++static int sc2235_set_mode(struct sc2235_dev *sensor) ++{ ++#ifdef UNUSED_CODE ++ bool auto_exp = sensor->ctrls.auto_exp->val == V4L2_EXPOSURE_AUTO; ++ const struct sc2235_mode_info *mode = sensor->current_mode; ++#endif ++ bool auto_gain = sensor->ctrls.auto_gain->val == 1; ++ int ret = 0; ++ ++ /* auto gain and exposure must be turned off when changing modes */ ++ if (auto_gain) { ++ ret = sc2235_set_autogain(sensor, false); ++ if (ret) ++ return ret; ++ } ++#ifdef UNUSED_CODE ++ /* This issue will be addressed in the EVB board*/ ++ /* This action will result in poor image display 2021 1111*/ ++ if (auto_exp) { ++ ret = sc2235_set_autoexposure(sensor, false); ++ if (ret) ++ goto restore_auto_gain; ++ } ++ ++ rate = sc2235_calc_pixel_rate(sensor); ++ ++ ret = sc2235_set_dvp_pclk(sensor, rate); ++ if (ret < 0) ++ return 0; ++ ++ ret = sc2235_set_mode_direct(sensor, mode); ++ if (ret < 0) ++ goto restore_auto_exp_gain; ++ ++ /* restore auto gain and exposure */ ++ if (auto_gain) ++ sc2235_set_autogain(sensor, true); ++ if (auto_exp) ++ sc2235_set_autoexposure(sensor, true); ++ ++ ++ sensor->pending_mode_change = false; ++ sensor->last_mode = mode; ++ return 0; ++ ++restore_auto_exp_gain: ++ if (auto_exp) ++ sc2235_set_autoexposure(sensor, true); ++restore_auto_gain: ++ if (auto_gain) ++ sc2235_set_autogain(sensor, true); ++#endif ++ return ret; ++} ++ ++static int sc2235_set_framefmt(struct sc2235_dev *sensor, ++ struct v4l2_mbus_framefmt *format); ++ ++/* restore the last set video mode after chip power-on */ ++static int sc2235_restore_mode(struct sc2235_dev *sensor) ++{ ++ int ret; ++ ++ /* first load the initial register values */ ++ ret = sc2235_load_regs(sensor, &sc2235_mode_init_data); ++ if (ret < 0) ++ return ret; ++ sensor->last_mode = &sc2235_mode_init_data; ++ /* now restore the last capture mode */ ++ ret = sc2235_set_mode(sensor); ++ if (ret < 0) ++ return ret; ++ ++ return sc2235_set_framefmt(sensor, &sensor->fmt); ++} ++ ++static void sc2235_power(struct sc2235_dev *sensor, bool enable) ++{ ++ if (!sensor->pwdn_gpio) ++ return; ++ gpiod_set_value_cansleep(sensor->pwdn_gpio, enable ? 0 : 1); ++} ++ ++static void sc2235_reset(struct sc2235_dev *sensor) ++{ ++ if (!sensor->reset_gpio) ++ return; ++ ++ gpiod_set_value_cansleep(sensor->reset_gpio, 0); ++ ++ /* camera power cycle */ ++ sc2235_power(sensor, false); ++ usleep_range(5000, 10000); ++ sc2235_power(sensor, true); ++ usleep_range(5000, 10000); ++ ++ gpiod_set_value_cansleep(sensor->reset_gpio, 1); ++ usleep_range(1000, 2000); ++ ++ gpiod_set_value_cansleep(sensor->reset_gpio, 0); ++ usleep_range(20000, 25000); ++} ++ ++static int sc2235_set_power_on(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct sc2235_dev *sensor = to_sc2235_dev(sd); ++ int ret; ++ ++ ret = clk_prepare_enable(sensor->xclk); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to enable clock\n", ++ __func__); ++ return ret; ++ } ++ ++ ret = regulator_bulk_enable(SC2235_NUM_SUPPLIES, ++ sensor->supplies); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to enable regulators\n", ++ __func__); ++ goto xclk_off; ++ } ++ ++ sc2235_reset(sensor); ++ sc2235_power(sensor, true); ++ ++ return 0; ++ ++xclk_off: ++ clk_disable_unprepare(sensor->xclk); ++ return ret; ++} ++ ++static int sc2235_set_power_off(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct sc2235_dev *sensor = to_sc2235_dev(sd); ++ ++ sc2235_power(sensor, false); ++ regulator_bulk_disable(SC2235_NUM_SUPPLIES, sensor->supplies); ++ clk_disable_unprepare(sensor->xclk); ++ ++ return 0; ++} ++ ++static int sc2235_set_power(struct sc2235_dev *sensor, bool on) ++{ ++ int ret = 0; ++ ++ if (on) { ++ pm_runtime_get_sync(&sensor->i2c_client->dev); ++ ++ ret = sc2235_restore_mode(sensor); ++ if (ret) ++ goto power_off; ++ } ++ ++ if (!on) ++ pm_runtime_put_sync(&sensor->i2c_client->dev); ++ ++ return 0; ++ ++power_off: ++ pm_runtime_put_sync(&sensor->i2c_client->dev); ++ ++ return ret; ++} ++ ++static int sc2235_s_power(struct v4l2_subdev *sd, int on) ++{ ++ struct sc2235_dev *sensor = to_sc2235_dev(sd); ++ int ret = 0; ++ ++ mutex_lock(&sensor->lock); ++ ++ ret = sc2235_set_power(sensor, !!on); ++ if (ret) ++ goto out; ++ ++ mutex_unlock(&sensor->lock); ++ return 0; ++ ++out: ++ mutex_unlock(&sensor->lock); ++ return ret; ++} ++ ++static int sc2235_try_frame_interval(struct sc2235_dev *sensor, ++ struct v4l2_fract *fi, ++ u32 width, u32 height) ++{ ++ const struct sc2235_mode_info *mode; ++ enum sc2235_frame_rate rate = SC2235_15_FPS; ++ int minfps, maxfps, best_fps, fps; ++ int i; ++ ++ minfps = sc2235_framerates[SC2235_15_FPS]; ++ maxfps = sc2235_framerates[SC2235_30_FPS]; ++ ++ if (fi->numerator == 0) { ++ fi->denominator = maxfps; ++ fi->numerator = 1; ++ rate = SC2235_30_FPS; ++ goto find_mode; ++ } ++ ++ fps = clamp_val(DIV_ROUND_CLOSEST(fi->denominator, fi->numerator), ++ minfps, maxfps); ++ ++ best_fps = minfps; ++ for (i = 0; i < ARRAY_SIZE(sc2235_framerates); i++) { ++ int curr_fps = sc2235_framerates[i]; ++ ++ if (abs(curr_fps - fps) < abs(best_fps - fps)) { ++ best_fps = curr_fps; ++ rate = i; ++ } ++ } ++ ++ fi->numerator = 1; ++ fi->denominator = best_fps; ++ ++find_mode: ++ mode = sc2235_find_mode(sensor, rate, width, height, false); ++ return mode ? rate : -EINVAL; ++} ++ ++static int sc2235_get_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *format) ++{ ++ struct sc2235_dev *sensor = to_sc2235_dev(sd); ++ struct v4l2_mbus_framefmt *fmt; ++ ++ if (format->pad != 0) ++ return -EINVAL; ++ ++ mutex_lock(&sensor->lock); ++ ++ if (format->which == V4L2_SUBDEV_FORMAT_TRY) ++ fmt = v4l2_subdev_get_try_format(&sensor->sd, state, ++ format->pad); ++ else ++ fmt = &sensor->fmt; ++ ++ format->format = *fmt; ++ ++ mutex_unlock(&sensor->lock); ++ ++ return 0; ++} ++ ++static int sc2235_try_fmt_internal(struct v4l2_subdev *sd, ++ struct v4l2_mbus_framefmt *fmt, ++ enum sc2235_frame_rate fr, ++ const struct sc2235_mode_info **new_mode) ++{ ++ struct sc2235_dev *sensor = to_sc2235_dev(sd); ++ const struct sc2235_mode_info *mode; ++ int i; ++ ++ mode = sc2235_find_mode(sensor, fr, fmt->width, fmt->height, true); ++ if (!mode) ++ return -EINVAL; ++ fmt->width = mode->hact; ++ fmt->height = mode->vact; ++ ++ if (new_mode) ++ *new_mode = mode; ++ ++ for (i = 0; i < ARRAY_SIZE(sc2235_formats); i++) ++ if (sc2235_formats[i].code == fmt->code) ++ break; ++ if (i >= ARRAY_SIZE(sc2235_formats)) ++ i = 0; ++ ++ fmt->code = sc2235_formats[i].code; ++ fmt->colorspace = sc2235_formats[i].colorspace; ++ 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); ++ ++ return 0; ++} ++ ++static int sc2235_set_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *format) ++{ ++ struct sc2235_dev *sensor = to_sc2235_dev(sd); ++ const struct sc2235_mode_info *new_mode; ++ struct v4l2_mbus_framefmt *mbus_fmt = &format->format; ++ struct v4l2_mbus_framefmt *fmt; ++ int ret; ++ ++ if (format->pad != 0) ++ return -EINVAL; ++ mutex_lock(&sensor->lock); ++ ++ if (sensor->streaming) { ++ ret = -EBUSY; ++ goto out; ++ } ++ ++ ret = sc2235_try_fmt_internal(sd, mbus_fmt, 0, &new_mode); ++ if (ret) ++ goto out; ++ ++ if (format->which == V4L2_SUBDEV_FORMAT_TRY) ++ fmt = v4l2_subdev_get_try_format(sd, state, 0); ++ else ++ fmt = &sensor->fmt; ++ ++ if (mbus_fmt->code != sensor->fmt.code) ++ sensor->pending_fmt_change = true; ++ ++ *fmt = *mbus_fmt; ++ ++ if (new_mode != sensor->current_mode) { ++ sensor->current_mode = new_mode; ++ sensor->pending_mode_change = true; ++ } ++ if (new_mode->max_fps < sensor->current_fr) { ++ sensor->current_fr = new_mode->max_fps; ++ sensor->frame_interval.numerator = 1; ++ sensor->frame_interval.denominator = ++ sc2235_framerates[sensor->current_fr]; ++ sensor->current_mode = new_mode; ++ sensor->pending_mode_change = true; ++ } ++ ++ __v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate, ++ sc2235_calc_pixel_rate(sensor)); ++out: ++ mutex_unlock(&sensor->lock); ++ return ret; ++} ++ ++static int sc2235_set_framefmt(struct sc2235_dev *sensor, ++ struct v4l2_mbus_framefmt *format) ++{ ++ int ret = 0; ++ ++ switch (format->code) { ++ default: ++ return ret; ++ } ++ return ret; ++} ++ ++/* ++ * Sensor Controls. ++ */ ++ ++static int sc2235_set_ctrl_hue(struct sc2235_dev *sensor, int value) ++{ ++ int ret = 0; ++ return ret; ++} ++ ++static int sc2235_set_ctrl_contrast(struct sc2235_dev *sensor, int value) ++{ ++ int ret = 0; ++ return ret; ++} ++ ++static int sc2235_set_ctrl_saturation(struct sc2235_dev *sensor, int value) ++{ ++ int ret = 0; ++ return ret; ++} ++ ++static int sc2235_set_ctrl_white_balance(struct sc2235_dev *sensor, int awb) ++{ ++ int ret = 0; ++ return ret; ++} ++ ++static int sc2235_set_ctrl_exposure(struct sc2235_dev *sensor, ++ enum v4l2_exposure_auto_type auto_exposure) ++{ ++ struct sc2235_ctrls *ctrls = &sensor->ctrls; ++ bool auto_exp = (auto_exposure == V4L2_EXPOSURE_AUTO); ++ int ret = 0; ++ ++ if (ctrls->auto_exp->is_new) { ++ ret = sc2235_set_autoexposure(sensor, auto_exp); ++ if (ret) ++ return ret; ++ } ++ ++ if (!auto_exp && ctrls->exposure->is_new) { ++ u16 max_exp = 0; ++ ++ ret = sc2235_get_vts(sensor); ++ if (ret < 0) ++ return ret; ++ max_exp += ret - 4; ++ ret = 0; ++ ++ if (ctrls->exposure->val < max_exp) ++ ret = sc2235_set_exposure(sensor, ctrls->exposure->val); ++ } ++ ++ return ret; ++} ++ ++static int sc2235_set_ctrl_gain(struct sc2235_dev *sensor, bool auto_gain) ++{ ++ struct sc2235_ctrls *ctrls = &sensor->ctrls; ++ int ret = 0; ++ ++ if (ctrls->auto_gain->is_new) { ++ ret = sc2235_set_autogain(sensor, auto_gain); ++ if (ret) ++ return ret; ++ } ++ ++ if (!auto_gain && ctrls->gain->is_new) ++ ret = sc2235_set_gain(sensor, ctrls->gain->val); ++ ++ return ret; ++} ++ ++static const char * const test_pattern_menu[] = { ++ "Disabled", ++ "Black bars", ++ "Auto Black bars", ++}; ++ ++#define SC2235_TEST_ENABLE BIT(3) ++#define SC2235_TEST_BLACK (3 << 0) ++ ++static int sc2235_set_ctrl_test_pattern(struct sc2235_dev *sensor, int value) ++{ ++ int ret = 0; ++ /* ++ *For 7110 platform, refer to 1125 FW code configuration. This operation will cause the image to be white. ++ */ ++#ifdef UNUSED_CODE ++ ret = sc2235_mod_reg(sensor, SC2235_REG_TEST_SET0, BIT(3), ++ !!value << 3); ++ ++ ret |= sc2235_mod_reg(sensor, SC2235_REG_TEST_SET1, BIT(6), ++ (value >> 1) << 6); ++#endif ++ return ret; ++} ++ ++static int sc2235_set_ctrl_light_freq(struct sc2235_dev *sensor, int value) ++{ ++ return 0; ++} ++ ++static int sc2235_set_ctrl_hflip(struct sc2235_dev *sensor, int value) ++{ ++ return sc2235_mod_reg(sensor, SC2235_REG_TIMING_TC_REG21, ++ BIT(2) | BIT(1), ++ (!(value ^ sensor->upside_down)) ? ++ (BIT(2) | BIT(1)) : 0); ++} ++ ++static int sc2235_set_ctrl_vflip(struct sc2235_dev *sensor, int value) ++{ ++ return sc2235_mod_reg(sensor, SC2235_REG_TIMING_TC_REG21, ++ BIT(6) | BIT(5), ++ (value ^ sensor->upside_down) ? ++ (BIT(6) | BIT(5)) : 0); ++} ++ ++static int sc2235_g_volatile_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct v4l2_subdev *sd = ctrl_to_sd(ctrl); ++ struct sc2235_dev *sensor = to_sc2235_dev(sd); ++ int val; ++ ++ /* v4l2_ctrl_lock() locks our own mutex */ ++ ++ if (!pm_runtime_get_if_in_use(&sensor->i2c_client->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_AUTOGAIN: ++ val = sc2235_get_gain(sensor); ++ if (val < 0) ++ return val; ++ sensor->ctrls.gain->val = val; ++ break; ++ case V4L2_CID_EXPOSURE_AUTO: ++ val = sc2235_get_exposure(sensor); ++ if (val < 0) ++ return val; ++ sensor->ctrls.exposure->val = val; ++ break; ++ } ++ ++ pm_runtime_put(&sensor->i2c_client->dev); ++ ++ return 0; ++} ++ ++static int sc2235_s_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct v4l2_subdev *sd = ctrl_to_sd(ctrl); ++ struct sc2235_dev *sensor = to_sc2235_dev(sd); ++ int ret; ++ ++ /* v4l2_ctrl_lock() locks our own mutex */ ++ ++ /* ++ * If the device is not powered up by the host driver do ++ * not apply any controls to H/W at this time. Instead ++ * the controls will be restored at start streaming time. ++ */ ++ if (!pm_runtime_get_if_in_use(&sensor->i2c_client->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_AUTOGAIN: ++ ret = sc2235_set_ctrl_gain(sensor, ctrl->val); ++ break; ++ case V4L2_CID_EXPOSURE_AUTO: ++ ret = sc2235_set_ctrl_exposure(sensor, ctrl->val); ++ break; ++ case V4L2_CID_AUTO_WHITE_BALANCE: ++ ret = sc2235_set_ctrl_white_balance(sensor, ctrl->val); ++ break; ++ case V4L2_CID_HUE: ++ ret = sc2235_set_ctrl_hue(sensor, ctrl->val); ++ break; ++ case V4L2_CID_CONTRAST: ++ ret = sc2235_set_ctrl_contrast(sensor, ctrl->val); ++ break; ++ case V4L2_CID_SATURATION: ++ ret = sc2235_set_ctrl_saturation(sensor, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN: ++ ret = sc2235_set_ctrl_test_pattern(sensor, ctrl->val); ++ break; ++ case V4L2_CID_POWER_LINE_FREQUENCY: ++ ret = sc2235_set_ctrl_light_freq(sensor, ctrl->val); ++ break; ++ case V4L2_CID_HFLIP: ++ ret = sc2235_set_ctrl_hflip(sensor, ctrl->val); ++ break; ++ case V4L2_CID_VFLIP: ++ ret = sc2235_set_ctrl_vflip(sensor, ctrl->val); ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ pm_runtime_put(&sensor->i2c_client->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops sc2235_ctrl_ops = { ++ .g_volatile_ctrl = sc2235_g_volatile_ctrl, ++ .s_ctrl = sc2235_s_ctrl, ++}; ++ ++static int sc2235_init_controls(struct sc2235_dev *sensor) ++{ ++ const struct v4l2_ctrl_ops *ops = &sc2235_ctrl_ops; ++ struct sc2235_ctrls *ctrls = &sensor->ctrls; ++ struct v4l2_ctrl_handler *hdl = &ctrls->handler; ++ int ret; ++ ++ v4l2_ctrl_handler_init(hdl, 32); ++ ++ /* we can use our own mutex for the ctrl lock */ ++ hdl->lock = &sensor->lock; ++ ++ /* Clock related controls */ ++ ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, ++ 0, INT_MAX, 1, ++ sc2235_calc_pixel_rate(sensor)); ++ ++ /* Auto/manual white balance */ ++ ctrls->auto_wb = v4l2_ctrl_new_std(hdl, ops, ++ V4L2_CID_AUTO_WHITE_BALANCE, ++ 0, 1, 1, 1); ++ ctrls->blue_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BLUE_BALANCE, ++ 0, 4095, 1, 0); ++ ctrls->red_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE, ++ 0, 4095, 1, 0); ++ /* Auto/manual exposure */ ++#ifdef UNUSED_CODE ++ /* ++ *For 7110 platform, This operation will cause the image to be white. ++ */ ++ ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, ++ V4L2_CID_EXPOSURE_AUTO, ++ V4L2_EXPOSURE_MANUAL, 0, ++ V4L2_EXPOSURE_AUTO); ++ ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, ++ 0, 65535, 1, 0); ++ /* Auto/manual gain */ ++ ctrls->auto_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTOGAIN, ++ 0, 1, 1, 1); ++ ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, ++ 0, 1023, 1, 0); ++#else ++ ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, ++ V4L2_CID_EXPOSURE_AUTO, ++ V4L2_EXPOSURE_MANUAL, 0, ++ 1); ++ ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, ++ 0, 65535, 1, 720); ++ /* Auto/manual gain */ ++ ctrls->auto_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTOGAIN, ++ 0, 1, 1, 0); ++ ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, ++ 0, 1023, 1, 0x10); ++#endif ++ ctrls->saturation = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, ++ 0, 255, 1, 64); ++ ctrls->hue = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HUE, ++ 0, 359, 1, 0); ++ ctrls->contrast = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, ++ 0, 255, 1, 0); ++ ctrls->test_pattern = ++ v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(test_pattern_menu) - 1, ++ 0, 0, test_pattern_menu); //0x02 ++ ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, ++ 0, 1, 1, 1); ++ ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, ++ 0, 1, 1, 0); ++ ++ ctrls->light_freq = ++ v4l2_ctrl_new_std_menu(hdl, ops, ++ V4L2_CID_POWER_LINE_FREQUENCY, ++ V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, ++ V4L2_CID_POWER_LINE_FREQUENCY_50HZ); ++ ++ if (hdl->error) { ++ ret = hdl->error; ++ goto free_ctrls; ++ } ++ ++ ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ctrls->gain->flags |= V4L2_CTRL_FLAG_VOLATILE; ++ ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; ++ ++ v4l2_ctrl_auto_cluster(3, &ctrls->auto_wb, 0, false); ++ v4l2_ctrl_auto_cluster(2, &ctrls->auto_gain, 0, true); ++ v4l2_ctrl_auto_cluster(2, &ctrls->auto_exp, 1, true); ++ ++ sensor->sd.ctrl_handler = hdl; ++ return 0; ++ ++free_ctrls: ++ v4l2_ctrl_handler_free(hdl); ++ return ret; ++} ++ ++static int sc2235_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ if (fse->pad != 0) ++ return -EINVAL; ++ if (fse->index >= SC2235_NUM_MODES) ++ return -EINVAL; ++ ++ fse->min_width = ++ sc2235_mode_data[fse->index].hact; ++ fse->max_width = fse->min_width; ++ fse->min_height = ++ sc2235_mode_data[fse->index].vact; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static int sc2235_enum_frame_interval( ++ struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_interval_enum *fie) ++{ ++ struct v4l2_fract tpf; ++ int i; ++ ++ if (fie->pad != 0) ++ return -EINVAL; ++ ++ if (fie->index >= SC2235_NUM_FRAMERATES) ++ return -EINVAL; ++ ++ tpf.numerator = 1; ++ tpf.denominator = sc2235_framerates[fie->index]; ++ ++ for (i = 0; i < SC2235_NUM_MODES; i++) { ++ if (fie->width == sc2235_mode_data[i].hact && ++ fie->height == sc2235_mode_data[i].vact) ++ break; ++ } ++ if (i == SC2235_NUM_MODES) ++ return -ENOTTY; ++ ++ fie->interval = tpf; ++ return 0; ++} ++ ++static int sc2235_g_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_frame_interval *fi) ++{ ++ struct sc2235_dev *sensor = to_sc2235_dev(sd); ++ ++ mutex_lock(&sensor->lock); ++ fi->interval = sensor->frame_interval; ++ mutex_unlock(&sensor->lock); ++ ++ return 0; ++} ++ ++static int sc2235_s_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_frame_interval *fi) ++{ ++ struct sc2235_dev *sensor = to_sc2235_dev(sd); ++ const struct sc2235_mode_info *mode; ++ int frame_rate, ret = 0; ++ ++ if (fi->pad != 0) ++ return -EINVAL; ++ ++ mutex_lock(&sensor->lock); ++ ++ if (sensor->streaming) { ++ ret = -EBUSY; ++ goto out; ++ } ++ ++ mode = sensor->current_mode; ++ ++ frame_rate = sc2235_try_frame_interval(sensor, &fi->interval, ++ mode->hact, mode->vact); ++ if (frame_rate < 0) { ++ /* Always return a valid frame interval value */ ++ fi->interval = sensor->frame_interval; ++ goto out; ++ } ++ ++ mode = sc2235_find_mode(sensor, frame_rate, mode->hact, ++ mode->vact, true); ++ if (!mode) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ if (mode != sensor->current_mode || ++ frame_rate != sensor->current_fr) { ++ 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, ++ sc2235_calc_pixel_rate(sensor)); ++ } ++out: ++ mutex_unlock(&sensor->lock); ++ return ret; ++} ++ ++static int sc2235_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ if (code->pad != 0) ++ return -EINVAL; ++ if (code->index >= ARRAY_SIZE(sc2235_formats)) ++ return -EINVAL; ++ ++ code->code = sc2235_formats[code->index].code; ++ return 0; ++} ++ ++static int sc2235_stream_start(struct sc2235_dev *sensor, int enable) ++{ ++ return sc2235_mod_reg(sensor, SC2235_REG_STREAM_ON, BIT(0), !!enable); ++} ++ ++static int sc2235_s_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct sc2235_dev *sensor = to_sc2235_dev(sd); ++ int ret = 0; ++ ++ if (enable) { ++ ret = v4l2_ctrl_handler_setup(&sensor->ctrls.handler); ++ if (ret) ++ return ret; ++ } ++ ++ mutex_lock(&sensor->lock); ++ ++ if (sensor->streaming == !enable) { ++ if (enable && sensor->pending_mode_change) { ++ ret = sc2235_set_mode(sensor); ++ if (ret) ++ goto out; ++ } ++ ++ if (enable && sensor->pending_fmt_change) { ++ ret = sc2235_set_framefmt(sensor, &sensor->fmt); ++ if (ret) ++ goto out; ++ sensor->pending_fmt_change = false; ++ } ++ ++ ret = sc2235_stream_start(sensor, enable); ++ if (ret) ++ goto out; ++ } ++ sensor->streaming += enable ? 1 : -1; ++ WARN_ON(sensor->streaming < 0); ++out: ++ mutex_unlock(&sensor->lock); ++ ++ return ret; ++} ++ ++static const struct v4l2_subdev_core_ops sc2235_core_ops = { ++ .s_power = sc2235_s_power, ++ .log_status = v4l2_ctrl_subdev_log_status, ++ .subscribe_event = v4l2_ctrl_subdev_subscribe_event, ++ .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++}; ++ ++static const struct v4l2_subdev_video_ops sc2235_video_ops = { ++ .g_frame_interval = sc2235_g_frame_interval, ++ .s_frame_interval = sc2235_s_frame_interval, ++ .s_stream = sc2235_s_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops sc2235_pad_ops = { ++ .enum_mbus_code = sc2235_enum_mbus_code, ++ .get_fmt = sc2235_get_fmt, ++ .set_fmt = sc2235_set_fmt, ++ .enum_frame_size = sc2235_enum_frame_size, ++ .enum_frame_interval = sc2235_enum_frame_interval, ++}; ++ ++static const struct v4l2_subdev_ops sc2235_subdev_ops = { ++ .core = &sc2235_core_ops, ++ .video = &sc2235_video_ops, ++ .pad = &sc2235_pad_ops, ++}; ++ ++static int sc2235_get_regulators(struct sc2235_dev *sensor) ++{ ++ int i; ++ ++ for (i = 0; i < SC2235_NUM_SUPPLIES; i++) ++ sensor->supplies[i].supply = sc2235_supply_name[i]; ++ ++ return devm_regulator_bulk_get(&sensor->i2c_client->dev, ++ SC2235_NUM_SUPPLIES, ++ sensor->supplies); ++} ++ ++static int sc2235_check_chip_id(struct sc2235_dev *sensor) ++{ ++ struct i2c_client *client = sensor->i2c_client; ++ int ret = 0; ++ u16 chip_id; ++ ++ ret = sc2235_read_reg16(sensor, SC2235_REG_CHIP_ID, &chip_id); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to read chip identifier\n", ++ __func__); ++ return ret; ++ } ++ ++ if (chip_id != SC2235_CHIP_ID) { ++ dev_err(&client->dev, "%s: wrong chip identifier, expected 0x%x, got 0x%x\n", ++ __func__, SC2235_CHIP_ID, chip_id); ++ return -ENXIO; ++ } ++ dev_err(&client->dev, "%s: chip identifier, got 0x%x\n", ++ __func__, chip_id); ++ ++ return 0; ++} ++ ++static int sc2235_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct fwnode_handle *endpoint; ++ struct sc2235_dev *sensor; ++ struct v4l2_mbus_framefmt *fmt; ++ u32 rotation; ++ int ret; ++ ++ sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); ++ if (!sensor) ++ return -ENOMEM; ++ ++ sensor->i2c_client = client; ++ ++ fmt = &sensor->fmt; ++ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; ++ fmt->colorspace = V4L2_COLORSPACE_SRGB; ++ 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 = 1920; ++ fmt->height = 1080; ++ fmt->field = V4L2_FIELD_NONE; ++ sensor->frame_interval.numerator = 1; ++ sensor->frame_interval.denominator = sc2235_framerates[SC2235_30_FPS]; ++ sensor->current_fr = SC2235_30_FPS; ++ sensor->current_mode = ++ &sc2235_mode_data[SC2235_MODE_1080P_1920_1080]; ++ sensor->last_mode = sensor->current_mode; ++ ++ /* optional indication of physical rotation of sensor */ ++ ret = fwnode_property_read_u32(dev_fwnode(&client->dev), "rotation", ++ &rotation); ++ if (!ret) { ++ switch (rotation) { ++ case 180: ++ sensor->upside_down = true; ++ fallthrough; ++ case 0: ++ break; ++ default: ++ dev_warn(dev, "%u degrees rotation is not supported, ignoring...\n", ++ rotation); ++ } ++ } ++ ++ endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), ++ NULL); ++ if (!endpoint) { ++ dev_err(dev, "endpoint node not found\n"); ++ return -EINVAL; ++ } ++ ++ ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep); ++ fwnode_handle_put(endpoint); ++ if (ret) { ++ dev_err(dev, "Could not parse endpoint\n"); ++ return ret; ++ } ++ ++ if (sensor->ep.bus_type != V4L2_MBUS_PARALLEL) { ++ dev_err(dev, "Unsupported bus type %d\n", sensor->ep.bus_type); ++ return -EINVAL; ++ } ++ ++ /* get system clock (xclk) */ ++ sensor->xclk = devm_clk_get(dev, "xclk"); ++ if (IS_ERR(sensor->xclk)) { ++ dev_err(dev, "failed to get xclk\n"); ++ return PTR_ERR(sensor->xclk); ++ } ++ ++ sensor->xclk_freq = clk_get_rate(sensor->xclk); ++ if (sensor->xclk_freq < SC2235_XCLK_MIN || ++ sensor->xclk_freq > SC2235_XCLK_MAX) { ++ dev_err(dev, "xclk frequency out of range: %d Hz\n", ++ sensor->xclk_freq); ++ return -EINVAL; ++ } ++ ++ sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(sensor->pwdn_gpio)) ++ return PTR_ERR(sensor->pwdn_gpio); ++ ++ sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(sensor->reset_gpio)) ++ return PTR_ERR(sensor->reset_gpio); ++ ++ v4l2_i2c_subdev_init(&sensor->sd, client, &sc2235_subdev_ops); ++ ++ sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | ++ V4L2_SUBDEV_FL_HAS_EVENTS; ++ sensor->pad.flags = MEDIA_PAD_FL_SOURCE; ++ sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); ++ if (ret) ++ return ret; ++ ++ ret = sc2235_get_regulators(sensor); ++ if (ret) ++ return ret; ++ mutex_init(&sensor->lock); ++ ++ ret = sc2235_set_power_on(dev); ++ if (ret) { ++ dev_err(dev, "failed to power on\n"); ++ goto entity_cleanup; ++ } ++ ++ ret = sc2235_check_chip_id(sensor); ++ if (ret) ++ goto entity_power_off; ++ ++ ret = sc2235_init_controls(sensor); ++ if (ret) ++ goto entity_power_off; ++ ++ ret = v4l2_async_register_subdev_sensor(&sensor->sd); ++ if (ret) ++ goto free_ctrls; ++ ++ pm_runtime_set_active(dev); ++ pm_runtime_enable(dev); ++ ++ return 0; ++ ++free_ctrls: ++ v4l2_ctrl_handler_free(&sensor->ctrls.handler); ++entity_power_off: ++ sc2235_set_power_off(dev); ++entity_cleanup: ++ media_entity_cleanup(&sensor->sd.entity); ++ mutex_destroy(&sensor->lock); ++ return ret; ++} ++ ++static void sc2235_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct sc2235_dev *sensor = to_sc2235_dev(sd); ++ ++ v4l2_async_unregister_subdev(&sensor->sd); ++ media_entity_cleanup(&sensor->sd.entity); ++ v4l2_ctrl_handler_free(&sensor->ctrls.handler); ++ mutex_destroy(&sensor->lock); ++ ++ pm_runtime_disable(&client->dev); ++ if (!pm_runtime_status_suspended(&client->dev)) ++ sc2235_set_power_off(&client->dev); ++ pm_runtime_set_suspended(&client->dev); ++} ++ ++static const struct i2c_device_id sc2235_id[] = { ++ { "sc2235", 0 }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(i2c, sc2235_id); ++ ++static const struct of_device_id sc2235_dt_ids[] = { ++ { .compatible = "smartsens,sc2235" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, sc2235_dt_ids); ++ ++static const struct dev_pm_ops sc2235_pm_ops = { ++ SET_RUNTIME_PM_OPS(sc2235_set_power_off, sc2235_set_power_on, NULL) ++}; ++ ++static struct i2c_driver sc2235_i2c_driver = { ++ .driver = { ++ .name = "sc2235", ++ .of_match_table = sc2235_dt_ids, ++ .pm = &sc2235_pm_ops, ++ }, ++ .id_table = sc2235_id, ++ .probe = sc2235_probe, ++ .remove = sc2235_remove, ++}; ++ ++module_i2c_driver(sc2235_i2c_driver); ++ ++MODULE_DESCRIPTION("SC2235 Camera Subdev Driver"); ++MODULE_LICENSE("GPL"); +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_common.h +@@ -0,0 +1,185 @@ ++/* SPDX-License-Identifier: GPL-2.0 ++ * ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#ifndef STF_COMMON_H ++#define STF_COMMON_H ++ ++#include <linux/kern_levels.h> ++ ++// #define STF_DEBUG ++ ++// #define USE_CSIDPHY_ONE_CLK_MODE 1 ++ ++enum { ++ ST_DVP = 0x0001, ++ ST_CSIPHY = 0x0002, ++ ST_CSI = 0x0004, ++ ST_ISP = 0x0008, ++ ST_VIN = 0x0010, ++ ST_VIDEO = 0x0020, ++ ST_CAMSS = 0x0040, ++ ST_SENSOR = 0x0080, ++}; ++ ++enum { ++ ST_NONE = 0x00, ++ ST_ERR = 0x01, ++ ST_WARN = 0x02, ++ ST_INFO = 0x03, ++ ST_DEBUG = 0x04, ++}; ++ ++extern unsigned int stdbg_level; ++extern unsigned int stdbg_mask; ++ ++#define ST_MODULE2STRING(__module) ({ \ ++ char *__str; \ ++ \ ++ switch (__module) { \ ++ case ST_DVP: \ ++ __str = "st_dvp"; \ ++ break; \ ++ case ST_CSIPHY: \ ++ __str = "st_csiphy"; \ ++ break; \ ++ case ST_CSI: \ ++ __str = "st_csi"; \ ++ break; \ ++ case ST_ISP: \ ++ __str = "st_isp"; \ ++ break; \ ++ case ST_VIN: \ ++ __str = "st_vin"; \ ++ break; \ ++ case ST_VIDEO: \ ++ __str = "st_video"; \ ++ break; \ ++ case ST_CAMSS: \ ++ __str = "st_camss"; \ ++ break; \ ++ case ST_SENSOR: \ ++ __str = "st_sensor"; \ ++ break; \ ++ default: \ ++ __str = "unknow"; \ ++ break; \ ++ } \ ++ \ ++ __str; \ ++ }) ++ ++#define st_debug(module, __fmt, arg...) \ ++ do { \ ++ if (stdbg_level > ST_INFO) { \ ++ if (stdbg_mask & module) \ ++ pr_err("[%s] debug: " __fmt, \ ++ ST_MODULE2STRING(module), \ ++ ## arg); \ ++ } \ ++ } while (0) ++ ++#define st_info(module, __fmt, arg...) \ ++ do { \ ++ if (stdbg_level > ST_WARN) { \ ++ if (stdbg_mask & module) \ ++ pr_err("[%s] info: " __fmt, \ ++ ST_MODULE2STRING(module), \ ++ ## arg); \ ++ } \ ++ } while (0) ++ ++#define st_warn(module, __fmt, arg...) \ ++ do { \ ++ if (stdbg_level > ST_ERR) { \ ++ if (stdbg_mask & module) \ ++ pr_err("[%s] warn: " __fmt, \ ++ ST_MODULE2STRING(module), \ ++ ## arg); \ ++ } \ ++ } while (0) ++ ++#define st_err(module, __fmt, arg...) \ ++ do { \ ++ if (stdbg_level > ST_NONE) { \ ++ if (stdbg_mask & module) \ ++ pr_err("[%s] error: " __fmt, \ ++ ST_MODULE2STRING(module), \ ++ ## arg); \ ++ } \ ++ } while (0) ++ ++#define st_err_ratelimited(module, fmt, ...) \ ++ do { \ ++ static DEFINE_RATELIMIT_STATE(_rs, \ ++ DEFAULT_RATELIMIT_INTERVAL, \ ++ DEFAULT_RATELIMIT_BURST); \ ++ if (__ratelimit(&_rs) && (stdbg_level > ST_NONE)) { \ ++ if (stdbg_mask & module) \ ++ pr_err("[%s] error: " fmt, \ ++ ST_MODULE2STRING(module), \ ++ ##__VA_ARGS__); \ ++ } \ ++ } while (0) ++ ++#define set_bits(p, v, b, m) (((p) & ~(m)) | ((v) << (b))) ++ ++static inline u32 reg_read(void __iomem *base, u32 reg) ++{ ++ return ioread32(base + reg); ++} ++ ++static inline void reg_write(void __iomem *base, u32 reg, u32 val) ++{ ++ iowrite32(val, base + reg); ++} ++ ++static inline void reg_set_bit(void __iomem *base, u32 reg, u32 mask, u32 val) ++{ ++ u32 value; ++ ++ value = ioread32(base + reg) & ~mask; ++ val &= mask; ++ val |= value; ++ iowrite32(val, base + reg); ++} ++ ++static inline void reg_set(void __iomem *base, u32 reg, u32 mask) ++{ ++ iowrite32(ioread32(base + reg) | mask, base + reg); ++} ++ ++static inline void reg_clear(void __iomem *base, u32 reg, u32 mask) ++{ ++ iowrite32(ioread32(base + reg) & ~mask, base + reg); ++} ++ ++static inline void reg_set_highest_bit(void __iomem *base, u32 reg) ++{ ++ u32 val; ++ ++ val = ioread32(base + reg); ++ val &= ~(0x1 << 31); ++ val |= (0x1 & 0x1) << 31; ++ iowrite32(val, base + reg); ++} ++ ++static inline void reg_clr_highest_bit(void __iomem *base, u32 reg) ++{ ++ u32 val; ++ ++ val = ioread32(base + reg); ++ val &= ~(0x1 << 31); ++ val |= (0x0 & 0x1) << 31; ++ iowrite32(val, base + reg); ++} ++ ++static inline void print_reg(unsigned int module, void __iomem *base, u32 reg) ++{ ++ //st_debug(module, "REG 0x%x = 0x%x\n", ++ // base + reg, ioread32(base + reg)); ++} ++ ++#endif /* STF_COMMON_H */ +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_csi.c +@@ -0,0 +1,465 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include "stfcamss.h" ++#include <media/v4l2-async.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-event.h> ++#include <media/v4l2-fwnode.h> ++#include <media/v4l2-subdev.h> ++ ++static const struct csi_format csi_formats_sink[] = { ++ { MEDIA_BUS_FMT_UYVY8_2X8, 16}, ++ { MEDIA_BUS_FMT_SRGGB10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGRBG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGBRG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SBGGR10_1X10, 10}, ++}; ++ ++/* this bpp need see csi controllor */ ++static const struct csi_format csi_formats_src[] = { ++ { MEDIA_BUS_FMT_AYUV8_1X32, 32}, ++ { MEDIA_BUS_FMT_SRGGB10_1X10, 16}, ++ { MEDIA_BUS_FMT_SGRBG10_1X10, 16}, ++ { MEDIA_BUS_FMT_SGBRG10_1X10, 16}, ++ { MEDIA_BUS_FMT_SBGGR10_1X10, 16}, ++}; ++ ++static int csi_find_format(u32 code, ++ const struct csi_format *formats, ++ unsigned int nformats) ++{ ++ int i; ++ ++ for (i = 0; i < nformats; i++) ++ if (formats[i].code == code) ++ return i; ++ return -EINVAL; ++} ++ ++int stf_csi_subdev_init(struct stfcamss *stfcamss) ++{ ++ struct stf_csi_dev *csi_dev = stfcamss->csi_dev; ++ ++ csi_dev->s_type = SENSOR_VIN; ++ csi_dev->hw_ops = &csi_ops; ++ csi_dev->stfcamss = stfcamss; ++ csi_dev->formats_sink = csi_formats_sink; ++ csi_dev->nformats_sink = ARRAY_SIZE(csi_formats_sink); ++ csi_dev->formats_src = csi_formats_src; ++ csi_dev->nformats_src = ARRAY_SIZE(csi_formats_src); ++ mutex_init(&csi_dev->stream_lock); ++ return 0; ++} ++ ++static int csi_set_power(struct v4l2_subdev *sd, int on) ++{ ++ struct stf_csi_dev *csi_dev = v4l2_get_subdevdata(sd); ++ ++ csi_dev->hw_ops->csi_power_on(csi_dev, (u8)on); ++ return 0; ++} ++ ++static struct v4l2_mbus_framefmt * ++__csi_get_format(struct stf_csi_dev *csi_dev, ++ struct v4l2_subdev_state *state, ++ unsigned int pad, ++ enum v4l2_subdev_format_whence which) ++{ ++ if (which == V4L2_SUBDEV_FORMAT_TRY) ++ return v4l2_subdev_get_try_format(&csi_dev->subdev, state, pad); ++ ++ return &csi_dev->fmt[pad]; ++} ++ ++static u32 code_to_data_type(int code) ++{ ++ switch (code) { ++ case MEDIA_BUS_FMT_SRGGB10_1X10: ++ case MEDIA_BUS_FMT_SGRBG10_1X10: ++ case MEDIA_BUS_FMT_SGBRG10_1X10: ++ case MEDIA_BUS_FMT_SBGGR10_1X10: ++ return 0x2b; ++ case MEDIA_BUS_FMT_UYVY8_2X8: ++ return 0x1E; ++ default: ++ return 0x2b; ++ } ++} ++ ++static int csi_set_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct stf_csi_dev *csi_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt *format; ++ int ret = 0; ++ u32 code, width, dt; ++ u8 bpp; ++ ++ format = __csi_get_format(csi_dev, NULL, STF_CSI_PAD_SINK, ++ V4L2_SUBDEV_FORMAT_ACTIVE); ++ if (format == NULL) ++ return -EINVAL; ++ ++ width = format->width; ++ ++ ret = csi_find_format(format->code, ++ csi_dev->formats_sink, ++ csi_dev->nformats_sink); ++ if (ret < 0) ++ return ret; ++ ++ code = csi_dev->formats_sink[ret].code; ++ bpp = csi_dev->formats_src[ret].bpp; ++ dt = code_to_data_type(code); ++ ++ mutex_lock(&csi_dev->stream_lock); ++ if (enable) { ++ if (csi_dev->stream_count == 0) { ++ csi_dev->hw_ops->csi_clk_enable(csi_dev); ++ csi_dev->hw_ops->csi_stream_set(csi_dev, enable, dt, width, bpp); ++ } ++ csi_dev->stream_count++; ++ } else { ++ if (csi_dev->stream_count == 0) ++ goto exit; ++ if (csi_dev->stream_count == 1) { ++ csi_dev->hw_ops->csi_stream_set(csi_dev, enable, dt, width, bpp); ++ csi_dev->hw_ops->csi_clk_disable(csi_dev); ++ } ++ csi_dev->stream_count--; ++ } ++exit: ++ mutex_unlock(&csi_dev->stream_lock); ++ return 0; ++} ++ ++static void csi_try_format(struct stf_csi_dev *csi_dev, ++ struct v4l2_subdev_state *state, ++ unsigned int pad, ++ struct v4l2_mbus_framefmt *fmt, ++ enum v4l2_subdev_format_whence which) ++{ ++ unsigned int i; ++ ++ switch (pad) { ++ case STF_CSI_PAD_SINK: ++ /* Set format on sink pad */ ++ ++ for (i = 0; i < csi_dev->nformats_sink; i++) ++ if (fmt->code == csi_dev->formats_sink[i].code) ++ break; ++ ++ if (i >= csi_dev->nformats_sink) ++ fmt->code = csi_dev->formats_sink[0].code; ++ ++ fmt->width = clamp_t(u32, ++ fmt->width, ++ STFCAMSS_FRAME_MIN_WIDTH, ++ STFCAMSS_FRAME_MAX_WIDTH); ++ fmt->height = clamp_t(u32, ++ fmt->height, ++ STFCAMSS_FRAME_MIN_HEIGHT, ++ STFCAMSS_FRAME_MAX_HEIGHT); ++ ++ ++ fmt->field = V4L2_FIELD_NONE; ++ fmt->colorspace = V4L2_COLORSPACE_SRGB; ++ fmt->flags = 0; ++ ++ break; ++ ++ case STF_CSI_PAD_SRC: ++ /* Set format on src pad */ ++ ++ for (i = 0; i < csi_dev->nformats_src; i++) ++ if (fmt->code == csi_dev->formats_src[i].code) ++ break; ++ ++ if (i >= csi_dev->nformats_src) ++ fmt->code = csi_dev->formats_src[0].code; ++ ++ fmt->width = clamp_t(u32, ++ fmt->width, ++ STFCAMSS_FRAME_MIN_WIDTH, ++ STFCAMSS_FRAME_MAX_WIDTH); ++ fmt->height = clamp_t(u32, ++ fmt->height, ++ STFCAMSS_FRAME_MIN_HEIGHT, ++ STFCAMSS_FRAME_MAX_HEIGHT); ++ ++ fmt->field = V4L2_FIELD_NONE; ++ fmt->colorspace = V4L2_COLORSPACE_SRGB; ++ fmt->flags = 0; ++ ++ break; ++ } ++} ++ ++static int csi_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct stf_csi_dev *csi_dev = v4l2_get_subdevdata(sd); ++ ++ if (code->index >= csi_dev->nformats_sink) ++ return -EINVAL; ++ if (code->pad == STF_CSI_PAD_SINK) { ++ code->code = csi_dev->formats_sink[code->index].code; ++ } else { ++ struct v4l2_mbus_framefmt *sink_fmt; ++ ++ sink_fmt = __csi_get_format(csi_dev, state, STF_CSI_PAD_SINK, ++ code->which); ++ ++ code->code = sink_fmt->code; ++ if (!code->code) ++ return -EINVAL; ++ } ++ code->flags = 0; ++ ++ return 0; ++} ++ ++static int csi_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ struct stf_csi_dev *csi_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt format; ++ ++ if (fse->index != 0) ++ return -EINVAL; ++ ++ format.code = fse->code; ++ format.width = 1; ++ format.height = 1; ++ csi_try_format(csi_dev, state, fse->pad, &format, fse->which); ++ fse->min_width = format.width; ++ fse->min_height = format.height; ++ ++ if (format.code != fse->code) ++ return -EINVAL; ++ ++ format.code = fse->code; ++ format.width = -1; ++ format.height = -1; ++ csi_try_format(csi_dev, state, fse->pad, &format, fse->which); ++ fse->max_width = format.width; ++ fse->max_height = format.height; ++ ++ return 0; ++} ++ ++static int csi_get_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct stf_csi_dev *csi_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt *format; ++ ++ format = __csi_get_format(csi_dev, state, fmt->pad, fmt->which); ++ if (format == NULL) ++ return -EINVAL; ++ ++ fmt->format = *format; ++ ++ return 0; ++} ++ ++static int csi_set_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct stf_csi_dev *csi_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt *format; ++ struct v4l2_mbus_framefmt *format_src; ++ int ret; ++ ++ format = __csi_get_format(csi_dev, state, fmt->pad, fmt->which); ++ if (format == NULL) ++ return -EINVAL; ++ ++ mutex_lock(&csi_dev->stream_lock); ++ if (csi_dev->stream_count) { ++ fmt->format = *format; ++ mutex_unlock(&csi_dev->stream_lock); ++ goto out; ++ } else { ++ csi_try_format(csi_dev, state, fmt->pad, &fmt->format, fmt->which); ++ *format = fmt->format; ++ } ++ mutex_unlock(&csi_dev->stream_lock); ++ ++ if (fmt->pad == STF_CSI_PAD_SINK) { ++ format_src = __csi_get_format(csi_dev, state, STF_DVP_PAD_SRC, ++ fmt->which); ++ ++ ret = csi_find_format(format->code, csi_dev->formats_sink, ++ csi_dev->nformats_sink); ++ if (ret < 0) ++ return ret; ++ ++ format_src->code = csi_dev->formats_src[ret].code; ++ csi_try_format(csi_dev, state, STF_DVP_PAD_SRC, format_src, ++ fmt->which); ++ } ++out: ++ return 0; ++} ++ ++static int csi_init_formats(struct v4l2_subdev *sd, ++ struct v4l2_subdev_fh *fh) ++{ ++ struct v4l2_subdev_format format = { ++ .pad = STF_CSI_PAD_SINK, ++ .which = fh ? V4L2_SUBDEV_FORMAT_TRY : ++ V4L2_SUBDEV_FORMAT_ACTIVE, ++ .format = { ++ .code = MEDIA_BUS_FMT_RGB565_2X8_LE, ++ .width = 1920, ++ .height = 1080 ++ } ++ }; ++ ++ return csi_set_format(sd, fh ? fh->state : NULL, &format); ++} ++ ++static int csi_link_setup(struct media_entity *entity, ++ const struct media_pad *local, ++ const struct media_pad *remote, u32 flags) ++{ ++ if ((local->flags & MEDIA_PAD_FL_SOURCE) && ++ (flags & MEDIA_LNK_FL_ENABLED)) { ++ struct v4l2_subdev *sd; ++ struct stf_csi_dev *csi_dev; ++ struct vin_line *line; ++ ++ if (media_pad_remote_pad_first(local)) ++ return -EBUSY; ++ ++ sd = media_entity_to_v4l2_subdev(entity); ++ csi_dev = v4l2_get_subdevdata(sd); ++ ++ sd = media_entity_to_v4l2_subdev(remote->entity); ++ line = v4l2_get_subdevdata(sd); ++ if (line->sdev_type == VIN_DEV_TYPE) ++ csi_dev->s_type = SENSOR_VIN; ++ if (line->sdev_type == ISP_DEV_TYPE) ++ csi_dev->s_type = SENSOR_ISP; ++ st_info(ST_CSI, "CSI device sensor type: %d\n", csi_dev->s_type); ++ } ++ ++ if ((local->flags & MEDIA_PAD_FL_SINK) && ++ (flags & MEDIA_LNK_FL_ENABLED)) { ++ struct v4l2_subdev *sd; ++ struct stf_csi_dev *csi_dev; ++ struct stf_csiphy_dev *csiphy_dev; ++ ++ if (media_pad_remote_pad_first(local)) ++ return -EBUSY; ++ ++ sd = media_entity_to_v4l2_subdev(entity); ++ csi_dev = v4l2_get_subdevdata(sd); ++ ++ sd = media_entity_to_v4l2_subdev(remote->entity); ++ csiphy_dev = v4l2_get_subdevdata(sd); ++ ++ st_info(ST_CSI, "CSI0 link to csiphy0\n"); ++ } ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_core_ops csi_core_ops = { ++ .s_power = csi_set_power, ++}; ++ ++static const struct v4l2_subdev_video_ops csi_video_ops = { ++ .s_stream = csi_set_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops csi_pad_ops = { ++ .enum_mbus_code = csi_enum_mbus_code, ++ .enum_frame_size = csi_enum_frame_size, ++ .get_fmt = csi_get_format, ++ .set_fmt = csi_set_format, ++}; ++ ++static const struct v4l2_subdev_ops csi_v4l2_ops = { ++ .core = &csi_core_ops, ++ .video = &csi_video_ops, ++ .pad = &csi_pad_ops, ++}; ++ ++static const struct v4l2_subdev_internal_ops csi_v4l2_internal_ops = { ++ .open = csi_init_formats, ++}; ++ ++static const struct media_entity_operations csi_media_ops = { ++ .link_setup = csi_link_setup, ++ .link_validate = v4l2_subdev_link_validate, ++}; ++ ++int stf_csi_register(struct stf_csi_dev *csi_dev, struct v4l2_device *v4l2_dev) ++{ ++ struct v4l2_subdev *sd = &csi_dev->subdev; ++ struct device *dev = csi_dev->stfcamss->dev; ++ struct media_pad *pads = csi_dev->pads; ++ int ret; ++ ++ csi_dev->mipirx_1p8 = devm_regulator_get(dev, "mipirx_1p8"); ++ if (IS_ERR(csi_dev->mipirx_1p8)) ++ return PTR_ERR(csi_dev->mipirx_1p8); ++ ++ csi_dev->mipirx_0p9 = devm_regulator_get(dev, "mipirx_0p9"); ++ if (IS_ERR(csi_dev->mipirx_0p9)) ++ return PTR_ERR(csi_dev->mipirx_0p9); ++ ++ v4l2_subdev_init(sd, &csi_v4l2_ops); ++ sd->internal_ops = &csi_v4l2_internal_ops; ++ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ++ snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", ++ STF_CSI_NAME, 0); ++ v4l2_set_subdevdata(sd, csi_dev); ++ ++ ret = csi_init_formats(sd, NULL); ++ if (ret < 0) { ++ dev_err(dev, "Failed to init format: %d\n", ret); ++ return ret; ++ } ++ ++ pads[STF_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK; ++ pads[STF_CSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; ++ ++ sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; ++ sd->entity.ops = &csi_media_ops; ++ ret = media_entity_pads_init(&sd->entity, STF_CSI_PADS_NUM, pads); ++ if (ret < 0) { ++ dev_err(dev, "Failed to init media entity: %d\n", ret); ++ return ret; ++ } ++ ++ ret = v4l2_device_register_subdev(v4l2_dev, sd); ++ if (ret < 0) { ++ dev_err(dev, "Failed to register subdev: %d\n", ret); ++ goto err_sreg; ++ } ++ ++ return 0; ++ ++err_sreg: ++ media_entity_cleanup(&sd->entity); ++ return ret; ++} ++ ++int stf_csi_unregister(struct stf_csi_dev *csi_dev) ++{ ++ v4l2_device_unregister_subdev(&csi_dev->subdev); ++ media_entity_cleanup(&csi_dev->subdev.entity); ++ mutex_destroy(&csi_dev->stream_lock); ++ return 0; ++} +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_csi.h +@@ -0,0 +1,61 @@ ++/* SPDX-License-Identifier: GPL-2.0 ++ * ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#ifndef STF_CSI_H ++#define STF_CSI_H ++ ++#include <linux/regulator/consumer.h> ++#include <media/v4l2-subdev.h> ++#include <media/v4l2-device.h> ++#include <media/media-entity.h> ++#include <video/stf-vin.h> ++ ++#define STF_CSI_NAME "stf_csi" ++ ++#define STF_CSI_PAD_SINK 0 ++#define STF_CSI_PAD_SRC 1 ++#define STF_CSI_PADS_NUM 2 ++ ++struct csi_format { ++ u32 code; ++ u8 bpp; ++}; ++ ++struct stf_csi_dev; ++ ++struct csi_hw_ops { ++ int (*csi_power_on)(struct stf_csi_dev *csi_dev, u8 on); ++ int (*csi_clk_enable)(struct stf_csi_dev *csi_dev); ++ int (*csi_clk_disable)(struct stf_csi_dev *csi_dev); ++ int (*csi_stream_set)(struct stf_csi_dev *csi_dev, int on, ++ u32 dt, u32 width, u8 bpp); ++}; ++ ++struct stf_csi_dev { ++ struct stfcamss *stfcamss; ++ enum sensor_type s_type; ++ struct v4l2_subdev subdev; ++ struct media_pad pads[STF_CSI_PADS_NUM]; ++ struct v4l2_mbus_framefmt fmt[STF_CSI_PADS_NUM]; ++ const struct csi_format *formats_sink; ++ unsigned int nformats_sink; ++ const struct csi_format *formats_src; ++ unsigned int nformats_src; ++ struct csi_hw_ops *hw_ops; ++ struct mutex stream_lock; ++ int stream_count; ++ struct regulator *mipirx_1p8; ++ struct regulator *mipirx_0p9; ++}; ++ ++extern int stf_csi_subdev_init(struct stfcamss *stfcamss); ++extern int stf_csi_register(struct stf_csi_dev *csi_dev, ++ struct v4l2_device *v4l2_dev); ++extern int stf_csi_unregister(struct stf_csi_dev *csi_dev); ++extern struct csi_hw_ops csi_ops; ++extern void dump_csi_reg(void *__iomem csibase); ++ ++#endif /* STF_CSI_H */ +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_csi_hw_ops.c +@@ -0,0 +1,310 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include "stfcamss.h" ++#include <linux/regmap.h> ++ ++#define CSI2RX_DEVICE_CFG_REG 0x000 ++ ++#define CSI2RX_SOFT_RESET_REG 0x004 ++#define CSI2RX_SOFT_RESET_PROTOCOL BIT(1) ++#define CSI2RX_SOFT_RESET_FRONT BIT(0) ++ ++#define CSI2RX_DPHY_LANE_CONTROL 0x040 ++ ++#define CSI2RX_STATIC_CFG_REG 0x008 ++#define CSI2RX_STATIC_CFG_DLANE_MAP(llane, plane) \ ++ ((plane) << (16 + (llane) * 4)) ++#define CSI2RX_STATIC_CFG_LANES_MASK GENMASK(11, 8) ++ ++#define CSI2RX_STREAM_BASE(n) (((n) + 1) * 0x100) ++ ++#define CSI2RX_STREAM_CTRL_REG(n) (CSI2RX_STREAM_BASE(n) + 0x000) ++#define CSI2RX_STREAM_CTRL_START BIT(0) ++ ++#define CSI2RX_STREAM_DATA_CFG_REG(n) (CSI2RX_STREAM_BASE(n) + 0x008) ++#define CSI2RX_STREAM_DATA_CFG_EN_VC_SELECT BIT(31) ++#define CSI2RX_STREAM_DATA_CFG_EN_DATA_TYPE_0 BIT(7) ++#define CSI2RX_STREAM_DATA_CFG_VC_SELECT(n) BIT((n) + 16) ++ ++#define CSI2RX_STREAM_CFG_REG(n) (CSI2RX_STREAM_BASE(n) + 0x00c) ++#define CSI2RX_STREAM_CFG_FIFO_MODE_LARGE_BUF (1 << 8) ++ ++#define CSI2RX_LANES_MAX 4 ++#define CSI2RX_STREAMS_MAX 4 ++ ++static int stf_csi_power_on(struct stf_csi_dev *csi_dev, u8 on) ++{ ++ struct stfcamss *stfcamss = csi_dev->stfcamss; ++ int ret; ++ ++ if (on) { ++ ret = regulator_enable(csi_dev->mipirx_1p8); ++ if (ret) { ++ st_err(ST_CSI, "Cannot enable mipirx_1p8 regulator\n"); ++ goto err_1p8; ++ } ++ ++ ret = regulator_enable(csi_dev->mipirx_0p9); ++ if (ret) { ++ st_err(ST_CSI, "Cannot enable mipirx_0p9 regulator\n"); ++ goto err_0p9; ++ } ++ } else { ++ regulator_disable(csi_dev->mipirx_1p8); ++ regulator_disable(csi_dev->mipirx_0p9); ++ } ++ ++ regmap_update_bits(stfcamss->stf_aon_syscon, stfcamss->aon_gp_reg, ++ BIT(31), BIT(31)); ++ ++ return 0; ++ ++err_0p9: ++ regulator_disable(csi_dev->mipirx_1p8); ++err_1p8: ++ return ret; ++ ++} ++ ++static int stf_csi_clk_enable(struct stf_csi_dev *csi_dev) ++{ ++ struct stfcamss *stfcamss = csi_dev->stfcamss; ++ ++ clk_set_rate(stfcamss->sys_clk[STFCLK_MIPI_RX0_PXL].clk, 198000000); ++ clk_prepare_enable(stfcamss->sys_clk[STFCLK_PIXEL_CLK_IF0].clk); ++ clk_prepare_enable(stfcamss->sys_clk[STFCLK_PIXEL_CLK_IF1].clk); ++ clk_prepare_enable(stfcamss->sys_clk[STFCLK_PIXEL_CLK_IF2].clk); ++ clk_prepare_enable(stfcamss->sys_clk[STFCLK_PIXEL_CLK_IF3].clk); ++ ++ reset_control_deassert(stfcamss->sys_rst[STFRST_PIXEL_CLK_IF0].rstc); ++ reset_control_deassert(stfcamss->sys_rst[STFRST_PIXEL_CLK_IF1].rstc); ++ reset_control_deassert(stfcamss->sys_rst[STFRST_PIXEL_CLK_IF2].rstc); ++ reset_control_deassert(stfcamss->sys_rst[STFRST_PIXEL_CLK_IF3].rstc); ++ ++ switch (csi_dev->s_type) { ++ case SENSOR_VIN: ++ reset_control_deassert(stfcamss->sys_rst[STFRST_AXIWR].rstc); ++ clk_set_parent(stfcamss->sys_clk[STFCLK_AXIWR].clk, ++ stfcamss->sys_clk[STFCLK_MIPI_RX0_PXL].clk); ++ break; ++ case SENSOR_ISP: ++ clk_set_parent(stfcamss->sys_clk[STFCLK_WRAPPER_CLK_C].clk, ++ stfcamss->sys_clk[STFCLK_MIPI_RX0_PXL].clk); ++ break; ++ } ++ ++ return 0; ++} ++ ++static int stf_csi_clk_disable(struct stf_csi_dev *csi_dev) ++{ ++ struct stfcamss *stfcamss = csi_dev->stfcamss; ++ ++ switch (csi_dev->s_type) { ++ case SENSOR_VIN: ++ reset_control_assert(stfcamss->sys_rst[STFRST_AXIWR].rstc); ++ break; ++ case SENSOR_ISP: ++ break; ++ } ++ ++ reset_control_assert(stfcamss->sys_rst[STFRST_PIXEL_CLK_IF3].rstc); ++ reset_control_assert(stfcamss->sys_rst[STFRST_PIXEL_CLK_IF2].rstc); ++ reset_control_assert(stfcamss->sys_rst[STFRST_PIXEL_CLK_IF1].rstc); ++ reset_control_assert(stfcamss->sys_rst[STFRST_PIXEL_CLK_IF0].rstc); ++ ++ clk_disable_unprepare(stfcamss->sys_clk[STFCLK_PIXEL_CLK_IF3].clk); ++ clk_disable_unprepare(stfcamss->sys_clk[STFCLK_PIXEL_CLK_IF2].clk); ++ clk_disable_unprepare(stfcamss->sys_clk[STFCLK_PIXEL_CLK_IF1].clk); ++ clk_disable_unprepare(stfcamss->sys_clk[STFCLK_PIXEL_CLK_IF0].clk); ++ ++ return 0; ++} ++ ++static void csi2rx_reset(void *reg_base) ++{ ++ writel(CSI2RX_SOFT_RESET_PROTOCOL | CSI2RX_SOFT_RESET_FRONT, ++ reg_base + CSI2RX_SOFT_RESET_REG); ++ ++ udelay(10); ++ ++ writel(0, reg_base + CSI2RX_SOFT_RESET_REG); ++} ++ ++static int csi2rx_start(struct stf_csi_dev *csi_dev, void *reg_base, u32 dt) ++{ ++ struct stfcamss *stfcamss = csi_dev->stfcamss; ++ struct csi2phy_cfg *csiphy = ++ stfcamss->csiphy_dev->csiphy; ++ unsigned int i; ++ unsigned long lanes_used = 0; ++ u32 reg; ++ ++ if (!csiphy) { ++ st_err(ST_CSI, "csiphy0 config not exist\n"); ++ return -EINVAL; ++ } ++ ++ csi2rx_reset(reg_base); ++ ++ reg = csiphy->num_data_lanes << 8; ++ for (i = 0; i < csiphy->num_data_lanes; i++) { ++#ifndef USE_CSIDPHY_ONE_CLK_MODE ++ reg |= CSI2RX_STATIC_CFG_DLANE_MAP(i, csiphy->data_lanes[i]); ++ set_bit(csiphy->data_lanes[i] - 1, &lanes_used); ++#else ++ reg |= CSI2RX_STATIC_CFG_DLANE_MAP(i, i + 1); ++ set_bit(i, &lanes_used); ++#endif ++ } ++ ++ /* ++ * Even the unused lanes need to be mapped. In order to avoid ++ * to map twice to the same physical lane, keep the lanes used ++ * in the previous loop, and only map unused physical lanes to ++ * the rest of our logical lanes. ++ */ ++ for (i = csiphy->num_data_lanes; i < CSI2RX_LANES_MAX; i++) { ++ unsigned int idx = find_first_zero_bit(&lanes_used, ++ CSI2RX_LANES_MAX); ++ ++ set_bit(idx, &lanes_used); ++ reg |= CSI2RX_STATIC_CFG_DLANE_MAP(i, idx + 1); ++ } ++ ++ writel(reg, reg_base + CSI2RX_STATIC_CFG_REG); ++ ++ // 0x40 DPHY_LANE_CONTROL ++ reg = 0; ++#ifndef USE_CSIDPHY_ONE_CLK_MODE ++ for (i = 0; i < csiphy->num_data_lanes; i++) ++ reg |= 1 << (csiphy->data_lanes[i] - 1) ++ | 1 << (csiphy->data_lanes[i] + 11); ++#else ++ for (i = 0; i < csiphy->num_data_lanes; i++) ++ reg |= 1 << i | 1 << (i + 12); //data_clane ++#endif ++ ++ reg |= 1 << 4 | 1 << 16; //clk_lane ++ writel(reg, reg_base + CSI2RX_DPHY_LANE_CONTROL); ++ ++ /* ++ * Create a static mapping between the CSI virtual channels ++ * and the output stream. ++ * ++ * This should be enhanced, but v4l2 lacks the support for ++ * changing that mapping dynamically. ++ * ++ * We also cannot enable and disable independent streams here, ++ * hence the reference counting. ++ */ ++ for (i = 0; i < CSI2RX_STREAMS_MAX; i++) { ++ writel(CSI2RX_STREAM_CFG_FIFO_MODE_LARGE_BUF, ++ reg_base + CSI2RX_STREAM_CFG_REG(i)); ++ ++ writel(CSI2RX_STREAM_DATA_CFG_EN_VC_SELECT | ++ CSI2RX_STREAM_DATA_CFG_VC_SELECT(i) | ++ CSI2RX_STREAM_DATA_CFG_EN_DATA_TYPE_0 | dt, ++ reg_base + CSI2RX_STREAM_DATA_CFG_REG(i)); ++ ++ writel(CSI2RX_STREAM_CTRL_START, ++ reg_base + CSI2RX_STREAM_CTRL_REG(i)); ++ } ++ ++ return 0; ++} ++ ++static void csi2rx_stop(struct stf_csi_dev *csi_dev, void *reg_base) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < CSI2RX_STREAMS_MAX; i++) ++ writel(0, reg_base + CSI2RX_STREAM_CTRL_REG(i)); ++} ++ ++static void csi_set_vin_axiwr_pix(struct stf_csi_dev *csi_dev, u32 width, u8 bpp) ++{ ++ struct stf_vin_dev *vin = csi_dev->stfcamss->vin; ++ u32 value = 0; ++ int cnfg_axiwr_pix_ct = 64 / bpp; ++ ++ if (cnfg_axiwr_pix_ct == 2) ++ value = 0; ++ else if (cnfg_axiwr_pix_ct == 4) ++ value = 1; ++ else if (cnfg_axiwr_pix_ct == 8) ++ value = 2; ++ ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_28, ++ BIT(14)|BIT(13), value << 13); //u0_vin_cnfg_axiwr0_pix_ct ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_28, ++ BIT(12)|BIT(11)|BIT(10)|BIT(9)|BIT(8)|BIT(7)|BIT(6)|BIT(5)|BIT(4)|BIT(3)|BIT(2), ++ (width / cnfg_axiwr_pix_ct - 1)<<2); //u0_vin_cnfg_axiwr0_pix_cnt_end ++} ++ ++static int stf_csi_stream_set(struct stf_csi_dev *csi_dev, ++ int on, u32 dt, u32 width, u8 bpp) ++{ ++ struct stf_vin_dev *vin = csi_dev->stfcamss->vin; ++ void __iomem *reg_base = vin->csi2rx_base; ++ ++ switch (csi_dev->s_type) { ++ case SENSOR_VIN: ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_20, ++ BIT(3)|BIT(2)|BIT(1)|BIT(0), ++ 0<<0); //u0_vin_cnfg_axiwr0_channel_sel ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_28, ++ BIT(16)|BIT(15), ++ 0<<15); //u0_vin_cnfg_axiwr0_pixel_high_bit_sel ++ csi_set_vin_axiwr_pix(csi_dev, width, bpp); ++ break; ++ case SENSOR_ISP: ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_36, ++ BIT(7)|BIT(6), ++ 0<<6); //u0_vin_cnfg_mipi_byte_en_isp ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_36, ++ BIT(11)|BIT(10)|BIT(9)|BIT(8), ++ 0<<8); //u0_vin_cnfg_mipi_channel_sel0 ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_36, ++ BIT(16)|BIT(15)|BIT(14)|BIT(13), ++ 0<<13); //u0_vin_cnfg_pix_num ++ ++ if (dt == 0x2b) ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_36, ++ BIT(12), ++ 1<<12); //u0_vin_cnfg_p_i_mipi_header_en0 ++ break; ++ } ++ ++ if (on) ++ csi2rx_start(csi_dev, reg_base, dt); ++ else ++ csi2rx_stop(csi_dev, reg_base); ++ ++ return 0; ++} ++ ++void dump_csi_reg(void *__iomem csibase) ++{ ++ st_info(ST_CSI, "DUMP CSI register:\n"); ++ print_reg(ST_CSI, csibase, 0x00); ++ print_reg(ST_CSI, csibase, 0x04); ++ print_reg(ST_CSI, csibase, 0x08); ++ print_reg(ST_CSI, csibase, 0x10); ++ ++ print_reg(ST_CSI, csibase, 0x40); ++ print_reg(ST_CSI, csibase, 0x48); ++ print_reg(ST_CSI, csibase, 0x4c); ++ print_reg(ST_CSI, csibase, 0x50); ++} ++ ++struct csi_hw_ops csi_ops = { ++ .csi_power_on = stf_csi_power_on, ++ .csi_clk_enable = stf_csi_clk_enable, ++ .csi_clk_disable = stf_csi_clk_disable, ++ .csi_stream_set = stf_csi_stream_set, ++}; +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_csiphy.c +@@ -0,0 +1,357 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include "stfcamss.h" ++#include <media/v4l2-async.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-event.h> ++#include <media/v4l2-fwnode.h> ++#include <media/v4l2-subdev.h> ++ ++static const struct csiphy_format csiphy_formats_st7110[] = { ++ { MEDIA_BUS_FMT_UYVY8_2X8, 16}, ++ { MEDIA_BUS_FMT_SRGGB10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGRBG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGBRG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SBGGR10_1X10, 10}, ++}; ++ ++int stf_csiphy_subdev_init(struct stfcamss *stfcamss) ++{ ++ struct stf_csiphy_dev *csiphy_dev = stfcamss->csiphy_dev; ++ ++ csiphy_dev->hw_ops = &csiphy_ops; ++ csiphy_dev->stfcamss = stfcamss; ++ csiphy_dev->formats = csiphy_formats_st7110; ++ csiphy_dev->nformats = ARRAY_SIZE(csiphy_formats_st7110); ++ mutex_init(&csiphy_dev->stream_lock); ++ return 0; ++} ++ ++static int csiphy_set_power(struct v4l2_subdev *sd, int on) ++{ ++ return 0; ++} ++ ++static int csiphy_set_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct stf_csiphy_dev *csiphy_dev = v4l2_get_subdevdata(sd); ++ ++ mutex_lock(&csiphy_dev->stream_lock); ++ if (enable) { ++ if (csiphy_dev->stream_count == 0) { ++ csiphy_dev->hw_ops->csiphy_clk_enable(csiphy_dev); ++ csiphy_dev->hw_ops->csiphy_config_set(csiphy_dev); ++ csiphy_dev->hw_ops->csiphy_stream_set(csiphy_dev, 1); ++ } ++ csiphy_dev->stream_count++; ++ } else { ++ if (csiphy_dev->stream_count == 0) ++ goto exit; ++ if (csiphy_dev->stream_count == 1) { ++ csiphy_dev->hw_ops->csiphy_clk_disable(csiphy_dev); ++ csiphy_dev->hw_ops->csiphy_stream_set(csiphy_dev, 0); ++ } ++ csiphy_dev->stream_count--; ++ } ++exit: ++ mutex_unlock(&csiphy_dev->stream_lock); ++ ++ return 0; ++} ++ ++static struct v4l2_mbus_framefmt * ++__csiphy_get_format(struct stf_csiphy_dev *csiphy_dev, ++ struct v4l2_subdev_state *state, ++ unsigned int pad, ++ enum v4l2_subdev_format_whence which) ++{ ++ if (which == V4L2_SUBDEV_FORMAT_TRY) ++ return v4l2_subdev_get_try_format( ++ &csiphy_dev->subdev, ++ state, ++ pad); ++ ++ return &csiphy_dev->fmt[pad]; ++} ++ ++static void csiphy_try_format(struct stf_csiphy_dev *csiphy_dev, ++ struct v4l2_subdev_state *state, ++ unsigned int pad, ++ struct v4l2_mbus_framefmt *fmt, ++ enum v4l2_subdev_format_whence which) ++{ ++ unsigned int i; ++ ++ switch (pad) { ++ case STF_CSIPHY_PAD_SINK: ++ /* Set format on sink pad */ ++ ++ for (i = 0; i < csiphy_dev->nformats; i++) ++ if (fmt->code == csiphy_dev->formats[i].code) ++ break; ++ ++ if (i >= csiphy_dev->nformats) ++ fmt->code = csiphy_dev->formats[0].code; ++ ++ fmt->width = clamp_t(u32, ++ fmt->width, ++ STFCAMSS_FRAME_MIN_WIDTH, ++ STFCAMSS_FRAME_MAX_WIDTH); ++ fmt->height = clamp_t(u32, ++ fmt->height, ++ STFCAMSS_FRAME_MIN_HEIGHT, ++ STFCAMSS_FRAME_MAX_HEIGHT); ++ ++ fmt->field = V4L2_FIELD_NONE; ++ fmt->colorspace = V4L2_COLORSPACE_SRGB; ++ fmt->flags = 0; ++ ++ break; ++ ++ case STF_CSIPHY_PAD_SRC: ++ ++ *fmt = *__csiphy_get_format(csiphy_dev, ++ state, ++ STF_CSIPHY_PAD_SINK, which); ++ ++ break; ++ } ++} ++ ++static int csiphy_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct stf_csiphy_dev *csiphy_dev = v4l2_get_subdevdata(sd); ++ ++ if (code->index >= csiphy_dev->nformats) ++ return -EINVAL; ++ ++ if (code->pad == STF_CSIPHY_PAD_SINK) { ++ code->code = csiphy_dev->formats[code->index].code; ++ } else { ++ struct v4l2_mbus_framefmt *sink_fmt; ++ ++ sink_fmt = __csiphy_get_format(csiphy_dev, state, ++ STF_CSIPHY_PAD_SINK, ++ code->which); ++ ++ code->code = sink_fmt->code; ++ if (!code->code) ++ return -EINVAL; ++ } ++ code->flags = 0; ++ return 0; ++} ++ ++static int csiphy_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ struct stf_csiphy_dev *csiphy_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt format; ++ ++ if (fse->index != 0) ++ return -EINVAL; ++ ++ format.code = fse->code; ++ format.width = 1; ++ format.height = 1; ++ csiphy_try_format(csiphy_dev, state, fse->pad, &format, fse->which); ++ fse->min_width = format.width; ++ fse->min_height = format.height; ++ ++ if (format.code != fse->code) ++ return -EINVAL; ++ ++ format.code = fse->code; ++ format.width = -1; ++ format.height = -1; ++ csiphy_try_format(csiphy_dev, state, fse->pad, &format, fse->which); ++ fse->max_width = format.width; ++ fse->max_height = format.height; ++ ++ return 0; ++} ++ ++static int csiphy_get_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct stf_csiphy_dev *csiphy_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt *format; ++ ++ format = __csiphy_get_format(csiphy_dev, state, fmt->pad, fmt->which); ++ if (format == NULL) ++ return -EINVAL; ++ ++ fmt->format = *format; ++ ++ return 0; ++} ++ ++static int csiphy_set_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct stf_csiphy_dev *csiphy_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt *format; ++ ++ format = __csiphy_get_format(csiphy_dev, state, fmt->pad, fmt->which); ++ if (format == NULL) ++ return -EINVAL; ++ ++ mutex_lock(&csiphy_dev->stream_lock); ++ if (csiphy_dev->stream_count) { ++ fmt->format = *format; ++ mutex_unlock(&csiphy_dev->stream_lock); ++ goto out; ++ } else { ++ csiphy_try_format(csiphy_dev, state, fmt->pad, &fmt->format, fmt->which); ++ *format = fmt->format; ++ } ++ mutex_unlock(&csiphy_dev->stream_lock); ++ ++ /* Propagate the format from sink to source */ ++ if (fmt->pad == STF_CSIPHY_PAD_SINK) { ++ format = __csiphy_get_format(csiphy_dev, ++ state, ++ STF_CSIPHY_PAD_SRC, ++ fmt->which); ++ ++ *format = fmt->format; ++ csiphy_try_format(csiphy_dev, state, STF_CSIPHY_PAD_SRC, format, ++ fmt->which); ++ } ++out: ++ return 0; ++} ++ ++static int csiphy_init_formats(struct v4l2_subdev *sd, ++ struct v4l2_subdev_fh *fh) ++{ ++ struct v4l2_subdev_format format = { ++ .pad = STF_CSIPHY_PAD_SINK, ++ .which = fh ? V4L2_SUBDEV_FORMAT_TRY : ++ V4L2_SUBDEV_FORMAT_ACTIVE, ++ .format = { ++ .code = MEDIA_BUS_FMT_RGB565_2X8_LE, ++ .width = 1920, ++ .height = 1080 ++ } ++ }; ++ ++ return csiphy_set_format(sd, fh ? fh->state : NULL, &format); ++} ++ ++static int csiphy_link_setup(struct media_entity *entity, ++ const struct media_pad *local, ++ const struct media_pad *remote, u32 flags) ++{ ++ if ((local->flags & MEDIA_PAD_FL_SOURCE) && ++ (flags & MEDIA_LNK_FL_ENABLED)) { ++ struct v4l2_subdev *sd; ++ struct stf_csiphy_dev *csiphy_dev; ++ struct stf_csi_dev *csi_dev; ++ ++ if (media_pad_remote_pad_first(local)) ++ return -EBUSY; ++ ++ sd = media_entity_to_v4l2_subdev(entity); ++ csiphy_dev = v4l2_get_subdevdata(sd); ++ ++ sd = media_entity_to_v4l2_subdev(remote->entity); ++ csi_dev = v4l2_get_subdevdata(sd); ++ st_info(ST_CSIPHY, "CSIPHY0 link to CSI0\n"); ++ } ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_core_ops csiphy_core_ops = { ++ .s_power = csiphy_set_power, ++}; ++ ++static const struct v4l2_subdev_video_ops csiphy_video_ops = { ++ .s_stream = csiphy_set_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops csiphy_pad_ops = { ++ .enum_mbus_code = csiphy_enum_mbus_code, ++ .enum_frame_size = csiphy_enum_frame_size, ++ .get_fmt = csiphy_get_format, ++ .set_fmt = csiphy_set_format, ++}; ++ ++static const struct v4l2_subdev_ops csiphy_v4l2_ops = { ++ .core = &csiphy_core_ops, ++ .video = &csiphy_video_ops, ++ .pad = &csiphy_pad_ops, ++}; ++ ++static const struct v4l2_subdev_internal_ops csiphy_v4l2_internal_ops = { ++ .open = csiphy_init_formats, ++}; ++ ++static const struct media_entity_operations csiphy_media_ops = { ++ .link_setup = csiphy_link_setup, ++ .link_validate = v4l2_subdev_link_validate, ++}; ++ ++int stf_csiphy_register(struct stf_csiphy_dev *csiphy_dev, ++ struct v4l2_device *v4l2_dev) ++{ ++ struct v4l2_subdev *sd = &csiphy_dev->subdev; ++ struct device *dev = csiphy_dev->stfcamss->dev; ++ struct media_pad *pads = csiphy_dev->pads; ++ int ret; ++ ++ v4l2_subdev_init(sd, &csiphy_v4l2_ops); ++ sd->internal_ops = &csiphy_v4l2_internal_ops; ++ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ++ snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", ++ STF_CSIPHY_NAME, 0); ++ v4l2_set_subdevdata(sd, csiphy_dev); ++ ++ ret = csiphy_init_formats(sd, NULL); ++ if (ret < 0) { ++ dev_err(dev, "Failed to init format: %d\n", ret); ++ return ret; ++ } ++ ++ pads[STF_CSIPHY_PAD_SINK].flags = MEDIA_PAD_FL_SINK; ++ pads[STF_CSIPHY_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; ++ ++ sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; ++ sd->entity.ops = &csiphy_media_ops; ++ ret = media_entity_pads_init(&sd->entity, STF_CSIPHY_PADS_NUM, pads); ++ if (ret < 0) { ++ dev_err(dev, "Failed to init media entity: %d\n", ret); ++ return ret; ++ } ++ ++ ret = v4l2_device_register_subdev(v4l2_dev, sd); ++ if (ret < 0) { ++ dev_err(dev, "Failed to register subdev: %d\n", ret); ++ goto err_sreg; ++ } ++ ++ return 0; ++ ++err_sreg: ++ media_entity_cleanup(&sd->entity); ++ return ret; ++} ++ ++int stf_csiphy_unregister(struct stf_csiphy_dev *csiphy_dev) ++{ ++ v4l2_device_unregister_subdev(&csiphy_dev->subdev); ++ media_entity_cleanup(&csiphy_dev->subdev.entity); ++ mutex_destroy(&csiphy_dev->stream_lock); ++ return 0; ++} +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_csiphy.h +@@ -0,0 +1,188 @@ ++/* SPDX-License-Identifier: GPL-2.0 ++ * ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#ifndef STF_CSIPHY_H ++#define STF_CSIPHY_H ++ ++#include <media/v4l2-subdev.h> ++#include <media/v4l2-device.h> ++#include <media/media-entity.h> ++#include <video/stf-vin.h> ++ ++#define STF_CSIPHY_NAME "stf_csiphy" ++ ++#define STF_CSIPHY_PAD_SINK 0 ++#define STF_CSIPHY_PAD_SRC 1 ++#define STF_CSIPHY_PADS_NUM 2 ++ ++#define STF_CSI2_MAX_DATA_LANES 4 ++ ++union static_config { ++ u32 raw; ++ struct { ++ u32 sel : 2; ++ u32 rsvd_6 : 2; ++ u32 v2p0_support_enable : 1; ++ u32 rsvd_5 : 3; ++ u32 lane_nb : 3; ++ u32 rsvd_4 : 5; ++ u32 dl0_map : 3; ++ u32 rsvd_3 : 1; ++ u32 dl1_map : 3; ++ u32 rsvd_2 : 1; ++ u32 dl2_map : 3; ++ u32 rsvd_1 : 1; ++ u32 dl3_map : 3; ++ u32 rsvd_0 : 1; ++ } bits; ++}; ++ ++union error_bypass_cfg { ++ u32 value; ++ struct { ++ u32 crc : 1; ++ u32 ecc : 1; ++ u32 data_id : 1; ++ u32 rsvd_0 : 29; ++ }; ++}; ++ ++union stream_monitor_ctrl { ++ u32 value; ++ struct { ++ u32 lb_vc : 4; ++ u32 lb_en : 1; ++ u32 timer_vc : 4; ++ u32 timer_en : 1; ++ u32 timer_eof : 1; ++ u32 frame_mon_vc : 4; ++ u32 frame_mon_en : 1; ++ u32 frame_length : 16; ++ }; ++}; ++ ++union stream_cfg { ++ u32 value; ++ struct { ++ u32 interface_mode : 1; ++ u32 ls_le_mode : 1; ++ u32 rsvd_3 : 2; ++ u32 num_pixels : 2; ++ u32 rsvd_2 : 2; ++ u32 fifo_mode : 2; ++ u32 rsvd_1 : 2; ++ u32 bpp_bypass : 3; ++ u32 rsvd_0 : 1; ++ u32 fifo_fill : 16; ++ }; ++}; ++ ++union dphy_lane_ctrl { ++ u32 raw; ++ struct { ++ u32 dl0_en : 1; ++ u32 dl1_en : 1; ++ u32 dl2_en : 1; ++ u32 dl3_en : 1; ++ u32 cl_en : 1; ++ u32 rsvd_1 : 7; ++ u32 dl0_reset : 1; ++ u32 dl1_reset : 1; ++ u32 dl2_reset : 1; ++ u32 dl3_reset : 1; ++ u32 cl_reset : 1; ++ u32 rsvd_0 : 15; ++ } bits; ++}; ++ ++union dphy_lane_swap { ++ u32 raw; ++ struct { ++ u32 rx_1c2c_sel : 1; ++ u32 lane_swap_clk : 3; ++ u32 lane_swap_clk1 : 3; ++ u32 lane_swap_lan0 : 3; ++ u32 lane_swap_lan1 : 3; ++ u32 lane_swap_lan2 : 3; ++ u32 lane_swap_lan3 : 3; ++ u32 dpdn_swap_clk : 1; ++ u32 dpdn_swap_clk1 : 1; ++ u32 dpdn_swap_lan0 : 1; ++ u32 dpdn_swap_lan1 : 1; ++ u32 dpdn_swap_lan2 : 1; ++ u32 dpdn_swap_lan3 : 1; ++ u32 hs_freq_chang_clk0 : 1; ++ u32 hs_freq_chang_clk1 : 1; ++ u32 reserved : 5; ++ } bits; ++}; ++ ++union dphy_lane_en { ++ u32 raw; ++ struct { ++ u32 gpio_en : 6; ++ u32 mp_test_mode_sel : 5; ++ u32 mp_test_en : 1; ++ u32 dphy_enable_lan0 : 1; ++ u32 dphy_enable_lan1 : 1; ++ u32 dphy_enable_lan2 : 1; ++ u32 dphy_enable_lan3 : 1; ++ u32 rsvd_0 : 16; ++ } bits; ++}; ++ ++struct csiphy_format { ++ u32 code; ++ u8 bpp; ++}; ++ ++struct csi2phy_cfg { ++ unsigned int flags; ++ unsigned char data_lanes[STF_CSI2_MAX_DATA_LANES]; ++ unsigned char clock_lane; ++ unsigned char num_data_lanes; ++ bool lane_polarities[1 + STF_CSI2_MAX_DATA_LANES]; ++}; ++ ++struct csi2phy_cfg2 { ++ unsigned char data_lanes[STF_CSI2_MAX_DATA_LANES]; ++ unsigned char num_data_lanes; ++ unsigned char num_clks; ++ unsigned char clock_lane; ++ unsigned char clock1_lane; ++ bool lane_polarities[2 + STF_CSI2_MAX_DATA_LANES]; ++}; ++ ++struct stf_csiphy_dev; ++ ++struct csiphy_hw_ops { ++ int (*csiphy_clk_enable)(struct stf_csiphy_dev *csiphy_dev); ++ int (*csiphy_clk_disable)(struct stf_csiphy_dev *csiphy_dev); ++ int (*csiphy_config_set)(struct stf_csiphy_dev *csiphy_dev); ++ int (*csiphy_stream_set)(struct stf_csiphy_dev *csiphy_dev, int on); ++}; ++ ++struct stf_csiphy_dev { ++ struct stfcamss *stfcamss; ++ struct csi2phy_cfg *csiphy; ++ struct v4l2_subdev subdev; ++ struct media_pad pads[STF_CSIPHY_PADS_NUM]; ++ struct v4l2_mbus_framefmt fmt[STF_CSIPHY_PADS_NUM]; ++ const struct csiphy_format *formats; ++ unsigned int nformats; ++ struct csiphy_hw_ops *hw_ops; ++ struct mutex stream_lock; ++ int stream_count; ++}; ++ ++extern int stf_csiphy_subdev_init(struct stfcamss *stfcamss); ++extern int stf_csiphy_register(struct stf_csiphy_dev *csiphy_dev, ++ struct v4l2_device *v4l2_dev); ++extern int stf_csiphy_unregister(struct stf_csiphy_dev *csiphy_dev); ++ ++extern struct csiphy_hw_ops csiphy_ops; ++ ++#endif /* STF_CSIPHY_H */ +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_csiphy_hw_ops.c +@@ -0,0 +1,335 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include "stfcamss.h" ++#include <linux/sort.h> ++ ++static int stf_csiphy_clk_set(struct stf_csiphy_dev *csiphy_dev, int on) ++{ ++ struct stfcamss *stfcamss = csiphy_dev->stfcamss; ++ static int init_flag; ++ static struct mutex count_lock; ++ static int count; ++ ++ if (!init_flag) { ++ init_flag = 1; ++ mutex_init(&count_lock); ++ } ++ mutex_lock(&count_lock); ++ if (on) { ++ clk_set_rate(stfcamss->sys_clk[STFCLK_M31DPHY_CFGCLK_IN].clk, ++ 99000000); ++ clk_set_rate(stfcamss->sys_clk[STFCLK_M31DPHY_REFCLK_IN].clk, ++ 49500000); ++ clk_set_rate(stfcamss->sys_clk[STFCLK_M31DPHY_TXCLKESC_LAN0].clk, ++ 19800000); ++ ++ reset_control_deassert(stfcamss->sys_rst[STFRST_M31DPHY_HW].rstc); ++ reset_control_deassert(stfcamss->sys_rst[STFRST_M31DPHY_B09_ALWAYS_ON].rstc); ++ ++ count++; ++ } else { ++ if (count == 0) ++ goto exit; ++ if (count == 1) { ++ reset_control_assert(stfcamss->sys_rst[STFRST_M31DPHY_HW].rstc); ++ reset_control_assert(stfcamss->sys_rst[STFRST_M31DPHY_B09_ALWAYS_ON].rstc); ++ } ++ count--; ++ } ++exit: ++ mutex_unlock(&count_lock); ++ return 0; ++} ++ ++static int stf_csiphy_clk_enable(struct stf_csiphy_dev *csiphy_dev) ++{ ++ return stf_csiphy_clk_set(csiphy_dev, 1); ++} ++ ++static int stf_csiphy_clk_disable(struct stf_csiphy_dev *csiphy_dev) ++{ ++ return stf_csiphy_clk_set(csiphy_dev, 0); ++} ++ ++#ifndef USE_CSIDPHY_ONE_CLK_MODE ++static int cmp_func(const void *x1, const void *x2) ++{ ++ return *((unsigned char *)x1) - *((unsigned char *)x2); ++} ++#endif ++ ++int try_cfg(struct csi2phy_cfg2 *cfg, struct csi2phy_cfg *cfg0, ++ struct csi2phy_cfg *cfg1) ++{ ++ int i = 0; ++ ++ cfg->clock_lane = 0; ++ cfg->clock1_lane = 5; ++ cfg->data_lanes[0] = 1; ++ cfg->data_lanes[1] = 2; ++ cfg->data_lanes[2] = 3; ++ cfg->data_lanes[3] = 4; ++ ++ if (cfg0 && cfg1) { ++ st_debug(ST_CSIPHY, "CSIPHY use 2 clk mode\n"); ++ cfg->num_clks = 2; ++ cfg->num_data_lanes = ++ cfg1->num_data_lanes + cfg0->num_data_lanes; ++ if (cfg->num_data_lanes > STF_CSI2_MAX_DATA_LANES) ++ return -EINVAL; ++ cfg->clock_lane = cfg0->clock_lane; ++ cfg->lane_polarities[0] = cfg0->lane_polarities[0]; ++ cfg->clock1_lane = cfg1->clock_lane; ++ cfg->lane_polarities[1] = cfg1->lane_polarities[0]; ++ for (i = 0; i < cfg0->num_data_lanes; i++) { ++ cfg->data_lanes[i] = cfg0->data_lanes[i]; ++ cfg->lane_polarities[i + 2] = ++ cfg0->lane_polarities[i + 1]; ++ } ++ ++ for (i = cfg0->num_data_lanes; i < cfg->num_data_lanes; i++) { ++ cfg->data_lanes[i] = ++ cfg1->data_lanes[i - cfg0->num_data_lanes]; ++ cfg->lane_polarities[i + 2] = ++ cfg1->lane_polarities[i - cfg0->num_data_lanes + 1]; ++ } ++ } else if (cfg0 && !cfg1) { ++ st_debug(ST_CSIPHY, "CSIPHY cfg0 use 1 clk mode\n"); ++ cfg->num_clks = 1; ++ cfg->num_data_lanes = cfg0->num_data_lanes; ++ cfg->clock_lane = cfg->clock1_lane = cfg0->clock_lane; ++ cfg->lane_polarities[0] = cfg->lane_polarities[1] = ++ cfg0->lane_polarities[0]; ++ for (i = 0; i < cfg0->num_data_lanes; i++) { ++ cfg->data_lanes[i] = cfg0->data_lanes[i]; ++ cfg->lane_polarities[i + 2] = cfg0->lane_polarities[i + 1]; ++ } ++ } else if (!cfg0 && cfg1) { ++ st_debug(ST_CSIPHY, "CSIPHY cfg1 use 1 clk mode\n"); ++ cfg->num_clks = 1; ++ cfg->num_data_lanes = cfg1->num_data_lanes; ++ cfg->clock_lane = cfg->clock1_lane = cfg1->clock_lane; ++ cfg->lane_polarities[0] = cfg->lane_polarities[1] = ++ cfg1->lane_polarities[0]; ++ for (i = 0; i < cfg1->num_data_lanes; i++) { ++ cfg->data_lanes[i] = cfg1->data_lanes[i]; ++ cfg->lane_polarities[i + 2] = cfg1->lane_polarities[i + 1]; ++ } ++ } else { ++ return -EINVAL; ++ } ++ ++#ifndef USE_CSIDPHY_ONE_CLK_MODE ++ sort(cfg->data_lanes, cfg->num_data_lanes, ++ sizeof(cfg->data_lanes[0]), cmp_func, NULL); ++#endif ++ for (i = 0; i < cfg->num_data_lanes; i++) ++ st_debug(ST_CSIPHY, "%d: %d\n", i, cfg->data_lanes[i]); ++ return 0; ++} ++ ++static int csi2rx_dphy_config(struct stf_vin_dev *vin, ++ struct stf_csiphy_dev *csiphy_dev) ++{ ++ struct csi2phy_cfg2 cfg2 = {0}; ++ struct csi2phy_cfg2 *cfg = &cfg2; ++ struct csi2phy_cfg *phycfg = csiphy_dev->csiphy; ++ ++ if (!phycfg) ++ return -EINVAL; ++ ++ if (try_cfg(cfg, phycfg, NULL)) ++ return -EINVAL; ++ ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_4, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_8, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_12, 0xfff0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_16, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_20, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_24, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_28, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_32, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_36, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_40, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_44, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_48, 0x24000000); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_52, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_56, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_60, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_64, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_68, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_72, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_76, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_80, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_84, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_88, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_92, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_96, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_100, 0x02000000); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_104, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_108, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_112, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_116, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_120, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_124, 0xc); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_128, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_132, 0xcc500000); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_136, 0xcc); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_140, 0x0); ++ reg_write(vin->rstgen_base, M31DPHY_APBCFGSAIF__SYSCFG_144, 0x0); ++ ++ reg_set_bit(vin->rstgen_base, //r100_ctrl0_2d1c_efuse_en ++ M31DPHY_APBCFGSAIF__SYSCFG_0, ++ BIT(6), 1<<6); ++ reg_set_bit(vin->rstgen_base, //r100_ctrl0_2d1c_efuse_in ++ M31DPHY_APBCFGSAIF__SYSCFG_0, ++ BIT(12)|BIT(11)|BIT(10)|BIT(9)|BIT(8)|BIT(7), 0x1b<<7); ++ reg_set_bit(vin->rstgen_base, //r100_ctrl1_2d1c_efuse_en ++ M31DPHY_APBCFGSAIF__SYSCFG_0, ++ BIT(13), 1<<13); ++ reg_set_bit(vin->rstgen_base, //r100_ctrl1_2d1c_efuse_in ++ M31DPHY_APBCFGSAIF__SYSCFG_0, ++ BIT(19)|BIT(18)|BIT(17)|BIT(16)|BIT(15)|BIT(14), 0x1b<<14); ++ ++ reg_set_bit(vin->rstgen_base, //data_bus16_8 ++ M31DPHY_APBCFGSAIF__SYSCFG_184, ++ BIT(8), 0<<8); ++ ++ reg_set_bit(vin->rstgen_base, //debug_mode_sel ++ M31DPHY_APBCFGSAIF__SYSCFG_184, ++ BIT(15)|BIT(14)|BIT(13)|BIT(12)|BIT(11)|BIT(10)|BIT(9), 0x5a<<9); ++ ++ reg_set_bit(vin->rstgen_base, //dpdn_swap_clk0 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(0), cfg->lane_polarities[0]<<0); ++ reg_set_bit(vin->rstgen_base, //dpdn_swap_clk1 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(1), cfg->lane_polarities[1]<<1); ++ reg_set_bit(vin->rstgen_base, //dpdn_swap_lan0 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(2), cfg->lane_polarities[2]<<2); ++ reg_set_bit(vin->rstgen_base, //dpdn_swap_lan1 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(3), cfg->lane_polarities[3]<<3); ++ reg_set_bit(vin->rstgen_base, //dpdn_swap_lan2 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(4), cfg->lane_polarities[4]<<4); ++ reg_set_bit(vin->rstgen_base, //dpdn_swap_lan3 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(5), cfg->lane_polarities[5]<<5); ++ reg_set_bit(vin->rstgen_base, //enable clk0 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(6), 1<<6); ++ reg_set_bit(vin->rstgen_base, //enable clk1 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(7), 1<<7); ++ reg_set_bit(vin->rstgen_base, //enable lan0 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(8), 1<<8); ++ reg_set_bit(vin->rstgen_base, //enable lan1 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(9), 1<<9); ++ reg_set_bit(vin->rstgen_base, //enable lan2 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(10), 1<<10); ++ reg_set_bit(vin->rstgen_base, //enable lan3 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(11), 1<<11); ++ reg_set_bit(vin->rstgen_base, //gpi_en ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(17)|BIT(16)|BIT(15)|BIT(14)|BIT(13)|BIT(12), ++ 0<<12); ++ reg_set_bit(vin->rstgen_base, //hs_freq_change_clk0 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(18), 0<<18); ++ reg_set_bit(vin->rstgen_base, //hs_freq_change_clk1 ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(19), 0<<19); ++ ++ reg_set_bit(vin->rstgen_base, ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(22)|BIT(21)|BIT(20), cfg->clock_lane<<20); //clock lane 0 ++ reg_set_bit(vin->rstgen_base, ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(25)|BIT(24)|BIT(23), cfg->clock1_lane<<23); //clock lane 1 ++ ++ reg_set_bit(vin->rstgen_base, ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(28)|BIT(27)|BIT(26), cfg->data_lanes[0]<<26); //data lane 0 ++ reg_set_bit(vin->rstgen_base, ++ M31DPHY_APBCFGSAIF__SYSCFG_188, ++ BIT(31)|BIT(30)|BIT(29), cfg->data_lanes[1]<<29); //data lane 1 ++ reg_set_bit(vin->rstgen_base, ++ M31DPHY_APBCFGSAIF__SYSCFG_192, ++ BIT(2)|BIT(1)|BIT(0), cfg->data_lanes[2]<<0); //data lane 2 ++ reg_set_bit(vin->rstgen_base, ++ M31DPHY_APBCFGSAIF__SYSCFG_192, ++ BIT(5)|BIT(4)|BIT(3), cfg->data_lanes[3]<<3); //data lane 3 ++ ++ reg_set_bit(vin->rstgen_base, //mp_test_en ++ M31DPHY_APBCFGSAIF__SYSCFG_192, ++ BIT(6), 0<<6); ++ reg_set_bit(vin->rstgen_base, //mp_test_mode_sel ++ M31DPHY_APBCFGSAIF__SYSCFG_192, ++ BIT(11)|BIT(10)|BIT(9)|BIT(8)|BIT(7), 0<<7); ++ ++ reg_set_bit(vin->rstgen_base, //pll_clk_sel ++ M31DPHY_APBCFGSAIF__SYSCFG_192, ++ BIT(20)|BIT(19)|BIT(18)|BIT(17)|BIT(16)|BIT(15)|BIT(14)|BIT(13)|BIT(12), ++ 0x37c<<12); ++ ++ reg_set_bit(vin->rstgen_base, //rx_1c2c_sel ++ M31DPHY_APBCFGSAIF__SYSCFG_200, ++ BIT(8), 0<<8); ++ ++ reg_set_bit(vin->rstgen_base, //precounter in clk0 ++ M31DPHY_APBCFGSAIF__SYSCFG_192, ++ BIT(29)|BIT(28)|BIT(27)|BIT(26)|BIT(25)|BIT(24)|BIT(23)|BIT(22), ++ 8<<22); ++ reg_set_bit(vin->rstgen_base, //precounter in clk1 ++ M31DPHY_APBCFGSAIF__SYSCFG_196, ++ BIT(7)|BIT(6)|BIT(5)|BIT(4)|BIT(3)|BIT(2)|BIT(1)|BIT(0), ++ 8<<0); ++ reg_set_bit(vin->rstgen_base, //precounter in lan0 ++ M31DPHY_APBCFGSAIF__SYSCFG_196, ++ BIT(15)|BIT(14)|BIT(13)|BIT(12)|BIT(11)|BIT(10)|BIT(9)|BIT(8), ++ 7<<8); ++ reg_set_bit(vin->rstgen_base, //precounter in lan1 ++ M31DPHY_APBCFGSAIF__SYSCFG_196, ++ BIT(23)|BIT(22)|BIT(21)|BIT(20)|BIT(19)|BIT(18)|BIT(17)|BIT(16), ++ 7<<16); ++ reg_set_bit(vin->rstgen_base, //precounter in lan2 ++ M31DPHY_APBCFGSAIF__SYSCFG_196, ++ BIT(31)|BIT(30)|BIT(29)|BIT(28)|BIT(27)|BIT(26)|BIT(25)|BIT(24), ++ 7<<24); ++ reg_set_bit(vin->rstgen_base, //precounter in lan3 ++ M31DPHY_APBCFGSAIF__SYSCFG_200, ++ BIT(7)|BIT(6)|BIT(5)|BIT(4)|BIT(3)|BIT(2)|BIT(1)|BIT(0), ++ 7<<0); ++ ++ return 0; ++} ++ ++static int stf_csiphy_config_set(struct stf_csiphy_dev *csiphy_dev) ++{ ++ struct stf_vin_dev *vin = csiphy_dev->stfcamss->vin; ++ ++ csi2rx_dphy_config(vin, csiphy_dev); ++ return 0; ++} ++ ++static int stf_csiphy_stream_set(struct stf_csiphy_dev *csiphy_dev, int on) ++{ ++ return 0; ++} ++ ++struct csiphy_hw_ops csiphy_ops = { ++ .csiphy_clk_enable = stf_csiphy_clk_enable, ++ .csiphy_clk_disable = stf_csiphy_clk_disable, ++ .csiphy_config_set = stf_csiphy_config_set, ++ .csiphy_stream_set = stf_csiphy_stream_set, ++}; +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_dmabuf.c +@@ -0,0 +1,123 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include <linux/dma-buf.h> ++#include <media/v4l2-subdev.h> ++#include <media/videobuf2-dma-contig.h> ++ ++#include "stf_isp_ioctl.h" ++#include "stf_dmabuf.h" ++ ++#define TOTAL_SIZE_LIMIT (64 * 1024 * 1024) ++ ++static size_t total_size; ++static struct vb2_queue vb2_queue = { ++ .dma_attrs = 0, ++ .gfp_flags = 0, ++ .dma_dir = DMA_TO_DEVICE, ++}; ++static struct vb2_buffer vb = { ++ .vb2_queue = &vb2_queue, ++}; ++ ++static int dmabuf_create(struct device *dev, ++ struct dmabuf_create *head) ++{ ++ struct dma_buf *dmabuf = NULL; ++ void *mem_priv = NULL; ++ dma_addr_t *paddr = NULL; ++ int ret = 0; ++ ++ mem_priv = vb2_dma_contig_memops.alloc(&vb, dev, head->size); ++ if (IS_ERR_OR_NULL(mem_priv)) { ++ if (mem_priv) ++ ret = PTR_ERR(mem_priv); ++ goto exit; ++ } ++ ++ dmabuf = vb2_dma_contig_memops.get_dmabuf(&vb, mem_priv, O_RDWR); ++ if (IS_ERR(dmabuf)) { ++ ret = PTR_ERR(dmabuf); ++ goto free; ++ } ++ ++ head->fd = dma_buf_fd(dmabuf, O_CLOEXEC); ++ if (head->fd < 0) { ++ dma_buf_put(dmabuf); ++ ret = head->fd; ++ goto free; ++ } ++ ++ paddr = vb2_dma_contig_memops.cookie(&vb, mem_priv); ++ head->paddr = *paddr; ++ return 0; ++free: ++ vb2_dma_contig_memops.put(mem_priv); ++exit: ++ return ret; ++} ++ ++int stf_dmabuf_ioctl_alloc(struct device *dev, void *arg) ++{ ++ struct dmabuf_create *head = arg; ++ int ret = -EINVAL; ++ ++ if (IS_ERR_OR_NULL(head)) ++ return -EFAULT; ++ ++ head->size = PAGE_ALIGN(head->size); ++ if (!head->size) ++ return -EINVAL; ++ if ((head->size + total_size) > TOTAL_SIZE_LIMIT) ++ return -ENOMEM; ++ ++ ret = dmabuf_create(dev, head); ++ if (ret) ++ return -EFAULT; ++ ++ total_size += head->size; ++ return ret; ++} ++ ++int stf_dmabuf_ioctl_free(struct device *dev, void *arg) ++{ ++ struct dmabuf_create *head = arg; ++ struct dma_buf *dmabuf = NULL; ++ int ret = 0; ++ ++ if (IS_ERR_OR_NULL(head)) ++ return -EFAULT; ++ if (head->size != PAGE_ALIGN(head->size)) ++ return -EINVAL; ++ if (head->size > total_size) ++ return -EINVAL; ++ ++ dmabuf = dma_buf_get(head->fd); ++ if (IS_ERR_OR_NULL(dmabuf)) ++ return -EINVAL; ++ ++ dma_buf_put(dmabuf); ++ vb2_dma_contig_memops.put(dmabuf->priv); ++ total_size -= head->size; ++ return ret; ++} ++ ++int stf_dmabuf_ioctl(struct device *dev, unsigned int cmd, void *arg) ++{ ++ int ret = -ENOIOCTLCMD; ++ ++ switch (cmd) { ++ case VIDIOC_STF_DMABUF_ALLOC: ++ ret = stf_dmabuf_ioctl_alloc(dev, arg); ++ break; ++ case VIDIOC_STF_DMABUF_FREE: ++ ret = stf_dmabuf_ioctl_free(dev, arg); ++ break; ++ default: ++ break; ++ } ++ return ret; ++} +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_dmabuf.h +@@ -0,0 +1,12 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#ifndef STF_DMABUF_H ++#define STF_DMABUF_H ++ ++extern int stf_dmabuf_ioctl(struct device *dev, unsigned int cmd, void *arg); ++ ++#endif /* STF_DMABUF_H */ +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_dvp.c +@@ -0,0 +1,385 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include "stfcamss.h" ++#include <media/v4l2-async.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-event.h> ++#include <media/v4l2-fwnode.h> ++#include <media/v4l2-subdev.h> ++ ++static const struct dvp_format dvp_formats_st7110[] = { ++ { MEDIA_BUS_FMT_YUYV8_2X8, 8}, ++ { MEDIA_BUS_FMT_RGB565_2X8_LE, 8}, ++ { MEDIA_BUS_FMT_SRGGB8_1X8, 8}, ++ { MEDIA_BUS_FMT_SGRBG8_1X8, 8}, ++ { MEDIA_BUS_FMT_SGBRG8_1X8, 8}, ++ { MEDIA_BUS_FMT_SBGGR8_1X8, 8}, ++ { MEDIA_BUS_FMT_SRGGB10_1X10, 8}, ++ { MEDIA_BUS_FMT_SGRBG10_1X10, 8}, ++ { MEDIA_BUS_FMT_SGBRG10_1X10, 8}, ++ { MEDIA_BUS_FMT_SBGGR10_1X10, 8}, ++}; ++ ++static int dvp_find_format(u32 code, ++ const struct dvp_format *formats, ++ unsigned int nformats) ++{ ++ int i; ++ ++ for (i = 0; i < nformats; i++) ++ if (formats[i].code == code) ++ return i; ++ return -EINVAL; ++} ++ ++int stf_dvp_subdev_init(struct stfcamss *stfcamss) ++{ ++ struct stf_dvp_dev *dvp_dev = stfcamss->dvp_dev; ++ ++ dvp_dev->s_type = SENSOR_VIN; ++ dvp_dev->hw_ops = &dvp_ops; ++ dvp_dev->stfcamss = stfcamss; ++ dvp_dev->formats = dvp_formats_st7110; ++ dvp_dev->nformats = ARRAY_SIZE(dvp_formats_st7110); ++ mutex_init(&dvp_dev->stream_lock); ++ dvp_dev->stream_count = 0; ++ return 0; ++} ++ ++static int dvp_set_power(struct v4l2_subdev *sd, int on) ++{ ++ return 0; ++} ++ ++static struct v4l2_mbus_framefmt * ++__dvp_get_format(struct stf_dvp_dev *dvp_dev, ++ struct v4l2_subdev_state *state, ++ unsigned int pad, ++ enum v4l2_subdev_format_whence which) ++{ ++ ++ if (which == V4L2_SUBDEV_FORMAT_TRY) ++ return v4l2_subdev_get_try_format( ++ &dvp_dev->subdev, state, pad); ++ return &dvp_dev->fmt[pad]; ++} ++ ++static int dvp_set_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct stf_dvp_dev *dvp_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt *format; ++ int ret = 0; ++ ++ format = __dvp_get_format(dvp_dev, NULL, STF_DVP_PAD_SRC, ++ V4L2_SUBDEV_FORMAT_ACTIVE); ++ if (format == NULL) ++ return -EINVAL; ++ ret = dvp_find_format(format->code, ++ dvp_dev->formats, ++ dvp_dev->nformats); ++ if (ret < 0) ++ return ret; ++ ++ mutex_lock(&dvp_dev->stream_lock); ++ if (enable) { ++ if (dvp_dev->stream_count == 0) { ++ dvp_dev->hw_ops->dvp_clk_enable(dvp_dev); ++ dvp_dev->hw_ops->dvp_config_set(dvp_dev); ++ dvp_dev->hw_ops->dvp_set_format(dvp_dev, ++ format->width, dvp_dev->formats[ret].bpp); ++ dvp_dev->hw_ops->dvp_stream_set(dvp_dev, 1); ++ } ++ dvp_dev->stream_count++; ++ } else { ++ if (dvp_dev->stream_count == 0) ++ goto exit; ++ if (dvp_dev->stream_count == 1) { ++ dvp_dev->hw_ops->dvp_stream_set(dvp_dev, 0); ++ dvp_dev->hw_ops->dvp_clk_disable(dvp_dev); ++ } ++ dvp_dev->stream_count--; ++ } ++exit: ++ mutex_unlock(&dvp_dev->stream_lock); ++ return 0; ++} ++ ++static void dvp_try_format(struct stf_dvp_dev *dvp_dev, ++ struct v4l2_subdev_state *state, ++ unsigned int pad, ++ struct v4l2_mbus_framefmt *fmt, ++ enum v4l2_subdev_format_whence which) ++{ ++ unsigned int i; ++ ++ switch (pad) { ++ case STF_DVP_PAD_SINK: ++ /* Set format on sink pad */ ++ ++ for (i = 0; i < dvp_dev->nformats; i++) ++ if (fmt->code == dvp_dev->formats[i].code) ++ break; ++ ++ if (i >= dvp_dev->nformats) ++ fmt->code = dvp_dev->formats[0].code; ++ ++ fmt->width = clamp_t(u32, ++ fmt->width, STFCAMSS_FRAME_MIN_WIDTH, ++ STFCAMSS_FRAME_MAX_WIDTH); ++ fmt->height = clamp_t(u32, ++ fmt->height, STFCAMSS_FRAME_MIN_HEIGHT, ++ STFCAMSS_FRAME_MAX_HEIGHT); ++ ++ fmt->field = V4L2_FIELD_NONE; ++ fmt->colorspace = V4L2_COLORSPACE_SRGB; ++ fmt->flags = 0; ++ ++ break; ++ ++ case STF_DVP_PAD_SRC: ++ ++ *fmt = *__dvp_get_format(dvp_dev, state, STF_DVP_PAD_SINK, which); ++ ++ break; ++ } ++} ++ ++static int dvp_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct stf_dvp_dev *dvp_dev = v4l2_get_subdevdata(sd); ++ ++ if (code->index >= dvp_dev->nformats) ++ return -EINVAL; ++ ++ if (code->pad == STF_DVP_PAD_SINK) { ++ code->code = dvp_dev->formats[code->index].code; ++ } else { ++ struct v4l2_mbus_framefmt *sink_fmt; ++ ++ sink_fmt = __dvp_get_format(dvp_dev, state, STF_DVP_PAD_SINK, ++ code->which); ++ ++ code->code = sink_fmt->code; ++ if (!code->code) ++ return -EINVAL; ++ } ++ code->flags = 0; ++ ++ return 0; ++} ++ ++static int dvp_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ struct stf_dvp_dev *dvp_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt format; ++ ++ if (fse->index != 0) ++ return -EINVAL; ++ ++ format.code = fse->code; ++ format.width = 1; ++ format.height = 1; ++ dvp_try_format(dvp_dev, state, fse->pad, &format, fse->which); ++ fse->min_width = format.width; ++ fse->min_height = format.height; ++ ++ if (format.code != fse->code) ++ return -EINVAL; ++ ++ format.code = fse->code; ++ format.width = -1; ++ format.height = -1; ++ dvp_try_format(dvp_dev, state, fse->pad, &format, fse->which); ++ fse->max_width = format.width; ++ fse->max_height = format.height; ++ ++ return 0; ++} ++ ++static int dvp_get_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct stf_dvp_dev *dvp_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt *format; ++ ++ format = __dvp_get_format(dvp_dev, state, fmt->pad, fmt->which); ++ if (format == NULL) ++ return -EINVAL; ++ ++ fmt->format = *format; ++ ++ return 0; ++} ++ ++static int dvp_set_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct stf_dvp_dev *dvp_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt *format; ++ ++ format = __dvp_get_format(dvp_dev, state, fmt->pad, fmt->which); ++ if (format == NULL) ++ return -EINVAL; ++ ++ mutex_lock(&dvp_dev->stream_lock); ++ if (dvp_dev->stream_count) { ++ fmt->format = *format; ++ mutex_unlock(&dvp_dev->stream_lock); ++ goto out; ++ } else { ++ dvp_try_format(dvp_dev, state, fmt->pad, &fmt->format, fmt->which); ++ *format = fmt->format; ++ } ++ mutex_unlock(&dvp_dev->stream_lock); ++ ++ /* Propagate the format from sink to source */ ++ if (fmt->pad == STF_DVP_PAD_SINK) { ++ format = __dvp_get_format(dvp_dev, state, STF_DVP_PAD_SRC, ++ fmt->which); ++ ++ *format = fmt->format; ++ dvp_try_format(dvp_dev, state, STF_DVP_PAD_SRC, format, ++ fmt->which); ++ } ++ ++out: ++ return 0; ++} ++ ++static int dvp_init_formats(struct v4l2_subdev *sd, ++ struct v4l2_subdev_fh *fh) ++{ ++ struct v4l2_subdev_format format = { ++ .pad = STF_DVP_PAD_SINK, ++ .which = fh ? V4L2_SUBDEV_FORMAT_TRY : ++ V4L2_SUBDEV_FORMAT_ACTIVE, ++ .format = { ++ .code = MEDIA_BUS_FMT_RGB565_2X8_LE, ++ .width = 1920, ++ .height = 1080 ++ } ++ }; ++ ++ return dvp_set_format(sd, fh ? fh->state : NULL, &format); ++} ++ ++static int dvp_link_setup(struct media_entity *entity, ++ const struct media_pad *local, ++ const struct media_pad *remote, u32 flags) ++{ ++ if ((local->flags & MEDIA_PAD_FL_SOURCE) && ++ (flags & MEDIA_LNK_FL_ENABLED)) { ++ struct v4l2_subdev *sd; ++ struct stf_dvp_dev *dvp_dev; ++ struct vin_line *line; ++ ++ if (media_pad_remote_pad_first(local)) ++ return -EBUSY; ++ ++ sd = media_entity_to_v4l2_subdev(entity); ++ dvp_dev = v4l2_get_subdevdata(sd); ++ ++ sd = media_entity_to_v4l2_subdev(remote->entity); ++ line = v4l2_get_subdevdata(sd); ++ if (line->sdev_type == VIN_DEV_TYPE) ++ dvp_dev->s_type = SENSOR_VIN; ++ if (line->sdev_type == ISP_DEV_TYPE) ++ dvp_dev->s_type = SENSOR_ISP; ++ st_info(ST_DVP, "DVP device sensor type: %d\n", dvp_dev->s_type); ++ } ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_core_ops dvp_core_ops = { ++ .s_power = dvp_set_power, ++}; ++ ++static const struct v4l2_subdev_video_ops dvp_video_ops = { ++ .s_stream = dvp_set_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops dvp_pad_ops = { ++ .enum_mbus_code = dvp_enum_mbus_code, ++ .enum_frame_size = dvp_enum_frame_size, ++ .get_fmt = dvp_get_format, ++ .set_fmt = dvp_set_format, ++}; ++ ++static const struct v4l2_subdev_ops dvp_v4l2_ops = { ++ .core = &dvp_core_ops, ++ .video = &dvp_video_ops, ++ .pad = &dvp_pad_ops, ++}; ++ ++static const struct v4l2_subdev_internal_ops dvp_v4l2_internal_ops = { ++ .open = dvp_init_formats, ++}; ++ ++static const struct media_entity_operations dvp_media_ops = { ++ .link_setup = dvp_link_setup, ++ .link_validate = v4l2_subdev_link_validate, ++}; ++ ++int stf_dvp_register(struct stf_dvp_dev *dvp_dev, ++ struct v4l2_device *v4l2_dev) ++{ ++ struct v4l2_subdev *sd = &dvp_dev->subdev; ++ struct media_pad *pads = dvp_dev->pads; ++ int ret; ++ ++ v4l2_subdev_init(sd, &dvp_v4l2_ops); ++ sd->internal_ops = &dvp_v4l2_internal_ops; ++ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ++ snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", ++ STF_DVP_NAME, 0); ++ v4l2_set_subdevdata(sd, dvp_dev); ++ ++ ret = dvp_init_formats(sd, NULL); ++ if (ret < 0) { ++ st_err(ST_DVP, "Failed to init format: %d\n", ret); ++ return ret; ++ } ++ ++ pads[STF_DVP_PAD_SINK].flags = MEDIA_PAD_FL_SINK; ++ pads[STF_DVP_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; ++ ++ sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; ++ sd->entity.ops = &dvp_media_ops; ++ ret = media_entity_pads_init(&sd->entity, STF_DVP_PADS_NUM, pads); ++ if (ret < 0) { ++ st_err(ST_DVP, "Failed to init media entity: %d\n", ret); ++ return ret; ++ } ++ ++ ret = v4l2_device_register_subdev(v4l2_dev, sd); ++ if (ret < 0) { ++ st_err(ST_DVP, "Failed to register subdev: %d\n", ret); ++ goto err_sreg; ++ } ++ ++ return 0; ++ ++err_sreg: ++ media_entity_cleanup(&sd->entity); ++ return ret; ++} ++ ++int stf_dvp_unregister(struct stf_dvp_dev *dvp_dev) ++{ ++ v4l2_device_unregister_subdev(&dvp_dev->subdev); ++ media_entity_cleanup(&dvp_dev->subdev.entity); ++ mutex_destroy(&dvp_dev->stream_lock); ++ return 0; ++} +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_dvp.h +@@ -0,0 +1,67 @@ ++/* SPDX-License-Identifier: GPL-2.0 ++ * ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#ifndef STF_DVP_H ++#define STF_DVP_H ++ ++#include <media/v4l2-subdev.h> ++#include <media/v4l2-device.h> ++#include <media/media-entity.h> ++#include <video/stf-vin.h> ++ ++#define STF_DVP_NAME "stf_dvp" ++ ++#define STF_DVP_PAD_SINK 0 ++#define STF_DVP_PAD_SRC 1 ++#define STF_DVP_PADS_NUM 2 ++ ++struct dvp_format { ++ u32 code; ++ u8 bpp; ++}; ++ ++enum sensor_type; ++enum subdev_type; ++ ++struct dvp_cfg { ++ unsigned int flags; ++ unsigned char bus_width; ++ unsigned char data_shift; ++}; ++ ++struct stf_dvp_dev; ++ ++struct dvp_hw_ops { ++ int (*dvp_clk_enable)(struct stf_dvp_dev *dvp_dev); ++ int (*dvp_clk_disable)(struct stf_dvp_dev *dvp_dev); ++ int (*dvp_config_set)(struct stf_dvp_dev *dvp_dev); ++ int (*dvp_set_format)(struct stf_dvp_dev *dvp_dev, ++ u32 pix_width, u8 bpp); ++ int (*dvp_stream_set)(struct stf_dvp_dev *dvp_dev, int on); ++}; ++ ++struct stf_dvp_dev { ++ struct stfcamss *stfcamss; ++ struct dvp_cfg *dvp; ++ enum sensor_type s_type; ++ struct v4l2_subdev subdev; ++ struct media_pad pads[STF_DVP_PADS_NUM]; ++ struct v4l2_mbus_framefmt fmt[STF_DVP_PADS_NUM]; ++ const struct dvp_format *formats; ++ unsigned int nformats; ++ struct dvp_hw_ops *hw_ops; ++ struct mutex stream_lock; ++ int stream_count; ++}; ++ ++extern int stf_dvp_subdev_init(struct stfcamss *stfcamss); ++extern int stf_dvp_register(struct stf_dvp_dev *dvp_dev, ++ struct v4l2_device *v4l2_dev); ++extern int stf_dvp_unregister(struct stf_dvp_dev *dvp_dev); ++ ++extern struct dvp_hw_ops dvp_ops; ++ ++#endif /* STF_DVP_H */ +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_dvp_hw_ops.c +@@ -0,0 +1,187 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include "stfcamss.h" ++ ++static int stf_dvp_clk_enable(struct stf_dvp_dev *dvp_dev) ++{ ++ struct stfcamss *stfcamss = dvp_dev->stfcamss; ++ ++ switch (dvp_dev->s_type) { ++ case SENSOR_VIN: ++ reset_control_deassert(stfcamss->sys_rst[STFRST_AXIWR].rstc); ++ clk_set_phase(stfcamss->sys_clk[STFCLK_DVP_INV].clk, 0); ++ clk_set_parent(stfcamss->sys_clk[STFCLK_AXIWR].clk, ++ stfcamss->sys_clk[STFCLK_DVP_INV].clk); ++ break; ++ case SENSOR_ISP: ++ clk_set_phase(stfcamss->sys_clk[STFCLK_DVP_INV].clk, 0); ++ clk_set_parent(stfcamss->sys_clk[STFCLK_WRAPPER_CLK_C].clk, ++ stfcamss->sys_clk[STFCLK_DVP_INV].clk); ++ break; ++ } ++ ++ return 0; ++} ++ ++static int stf_dvp_clk_disable(struct stf_dvp_dev *dvp_dev) ++{ ++ struct stfcamss *stfcamss = dvp_dev->stfcamss; ++ ++ switch (dvp_dev->s_type) { ++ case SENSOR_VIN: ++ clk_set_parent(stfcamss->sys_clk[STFCLK_AXIWR].clk, ++ stfcamss->sys_clk[STFCLK_MIPI_RX0_PXL].clk); ++ reset_control_assert(stfcamss->sys_rst[STFRST_AXIWR].rstc); ++ break; ++ case SENSOR_ISP: ++ clk_set_parent(stfcamss->sys_clk[STFCLK_WRAPPER_CLK_C].clk, ++ stfcamss->sys_clk[STFCLK_MIPI_RX0_PXL].clk); ++ break; ++ } ++ ++ return 0; ++} ++ ++static int stf_dvp_config_set(struct stf_dvp_dev *dvp_dev) ++{ ++ ++ struct stf_vin_dev *vin = dvp_dev->stfcamss->vin; ++ unsigned int flags = 0; ++ unsigned char data_shift = 0; ++ u32 polarities = 0; ++ ++ if (!dvp_dev->dvp) ++ return -EINVAL; ++ ++ flags = dvp_dev->dvp->flags; ++ data_shift = dvp_dev->dvp->data_shift; ++ st_info(ST_DVP, "%s, polarities = 0x%x, flags = 0x%x\n", ++ __func__, polarities, flags); ++ ++ if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) ++ polarities |= BIT(1); ++ if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) ++ polarities |= BIT(3); ++ print_reg(ST_DVP, vin->sysctrl_base, SYSCONSAIF_SYSCFG_36); ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_36, ++ U0_VIN_CNFG_DVP_HS_POS ++ | U0_VIN_CNFG_DVP_VS_POS, ++ polarities); ++ print_reg(ST_DVP, vin->sysctrl_base, SYSCONSAIF_SYSCFG_36); ++ ++ switch (data_shift) { ++ case 0: ++ data_shift = 0; ++ break; ++ case 2: ++ data_shift = 1; ++ break; ++ case 4: ++ data_shift = 2; ++ break; ++ case 6: ++ data_shift = 3; ++ break; ++ default: ++ data_shift = 0; ++ break; ++ }; ++ print_reg(ST_DVP, vin->sysctrl_base, SYSCONSAIF_SYSCFG_28); ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_28, ++ UO_VIN_CNFG_AXIWR0_PIXEL_HEIGH_BIT_SEL, ++ data_shift << 15); ++ print_reg(ST_DVP, vin->sysctrl_base, SYSCONSAIF_SYSCFG_28); ++ ++ return 0; ++} ++ ++static int set_vin_axiwr_pix_ct(struct stf_vin_dev *vin, u8 bpp) ++{ ++ u32 value = 0; ++ int cnfg_axiwr_pix_ct = 64 / bpp; ++ ++ // need check ++ if (cnfg_axiwr_pix_ct == 2) ++ value = 1; ++ else if (cnfg_axiwr_pix_ct == 4) ++ value = 1; ++ else if (cnfg_axiwr_pix_ct == 8) ++ value = 0; ++ else ++ return 0; ++ ++ print_reg(ST_DVP, vin->sysctrl_base, SYSCONSAIF_SYSCFG_28); ++ reg_set_bit(vin->sysctrl_base, ++ SYSCONSAIF_SYSCFG_28, ++ U0_VIN_CNFG_AXIWR0_PIX_CT, ++ value<<13); ++ print_reg(ST_DVP, vin->sysctrl_base, SYSCONSAIF_SYSCFG_28); ++ ++ return cnfg_axiwr_pix_ct; ++ ++} ++ ++static int stf_dvp_set_format(struct stf_dvp_dev *dvp_dev, ++ u32 pix_width, u8 bpp) ++{ ++ struct stf_vin_dev *vin = dvp_dev->stfcamss->vin; ++ int val, pix_ct; ++ ++ if (dvp_dev->s_type == SENSOR_VIN) { ++ pix_ct = set_vin_axiwr_pix_ct(vin, bpp); ++ val = (pix_width / pix_ct) - 1; ++ print_reg(ST_DVP, vin->sysctrl_base, SYSCTRL_VIN_WR_PIX_TOTAL); ++ reg_set_bit(vin->sysctrl_base, ++ SYSCONSAIF_SYSCFG_28, ++ U0_VIN_CNFG_AXIWR0_PIX_CNT_END, ++ val << 2); ++ print_reg(ST_DVP, vin->sysctrl_base, SYSCTRL_VIN_WR_PIX_TOTAL); ++ ++ } ++ ++ return 0; ++} ++ ++static int stf_dvp_stream_set(struct stf_dvp_dev *dvp_dev, int on) ++{ ++ struct stf_vin_dev *vin = dvp_dev->stfcamss->vin; ++ ++ switch (dvp_dev->s_type) { ++ case SENSOR_VIN: ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_36, ++ U0_VIN_CNFG_ISP_DVP_EN0, ++ 0); ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_0, ++ U0_VIN_CNFG_AXI_DVP_EN, ++ !!on<<2); ++ break; ++ case SENSOR_ISP: ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_36, ++ U0_VIN_CNFG_ISP_DVP_EN0, ++ !!on<<5); ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_0, ++ U0_VIN_CNFG_AXI_DVP_EN, ++ 0); ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_36, ++ U0_VIN_CNFG_DVP_SWAP_EN, ++ 0); ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_36, ++ U0_VIN_CNFG_GEN_EN_AXIRD, ++ 0); ++ break; ++ } ++ ++ return 0; ++} ++ ++struct dvp_hw_ops dvp_ops = { ++ .dvp_clk_enable = stf_dvp_clk_enable, ++ .dvp_clk_disable = stf_dvp_clk_disable, ++ .dvp_config_set = stf_dvp_config_set, ++ .dvp_set_format = stf_dvp_set_format, ++ .dvp_stream_set = stf_dvp_stream_set, ++}; +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_event.c +@@ -0,0 +1,36 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include <linux/notifier.h> ++#include <linux/module.h> ++#include <linux/init.h> ++#include <linux/kernel.h> ++#include <linux/fs.h> ++ ++static ATOMIC_NOTIFIER_HEAD(vin_notifier_list); ++ ++int vin_notifier_register(struct notifier_block *nb) ++{ ++ return atomic_notifier_chain_register(&vin_notifier_list, nb); ++} ++EXPORT_SYMBOL_GPL(vin_notifier_register); ++ ++void vin_notifier_unregister(struct notifier_block *nb) ++{ ++ atomic_notifier_chain_unregister(&vin_notifier_list, nb); ++} ++EXPORT_SYMBOL_GPL(vin_notifier_unregister); ++ ++int vin_notifier_call(unsigned long e, void *v) ++{ ++ return atomic_notifier_call_chain(&vin_notifier_list, e, v); ++} ++EXPORT_SYMBOL_GPL(vin_notifier_call); ++ ++MODULE_AUTHOR("StarFive Technology Co., Ltd."); ++MODULE_DESCRIPTION("Starfive VIC video in notifier"); ++MODULE_LICENSE("GPL"); ++//MODULE_SUPPORTED_DEVICE("video"); +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_isp.c +@@ -0,0 +1,1521 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include "stfcamss.h" ++#include <media/v4l2-async.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-event.h> ++#include <media/v4l2-fwnode.h> ++#include <media/v4l2-subdev.h> ++#include <linux/firmware.h> ++#include <linux/jh7110-isp.h> ++#include "stf_isp_ioctl.h" ++#include "stf_dmabuf.h" ++ ++static int user_config_isp; ++static int isp_set_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_selection *sel); ++ ++static struct v4l2_rect * ++__isp_get_compose(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ enum v4l2_subdev_format_whence which); ++ ++static struct v4l2_rect * ++__isp_get_crop(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ enum v4l2_subdev_format_whence which); ++ ++static struct v4l2_rect * ++__isp_get_scale(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_selection *sel); ++ ++static struct v4l2_rect * ++__isp_get_itiws(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ enum v4l2_subdev_format_whence which); ++ ++// sink format and raw format must one by one ++static const struct isp_format isp_formats_st7110_sink[] = { ++ { MEDIA_BUS_FMT_SRGGB10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGRBG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGBRG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SBGGR10_1X10, 10}, ++}; ++ ++static const struct isp_format isp_formats_st7110_raw[] = { ++ { MEDIA_BUS_FMT_SRGGB12_1X12, 12}, ++ { MEDIA_BUS_FMT_SGRBG12_1X12, 12}, ++ { MEDIA_BUS_FMT_SGBRG12_1X12, 12}, ++ { MEDIA_BUS_FMT_SBGGR12_1X12, 12}, ++}; ++ ++static const struct isp_format isp_formats_st7110_compat_10bit_raw[] = { ++ { MEDIA_BUS_FMT_SRGGB10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGRBG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGBRG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SBGGR10_1X10, 10}, ++}; ++ ++static const struct isp_format isp_formats_st7110_compat_8bit_raw[] = { ++ { MEDIA_BUS_FMT_SRGGB8_1X8, 8}, ++ { MEDIA_BUS_FMT_SGRBG8_1X8, 8}, ++ { MEDIA_BUS_FMT_SGBRG8_1X8, 8}, ++ { MEDIA_BUS_FMT_SBGGR8_1X8, 8}, ++}; ++ ++static const struct isp_format isp_formats_st7110_uo[] = { ++ { MEDIA_BUS_FMT_Y12_1X12, 8}, ++}; ++ ++static const struct isp_format isp_formats_st7110_iti[] = { ++ { MEDIA_BUS_FMT_SRGGB10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGRBG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGBRG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SBGGR10_1X10, 10}, ++ { MEDIA_BUS_FMT_SRGGB12_1X12, 12}, ++ { MEDIA_BUS_FMT_SGRBG12_1X12, 12}, ++ { MEDIA_BUS_FMT_SGBRG12_1X12, 12}, ++ { MEDIA_BUS_FMT_SBGGR12_1X12, 12}, ++ { MEDIA_BUS_FMT_Y12_1X12, 8}, ++ { MEDIA_BUS_FMT_YUV8_1X24, 8}, ++}; ++ ++static const struct isp_format_table isp_formats_st7110[] = { ++ { isp_formats_st7110_sink, ARRAY_SIZE(isp_formats_st7110_sink) }, /* pad 0 */ ++ { isp_formats_st7110_uo, ARRAY_SIZE(isp_formats_st7110_uo) }, /* pad 1 */ ++ { isp_formats_st7110_uo, ARRAY_SIZE(isp_formats_st7110_uo) }, /* pad 2 */ ++ { isp_formats_st7110_uo, ARRAY_SIZE(isp_formats_st7110_uo) }, /* pad 3 */ ++ { isp_formats_st7110_iti, ARRAY_SIZE(isp_formats_st7110_iti) }, /* pad 4 */ ++ { isp_formats_st7110_iti, ARRAY_SIZE(isp_formats_st7110_iti) }, /* pad 5 */ ++ { isp_formats_st7110_raw, ARRAY_SIZE(isp_formats_st7110_raw) }, /* pad 6 */ ++ { isp_formats_st7110_raw, ARRAY_SIZE(isp_formats_st7110_raw) }, /* pad 7 */ ++}; ++ ++int stf_isp_subdev_init(struct stfcamss *stfcamss) ++{ ++ struct stf_isp_dev *isp_dev = stfcamss->isp_dev; ++ ++ isp_dev->sdev_type = ISP_DEV_TYPE; ++ isp_dev->hw_ops = &isp_ops; ++ isp_dev->stfcamss = stfcamss; ++ isp_dev->formats = isp_formats_st7110; ++ isp_dev->nformats = ARRAY_SIZE(isp_formats_st7110); ++ mutex_init(&isp_dev->stream_lock); ++ mutex_init(&isp_dev->power_lock); ++ mutex_init(&isp_dev->setfile_lock); ++ atomic_set(&isp_dev->shadow_count, 0); ++ return 0; ++} ++ ++/* ++ * ISP Controls. ++ */ ++ ++static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) ++{ ++ return &container_of(ctrl->handler, struct stf_isp_dev, ++ ctrls.handler)->subdev; ++} ++ ++static u64 isp_calc_pixel_rate(struct stf_isp_dev *isp_dev) ++{ ++ u64 rate = 0; ++ ++ return rate; ++} ++ ++static int isp_set_ctrl_hue(struct stf_isp_dev *isp_dev, int value) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int isp_set_ctrl_contrast(struct stf_isp_dev *isp_dev, int value) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int isp_set_ctrl_saturation(struct stf_isp_dev *isp_dev, int value) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int isp_set_ctrl_white_balance(struct stf_isp_dev *isp_dev, int awb) ++{ ++ struct isp_ctrls *ctrls = &isp_dev->ctrls; ++ int ret = 0; ++ ++ if (!awb && (ctrls->red_balance->is_new ++ || ctrls->blue_balance->is_new)) { ++ u16 red = (u16)ctrls->red_balance->val; ++ u16 blue = (u16)ctrls->blue_balance->val; ++ ++ st_debug(ST_ISP, "red = 0x%x, blue = 0x%x\n", red, blue); ++ //isp_dev->hw_ops->isp_set_awb_r_gain(isp_dev, red); ++ //if (ret) ++ // return ret; ++ //isp_dev->hw_ops->isp_set_awb_b_gain(isp_dev, blue); ++ } ++ ++ return ret; ++} ++ ++static int isp_set_ctrl_exposure(struct stf_isp_dev *isp_dev, ++ enum v4l2_exposure_auto_type auto_exposure) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int isp_set_ctrl_gain(struct stf_isp_dev *isp_dev, bool auto_gain) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static const char * const test_pattern_menu[] = { ++ "Disabled", ++ "Color bars", ++ "Color bars w/ rolling bar", ++ "Color squares", ++ "Color squares w/ rolling bar", ++}; ++ ++#define ISP_TEST_ENABLE BIT(7) ++#define ISP_TEST_ROLLING BIT(6) /* rolling horizontal bar */ ++#define ISP_TEST_TRANSPARENT BIT(5) ++#define ISP_TEST_SQUARE_BW BIT(4) /* black & white squares */ ++#define ISP_TEST_BAR_STANDARD (0 << 2) ++#define ISP_TEST_BAR_VERT_CHANGE_1 (1 << 2) ++#define ISP_TEST_BAR_HOR_CHANGE (2 << 2) ++#define ISP_TEST_BAR_VERT_CHANGE_2 (3 << 2) ++#define ISP_TEST_BAR (0 << 0) ++#define ISP_TEST_RANDOM (1 << 0) ++#define ISP_TEST_SQUARE (2 << 0) ++#define ISP_TEST_BLACK (3 << 0) ++ ++static const u8 test_pattern_val[] = { ++ 0, ++ ISP_TEST_ENABLE | ISP_TEST_BAR_VERT_CHANGE_1 | ++ ISP_TEST_BAR, ++ ISP_TEST_ENABLE | ISP_TEST_ROLLING | ++ ISP_TEST_BAR_VERT_CHANGE_1 | ISP_TEST_BAR, ++ ISP_TEST_ENABLE | ISP_TEST_SQUARE, ++ ISP_TEST_ENABLE | ISP_TEST_ROLLING | ISP_TEST_SQUARE, ++}; ++ ++static int isp_set_ctrl_test_pattern(struct stf_isp_dev *isp_dev, int value) ++{ ++ int ret = 0; ++ ++ // return isp_write_reg(isp_dev, ISP_REG_PRE_ISP_TEST_SET1, ++ // test_pattern_val[value]); ++ return ret; ++} ++ ++static int isp_set_ctrl_light_freq(struct stf_isp_dev *isp_dev, int value) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int isp_set_ctrl_hflip(struct stf_isp_dev *isp_dev, int value) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int isp_set_ctrl_vflip(struct stf_isp_dev *isp_dev, int value) ++{ ++ int ret = 0; ++ ++ return ret; ++} ++ ++static int isp_g_volatile_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ switch (ctrl->id) { ++ case V4L2_CID_AUTOGAIN: ++ break; ++ case V4L2_CID_EXPOSURE_AUTO: ++ break; ++ } ++ ++ return 0; ++} ++ ++static int isp_s_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct v4l2_subdev *sd = ctrl_to_sd(ctrl); ++ struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd); ++ int ret = 0; ++ ++ /* ++ * If the device is not powered up by the host driver do ++ * not apply any controls to H/W at this time. Instead ++ * the controls will be restored right after power-up. ++ */ ++ mutex_lock(&isp_dev->power_lock); ++ if (isp_dev->power_count == 0) { ++ mutex_unlock(&isp_dev->power_lock); ++ return 0; ++ } ++ mutex_unlock(&isp_dev->power_lock); ++ ++ switch (ctrl->id) { ++ case V4L2_CID_AUTOGAIN: ++ ret = isp_set_ctrl_gain(isp_dev, ctrl->val); ++ break; ++ case V4L2_CID_EXPOSURE_AUTO: ++ ret = isp_set_ctrl_exposure(isp_dev, ctrl->val); ++ break; ++ case V4L2_CID_AUTO_WHITE_BALANCE: ++ ret = isp_set_ctrl_white_balance(isp_dev, ctrl->val); ++ break; ++ case V4L2_CID_HUE: ++ ret = isp_set_ctrl_hue(isp_dev, ctrl->val); ++ break; ++ case V4L2_CID_CONTRAST: ++ ret = isp_set_ctrl_contrast(isp_dev, ctrl->val); ++ break; ++ case V4L2_CID_SATURATION: ++ ret = isp_set_ctrl_saturation(isp_dev, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN: ++ ret = isp_set_ctrl_test_pattern(isp_dev, ctrl->val); ++ break; ++ case V4L2_CID_POWER_LINE_FREQUENCY: ++ ret = isp_set_ctrl_light_freq(isp_dev, ctrl->val); ++ break; ++ case V4L2_CID_HFLIP: ++ ret = isp_set_ctrl_hflip(isp_dev, ctrl->val); ++ break; ++ case V4L2_CID_VFLIP: ++ ret = isp_set_ctrl_vflip(isp_dev, ctrl->val); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_WB_SETTING: ++ break; ++ case V4L2_CID_USER_JH7110_ISP_CAR_SETTING: ++ break; ++ case V4L2_CID_USER_JH7110_ISP_CCM_SETTING: ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops isp_ctrl_ops = { ++ .g_volatile_ctrl = isp_g_volatile_ctrl, ++ .s_ctrl = isp_s_ctrl, ++}; ++ ++struct v4l2_ctrl_config isp_ctrl[] = { ++ [0] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "WB Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_WB_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_wb_setting), ++ .flags = 0, ++ }, ++ [1] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "Car Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_CAR_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_car_setting), ++ .flags = 0, ++ }, ++ [2] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "CCM Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_CCM_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_ccm_setting), ++ .flags = 0, ++ }, ++}; ++ ++static int isp_init_controls(struct stf_isp_dev *isp_dev) ++{ ++ const struct v4l2_ctrl_ops *ops = &isp_ctrl_ops; ++ struct isp_ctrls *ctrls = &isp_dev->ctrls; ++ struct v4l2_ctrl_handler *hdl = &ctrls->handler; ++ int ret; ++ int i; ++ ++ v4l2_ctrl_handler_init(hdl, 32); ++ ++ /* Clock related controls */ ++ ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, ++ 0, INT_MAX, 1, ++ isp_calc_pixel_rate(isp_dev)); ++ ++ /* Auto/manual white balance */ ++ ctrls->auto_wb = v4l2_ctrl_new_std(hdl, ops, ++ V4L2_CID_AUTO_WHITE_BALANCE, ++ 0, 1, 1, 1); ++ ctrls->blue_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BLUE_BALANCE, ++ 0, 4095, 1, 0); ++ ctrls->red_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE, ++ 0, 4095, 1, 0); ++ /* Auto/manual exposure */ ++ ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, ++ V4L2_CID_EXPOSURE_AUTO, ++ V4L2_EXPOSURE_MANUAL, 0, ++ V4L2_EXPOSURE_AUTO); ++ ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, ++ 0, 65535, 1, 0); ++ /* Auto/manual gain */ ++ ctrls->auto_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTOGAIN, ++ 0, 1, 1, 1); ++ ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, ++ 0, 1023, 1, 0); ++ ++ ctrls->saturation = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, ++ 0, 255, 1, 64); ++ ctrls->hue = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HUE, ++ 0, 359, 1, 0); ++ ctrls->contrast = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, ++ 0, 255, 1, 0); ++ ctrls->test_pattern = ++ v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(test_pattern_menu) - 1, ++ 0, 0, test_pattern_menu); ++ ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, ++ 0, 1, 1, 0); ++ ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, ++ 0, 1, 1, 0); ++ ++ ctrls->light_freq = ++ v4l2_ctrl_new_std_menu(hdl, ops, ++ V4L2_CID_POWER_LINE_FREQUENCY, ++ V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, ++ V4L2_CID_POWER_LINE_FREQUENCY_50HZ); ++ ++ for (i = 0; i < ARRAY_SIZE(isp_ctrl); i++) ++ v4l2_ctrl_new_custom(hdl, &isp_ctrl[i], NULL); ++ ++ ++ if (hdl->error) { ++ ret = hdl->error; ++ goto free_ctrls; ++ } ++ ++ ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ctrls->gain->flags |= V4L2_CTRL_FLAG_VOLATILE; ++ ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; ++ ++ v4l2_ctrl_auto_cluster(3, &ctrls->auto_wb, 0, false); ++ v4l2_ctrl_auto_cluster(2, &ctrls->auto_gain, 0, true); ++ v4l2_ctrl_auto_cluster(2, &ctrls->auto_exp, 1, true); ++ ++ isp_dev->subdev.ctrl_handler = hdl; ++ return 0; ++ ++free_ctrls: ++ v4l2_ctrl_handler_free(hdl); ++ return ret; ++} ++ ++static int isp_set_power(struct v4l2_subdev *sd, int on) ++{ ++ struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd); ++ ++ st_debug(ST_ISP, "%s, %d\n", __func__, __LINE__); ++ mutex_lock(&isp_dev->power_lock); ++ if (on) { ++ if (isp_dev->power_count == 0) ++ st_debug(ST_ISP, "turn on isp\n"); ++ isp_dev->power_count++; ++ } else { ++ if (isp_dev->power_count == 0) ++ goto exit; ++ isp_dev->power_count--; ++ } ++exit: ++ mutex_unlock(&isp_dev->power_lock); ++ ++ return 0; ++} ++ ++static struct v4l2_mbus_framefmt * ++__isp_get_format(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ unsigned int pad, ++ enum v4l2_subdev_format_whence which) ++{ ++ ++ if (which == V4L2_SUBDEV_FORMAT_TRY) ++ return v4l2_subdev_get_try_format(&isp_dev->subdev, state, pad); ++ ++ return &isp_dev->fmt[pad]; ++} ++ ++static int isp_get_interface_type(struct media_entity *entity) ++{ ++ struct v4l2_subdev *subdev; ++ struct media_pad *pad = &entity->pads[0]; ++ ++ if (!(pad->flags & MEDIA_PAD_FL_SINK)) ++ return -EINVAL; ++ ++ pad = media_pad_remote_pad_first(pad); ++ if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) ++ return -EINVAL; ++ ++ subdev = media_entity_to_v4l2_subdev(pad->entity); ++ ++ st_debug(ST_ISP, "interface subdev name %s\n", subdev->name); ++ if (!strncmp(subdev->name, STF_CSI_NAME, strlen(STF_CSI_NAME))) ++ return CSI_SENSOR; ++ if (!strncmp(subdev->name, STF_DVP_NAME, strlen(STF_DVP_NAME))) ++ return DVP_SENSOR; ++ return -EINVAL; ++} ++ ++static int isp_set_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd); ++ int ret = 0, interface_type; ++ struct v4l2_mbus_framefmt *fmt; ++ struct v4l2_event src_ch = { 0 }; ++ ++ fmt = __isp_get_format(isp_dev, NULL, STF_ISP_PAD_SINK, V4L2_SUBDEV_FORMAT_ACTIVE); ++ mutex_lock(&isp_dev->stream_lock); ++ if (enable) { ++ if (isp_dev->stream_count == 0) { ++ isp_dev->hw_ops->isp_clk_enable(isp_dev); ++ if (!user_config_isp) ++ isp_dev->hw_ops->isp_config_set(isp_dev); ++ interface_type = isp_get_interface_type(&sd->entity); ++ if (interface_type < 0) { ++ st_err(ST_ISP, "%s, pipeline not config\n", __func__); ++ goto exit; ++ } ++ isp_dev->hw_ops->isp_set_format(isp_dev, ++ isp_dev->rect, fmt->code, interface_type); ++ isp_dev->hw_ops->isp_reset(isp_dev); ++ isp_dev->hw_ops->isp_stream_set(isp_dev, enable); ++ user_config_isp = 0; ++ } ++ isp_dev->stream_count++; ++ } else { ++ if (isp_dev->stream_count == 0) ++ goto exit; ++ if (isp_dev->stream_count == 1) { ++ isp_dev->hw_ops->isp_stream_set(isp_dev, enable); ++ isp_dev->hw_ops->isp_clk_disable(isp_dev); ++ } ++ isp_dev->stream_count--; ++ } ++ src_ch.type = V4L2_EVENT_SOURCE_CHANGE, ++ src_ch.u.src_change.changes = isp_dev->stream_count, ++ ++ v4l2_subdev_notify_event(sd, &src_ch); ++exit: ++ mutex_unlock(&isp_dev->stream_lock); ++ ++ mutex_lock(&isp_dev->power_lock); ++ /* restore controls */ ++ if (enable && isp_dev->power_count == 1) { ++ mutex_unlock(&isp_dev->power_lock); ++ ret = v4l2_ctrl_handler_setup(&isp_dev->ctrls.handler); ++ } else ++ mutex_unlock(&isp_dev->power_lock); ++ ++ return ret; ++} ++ ++/*Try to match sensor format with sink, and then get the index as default.*/ ++static int isp_match_sensor_format_get_index(struct stf_isp_dev *isp_dev) ++{ ++ int ret, idx; ++ struct media_entity *sensor; ++ struct v4l2_subdev *subdev; ++ struct v4l2_subdev_format fmt; ++ const struct isp_format_table *formats; ++ ++ if (!isp_dev) ++ return -EINVAL; ++ ++ sensor = stfcamss_find_sensor(&isp_dev->subdev.entity); ++ if (!sensor) ++ return -EINVAL; ++ ++ subdev = media_entity_to_v4l2_subdev(sensor); ++ st_debug(ST_ISP, "Found sensor = %s\n", sensor->name); ++ ++ fmt.pad = 0; ++ fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; ++ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); ++ if (ret) { ++ st_warn(ST_ISP, "Sonser get format failed !!\n"); ++ return -EINVAL; ++ } ++ ++ st_debug(ST_ISP, "Got sensor format 0x%x !!\n", fmt.format.code); ++ ++ formats = &isp_dev->formats[0]; /* isp sink format */ ++ for (idx = 0; idx < formats->nfmts; idx++) { ++ if (formats->fmts[idx].code == fmt.format.code) { ++ st_info(ST_ISP, ++ "Match sensor format to isp_formats_st7110_sink index %d !!\n", ++ idx); ++ return idx; ++ } ++ } ++ return -ERANGE; ++} ++ ++static int isp_match_format_get_index(const struct isp_format_table *f_table, ++ __u32 mbus_code, ++ unsigned int pad) ++{ ++ int i; ++ ++ for (i = 0; i < f_table->nfmts; i++) { ++ if (mbus_code == f_table->fmts[i].code) { ++ break; ++ } else { ++ if (pad == STF_ISP_PAD_SRC_RAW || pad == STF_ISP_PAD_SRC_SCD_Y) { ++ if (mbus_code == (isp_formats_st7110_compat_10bit_raw[i].code || ++ isp_formats_st7110_compat_8bit_raw[i].code)) ++ break; ++ } ++ } ++ } ++ ++ return i; ++} ++ ++static void isp_try_format(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ unsigned int pad, ++ struct v4l2_mbus_framefmt *fmt, ++ enum v4l2_subdev_format_whence which) ++{ ++ const struct isp_format_table *formats; ++ unsigned int i; ++ u32 code = fmt->code; ++ u32 bpp; ++ ++ if (pad == STF_ISP_PAD_SINK) { ++ /* Set format on sink pad */ ++ ++ formats = &isp_dev->formats[pad]; ++ fmt->width = clamp_t(u32, ++ fmt->width, STFCAMSS_FRAME_MIN_WIDTH, ++ STFCAMSS_FRAME_MAX_WIDTH); ++ fmt->height = clamp_t(u32, ++ fmt->height, STFCAMSS_FRAME_MIN_HEIGHT, ++ STFCAMSS_FRAME_MAX_HEIGHT); ++ fmt->height &= ~0x1; ++ ++ fmt->field = V4L2_FIELD_NONE; ++ fmt->colorspace = V4L2_COLORSPACE_SRGB; ++ fmt->flags = 0; ++ } else { ++ formats = &isp_dev->formats[pad]; ++ } ++ ++ i = isp_match_format_get_index(formats, fmt->code, pad); ++ st_debug(ST_ISP, "%s pad=%d, code=%x isp_match_format_get_index = %d\n", ++ __func__, pad, code, i); ++ ++ if (i >= formats->nfmts && ++ (pad == STF_ISP_PAD_SRC_RAW || pad == STF_ISP_PAD_SRC_SCD_Y)) { ++ int sensor_idx; ++ ++ sensor_idx = isp_match_sensor_format_get_index(isp_dev); ++ if (sensor_idx) ++ i = sensor_idx; ++ } ++ ++ if (pad != STF_ISP_PAD_SINK) ++ *fmt = *__isp_get_format(isp_dev, state, STF_ISP_PAD_SINK, which); ++ ++ if (i >= formats->nfmts) { ++ fmt->code = formats->fmts[0].code; ++ bpp = formats->fmts[0].bpp; ++ st_info(ST_ISP, "Use default index 0 format = 0x%x\n", fmt->code); ++ } else { ++ // sink format and raw format must one by one ++ if (pad == STF_ISP_PAD_SRC_RAW || pad == STF_ISP_PAD_SRC_SCD_Y) { ++ fmt->code = formats->fmts[i].code; ++ bpp = formats->fmts[i].bpp; ++ st_info(ST_ISP, "Use mapping format from sink index %d = 0x%x\n", ++ i, fmt->code); ++ } else { ++ fmt->code = code; ++ bpp = formats->fmts[i].bpp; ++ st_info(ST_ISP, "Use input format = 0x%x\n", fmt->code); ++ } ++ } ++ ++ switch (pad) { ++ case STF_ISP_PAD_SINK: ++ break; ++ case STF_ISP_PAD_SRC: ++ isp_dev->rect[ISP_COMPOSE].bpp = bpp; ++ break; ++ case STF_ISP_PAD_SRC_SS0: ++ isp_dev->rect[ISP_SCALE_SS0].bpp = bpp; ++ break; ++ case STF_ISP_PAD_SRC_SS1: ++ isp_dev->rect[ISP_SCALE_SS1].bpp = bpp; ++ break; ++ case STF_ISP_PAD_SRC_ITIW: ++ case STF_ISP_PAD_SRC_ITIR: ++ isp_dev->rect[ISP_ITIWS].bpp = bpp; ++ break; ++ case STF_ISP_PAD_SRC_RAW: ++ isp_dev->rect[ISP_CROP].bpp = bpp; ++ break; ++ case STF_ISP_PAD_SRC_SCD_Y: ++ break; ++ } ++} ++ ++static int isp_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd); ++ const struct isp_format_table *formats; ++ ++ if (code->index >= isp_dev->formats[code->pad].nfmts) ++ return -EINVAL; ++ ++ formats = &isp_dev->formats[code->pad]; ++ code->code = formats->fmts[code->index].code; ++ code->flags = 0; ++ ++ return 0; ++} ++ ++static int isp_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt format; ++ ++ if (fse->index != 0) ++ return -EINVAL; ++ ++ format.code = fse->code; ++ format.width = 1; ++ format.height = 1; ++ isp_try_format(isp_dev, state, fse->pad, &format, fse->which); ++ fse->min_width = format.width; ++ fse->min_height = format.height; ++ ++ if (format.code != fse->code) ++ return -EINVAL; ++ ++ format.code = fse->code; ++ format.width = -1; ++ format.height = -1; ++ isp_try_format(isp_dev, state, fse->pad, &format, fse->which); ++ fse->max_width = format.width; ++ fse->max_height = format.height; ++ ++ return 0; ++} ++ ++static int isp_get_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt *format; ++ ++ format = __isp_get_format(isp_dev, state, fmt->pad, fmt->which); ++ if (format == NULL) ++ return -EINVAL; ++ ++ fmt->format = *format; ++ ++ return 0; ++} ++ ++static int isp_set_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt *format; ++ struct v4l2_subdev_selection sel = { 0 }; ++ struct v4l2_rect *rect = NULL; ++ int ret; ++ ++ st_debug(ST_ISP, "%s pad=%d, code=%x, which=%d\n", ++ __func__, fmt->reserved[0], fmt->format.code, fmt->which); ++ format = __isp_get_format(isp_dev, state, fmt->pad, fmt->which); ++ if (format == NULL) ++ return -EINVAL; ++ ++ mutex_lock(&isp_dev->stream_lock); ++ if (isp_dev->stream_count) { ++ fmt->format = *format; ++ if (fmt->reserved[0] != 0) { ++ sel.which = fmt->which; ++ sel.pad = fmt->reserved[0]; ++ ++ switch (fmt->reserved[0]) { ++ case STF_ISP_PAD_SRC: ++ rect = __isp_get_compose(isp_dev, state, fmt->which); ++ break; ++ case STF_ISP_PAD_SRC_SS0: ++ case STF_ISP_PAD_SRC_SS1: ++ rect = __isp_get_scale(isp_dev, state, &sel); ++ break; ++ case STF_ISP_PAD_SRC_ITIW: ++ case STF_ISP_PAD_SRC_ITIR: ++ rect = __isp_get_itiws(isp_dev, state, fmt->which); ++ break; ++ case STF_ISP_PAD_SRC_RAW: ++ case STF_ISP_PAD_SRC_SCD_Y: ++ rect = __isp_get_crop(isp_dev, state, fmt->which); ++ break; ++ default: ++ break; ++ } ++ if (rect != NULL) { ++ fmt->format.width = rect->width; ++ fmt->format.height = rect->height; ++ } ++ } ++ mutex_unlock(&isp_dev->stream_lock); ++ goto out; ++ } else { ++ isp_try_format(isp_dev, state, fmt->pad, &fmt->format, fmt->which); ++ *format = fmt->format; ++ } ++ mutex_unlock(&isp_dev->stream_lock); ++ ++ /* Propagate the format from sink to source */ ++ if (fmt->pad == STF_ISP_PAD_SINK) { ++ /* Reset sink pad compose selection */ ++ sel.which = fmt->which; ++ sel.pad = STF_ISP_PAD_SINK; ++ sel.target = V4L2_SEL_TGT_CROP; ++ sel.r.width = fmt->format.width; ++ sel.r.height = fmt->format.height; ++ ret = isp_set_selection(sd, state, &sel); ++ if (ret < 0) ++ return ret; ++ } ++ ++out: ++ return 0; ++} ++ ++static struct v4l2_rect * ++__isp_get_compose(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ enum v4l2_subdev_format_whence which) ++{ ++ if (which == V4L2_SUBDEV_FORMAT_TRY) ++ return v4l2_subdev_get_try_compose(&isp_dev->subdev, state, ++ STF_ISP_PAD_SINK); ++ ++ ++ return &isp_dev->rect[ISP_COMPOSE].rect; ++} ++ ++static struct v4l2_rect * ++__isp_get_crop(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ enum v4l2_subdev_format_whence which) ++{ ++ if (which == V4L2_SUBDEV_FORMAT_TRY) ++ return v4l2_subdev_get_try_crop(&isp_dev->subdev, state, ++ STF_ISP_PAD_SINK); ++ ++ return &isp_dev->rect[ISP_CROP].rect; ++} ++ ++static struct v4l2_rect * ++__isp_get_scale(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_selection *sel) ++{ ++ int pad; ++ ++ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) ++ return v4l2_subdev_get_try_compose(&isp_dev->subdev, state, ++ STF_ISP_PAD_SINK); ++ if (sel->pad != STF_ISP_PAD_SRC_SS0 && sel->pad != STF_ISP_PAD_SRC_SS1) ++ return NULL; ++ ++ pad = sel->pad == STF_ISP_PAD_SRC_SS0 ? ISP_SCALE_SS0 : ISP_SCALE_SS1; ++ return &isp_dev->rect[pad].rect; ++} ++ ++static struct v4l2_rect * ++__isp_get_itiws(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ enum v4l2_subdev_format_whence which) ++{ ++ if (which == V4L2_SUBDEV_FORMAT_TRY) ++ return v4l2_subdev_get_try_crop(&isp_dev->subdev, state, STF_ISP_PAD_SINK); ++ ++ return &isp_dev->rect[ISP_ITIWS].rect; ++} ++ ++static void isp_try_crop(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ struct v4l2_rect *rect, ++ enum v4l2_subdev_format_whence which) ++{ ++ struct v4l2_mbus_framefmt *fmt; ++ ++ fmt = __isp_get_format(isp_dev, state, STF_ISP_PAD_SINK, which); ++ ++ if (rect->width > fmt->width) ++ rect->width = fmt->width; ++ ++ if (rect->width + rect->left > fmt->width) ++ rect->left = fmt->width - rect->width; ++ ++ if (rect->height > fmt->height) ++ rect->height = fmt->height; ++ ++ if (rect->height + rect->top > fmt->height) ++ rect->top = fmt->height - rect->height; ++ ++ if (rect->width < STFCAMSS_FRAME_MIN_WIDTH) { ++ rect->left = 0; ++ rect->width = STFCAMSS_FRAME_MIN_WIDTH; ++ } ++ ++ if (rect->height < STFCAMSS_FRAME_MIN_HEIGHT) { ++ rect->top = 0; ++ rect->height = STFCAMSS_FRAME_MIN_HEIGHT; ++ } ++ rect->height &= ~0x1; ++} ++ ++static void isp_try_compose(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ struct v4l2_rect *rect, ++ enum v4l2_subdev_format_whence which) ++{ ++ struct v4l2_rect *crop; ++ ++ crop = __isp_get_crop(isp_dev, state, which); ++ ++ if (rect->width > crop->width) ++ rect->width = crop->width; ++ ++ if (rect->height > crop->height) ++ rect->height = crop->height; ++ ++ if (crop->width > rect->width * SCALER_RATIO_MAX) ++ rect->width = (crop->width + SCALER_RATIO_MAX - 1) / ++ SCALER_RATIO_MAX; ++ ++ if (crop->height > rect->height * SCALER_RATIO_MAX) ++ rect->height = (crop->height + SCALER_RATIO_MAX - 1) / ++ SCALER_RATIO_MAX; ++ ++ if (rect->width < STFCAMSS_FRAME_MIN_WIDTH) ++ rect->width = STFCAMSS_FRAME_MIN_WIDTH; ++ ++ if (rect->height < STFCAMSS_FRAME_MIN_HEIGHT) ++ rect->height = STFCAMSS_FRAME_MIN_HEIGHT; ++ rect->height &= ~0x1; ++} ++ ++static void isp_try_scale(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ struct v4l2_rect *rect, ++ enum v4l2_subdev_format_whence which) ++{ ++ struct v4l2_rect *compose; ++ ++ compose = __isp_get_compose(isp_dev, state, which); ++ ++ if (rect->width > compose->width) ++ rect->width = compose->width; ++ ++ if (rect->width + rect->left > compose->width) ++ rect->left = compose->width - rect->width; ++ ++ if (rect->height > compose->height) ++ rect->height = compose->height; ++ ++ if (rect->height + rect->top > compose->height) ++ rect->top = compose->height - rect->height; ++ ++ if (rect->width < STFCAMSS_FRAME_MIN_WIDTH) { ++ rect->left = 0; ++ rect->width = STFCAMSS_FRAME_MIN_WIDTH; ++ } ++ ++ if (rect->height < STFCAMSS_FRAME_MIN_HEIGHT) { ++ rect->top = 0; ++ rect->height = STFCAMSS_FRAME_MIN_HEIGHT; ++ } ++ rect->height &= ~0x1; ++} ++ ++static void isp_try_itiws(struct stf_isp_dev *isp_dev, ++ struct v4l2_subdev_state *state, ++ struct v4l2_rect *rect, ++ enum v4l2_subdev_format_whence which) ++{ ++ struct v4l2_rect *crop; ++ ++ crop = __isp_get_crop(isp_dev, state, which); ++ ++ if (rect->width > crop->width) ++ rect->width = crop->width; ++ ++ if (rect->width + rect->left > crop->width) ++ rect->left = crop->width - rect->width; ++ ++ if (rect->height > crop->height) ++ rect->height = crop->height; ++ ++ if (rect->height + rect->top > crop->height) ++ rect->top = crop->height - rect->height; ++ ++ if (rect->width < STFCAMSS_FRAME_MIN_WIDTH) { ++ rect->left = 0; ++ rect->width = STFCAMSS_FRAME_MIN_WIDTH; ++ } ++ ++ if (rect->height < STFCAMSS_FRAME_MIN_HEIGHT) { ++ rect->top = 0; ++ rect->height = STFCAMSS_FRAME_MIN_HEIGHT; ++ } ++ rect->height &= ~0x1; ++} ++ ++static int isp_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_selection *sel) ++{ ++ struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_subdev_format fmt = { 0 }; ++ struct v4l2_rect *rect; ++ int ret; ++ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ fmt.pad = sel->pad; ++ fmt.which = sel->which; ++ ret = isp_get_format(sd, state, &fmt); ++ if (ret < 0) ++ return ret; ++ ++ sel->r.left = 0; ++ sel->r.top = 0; ++ sel->r.width = fmt.format.width; ++ sel->r.height = fmt.format.height; ++ break; ++ case V4L2_SEL_TGT_CROP: ++ rect = __isp_get_crop(isp_dev, state, sel->which); ++ if (rect == NULL) ++ return -EINVAL; ++ ++ sel->r = *rect; ++ break; ++ case V4L2_SEL_TGT_COMPOSE_BOUNDS: ++ case V4L2_SEL_TGT_COMPOSE_DEFAULT: ++ if (sel->pad > STF_ISP_PAD_SRC_ITIR) ++ return -EINVAL; ++ rect = __isp_get_crop(isp_dev, state, sel->which); ++ if (rect == NULL) ++ return -EINVAL; ++ ++ sel->r.left = rect->left; ++ sel->r.top = rect->top; ++ sel->r.width = rect->width; ++ sel->r.height = rect->height; ++ break; ++ case V4L2_SEL_TGT_COMPOSE: ++ if (sel->pad > STF_ISP_PAD_SRC_ITIR) ++ return -EINVAL; ++ if (sel->pad == STF_ISP_PAD_SRC_SS0 ++ || sel->pad == STF_ISP_PAD_SRC_SS1) { ++ rect = __isp_get_scale(isp_dev, state, sel); ++ if (rect == NULL) ++ return -EINVAL; ++ } else if (sel->pad == STF_ISP_PAD_SRC_ITIW ++ || sel->pad == STF_ISP_PAD_SRC_ITIR) { ++ rect = __isp_get_itiws(isp_dev, state, sel->which); ++ if (rect == NULL) ++ return -EINVAL; ++ } else { ++ rect = __isp_get_compose(isp_dev, state, sel->which); ++ if (rect == NULL) ++ return -EINVAL; ++ } ++ sel->r = *rect; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ st_info(ST_ISP, "%s pad = %d, left = %d, %d, %d, %d\n", ++ __func__, sel->pad, sel->r.left, sel->r.top, sel->r.width, sel->r.height); ++ return 0; ++} ++ ++static int isp_set_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_selection *sel) ++{ ++ struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd); ++ struct v4l2_rect *rect; ++ int ret = 0; ++ ++ if (sel->target == V4L2_SEL_TGT_COMPOSE && ++ ((sel->pad == STF_ISP_PAD_SINK) ++ || (sel->pad == STF_ISP_PAD_SRC))) { ++ struct v4l2_subdev_format fmt = { 0 }; ++ int i; ++ ++ rect = __isp_get_compose(isp_dev, state, sel->which); ++ if (rect == NULL) ++ return -EINVAL; ++ ++ mutex_lock(&isp_dev->stream_lock); ++ if (isp_dev->stream_count) { ++ sel->r = *rect; ++ mutex_unlock(&isp_dev->stream_lock); ++ ret = 0; ++ goto out; ++ } else { ++ isp_try_compose(isp_dev, state, &sel->r, sel->which); ++ *rect = sel->r; ++ } ++ mutex_unlock(&isp_dev->stream_lock); ++ ++ /* Reset source pad format width and height */ ++ fmt.which = sel->which; ++ fmt.pad = STF_ISP_PAD_SRC; ++ ret = isp_get_format(sd, state, &fmt); ++ if (ret < 0) ++ return ret; ++ ++ fmt.format.width = rect->width; ++ fmt.format.height = rect->height; ++ ret = isp_set_format(sd, state, &fmt); ++ ++ /* Reset scale */ ++ for (i = STF_ISP_PAD_SRC_SS0; i <= STF_ISP_PAD_SRC_ITIR; i++) { ++ struct v4l2_subdev_selection scale = { 0 }; ++ ++ scale.which = sel->which; ++ scale.target = V4L2_SEL_TGT_COMPOSE; ++ scale.r = *rect; ++ scale.pad = i; ++ ret = isp_set_selection(sd, state, &scale); ++ } ++ } else if (sel->target == V4L2_SEL_TGT_COMPOSE ++ && ((sel->pad == STF_ISP_PAD_SRC_SS0) ++ || (sel->pad == STF_ISP_PAD_SRC_SS1))) { ++ struct v4l2_subdev_format fmt = { 0 }; ++ ++ rect = __isp_get_scale(isp_dev, state, sel); ++ if (rect == NULL) ++ return -EINVAL; ++ ++ mutex_lock(&isp_dev->stream_lock); ++ if (isp_dev->stream_count) { ++ sel->r = *rect; ++ mutex_unlock(&isp_dev->stream_lock); ++ ret = 0; ++ goto out; ++ } else { ++ isp_try_scale(isp_dev, state, &sel->r, sel->which); ++ *rect = sel->r; ++ } ++ mutex_unlock(&isp_dev->stream_lock); ++ ++ /* Reset source pad format width and height */ ++ fmt.which = sel->which; ++ fmt.pad = sel->pad; ++ ret = isp_get_format(sd, state, &fmt); ++ if (ret < 0) ++ return ret; ++ ++ fmt.format.width = rect->width; ++ fmt.format.height = rect->height; ++ ret = isp_set_format(sd, state, &fmt); ++ } else if (sel->target == V4L2_SEL_TGT_COMPOSE ++ && ((sel->pad == STF_ISP_PAD_SRC_ITIW) ++ || (sel->pad == STF_ISP_PAD_SRC_ITIR))) { ++ struct v4l2_subdev_format fmt = { 0 }; ++ ++ rect = __isp_get_itiws(isp_dev, state, sel->which); ++ if (rect == NULL) ++ return -EINVAL; ++ ++ mutex_lock(&isp_dev->stream_lock); ++ if (isp_dev->stream_count) { ++ sel->r = *rect; ++ mutex_unlock(&isp_dev->stream_lock); ++ ret = 0; ++ goto out; ++ } else { ++ isp_try_itiws(isp_dev, state, &sel->r, sel->which); ++ *rect = sel->r; ++ } ++ mutex_unlock(&isp_dev->stream_lock); ++ ++ /* Reset source pad format width and height */ ++ fmt.which = sel->which; ++ fmt.pad = sel->pad; ++ ret = isp_get_format(sd, state, &fmt); ++ if (ret < 0) ++ return ret; ++ ++ fmt.format.width = rect->width; ++ fmt.format.height = rect->height; ++ ret = isp_set_format(sd, state, &fmt); ++ } else if (sel->target == V4L2_SEL_TGT_CROP) { ++ struct v4l2_subdev_selection compose = { 0 }; ++ int i; ++ ++ rect = __isp_get_crop(isp_dev, state, sel->which); ++ if (rect == NULL) ++ return -EINVAL; ++ ++ mutex_lock(&isp_dev->stream_lock); ++ if (isp_dev->stream_count) { ++ sel->r = *rect; ++ mutex_unlock(&isp_dev->stream_lock); ++ ret = 0; ++ goto out; ++ } else { ++ isp_try_crop(isp_dev, state, &sel->r, sel->which); ++ *rect = sel->r; ++ } ++ mutex_unlock(&isp_dev->stream_lock); ++ ++ /* Reset source compose selection */ ++ compose.which = sel->which; ++ compose.target = V4L2_SEL_TGT_COMPOSE; ++ compose.r.width = rect->width; ++ compose.r.height = rect->height; ++ compose.pad = STF_ISP_PAD_SINK; ++ ret = isp_set_selection(sd, state, &compose); ++ ++ /* Reset source pad format width and height */ ++ for (i = STF_ISP_PAD_SRC_RAW; i < STF_ISP_PAD_MAX; i++) { ++ struct v4l2_subdev_format fmt = { 0 }; ++ ++ fmt.which = sel->which; ++ fmt.pad = i; ++ ret = isp_get_format(sd, state, &fmt); ++ if (ret < 0) ++ return ret; ++ ++ fmt.format.width = rect->width; ++ fmt.format.height = rect->height; ++ ret = isp_set_format(sd, state, &fmt); ++ } ++ } else { ++ ret = -EINVAL; ++ } ++ ++ st_info(ST_ISP, "%s pad = %d, left = %d, %d, %d, %d\n", ++ __func__, sel->pad, sel->r.left, sel->r.top, sel->r.width, sel->r.height); ++out: ++ return ret; ++} ++ ++static int isp_init_formats(struct v4l2_subdev *sd, ++ struct v4l2_subdev_fh *fh) ++{ ++ struct v4l2_subdev_format format = { ++ .pad = STF_ISP_PAD_SINK, ++ .which = fh ? V4L2_SUBDEV_FORMAT_TRY : ++ V4L2_SUBDEV_FORMAT_ACTIVE, ++ .format = { ++ .code = MEDIA_BUS_FMT_RGB565_2X8_LE, ++ .width = 1920, ++ .height = 1080 ++ } ++ }; ++ ++ return isp_set_format(sd, fh ? fh->state : NULL, &format); ++} ++ ++static int isp_link_setup(struct media_entity *entity, ++ const struct media_pad *local, ++ const struct media_pad *remote, u32 flags) ++{ ++ if (flags & MEDIA_LNK_FL_ENABLED) ++ if (media_pad_remote_pad_first(local)) ++ return -EBUSY; ++ return 0; ++} ++ ++static int stf_isp_load_setfile(struct stf_isp_dev *isp_dev, char *file_name) ++{ ++ struct device *dev = isp_dev->stfcamss->dev; ++ const struct firmware *fw; ++ u8 *buf = NULL; ++ int *regval_num; ++ int ret; ++ ++ st_debug(ST_ISP, "%s, file_name %s\n", __func__, file_name); ++ ret = request_firmware(&fw, file_name, dev); ++ if (ret < 0) { ++ st_err(ST_ISP, "firmware request failed (%d)\n", ret); ++ return ret; ++ } ++ buf = devm_kzalloc(dev, fw->size, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ memcpy(buf, fw->data, fw->size); ++ ++ mutex_lock(&isp_dev->setfile_lock); ++ if (isp_dev->setfile.state == 1) ++ devm_kfree(dev, isp_dev->setfile.data); ++ isp_dev->setfile.data = buf; ++ isp_dev->setfile.size = fw->size; ++ isp_dev->setfile.state = 1; ++ regval_num = (int *)&buf[fw->size - sizeof(unsigned int)]; ++ isp_dev->setfile.settings.regval_num = *regval_num; ++ isp_dev->setfile.settings.regval = (struct regval_t *)buf; ++ mutex_unlock(&isp_dev->setfile_lock); ++ ++ st_debug(ST_ISP, "stf_isp setfile loaded size: %zu B, reg_nul: %d\n", ++ fw->size, isp_dev->setfile.settings.regval_num); ++ ++ release_firmware(fw); ++ return ret; ++} ++ ++static long stf_isp_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) ++{ ++ struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd); ++ struct device *dev = isp_dev->stfcamss->dev; ++ int ret = -ENOIOCTLCMD; ++ ++ switch (cmd) { ++ case VIDIOC_STFISP_LOAD_FW: { ++ struct stfisp_fw_info *fw_info = arg; ++ ++ if (IS_ERR(fw_info)) { ++ st_err(ST_ISP, "fw_info failed, params invaild\n"); ++ return -EINVAL; ++ } ++ ++ ret = stf_isp_load_setfile(isp_dev, fw_info->filename); ++ break; ++ } ++ case VIDIOC_STF_DMABUF_ALLOC: ++ case VIDIOC_STF_DMABUF_FREE: ++ ret = stf_dmabuf_ioctl(dev, cmd, arg); ++ break; ++ case VIDIOC_STFISP_GET_REG: ++ ret = isp_dev->hw_ops->isp_reg_read(isp_dev, arg); ++ break; ++ case VIDIOC_STFISP_SET_REG: ++ ret = isp_dev->hw_ops->isp_reg_write(isp_dev, arg); ++ break; ++ case VIDIOC_STFISP_SHADOW_LOCK: ++ if (atomic_add_unless(&isp_dev->shadow_count, 1, 1)) ++ ret = 0; ++ else ++ ret = -EBUSY; ++ st_debug(ST_ISP, "%s, %d, ret = %d\n", __func__, __LINE__, ret); ++ break; ++ case VIDIOC_STFISP_SHADOW_UNLOCK: ++ if (atomic_dec_if_positive(&isp_dev->shadow_count) < 0) ++ ret = -EINVAL; ++ else ++ ret = 0; ++ st_debug(ST_ISP, "%s, %d, ret = %d\n", __func__, __LINE__, ret); ++ break; ++ case VIDIOC_STFISP_SHADOW_UNLOCK_N_TRIGGER: ++ { ++ isp_dev->hw_ops->isp_shadow_trigger(isp_dev); ++ if (atomic_dec_if_positive(&isp_dev->shadow_count) < 0) ++ ret = -EINVAL; ++ else ++ ret = 0; ++ st_debug(ST_ISP, "%s, %d, ret = %d\n", __func__, __LINE__, ret); ++ } ++ break; ++ case VIDIOC_STFISP_SET_USER_CONFIG_ISP: ++ st_debug(ST_ISP, "%s, %d set user_config_isp\n", __func__, __LINE__); ++ user_config_isp = 1; ++ break; ++ default: ++ break; ++ } ++ return ret; ++} ++ ++int isp_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) ++{ ++ struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd); ++ ++ st_debug(ST_ISP, "%s, %d\n", __func__, __LINE__); ++ while (atomic_dec_if_positive(&isp_dev->shadow_count) > 0) ++ st_warn(ST_ISP, "user not unlocked the shadow lock, driver unlock it!\n"); ++ ++ return 0; ++} ++ ++static int stf_isp_subscribe_event(struct v4l2_subdev *sd, ++ struct v4l2_fh *fh, ++ struct v4l2_event_subscription *sub) ++{ ++ switch (sub->type) { ++ case V4L2_EVENT_SOURCE_CHANGE: ++ return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); ++ case V4L2_EVENT_CTRL: ++ return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); ++ default: ++ st_debug(ST_ISP, "unspport subscribe_event\n"); ++ return -EINVAL; ++ } ++} ++ ++static const struct v4l2_subdev_core_ops isp_core_ops = { ++ .s_power = isp_set_power, ++ .ioctl = stf_isp_ioctl, ++ .log_status = v4l2_ctrl_subdev_log_status, ++ // .subscribe_event = v4l2_ctrl_subdev_subscribe_event, ++ .subscribe_event = stf_isp_subscribe_event, ++ .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++}; ++ ++static const struct v4l2_subdev_video_ops isp_video_ops = { ++ .s_stream = isp_set_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops isp_pad_ops = { ++ .enum_mbus_code = isp_enum_mbus_code, ++ .enum_frame_size = isp_enum_frame_size, ++ .get_fmt = isp_get_format, ++ .set_fmt = isp_set_format, ++ .get_selection = isp_get_selection, ++ .set_selection = isp_set_selection, ++}; ++ ++static const struct v4l2_subdev_ops isp_v4l2_ops = { ++ .core = &isp_core_ops, ++ .video = &isp_video_ops, ++ .pad = &isp_pad_ops, ++}; ++ ++static const struct v4l2_subdev_internal_ops isp_v4l2_internal_ops = { ++ .open = isp_init_formats, ++ .close = isp_close, ++}; ++ ++static const struct media_entity_operations isp_media_ops = { ++ .link_setup = isp_link_setup, ++ .link_validate = v4l2_subdev_link_validate, ++}; ++ ++int stf_isp_register(struct stf_isp_dev *isp_dev, ++ struct v4l2_device *v4l2_dev) ++{ ++ struct v4l2_subdev *sd = &isp_dev->subdev; ++ struct media_pad *pads = isp_dev->pads; ++ int ret; ++ ++ v4l2_subdev_init(sd, &isp_v4l2_ops); ++ sd->internal_ops = &isp_v4l2_internal_ops; ++ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; ++ snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", ++ STF_ISP_NAME, 0); ++ v4l2_set_subdevdata(sd, isp_dev); ++ ++ ret = isp_init_formats(sd, NULL); ++ if (ret < 0) { ++ st_err(ST_ISP, "Failed to init format: %d\n", ret); ++ return ret; ++ } ++ ++ pads[STF_ISP_PAD_SINK].flags = MEDIA_PAD_FL_SINK; ++ pads[STF_ISP_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; ++ pads[STF_ISP_PAD_SRC_SS0].flags = MEDIA_PAD_FL_SOURCE; ++ pads[STF_ISP_PAD_SRC_SS1].flags = MEDIA_PAD_FL_SOURCE; ++ pads[STF_ISP_PAD_SRC_ITIW].flags = MEDIA_PAD_FL_SOURCE; ++ pads[STF_ISP_PAD_SRC_ITIR].flags = MEDIA_PAD_FL_SOURCE; ++ pads[STF_ISP_PAD_SRC_RAW].flags = MEDIA_PAD_FL_SOURCE; ++ pads[STF_ISP_PAD_SRC_SCD_Y].flags = MEDIA_PAD_FL_SOURCE; ++ ++ sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; ++ sd->entity.ops = &isp_media_ops; ++ ret = media_entity_pads_init(&sd->entity, STF_ISP_PAD_MAX, pads); ++ if (ret < 0) { ++ st_err(ST_ISP, "Failed to init media entity: %d\n", ret); ++ return ret; ++ } ++ ++ ret = isp_init_controls(isp_dev); ++ if (ret) ++ goto err_sreg; ++ ++ ret = v4l2_device_register_subdev(v4l2_dev, sd); ++ if (ret < 0) { ++ st_err(ST_ISP, "Failed to register subdev: %d\n", ret); ++ goto free_ctrls; ++ } ++ ++ return 0; ++ ++free_ctrls: ++ v4l2_ctrl_handler_free(&isp_dev->ctrls.handler); ++err_sreg: ++ media_entity_cleanup(&sd->entity); ++ return ret; ++} ++ ++int stf_isp_unregister(struct stf_isp_dev *isp_dev) ++{ ++ v4l2_device_unregister_subdev(&isp_dev->subdev); ++ media_entity_cleanup(&isp_dev->subdev.entity); ++ v4l2_ctrl_handler_free(&isp_dev->ctrls.handler); ++ mutex_destroy(&isp_dev->stream_lock); ++ mutex_destroy(&isp_dev->power_lock); ++ mutex_destroy(&isp_dev->setfile_lock); ++ return 0; ++} +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_isp.h +@@ -0,0 +1,222 @@ ++/* SPDX-License-Identifier: GPL-2.0 ++ * ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#ifndef STF_ISP_H ++#define STF_ISP_H ++ ++#include <media/v4l2-subdev.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-device.h> ++#include <media/media-entity.h> ++#include <video/stf-vin.h> ++ ++#define STF_ISP_NAME "stf_isp" ++#define STF_ISP_SETFILE "stf_isp0_fw.bin" ++ ++#define ISP_SCD_BUFFER_SIZE (19 * 256 * 4) // align 128 ++#define ISP_YHIST_BUFFER_SIZE (64 * 4) ++#define ISP_SCD_Y_BUFFER_SIZE (ISP_SCD_BUFFER_SIZE + ISP_YHIST_BUFFER_SIZE) ++#define ISP_RAW_DATA_BITS 12 ++#define SCALER_RATIO_MAX 1 // no compose function ++#define STF_ISP_REG_OFFSET_MAX 0x0FFF ++#define STF_ISP_REG_DELAY_MAX 100 ++ ++#define ISP_REG_CSIINTS_ADDR 0x00000008 ++#define ISP_REG_SENSOR 0x00000014 ++#define ISP_REG_DUMP_CFG_0 0x00000024 ++#define ISP_REG_DUMP_CFG_1 0x00000028 ++#define ISP_REG_SCD_CFG_0 0x00000098 ++#define ISP_REG_SCD_CFG_1 0x0000009C ++#define ISP_REG_SC_CFG_1 0x000000BC ++#define ISP_REG_IESHD_ADDR 0x00000A50 ++#define ISP_REG_SS0AY 0x00000A94 ++#define ISP_REG_SS0AUV 0x00000A98 ++#define ISP_REG_SS0S 0x00000A9C ++#define ISP_REG_SS0IW 0x00000AA8 ++#define ISP_REG_SS1AY 0x00000AAC ++#define ISP_REG_SS1AUV 0x00000AB0 ++#define ISP_REG_SS1S 0x00000AB4 ++#define ISP_REG_SS1IW 0x00000AC0 ++#define ISP_REG_YHIST_CFG_4 0x00000CD8 ++#define ISP_REG_ITIIWSR 0x00000B20 ++#define ISP_REG_ITIDWLSR 0x00000B24 ++#define ISP_REG_ITIDWYSAR 0x00000B28 ++#define ISP_REG_ITIDWUSAR 0x00000B2C ++#define ISP_REG_ITIDRYSAR 0x00000B30 ++#define ISP_REG_ITIDRUSAR 0x00000B34 ++#define ISP_REG_ITIPDFR 0x00000B38 ++#define ISP_REG_ITIDRLSR 0x00000B3C ++#define ISP_REG_ITIBSR 0x00000B40 ++#define ISP_REG_ITIAIR 0x00000B44 ++#define ISP_REG_ITIDPSR 0x00000B48 ++ ++/* The output line of a isp controller */ ++enum isp_line_id { ++ STF_ISP_LINE_INVALID = -1, ++ STF_ISP_LINE_SRC = 1, ++ STF_ISP_LINE_SRC_SS0 = 2, ++ STF_ISP_LINE_SRC_SS1 = 3, ++ STF_ISP_LINE_SRC_ITIW = 4, ++ STF_ISP_LINE_SRC_ITIR = 5, ++ STF_ISP_LINE_SRC_RAW = 6, ++ STF_ISP_LINE_SRC_SCD_Y = 7, ++ STF_ISP_LINE_MAX = STF_ISP_LINE_SRC_SCD_Y ++}; ++ ++/* pad id for media framework */ ++enum isp_pad_id { ++ STF_ISP_PAD_SINK = 0, ++ STF_ISP_PAD_SRC = 1, ++ STF_ISP_PAD_SRC_SS0 = 2, ++ STF_ISP_PAD_SRC_SS1 = 3, ++ STF_ISP_PAD_SRC_ITIW = 4, ++ STF_ISP_PAD_SRC_ITIR = 5, ++ STF_ISP_PAD_SRC_RAW = 6, ++ STF_ISP_PAD_SRC_SCD_Y = 7, ++ STF_ISP_PAD_MAX = 8 ++}; ++ ++enum { ++ EN_INT_NONE = 0, ++ EN_INT_ISP_DONE = (0x1 << 24), ++ EN_INT_CSI_DONE = (0x1 << 25), ++ EN_INT_SC_DONE = (0x1 << 26), ++ EN_INT_LINE_INT = (0x1 << 27), ++ EN_INT_ALL = (0xF << 24), ++}; ++ ++enum { ++ DVP_SENSOR = 0, ++ CSI_SENSOR, ++}; ++ ++#define ISP_AWB_OECF_SKIP_FRAME 0 ++// 0x0BC [31:30] SEL - sc0 input mux for sc awb ++// 00 : after DEC, 01 : after OBC, 10 : after OECF, 11 : after AWB ++enum scd_type { ++ DEC_TYPE = 0, ++ OBC_TYPE, ++ OECF_TYPE, ++ AWB_TYPE ++}; ++ ++struct isp_format { ++ u32 code; ++ u8 bpp; ++}; ++ ++struct isp_format_table { ++ const struct isp_format *fmts; ++ int nfmts; ++}; ++ ++struct regval_t { ++ u32 addr; ++ u32 val; ++ u32 mask; ++ u32 delay_ms; ++}; ++ ++struct reg_table { ++ struct regval_t *regval; ++ int regval_num; ++}; ++ ++struct isp_stream_format { ++ struct v4l2_rect rect; ++ u32 bpp; ++}; ++ ++struct stf_isp_dev; ++enum subdev_type; ++ ++struct isp_hw_ops { ++ int (*isp_clk_enable)(struct stf_isp_dev *isp_dev); ++ int (*isp_clk_disable)(struct stf_isp_dev *isp_dev); ++ int (*isp_reset)(struct stf_isp_dev *isp_dev); ++ int (*isp_config_set)(struct stf_isp_dev *isp_dev); ++ int (*isp_set_format)(struct stf_isp_dev *isp_dev, ++ struct isp_stream_format *crop, u32 mcode, ++ int type); ++ // u32 width, u32 height); ++ int (*isp_stream_set)(struct stf_isp_dev *isp_dev, int on); ++ int (*isp_reg_read)(struct stf_isp_dev *isp_dev, void *arg); ++ int (*isp_reg_write)(struct stf_isp_dev *isp_dev, void *arg); ++ int (*isp_shadow_trigger)(struct stf_isp_dev *isp_dev); ++}; ++ ++struct isp_ctrls { ++ struct v4l2_ctrl_handler handler; ++ struct v4l2_ctrl *pixel_rate; ++ struct { ++ struct v4l2_ctrl *auto_exp; ++ struct v4l2_ctrl *exposure; ++ }; ++ struct { ++ struct v4l2_ctrl *auto_wb; ++ struct v4l2_ctrl *blue_balance; ++ struct v4l2_ctrl *red_balance; ++ }; ++ struct { ++ struct v4l2_ctrl *auto_gain; ++ struct v4l2_ctrl *gain; ++ }; ++ struct v4l2_ctrl *brightness; ++ struct v4l2_ctrl *light_freq; ++ struct v4l2_ctrl *saturation; ++ struct v4l2_ctrl *contrast; ++ struct v4l2_ctrl *hue; ++ struct v4l2_ctrl *test_pattern; ++ struct v4l2_ctrl *hflip; ++ struct v4l2_ctrl *vflip; ++}; ++ ++struct isp_setfile { ++ struct reg_table settings; ++ const u8 *data; ++ unsigned int size; ++ unsigned int state; ++}; ++ ++enum { ++ ISP_CROP = 0, ++ ISP_COMPOSE, ++ ISP_SCALE_SS0, ++ ISP_SCALE_SS1, ++ ISP_ITIWS, ++ ISP_RECT_MAX ++}; ++ ++struct stf_isp_dev { ++ enum subdev_type sdev_type; // must be frist ++ struct stfcamss *stfcamss; ++ struct v4l2_subdev subdev; ++ struct media_pad pads[STF_ISP_PAD_MAX]; ++ struct v4l2_mbus_framefmt fmt[STF_ISP_PAD_MAX]; ++ struct isp_stream_format rect[ISP_RECT_MAX]; ++ const struct isp_format_table *formats; ++ unsigned int nformats; ++ struct isp_hw_ops *hw_ops; ++ struct mutex power_lock; ++ int power_count; ++ struct mutex stream_lock; ++ int stream_count; ++ atomic_t shadow_count; ++ ++ struct isp_ctrls ctrls; ++ struct mutex setfile_lock; ++ struct isp_setfile setfile; ++ struct reg_table *context_regs; ++}; ++ ++extern int stf_isp_subdev_init(struct stfcamss *stfcamss); ++extern int stf_isp_register(struct stf_isp_dev *isp_dev, ++ struct v4l2_device *v4l2_dev); ++extern int stf_isp_unregister(struct stf_isp_dev *isp_dev); ++extern struct isp_hw_ops isp_ops; ++extern void dump_isp_reg(void *__iomem ispbase); ++ ++#endif /* STF_ISP_H */ +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_isp_hw_ops.c +@@ -0,0 +1,1550 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include "stfcamss.h" ++#include <linux/io.h> ++#include <linux/fb.h> ++#include <linux/module.h> ++#include <video/stf-vin.h> ++#include "stf_isp_ioctl.h" ++#include "stf_isp.h" ++#include <linux/delay.h> ++#include <linux/clk.h> ++#define USE_NEW_CONFIG_SETTING ++ ++static struct regval_t isp_reg_init_config_list[] = { ++ /* config DC(0040H~0044H) */ ++ {0x00000044, 0x00000000, 0, 0}, ++ /* config DEC(0030H) */ ++ {0x00000030, 0x00000000, 0, 0}, ++ /* config OBC(0034H, 02E0H~02FCH) */ ++ {0x00000034, 0x000000BB, 0, 0}, ++ {0x000002E0, 0x40404040, 0, 0}, ++ {0x000002E4, 0x40404040, 0, 0}, ++ {0x000002E8, 0x40404040, 0, 0}, ++ {0x000002EC, 0x40404040, 0, 0}, ++ {0x000002F0, 0x00000000, 0, 0}, ++ {0x000002F4, 0x00000000, 0, 0}, ++ {0x000002F8, 0x00000000, 0, 0}, ++ {0x000002FC, 0x00000000, 0, 0}, ++ /* config LCBQ(0074H, 007CH, 0300H~039FH, and 0400H~049FH) */ ++ {0x00000074, 0x00009900, 0, 0}, ++ {0x0000007C, 0x01E40040, 0, 0}, ++ {0x00000300, 0x01000100, 0, 0}, ++ {0x00000304, 0x01000100, 0, 0}, ++ {0x00000308, 0x01000100, 0, 0}, ++ {0x0000030C, 0x01000100, 0, 0}, ++ {0x00000310, 0x01000100, 0, 0}, ++ {0x00000314, 0x01000100, 0, 0}, ++ {0x00000318, 0x01000100, 0, 0}, ++ {0x0000031C, 0x01000100, 0, 0}, ++ {0x00000320, 0x01000100, 0, 0}, ++ {0x00000324, 0x01000100, 0, 0}, ++ {0x00000328, 0x01000100, 0, 0}, ++ {0x0000032C, 0x01000100, 0, 0}, ++ {0x00000330, 0x00000100, 0, 0}, ++ {0x00000334, 0x01000100, 0, 0}, ++ {0x00000338, 0x01000100, 0, 0}, ++ {0x0000033C, 0x01000100, 0, 0}, ++ {0x00000340, 0x01000100, 0, 0}, ++ {0x00000344, 0x01000100, 0, 0}, ++ {0x00000348, 0x01000100, 0, 0}, ++ {0x0000034C, 0x01000100, 0, 0}, ++ {0x00000350, 0x01000100, 0, 0}, ++ {0x00000354, 0x01000100, 0, 0}, ++ {0x00000358, 0x01000100, 0, 0}, ++ {0x0000035C, 0x01000100, 0, 0}, ++ {0x00000360, 0x01000100, 0, 0}, ++ {0x00000364, 0x00000100, 0, 0}, ++ {0x00000368, 0x01000100, 0, 0}, ++ {0x0000036C, 0x01000100, 0, 0}, ++ {0x00000370, 0x01000100, 0, 0}, ++ {0x00000374, 0x01000100, 0, 0}, ++ {0x00000378, 0x01000100, 0, 0}, ++ {0x0000037C, 0x01000100, 0, 0}, ++ {0x00000380, 0x01000100, 0, 0}, ++ {0x00000384, 0x01000100, 0, 0}, ++ {0x00000388, 0x01000100, 0, 0}, ++ {0x0000038C, 0x01000100, 0, 0}, ++ {0x00000390, 0x01000100, 0, 0}, ++ {0x00000394, 0x01000100, 0, 0}, ++ {0x00000398, 0x00000100, 0, 0}, ++ {0x0000039C, 0x01000100, 0, 0}, ++ {0x000003A0, 0x01000100, 0, 0}, ++ {0x000003A4, 0x01000100, 0, 0}, ++ {0x000003A8, 0x01000100, 0, 0}, ++ {0x000003AC, 0x01000100, 0, 0}, ++ {0x000003B0, 0x01000100, 0, 0}, ++ {0x000003B4, 0x01000100, 0, 0}, ++ {0x000003B8, 0x01000100, 0, 0}, ++ {0x000003BC, 0x01000100, 0, 0}, ++ {0x000003C0, 0x01000100, 0, 0}, ++ {0x000003C4, 0x01000100, 0, 0}, ++ {0x000003C8, 0x01000100, 0, 0}, ++ {0x000003CC, 0x00000100, 0, 0}, ++ {0x00000400, 0x00000000, 0, 0}, ++ {0x00000404, 0x00000000, 0, 0}, ++ {0x00000408, 0x00000000, 0, 0}, ++ {0x0000040C, 0x00000000, 0, 0}, ++ {0x00000410, 0x00000000, 0, 0}, ++ {0x00000414, 0x00000000, 0, 0}, ++ {0x00000418, 0x00000000, 0, 0}, ++ {0x0000041C, 0x00000000, 0, 0}, ++ {0x00000420, 0x00000000, 0, 0}, ++ {0x00000424, 0x00000000, 0, 0}, ++ {0x00000428, 0x00000000, 0, 0}, ++ {0x0000042C, 0x00000000, 0, 0}, ++ {0x00000430, 0x00000000, 0, 0}, ++ {0x00000434, 0x00000000, 0, 0}, ++ {0x00000438, 0x00000000, 0, 0}, ++ {0x0000043C, 0x00000000, 0, 0}, ++ {0x00000440, 0x00000000, 0, 0}, ++ {0x00000444, 0x00000000, 0, 0}, ++ {0x00000448, 0x00000000, 0, 0}, ++ {0x0000044C, 0x00000000, 0, 0}, ++ {0x00000450, 0x00000000, 0, 0}, ++ {0x00000454, 0x00000000, 0, 0}, ++ {0x00000458, 0x00000000, 0, 0}, ++ {0x0000045C, 0x00000000, 0, 0}, ++ {0x00000460, 0x00000000, 0, 0}, ++ {0x00000464, 0x00000000, 0, 0}, ++ {0x00000468, 0x00000000, 0, 0}, ++ {0x0000046C, 0x00000000, 0, 0}, ++ {0x00000470, 0x00000000, 0, 0}, ++ {0x00000474, 0x00000000, 0, 0}, ++ {0x00000478, 0x00000000, 0, 0}, ++ {0x0000047C, 0x00000000, 0, 0}, ++ {0x00000480, 0x00000000, 0, 0}, ++ {0x00000484, 0x00000000, 0, 0}, ++ {0x00000488, 0x00000000, 0, 0}, ++ {0x0000048C, 0x00000000, 0, 0}, ++ {0x00000490, 0x00000000, 0, 0}, ++ {0x00000494, 0x00000000, 0, 0}, ++ {0x00000498, 0x00000000, 0, 0}, ++ {0x0000049C, 0x00000000, 0, 0}, ++ {0x000004A0, 0x00000000, 0, 0}, ++ {0x000004A4, 0x00000000, 0, 0}, ++ {0x000004A8, 0x00000000, 0, 0}, ++ {0x000004AC, 0x00000000, 0, 0}, ++ {0x000004B0, 0x00000000, 0, 0}, ++ {0x000004B4, 0x00000000, 0, 0}, ++ {0x000004B8, 0x00000000, 0, 0}, ++ {0x000004BC, 0x00000000, 0, 0}, ++ {0x000004C0, 0x00000000, 0, 0}, ++ {0x000004C4, 0x00000000, 0, 0}, ++ {0x000004C8, 0x00000000, 0, 0}, ++ {0x000004CC, 0x00000000, 0, 0}, ++ /* config OECF(0100H~027CH) */ ++ {0x00000100, 0x00100000, 0, 0}, ++ {0x00000104, 0x00400020, 0, 0}, ++ {0x00000108, 0x00800060, 0, 0}, ++ {0x0000010C, 0x00C000A0, 0, 0}, ++ {0x00000110, 0x010000E0, 0, 0}, ++ {0x00000114, 0x02000180, 0, 0}, ++ {0x00000118, 0x03000280, 0, 0}, ++ {0x0000011C, 0x03FE0380, 0, 0}, ++ {0x00000120, 0x00100000, 0, 0}, ++ {0x00000124, 0x00400020, 0, 0}, ++ {0x00000128, 0x00800060, 0, 0}, ++ {0x0000012C, 0x00C000A0, 0, 0}, ++ {0x00000130, 0x010000E0, 0, 0}, ++ {0x00000134, 0x02000180, 0, 0}, ++ {0x00000138, 0x03000280, 0, 0}, ++ {0x0000013C, 0x03FE0380, 0, 0}, ++ {0x00000140, 0x00100000, 0, 0}, ++ {0x00000144, 0x00400020, 0, 0}, ++ {0x00000148, 0x00800060, 0, 0}, ++ {0x0000014C, 0x00C000A0, 0, 0}, ++ {0x00000150, 0x010000E0, 0, 0}, ++ {0x00000154, 0x02000180, 0, 0}, ++ {0x00000158, 0x03000280, 0, 0}, ++ {0x0000015C, 0x03FE0380, 0, 0}, ++ {0x00000160, 0x00100000, 0, 0}, ++ {0x00000164, 0x00400020, 0, 0}, ++ {0x00000168, 0x00800060, 0, 0}, ++ {0x0000016C, 0x00C000A0, 0, 0}, ++ {0x00000170, 0x010000E0, 0, 0}, ++ {0x00000174, 0x02000180, 0, 0}, ++ {0x00000178, 0x03000280, 0, 0}, ++ {0x0000017C, 0x03FE0380, 0, 0}, ++ {0x00000180, 0x00100000, 0, 0}, ++ {0x00000184, 0x00400020, 0, 0}, ++ {0x00000188, 0x00800060, 0, 0}, ++ {0x0000018C, 0x00C000A0, 0, 0}, ++ {0x00000190, 0x010000E0, 0, 0}, ++ {0x00000194, 0x02000180, 0, 0}, ++ {0x00000198, 0x03000280, 0, 0}, ++ {0x0000019C, 0x03FE0380, 0, 0}, ++ {0x000001A0, 0x00100000, 0, 0}, ++ {0x000001A4, 0x00400020, 0, 0}, ++ {0x000001A8, 0x00800060, 0, 0}, ++ {0x000001AC, 0x00C000A0, 0, 0}, ++ {0x000001B0, 0x010000E0, 0, 0}, ++ {0x000001B4, 0x02000180, 0, 0}, ++ {0x000001B8, 0x03000280, 0, 0}, ++ {0x000001BC, 0x03FE0380, 0, 0}, ++ {0x000001C0, 0x00100000, 0, 0}, ++ {0x000001C4, 0x00400020, 0, 0}, ++ {0x000001C8, 0x00800060, 0, 0}, ++ {0x000001CC, 0x00C000A0, 0, 0}, ++ {0x000001D0, 0x010000E0, 0, 0}, ++ {0x000001D4, 0x02000180, 0, 0}, ++ {0x000001D8, 0x03000280, 0, 0}, ++ {0x000001DC, 0x03FE0380, 0, 0}, ++ {0x000001E0, 0x00100000, 0, 0}, ++ {0x000001E4, 0x00400020, 0, 0}, ++ {0x000001E8, 0x00800060, 0, 0}, ++ {0x000001EC, 0x00C000A0, 0, 0}, ++ {0x000001F0, 0x010000E0, 0, 0}, ++ {0x000001F4, 0x02000180, 0, 0}, ++ {0x000001F8, 0x03000280, 0, 0}, ++ {0x000001FC, 0x03FE0380, 0, 0}, ++ {0x00000200, 0x00800080, 0, 0}, ++ {0x00000204, 0x00800080, 0, 0}, ++ {0x00000208, 0x00800080, 0, 0}, ++ {0x0000020C, 0x00800080, 0, 0}, ++ {0x00000210, 0x00800080, 0, 0}, ++ {0x00000214, 0x00800080, 0, 0}, ++ {0x00000218, 0x00800080, 0, 0}, ++ {0x0000021C, 0x00800080, 0, 0}, ++ {0x00000220, 0x00800080, 0, 0}, ++ {0x00000224, 0x00800080, 0, 0}, ++ {0x00000228, 0x00800080, 0, 0}, ++ {0x0000022C, 0x00800080, 0, 0}, ++ {0x00000230, 0x00800080, 0, 0}, ++ {0x00000234, 0x00800080, 0, 0}, ++ {0x00000238, 0x00800080, 0, 0}, ++ {0x0000023C, 0x00800080, 0, 0}, ++ {0x00000240, 0x00800080, 0, 0}, ++ {0x00000244, 0x00800080, 0, 0}, ++ {0x00000248, 0x00800080, 0, 0}, ++ {0x0000024C, 0x00800080, 0, 0}, ++ {0x00000250, 0x00800080, 0, 0}, ++ {0x00000254, 0x00800080, 0, 0}, ++ {0x00000258, 0x00800080, 0, 0}, ++ {0x0000025C, 0x00800080, 0, 0}, ++ {0x00000260, 0x00800080, 0, 0}, ++ {0x00000264, 0x00800080, 0, 0}, ++ {0x00000268, 0x00800080, 0, 0}, ++ {0x0000026C, 0x00800080, 0, 0}, ++ {0x00000270, 0x00800080, 0, 0}, ++ {0x00000274, 0x00800080, 0, 0}, ++ {0x00000278, 0x00800080, 0, 0}, ++ {0x0000027C, 0x00800080, 0, 0}, ++ /* config OECFHM(03D0H~03E4H) */ ++ {0x000003D0, 0x04000000, 0, 0}, ++ {0x000003D4, 0x0C000800, 0, 0}, ++ {0x000003D8, 0x00000FFF, 0, 0}, ++ {0x000003DC, 0x08000800, 0, 0}, ++ {0x000003E0, 0x08000800, 0, 0}, ++ {0x000003E4, 0x00000800, 0, 0}, ++ /* config LCCF(0050H, 0058H, 00E0H~00ECH) */ ++ {0x00000050, 0x021C03C0, 0, 0}, ++ {0x00000058, 0x0000000B, 0, 0}, ++ {0x000000E0, 0x00000000, 0, 0}, ++ {0x000000E4, 0x00000000, 0, 0}, ++ {0x000000E8, 0x00000000, 0, 0}, ++ {0x000000EC, 0x00000000, 0, 0}, ++ /* config AWB(0280H~02DCH) */ ++ {0x00000280, 0x00000000, 0, 0}, ++ {0x00000284, 0x00000000, 0, 0}, ++ {0x00000288, 0x00000000, 0, 0}, ++ {0x0000028C, 0x00000000, 0, 0}, ++ {0x00000290, 0x00000000, 0, 0}, ++ {0x00000294, 0x00000000, 0, 0}, ++ {0x00000298, 0x00000000, 0, 0}, ++ {0x0000029C, 0x00000000, 0, 0}, ++ {0x000002A0, 0x00000000, 0, 0}, ++ {0x000002A4, 0x00000000, 0, 0}, ++ {0x000002A8, 0x00000000, 0, 0}, ++ {0x000002AC, 0x00000000, 0, 0}, ++ {0x000002B0, 0x00000000, 0, 0}, ++ {0x000002B4, 0x00000000, 0, 0}, ++ {0x000002B8, 0x00000000, 0, 0}, ++ {0x000002BC, 0x00000000, 0, 0}, ++ {0x000002C0, 0x00800080, 0, 0}, ++ {0x000002C4, 0x00800080, 0, 0}, ++ {0x000002C8, 0x00800080, 0, 0}, ++ {0x000002CC, 0x00800080, 0, 0}, ++ {0x000002D0, 0x00800080, 0, 0}, ++ {0x000002D4, 0x00800080, 0, 0}, ++ {0x000002D8, 0x00800080, 0, 0}, ++ {0x000002DC, 0x00800080, 0, 0}, ++ /* config CTC(0A10H) and DBC(0A14H) filter */ ++ {0x00000A10, 0x41400040, 0, 0}, ++ {0x00000A14, 0x02000200, 0, 0}, ++ /* config CFA(0018H, 0A1CH) */ ++ {0x00000018, 0x000011BB, 0, 0}, ++ {0x00000A1C, 0x00000032, 0, 0}, ++ /* config CCM(0C40H~0CA4H) */ ++ {0x00000C40, 0x00060000, 0, 0}, ++ {0x00000C44, 0x00000000, 0, 0}, ++ {0x00000C48, 0x00000000, 0, 0}, ++ {0x00000C4C, 0x00000000, 0, 0}, ++ {0x00000C50, 0x00000000, 0, 0}, ++ {0x00000C54, 0x00000000, 0, 0}, ++ {0x00000C58, 0x00000000, 0, 0}, ++ {0x00000C5C, 0x00000000, 0, 0}, ++ {0x00000C60, 0x00000000, 0, 0}, ++ {0x00000C64, 0x00000000, 0, 0}, ++ {0x00000C68, 0x00000000, 0, 0}, ++ {0x00000C6C, 0x00000000, 0, 0}, ++ {0x00000C70, 0x00000080, 0, 0}, ++ {0x00000C74, 0x00000000, 0, 0}, ++ {0x00000C78, 0x00000000, 0, 0}, ++ {0x00000C7C, 0x00000000, 0, 0}, ++ {0x00000C80, 0x00000080, 0, 0}, ++ {0x00000C84, 0x00000000, 0, 0}, ++ {0x00000C88, 0x00000000, 0, 0}, ++ {0x00000C8C, 0x00000000, 0, 0}, ++ {0x00000C90, 0x00000080, 0, 0}, ++ {0x00000C94, 0x00000000, 0, 0}, ++ {0x00000C98, 0x00000000, 0, 0}, ++ {0x00000C9C, 0x00000000, 0, 0}, ++ {0x00000CA0, 0x00000700, 0, 0}, ++ {0x00000CA4, 0x00000200, 0, 0}, ++ /* config GMARGB(0E00H~0E38H) */ ++ {0x00000E00, 0x24000000, 0, 0}, ++ {0x00000E04, 0x08000020, 0, 0}, ++ {0x00000E08, 0x08000040, 0, 0}, ++ {0x00000E0C, 0x08000060, 0, 0}, ++ {0x00000E10, 0x08000080, 0, 0}, ++ {0x00000E14, 0x080000A0, 0, 0}, ++ {0x00000E18, 0x080000C0, 0, 0}, ++ {0x00000E1C, 0x080000E0, 0, 0}, ++ {0x00000E20, 0x08000100, 0, 0}, ++ {0x00000E24, 0x08000180, 0, 0}, ++ {0x00000E28, 0x08000200, 0, 0}, ++ {0x00000E2C, 0x08000280, 0, 0}, ++ {0x00000E30, 0x08000300, 0, 0}, ++ {0x00000E34, 0x08000380, 0, 0}, ++ {0x00000E38, 0x080003FE, 0, 0}, ++ /* config R2Y(0E40H~0E60H) */ ++ {0x00000E40, 0x0000004C, 0, 0}, ++ {0x00000E44, 0x00000097, 0, 0}, ++ {0x00000E48, 0x0000001D, 0, 0}, ++ {0x00000E4C, 0x000001D5, 0, 0}, ++ {0x00000E50, 0x000001AC, 0, 0}, ++ {0x00000E54, 0x00000080, 0, 0}, ++ {0x00000E58, 0x00000080, 0, 0}, ++ {0x00000E5C, 0x00000194, 0, 0}, ++ {0x00000E60, 0x000001EC, 0, 0}, ++ /* config YCRV(0F00H~0FFCH) */ ++ {0x00000F00, 0x00000000, 0, 0}, ++ {0x00000F04, 0x00000010, 0, 0}, ++ {0x00000F08, 0x00000020, 0, 0}, ++ {0x00000F0C, 0x00000030, 0, 0}, ++ {0x00000F10, 0x00000040, 0, 0}, ++ {0x00000F14, 0x00000050, 0, 0}, ++ {0x00000F18, 0x00000060, 0, 0}, ++ {0x00000F1C, 0x00000070, 0, 0}, ++ {0x00000F20, 0x00000080, 0, 0}, ++ {0x00000F24, 0x00000090, 0, 0}, ++ {0x00000F28, 0x000000A0, 0, 0}, ++ {0x00000F2C, 0x000000B0, 0, 0}, ++ {0x00000F30, 0x000000C0, 0, 0}, ++ {0x00000F34, 0x000000D0, 0, 0}, ++ {0x00000F38, 0x000000E0, 0, 0}, ++ {0x00000F3C, 0x000000F0, 0, 0}, ++ {0x00000F40, 0x00000100, 0, 0}, ++ {0x00000F44, 0x00000110, 0, 0}, ++ {0x00000F48, 0x00000120, 0, 0}, ++ {0x00000F4C, 0x00000130, 0, 0}, ++ {0x00000F50, 0x00000140, 0, 0}, ++ {0x00000F54, 0x00000150, 0, 0}, ++ {0x00000F58, 0x00000160, 0, 0}, ++ {0x00000F5C, 0x00000170, 0, 0}, ++ {0x00000F60, 0x00000180, 0, 0}, ++ {0x00000F64, 0x00000190, 0, 0}, ++ {0x00000F68, 0x000001A0, 0, 0}, ++ {0x00000F6C, 0x000001B0, 0, 0}, ++ {0x00000F70, 0x000001C0, 0, 0}, ++ {0x00000F74, 0x000001D0, 0, 0}, ++ {0x00000F78, 0x000001E0, 0, 0}, ++ {0x00000F7C, 0x000001F0, 0, 0}, ++ {0x00000F80, 0x00000200, 0, 0}, ++ {0x00000F84, 0x00000210, 0, 0}, ++ {0x00000F88, 0x00000220, 0, 0}, ++ {0x00000F8C, 0x00000230, 0, 0}, ++ {0x00000F90, 0x00000240, 0, 0}, ++ {0x00000F94, 0x00000250, 0, 0}, ++ {0x00000F98, 0x00000260, 0, 0}, ++ {0x00000F9C, 0x00000270, 0, 0}, ++ {0x00000FA0, 0x00000280, 0, 0}, ++ {0x00000FA4, 0x00000290, 0, 0}, ++ {0x00000FA8, 0x000002A0, 0, 0}, ++ {0x00000FAC, 0x000002B0, 0, 0}, ++ {0x00000FB0, 0x000002C0, 0, 0}, ++ {0x00000FB4, 0x000002D0, 0, 0}, ++ {0x00000FB8, 0x000002E0, 0, 0}, ++ {0x00000FBC, 0x000002F0, 0, 0}, ++ {0x00000FC0, 0x00000300, 0, 0}, ++ {0x00000FC4, 0x00000310, 0, 0}, ++ {0x00000FC8, 0x00000320, 0, 0}, ++ {0x00000FCC, 0x00000330, 0, 0}, ++ {0x00000FD0, 0x00000340, 0, 0}, ++ {0x00000FD4, 0x00000350, 0, 0}, ++ {0x00000FD8, 0x00000360, 0, 0}, ++ {0x00000FDC, 0x00000370, 0, 0}, ++ {0x00000FE0, 0x00000380, 0, 0}, ++ {0x00000FE4, 0x00000390, 0, 0}, ++ {0x00000FE8, 0x000003A0, 0, 0}, ++ {0x00000FEC, 0x000003B0, 0, 0}, ++ {0x00000FF0, 0x000003C0, 0, 0}, ++ {0x00000FF4, 0x000003D0, 0, 0}, ++ {0x00000FF8, 0x000003E0, 0, 0}, ++ {0x00000FFC, 0x000003F0, 0, 0}, ++ /* config Shrp(0E80H~0EE8H) */ ++ {0x00000E80, 0x00070F00, 0, 0}, ++ {0x00000E84, 0x00180F00, 0, 0}, ++ {0x00000E88, 0x00800F00, 0, 0}, ++ {0x00000E8C, 0x01000F00, 0, 0}, ++ {0x00000E90, 0x00100F00, 0, 0}, ++ {0x00000E94, 0x00600F00, 0, 0}, ++ {0x00000E98, 0x01000F00, 0, 0}, ++ {0x00000E9C, 0x01900F00, 0, 0}, ++ {0x00000EA0, 0x00000F00, 0, 0}, ++ {0x00000EA4, 0x00000F00, 0, 0}, ++ {0x00000EA8, 0x00000F00, 0, 0}, ++ {0x00000EAC, 0x00000F00, 0, 0}, ++ {0x00000EB0, 0x00000F00, 0, 0}, ++ {0x00000EB4, 0x00000F00, 0, 0}, ++ {0x00000EB8, 0x00000F00, 0, 0}, ++ {0x00000EBC, 0x10000000, 0, 0}, ++ {0x00000EC0, 0x10000000, 0, 0}, ++ {0x00000EC4, 0x10000000, 0, 0}, ++ {0x00000EC8, 0x10000000, 0, 0}, ++ {0x00000ECC, 0x10000000, 0, 0}, ++ {0x00000ED0, 0x10000000, 0, 0}, ++ {0x00000ED4, 0x88000D7C, 0, 0}, ++ {0x00000ED8, 0x00C00040, 0, 0}, ++ {0x00000EDC, 0xFF000000, 0, 0}, ++ {0x00000EE0, 0x00A00040, 0, 0}, ++ {0x00000EE4, 0x00000000, 0, 0}, ++ {0x00000EE8, 0x00000000, 0, 0}, ++ /* config DNYUV(0C00H~0C24H) */ ++ {0x00000C00, 0x00777777, 0, 0}, ++ {0x00000C04, 0x00007777, 0, 0}, ++ {0x00000C08, 0x00777777, 0, 0}, ++ {0x00000C0C, 0x00007777, 0, 0}, ++ {0x00000C10, 0x00600040, 0, 0}, ++ {0x00000C14, 0x00D80090, 0, 0}, ++ {0x00000C18, 0x01E60144, 0, 0}, ++ {0x00000C1C, 0x00600040, 0, 0}, ++ {0x00000C20, 0x00D80090, 0, 0}, ++ {0x00000C24, 0x01E60144, 0, 0}, ++ /* config SAT(0A30H~0A40H, 0A54H~0A58H) */ ++ {0x00000A30, 0x00000100, 0, 0}, ++ {0x00000A34, 0x001F0001, 0, 0}, ++ {0x00000A38, 0x00000000, 0, 0}, ++ {0x00000A3C, 0x00000100, 0, 0}, ++ {0x00000A40, 0x00000008, 0, 0}, ++ {0x00000A54, 0x04010001, 0, 0}, ++ {0x00000A58, 0x03FF0001, 0, 0}, ++ /* config OBA(0090H~0094H) */ ++ {0x00000090, 0x04380000, 0, 0}, ++ {0x00000094, 0x04390780, 0, 0}, ++ /* config SC(0098H~009CH, 00B8H~00BCH, ++ * 00C0H, 0C4H~0D4H, 04D0H~054CH, 5D0H~5D4H) ++ */ ++ {0x0000009C, 0x01000000, 0, 0}, ++ {0x000000B8, 0x000C0000, 0, 0}, ++ {0x000000BC, 0xC010151D, 0, 0}, ++ {0x000000C0, 0x01F1BF08, 0, 0}, ++ {0x000000C4, 0xFF00FF00, 0, 0}, ++ {0x000000C8, 0xFF00FF00, 0, 0}, ++ {0x000000CC, 0xFFFF0000, 0, 0}, ++ {0x000000D0, 0xFFFF0000, 0, 0}, ++ {0x000000D4, 0xFFFF0000, 0, 0}, ++ {0x000000D8, 0x01050107, 0, 0}, ++ {0x000004D0, 0x00000000, 0, 0}, ++ {0x000004D4, 0x00000000, 0, 0}, ++ {0x000004D8, 0x00000000, 0, 0}, ++ {0x000004DC, 0x00000000, 0, 0}, ++ {0x000004E0, 0x00000000, 0, 0}, ++ {0x000004E4, 0x00000000, 0, 0}, ++ {0x000004E8, 0x00000000, 0, 0}, ++ {0x000004EC, 0x00000000, 0, 0}, ++ {0x000004F0, 0x00100000, 0, 0}, ++ {0x000004F4, 0x00000000, 0, 0}, ++ {0x000004F8, 0x03D20000, 0, 0}, ++ {0x000004FC, 0x00000000, 0, 0}, ++ {0x00000500, 0x00950000, 0, 0}, ++ {0x00000504, 0x00000000, 0, 0}, ++ {0x00000508, 0x00253000, 0, 0}, ++ {0x0000050C, 0x00000000, 0, 0}, ++ {0x00000510, 0x00000000, 0, 0}, ++ {0x00000514, 0x00000000, 0, 0}, ++ {0x00000518, 0x00000000, 0, 0}, ++ {0x0000051C, 0x00000000, 0, 0}, ++ {0x00000520, 0x00000000, 0, 0}, ++ {0x00000524, 0x00000000, 0, 0}, ++ {0x00000528, 0x00000000, 0, 0}, ++ {0x0000052C, 0x00000000, 0, 0}, ++ {0x00000530, 0x00000000, 0, 0}, ++ {0x00000534, 0x00000000, 0, 0}, ++ {0x00000538, 0xFFFFFFF0, 0, 0}, ++ {0x0000053C, 0x8FFFFFFF, 0, 0}, ++ {0x00000540, 0x0000001E, 0, 0}, ++ {0x00000544, 0x00000000, 0, 0}, ++ {0x00000548, 0x00000000, 0, 0}, ++ {0x0000054C, 0xF0F20000, 0, 0}, ++ {0x000005D0, 0xFF00FF00, 0, 0}, ++ {0x000005D4, 0xFF00FF00, 0, 0}, ++ /* config YHIST(0CC8H~0CD8H) */ ++ {0x00000CC8, 0x00000000, 0, 0}, ++ {0x00000CCC, 0x0437077F, 0, 0}, ++ {0x00000CD0, 0x00010002, 0, 0}, ++ {0x00000CD4, 0x00000000, 0, 0}, ++ /* config CBAR(0600H-0653H) */ ++ {0x00000600, 0x043E0782, 0, 0}, ++ {0x00000604, 0x00000000, 0, 0}, ++ {0x00000608, 0x0437077F, 0, 0}, ++ {0x0000060C, 0x00443150, 0, 0}, ++ {0x00000610, 0x00000000, 0, 0}, ++ {0x00000614, 0x08880888, 0, 0}, ++ {0x00000618, 0x02220222, 0, 0}, ++ {0x0000061C, 0x04440444, 0, 0}, ++ {0x00000620, 0x08880888, 0, 0}, ++ {0x00000624, 0x0AAA0AAA, 0, 0}, ++ {0x00000628, 0x0CCC0CCC, 0, 0}, ++ {0x0000062C, 0x0EEE0EEE, 0, 0}, ++ {0x00000630, 0x0FFF0FFF, 0, 0}, ++ {0x00000634, 0x08880888, 0, 0}, ++ {0x00000638, 0x02220222, 0, 0}, ++ {0x0000063C, 0x04440444, 0, 0}, ++ {0x00000640, 0x08880888, 0, 0}, ++ {0x00000644, 0x0AAA0AAA, 0, 0}, ++ {0x00000648, 0x0CCC0CCC, 0, 0}, ++ {0x0000064C, 0x0EEE0EEE, 0, 0}, ++ {0x00000650, 0x0FFF0FFF, 0, 0}, ++ /* config sensor(0014H) */ ++ {0x00000014, 0x0000000c, 0, 0}, ++ /* config CROP(001CH, 0020H) */ ++ {0x0000001C, 0x00000000, 0, 0}, ++ {0x00000020, 0x0437077F, 0, 0}, ++ /* config isp pileline X/Y size(A0CH) */ ++ {0x00000A0C, 0x04380780, 0, 0}, ++ /* config CSI dump (24H/28H) */ ++ {0x00000028, 0x00030B80, 0, 0}, ++ /* Video Output */ ++ /* config UO(0A80H~0A90H) */ ++ {0x00000A88, 0x00000780, 0, 0}, ++ /* NV12 */ ++ {0x00000A8C, 0x00000000, 0, 0}, ++ /* NV21 ++ *{0x00000A8C, 0x00000020, 0, 0}, ++ */ ++ {0x00000A90, 0x00000000, 0, 0}, ++ {0x00000A9C, 0x00000780, 0, 0}, ++ {0x00000AA0, 0x00000002, 0, 0}, ++ {0x00000AA4, 0x00000002, 0, 0}, ++ {0x00000AA8, 0x07800438, 0, 0}, ++ {0x00000AB4, 0x00000780, 0, 0}, ++ {0x00000AB8, 0x00000002, 0, 0}, ++ {0x00000ABC, 0x00000002, 0, 0}, ++ {0x00000AC0, 0x07800438, 0, 0}, ++ {0x00000AC4, 0x00000000, 0, 0}, ++ /* config TIL(0B20H~0B48H) */ ++ {0x00000B20, 0x04380780, 0, 0}, ++ {0x00000B24, 0x00000960, 0, 0}, ++ {0x00000B38, 0x00030003, 0, 0}, ++ {0x00000B3C, 0x00000960, 0, 0}, ++ {0x00000B44, 0x00000000, 0, 0}, ++ {0x00000B48, 0x00000000, 0, 0}, ++ /* Enable DEC/OBC/OECF/LCCF/AWB/SC/DUMP */ ++ {0x00000010, 0x000A00D6, 0x00000000, 0x00}, ++ /* Enable CFA/CAR/CCM/GMARGB/R2Y/SHRP/SAT/DNYUV/YCRV/YHIST/CTC/DBC */ ++ {0x00000A08, 0x107A01BE, 0x00000000, 0x00}, ++}; ++ ++const struct reg_table isp_reg_init_settings[] = { ++ {isp_reg_init_config_list, ++ ARRAY_SIZE(isp_reg_init_config_list)}, ++}; ++ ++static struct regval_t isp_reg_start_config_list[] = { ++#if defined(ENABLE_SS0_SS1) ++ /* ENABLE UO/SS0/SS1/Multi-Frame and Reset ISP */ ++ {0x00000A00, 0x00121802, 0x00000000, 0x0A}, ++ /* ENABLE UO/SS0/SS1/Multi-Frame and Leave ISP reset */ ++ {0x00000A00, 0x00121800, 0x00000000, 0x0A}, ++#else ++ /* ENABLE UO/Multi-Frame and Reset ISP */ ++ {0x00000A00, 0x00120002, 0x00000000, 0x0A}, ++ /* ENABLE UO/Multi-Frame and Leave ISP reset */ ++ {0x00000A00, 0x00120000, 0x00000000, 0x0A}, ++#endif ++ /* Config ISP shadow mode as next-vsync */ ++ {0x00000A50, 0x00000002, 0x00000000, 0x00}, ++#if defined(ENABLE_SS0_SS1) ++ /* ENABLE UO/SS0/SS1/Multi-Frame and Enable ISP */ ++ {0x00000A00, 0x00121801, 0x00000000, 0x0A}, ++#else ++ /* ENABLE UO/Multi-Frame and Enable ISP */ ++ {0x00000A00, 0x00120001, 0x00000000, 0x0A}, ++#endif ++ /* Config CSI shadow mode as immediate to fetch current setting */ ++ {0x00000008, 0x00010004, 0x00000000, 0x0A}, ++ /* Config CSI shadow mode as next-vsync */ ++ {0x00000008, 0x00020004, 0x00000000, 0x00}, ++ /* Enable CSI */ ++ {0x00000000, 0x00000001, 0x00000000, 0x0A}, ++}; ++ ++const struct reg_table isp_reg_start_settings[] = { ++ {isp_reg_start_config_list, ++ ARRAY_SIZE(isp_reg_start_config_list)}, ++}; ++ ++static struct regval_t isp_imx_219_reg_config_list[] = { ++ /* MIPI sensor */ ++ {0x00000014, 0x0000000D, 0, 0}, ++ /* config CFA(0018H, 0A1CH) */ ++ {0x00000A1C, 0x00000032, 0, 0}, ++ {0x00000A8C, 0x00000000, 0, 0}, ++ {0x00000A90, 0x00000000, 0, 0}, ++ /* config R2Y(0E40H~0E60H) */ ++ {0x00000E40, 0x0000004C, 0, 0}, ++ {0x00000E44, 0x00000097, 0, 0}, ++ {0x00000E48, 0x0000001D, 0, 0}, ++ {0x00000E4C, 0x000001D5, 0, 0}, ++ {0x00000E50, 0x000001AC, 0, 0}, ++ {0x00000E54, 0x00000080, 0, 0}, ++ {0x00000E58, 0x00000080, 0, 0}, ++ {0x00000E5C, 0x00000194, 0, 0}, ++ {0x00000E60, 0x000001EC, 0, 0}, ++ /* Config AWB(0280H~02DCH). Fixed WB gain for IMX-219 sensor. */ ++ {0x00000280, 0x00000000, 0, 0}, ++ {0x00000284, 0x00000000, 0, 0}, ++ {0x00000288, 0x00000000, 0, 0}, ++ {0x0000028C, 0x00000000, 0, 0}, ++ {0x00000290, 0x00000000, 0, 0}, ++ {0x00000294, 0x00000000, 0, 0}, ++ {0x00000298, 0x00000000, 0, 0}, ++ {0x0000029C, 0x00000000, 0, 0}, ++ {0x000002A0, 0x00000000, 0, 0}, ++ {0x000002A4, 0x00000000, 0, 0}, ++ {0x000002A8, 0x00000000, 0, 0}, ++ {0x000002AC, 0x00000000, 0, 0}, ++ {0x000002B0, 0x00000000, 0, 0}, ++ {0x000002B4, 0x00000000, 0, 0}, ++ {0x000002B8, 0x00000000, 0, 0}, ++ {0x000002BC, 0x00000000, 0, 0}, ++ {0x000002C0, 0x00F000F0, 0, 0}, ++ {0x000002C4, 0x00F000F0, 0, 0}, ++ {0x000002C8, 0x00800080, 0, 0}, ++ {0x000002CC, 0x00800080, 0, 0}, ++ {0x000002D0, 0x00800080, 0, 0}, ++ {0x000002D4, 0x00800080, 0, 0}, ++ {0x000002D8, 0x00B000B0, 0, 0}, ++ {0x000002DC, 0x00B000B0, 0, 0}, ++ /* config GMARGB(0E00H~0E38H) ++ * Gamma RGB 1.9 for IMX-219 sensor ++ */ ++ {0x00000E00, 0x24000000, 0, 0}, ++ {0x00000E04, 0x159500A5, 0, 0}, ++ {0x00000E08, 0x0F9900EE, 0, 0}, ++ {0x00000E0C, 0x0CE40127, 0, 0}, ++ {0x00000E10, 0x0B410157, 0, 0}, ++ {0x00000E14, 0x0A210181, 0, 0}, ++ {0x00000E18, 0x094B01A8, 0, 0}, ++ {0x00000E1C, 0x08A401CC, 0, 0}, ++ {0x00000E20, 0x081D01EE, 0, 0}, ++ {0x00000E24, 0x06B20263, 0, 0}, ++ {0x00000E28, 0x05D802C7, 0, 0}, ++ {0x00000E2C, 0x05420320, 0, 0}, ++ {0x00000E30, 0x04D30370, 0, 0}, ++ {0x00000E34, 0x047C03BB, 0, 0}, ++ {0x00000E38, 0x043703FF, 0, 0}, ++ {0x00000010, 0x00000080, 0, 0}, ++ /* Enable CFA/GMARGB/R2Y */ ++ {0x00000A08, 0x10000032, 0x0FFFFFFF, 0x00}, ++ {0x00000A00, 0x00120002, 0, 0}, ++ {0x00000A00, 0x00120000, 0, 0}, ++ {0x00000A50, 0x00000002, 0, 0}, ++ {0x00000008, 0x00010000, 0, 0}, ++ {0x00000008, 0x0002000A, 0, 0}, ++ {0x00000000, 0x00000001, 0, 0}, ++}; ++ ++const struct reg_table isp_imx_219_settings[] = { ++ {isp_imx_219_reg_config_list, ++ ARRAY_SIZE(isp_imx_219_reg_config_list)}, ++}; ++ ++static struct regval_t isp_format_reg_list[] = { ++ {0x0000001C, 0x00000000, 0x00000000, 0}, ++ {0x00000020, 0x0437077F, 0x00000000, 0}, ++ {0x00000A0C, 0x04380780, 0x00000000, 0}, ++ {0x00000A88, 0x00000780, 0x00000000, 0}, ++ {0x00000018, 0x000011BB, 0x00000000, 0}, ++ {0x00000A08, 0x10000000, 0xF0000000, 0}, ++ {0x00000028, 0x00030B80, 0x0003FFFF, 0}, ++ {0x00000AA8, 0x07800438, 0x00000000, 0}, ++ {0x00000A9C, 0x00000780, 0x00000000, 0}, ++ {0x00000AC0, 0x07800438, 0x00000000, 0}, ++ {0x00000AB4, 0x00000780, 0x00000000, 0}, ++ {0x00000B20, 0x04380780, 0x00000000, 0}, ++ {0x00000B24, 0x00000960, 0x00000000, 0}, ++ {0x00000B3C, 0x00000960, 0x00000000, 0}, ++ {0x00000014, 0x00000008, 0x00000000, 0}, ++}; ++ ++const struct reg_table isp_format_settings[] = { ++ {isp_format_reg_list, ++ ARRAY_SIZE(isp_format_reg_list)}, ++}; ++ ++#if defined(USE_NEW_CONFIG_SETTING) ++#else ++static struct reg_table *isp_settings = (struct reg_table *)isp_imx_219_settings; ++#endif ++ ++static void isp_load_regs(void __iomem *ispbase, const struct reg_table *table) ++{ ++ int j; ++ u32 delay_ms, reg_addr, mask, val; ++ ++ for (j = 0; j < table->regval_num; j++) { ++ delay_ms = table->regval[j].delay_ms; ++ reg_addr = table->regval[j].addr; ++ val = table->regval[j].val; ++ mask = table->regval[j].mask; ++ ++ if (reg_addr % 4 ++ || reg_addr > STF_ISP_REG_OFFSET_MAX ++ || delay_ms > STF_ISP_REG_DELAY_MAX) ++ continue; ++ ++ if (mask) ++ reg_set_bit(ispbase, reg_addr, mask, val); ++ else ++ reg_write(ispbase, reg_addr, val); ++ if (delay_ms) ++ usleep_range(1000 * delay_ms, 1000 * delay_ms + 100); ++ } ++} ++ ++static void isp_load_regs_exclude_csi_isp_enable( ++ void __iomem *ispbase, ++ const struct reg_table *table) ++{ ++ int j; ++ u32 delay_ms, reg_addr, mask, val; ++ ++ for (j = 0; j < table->regval_num; j++) { ++ delay_ms = table->regval[j].delay_ms; ++ reg_addr = table->regval[j].addr; ++ val = table->regval[j].val; ++ mask = table->regval[j].mask; ++ ++ if (reg_addr % 4 ++ || reg_addr > STF_ISP_REG_OFFSET_MAX ++ || delay_ms > STF_ISP_REG_DELAY_MAX ++ || ((reg_addr == ISP_REG_CSI_INPUT_EN_AND_STATUS) && (val & 0x01)) ++ || ((reg_addr == ISP_REG_ISP_CTRL_0) && (val & 0x01))) ++ continue; ++ ++ if (mask) ++ reg_set_bit(ispbase, reg_addr, mask, val); ++ else ++ reg_write(ispbase, reg_addr, val); ++ if (delay_ms) ++ usleep_range(1000 * delay_ms, 1000 * delay_ms + 100); ++ } ++} ++ ++static int stf_isp_clk_enable(struct stf_isp_dev *isp_dev) ++{ ++ struct stfcamss *stfcamss = isp_dev->stfcamss; ++ ++ clk_prepare_enable(stfcamss->sys_clk[STFCLK_WRAPPER_CLK_C].clk); ++ reset_control_deassert(stfcamss->sys_rst[STFRST_WRAPPER_C].rstc); ++ reset_control_deassert(stfcamss->sys_rst[STFRST_WRAPPER_P].rstc); ++ ++ return 0; ++} ++ ++static int stf_isp_clk_disable(struct stf_isp_dev *isp_dev) ++{ ++ struct stfcamss *stfcamss = isp_dev->stfcamss; ++ ++ reset_control_assert(stfcamss->sys_rst[STFRST_WRAPPER_C].rstc); ++ reset_control_assert(stfcamss->sys_rst[STFRST_WRAPPER_P].rstc); ++ clk_disable_unprepare(stfcamss->sys_clk[STFCLK_WRAPPER_CLK_C].clk); ++ ++ return 0; ++} ++ ++static void __iomem *stf_isp_get_ispbase(struct stf_vin_dev *vin) ++{ ++ void __iomem *base = vin->isp_base; ++ ++ return base; ++} ++ ++static int stf_isp_save_ctx_regs(struct stf_isp_dev *isp_dev) ++{ ++ int j; ++ u32 addr, val; ++ void __iomem *ispbase; ++ struct device *dev = isp_dev->stfcamss->dev; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ ++ ispbase = stf_isp_get_ispbase(vin); ++ ++ if (!isp_dev->context_regs) { ++ int regs_size = ++ sizeof(struct regval_t) * isp_reg_init_settings->regval_num; ++ isp_dev->context_regs = ++ devm_kzalloc(dev, sizeof(struct reg_table), GFP_KERNEL); ++ isp_dev->context_regs->regval = ++ devm_kzalloc(dev, regs_size, GFP_KERNEL); ++ isp_dev->context_regs->regval_num = isp_reg_init_settings->regval_num; ++ } ++ ++ if (!isp_dev->context_regs || !isp_dev->context_regs->regval) ++ return -ENOMEM; ++ ++ st_debug(ST_ISP, "Saving ISP context registers\n"); ++ for (j = 0; j < isp_reg_init_settings->regval_num; j++) { ++ addr = isp_reg_init_settings->regval[j].addr; ++ val = ioread32(ispbase + addr); ++ isp_dev->context_regs->regval[j].addr = addr; ++ isp_dev->context_regs->regval[j].val = val; ++ } ++ st_debug(ST_ISP, "ISP context registers have been saved\n"); ++ ++ return 0; ++}; ++ ++static int stf_isp_restore_ctx_regs(struct stf_isp_dev *isp_dev) ++{ ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase; ++ ++ ispbase = stf_isp_get_ispbase(vin); ++ ++ if (isp_dev->context_regs) { ++ isp_load_regs(ispbase, isp_dev->context_regs); ++ st_debug(ST_ISP, "Restored ISP register: isp_reg_init_settings.\n"); ++ } ++ ++ return 0; ++} ++ ++static int stf_isp_reset(struct stf_isp_dev *isp_dev) ++{ ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase; ++ ++ ispbase = stf_isp_get_ispbase(vin); ++ ++ reg_set_bit(ispbase, ISP_REG_ISP_CTRL_0, BIT(1), BIT(1)); ++ reg_set_bit(ispbase, ISP_REG_ISP_CTRL_0, BIT(1), 0); ++ ++ return 0; ++} ++ ++static int stf_isp_config_set(struct stf_isp_dev *isp_dev) ++{ ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase; ++ ++ ispbase = stf_isp_get_ispbase(vin); ++ ++ st_debug(ST_ISP, "%s\n", __func__); ++ ++#if defined(USE_NEW_CONFIG_SETTING) ++ mutex_lock(&isp_dev->setfile_lock); ++ ++ if (isp_dev->context_regs) { ++ stf_isp_restore_ctx_regs(isp_dev); ++ st_debug(ST_ISP, "%s context regs restore done\n", __func__); ++ } else { ++ isp_load_regs(ispbase, isp_reg_init_settings); ++ st_debug(ST_ISP, "%s isp_reg_init_settings done\n", __func__); ++ } ++ if (isp_dev->setfile.state) { ++ st_info(ST_ISP, "%s, Program extra ISP setting!\n", __func__); ++ isp_load_regs_exclude_csi_isp_enable(ispbase, ++ &isp_dev->setfile.settings); ++ } ++ ++ mutex_unlock(&isp_dev->setfile_lock); ++#else ++ mutex_lock(&isp_dev->setfile_lock); ++ if (isp_dev->setfile.state) ++ isp_load_regs(ispbase, &isp_dev->setfile.settings); ++ else ++ isp_load_regs(ispbase, isp_settings); ++ mutex_unlock(&isp_dev->setfile_lock); ++ ++ st_debug(ST_ISP, "config 0x%x = 0x%x\n", ++ isp_format_reg_list[0].addr, ++ isp_format_reg_list[0].val); ++ st_debug(ST_ISP, "config 0x%x = 0x%x\n", ++ isp_format_reg_list[1].addr, ++ isp_format_reg_list[1].val); ++ st_debug(ST_ISP, "config 0x%x = 0x%x\n", ++ isp_format_reg_list[2].addr, ++ isp_format_reg_list[2].val); ++ st_debug(ST_ISP, "config 0x%x = 0x%x\n", ++ isp_format_reg_list[3].addr, ++ isp_format_reg_list[3].val); ++#endif ++ ++ return 0; ++} ++ ++static int stf_isp_set_format(struct stf_isp_dev *isp_dev, ++ struct isp_stream_format *crop_array, u32 mcode, ++ int type) ++{ ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ struct stf_dvp_dev *dvp_dev = isp_dev->stfcamss->dvp_dev; ++ struct v4l2_rect *crop = &crop_array[ISP_COMPOSE].rect; ++ u32 bpp = crop_array[ISP_COMPOSE].bpp; ++ void __iomem *ispbase; ++ u32 val, val1; ++ ++ ispbase = stf_isp_get_ispbase(vin); ++ ++ st_debug(ST_ISP, "interface type is %d(%s)\n", ++ type, type == CSI_SENSOR ? "CSI" : "DVP"); ++ ++ if (type == DVP_SENSOR) { ++ unsigned int flags = dvp_dev->dvp->flags; ++ ++ st_debug(ST_ISP, "dvp flags = 0x%x, hsync active is %s, vsync active is %s\n", ++ flags, flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH ? "high" : "low", ++ flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH ? "high" : "low"); ++ } ++ ++ val = crop->left + (crop->top << 16); ++ isp_format_reg_list[0].addr = ISP_REG_PIC_CAPTURE_START_CFG; ++ isp_format_reg_list[0].val = val; ++ ++ val = (crop->width + crop->left - 1) ++ + ((crop->height + crop->top - 1) << 16); ++ isp_format_reg_list[1].addr = ISP_REG_PIC_CAPTURE_END_CFG; ++ isp_format_reg_list[1].val = val; ++ ++ val = crop->width + (crop->height << 16); ++ isp_format_reg_list[2].addr = ISP_REG_PIPELINE_XY_SIZE; ++ isp_format_reg_list[2].val = val; ++ ++ isp_format_reg_list[3].addr = ISP_REG_STRIDE; ++ isp_format_reg_list[3].val = ALIGN(crop->width * bpp / 8, STFCAMSS_FRAME_WIDTH_ALIGN_8); ++ ++ switch (mcode) { ++ case MEDIA_BUS_FMT_SRGGB10_1X10: ++ case MEDIA_BUS_FMT_SRGGB8_1X8: ++ // 3 2 3 2 1 0 1 0 B Gb B Gb Gr R Gr R ++ val = 0x0000EE44; ++ val1 = 0x00000000; ++ break; ++ case MEDIA_BUS_FMT_SGRBG10_1X10: ++ case MEDIA_BUS_FMT_SGRBG8_1X8: ++ // 2 3 2 3 0 1 0 1, Gb B Gb B R Gr R Gr ++ val = 0x0000BB11; ++ val1 = 0x20000000; ++ break; ++ case MEDIA_BUS_FMT_SGBRG10_1X10: ++ case MEDIA_BUS_FMT_SGBRG8_1X8: ++ // 1 0 1 0 3 2 3 2, Gr R Gr R B Gb B Gb ++ val = 0x000044EE; ++ val1 = 0x30000000; ++ break; ++ case MEDIA_BUS_FMT_SBGGR10_1X10: ++ case MEDIA_BUS_FMT_SBGGR8_1X8: ++ // 0 1 0 1 2 3 2 3 R Gr R Gr Gb B Gb B ++ val = 0x000011BB; ++ val1 = 0x10000000; ++ break; ++ default: ++ st_err(ST_ISP, "UNKNOW format\n"); ++ val = 0x000011BB; ++ val1 = 0x10000000; ++ break; ++ } ++ ++ isp_format_reg_list[4].addr = ISP_REG_RAW_FORMAT_CFG; ++ isp_format_reg_list[4].val = val; ++ ++ isp_format_reg_list[5].addr = ISP_REG_ISP_CTRL_1; ++ isp_format_reg_list[5].val = val1; ++ isp_format_reg_list[5].mask = 0xF0000000; ++ ++ st_info(ST_ISP, "src left: %d, top: %d, width = %d, height = %d, bpp = %d\n", ++ crop->left, crop->top, crop->width, crop->height, bpp); ++ ++ crop = &crop_array[ISP_CROP].rect; ++ bpp = crop_array[ISP_CROP].bpp; ++ val = ALIGN(crop->width * bpp / 8, STFCAMSS_FRAME_WIDTH_ALIGN_128); ++ isp_format_reg_list[6].addr = ISP_REG_DUMP_CFG_1; ++ isp_format_reg_list[6].val = val | 3 << 16; ++ isp_format_reg_list[6].mask = 0x0003FFFF; ++ ++ st_info(ST_ISP, "raw left: %d, top: %d, width = %d, height = %d, bpp = %d\n", ++ crop->left, crop->top, crop->width, crop->height, bpp); ++ ++ crop = &crop_array[ISP_SCALE_SS0].rect; ++ bpp = crop_array[ISP_SCALE_SS0].bpp; ++ isp_format_reg_list[7].addr = ISP_REG_SS0IW; ++ isp_format_reg_list[7].val = (crop->width << 16) + crop->height; ++ isp_format_reg_list[8].addr = ISP_REG_SS0S; ++ isp_format_reg_list[8].val = ALIGN(crop->width * bpp / 8, STFCAMSS_FRAME_WIDTH_ALIGN_8); ++ ++ st_info(ST_ISP, "ss0 left: %d, top: %d, width = %d, height = %d, bpp = %d\n", ++ crop->left, crop->top, crop->width, crop->height, bpp); ++ ++ crop = &crop_array[ISP_SCALE_SS1].rect; ++ bpp = crop_array[ISP_SCALE_SS1].bpp; ++ isp_format_reg_list[9].addr = ISP_REG_SS1IW; ++ isp_format_reg_list[9].val = (crop->width << 16) + crop->height; ++ isp_format_reg_list[10].addr = ISP_REG_SS1S; ++ isp_format_reg_list[10].val = ALIGN(crop->width * bpp / 8, STFCAMSS_FRAME_WIDTH_ALIGN_8); ++ ++ crop = &crop_array[ISP_ITIWS].rect; ++ bpp = crop_array[ISP_ITIWS].bpp; ++ isp_format_reg_list[11].addr = ISP_REG_ITIIWSR; ++ isp_format_reg_list[11].val = (crop->height << 16) + crop->width; ++ isp_format_reg_list[12].addr = ISP_REG_ITIDWLSR; ++ isp_format_reg_list[12].val = ALIGN(crop->width * bpp / 8, STFCAMSS_FRAME_WIDTH_ALIGN_8); ++ isp_format_reg_list[13].addr = ISP_REG_ITIDRLSR; ++ isp_format_reg_list[13].val = ALIGN(crop->width * bpp / 8, STFCAMSS_FRAME_WIDTH_ALIGN_8); ++ ++ st_info(ST_ISP, "iti left: %d, top: %d, width = %d, height = %d, bpp = %d\n", ++ crop->left, crop->top, crop->width, crop->height, bpp); ++ ++ isp_format_reg_list[14].addr = ISP_REG_SENSOR; ++ isp_format_reg_list[14].val = 0x00000000; ++ if (type == DVP_SENSOR) { ++ unsigned int flags = dvp_dev->dvp->flags; ++ ++ if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) ++ isp_format_reg_list[14].val |= 0x08; ++ if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) ++ isp_format_reg_list[14].val |= 0x04; ++ } else { ++ isp_format_reg_list[14].val |= 0x01; ++ } ++ ++ isp_load_regs(ispbase, isp_format_settings); ++ return 0; ++} ++ ++static int stf_isp_stream_set(struct stf_isp_dev *isp_dev, int on) ++{ ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ ++ void __iomem *ispbase; ++ ++ ispbase = stf_isp_get_ispbase(vin); ++ ++ if (on) { ++#if defined(USE_NEW_CONFIG_SETTING) ++ isp_load_regs(ispbase, isp_reg_start_settings); ++#else ++ reg_set_bit(ispbase, ISP_REG_CSIINTS_ADDR, 0x3FFFF, 0x3000a); ++ reg_set_bit(ispbase, ISP_REG_IESHD_ADDR, BIT(1) | BIT(0), 0x3); ++ reg_set_bit(ispbase, ISP_REG_ISP_CTRL_0, BIT(0), 1); ++#endif //#if defined(USE_NEW_CONFIG_SETTING) ++ } else { ++ /* NOTE: Clear bit 0 of ISP_REG_ISP_CTRL_0 here will get crash. */ ++ stf_isp_save_ctx_regs(isp_dev); ++ } ++ ++ return 0; ++} ++ ++static union reg_buf reg_buf; ++static int stf_isp_reg_read(struct stf_isp_dev *isp_dev, void *arg) ++{ ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase; ++ struct isp_reg_param *reg_param = arg; ++ u32 size; ++ unsigned long r; ++ ++ if (reg_param->reg_buf == NULL) { ++ st_err(ST_ISP, "Failed to access register. The pointer is NULL!!!\n"); ++ return -EINVAL; ++ } ++ ++ ispbase = stf_isp_get_ispbase(vin); ++ ++ size = 0; ++ switch (reg_param->reg_info.method) { ++ case STF_ISP_REG_METHOD_ONE_REG: ++ break; ++ ++ case STF_ISP_REG_METHOD_SERIES: ++ if (reg_param->reg_info.length > STF_ISP_REG_BUF_SIZE) { ++ st_err(ST_ISP, "Failed to access register. \ ++ The (length=0x%08X > 0x%08X) is out of size!!!\n", ++ reg_param->reg_info.length, STF_ISP_REG_BUF_SIZE); ++ return -EINVAL; ++ } ++ break; ++ ++ case STF_ISP_REG_METHOD_MODULE: ++ /* This mode is not supported in the V4L2 version. */ ++ st_err(ST_ISP, "Reg Read - Failed to access register. The method = \ ++ STF_ISP_REG_METHOD_MODULE is not supported!!!\n"); ++ return -ENOTTY; ++ ++ case STF_ISP_REG_METHOD_TABLE: ++ if (reg_param->reg_info.length > STF_ISP_REG_TBL_BUF_SIZE) { ++ st_err(ST_ISP, "Failed to access register. \ ++ The (length=0x%08X > 0x%08X) is out of size!!!\n", ++ reg_param->reg_info.length, STF_ISP_REG_TBL_BUF_SIZE); ++ return -EINVAL; ++ } ++ size = sizeof(u32) * reg_param->reg_info.length * 2; ++ break; ++ ++ case STF_ISP_REG_METHOD_TABLE_2: ++ if (reg_param->reg_info.length > STF_ISP_REG_TBL_2_BUF_SIZE) { ++ st_err(ST_ISP, "Failed to access register. \ ++ The (length=0x%08X > 0x%08X) is out of size!!!\n", ++ reg_param->reg_info.length, STF_ISP_REG_TBL_2_BUF_SIZE); ++ return -EINVAL; ++ } ++ size = sizeof(u32) * reg_param->reg_info.length * 3; ++ break; ++ ++ case STF_ISP_REG_METHOD_TABLE_3: ++ if (reg_param->reg_info.length > STF_ISP_REG_TBL_3_BUF_SIZE) { ++ st_err(ST_ISP, "Failed to access register. \ ++ The (length=0x%08X > 0x%08X) is out of size!!!\n", ++ reg_param->reg_info.length, STF_ISP_REG_TBL_3_BUF_SIZE); ++ return -EINVAL; ++ } ++ size = sizeof(u32) * reg_param->reg_info.length * 4; ++ break; ++ ++ case STF_ISP_REG_METHOD_SMPL_PACK: ++ st_err(ST_ISP, "Reg Read - Failed to access register. The method = \ ++ STF_ISP_REG_METHOD_SMPL_PACK is not supported!!!\n"); ++ return -ENOTTY; ++ ++ case STF_ISP_REG_METHOD_SOFT_RDMA: ++ // This mode is not supported in the V4L2 version. ++ st_err(ST_ISP, "Reg Read - Failed to access register. The method = \ ++ STF_ISP_REG_METHOD_SOFT_RDMA is not supported!!!\n"); ++ return -ENOTTY; ++ ++ default: ++ st_err(ST_ISP, "Failed to access register. The method=%d \ ++ is not supported!!!\n", reg_param->reg_info.method); ++ return -ENOTTY; ++ } ++ ++ memset(®_buf, 0, sizeof(union reg_buf)); ++ if (size) { ++ r = copy_from_user((u8 *)reg_buf.buffer, ++ (u8 *)reg_param->reg_buf->buffer, size); ++ if (r) { ++ st_err(ST_ISP, "Failed to call copy_from_user for the \ ++ reg_param->reg_buf value\n"); ++ return -EIO; ++ } ++ } ++ ++ size = 0; ++ switch (reg_param->reg_info.method) { ++ case STF_ISP_REG_METHOD_ONE_REG: ++ reg_buf.buffer[0] = reg_read(ispbase, reg_param->reg_info.offset); ++ size = sizeof(u32); ++ break; ++ ++ case STF_ISP_REG_METHOD_SERIES: ++ for (r = 0; r < reg_param->reg_info.length; r++) { ++ reg_buf.buffer[r] = reg_read(ispbase, ++ reg_param->reg_info.offset + (r * 4)); ++ } ++ size = sizeof(u32) * reg_param->reg_info.length; ++ break; ++ ++ case STF_ISP_REG_METHOD_MODULE: ++ break; ++ ++ case STF_ISP_REG_METHOD_TABLE: ++ for (r = 0; r < reg_param->reg_info.length; r++) { ++ reg_buf.reg_tbl[r].value = reg_read(ispbase, ++ reg_buf.reg_tbl[r].offset); ++ } ++ size = sizeof(u32) * reg_param->reg_info.length * 2; ++ break; ++ ++ case STF_ISP_REG_METHOD_TABLE_2: ++ for (r = 0; r < reg_param->reg_info.length; r++) { ++ if (reg_buf.reg_tbl2[r].mask) { ++ reg_buf.reg_tbl2[r].value = (reg_read(ispbase, ++ reg_buf.reg_tbl2[r].offset) ++ & reg_buf.reg_tbl2[r].mask); ++ } else { ++ reg_buf.reg_tbl2[r].value = reg_read(ispbase, ++ reg_buf.reg_tbl2[r].offset); ++ } ++ } ++ size = sizeof(u32) * reg_param->reg_info.length * 3; ++ break; ++ ++ case STF_ISP_REG_METHOD_TABLE_3: ++ for (r = 0; r < reg_param->reg_info.length; r++) { ++ if (reg_buf.reg_tbl3[r].mask) { ++ reg_buf.reg_tbl3[r].value = (reg_read(ispbase, ++ reg_buf.reg_tbl3[r].offset) ++ & reg_buf.reg_tbl3[r].mask); ++ } else { ++ reg_buf.reg_tbl3[r].value = reg_read(ispbase, ++ reg_buf.reg_tbl3[r].offset); ++ } ++ if (reg_buf.reg_tbl3[r].delay_ms) { ++ usleep_range(1000 * reg_buf.reg_tbl3[r].delay_ms, ++ 1000 * reg_buf.reg_tbl3[r].delay_ms + 100); ++ } ++ } ++ size = sizeof(u32) * reg_param->reg_info.length * 4; ++ break; ++ ++ case STF_ISP_REG_METHOD_SMPL_PACK: ++ break; ++ ++ case STF_ISP_REG_METHOD_SOFT_RDMA: ++ break; ++ ++ default: ++ break; ++ } ++ ++ r = copy_to_user((u8 *)reg_param->reg_buf->buffer, (u8 *)reg_buf.buffer, ++ size); ++ if (r) { ++ st_err(ST_ISP, "Failed to call copy_to_user for the \ ++ reg_param->buffer value\n"); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++static int stf_isp_soft_rdma(struct stf_isp_dev *isp_dev, u32 rdma_addr) ++{ ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase; ++ struct isp_rdma_info *rdma_info = NULL; ++ s32 len; ++ u32 offset; ++ int ret = 0; ++ ++ ispbase = stf_isp_get_ispbase(vin); ++ ++ rdma_info = phys_to_virt(rdma_addr); ++ while (1) { ++ if (rdma_info->tag == RDMA_WR_ONE) { ++ reg_write(ispbase, rdma_info->offset, rdma_info->param); ++ rdma_info++; ++ } else if (rdma_info->tag == RDMA_WR_SRL) { ++ offset = rdma_info->offset; ++ len = rdma_info->param; ++ rdma_info++; ++ while (len > 0) { ++ reg_write(ispbase, offset, rdma_info->param); ++ offset += 4; ++ len--; ++ if (len > 0) { ++ reg_write(ispbase, offset, rdma_info->value); ++ len--; ++ } ++ offset += 4; ++ rdma_info++; ++ } ++ } else if (rdma_info->tag == RDMA_LINK) { ++ rdma_info = phys_to_virt(rdma_info->param); ++ } else if (rdma_info->tag == RDMA_SINT) { ++ /* Software not support this command. */ ++ rdma_info++; ++ } else if (rdma_info->tag == RDMA_END) { ++ break; ++ } else ++ rdma_info++; ++ } ++ ++ return ret; ++} ++ ++static int stf_isp_reg_write(struct stf_isp_dev *isp_dev, void *arg) ++{ ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase; ++ struct isp_reg_param *reg_param = arg; ++ struct isp_rdma_info *rdma_info = NULL; ++ s32 len; ++ u32 offset; ++ u32 size; ++ unsigned long r; ++ int ret = 0; ++ ++ if ((reg_param->reg_buf == NULL) ++ && (reg_param->reg_info.method != STF_ISP_REG_METHOD_SOFT_RDMA)) { ++ st_err(ST_ISP, "Failed to access register. \ ++ The register buffer pointer is NULL!!!\n"); ++ return -EINVAL; ++ } ++ ++ ispbase = stf_isp_get_ispbase(vin); ++ ++ size = 0; ++ switch (reg_param->reg_info.method) { ++ case STF_ISP_REG_METHOD_ONE_REG: ++ size = sizeof(u32); ++ break; ++ ++ case STF_ISP_REG_METHOD_SERIES: ++ if (reg_param->reg_info.length > STF_ISP_REG_BUF_SIZE) { ++ st_err(ST_ISP, "Failed to access register. \ ++ The (length=0x%08X > 0x%08X) is out of size!!!\n", ++ reg_param->reg_info.length, STF_ISP_REG_BUF_SIZE); ++ return -EINVAL; ++ } ++ size = sizeof(u32) * reg_param->reg_info.length; ++ break; ++ ++ case STF_ISP_REG_METHOD_MODULE: ++ // This mode is not supported in the V4L2 version. ++ st_err(ST_ISP, "Reg Write - Failed to access register. \ ++ The method = STF_ISP_REG_METHOD_MODULE is not supported!!!\n"); ++ return -ENOTTY; ++ ++ case STF_ISP_REG_METHOD_TABLE: ++ if (reg_param->reg_info.length > STF_ISP_REG_TBL_BUF_SIZE) { ++ st_err(ST_ISP, "Failed to access register. \ ++ The (length=0x%08X > 0x%08X) is out of size!!!\n", ++ reg_param->reg_info.length, STF_ISP_REG_TBL_BUF_SIZE); ++ return -EINVAL; ++ } ++ size = sizeof(u32) * reg_param->reg_info.length * 2; ++ break; ++ ++ case STF_ISP_REG_METHOD_TABLE_2: ++ if (reg_param->reg_info.length > STF_ISP_REG_TBL_2_BUF_SIZE) { ++ st_err(ST_ISP, "Failed to access register. \ ++ The (length=0x%08X > 0x%08X) is out of size!!!\n", ++ reg_param->reg_info.length, STF_ISP_REG_TBL_2_BUF_SIZE); ++ return -EINVAL; ++ } ++ size = sizeof(u32) * reg_param->reg_info.length * 3; ++ break; ++ ++ case STF_ISP_REG_METHOD_TABLE_3: ++ if (reg_param->reg_info.length > STF_ISP_REG_TBL_3_BUF_SIZE) { ++ st_err(ST_ISP, "Failed to access register. \ ++ The (length=0x%08X > 0x%08X) is out of size!!!\n", ++ reg_param->reg_info.length, STF_ISP_REG_TBL_3_BUF_SIZE); ++ return -EINVAL; ++ } ++ size = sizeof(u32) * reg_param->reg_info.length * 4; ++ break; ++ ++ case STF_ISP_REG_METHOD_SMPL_PACK: ++ if (reg_param->reg_info.length > STF_ISP_REG_SMPL_PACK_BUF_SIZE) { ++ st_err(ST_ISP, "Failed to access register. \ ++ The (length=0x%08X > 0x%08X) is out of size!!!\n", ++ reg_param->reg_info.length, STF_ISP_REG_SMPL_PACK_BUF_SIZE); ++ return -EINVAL; ++ } ++ size = sizeof(u32) * reg_param->reg_info.length * 2; ++ break; ++ ++ case STF_ISP_REG_METHOD_SOFT_RDMA: ++ break; ++ ++ default: ++ st_err(ST_ISP, "Failed to access register. The method=%d \ ++ is not supported!!!\n", reg_param->reg_info.method); ++ return -ENOTTY; ++ } ++ ++ memset(®_buf, 0, sizeof(union reg_buf)); ++ if (size) { ++ r = copy_from_user((u8 *)reg_buf.buffer, ++ (u8 *)reg_param->reg_buf->buffer, size); ++ if (r) { ++ st_err(ST_ISP, "Failed to call copy_from_user for the \ ++ reg_param->reg_buf value\n"); ++ return -EIO; ++ } ++ } ++ ++ switch (reg_param->reg_info.method) { ++ case STF_ISP_REG_METHOD_ONE_REG: ++ reg_write(ispbase, reg_param->reg_info.offset, reg_buf.buffer[0]); ++ break; ++ ++ case STF_ISP_REG_METHOD_SERIES: ++ for (r = 0; r < reg_param->reg_info.length; r++) { ++ reg_write(ispbase, reg_param->reg_info.offset + (r * 4), ++ reg_buf.buffer[r]); ++ } ++ break; ++ ++ case STF_ISP_REG_METHOD_MODULE: ++ /* This mode is not supported in the V4L2 version. */ ++ break; ++ ++ case STF_ISP_REG_METHOD_TABLE: ++ for (r = 0; r < reg_param->reg_info.length; r++) { ++ reg_write(ispbase, reg_buf.reg_tbl[r].offset, ++ reg_buf.reg_tbl[r].value); ++ } ++ break; ++ ++ case STF_ISP_REG_METHOD_TABLE_2: ++ for (r = 0; r < reg_param->reg_info.length; r++) { ++ if (reg_buf.reg_tbl2[r].mask) { ++ reg_set_bit(ispbase, reg_buf.reg_tbl2[r].offset, ++ reg_buf.reg_tbl2[r].mask, reg_buf.reg_tbl2[r].value); ++ } else { ++ reg_write(ispbase, reg_buf.reg_tbl2[r].offset, ++ reg_buf.reg_tbl2[r].value); ++ } ++ } ++ break; ++ ++ case STF_ISP_REG_METHOD_TABLE_3: ++ for (r = 0; r < reg_param->reg_info.length; r++) { ++ if (reg_buf.reg_tbl3[r].mask) { ++ reg_set_bit(ispbase, reg_buf.reg_tbl3[r].offset, ++ reg_buf.reg_tbl3[r].mask, reg_buf.reg_tbl3[r].value); ++ } else { ++ reg_write(ispbase, reg_buf.reg_tbl3[r].offset, ++ reg_buf.reg_tbl3[r].value); ++ } ++ if (reg_buf.reg_tbl3[r].delay_ms) { ++ usleep_range(1000 * reg_buf.reg_tbl3[r].delay_ms, ++ 1000 * reg_buf.reg_tbl3[r].delay_ms + 100); ++ } ++ } ++ break; ++ ++ case STF_ISP_REG_METHOD_SMPL_PACK: ++ size = reg_param->reg_info.length; ++ rdma_info = ®_buf.rdma_cmd[0]; ++ while (size) { ++ if (rdma_info->tag == RDMA_WR_ONE) { ++ reg_write(ispbase, rdma_info->offset, rdma_info->param); ++ rdma_info++; ++ size--; ++ } else if (rdma_info->tag == RDMA_WR_SRL) { ++ offset = rdma_info->offset; ++ len = rdma_info->param; ++ rdma_info++; ++ size--; ++ while (size && (len > 0)) { ++ reg_write(ispbase, offset, rdma_info->param); ++ offset += 4; ++ len--; ++ if (len > 0) { ++ reg_write(ispbase, offset, rdma_info->value); ++ len--; ++ } ++ offset += 4; ++ rdma_info++; ++ size--; ++ } ++ } else if (rdma_info->tag == RDMA_END) { ++ break; ++ } else { ++ rdma_info++; ++ size--; ++ } ++ } ++ break; ++ ++ case STF_ISP_REG_METHOD_SOFT_RDMA: ++ /* ++ * Simulation the hardware RDMA behavior to debug and verify ++ * the RDMA chain. ++ */ ++ ret = stf_isp_soft_rdma(isp_dev, reg_param->reg_info.offset); ++ break; ++ ++ default: ++ break; ++ } ++ ++ return ret; ++} ++ ++static int stf_isp_shadow_trigger(struct stf_isp_dev *isp_dev) ++{ ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase; ++ ++ ispbase = stf_isp_get_ispbase(vin); ++ ++ // shadow update ++ reg_set_bit(ispbase, ISP_REG_CSIINTS_ADDR, (BIT(17) | BIT(16)), 0x30000); ++ reg_set_bit(ispbase, ISP_REG_IESHD_ADDR, (BIT(1) | BIT(0)), 0x3); ++ return 0; ++} ++ ++void dump_isp_reg(void *__iomem ispbase) ++{ ++ int j; ++ u32 addr, val; ++ ++ st_debug(ST_ISP, "DUMP ISP register:\n -- isp_reg_init_settings --\n"); ++ for (j = 0; j < isp_reg_init_settings->regval_num; j++) { ++ addr = isp_reg_init_settings->regval[j].addr; ++ val = ioread32(ispbase + addr); ++ st_debug(ST_ISP, "{0x%08x, 0x%08x}\n", addr, val); ++ } ++ ++ st_debug(ST_ISP, " --- isp_format_settings ---\n"); ++ for (j = 0; j < isp_format_settings->regval_num; j++) { ++ addr = isp_format_settings->regval[j].addr; ++ val = ioread32(ispbase + addr); ++ st_debug(ST_ISP, "{0x%08x, 0x%08x}\n", addr, val); ++ } ++ ++ val = ioread32(ispbase + ISP_REG_Y_PLANE_START_ADDR); ++ st_debug(ST_ISP, "-- ISP_REG_Y_PLANE_START_ADDR --\n {0x%08x, 0x%08x}\n", ++ ISP_REG_Y_PLANE_START_ADDR, val); ++ val = ioread32(ispbase + ISP_REG_UV_PLANE_START_ADDR); ++ st_debug(ST_ISP, "-- ISP_REG_UV_PLANE_START_ADDR --\n {0x%08x, 0x%08x}\n", ++ ISP_REG_UV_PLANE_START_ADDR, val); ++ val = ioread32(ispbase + ISP_REG_DUMP_CFG_0); ++ st_debug(ST_ISP, "-- ISP_REG_DUMP_CFG_0 --\n {0x%08x, 0x%08x}\n", ++ ISP_REG_DUMP_CFG_0, val); ++ val = ioread32(ispbase + ISP_REG_DUMP_CFG_1); ++ st_debug(ST_ISP, " --- ISP_REG_DUMP_CFG_1 ---\n {0x%08x, 0x%08x}\n", ++ ISP_REG_DUMP_CFG_1, val); ++ ++ st_debug(ST_ISP, " --- isp_reg_start_settings ---\n"); ++ for (j = 0; j < isp_reg_start_settings->regval_num; j++) { ++ addr = isp_reg_start_settings->regval[j].addr; ++ val = ioread32(ispbase + addr); ++ st_debug(ST_ISP, "{0x%08x, 0x%08x}\n", addr, val); ++ } ++} ++ ++struct isp_hw_ops isp_ops = { ++ .isp_clk_enable = stf_isp_clk_enable, ++ .isp_clk_disable = stf_isp_clk_disable, ++ .isp_reset = stf_isp_reset, ++ .isp_config_set = stf_isp_config_set, ++ .isp_set_format = stf_isp_set_format, ++ .isp_stream_set = stf_isp_stream_set, ++ .isp_reg_read = stf_isp_reg_read, ++ .isp_reg_write = stf_isp_reg_write, ++ .isp_shadow_trigger = stf_isp_shadow_trigger, ++}; +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_isp_ioctl.h +@@ -0,0 +1,133 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#ifndef STF_ISP_IOCTL_H ++#define STF_ISP_IOCTL_H ++ ++ ++#include <media/v4l2-ctrls.h> ++ ++ ++#define FILENAME_MAX_LEN 30 ++ ++#define ISP_IOC ('V') ++#define STF_ISP_REG_BUF_SIZE (768) ++#define STF_ISP_REG_TBL_BUF_SIZE (STF_ISP_REG_BUF_SIZE / 2) ++#define STF_ISP_REG_TBL_2_BUF_SIZE (STF_ISP_REG_BUF_SIZE / 3) ++#define STF_ISP_REG_TBL_3_BUF_SIZE (STF_ISP_REG_BUF_SIZE / 4) ++#define STF_ISP_REG_SMPL_PACK_BUF_SIZE (STF_ISP_REG_BUF_SIZE / 2) ++#define RDMA_WR_ONE (0xA0) ++#define RDMA_WR_SRL (0xA1) ++#define RDMA_LINK (0xA2) ++#define RDMA_SINT (0xA3) ++#define RDMA_END (0xAF) ++#define ENABLE_SS0_SS1 ++ ++enum _STF_ISP_IOCTL { ++ STF_ISP_IOCTL_LOAD_FW = BASE_VIDIOC_PRIVATE + 1, ++ STF_ISP_IOCTL_DMABUF_ALLOC, ++ STF_ISP_IOCTL_DMABUF_FREE, ++ STF_ISP_IOCTL_GET_HW_VER, ++ STF_ISP_IOCTL_REG, ++ STF_ISP_IOCTL_SHADOW_LOCK, ++ STF_ISP_IOCTL_SHADOW_UNLOCK, ++ STF_ISP_IOCTL_SHADOW_UNLOCK_N_TRIGGER, ++ STF_ISP_IOCTL_SET_USER_CONFIG_ISP, ++ STF_ISP_IOCTL_MAX ++}; ++ ++enum _STF_ISP_REG_METHOD { ++ STF_ISP_REG_METHOD_ONE_REG = 0, ++ STF_ISP_REG_METHOD_SERIES, ++ STF_ISP_REG_METHOD_MODULE, ++ STF_ISP_REG_METHOD_TABLE, ++ STF_ISP_REG_METHOD_TABLE_2, ++ STF_ISP_REG_METHOD_TABLE_3, ++ STF_ISP_REG_METHOD_SMPL_PACK, ++ STF_ISP_REG_METHOD_SOFT_RDMA, ++ STF_ISP_REG_METHOD_MAX ++}; ++ ++ ++struct stfisp_fw_info { ++ char __user filename[FILENAME_MAX_LEN]; ++}; ++ ++struct dmabuf_create { ++ __u32 fd; ++ __u32 size; ++ __u32 paddr; ++}; ++ ++struct isp_rdma_info { ++ u32 param; ++ union { ++ u32 value; ++ struct { ++ u32 offset : 24; ++ u32 tag : 8; ++ }; ++ }; ++}; ++ ++struct isp_reg_info { ++ /** @brief [in] access method of register */ ++ u8 method; ++ /** @brief [in] offset indicated which register will be read/write */ ++ u32 offset; ++ /** @brief [in] length for indicated how much register will be read/write */ ++ u32 length; ++}; ++ ++union reg_buf { ++ u32 buffer[STF_ISP_REG_BUF_SIZE]; ++ struct { ++ u32 offset; ++ u32 value; ++ } reg_tbl[STF_ISP_REG_TBL_BUF_SIZE]; ++ struct { ++ u32 offset; ++ u32 value; ++ u32 mask; ++ } reg_tbl2[STF_ISP_REG_TBL_2_BUF_SIZE]; ++ struct { ++ u32 offset; ++ u32 value; ++ u32 mask; ++ u32 delay_ms; ++ } reg_tbl3[STF_ISP_REG_TBL_3_BUF_SIZE]; ++ struct isp_rdma_info rdma_cmd[STF_ISP_REG_SMPL_PACK_BUF_SIZE]; ++}; ++ ++struct isp_reg_param { ++ /** @brief [in, out] register read/write information */ ++ struct isp_reg_info reg_info; ++ /** @brief [in, out] buffer */ ++ union reg_buf *reg_buf; ++}; ++ ++ ++#define VIDIOC_STFISP_LOAD_FW \ ++ _IOW(ISP_IOC, STF_ISP_IOCTL_LOAD_FW, struct stfisp_fw_info) ++#define VIDIOC_STF_DMABUF_ALLOC \ ++ _IOWR(ISP_IOC, STF_ISP_IOCTL_DMABUF_ALLOC, struct dmabuf_create) ++#define VIDIOC_STF_DMABUF_FREE \ ++ _IOWR(ISP_IOC, STF_ISP_IOCTL_DMABUF_FREE, struct dmabuf_create) ++#define VIDIOC_STFISP_GET_REG \ ++ _IOWR(ISP_IOC, STF_ISP_IOCTL_REG, struct isp_reg_param) ++#define VIDIOC_STFISP_SET_REG \ ++ _IOW(ISP_IOC, STF_ISP_IOCTL_REG, struct isp_reg_param) ++#define VIDIOC_STFISP_SHADOW_LOCK \ ++ _IO(ISP_IOC, STF_ISP_IOCTL_SHADOW_LOCK) ++#define VIDIOC_STFISP_SHADOW_UNLOCK \ ++ _IO(ISP_IOC, STF_ISP_IOCTL_SHADOW_UNLOCK) ++#define VIDIOC_STFISP_SHADOW_UNLOCK_N_TRIGGER \ ++ _IO(ISP_IOC, STF_ISP_IOCTL_SHADOW_UNLOCK_N_TRIGGER) ++#define VIDIOC_STFISP_SET_USER_CONFIG_ISP \ ++ _IO(ISP_IOC, STF_ISP_IOCTL_SET_USER_CONFIG_ISP) ++ ++ ++#endif /* STF_ISP_IOCTL_H */ +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_video.c +@@ -0,0 +1,1552 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include "stfcamss.h" ++#include "stf_video.h" ++#include <media/media-entity.h> ++#include <media/v4l2-event.h> ++#include <media/v4l2-mc.h> ++#include <media/videobuf2-dma-sg.h> ++#include <media/videobuf2-vmalloc.h> ++#include <media/videobuf2-dma-contig.h> ++ ++static const struct stfcamss_format_info formats_pix_st7110_wr[] = { ++ { MEDIA_BUS_FMT_AYUV8_1X32, V4L2_PIX_FMT_AYUV32, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 32 } }, ++ { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_YUYV, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 16 } }, ++ { MEDIA_BUS_FMT_RGB565_2X8_LE, V4L2_PIX_FMT_RGB565, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 16 } }, ++ { MEDIA_BUS_FMT_SRGGB8_1X8, V4L2_PIX_FMT_SRGGB8, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 8 } }, ++ { MEDIA_BUS_FMT_SGRBG8_1X8, V4L2_PIX_FMT_SGRBG8, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 8 } }, ++ { MEDIA_BUS_FMT_SGBRG8_1X8, V4L2_PIX_FMT_SGBRG8, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 8 } }, ++ { MEDIA_BUS_FMT_SBGGR8_1X8, V4L2_PIX_FMT_SBGGR8, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 8 } }, ++ { MEDIA_BUS_FMT_SRGGB10_1X10, V4L2_PIX_FMT_SRGGB10, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 10 } }, ++ { MEDIA_BUS_FMT_SGRBG10_1X10, V4L2_PIX_FMT_SGRBG10, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 10 } }, ++ { MEDIA_BUS_FMT_SGBRG10_1X10, V4L2_PIX_FMT_SGBRG10, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 10 } }, ++ { MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_PIX_FMT_SBGGR10, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 10 } }, ++}; ++ ++static const struct stfcamss_format_info formats_raw_st7110_isp[] = { ++ { MEDIA_BUS_FMT_SBGGR12_1X12, V4L2_PIX_FMT_SBGGR12, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 12 } }, ++ { MEDIA_BUS_FMT_SRGGB12_1X12, V4L2_PIX_FMT_SRGGB12, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 12 } }, ++ { MEDIA_BUS_FMT_SGRBG12_1X12, V4L2_PIX_FMT_SGRBG12, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 12 } }, ++ { MEDIA_BUS_FMT_SGBRG12_1X12, V4L2_PIX_FMT_SGBRG12, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 12 } }, ++}; ++ ++static const struct stfcamss_format_info formats_pix_st7110_isp[] = { ++ // { MEDIA_BUS_FMT_YUYV12_2X12, V4L2_PIX_FMT_NV12M, 2, ++ // { { 1, 1 }, { 1, 1 } }, { { 1, 1 }, { 1, 1 } }, { 8 , 4 } }, ++ { MEDIA_BUS_FMT_Y12_1X12, V4L2_PIX_FMT_NV12, 1, ++ { { 1, 1 } }, { { 2, 3 } }, { 8 } }, ++ { MEDIA_BUS_FMT_Y12_1X12, V4L2_PIX_FMT_NV21, 1, ++ { { 1, 1 } }, { { 2, 3 } }, { 8 } }, ++}; ++ ++static const struct stfcamss_format_info formats_st7110_isp_iti[] = { ++ // raw format ++ { MEDIA_BUS_FMT_SRGGB10_1X10, V4L2_PIX_FMT_SRGGB10, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 10 } }, ++ { MEDIA_BUS_FMT_SGRBG10_1X10, V4L2_PIX_FMT_SGRBG10, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 10 } }, ++ { MEDIA_BUS_FMT_SGBRG10_1X10, V4L2_PIX_FMT_SGBRG10, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 10 } }, ++ { MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_PIX_FMT_SBGGR10, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 10 } }, ++ { MEDIA_BUS_FMT_SRGGB12_1X12, V4L2_PIX_FMT_SRGGB12, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 12 } }, ++ { MEDIA_BUS_FMT_SGRBG12_1X12, V4L2_PIX_FMT_SGRBG12, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 12 } }, ++ { MEDIA_BUS_FMT_SGBRG12_1X12, V4L2_PIX_FMT_SGBRG12, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 12 } }, ++ { MEDIA_BUS_FMT_SBGGR12_1X12, V4L2_PIX_FMT_SBGGR12, 1, ++ { { 1, 1 } }, { { 1, 1 } }, { 12 } }, ++ ++ // YUV420 ++ { MEDIA_BUS_FMT_Y12_1X12, V4L2_PIX_FMT_NV12, 1, ++ { { 1, 1 } }, { { 2, 3 } }, { 8 } }, ++ { MEDIA_BUS_FMT_Y12_1X12, V4L2_PIX_FMT_NV21, 1, ++ { { 1, 1 } }, { { 2, 3 } }, { 8 } }, ++ ++ // YUV444 ++ { MEDIA_BUS_FMT_YUV8_1X24, V4L2_PIX_FMT_NV24, 1, ++ { { 1, 1 } }, { { 1, 3 } }, { 8 } }, ++ { MEDIA_BUS_FMT_VUY8_1X24, V4L2_PIX_FMT_NV42, 1, ++ { { 1, 1 } }, { { 1, 3 } }, { 8 } }, ++}; ++ ++static int video_find_format(u32 code, u32 pixelformat, ++ const struct stfcamss_format_info *formats, ++ unsigned int nformats) ++{ ++ int i; ++ ++ for (i = 0; i < nformats; i++) { ++ if (formats[i].code == code && ++ formats[i].pixelformat == pixelformat) ++ return i; ++ } ++ ++ for (i = 0; i < nformats; i++) ++ if (formats[i].code == code) ++ return i; ++ ++ for (i = 0; i < nformats; i++) ++ if (formats[i].pixelformat == pixelformat) ++ return i; ++ ++ return -EINVAL; ++} ++ ++static int __video_try_fmt(struct stfcamss_video *video, ++ struct v4l2_format *f, int is_mp) ++{ ++ struct v4l2_pix_format *pix; ++ struct v4l2_pix_format_mplane *pix_mp; ++ const struct stfcamss_format_info *fi; ++ u32 width, height; ++ u32 bpl; ++ int i, j; ++ ++ st_debug(ST_VIDEO, "%s, fmt.type = 0x%x\n", __func__, f->type); ++ pix = &f->fmt.pix; ++ pix_mp = &f->fmt.pix_mp; ++ ++ if (is_mp) { ++ for (i = 0; i < video->nformats; i++) ++ if (pix_mp->pixelformat ++ == video->formats[i].pixelformat) ++ break; ++ ++ if (i == video->nformats) ++ i = 0; /* default format */ ++ ++ fi = &video->formats[i]; ++ width = pix_mp->width; ++ height = pix_mp->height; ++ ++ memset(pix_mp, 0, sizeof(*pix_mp)); ++ ++ pix_mp->pixelformat = fi->pixelformat; ++ pix_mp->width = clamp_t(u32, width, STFCAMSS_FRAME_MIN_WIDTH, ++ STFCAMSS_FRAME_MAX_WIDTH); ++ pix_mp->height = clamp_t(u32, height, STFCAMSS_FRAME_MIN_HEIGHT, ++ STFCAMSS_FRAME_MAX_HEIGHT); ++ pix_mp->num_planes = fi->planes; ++ for (j = 0; j < pix_mp->num_planes; j++) { ++ bpl = pix_mp->width / fi->hsub[j].numerator * ++ fi->hsub[j].denominator * fi->bpp[j] / 8; ++ bpl = ALIGN(bpl, video->bpl_alignment); ++ pix_mp->plane_fmt[j].bytesperline = bpl; ++ pix_mp->plane_fmt[j].sizeimage = pix_mp->height / ++ fi->vsub[j].numerator ++ * fi->vsub[j].denominator * bpl; ++ } ++ ++ pix_mp->field = V4L2_FIELD_NONE; ++ pix_mp->colorspace = V4L2_COLORSPACE_SRGB; ++ pix_mp->flags = 0; ++ pix_mp->ycbcr_enc = ++ V4L2_MAP_YCBCR_ENC_DEFAULT(pix_mp->colorspace); ++ pix_mp->quantization = ++ V4L2_MAP_QUANTIZATION_DEFAULT(true, ++ pix_mp->colorspace, pix_mp->ycbcr_enc); ++ pix_mp->xfer_func = ++ V4L2_MAP_XFER_FUNC_DEFAULT(pix_mp->colorspace); ++ ++ st_info(ST_VIDEO, "w, h = %d, %d, bpp = %d\n", pix_mp->width, ++ pix_mp->height, fi->bpp[0]); ++ st_info(ST_VIDEO, "i = %d, p = %d, s = 0x%x\n", i, ++ pix_mp->num_planes, pix_mp->plane_fmt[0].sizeimage); ++ ++ } else { ++ for (i = 0; i < video->nformats; i++) ++ if (pix->pixelformat == video->formats[i].pixelformat) ++ break; ++ ++ if (i == video->nformats) ++ i = 0; /* default format */ ++ ++ fi = &video->formats[i]; ++ width = pix->width; ++ height = pix->height; ++ ++ memset(pix, 0, sizeof(*pix)); ++ ++ pix->pixelformat = fi->pixelformat; ++ pix->width = clamp_t(u32, width, STFCAMSS_FRAME_MIN_WIDTH, ++ STFCAMSS_FRAME_MAX_WIDTH); ++ pix->height = clamp_t(u32, height, STFCAMSS_FRAME_MIN_HEIGHT, ++ STFCAMSS_FRAME_MAX_HEIGHT); ++ bpl = pix->width / fi->hsub[0].numerator * ++ fi->hsub[0].denominator * fi->bpp[0] / 8; ++ bpl = ALIGN(bpl, video->bpl_alignment); ++ pix->bytesperline = bpl; ++ pix->sizeimage = pix->height / ++ fi->vsub[0].numerator ++ * fi->vsub[0].denominator * bpl; ++ ++ pix->field = V4L2_FIELD_NONE; ++ pix->colorspace = V4L2_COLORSPACE_SRGB; ++ pix->flags = 0; ++ pix->ycbcr_enc = ++ V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); ++ pix->quantization = ++ V4L2_MAP_QUANTIZATION_DEFAULT(true, ++ pix->colorspace, pix->ycbcr_enc); ++ pix->xfer_func = ++ V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); ++ ++ st_info(ST_VIDEO, "w, h = %d, %d, bpp = %d\n", pix->width, ++ pix->height, fi->bpp[0]); ++ st_info(ST_VIDEO, "i = %d, s = 0x%x\n", i, pix->sizeimage); ++ } ++ return 0; ++} ++ ++static int stf_video_init_format(struct stfcamss_video *video, int is_mp) ++{ ++ int ret; ++ struct v4l2_format format = { ++ .type = video->type, ++ .fmt.pix = { ++ .width = 1920, ++ .height = 1080, ++ .pixelformat = V4L2_PIX_FMT_RGB565, ++ }, ++ }; ++ ++ ret = __video_try_fmt(video, &format, is_mp); ++ ++ if (ret < 0) ++ return ret; ++ ++ video->active_fmt = format; ++ ++ return 0; ++} ++ ++static int video_queue_setup(struct vb2_queue *q, ++ unsigned int *num_buffers, unsigned int *num_planes, ++ unsigned int sizes[], struct device *alloc_devs[]) ++{ ++ struct stfcamss_video *video = vb2_get_drv_priv(q); ++ const struct v4l2_pix_format *format = ++ &video->active_fmt.fmt.pix; ++ const struct v4l2_pix_format_mplane *format_mp = ++ &video->active_fmt.fmt.pix_mp; ++ unsigned int i; ++ ++ st_debug(ST_VIDEO, "%s, planes = %d\n", __func__, *num_planes); ++ ++ if (video->is_mp) { ++ if (*num_planes) { ++ if (*num_planes != format_mp->num_planes) ++ return -EINVAL; ++ ++ for (i = 0; i < *num_planes; i++) ++ if (sizes[i] < ++ format_mp->plane_fmt[i].sizeimage) ++ return -EINVAL; ++ ++ return 0; ++ } ++ ++ *num_planes = format_mp->num_planes; ++ ++ for (i = 0; i < *num_planes; i++) ++ sizes[i] = format_mp->plane_fmt[i].sizeimage; ++ } else { ++ if (*num_planes) { ++ if (*num_planes != 1) ++ return -EINVAL; ++ ++ if (sizes[0] < format->sizeimage) ++ return -EINVAL; ++ } ++ ++ *num_planes = 1; ++ sizes[0] = format->sizeimage; ++ if (!sizes[0]) ++ st_err(ST_VIDEO, "%s: error size is zero!!!\n", __func__); ++ } ++ if ((stf_vin_map_isp_pad(video->id, STF_ISP_PAD_SRC) ++ == STF_ISP_PAD_SRC_SCD_Y) && ++ sizes[0] < ISP_SCD_Y_BUFFER_SIZE) { ++ sizes[0] = ISP_SCD_Y_BUFFER_SIZE; ++ } ++ ++ st_info(ST_VIDEO, "%s, planes = %d, size = %d\n", ++ __func__, *num_planes, sizes[0]); ++ return 0; ++} ++ ++static int video_buf_init(struct vb2_buffer *vb) ++{ ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ struct stfcamss_video *video = vb2_get_drv_priv(vb->vb2_queue); ++ struct stfcamss_buffer *buffer = ++ container_of(vbuf, struct stfcamss_buffer, vb); ++ const struct v4l2_pix_format *fmt = &video->active_fmt.fmt.pix; ++ const struct v4l2_pix_format_mplane *fmt_mp = ++ &video->active_fmt.fmt.pix_mp; ++ //struct sg_table *sgt; ++ dma_addr_t *paddr; ++ unsigned int i; ++ ++ buffer->sizeimage = 0; ++ ++ if (video->is_mp) { ++ for (i = 0; i < fmt_mp->num_planes; i++) { ++ paddr = vb2_plane_cookie(vb, i); ++ buffer->addr[i] = *paddr; ++ buffer->sizeimage += vb2_plane_size(vb, i); ++ } ++ ++ if (fmt_mp->num_planes == 1 ++ && (fmt_mp->pixelformat == V4L2_PIX_FMT_NV12 ++ || fmt_mp->pixelformat == V4L2_PIX_FMT_NV21 ++ || fmt_mp->pixelformat == V4L2_PIX_FMT_NV16 ++ || fmt_mp->pixelformat == V4L2_PIX_FMT_NV61)) ++ buffer->addr[1] = buffer->addr[0] + ++ fmt_mp->plane_fmt[0].bytesperline * ++ fmt_mp->height; ++ } else { ++ paddr = vb2_plane_cookie(vb, 0); ++ buffer->sizeimage = vb2_plane_size(vb, 0); ++ buffer->addr[0] = *paddr; ++ if (fmt->pixelformat == V4L2_PIX_FMT_NV12 ++ || fmt->pixelformat == V4L2_PIX_FMT_NV21 ++ || fmt->pixelformat == V4L2_PIX_FMT_NV16 ++ || fmt->pixelformat == V4L2_PIX_FMT_NV61) ++ buffer->addr[1] = buffer->addr[0] + ++ fmt->bytesperline * ++ fmt->height; ++ } ++ ++ if (stf_vin_map_isp_pad(video->id, STF_ISP_PAD_SRC) ++ == STF_ISP_PAD_SRC_SCD_Y) { ++ buffer->addr[1] = buffer->addr[0] + ISP_YHIST_BUFFER_SIZE; ++ buffer->vaddr_sc = vb2_plane_vaddr(vb, 0); ++ } ++ ++ return 0; ++} ++ ++static int video_buf_prepare(struct vb2_buffer *vb) ++{ ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ struct stfcamss_video *video = vb2_get_drv_priv(vb->vb2_queue); ++ const struct v4l2_pix_format *fmt = &video->active_fmt.fmt.pix; ++ const struct v4l2_pix_format_mplane *fmt_mp = ++ &video->active_fmt.fmt.pix_mp; ++ unsigned int i; ++ ++ if (video->is_mp) { ++ for (i = 0; i < fmt_mp->num_planes; i++) { ++ if (fmt_mp->plane_fmt[i].sizeimage ++ > vb2_plane_size(vb, i)) ++ return -EINVAL; ++ ++ vb2_set_plane_payload(vb, i, ++ fmt_mp->plane_fmt[i].sizeimage); ++ } ++ } else { ++ if (fmt->sizeimage > vb2_plane_size(vb, 0)) { ++ st_err(ST_VIDEO, "sizeimage = %d, plane size = %d\n", ++ fmt->sizeimage, (unsigned int)vb2_plane_size(vb, 0)); ++ return -EINVAL; ++ } ++ vb2_set_plane_payload(vb, 0, fmt->sizeimage); ++ } ++ ++ vbuf->field = V4L2_FIELD_NONE; ++ ++ return 0; ++} ++ ++static void video_buf_queue(struct vb2_buffer *vb) ++{ ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ struct stfcamss_video *video = vb2_get_drv_priv(vb->vb2_queue); ++ struct stfcamss_buffer *buffer = ++ container_of(vbuf, struct stfcamss_buffer, vb); ++ ++ video->ops->queue_buffer(video, buffer); ++} ++ ++static int video_mbus_to_pix_mp(const struct v4l2_mbus_framefmt *mbus, ++ struct v4l2_pix_format_mplane *pix, ++ const struct stfcamss_format_info *f, ++ unsigned int alignment) ++{ ++ unsigned int i; ++ u32 bytesperline; ++ ++ memset(pix, 0, sizeof(*pix)); ++ v4l2_fill_pix_format_mplane(pix, mbus); ++ pix->pixelformat = f->pixelformat; ++ pix->num_planes = f->planes; ++ for (i = 0; i < pix->num_planes; i++) { ++ bytesperline = pix->width / f->hsub[i].numerator * ++ f->hsub[i].denominator * f->bpp[i] / 8; ++ bytesperline = ALIGN(bytesperline, alignment); ++ pix->plane_fmt[i].bytesperline = bytesperline; ++ pix->plane_fmt[i].sizeimage = pix->height / ++ f->vsub[i].numerator * f->vsub[i].denominator * ++ bytesperline; ++ } ++ ++ return 0; ++} ++ ++static int video_mbus_to_pix(const struct v4l2_mbus_framefmt *mbus, ++ struct v4l2_pix_format *pix, ++ const struct stfcamss_format_info *f, ++ unsigned int alignment) ++{ ++ u32 bytesperline; ++ ++ memset(pix, 0, sizeof(*pix)); ++ v4l2_fill_pix_format(pix, mbus); ++ pix->pixelformat = f->pixelformat; ++ bytesperline = pix->width / f->hsub[0].numerator * ++ f->hsub[0].denominator * f->bpp[0] / 8; ++ bytesperline = ALIGN(bytesperline, alignment); ++ pix->bytesperline = bytesperline; ++ pix->sizeimage = pix->height / ++ f->vsub[0].numerator * f->vsub[0].denominator * ++ bytesperline; ++ return 0; ++} ++ ++static struct v4l2_subdev *video_remote_subdev( ++ struct stfcamss_video *video, u32 *pad) ++{ ++ struct media_pad *remote; ++ ++ remote = media_pad_remote_pad_first(&video->pad); ++ ++ if (!remote || !is_media_entity_v4l2_subdev(remote->entity)) ++ return NULL; ++ ++ if (pad) ++ *pad = remote->index; ++ ++ return media_entity_to_v4l2_subdev(remote->entity); ++} ++ ++static int video_get_subdev_format(struct stfcamss_video *video, ++ struct v4l2_format *format) ++{ ++ struct v4l2_pix_format *pix = &video->active_fmt.fmt.pix; ++ struct v4l2_pix_format_mplane *pix_mp = ++ &video->active_fmt.fmt.pix_mp; ++ struct v4l2_subdev_format fmt; ++ struct v4l2_subdev *subdev; ++ u32 pixelformat; ++ u32 pad; ++ int ret; ++ ++ subdev = video_remote_subdev(video, &pad); ++ if (subdev == NULL) ++ return -EPIPE; ++ ++ fmt.pad = pad; ++ fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; ++ ++ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); ++ if (ret) ++ return ret; ++ ++ if (video->is_mp) ++ pixelformat = pix_mp->pixelformat; ++ else ++ pixelformat = pix->pixelformat; ++ ret = video_find_format(fmt.format.code, pixelformat, ++ video->formats, video->nformats); ++ if (ret < 0) ++ return ret; ++ ++ format->type = video->type; ++ ++ if (video->is_mp) ++ return video_mbus_to_pix_mp(&fmt.format, &format->fmt.pix_mp, ++ &video->formats[ret], video->bpl_alignment); ++ else ++ return video_mbus_to_pix(&fmt.format, &format->fmt.pix, ++ &video->formats[ret], video->bpl_alignment); ++} ++ ++static int video_check_format(struct stfcamss_video *video) ++{ ++ struct v4l2_pix_format *pix = &video->active_fmt.fmt.pix; ++ struct v4l2_pix_format_mplane *pix_mp = ++ &video->active_fmt.fmt.pix_mp; ++ struct v4l2_format format; ++ struct v4l2_pix_format *sd_pix = &format.fmt.pix; ++ struct v4l2_pix_format_mplane *sd_pix_mp = &format.fmt.pix_mp; ++ int ret; ++ ++ if (video->is_mp) { ++ sd_pix_mp->pixelformat = pix_mp->pixelformat; ++ ret = video_get_subdev_format(video, &format); ++ if (ret < 0) ++ return ret; ++ ++ if (pix_mp->pixelformat != sd_pix_mp->pixelformat || ++ pix_mp->height > sd_pix_mp->height || ++ pix_mp->width > sd_pix_mp->width || ++ pix_mp->num_planes != sd_pix_mp->num_planes || ++ pix_mp->field != format.fmt.pix_mp.field) { ++ st_err(ST_VIDEO, ++ "%s, not match:\n" ++ "0x%x 0x%x\n0x%x 0x%x\n0x%x 0x%x\n", ++ __func__, ++ pix_mp->pixelformat, sd_pix_mp->pixelformat, ++ pix_mp->height, sd_pix_mp->height, ++ pix_mp->field, format.fmt.pix_mp.field); ++ return -EPIPE; ++ } ++ ++ } else { ++ sd_pix->pixelformat = pix->pixelformat; ++ ret = video_get_subdev_format(video, &format); ++ if (ret < 0) ++ return ret; ++ ++ if (pix->pixelformat != sd_pix->pixelformat || ++ pix->height > sd_pix->height || ++ pix->width > sd_pix->width || ++ pix->field != format.fmt.pix.field) { ++ st_err(ST_VIDEO, ++ "%s, not match:\n" ++ "0x%x 0x%x\n0x%x 0x%x\n0x%x 0x%x\n", ++ __func__, ++ pix->pixelformat, sd_pix->pixelformat, ++ pix->height, sd_pix->height, ++ pix->field, format.fmt.pix.field); ++ return -EPIPE; ++ } ++ } ++ return 0; ++} ++ ++static int video_start_streaming(struct vb2_queue *q, unsigned int count) ++{ ++ struct stfcamss_video *video = vb2_get_drv_priv(q); ++ struct video_device *vdev = &video->vdev; ++ struct media_entity *entity; ++ struct media_pad *pad; ++ struct v4l2_subdev *subdev; ++ int ret; ++ ++ ret = video_device_pipeline_start(vdev, &video->stfcamss->pipe); ++ if (ret < 0) { ++ st_err(ST_VIDEO, ++ "Failed to video_device_pipeline_start: %d\n", ret); ++ return ret; ++ } ++ ++ ret = video_check_format(video); ++ if (ret < 0) ++ goto error; ++ entity = &vdev->entity; ++ while (1) { ++ pad = &entity->pads[0]; ++ if (!(pad->flags & MEDIA_PAD_FL_SINK)) ++ break; ++ ++ pad = media_pad_remote_pad_first(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, 1); ++ if (ret < 0 && ret != -ENOIOCTLCMD) ++ goto error; ++ } ++ return 0; ++ ++error: ++ video_device_pipeline_stop(vdev); ++ video->ops->flush_buffers(video, VB2_BUF_STATE_QUEUED); ++ return ret; ++} ++ ++static void video_stop_streaming(struct vb2_queue *q) ++{ ++ struct stfcamss_video *video = vb2_get_drv_priv(q); ++ struct video_device *vdev = &video->vdev; ++ struct media_entity *entity; ++ struct media_pad *pad; ++ struct v4l2_subdev *subdev; ++ ++ entity = &vdev->entity; ++ while (1) { ++ pad = &entity->pads[0]; ++ if (!(pad->flags & MEDIA_PAD_FL_SINK)) ++ break; ++ ++ pad = media_pad_remote_pad_first(pad); ++ if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) ++ break; ++ ++ entity = pad->entity; ++ subdev = media_entity_to_v4l2_subdev(entity); ++ ++ v4l2_subdev_call(subdev, video, s_stream, 0); ++ } ++ ++ video_device_pipeline_stop(vdev); ++ video->ops->flush_buffers(video, VB2_BUF_STATE_ERROR); ++} ++ ++static const struct vb2_ops stf_video_vb2_q_ops = { ++ .queue_setup = video_queue_setup, ++ .wait_prepare = vb2_ops_wait_prepare, ++ .wait_finish = vb2_ops_wait_finish, ++ .buf_init = video_buf_init, ++ .buf_prepare = video_buf_prepare, ++ .buf_queue = video_buf_queue, ++ .start_streaming = video_start_streaming, ++ .stop_streaming = video_stop_streaming, ++}; ++ ++/* ----------------------------------------------------- ++ * V4L2 ioctls ++ */ ++ ++static int getcrop_pad_id(int video_id) ++{ ++ return stf_vin_map_isp_pad(video_id, STF_ISP_PAD_SRC); ++} ++ ++static int video_querycap(struct file *file, void *fh, ++ struct v4l2_capability *cap) ++{ ++ struct stfcamss_video *video = video_drvdata(file); ++ ++ strscpy(cap->driver, "stf camss", sizeof(cap->driver)); ++ strscpy(cap->card, "Starfive Camera Subsystem", sizeof(cap->card)); ++ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", ++ dev_name(video->stfcamss->dev)); ++ return 0; ++} ++ ++static int video_get_unique_pixelformat_by_index(struct stfcamss_video *video, ++ int ndx) ++{ ++ int i, j, k; ++ ++ /* find index "i" of "k"th unique pixelformat in formats array */ ++ k = -1; ++ for (i = 0; i < video->nformats; i++) { ++ for (j = 0; j < i; j++) { ++ if (video->formats[i].pixelformat == ++ video->formats[j].pixelformat) ++ break; ++ } ++ ++ if (j == i) ++ k++; ++ ++ if (k == ndx) ++ return i; ++ } ++ ++ return -EINVAL; ++} ++ ++static int video_get_pixelformat_by_mbus_code(struct stfcamss_video *video, ++ u32 mcode) ++{ ++ int i; ++ ++ for (i = 0; i < video->nformats; i++) { ++ if (video->formats[i].code == mcode) ++ return i; ++ } ++ ++ return -EINVAL; ++} ++ ++static int video_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) ++{ ++ struct stfcamss_video *video = video_drvdata(file); ++ int i; ++ ++ st_debug(ST_VIDEO, "%s:\n0x%x 0x%x\n 0x%x, 0x%x\n0x%x\n", ++ __func__, ++ f->type, video->type, ++ f->index, video->nformats, ++ f->mbus_code); ++ ++ if (f->type != video->type) ++ return -EINVAL; ++ if (f->index >= video->nformats) ++ return -EINVAL; ++ ++ if (f->mbus_code) { ++ /* Each entry in formats[] table has unique mbus_code */ ++ if (f->index > 0) ++ return -EINVAL; ++ ++ i = video_get_pixelformat_by_mbus_code(video, f->mbus_code); ++ } else { ++ i = video_get_unique_pixelformat_by_index(video, f->index); ++ } ++ ++ if (i < 0) ++ return -EINVAL; ++ ++ f->pixelformat = video->formats[i].pixelformat; ++ ++ return 0; ++} ++ ++static int video_enum_framesizes(struct file *file, void *fh, ++ struct v4l2_frmsizeenum *fsize) ++{ ++ struct v4l2_subdev_frame_size_enum fse = {0}; ++ struct v4l2_subdev_mbus_code_enum code = {0}; ++ struct stfcamss_video *video = video_drvdata(file); ++ struct video_device *vdev = &video->vdev; ++ struct media_entity *entity = &vdev->entity; ++ struct media_entity *sensor; ++ struct v4l2_subdev *subdev; ++ struct media_pad *pad; ++ bool support_selection = false; ++ int i; ++ int ret; ++ ++ for (i = 0; i < video->nformats; i++) { ++ if (video->formats[i].pixelformat == fsize->pixel_format) ++ break; ++ } ++ ++ if (i == video->nformats) ++ return -EINVAL; ++ ++ entity = &vdev->entity; ++ while (1) { ++ pad = &entity->pads[0]; ++ if (!(pad->flags & MEDIA_PAD_FL_SINK)) ++ break; ++ ++ pad = media_pad_remote_pad_first(pad); ++ if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) ++ break; ++ ++ entity = pad->entity; ++ subdev = media_entity_to_v4l2_subdev(entity); ++ ++ if (subdev->ops->pad->set_selection) { ++ support_selection = true; ++ break; ++ } ++ } ++ ++ if (support_selection) { ++ if (fsize->index) ++ return -EINVAL; ++ fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; ++ fsize->stepwise.min_width = STFCAMSS_FRAME_MIN_WIDTH; ++ fsize->stepwise.max_width = STFCAMSS_FRAME_MAX_WIDTH; ++ fsize->stepwise.min_height = STFCAMSS_FRAME_MIN_HEIGHT; ++ fsize->stepwise.max_height = STFCAMSS_FRAME_MAX_HEIGHT; ++ fsize->stepwise.step_width = 1; ++ fsize->stepwise.step_height = 1; ++ } else { ++ entity = &vdev->entity; ++ sensor = stfcamss_find_sensor(entity); ++ if (!sensor) ++ return -ENOTTY; ++ ++ subdev = media_entity_to_v4l2_subdev(sensor); ++ code.index = 0; ++ code.which = V4L2_SUBDEV_FORMAT_ACTIVE; ++ ret = v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL, &code); ++ if (ret < 0) ++ return -EINVAL; ++ fse.index = fsize->index; ++ fse.code = code.code; ++ fse.which = V4L2_SUBDEV_FORMAT_ACTIVE; ++ ret = v4l2_subdev_call(subdev, pad, enum_frame_size, NULL, &fse); ++ if (ret < 0) ++ return -EINVAL; ++ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; ++ fsize->discrete.width = fse.min_width; ++ fsize->discrete.height = fse.min_height; ++ } ++ ++ return 0; ++} ++ ++static int video_enum_frameintervals(struct file *file, void *fh, ++ struct v4l2_frmivalenum *fival) ++{ ++ int ret = 0; ++ struct stfcamss_video *video = video_drvdata(file); ++ struct video_device *vdev = &video->vdev; ++ struct media_entity *entity = &vdev->entity; ++ struct media_entity *sensor; ++ struct v4l2_subdev *subdev; ++ struct v4l2_subdev_mbus_code_enum code = {0}; ++ struct v4l2_subdev_frame_interval_enum fie = {0}; ++ ++ sensor = stfcamss_find_sensor(entity); ++ if (!sensor) ++ return -ENOTTY; ++ fie.index = fival->index; ++ fie.width = fival->width; ++ fie.height = fival->height; ++ fie.which = V4L2_SUBDEV_FORMAT_ACTIVE; ++ subdev = media_entity_to_v4l2_subdev(sensor); ++ ++ code.index = 0; ++ code.which = V4L2_SUBDEV_FORMAT_ACTIVE; ++ ++ /* Don't care about the code, just find by pixelformat */ ++ ret = video_find_format(0, fival->pixel_format, ++ video->formats, video->nformats); ++ if (ret < 0) ++ return -EINVAL; ++ ++ ret = v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL, &code); ++ if (ret < 0) ++ return -EINVAL; ++ ++ fie.code = code.code; ++ ret = v4l2_subdev_call(subdev, pad, enum_frame_interval, NULL, &fie); ++ if (ret < 0) ++ return ret; ++ ++ fival->type = V4L2_FRMSIZE_TYPE_DISCRETE; ++ fival->discrete = fie.interval; ++ ++ return 0; ++} ++ ++static int video_g_fmt(struct file *file, void *fh, struct v4l2_format *f) ++{ ++ struct stfcamss_video *video = video_drvdata(file); ++ ++ st_debug(ST_VIDEO, "%s, fmt.type = 0x%x\n", __func__, f->type); ++ st_debug(ST_VIDEO, "%s, active_fmt.type = 0x%x,0x%x\n", ++ __func__, video->active_fmt.type, ++ video->active_fmt.fmt.pix.pixelformat); ++ *f = video->active_fmt; ++ return 0; ++} ++ ++static int video_g_fmt_mp(struct file *file, void *fh, struct v4l2_format *f) ++{ ++ struct stfcamss_video *video = video_drvdata(file); ++ ++ st_debug(ST_VIDEO, "%s, fmt.type = 0x%x\n", __func__, f->type); ++ st_debug(ST_VIDEO, "%s, active_fmt.type = 0x%x\n", ++ __func__, video->active_fmt.type); ++ *f = video->active_fmt; ++ return 0; ++} ++ ++static int video_entity_s_fmt(struct stfcamss_video *video, ++ struct media_entity *entity, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct v4l2_subdev *subdev; ++ struct media_pad *pad; ++ struct v4l2_mbus_framefmt *mf = &fmt->format; ++ struct v4l2_subdev_format fmt_src = { ++ .which = V4L2_SUBDEV_FORMAT_ACTIVE, ++ }; ++ u32 width, height, code; ++ int ret, index = 0; ++ ++ code = mf->code; ++ width = mf->width; ++ height = mf->height; ++ subdev = media_entity_to_v4l2_subdev(entity); ++ while (1) { ++ if (index >= entity->num_pads) ++ break; ++ pad = &entity->pads[index]; ++ pad = media_pad_remote_pad_first(pad); ++ if (pad && is_media_entity_v4l2_subdev(pad->entity)) { ++ fmt->pad = index; ++ ret = v4l2_subdev_call(subdev, pad, set_fmt, state, fmt); ++ if (mf->code != code || ++ mf->width != width || mf->height != height) { ++ st_warn(ST_VIDEO, ++ "\"%s\":%d pad fmt has been" ++ " changed to 0x%x %ux%u\n", ++ subdev->name, fmt->pad, mf->code, ++ mf->width, mf->height); ++ } ++ if (index) { ++ fmt_src.pad = index; ++ ret = v4l2_subdev_call(subdev, pad, get_fmt, state, &fmt_src); ++ if (ret) ++ return ret; ++ ++ fmt->format.code = fmt_src.format.code; ++ ret = video_entity_s_fmt(video, pad->entity, state, fmt); ++ } ++ } ++ ++ if (ret < 0 && ret != -ENOIOCTLCMD) ++ break; ++ index++; ++ } ++ return ret; ++} ++ ++static int video_pipeline_s_fmt(struct stfcamss_video *video, ++ struct v4l2_subdev_state *state, ++ struct v4l2_format *f) ++{ ++ struct video_device *vdev = &video->vdev; ++ struct media_entity *entity = &vdev->entity; ++ struct v4l2_subdev *subdev; ++ int ret, index; ++ struct v4l2_subdev_format fmt = { ++ .pad = 0, ++ .which = V4L2_SUBDEV_FORMAT_ACTIVE, ++ .reserved = {getcrop_pad_id(video->id)} ++ }; ++ struct v4l2_mbus_framefmt *mf = &fmt.format; ++ struct v4l2_pix_format *pix = &f->fmt.pix; ++ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; ++ struct media_entity *sensor; ++ u32 width, height; ++ struct media_pad *pad; ++ ++ /* pix to mbus format */ ++ if (video->is_mp) { ++ index = video_find_format(mf->code, ++ pix_mp->pixelformat, ++ video->formats, video->nformats); ++ if (index < 0) ++ return index; ++ v4l2_fill_mbus_format_mplane(mf, pix_mp); ++ mf->code = video->formats[index].code; ++ } else { ++ index = video_find_format(mf->code, ++ pix->pixelformat, ++ video->formats, video->nformats); ++ if (index < 0) ++ return index; ++ v4l2_fill_mbus_format(mf, pix, video->formats[index].code); ++ } ++ ++ width = mf->width; ++ height = mf->height; ++ ++ sensor = stfcamss_find_sensor(entity); ++ if (!sensor) { ++ st_err(ST_VIDEO, "Can't find sensor\n"); ++ return -ENOTTY; ++ } ++ ++ subdev = media_entity_to_v4l2_subdev(sensor); ++ ret = v4l2_subdev_call(subdev, pad, get_fmt, state, &fmt); ++ if (ret) ++ return ret; ++ ++ /* ++ * Starting from sensor subdevice, walk within ++ * pipeline and set format on each subdevice ++ */ ++ pad = media_pad_remote_pad_first(&sensor->pads[0]); ++ ret = video_entity_s_fmt(video, pad->entity, state, &fmt); ++ if (ret < 0 && ret != -ENOIOCTLCMD) ++ return ret; ++ ++ index = video_find_format(mf->code, ++ video->formats[index].pixelformat, ++ video->formats, video->nformats); ++ st_debug(ST_VIDEO, "%s, code=%x, index=%d\n", ++ __func__, mf->code, index); ++ ++ if (index < 0) ++ return index; ++ ++ if (video->is_mp) ++ video_mbus_to_pix_mp(mf, pix_mp, ++ &video->formats[index], video->bpl_alignment); ++ else ++ video_mbus_to_pix(mf, pix, ++ &video->formats[index], video->bpl_alignment); ++ ++ ret = __video_try_fmt(video, f, video->is_mp); ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++static int video_s_fmt(struct file *file, void *fh, struct v4l2_format *f) ++{ ++ struct stfcamss_video *video = video_drvdata(file); ++ int ret; ++ ++ st_debug(ST_VIDEO, "%s, fmt.type = 0x%x, v4l2fmt=%x\n", ++ __func__, f->type, f->fmt.pix.pixelformat); ++ ++ if (vb2_is_busy(&video->vb2_q)) ++ return -EBUSY; ++ ++ ret = __video_try_fmt(video, f, false); ++ if (ret < 0) ++ return ret; ++ ++ ret = video_pipeline_s_fmt(video, NULL, f); ++ ++ st_debug(ST_VIDEO, "%s, pixelformat=0x%x, ret=%d\n", ++ __func__, f->fmt.pix.pixelformat, ret); ++ if (ret < 0) ++ return ret; ++ ++ video->active_fmt = *f; ++ ++ return 0; ++} ++ ++static int video_s_fmt_mp(struct file *file, void *fh, struct v4l2_format *f) ++{ ++ struct stfcamss_video *video = video_drvdata(file); ++ int ret; ++ ++ st_debug(ST_VIDEO, "%s, fmt.type = 0x%x\n", __func__, f->type); ++ if (vb2_is_busy(&video->vb2_q)) ++ return -EBUSY; ++ ++ ret = __video_try_fmt(video, f, true); ++ if (ret < 0) ++ return ret; ++ ++ ret = video_pipeline_s_fmt(video, NULL, f); ++ if (ret < 0) ++ return ret; ++ ++ video->active_fmt = *f; ++ ++ return 0; ++} ++ ++static int video_try_fmt(struct file *file, ++ void *fh, struct v4l2_format *f) ++{ ++ struct stfcamss_video *video = video_drvdata(file); ++ ++ return __video_try_fmt(video, f, false); ++} ++ ++static int video_try_fmt_mp(struct file *file, ++ void *fh, struct v4l2_format *f) ++{ ++ struct stfcamss_video *video = video_drvdata(file); ++ ++ return __video_try_fmt(video, f, true); ++} ++ ++static int video_enum_input(struct file *file, void *fh, ++ struct v4l2_input *input) ++{ ++ if (input->index > 0) ++ return -EINVAL; ++ ++ strscpy(input->name, "camera", sizeof(input->name)); ++ input->type = V4L2_INPUT_TYPE_CAMERA; ++ ++ return 0; ++} ++ ++static int video_g_input(struct file *file, void *fh, unsigned int *input) ++{ ++ *input = 0; ++ ++ return 0; ++} ++ ++static int video_s_input(struct file *file, void *fh, unsigned int input) ++{ ++ return input == 0 ? 0 : -EINVAL; ++} ++ ++static int video_g_parm(struct file *file, void *priv, ++ struct v4l2_streamparm *p) ++{ ++ struct stfcamss_video *video = video_drvdata(file); ++ struct video_device *vdev = &video->vdev; ++ struct media_entity *entity; ++ struct v4l2_subdev *subdev; ++ struct media_pad *pad; ++ int ret, is_support = 0; ++ ++ entity = &vdev->entity; ++ while (1) { ++ pad = &entity->pads[0]; ++ if (!(pad->flags & MEDIA_PAD_FL_SINK)) ++ break; ++ ++ pad = media_pad_remote_pad_first(pad); ++ if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) ++ break; ++ ++ entity = pad->entity; ++ subdev = media_entity_to_v4l2_subdev(entity); ++ ++ ret = v4l2_g_parm_cap(vdev, subdev, p); ++ if (ret < 0 && ret != -ENOIOCTLCMD) ++ break; ++ if (!ret) ++ is_support = 1; ++ } ++ ++ return is_support ? 0 : ret; ++} ++ ++static int video_s_parm(struct file *file, void *priv, ++ struct v4l2_streamparm *p) ++{ ++ struct stfcamss_video *video = video_drvdata(file); ++ struct video_device *vdev = &video->vdev; ++ struct media_entity *entity; ++ struct v4l2_subdev *subdev; ++ struct media_pad *pad; ++ struct v4l2_streamparm tmp_p; ++ int ret, is_support = 0; ++ ++ entity = &vdev->entity; ++ while (1) { ++ pad = &entity->pads[0]; ++ if (!(pad->flags & MEDIA_PAD_FL_SINK)) ++ break; ++ ++ pad = media_pad_remote_pad_first(pad); ++ if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) ++ break; ++ ++ entity = pad->entity; ++ subdev = media_entity_to_v4l2_subdev(entity); ++ ++ tmp_p = *p; ++ ret = v4l2_s_parm_cap(vdev, subdev, &tmp_p); ++ if (ret < 0 && ret != -ENOIOCTLCMD) ++ break; ++ if (!ret) { ++ is_support = 1; ++ *p = tmp_p; ++ } ++ } ++ ++ return is_support ? 0 : ret; ++} ++ ++/* Crop ioctls */ ++int video_g_pixelaspect(struct file *file, void *fh, ++ int buf_type, struct v4l2_fract *aspect) ++{ ++ return 0; ++} ++ ++int video_g_selection(struct file *file, void *fh, ++ struct v4l2_selection *s) ++{ ++ struct stfcamss_video *video = video_drvdata(file); ++ struct video_device *vdev = &video->vdev; ++ struct media_entity *entity; ++ struct v4l2_subdev *subdev; ++ struct media_pad *pad; ++ struct v4l2_subdev_selection sel = { ++ .which = V4L2_SUBDEV_FORMAT_ACTIVE, ++ .pad = getcrop_pad_id(video->id), ++ .target = s->target, ++ .r = s->r, ++ .flags = s->flags, ++ }; ++ int ret; ++ ++ st_debug(ST_VIDEO, "%s, target = 0x%x, 0x%x\n", ++ __func__, sel.target, s->target); ++ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ++ && s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) ++ return -EINVAL; ++ ++ entity = &vdev->entity; ++ while (1) { ++ pad = &entity->pads[0]; ++ if (!(pad->flags & MEDIA_PAD_FL_SINK)) ++ break; ++ ++ pad = media_pad_remote_pad_first(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, pad, get_selection, NULL, &sel); ++ if (!ret) { ++ s->r = sel.r; ++ s->flags = sel.flags; ++ break; ++ } ++ if (ret != -ENOIOCTLCMD) ++ break; ++ } ++ ++ return ret; ++} ++ ++int video_s_selection(struct file *file, void *fh, ++ struct v4l2_selection *s) ++{ ++ struct stfcamss_video *video = video_drvdata(file); ++ struct video_device *vdev = &video->vdev; ++ struct media_entity *entity; ++ struct v4l2_subdev *subdev; ++ struct media_pad *pad; ++ struct v4l2_subdev_selection sel = { ++ .which = V4L2_SUBDEV_FORMAT_ACTIVE, ++ .pad = getcrop_pad_id(video->id), ++ .target = s->target, ++ .r = s->r, ++ .flags = s->flags, ++ }; ++ struct v4l2_pix_format *format = &video->active_fmt.fmt.pix; ++ struct v4l2_pix_format_mplane *format_mp = ++ &video->active_fmt.fmt.pix_mp; ++ int ret; ++ ++ st_debug(ST_VIDEO, "%s, target = 0x%x, 0x%x\n", ++ __func__, sel.target, s->target); ++ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ++ && s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) ++ return -EINVAL; ++ ++ entity = &vdev->entity; ++ while (1) { ++ pad = &entity->pads[0]; ++ if (!(pad->flags & MEDIA_PAD_FL_SINK)) ++ break; ++ ++ pad = media_pad_remote_pad_first(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, pad, set_selection, NULL, &sel); ++ if (!ret) { ++ s->r = sel.r; ++ s->flags = sel.flags; ++ format->width = s->r.width; ++ format->height = s->r.height; ++ format_mp->width = s->r.width; ++ format_mp->height = s->r.height; ++ ret = __video_try_fmt(video, &video->active_fmt, ++ video->is_mp); ++ if (ret < 0) ++ return ret; ++ break; ++ } ++ if (ret != -ENOIOCTLCMD) ++ break; ++ } ++ ++ st_debug(ST_VIDEO, "ret = 0x%x, -EINVAL = 0x%x\n", ret, -EINVAL); ++ ++ return ret; ++} ++ ++static const struct v4l2_ioctl_ops stf_vid_ioctl_ops = { ++ .vidioc_querycap = video_querycap, ++ .vidioc_enum_fmt_vid_cap = video_enum_fmt, ++ .vidioc_enum_framesizes = video_enum_framesizes, ++ .vidioc_enum_frameintervals = video_enum_frameintervals, ++ .vidioc_g_fmt_vid_cap = video_g_fmt, ++ .vidioc_s_fmt_vid_cap = video_s_fmt, ++ .vidioc_try_fmt_vid_cap = video_try_fmt, ++ .vidioc_reqbufs = vb2_ioctl_reqbufs, ++ .vidioc_querybuf = vb2_ioctl_querybuf, ++ .vidioc_qbuf = vb2_ioctl_qbuf, ++ .vidioc_expbuf = vb2_ioctl_expbuf, ++ .vidioc_dqbuf = vb2_ioctl_dqbuf, ++ .vidioc_create_bufs = vb2_ioctl_create_bufs, ++ .vidioc_prepare_buf = vb2_ioctl_prepare_buf, ++ .vidioc_streamon = vb2_ioctl_streamon, ++ .vidioc_streamoff = vb2_ioctl_streamoff, ++ .vidioc_enum_input = video_enum_input, ++ .vidioc_g_input = video_g_input, ++ .vidioc_s_input = video_s_input, ++ .vidioc_g_parm = video_g_parm, ++ .vidioc_s_parm = video_s_parm, ++ .vidioc_s_selection = video_s_selection, ++ .vidioc_g_selection = video_g_selection, ++}; ++ ++static const struct v4l2_ioctl_ops stf_vid_ioctl_ops_mp = { ++ .vidioc_querycap = video_querycap, ++ .vidioc_enum_fmt_vid_cap = video_enum_fmt, ++ .vidioc_enum_framesizes = video_enum_framesizes, ++ .vidioc_enum_frameintervals = video_enum_frameintervals, ++ .vidioc_g_fmt_vid_cap_mplane = video_g_fmt_mp, ++ .vidioc_s_fmt_vid_cap_mplane = video_s_fmt_mp, ++ .vidioc_try_fmt_vid_cap_mplane = video_try_fmt_mp, ++ .vidioc_reqbufs = vb2_ioctl_reqbufs, ++ .vidioc_querybuf = vb2_ioctl_querybuf, ++ .vidioc_qbuf = vb2_ioctl_qbuf, ++ .vidioc_expbuf = vb2_ioctl_expbuf, ++ .vidioc_dqbuf = vb2_ioctl_dqbuf, ++ .vidioc_create_bufs = vb2_ioctl_create_bufs, ++ .vidioc_prepare_buf = vb2_ioctl_prepare_buf, ++ .vidioc_streamon = vb2_ioctl_streamon, ++ .vidioc_streamoff = vb2_ioctl_streamoff, ++ .vidioc_enum_input = video_enum_input, ++ .vidioc_g_input = video_g_input, ++ .vidioc_s_input = video_s_input, ++ .vidioc_g_parm = video_g_parm, ++ .vidioc_s_parm = video_s_parm, ++ .vidioc_s_selection = video_s_selection, ++ .vidioc_g_selection = video_g_selection, ++}; ++ ++static const struct v4l2_ioctl_ops stf_vid_ioctl_ops_out = { ++ .vidioc_querycap = video_querycap, ++ .vidioc_enum_fmt_vid_out = video_enum_fmt, ++ .vidioc_enum_framesizes = video_enum_framesizes, ++ .vidioc_enum_frameintervals = video_enum_frameintervals, ++ .vidioc_g_fmt_vid_out = video_g_fmt, ++ .vidioc_s_fmt_vid_out = video_s_fmt, ++ .vidioc_try_fmt_vid_out = video_try_fmt, ++ .vidioc_reqbufs = vb2_ioctl_reqbufs, ++ .vidioc_querybuf = vb2_ioctl_querybuf, ++ .vidioc_qbuf = vb2_ioctl_qbuf, ++ .vidioc_expbuf = vb2_ioctl_expbuf, ++ .vidioc_dqbuf = vb2_ioctl_dqbuf, ++ .vidioc_create_bufs = vb2_ioctl_create_bufs, ++ .vidioc_prepare_buf = vb2_ioctl_prepare_buf, ++ .vidioc_streamon = vb2_ioctl_streamon, ++ .vidioc_streamoff = vb2_ioctl_streamoff, ++}; ++ ++static int video_open(struct file *file) ++{ ++ struct video_device *vdev = video_devdata(file); ++ struct stfcamss_video *video = video_drvdata(file); ++ struct v4l2_fh *vfh; ++ int ret; ++ ++ mutex_lock(&video->lock); ++ ++ vfh = kzalloc(sizeof(*vfh), GFP_KERNEL); ++ if (vfh == NULL) { ++ ret = -ENOMEM; ++ goto error_alloc; ++ } ++ ++ v4l2_fh_init(vfh, vdev); ++ v4l2_fh_add(vfh); ++ ++ file->private_data = vfh; ++ ++ if (!video->pm_count) { ++ ret = v4l2_pipeline_pm_get(&vdev->entity); ++ if (ret < 0) { ++ st_err(ST_VIDEO, ++ "Failed to power up pipeline: %d\n", ret); ++ goto error_pm_use; ++ } ++ } ++ ++ video->pm_count++; ++ ++ mutex_unlock(&video->lock); ++ ++ return 0; ++ ++error_pm_use: ++ v4l2_fh_release(file); ++error_alloc: ++ mutex_unlock(&video->lock); ++ return ret; ++} ++ ++static int video_release(struct file *file) ++{ ++ struct video_device *vdev = video_devdata(file); ++ struct stfcamss_video *video = video_drvdata(file); ++ ++ vb2_fop_release(file); ++ ++ video->pm_count--; ++ ++ if (!video->pm_count) ++ v4l2_pipeline_pm_put(&vdev->entity); ++ ++ file->private_data = NULL; ++ ++ return 0; ++} ++ ++static const struct v4l2_file_operations stf_vid_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = video_ioctl2, ++ .open = video_open, ++ .release = video_release, ++ .poll = vb2_fop_poll, ++ .mmap = vb2_fop_mmap, ++ .read = vb2_fop_read, ++}; ++ ++static void stf_video_release(struct video_device *vdev) ++{ ++ struct stfcamss_video *video = video_get_drvdata(vdev); ++ ++ media_entity_cleanup(&vdev->entity); ++ ++ mutex_destroy(&video->q_lock); ++ mutex_destroy(&video->lock); ++} ++ ++int stf_video_register(struct stfcamss_video *video, ++ struct v4l2_device *v4l2_dev, ++ const char *name, int is_mp) ++{ ++ struct video_device *vdev; ++ struct vb2_queue *q; ++ struct media_pad *pad = &video->pad; ++ int ret; ++ enum isp_pad_id isp_pad; ++ ++ vdev = &video->vdev; ++ ++ mutex_init(&video->q_lock); ++ ++ q = &video->vb2_q; ++ q->drv_priv = video; ++ q->mem_ops = &vb2_dma_contig_memops; ++ q->ops = &stf_video_vb2_q_ops; ++ //q->type = is_mp ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : ++ // V4L2_BUF_TYPE_VIDEO_CAPTURE; ++ q->type = video->type; ++ q->io_modes = VB2_DMABUF | VB2_MMAP | VB2_READ; ++ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; ++ q->buf_struct_size = sizeof(struct stfcamss_buffer); ++ q->dev = video->stfcamss->dev; ++ q->lock = &video->q_lock; ++ q->min_buffers_needed = STFCAMSS_MIN_BUFFERS; ++ ret = vb2_queue_init(q); ++ if (ret < 0) { ++ st_err(ST_VIDEO, ++ "Failed to init vb2 queue: %d\n", ret); ++ goto err_vb2_init; ++ } ++ ++ pad->flags = MEDIA_PAD_FL_SINK; ++ ret = media_entity_pads_init(&vdev->entity, 1, pad); ++ if (ret < 0) { ++ st_err(ST_VIDEO, ++ "Failed to init video entity: %d\n", ++ ret); ++ goto err_vb2_init; ++ } ++ ++ mutex_init(&video->lock); ++ ++ isp_pad = stf_vin_map_isp_pad(video->id, STF_ISP_PAD_SRC); ++ if (video->id == VIN_LINE_WR) { ++ video->formats = formats_pix_st7110_wr; ++ video->nformats = ARRAY_SIZE(formats_pix_st7110_wr); ++ video->bpl_alignment = STFCAMSS_FRAME_WIDTH_ALIGN_8; ++ } else if (isp_pad == STF_ISP_PAD_SRC ++ || isp_pad == STF_ISP_PAD_SRC_SS0 ++ || isp_pad == STF_ISP_PAD_SRC_SS1) { ++ video->formats = formats_pix_st7110_isp; ++ video->nformats = ARRAY_SIZE(formats_pix_st7110_isp); ++ video->bpl_alignment = STFCAMSS_FRAME_WIDTH_ALIGN_8; ++ } else if (isp_pad == STF_ISP_PAD_SRC_ITIW ++ || isp_pad == STF_ISP_PAD_SRC_ITIR) { ++ video->formats = formats_st7110_isp_iti; ++ video->nformats = ARRAY_SIZE(formats_st7110_isp_iti); ++ video->bpl_alignment = STFCAMSS_FRAME_WIDTH_ALIGN_8; ++ } else { // raw/scdump/yhist ++ video->formats = formats_raw_st7110_isp; ++ video->nformats = ARRAY_SIZE(formats_raw_st7110_isp); ++ video->bpl_alignment = STFCAMSS_FRAME_WIDTH_ALIGN_128; ++ } ++ video->is_mp = is_mp; ++ ++ ret = stf_video_init_format(video, is_mp); ++ if (ret < 0) { ++ st_err(ST_VIDEO, "Failed to init format: %d\n", ret); ++ goto err_vid_init_format; ++ } ++ ++ vdev->fops = &stf_vid_fops; ++ if (isp_pad == STF_ISP_PAD_SRC_ITIR) { ++ vdev->device_caps = V4L2_CAP_VIDEO_OUTPUT; ++ vdev->vfl_dir = VFL_DIR_TX; ++ } else { ++ vdev->device_caps = is_mp ? V4L2_CAP_VIDEO_CAPTURE_MPLANE : ++ V4L2_CAP_VIDEO_CAPTURE; ++ vdev->vfl_dir = VFL_DIR_RX; ++ } ++ vdev->device_caps |= V4L2_CAP_STREAMING | V4L2_CAP_READWRITE | V4L2_CAP_IO_MC; ++ if (video->type == V4L2_CAP_VIDEO_OUTPUT) ++ vdev->ioctl_ops = &stf_vid_ioctl_ops_out; ++ else ++ vdev->ioctl_ops = is_mp ? &stf_vid_ioctl_ops_mp : &stf_vid_ioctl_ops; ++ vdev->release = stf_video_release; ++ vdev->v4l2_dev = v4l2_dev; ++ vdev->queue = &video->vb2_q; ++ vdev->lock = &video->lock; ++ //strlcpy(vdev->name, name, sizeof(vdev->name)); ++ strscpy(vdev->name, name, sizeof(vdev->name)); ++ ++ ret = video_register_device(vdev, VFL_TYPE_VIDEO, video->id); ++ if (ret < 0) { ++ st_err(ST_VIDEO, ++ "Failed to register video device: %d\n", ++ ret); ++ goto err_vid_reg; ++ } ++ ++ video_set_drvdata(vdev, video); ++ return 0; ++ ++err_vid_reg: ++err_vid_init_format: ++ media_entity_cleanup(&vdev->entity); ++ mutex_destroy(&video->lock); ++err_vb2_init: ++ mutex_destroy(&video->q_lock); ++ return ret; ++} ++ ++void stf_video_unregister(struct stfcamss_video *video) ++{ ++ vb2_video_unregister_device(&video->vdev); ++} +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_video.h +@@ -0,0 +1,83 @@ ++/* SPDX-License-Identifier: GPL-2.0 ++ * ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#ifndef STF_VIDEO_H ++#define STF_VIDEO_H ++ ++#include <linux/mutex.h> ++#include <media/videobuf2-v4l2.h> ++#include <linux/videodev2.h> ++#include <media/v4l2-dev.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-fh.h> ++#include <media/v4l2-ioctl.h> ++ ++#define STFCAMSS_FRAME_MIN_WIDTH 64 ++#define STFCAMSS_FRAME_MAX_WIDTH 1920 ++#define STFCAMSS_FRAME_MIN_HEIGHT 64 ++#define STFCAMSS_FRAME_MAX_HEIGHT 1080 ++#define STFCAMSS_FRAME_WIDTH_ALIGN_8 8 ++#define STFCAMSS_FRAME_WIDTH_ALIGN_128 128 ++#define STFCAMSS_MIN_BUFFERS 2 ++ ++#define STFCAMSS_MAX_ENTITY_NAME_LEN 27 ++ ++struct stfcamss_buffer { ++ struct vb2_v4l2_buffer vb; ++ dma_addr_t addr[3]; ++ void *vaddr_sc; /* Use for isp sc data */ ++ struct list_head queue; ++ int sizeimage; ++}; ++ ++struct stfcamss_video; ++ ++struct stfcamss_video_ops { ++ int (*queue_buffer)(struct stfcamss_video *vid, ++ struct stfcamss_buffer *buf); ++ int (*flush_buffers)(struct stfcamss_video *vid, ++ enum vb2_buffer_state state); ++}; ++ ++struct fract { ++ u8 numerator; ++ u8 denominator; ++}; ++ ++struct stfcamss_format_info { ++ u32 code; ++ u32 pixelformat; ++ u8 planes; ++ struct fract hsub[3]; ++ struct fract vsub[3]; ++ u8 bpp[3]; ++}; ++ ++struct stfcamss_video { ++ struct stfcamss *stfcamss; ++ u8 id; ++ struct vb2_queue vb2_q; ++ struct video_device vdev; ++ struct media_pad pad; ++ struct media_pipeline pipe; ++ struct v4l2_format active_fmt; ++ enum v4l2_buf_type type; ++ const struct stfcamss_video_ops *ops; ++ struct mutex lock; ++ struct mutex q_lock; ++ unsigned int bpl_alignment; ++ const struct stfcamss_format_info *formats; ++ unsigned int nformats; ++ unsigned int is_mp; ++ unsigned int pm_count; ++}; ++ ++int stf_video_register(struct stfcamss_video *video, ++ struct v4l2_device *v4l2_dev, const char *name, int is_mp); ++ ++void stf_video_unregister(struct stfcamss_video *video); ++ ++#endif /* STF_VIDEO_H */ +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_vin.c +@@ -0,0 +1,1515 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/interrupt.h> ++#include <linux/dma-mapping.h> ++#include <linux/pm_runtime.h> ++#include <media/v4l2-event.h> ++ ++#include "stfcamss.h" ++ ++#define vin_line_array(ptr_line) \ ++ ((const struct vin_line (*)[]) &(ptr_line[-(ptr_line->id)])) ++ ++#define line_to_vin2_dev(ptr_line) \ ++ container_of(vin_line_array(ptr_line), struct stf_vin2_dev, line) ++ ++#define VIN_FRAME_DROP_MAX_VAL 90 ++#define VIN_FRAME_DROP_MIN_VAL 4 ++#define VIN_FRAME_PER_SEC_MAX_VAL 90 ++ ++/* ISP ctrl need 1 sec to let frames become stable. */ ++#define VIN_FRAME_DROP_SEC_FOR_ISP_CTRL 1 ++ ++ ++// #define VIN_TWO_BUFFER ++ ++static const struct vin2_format vin2_formats_st7110[] = { ++ { MEDIA_BUS_FMT_YUYV8_2X8, 16}, ++ { MEDIA_BUS_FMT_RGB565_2X8_LE, 16}, ++ { MEDIA_BUS_FMT_SRGGB8_1X8, 8}, ++ { MEDIA_BUS_FMT_SGRBG8_1X8, 8}, ++ { MEDIA_BUS_FMT_SGBRG8_1X8, 8}, ++ { MEDIA_BUS_FMT_SBGGR8_1X8, 8}, ++ { MEDIA_BUS_FMT_SRGGB10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGRBG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGBRG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SBGGR10_1X10, 10}, ++ { MEDIA_BUS_FMT_SRGGB12_1X12, 12}, ++ { MEDIA_BUS_FMT_SGRBG12_1X12, 12}, ++ { MEDIA_BUS_FMT_SGBRG12_1X12, 12}, ++ { MEDIA_BUS_FMT_SBGGR12_1X12, 12}, ++ { MEDIA_BUS_FMT_Y12_1X12, 8}, ++ { MEDIA_BUS_FMT_YUV8_1X24, 8}, ++ { MEDIA_BUS_FMT_AYUV8_1X32, 32}, ++}; ++ ++static const struct vin2_format isp_formats_st7110_raw[] = { ++ { MEDIA_BUS_FMT_SBGGR12_1X12, 12}, ++ { MEDIA_BUS_FMT_SRGGB12_1X12, 12}, ++ { MEDIA_BUS_FMT_SGRBG12_1X12, 12}, ++ { MEDIA_BUS_FMT_SGBRG12_1X12, 12}, ++}; ++ ++static const struct vin2_format isp_formats_st7110_uo[] = { ++ { MEDIA_BUS_FMT_Y12_1X12, 8}, ++}; ++ ++static const struct vin2_format isp_formats_st7110_iti[] = { ++ { MEDIA_BUS_FMT_SRGGB10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGRBG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SGBRG10_1X10, 10}, ++ { MEDIA_BUS_FMT_SBGGR10_1X10, 10}, ++ { MEDIA_BUS_FMT_SRGGB12_1X12, 12}, ++ { MEDIA_BUS_FMT_SGRBG12_1X12, 12}, ++ { MEDIA_BUS_FMT_SGBRG12_1X12, 12}, ++ { MEDIA_BUS_FMT_SBGGR12_1X12, 12}, ++ { MEDIA_BUS_FMT_Y12_1X12, 8}, ++ { MEDIA_BUS_FMT_YUV8_1X24, 8}, ++}; ++ ++static const struct vin2_format_table vin2_formats_table[] = { ++ /* VIN_LINE_WR */ ++ { vin2_formats_st7110, ARRAY_SIZE(vin2_formats_st7110) }, ++ /* VIN_LINE_ISP */ ++ { isp_formats_st7110_uo, ARRAY_SIZE(isp_formats_st7110_uo) }, ++ /* VIN_LINE_ISP_SS0 */ ++ { isp_formats_st7110_uo, ARRAY_SIZE(isp_formats_st7110_uo) }, ++ /* VIN_LINE_ISP_SS1 */ ++ { isp_formats_st7110_uo, ARRAY_SIZE(isp_formats_st7110_uo) }, ++ /* VIN_LINE_ISP_ITIW */ ++ { isp_formats_st7110_iti, ARRAY_SIZE(isp_formats_st7110_iti) }, ++ /* VIN_LINE_ISP_ITIR */ ++ { isp_formats_st7110_iti, ARRAY_SIZE(isp_formats_st7110_iti) }, ++ /* VIN_LINE_ISP_RAW */ ++ { isp_formats_st7110_raw, ARRAY_SIZE(isp_formats_st7110_raw) }, ++ /* VIN_LINE_ISP_SCD_Y */ ++ { isp_formats_st7110_raw, ARRAY_SIZE(isp_formats_st7110_raw) }, ++}; ++ ++static void vin_buffer_done(struct vin_line *line, struct vin_params *params); ++static void vin_change_buffer(struct vin_line *line); ++static struct stfcamss_buffer *vin_buf_get_pending(struct vin_output *output); ++static void vin_output_init_addrs(struct vin_line *line); ++static void vin_init_outputs(struct vin_line *line); ++static struct v4l2_mbus_framefmt * ++__vin_get_format(struct vin_line *line, ++ struct v4l2_subdev_state *state, ++ unsigned int pad, ++ enum v4l2_subdev_format_whence which); ++ ++static char *get_line_subdevname(int line_id) ++{ ++ char *name = NULL; ++ ++ switch (line_id) { ++ case VIN_LINE_WR: ++ name = "wr"; ++ break; ++ case VIN_LINE_ISP: ++ name = "isp0"; ++ break; ++ case VIN_LINE_ISP_SS0: ++ name = "isp0_ss0"; ++ break; ++ case VIN_LINE_ISP_SS1: ++ name = "isp0_ss1"; ++ break; ++ case VIN_LINE_ISP_ITIW: ++ name = "isp0_itiw"; ++ break; ++ case VIN_LINE_ISP_ITIR: ++ name = "isp0_itir"; ++ break; ++ case VIN_LINE_ISP_RAW: ++ name = "isp0_raw"; ++ break; ++ case VIN_LINE_ISP_SCD_Y: ++ name = "isp0_scd_y"; ++ break; ++ default: ++ name = "unknow"; ++ break; ++ } ++ return name; ++} ++ ++static enum isp_line_id stf_vin_map_isp_line(enum vin_line_id line) ++{ ++ enum isp_line_id line_id; ++ ++ if ((line > VIN_LINE_WR) && (line < VIN_LINE_MAX)) { ++ line_id = line % STF_ISP_LINE_MAX; ++ if (line_id == 0) ++ line_id = STF_ISP_LINE_SRC_SCD_Y; ++ } else ++ line_id = STF_ISP_LINE_INVALID; ++ ++ return line_id; ++} ++ ++enum isp_pad_id stf_vin_map_isp_pad(enum vin_line_id line, enum isp_pad_id def) ++{ ++ enum isp_pad_id pad_id; ++ ++ if (line == VIN_LINE_WR) ++ pad_id = STF_ISP_PAD_SINK; ++ else if ((line > VIN_LINE_WR) && (line < VIN_LINE_MAX)) ++ pad_id = stf_vin_map_isp_line(line); ++ else ++ pad_id = def; ++ ++ return pad_id; ++} ++ ++int stf_vin_subdev_init(struct stfcamss *stfcamss) ++{ ++ struct stf_vin_dev *vin; ++ struct device *dev = stfcamss->dev; ++ struct stf_vin2_dev *vin_dev = stfcamss->vin_dev; ++ int i, ret = 0; ++ ++ vin_dev->stfcamss = stfcamss; ++ vin_dev->hw_ops = &vin_ops; ++ vin_dev->hw_ops->isr_buffer_done = vin_buffer_done; ++ vin_dev->hw_ops->isr_change_buffer = vin_change_buffer; ++ ++ vin = stfcamss->vin; ++ atomic_set(&vin_dev->ref_count, 0); ++ ++ ret = devm_request_irq(dev, ++ vin->irq, vin_dev->hw_ops->vin_wr_irq_handler, ++ 0, "vin_axiwr_irq", vin_dev); ++ if (ret) { ++ st_err(ST_VIN, "failed to request irq\n"); ++ goto out; ++ } ++ ++ ret = devm_request_irq(dev, ++ vin->isp_irq, vin_dev->hw_ops->vin_isp_irq_handler, ++ 0, "vin_isp_irq", vin_dev); ++ if (ret) { ++ st_err(ST_VIN, "failed to request isp irq\n"); ++ goto out; ++ } ++ ++ st_info(ST_CAMSS, "%s, %d!\n", __func__, __LINE__); ++#ifdef ISP_USE_CSI_AND_SC_DONE_INTERRUPT ++ ret = devm_request_irq(dev, ++ vin->isp_csi_irq, vin_dev->hw_ops->vin_isp_csi_irq_handler, ++ 0, "vin_isp_csi_irq", vin_dev); ++ if (ret) { ++ st_err(ST_VIN, "failed to request isp raw irq\n"); ++ goto out; ++ } ++ ++ ret = devm_request_irq(dev, ++ vin->isp_scd_irq, vin_dev->hw_ops->vin_isp_scd_irq_handler, ++ 0, "vin_isp_scd_irq", vin_dev); ++ if (ret) { ++ st_err(ST_VIN, "failed to request isp scd irq\n"); ++ goto out; ++ } ++#endif ++ ++ ret = devm_request_irq(dev, ++ vin->isp_irq_csiline, vin_dev->hw_ops->vin_isp_irq_csiline_handler, ++ 0, "vin_isp_irq_csiline", vin_dev); ++ if (ret) { ++ st_err(ST_VIN, "failed to request isp irq csiline\n"); ++ goto out; ++ } ++ ++ mutex_init(&vin_dev->power_lock); ++ vin_dev->power_count = 0; ++ ++ for (i = 0; i < STF_DUMMY_MODULE_NUMS; i++) { ++ struct dummy_buffer *dummy_buffer = &vin_dev->dummy_buffer[i]; ++ ++ mutex_init(&dummy_buffer->stream_lock); ++ dummy_buffer->nums = i == 0 ? VIN_DUMMY_BUFFER_NUMS : ISP_DUMMY_BUFFER_NUMS; ++ dummy_buffer->stream_count = 0; ++ dummy_buffer->buffer = devm_kzalloc(dev, ++ dummy_buffer->nums * sizeof(struct vin_dummy_buffer), GFP_KERNEL); ++ atomic_set(&dummy_buffer->frame_skip, 0); ++ } ++ ++ for (i = VIN_LINE_WR; ++ i < STF_ISP_LINE_MAX + 1; i++) { ++ struct vin_line *l = &vin_dev->line[i]; ++ int is_mp; ++ ++ is_mp = i == VIN_LINE_WR ? false : true; ++ is_mp = false; ++ if (stf_vin_map_isp_line(i) == STF_ISP_LINE_SRC_ITIR) ++ l->video_out.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; ++ else ++ l->video_out.type = is_mp ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : ++ V4L2_BUF_TYPE_VIDEO_CAPTURE; ++ l->video_out.stfcamss = stfcamss; ++ l->id = i; ++ l->sdev_type = VIN_DEV_TYPE; ++ l->formats = vin2_formats_table[i].fmts; ++ l->nformats = vin2_formats_table[i].nfmts; ++ spin_lock_init(&l->output_lock); ++ ++ mutex_init(&l->stream_lock); ++ l->stream_count = 0; ++ mutex_init(&l->power_lock); ++ l->power_count = 0; ++ } ++ ++ return 0; ++out: ++ return ret; ++} ++ ++static int vin_set_power(struct v4l2_subdev *sd, int on) ++{ ++ struct vin_line *line = v4l2_get_subdevdata(sd); ++ struct stf_vin2_dev *vin_dev = line_to_vin2_dev(line); ++ struct stfcamss *stfcamss = vin_dev->stfcamss; ++ ++ mutex_lock(&line->power_lock); ++ if (on) { ++ if (line->power_count == 0) ++ vin_init_outputs(line); ++ line->power_count++; ++ } else { ++ if (line->power_count == 0) { ++ st_err(ST_VIN, ++ "line power off on power_count == 0\n"); ++ goto exit_line; ++ } ++ line->power_count--; ++ } ++exit_line: ++ mutex_unlock(&line->power_lock); ++ ++ mutex_lock(&vin_dev->power_lock); ++ if (on) { ++ if (vin_dev->power_count == 0) { ++ pm_runtime_get_sync(stfcamss->dev); ++ vin_dev->hw_ops->vin_clk_enable(vin_dev); ++ vin_dev->hw_ops->vin_config_set(vin_dev); ++ } ++ vin_dev->power_count++; ++ } else { ++ if (vin_dev->power_count == 0) { ++ st_err(ST_VIN, ++ "vin_dev power off on power_count == 0\n"); ++ goto exit; ++ } ++ if (vin_dev->power_count == 1) { ++ vin_dev->hw_ops->vin_clk_disable(vin_dev); ++ pm_runtime_put_sync(stfcamss->dev); ++ } ++ vin_dev->power_count--; ++ } ++exit: ++ ++ mutex_unlock(&vin_dev->power_lock); ++ ++ return 0; ++} ++ ++static unsigned int get_frame_skip(struct vin_line *line) ++{ ++ unsigned int frame_skip = 0; ++ unsigned int isp_ctrl_skip_frames = 0; ++ struct media_entity *sensor; ++ struct v4l2_subdev_frame_interval fi; ++ ++ sensor = stfcamss_find_sensor(&line->subdev.entity); ++ if (sensor) { ++ int fps = 0; ++ struct v4l2_subdev *subdev = ++ media_entity_to_v4l2_subdev(sensor); ++ ++ if (subdev->ops->video->g_frame_interval) { ++ if (!subdev->ops->video->g_frame_interval(subdev, &fi)) ++ fps = fi.interval.denominator; ++ ++ if (fps > 0 && fps <= 90) ++ isp_ctrl_skip_frames = fps * VIN_FRAME_DROP_SEC_FOR_ISP_CTRL; ++ } ++ if (!fps) ++ st_debug(ST_VIN, "%s, Failed to get sensor fps !\n", __func__); ++ ++ if (isp_ctrl_skip_frames <= VIN_FRAME_DROP_MIN_VAL) ++ isp_ctrl_skip_frames = VIN_FRAME_DROP_MIN_VAL; ++ ++ v4l2_subdev_call(subdev, sensor, g_skip_frames, &frame_skip); ++ ++ frame_skip += isp_ctrl_skip_frames; ++ ++ if (frame_skip > VIN_FRAME_DROP_MAX_VAL) ++ frame_skip = VIN_FRAME_DROP_MAX_VAL; ++ st_debug(ST_VIN, "%s, frame_skip %d\n", __func__, frame_skip); ++ } ++ ++ return frame_skip; ++} ++ ++static void vin_buf_l2cache_flush(struct vin_output *output) ++{ ++ struct stfcamss_buffer *buffer = NULL; ++ ++ if (!list_empty(&output->pending_bufs)) { ++ list_for_each_entry(buffer, &output->pending_bufs, queue) { ++ sifive_l2_flush64_range(buffer->addr[0], buffer->sizeimage); ++ } ++ } ++} ++ ++static int vin_enable_output(struct vin_line *line) ++{ ++ struct vin_output *output = &line->output; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&line->output_lock, flags); ++ ++ output->state = VIN_OUTPUT_IDLE; ++ ++ vin_buf_l2cache_flush(output); ++ ++ output->buf[0] = vin_buf_get_pending(output); ++#ifdef VIN_TWO_BUFFER ++ if (line->id == VIN_LINE_WR) ++ output->buf[1] = vin_buf_get_pending(output); ++#endif ++ if (!output->buf[0] && output->buf[1]) { ++ output->buf[0] = output->buf[1]; ++ output->buf[1] = NULL; ++ } ++ ++ if (output->buf[0]) ++ output->state = VIN_OUTPUT_SINGLE; ++ ++#ifdef VIN_TWO_BUFFER ++ if (output->buf[1] && line->id == VIN_LINE_WR) ++ output->state = VIN_OUTPUT_CONTINUOUS; ++#endif ++ output->sequence = 0; ++ ++ vin_output_init_addrs(line); ++ spin_unlock_irqrestore(&line->output_lock, flags); ++ return 0; ++} ++ ++static int vin_disable_output(struct vin_line *line) ++{ ++ struct vin_output *output = &line->output; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&line->output_lock, flags); ++ ++ output->state = VIN_OUTPUT_OFF; ++ ++ spin_unlock_irqrestore(&line->output_lock, flags); ++ return 0; ++} ++ ++static u32 line_to_dummy_module(struct vin_line *line) ++{ ++ u32 dummy_module = 0; ++ ++ switch (line->id) { ++ case VIN_LINE_WR: ++ dummy_module = STF_DUMMY_VIN; ++ break; ++ case VIN_LINE_ISP: ++ case VIN_LINE_ISP_SS0: ++ case VIN_LINE_ISP_SS1: ++ case VIN_LINE_ISP_ITIW: ++ case VIN_LINE_ISP_ITIR: ++ case VIN_LINE_ISP_RAW: ++ case VIN_LINE_ISP_SCD_Y: ++ dummy_module = STF_DUMMY_ISP; ++ break; ++ default: ++ dummy_module = STF_DUMMY_VIN; ++ break; ++ } ++ ++ return dummy_module; ++} ++ ++static int vin_alloc_dummy_buffer(struct stf_vin2_dev *vin_dev, ++ struct v4l2_mbus_framefmt *fmt, int dummy_module) ++{ ++ struct device *dev = vin_dev->stfcamss->dev; ++ struct dummy_buffer *dummy_buffer = &vin_dev->dummy_buffer[dummy_module]; ++ struct vin_dummy_buffer *buffer = NULL; ++ int ret = 0, i; ++ u32 aligns; ++ ++ for (i = 0; i < dummy_buffer->nums; i++) { ++ buffer = &vin_dev->dummy_buffer[dummy_module].buffer[i]; ++ buffer->width = fmt->width; ++ buffer->height = fmt->height; ++ buffer->mcode = fmt->code; ++ if (i == STF_VIN_PAD_SINK) { ++ aligns = ALIGN(fmt->width * 4, STFCAMSS_FRAME_WIDTH_ALIGN_8); ++ buffer->buffer_size = PAGE_ALIGN(aligns * fmt->height); ++ } else if (i == STF_ISP_PAD_SRC ++ || i == STF_ISP_PAD_SRC_SS0 ++ || i == STF_ISP_PAD_SRC_SS1) { ++ aligns = ALIGN(fmt->width, STFCAMSS_FRAME_WIDTH_ALIGN_8); ++ buffer->buffer_size = PAGE_ALIGN(aligns * fmt->height * 3 / 2); ++ } else if (i == STF_ISP_PAD_SRC_ITIW ++ || i == STF_ISP_PAD_SRC_ITIR) { ++ aligns = ALIGN(fmt->width, STFCAMSS_FRAME_WIDTH_ALIGN_8); ++ buffer->buffer_size = PAGE_ALIGN(aligns * fmt->height * 3); ++ } else if (i == STF_ISP_PAD_SRC_RAW) { ++ aligns = ALIGN(fmt->width * ISP_RAW_DATA_BITS / 8, ++ STFCAMSS_FRAME_WIDTH_ALIGN_128); ++ buffer->buffer_size = PAGE_ALIGN(aligns * fmt->height); ++ } else if (i == STF_ISP_PAD_SRC_SCD_Y) ++ buffer->buffer_size = PAGE_ALIGN(ISP_SCD_Y_BUFFER_SIZE); ++ else ++ continue; ++ ++ buffer->vaddr = dma_alloc_coherent(dev, buffer->buffer_size, ++ &buffer->paddr[0], GFP_DMA | GFP_KERNEL); ++ ++ if (buffer->vaddr) { ++ if (i == STF_ISP_PAD_SRC ++ || i == STF_ISP_PAD_SRC_SS0 ++ || i == STF_ISP_PAD_SRC_SS1 ++ || i == STF_ISP_PAD_SRC_ITIW ++ || i == STF_ISP_PAD_SRC_ITIR) ++ buffer->paddr[1] = (dma_addr_t)(buffer->paddr[0] + ++ aligns * fmt->height); ++ else if (i == STF_ISP_PAD_SRC_SCD_Y) ++ buffer->paddr[1] = (dma_addr_t)(buffer->paddr[0] + ++ ISP_YHIST_BUFFER_SIZE); ++ else ++ st_debug(ST_VIN, "signal plane\n"); ++ } ++ { ++ char szPadName[][32] = { ++ "VIN_PAD_SINK", ++ "ISP_PAD_SRC", ++ "ISP_PAD_SRC_SS0", ++ "ISP_PAD_SRC_SS1", ++ "ISP_PAD_SRC_ITIW", ++ "ISP_PAD_SRC_ITIR", ++ "ISP_PAD_SRC_RAW", ++ "ISP_PAD_SRC_SCD_Y", ++ "Unknown Pad" ++ }; ++ ++ st_debug(ST_VIN, "%s: i = %d(%s) addr[0] = %llx, addr[1] = %llx, size = %u bytes\n", ++ __func__, ++ i, ++ szPadName[i], ++ buffer->paddr[0], ++ buffer->paddr[1], ++ buffer->buffer_size ++ ); ++ } ++ } ++ ++ return ret; ++} ++ ++static void vin_free_dummy_buffer(struct stf_vin2_dev *vin_dev, int dummy_module) ++{ ++ struct device *dev = vin_dev->stfcamss->dev; ++ struct dummy_buffer *dummy_buffer = &vin_dev->dummy_buffer[dummy_module]; ++ struct vin_dummy_buffer *buffer = NULL; ++ int i; ++ ++ for (i = 0; i < dummy_buffer->nums; i++) { ++ buffer = &dummy_buffer->buffer[i]; ++ if (buffer->vaddr) ++ dma_free_coherent(dev, buffer->buffer_size, ++ buffer->vaddr, buffer->paddr[0]); ++ memset(buffer, 0, sizeof(struct vin_dummy_buffer)); ++ } ++} ++ ++static void vin_set_dummy_buffer(struct vin_line *line, u32 pad) ++{ ++ struct stf_vin2_dev *vin_dev = line_to_vin2_dev(line); ++ int dummy_module = line_to_dummy_module(line); ++ struct dummy_buffer *dummy_buffer = &vin_dev->dummy_buffer[dummy_module]; ++ struct vin_dummy_buffer *buffer = NULL; ++ ++ switch (pad) { ++ case STF_VIN_PAD_SINK: ++ if (line->id == VIN_LINE_WR) { ++ buffer = &dummy_buffer->buffer[STF_VIN_PAD_SINK]; ++ vin_dev->hw_ops->vin_wr_set_ping_addr(vin_dev, buffer->paddr[0]); ++ vin_dev->hw_ops->vin_wr_set_pong_addr(vin_dev, buffer->paddr[0]); ++ } else { ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC]; ++ vin_dev->hw_ops->vin_isp_set_yuv_addr(vin_dev, ++ buffer->paddr[0], buffer->paddr[1]); ++ ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC_SS0]; ++ vin_dev->hw_ops->vin_isp_set_ss0_addr(vin_dev, ++ buffer->paddr[0], buffer->paddr[1]); ++ ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC_SS1]; ++ vin_dev->hw_ops->vin_isp_set_ss1_addr(vin_dev, ++ buffer->paddr[0], buffer->paddr[1]); ++ ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC_ITIW]; ++ vin_dev->hw_ops->vin_isp_set_itiw_addr(vin_dev, ++ buffer->paddr[0], buffer->paddr[1]); ++ ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC_ITIR]; ++ vin_dev->hw_ops->vin_isp_set_itir_addr(vin_dev, ++ buffer->paddr[0], buffer->paddr[1]); ++ ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC_RAW]; ++ vin_dev->hw_ops->vin_isp_set_raw_addr(vin_dev, buffer->paddr[0]); ++ ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC_SCD_Y]; ++ vin_dev->hw_ops->vin_isp_set_scd_addr(vin_dev, ++ buffer->paddr[0], buffer->paddr[1], AWB_TYPE); ++ } ++ break; ++ case STF_ISP_PAD_SRC: ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC]; ++ vin_dev->hw_ops->vin_isp_set_yuv_addr(vin_dev, ++ buffer->paddr[0], buffer->paddr[1]); ++ break; ++ case STF_ISP_PAD_SRC_SS0: ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC_SS0]; ++ vin_dev->hw_ops->vin_isp_set_ss0_addr(vin_dev, ++ buffer->paddr[0], buffer->paddr[1]); ++ break; ++ case STF_ISP_PAD_SRC_SS1: ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC_SS1]; ++ vin_dev->hw_ops->vin_isp_set_ss1_addr(vin_dev, ++ buffer->paddr[0], buffer->paddr[1]); ++ break; ++ case STF_ISP_PAD_SRC_ITIW: ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC_ITIW]; ++ vin_dev->hw_ops->vin_isp_set_itiw_addr(vin_dev, ++ buffer->paddr[0], buffer->paddr[1]); ++ break; ++ case STF_ISP_PAD_SRC_ITIR: ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC_ITIR]; ++ vin_dev->hw_ops->vin_isp_set_itir_addr(vin_dev, ++ buffer->paddr[0], buffer->paddr[1]); ++ break; ++ case STF_ISP_PAD_SRC_RAW: ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC_RAW]; ++ vin_dev->hw_ops->vin_isp_set_raw_addr(vin_dev, buffer->paddr[0]); ++ break; ++ case STF_ISP_PAD_SRC_SCD_Y: ++ buffer = &dummy_buffer->buffer[STF_ISP_PAD_SRC_SCD_Y]; ++ vin_dev->hw_ops->vin_isp_set_scd_addr(vin_dev, ++ buffer->paddr[0], buffer->paddr[1], AWB_TYPE); ++ break; ++ default: ++ break; ++ } ++} ++ ++static int vin_set_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct vin_line *line = v4l2_get_subdevdata(sd); ++ struct stf_vin2_dev *vin_dev = line_to_vin2_dev(line); ++ int dummy_module = line_to_dummy_module(line); ++ struct dummy_buffer *dummy_buffer = &vin_dev->dummy_buffer[dummy_module]; ++ struct v4l2_mbus_framefmt *fmt; ++ ++ st_debug(ST_VIN, "%s, %d\n", __func__, __LINE__); ++ fmt = __vin_get_format(line, NULL, STF_VIN_PAD_SINK, V4L2_SUBDEV_FORMAT_ACTIVE); ++ mutex_lock(&dummy_buffer->stream_lock); ++ if (enable) { ++ if (dummy_buffer->stream_count == 0) { ++ vin_alloc_dummy_buffer(vin_dev, fmt, dummy_module); ++ vin_set_dummy_buffer(line, STF_VIN_PAD_SINK); ++ atomic_set(&dummy_buffer->frame_skip, get_frame_skip(line)); ++ } ++ dummy_buffer->stream_count++; ++ } else { ++ if (dummy_buffer->stream_count == 1) { ++ vin_free_dummy_buffer(vin_dev, dummy_module); ++ // set buffer addr to zero ++ vin_set_dummy_buffer(line, STF_VIN_PAD_SINK); ++ } else ++ vin_set_dummy_buffer(line, ++ stf_vin_map_isp_pad(line->id, STF_ISP_PAD_SINK)); ++ ++ dummy_buffer->stream_count--; ++ } ++ mutex_unlock(&dummy_buffer->stream_lock); ++ ++ mutex_lock(&line->stream_lock); ++ if (enable) { ++ if (line->stream_count == 0) { ++ if (line->id == VIN_LINE_WR) { ++ vin_dev->hw_ops->vin_wr_irq_enable(vin_dev, 1); ++ vin_dev->hw_ops->vin_wr_stream_set(vin_dev, 1); ++ } ++ } ++ line->stream_count++; ++ } else { ++ if (line->stream_count == 1) { ++ if (line->id == VIN_LINE_WR) { ++ vin_dev->hw_ops->vin_wr_irq_enable(vin_dev, 0); ++ vin_dev->hw_ops->vin_wr_stream_set(vin_dev, 0); ++ } ++ } ++ line->stream_count--; ++ } ++ mutex_unlock(&line->stream_lock); ++ ++ if (enable) ++ vin_enable_output(line); ++ else ++ vin_disable_output(line); ++ ++ return 0; ++} ++ ++static struct v4l2_mbus_framefmt * ++__vin_get_format(struct vin_line *line, ++ struct v4l2_subdev_state *state, ++ unsigned int pad, ++ enum v4l2_subdev_format_whence which) ++{ ++ if (which == V4L2_SUBDEV_FORMAT_TRY) ++ return v4l2_subdev_get_try_format(&line->subdev, state, pad); ++ return &line->fmt[pad]; ++} ++ ++static void vin_try_format(struct vin_line *line, ++ struct v4l2_subdev_state *state, ++ unsigned int pad, ++ struct v4l2_mbus_framefmt *fmt, ++ enum v4l2_subdev_format_whence which) ++{ ++ unsigned int i; ++ ++ switch (pad) { ++ case STF_VIN_PAD_SINK: ++ /* Set format on sink pad */ ++ ++ for (i = 0; i < line->nformats; i++) ++ if (fmt->code == line->formats[i].code) ++ break; ++ ++ /* If not found, use UYVY as default */ ++ if (i >= line->nformats) ++ fmt->code = line->formats[0].code; ++ ++ fmt->width = clamp_t(u32, ++ fmt->width, STFCAMSS_FRAME_MIN_WIDTH, STFCAMSS_FRAME_MAX_WIDTH); ++ fmt->height = clamp_t(u32, ++ fmt->height, STFCAMSS_FRAME_MIN_HEIGHT, STFCAMSS_FRAME_MAX_HEIGHT); ++ ++ fmt->field = V4L2_FIELD_NONE; ++ fmt->colorspace = V4L2_COLORSPACE_SRGB; ++ fmt->flags = 0; ++ ++ break; ++ ++ case STF_VIN_PAD_SRC: ++ /* Set and return a format same as sink pad */ ++ *fmt = *__vin_get_format(line, state, STF_VIN_PAD_SINK, which); ++ break; ++ } ++ ++ fmt->colorspace = V4L2_COLORSPACE_SRGB; ++} ++ ++static int vin_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct vin_line *line = v4l2_get_subdevdata(sd); ++ ++ if (code->index >= line->nformats) ++ return -EINVAL; ++ if (code->pad == STF_VIN_PAD_SINK) { ++ code->code = line->formats[code->index].code; ++ } else { ++ struct v4l2_mbus_framefmt *sink_fmt; ++ ++ sink_fmt = __vin_get_format(line, state, STF_VIN_PAD_SINK, ++ code->which); ++ ++ code->code = sink_fmt->code; ++ if (!code->code) ++ return -EINVAL; ++ } ++ code->flags = 0; ++ ++ return 0; ++} ++ ++static int vin_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ struct vin_line *line = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt format; ++ ++ if (fse->index != 0) ++ return -EINVAL; ++ ++ format.code = fse->code; ++ format.width = 1; ++ format.height = 1; ++ vin_try_format(line, state, fse->pad, &format, fse->which); ++ fse->min_width = format.width; ++ fse->min_height = format.height; ++ ++ if (format.code != fse->code) ++ return -EINVAL; ++ ++ format.code = fse->code; ++ format.width = -1; ++ format.height = -1; ++ vin_try_format(line, state, fse->pad, &format, fse->which); ++ fse->max_width = format.width; ++ fse->max_height = format.height; ++ ++ return 0; ++} ++ ++static int vin_get_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct vin_line *line = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt *format; ++ ++ format = __vin_get_format(line, state, fmt->pad, fmt->which); ++ if (format == NULL) ++ return -EINVAL; ++ ++ fmt->format = *format; ++ ++ return 0; ++} ++ ++static int vin_set_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct vin_line *line = v4l2_get_subdevdata(sd); ++ struct v4l2_mbus_framefmt *format; ++ ++ st_debug(ST_VIDEO, "%s, pad %d, fmt code %x\n", ++ __func__, fmt->pad, fmt->format.code); ++ ++ format = __vin_get_format(line, state, fmt->pad, fmt->which); ++ if (format == NULL) ++ return -EINVAL; ++ ++ mutex_lock(&line->stream_lock); ++ if (line->stream_count) { ++ fmt->format = *format; ++ mutex_unlock(&line->stream_lock); ++ goto out; ++ } else { ++ vin_try_format(line, state, fmt->pad, &fmt->format, fmt->which); ++ *format = fmt->format; ++ } ++ mutex_unlock(&line->stream_lock); ++ ++ if (fmt->pad == STF_VIN_PAD_SINK) { ++ /* Propagate the format from sink to source */ ++ format = __vin_get_format(line, state, STF_VIN_PAD_SRC, ++ fmt->which); ++ ++ *format = fmt->format; ++ vin_try_format(line, state, STF_VIN_PAD_SRC, format, ++ fmt->which); ++ } ++ ++out: ++ return 0; ++} ++ ++static int vin_init_formats(struct v4l2_subdev *sd, ++ struct v4l2_subdev_fh *fh) ++{ ++ struct v4l2_subdev_format format = { ++ .pad = STF_VIN_PAD_SINK, ++ .which = fh ? V4L2_SUBDEV_FORMAT_TRY : ++ V4L2_SUBDEV_FORMAT_ACTIVE, ++ .format = { ++ .code = MEDIA_BUS_FMT_RGB565_2X8_LE, ++ .width = 1920, ++ .height = 1080 ++ } ++ }; ++ ++ return vin_set_format(sd, fh ? fh->state : NULL, &format); ++} ++ ++static void vin_output_init_addrs(struct vin_line *line) ++{ ++ struct vin_output *output = &line->output; ++ struct stf_vin2_dev *vin_dev = line_to_vin2_dev(line); ++ dma_addr_t ping_addr; ++ dma_addr_t pong_addr; ++ dma_addr_t y_addr, uv_addr; ++ ++ output->active_buf = 0; ++ ++ if (output->buf[0]) { ++ ping_addr = output->buf[0]->addr[0]; ++ y_addr = output->buf[0]->addr[0]; ++ uv_addr = output->buf[0]->addr[1]; ++ } else ++ return; ++ ++ if (output->buf[1]) ++ pong_addr = output->buf[1]->addr[0]; ++ else ++ pong_addr = ping_addr; ++ ++ switch (stf_vin_map_isp_line(line->id)) { ++ case STF_ISP_LINE_SRC: ++ vin_dev->hw_ops->vin_isp_set_yuv_addr(vin_dev, ++ y_addr, uv_addr); ++ break; ++ case STF_ISP_LINE_SRC_SS0: ++ vin_dev->hw_ops->vin_isp_set_ss0_addr(vin_dev, ++ y_addr, uv_addr); ++ break; ++ case STF_ISP_LINE_SRC_SS1: ++ vin_dev->hw_ops->vin_isp_set_ss1_addr(vin_dev, ++ y_addr, uv_addr); ++ break; ++ case STF_ISP_LINE_SRC_ITIW: ++ vin_dev->hw_ops->vin_isp_set_itiw_addr(vin_dev, ++ y_addr, uv_addr); ++ break; ++ case STF_ISP_LINE_SRC_ITIR: ++ vin_dev->hw_ops->vin_isp_set_itir_addr(vin_dev, ++ y_addr, uv_addr); ++ break; ++ case STF_ISP_LINE_SRC_RAW: ++ vin_dev->hw_ops->vin_isp_set_raw_addr(vin_dev, y_addr); ++ break; ++ case STF_ISP_LINE_SRC_SCD_Y: ++ output->frame_skip = ISP_AWB_OECF_SKIP_FRAME; ++ vin_dev->hw_ops->vin_isp_set_scd_addr(vin_dev, ++ y_addr, uv_addr, AWB_TYPE); ++ break; ++ default: ++ if (line->id == VIN_LINE_WR) { ++ vin_dev->hw_ops->vin_wr_set_ping_addr(vin_dev, ping_addr); ++#ifdef VIN_TWO_BUFFER ++ vin_dev->hw_ops->vin_wr_set_pong_addr(vin_dev, pong_addr); ++#else ++ vin_dev->hw_ops->vin_wr_set_pong_addr(vin_dev, ping_addr); ++#endif ++ } ++ break; ++ } ++} ++ ++static void vin_init_outputs(struct vin_line *line) ++{ ++ struct vin_output *output = &line->output; ++ ++ output->state = VIN_OUTPUT_OFF; ++ output->buf[0] = NULL; ++ output->buf[1] = NULL; ++ output->active_buf = 0; ++ INIT_LIST_HEAD(&output->pending_bufs); ++ INIT_LIST_HEAD(&output->ready_bufs); ++} ++ ++static void vin_buf_add_ready(struct vin_output *output, ++ struct stfcamss_buffer *buffer) ++{ ++ INIT_LIST_HEAD(&buffer->queue); ++ list_add_tail(&buffer->queue, &output->ready_bufs); ++} ++ ++static struct stfcamss_buffer *vin_buf_get_ready(struct vin_output *output) ++{ ++ struct stfcamss_buffer *buffer = NULL; ++ ++ if (!list_empty(&output->ready_bufs)) { ++ buffer = list_first_entry(&output->ready_bufs, ++ struct stfcamss_buffer, ++ queue); ++ list_del(&buffer->queue); ++ } ++ ++ return buffer; ++} ++ ++static void vin_buf_add_pending(struct vin_output *output, ++ struct stfcamss_buffer *buffer) ++{ ++ INIT_LIST_HEAD(&buffer->queue); ++ list_add_tail(&buffer->queue, &output->pending_bufs); ++} ++ ++static struct stfcamss_buffer *vin_buf_get_pending(struct vin_output *output) ++{ ++ struct stfcamss_buffer *buffer = NULL; ++ ++ if (!list_empty(&output->pending_bufs)) { ++ buffer = list_first_entry(&output->pending_bufs, ++ struct stfcamss_buffer, ++ queue); ++ list_del(&buffer->queue); ++ } ++ ++ return buffer; ++} ++ ++#ifdef UNUSED_CODE ++static void vin_output_checkpending(struct vin_line *line) ++{ ++ struct vin_output *output = &line->output; ++ ++ if (output->state == VIN_OUTPUT_STOPPING) { ++ /* Release last buffer when hw is idle */ ++ if (output->last_buffer) { ++ // vb2_buffer_done(&output->last_buffer->vb.vb2_buf, ++ // VB2_BUF_STATE_DONE); ++ vin_buf_add_pending(output, output->last_buffer); ++ output->last_buffer = NULL; ++ } ++ output->state = VIN_OUTPUT_IDLE; ++ ++ /* Buffers received in stopping state are queued in */ ++ /* dma pending queue, start next capture here */ ++ output->buf[0] = vin_buf_get_pending(output); ++#ifdef VIN_TWO_BUFFER ++ if (line->id == VIN_LINE_WR) ++ output->buf[1] = vin_buf_get_pending(output); ++#endif ++ ++ if (!output->buf[0] && output->buf[1]) { ++ output->buf[0] = output->buf[1]; ++ output->buf[1] = NULL; ++ } ++ ++ if (output->buf[0]) ++ output->state = VIN_OUTPUT_SINGLE; ++ ++#ifdef VIN_TWO_BUFFER ++ if (output->buf[1] && line->id == VIN_LINE_WR) ++ output->state = VIN_OUTPUT_CONTINUOUS; ++#endif ++ vin_output_init_addrs(line); ++ } ++} ++#endif ++ ++static void vin_buf_update_on_last(struct vin_line *line) ++{ ++ struct vin_output *output = &line->output; ++ ++ switch (output->state) { ++ case VIN_OUTPUT_CONTINUOUS: ++ output->state = VIN_OUTPUT_SINGLE; ++ output->active_buf = !output->active_buf; ++ break; ++ case VIN_OUTPUT_SINGLE: ++ output->state = VIN_OUTPUT_STOPPING; ++ break; ++ default: ++ st_err_ratelimited(ST_VIN, ++ "Last buff in wrong state! %d\n", ++ output->state); ++ break; ++ } ++} ++ ++static void vin_buf_update_on_next(struct vin_line *line) ++{ ++ struct vin_output *output = &line->output; ++ ++ switch (output->state) { ++ case VIN_OUTPUT_CONTINUOUS: ++ output->active_buf = !output->active_buf; ++ break; ++ case VIN_OUTPUT_SINGLE: ++ default: ++#ifdef VIN_TWO_BUFFER ++ if (line->id == VIN_LINE_WR) ++ st_err_ratelimited(ST_VIN, ++ "Next buf in wrong state! %d\n", ++ output->state); ++#endif ++ break; ++ } ++} ++ ++static void vin_buf_update_on_new(struct vin_line *line, ++ struct vin_output *output, ++ struct stfcamss_buffer *new_buf) ++{ ++#ifdef VIN_TWO_BUFFER ++ struct stf_vin2_dev *vin_dev = line_to_vin2_dev(line); ++ int inactive_idx; ++#endif ++ ++ switch (output->state) { ++ case VIN_OUTPUT_SINGLE: ++#ifdef VIN_TWO_BUFFER ++ int inactive_idx = !output->active_buf; ++ ++ if (!output->buf[inactive_idx] && line->id == VIN_LINE_WR) { ++ output->buf[inactive_idx] = new_buf; ++ if (inactive_idx) ++ vin_dev->hw_ops->vin_wr_set_pong_addr(vin_dev, ++ output->buf[1]->addr[0]); ++ else ++ vin_dev->hw_ops->vin_wr_set_ping_addr(vin_dev, ++ output->buf[0]->addr[0]); ++ output->state = VIN_OUTPUT_CONTINUOUS; ++ ++ } else { ++ vin_buf_add_pending(output, new_buf); ++ if (line->id == VIN_LINE_WR) ++ st_warn(ST_VIN, "Inactive buffer is busy\n"); ++ } ++#else ++ vin_buf_add_pending(output, new_buf); ++#endif ++ break; ++ case VIN_OUTPUT_IDLE: ++ st_warn(ST_VIN, "Output idle buffer set!\n"); ++ if (!output->buf[0]) { ++ output->buf[0] = new_buf; ++ vin_output_init_addrs(line); ++ output->state = VIN_OUTPUT_SINGLE; ++ } else { ++ vin_buf_add_pending(output, new_buf); ++ st_warn(ST_VIN, "Output idle with buffer set!\n"); ++ } ++ break; ++ case VIN_OUTPUT_STOPPING: ++ if (output->last_buffer) { ++ output->buf[output->active_buf] = output->last_buffer; ++ output->last_buffer = NULL; ++ } else ++ st_err(ST_VIN, "stop state lost lastbuffer!\n"); ++ output->state = VIN_OUTPUT_SINGLE; ++ // vin_output_checkpending(line); ++ vin_buf_add_pending(output, new_buf); ++ break; ++ case VIN_OUTPUT_CONTINUOUS: ++ default: ++ vin_buf_add_pending(output, new_buf); ++ break; ++ } ++} ++ ++static void vin_buf_flush(struct vin_output *output, ++ enum vb2_buffer_state state) ++{ ++ struct stfcamss_buffer *buf; ++ struct stfcamss_buffer *t; ++ ++ list_for_each_entry_safe(buf, t, &output->pending_bufs, queue) { ++ vb2_buffer_done(&buf->vb.vb2_buf, state); ++ list_del(&buf->queue); ++ } ++ list_for_each_entry_safe(buf, t, &output->ready_bufs, queue) { ++ vb2_buffer_done(&buf->vb.vb2_buf, state); ++ list_del(&buf->queue); ++ } ++} ++ ++static void vin_buffer_done(struct vin_line *line, struct vin_params *params) ++{ ++ struct stfcamss_buffer *ready_buf; ++ struct vin_output *output = &line->output; ++ unsigned long flags; ++ u64 ts = ktime_get_ns(); ++ struct v4l2_event event = { ++ .type = V4L2_EVENT_FRAME_SYNC, ++ }; ++ ++ if (output->state == VIN_OUTPUT_OFF ++ || output->state == VIN_OUTPUT_RESERVED) ++ return; ++ ++ spin_lock_irqsave(&line->output_lock, flags); ++ ++ while ((ready_buf = vin_buf_get_ready(output))) { ++ if (line->id >= VIN_LINE_ISP && line->id <= VIN_LINE_ISP_SS1) { ++ event.u.frame_sync.frame_sequence = output->sequence; ++ v4l2_event_queue(line->subdev.devnode, &event); ++ } ++ ++ ready_buf->vb.vb2_buf.timestamp = ts; ++ ready_buf->vb.sequence = output->sequence++; ++ ++ /* The stf_isp_ctrl currently buffered with mmap, ++ * which will not update cache by default. ++ * Flush L2 cache to make sure data is updated. ++ */ ++ if (ready_buf->vb.vb2_buf.memory == VB2_MEMORY_MMAP) ++ sifive_l2_flush64_range(ready_buf->addr[0], ready_buf->sizeimage); ++ ++ vb2_buffer_done(&ready_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); ++ } ++ ++ spin_unlock_irqrestore(&line->output_lock, flags); ++} ++ ++static void vin_change_buffer(struct vin_line *line) ++{ ++ struct stfcamss_buffer *ready_buf; ++ struct vin_output *output = &line->output; ++ struct stf_vin2_dev *vin_dev = line_to_vin2_dev(line); ++ dma_addr_t *new_addr; ++ unsigned long flags; ++ u32 active_index; ++ int scd_type; ++ ++ if (output->state == VIN_OUTPUT_OFF ++ || output->state == VIN_OUTPUT_STOPPING ++ || output->state == VIN_OUTPUT_RESERVED ++ || output->state == VIN_OUTPUT_IDLE) ++ return; ++ ++ spin_lock_irqsave(&line->output_lock, flags); ++ ++ active_index = output->active_buf; ++ ++ ready_buf = output->buf[active_index]; ++ if (!ready_buf) { ++ st_err_ratelimited(ST_VIN, ++ "Missing ready buf %d %d!\n", ++ active_index, output->state); ++ active_index = !active_index; ++ ready_buf = output->buf[active_index]; ++ if (!ready_buf) { ++ st_err_ratelimited(ST_VIN, ++ "Missing ready buf 2 %d %d!\n", ++ active_index, output->state); ++ goto out_unlock; ++ } ++ } ++ ++ /* Get next buffer */ ++ output->buf[active_index] = vin_buf_get_pending(output); ++ if (!output->buf[active_index]) { ++ /* No next buffer - set same address */ ++ new_addr = ready_buf->addr; ++ vin_buf_update_on_last(line); ++ } else { ++ new_addr = output->buf[active_index]->addr; ++ vin_buf_update_on_next(line); ++ } ++ ++ if (output->state == VIN_OUTPUT_STOPPING) ++ output->last_buffer = ready_buf; ++ else { ++ switch (stf_vin_map_isp_line(line->id)) { ++ case STF_ISP_LINE_SRC: ++ vin_dev->hw_ops->vin_isp_set_yuv_addr(vin_dev, ++ new_addr[0], new_addr[1]); ++ break; ++ case STF_ISP_LINE_SRC_SS0: ++ vin_dev->hw_ops->vin_isp_set_ss0_addr(vin_dev, ++ new_addr[0], new_addr[1]); ++ break; ++ case STF_ISP_LINE_SRC_SS1: ++ vin_dev->hw_ops->vin_isp_set_ss1_addr(vin_dev, ++ new_addr[0], new_addr[1]); ++ break; ++ case STF_ISP_LINE_SRC_ITIW: ++ vin_dev->hw_ops->vin_isp_set_itiw_addr(vin_dev, ++ new_addr[0], new_addr[1]); ++ break; ++ case STF_ISP_LINE_SRC_ITIR: ++ vin_dev->hw_ops->vin_isp_set_itir_addr(vin_dev, ++ new_addr[0], new_addr[1]); ++ break; ++ case STF_ISP_LINE_SRC_RAW: ++ vin_dev->hw_ops->vin_isp_set_raw_addr(vin_dev, new_addr[0]); ++ break; ++ case STF_ISP_LINE_SRC_SCD_Y: ++ scd_type = vin_dev->hw_ops->vin_isp_get_scd_type(vin_dev); ++ ready_buf->vb.flags &= ~(V4L2_BUF_FLAG_PFRAME | V4L2_BUF_FLAG_BFRAME); ++ if (scd_type == AWB_TYPE) ++ ready_buf->vb.flags |= V4L2_BUF_FLAG_PFRAME; ++ else ++ ready_buf->vb.flags |= V4L2_BUF_FLAG_BFRAME; ++ if (!output->frame_skip) { ++ output->frame_skip = ISP_AWB_OECF_SKIP_FRAME; ++ scd_type = scd_type == AWB_TYPE ? OECF_TYPE : AWB_TYPE; ++ } else { ++ output->frame_skip--; ++ scd_type = scd_type == AWB_TYPE ? AWB_TYPE : OECF_TYPE; ++ } ++ vin_dev->hw_ops->vin_isp_set_scd_addr(vin_dev, ++ new_addr[0], new_addr[1], scd_type); ++ break; ++ default: ++ if (line->id == VIN_LINE_WR) { ++#ifdef VIN_TWO_BUFFER ++ if (active_index) ++ vin_dev->hw_ops->vin_wr_set_pong_addr(vin_dev, ++ new_addr[0]); ++ else ++ vin_dev->hw_ops->vin_wr_set_ping_addr(vin_dev, ++ new_addr[0]); ++#else ++ vin_dev->hw_ops->vin_wr_set_ping_addr(vin_dev, ++ new_addr[0]); ++ vin_dev->hw_ops->vin_wr_set_pong_addr(vin_dev, ++ new_addr[0]); ++#endif ++ } ++ break; ++ } ++ ++ vin_buf_add_ready(output, ready_buf); ++ } ++ ++ spin_unlock_irqrestore(&line->output_lock, flags); ++ return; ++ ++out_unlock: ++ spin_unlock_irqrestore(&line->output_lock, flags); ++} ++ ++static int vin_queue_buffer(struct stfcamss_video *vid, ++ struct stfcamss_buffer *buf) ++{ ++ struct vin_line *line = container_of(vid, struct vin_line, video_out); ++ struct vin_output *output; ++ unsigned long flags; ++ ++ ++ output = &line->output; ++ ++ spin_lock_irqsave(&line->output_lock, flags); ++ ++ vin_buf_update_on_new(line, output, buf); ++ ++ spin_unlock_irqrestore(&line->output_lock, flags); ++ ++ return 0; ++} ++ ++static int vin_flush_buffers(struct stfcamss_video *vid, ++ enum vb2_buffer_state state) ++{ ++ struct vin_line *line = container_of(vid, struct vin_line, video_out); ++ struct vin_output *output = &line->output; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&line->output_lock, flags); ++ ++ vin_buf_flush(output, state); ++ if (output->buf[0]) ++ vb2_buffer_done(&output->buf[0]->vb.vb2_buf, state); ++ ++ if (output->buf[1]) ++ vb2_buffer_done(&output->buf[1]->vb.vb2_buf, state); ++ ++ if (output->last_buffer) { ++ vb2_buffer_done(&output->last_buffer->vb.vb2_buf, state); ++ output->last_buffer = NULL; ++ } ++ output->buf[0] = output->buf[1] = NULL; ++ ++ spin_unlock_irqrestore(&line->output_lock, flags); ++ return 0; ++} ++ ++static int vin_link_setup(struct media_entity *entity, ++ const struct media_pad *local, ++ const struct media_pad *remote, u32 flags) ++{ ++ if (flags & MEDIA_LNK_FL_ENABLED) ++ if (media_pad_remote_pad_first(local)) ++ return -EBUSY; ++ return 0; ++} ++ ++static int stf_vin_subscribe_event(struct v4l2_subdev *sd, ++ struct v4l2_fh *fh, ++ struct v4l2_event_subscription *sub) ++{ ++ switch (sub->type) { ++ case V4L2_EVENT_FRAME_SYNC: ++ return v4l2_event_subscribe(fh, sub, 0, NULL); ++ default: ++ st_debug(ST_VIN, "unsupport subscribe_event\n"); ++ return -EINVAL; ++ } ++} ++ ++static const struct v4l2_subdev_core_ops vin_core_ops = { ++ .s_power = vin_set_power, ++ .subscribe_event = stf_vin_subscribe_event, ++ .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++}; ++ ++static const struct v4l2_subdev_video_ops vin_video_ops = { ++ .s_stream = vin_set_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops vin_pad_ops = { ++ .enum_mbus_code = vin_enum_mbus_code, ++ .enum_frame_size = vin_enum_frame_size, ++ .get_fmt = vin_get_format, ++ .set_fmt = vin_set_format, ++}; ++ ++static const struct v4l2_subdev_ops vin_v4l2_ops = { ++ .core = &vin_core_ops, ++ .video = &vin_video_ops, ++ .pad = &vin_pad_ops, ++}; ++ ++static const struct v4l2_subdev_internal_ops vin_v4l2_internal_ops = { ++ .open = vin_init_formats, ++}; ++ ++static const struct stfcamss_video_ops stfcamss_vin_video_ops = { ++ .queue_buffer = vin_queue_buffer, ++ .flush_buffers = vin_flush_buffers, ++}; ++ ++static const struct media_entity_operations vin_media_ops = { ++ .link_setup = vin_link_setup, ++ .link_validate = v4l2_subdev_link_validate, ++}; ++ ++int stf_vin_register(struct stf_vin2_dev *vin_dev, struct v4l2_device *v4l2_dev) ++{ ++ struct v4l2_subdev *sd; ++ struct stfcamss_video *video_out; ++ struct media_pad *pads; ++ int ret; ++ int i; ++ ++ for (i = 0; i < STF_ISP_LINE_MAX + 1; i++) { ++ char name[32]; ++ char *sub_name = get_line_subdevname(i); ++ int is_mp; ++ ++#ifdef STF_CAMSS_SKIP_ITI ++ if ((stf_vin_map_isp_line(i) == STF_ISP_LINE_SRC_ITIW) || ++ (stf_vin_map_isp_line(i) == STF_ISP_LINE_SRC_ITIR)) ++ continue; ++#endif ++ is_mp = (stf_vin_map_isp_line(i) == STF_ISP_LINE_SRC) ? true : false; ++ is_mp = false; ++ sd = &vin_dev->line[i].subdev; ++ pads = vin_dev->line[i].pads; ++ video_out = &vin_dev->line[i].video_out; ++ video_out->id = i; ++ ++ v4l2_subdev_init(sd, &vin_v4l2_ops); ++ sd->internal_ops = &vin_v4l2_internal_ops; ++ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; ++ snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d_%s", ++ STF_VIN_NAME, 0, sub_name); ++ v4l2_set_subdevdata(sd, &vin_dev->line[i]); ++ ++ ret = vin_init_formats(sd, NULL); ++ if (ret < 0) { ++ st_err(ST_VIN, "Failed to init format: %d\n", ret); ++ goto err_init; ++ } ++ ++ pads[STF_VIN_PAD_SINK].flags = MEDIA_PAD_FL_SINK; ++ pads[STF_VIN_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; ++ ++ sd->entity.function = ++ MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; ++ sd->entity.ops = &vin_media_ops; ++ ret = media_entity_pads_init(&sd->entity, ++ STF_VIN_PADS_NUM, pads); ++ if (ret < 0) { ++ st_err(ST_VIN, "Failed to init media entity: %d\n", ret); ++ goto err_init; ++ } ++ ++ ret = v4l2_device_register_subdev(v4l2_dev, sd); ++ if (ret < 0) { ++ st_err(ST_VIN, "Failed to register subdev: %d\n", ret); ++ goto err_reg_subdev; ++ } ++ ++ video_out->ops = &stfcamss_vin_video_ops; ++ video_out->bpl_alignment = 16 * 8; ++ ++ snprintf(name, ARRAY_SIZE(name), "%s_%s%d", ++ sd->name, "video", i); ++ ret = stf_video_register(video_out, v4l2_dev, name, is_mp); ++ if (ret < 0) { ++ st_err(ST_VIN, "Failed to register video node: %d\n", ++ ret); ++ goto err_vid_reg; ++ } ++ ++ ret = media_create_pad_link( ++ &sd->entity, STF_VIN_PAD_SRC, ++ &video_out->vdev.entity, 0, ++ MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); ++ if (ret < 0) { ++ st_err(ST_VIN, "Failed to link %s->%s entities: %d\n", ++ sd->entity.name, video_out->vdev.entity.name, ++ ret); ++ goto err_create_link; ++ } ++ } ++ ++ return 0; ++ ++err_create_link: ++ stf_video_unregister(video_out); ++err_vid_reg: ++ v4l2_device_unregister_subdev(sd); ++err_reg_subdev: ++ media_entity_cleanup(&sd->entity); ++err_init: ++ for (i--; i >= 0; i--) { ++ sd = &vin_dev->line[i].subdev; ++ video_out = &vin_dev->line[i].video_out; ++ ++ stf_video_unregister(video_out); ++ v4l2_device_unregister_subdev(sd); ++ media_entity_cleanup(&sd->entity); ++ } ++ return ret; ++} ++ ++int stf_vin_unregister(struct stf_vin2_dev *vin_dev) ++{ ++ struct v4l2_subdev *sd; ++ struct stfcamss_video *video_out; ++ int i; ++ ++ mutex_destroy(&vin_dev->power_lock); ++ for (i = 0; i < STF_DUMMY_MODULE_NUMS; i++) ++ mutex_destroy(&vin_dev->dummy_buffer[i].stream_lock); ++ ++ for (i = 0; i < STF_ISP_LINE_MAX + 1; i++) { ++ sd = &vin_dev->line[i].subdev; ++ video_out = &vin_dev->line[i].video_out; ++ ++ stf_video_unregister(video_out); ++ v4l2_device_unregister_subdev(sd); ++ media_entity_cleanup(&sd->entity); ++ mutex_destroy(&vin_dev->line[i].stream_lock); ++ mutex_destroy(&vin_dev->line[i].power_lock); ++ } ++ return 0; ++} +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_vin.h +@@ -0,0 +1,182 @@ ++/* SPDX-License-Identifier: GPL-2.0 ++ * ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#ifndef STF_VIN_H ++#define STF_VIN_H ++ ++#include <media/v4l2-device.h> ++#include <media/v4l2-subdev.h> ++#include <linux/spinlock_types.h> ++#include <video/stf-vin.h> ++#include <linux/platform_device.h> ++ ++#include "stf_video.h" ++ ++#define STF_VIN_NAME "stf_vin" ++ ++#define STF_VIN_PAD_SINK 0 ++#define STF_VIN_PAD_SRC 1 ++#define STF_VIN_PADS_NUM 2 ++ ++struct vin2_format { ++ u32 code; ++ u8 bpp; ++}; ++ ++struct vin2_format_table { ++ const struct vin2_format *fmts; ++ int nfmts; ++}; ++ ++enum vin_output_state { ++ VIN_OUTPUT_OFF, ++ VIN_OUTPUT_RESERVED, ++ VIN_OUTPUT_SINGLE, ++ VIN_OUTPUT_CONTINUOUS, ++ VIN_OUTPUT_IDLE, ++ VIN_OUTPUT_STOPPING ++}; ++ ++struct vin_output { ++ int active_buf; ++ struct stfcamss_buffer *buf[2]; ++ struct stfcamss_buffer *last_buffer; ++ struct list_head pending_bufs; ++ struct list_head ready_bufs; ++ enum vin_output_state state; ++ unsigned int sequence; ++ unsigned int frame_skip; ++}; ++ ++/* The vin output lines include all isp controller lines, ++ * and one vin_wr output line. ++ */ ++enum vin_line_id { ++ VIN_LINE_NONE = -1, ++ VIN_LINE_WR = 0, ++ VIN_LINE_ISP = 1, ++ VIN_LINE_ISP_SS0 = 2, ++ VIN_LINE_ISP_SS1 = 3, ++ VIN_LINE_ISP_ITIW = 4, ++ VIN_LINE_ISP_ITIR = 5, ++ VIN_LINE_ISP_RAW = 6, ++ VIN_LINE_ISP_SCD_Y = 7, ++ VIN_LINE_MAX = 8, ++}; ++ ++enum subdev_type; ++ ++struct vin_line { ++ enum subdev_type sdev_type; // must be frist ++ enum vin_line_id id; ++ struct v4l2_subdev subdev; ++ struct media_pad pads[STF_VIN_PADS_NUM]; ++ struct v4l2_mbus_framefmt fmt[STF_VIN_PADS_NUM]; ++ struct stfcamss_video video_out; ++ struct mutex stream_lock; ++ int stream_count; ++ struct mutex power_lock; ++ int power_count; ++ struct vin_output output; ++ spinlock_t output_lock; ++ const struct vin2_format *formats; ++ unsigned int nformats; ++#ifdef CONFIG_PM_SLEEP ++ int pm_stream_count; ++ int pm_power_count; ++#endif ++}; ++ ++struct stf_vin2_dev; ++ ++struct vin_hw_ops { ++ int (*vin_clk_enable)(struct stf_vin2_dev *vin_dev); ++ int (*vin_clk_disable)(struct stf_vin2_dev *vin_dev); ++ int (*vin_config_set)(struct stf_vin2_dev *vin_dev); ++ int (*vin_wr_stream_set)(struct stf_vin2_dev *vin_dev, int on); ++ void (*vin_wr_irq_enable)(struct stf_vin2_dev *vin_dev, int enable); ++ void (*vin_power_on)(struct stf_vin2_dev *vin_dev, int on); ++ void (*wr_rd_set_addr)(struct stf_vin2_dev *vin_dev, ++ dma_addr_t wr_addr, dma_addr_t rd_addr); ++ void (*vin_wr_set_ping_addr)(struct stf_vin2_dev *vin_dev, ++ dma_addr_t addr); ++ void (*vin_wr_set_pong_addr)(struct stf_vin2_dev *vin_dev, ++ dma_addr_t addr); ++ void (*vin_wr_get_ping_pong_status)(struct stf_vin2_dev *vin_dev); ++ void (*vin_isp_set_yuv_addr)(struct stf_vin2_dev *vin_dev, ++ dma_addr_t y_addr, dma_addr_t uv_addr); ++ void (*vin_isp_set_raw_addr)(struct stf_vin2_dev *vin_dev, ++ dma_addr_t raw_addr); ++ void (*vin_isp_set_ss0_addr)(struct stf_vin2_dev *vin_dev, ++ dma_addr_t y_addr, dma_addr_t uv_addr); ++ void (*vin_isp_set_ss1_addr)(struct stf_vin2_dev *vin_dev, ++ dma_addr_t y_addr, dma_addr_t uv_addr); ++ void (*vin_isp_set_itiw_addr)(struct stf_vin2_dev *vin_dev, ++ dma_addr_t y_addr, dma_addr_t uv_addr); ++ void (*vin_isp_set_itir_addr)(struct stf_vin2_dev *vin_dev, ++ dma_addr_t y_addr, dma_addr_t uv_addr); ++ void (*vin_isp_set_scd_addr)(struct stf_vin2_dev *vin_dev, ++ dma_addr_t yhist_addr, ++ dma_addr_t scd_addr, int scd_type); ++ int (*vin_isp_get_scd_type)(struct stf_vin2_dev *vin_dev); ++ irqreturn_t (*vin_wr_irq_handler)(int irq, void *priv); ++ irqreturn_t (*vin_isp_irq_handler)(int irq, void *priv); ++ irqreturn_t (*vin_isp_csi_irq_handler)(int irq, void *priv); ++ irqreturn_t (*vin_isp_scd_irq_handler)(int irq, void *priv); ++ irqreturn_t (*vin_isp_irq_csiline_handler)(int irq, void *priv); ++ void (*isr_buffer_done)(struct vin_line *line, ++ struct vin_params *params); ++ void (*isr_change_buffer)(struct vin_line *line); ++}; ++ ++#define ISP_DUMMY_BUFFER_NUMS STF_ISP_PAD_MAX ++#define VIN_DUMMY_BUFFER_NUMS 1 ++ ++enum { ++ STF_DUMMY_VIN, ++ STF_DUMMY_ISP, ++ STF_DUMMY_MODULE_NUMS, ++}; ++ ++struct vin_dummy_buffer { ++ dma_addr_t paddr[3]; ++ void *vaddr; ++ u32 buffer_size; ++ u32 width; ++ u32 height; ++ u32 mcode; ++}; ++ ++struct dummy_buffer { ++ struct vin_dummy_buffer *buffer; ++ u32 nums; ++ struct mutex stream_lock; ++ int stream_count; ++ atomic_t frame_skip; ++}; ++ ++struct stf_vin2_dev { ++ struct stfcamss *stfcamss; ++ struct vin_line line[VIN_LINE_MAX]; ++ struct dummy_buffer dummy_buffer[STF_DUMMY_MODULE_NUMS]; ++ struct vin_hw_ops *hw_ops; ++ atomic_t ref_count; ++ struct mutex power_lock; ++ int power_count; ++}; ++ ++extern void sifive_l2_flush64_range(unsigned long start, unsigned long len); ++extern int stf_vin_subdev_init(struct stfcamss *stfcamss); ++extern int stf_vin_register(struct stf_vin2_dev *vin_dev, ++ struct v4l2_device *v4l2_dev); ++extern int stf_vin_unregister(struct stf_vin2_dev *vin_dev); ++ ++extern struct vin_hw_ops vin_ops; ++extern void dump_vin_reg(void *__iomem regbase); ++extern enum isp_pad_id stf_vin_map_isp_pad(enum vin_line_id line, ++ enum isp_pad_id def); ++ ++#endif /* STF_VIN_H */ +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_vin_hw_ops.c +@@ -0,0 +1,433 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include "stfcamss.h" ++#include <linux/of_graph.h> ++#include <media/v4l2-async.h> ++#include <media/v4l2-fwnode.h> ++#include <media/v4l2-subdev.h> ++ ++static void vin_intr_clear(void __iomem *sysctrl_base) ++{ ++ reg_set_bit(sysctrl_base, SYSCONSAIF_SYSCFG_28, ++ U0_VIN_CNFG_AXIWR0_INTR_CLEAN, ++ 0x1); ++ reg_set_bit(sysctrl_base, SYSCONSAIF_SYSCFG_28, ++ U0_VIN_CNFG_AXIWR0_INTR_CLEAN, ++ 0x0); ++} ++ ++static irqreturn_t stf_vin_wr_irq_handler(int irq, void *priv) ++{ ++ static struct vin_params params; ++ struct stf_vin2_dev *vin_dev = priv; ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ struct dummy_buffer *dummy_buffer = ++ &vin_dev->dummy_buffer[STF_DUMMY_VIN]; ++ ++ if (atomic_dec_if_positive(&dummy_buffer->frame_skip) < 0) { ++ vin_dev->hw_ops->isr_change_buffer(&vin_dev->line[VIN_LINE_WR]); ++ vin_dev->hw_ops->isr_buffer_done(&vin_dev->line[VIN_LINE_WR], ¶ms); ++ } ++ ++ vin_intr_clear(vin->sysctrl_base); ++ ++ return IRQ_HANDLED; ++} ++ ++static void __iomem *stf_vin_get_ispbase(struct stf_vin_dev *vin) ++{ ++ void __iomem *base = vin->isp_base; ++ ++ return base; ++} ++ ++static irqreturn_t stf_vin_isp_irq_handler(int irq, void *priv) ++{ ++ static struct vin_params params; ++ struct stf_vin2_dev *vin_dev = priv; ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ void __iomem *ispbase; ++ u32 int_status, value; ++ ++ ispbase = stf_vin_get_ispbase(vin); ++ ++ int_status = reg_read(ispbase, ISP_REG_ISP_CTRL_0); ++ ++ if (int_status & BIT(24)) { ++ if ((int_status & BIT(11))) ++ vin_dev->hw_ops->isr_buffer_done( ++ &vin_dev->line[VIN_LINE_ISP_SS0], ¶ms); ++ ++ if ((int_status & BIT(12))) ++ vin_dev->hw_ops->isr_buffer_done( ++ &vin_dev->line[VIN_LINE_ISP_SS1], ¶ms); ++ ++ if ((int_status & BIT(20))) ++ vin_dev->hw_ops->isr_buffer_done( ++ &vin_dev->line[VIN_LINE_ISP], ¶ms); ++ ++ value = reg_read(ispbase, ISP_REG_ITIDPSR); ++ if ((value & BIT(17))) ++ vin_dev->hw_ops->isr_buffer_done( ++ &vin_dev->line[VIN_LINE_ISP_ITIW], ¶ms); ++ if ((value & BIT(16))) ++ vin_dev->hw_ops->isr_buffer_done( ++ &vin_dev->line[VIN_LINE_ISP_ITIR], ¶ms); ++ ++#ifndef ISP_USE_CSI_AND_SC_DONE_INTERRUPT ++ if (int_status & BIT(25)) ++ vin_dev->hw_ops->isr_buffer_done( ++ &vin_dev->line[VIN_LINE_ISP_RAW], ¶ms); ++ ++ if (int_status & BIT(26)) ++ vin_dev->hw_ops->isr_buffer_done( ++ &vin_dev->line[VIN_LINE_ISP_SCD_Y], ¶ms); ++ ++ /* clear interrupt */ ++ reg_write(ispbase, ISP_REG_ISP_CTRL_0, (int_status & ~EN_INT_ALL) ++ | EN_INT_ISP_DONE | EN_INT_CSI_DONE | EN_INT_SC_DONE); ++#else ++ /* clear interrupt */ ++ reg_write(ispbase, ISP_REG_ISP_CTRL_0, ++ (int_status & ~EN_INT_ALL) | EN_INT_ISP_DONE); ++#endif ++ } else ++ st_debug(ST_VIN, "%s, Unknown interrupt!!!\n", __func__); ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t stf_vin_isp_csi_irq_handler(int irq, void *priv) ++{ ++ static struct vin_params params; ++ struct stf_vin2_dev *vin_dev = priv; ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ void __iomem *ispbase; ++ u32 int_status; ++ ++ ispbase = stf_vin_get_ispbase(vin); ++ ++ int_status = reg_read(ispbase, ISP_REG_ISP_CTRL_0); ++ ++ if (int_status & BIT(25)) { ++ vin_dev->hw_ops->isr_buffer_done( ++ &vin_dev->line[VIN_LINE_ISP_RAW], ¶ms); ++ ++ /* clear interrupt */ ++ reg_write(ispbase, ISP_REG_ISP_CTRL_0, ++ (int_status & ~EN_INT_ALL) | EN_INT_CSI_DONE); ++ } else ++ st_debug(ST_VIN, "%s, Unknown interrupt!!!\n", __func__); ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t stf_vin_isp_scd_irq_handler(int irq, void *priv) ++{ ++ static struct vin_params params; ++ struct stf_vin2_dev *vin_dev = priv; ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ void __iomem *ispbase; ++ u32 int_status; ++ ++ ispbase = stf_vin_get_ispbase(vin); ++ ++ int_status = reg_read(ispbase, ISP_REG_ISP_CTRL_0); ++ ++ if (int_status & BIT(26)) { ++ vin_dev->hw_ops->isr_buffer_done( ++ &vin_dev->line[VIN_LINE_ISP_SCD_Y], ¶ms); ++ ++ /* clear interrupt */ ++ reg_write(ispbase, ISP_REG_ISP_CTRL_0, (int_status & ~EN_INT_ALL) | EN_INT_SC_DONE); ++ } else ++ st_debug(ST_VIN, "%s, Unknown interrupt!!!\n", __func__); ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t stf_vin_isp_irq_csiline_handler(int irq, void *priv) ++{ ++ struct stf_vin2_dev *vin_dev = priv; ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ struct stf_isp_dev *isp_dev; ++ void __iomem *ispbase; ++ u32 int_status, value; ++ ++ ispbase = stf_vin_get_ispbase(vin); ++ ++ isp_dev = vin_dev->stfcamss->isp_dev; ++ ++ int_status = reg_read(ispbase, ISP_REG_ISP_CTRL_0); ++ if (int_status & BIT(27)) { ++ struct dummy_buffer *dummy_buffer = ++ &vin_dev->dummy_buffer[STF_DUMMY_ISP]; ++ ++ if (!atomic_read(&isp_dev->shadow_count)) { ++ if (atomic_dec_if_positive(&dummy_buffer->frame_skip) < 0) { ++ if ((int_status & BIT(11))) ++ vin_dev->hw_ops->isr_change_buffer( ++ &vin_dev->line[VIN_LINE_ISP_SS0]); ++ if ((int_status & BIT(12))) ++ vin_dev->hw_ops->isr_change_buffer( ++ &vin_dev->line[VIN_LINE_ISP_SS1]); ++ if ((int_status & BIT(20))) ++ vin_dev->hw_ops->isr_change_buffer( ++ &vin_dev->line[VIN_LINE_ISP]); ++ ++ value = reg_read(ispbase, ISP_REG_ITIDPSR); ++ if ((value & BIT(17))) ++ vin_dev->hw_ops->isr_change_buffer( ++ &vin_dev->line[VIN_LINE_ISP_ITIW]); ++ if ((value & BIT(16))) ++ vin_dev->hw_ops->isr_change_buffer( ++ &vin_dev->line[VIN_LINE_ISP_ITIR]); ++ ++ value = reg_read(ispbase, ISP_REG_CSI_MODULE_CFG); ++ if ((value & BIT(19))) ++ vin_dev->hw_ops->isr_change_buffer( ++ &vin_dev->line[VIN_LINE_ISP_RAW]); ++ if ((value & BIT(17))) ++ vin_dev->hw_ops->isr_change_buffer( ++ &vin_dev->line[VIN_LINE_ISP_SCD_Y]); ++ } ++ ++ // shadow update ++ reg_set_bit(ispbase, ISP_REG_CSIINTS_ADDR, 0x30000, 0x30000); ++ reg_set_bit(ispbase, ISP_REG_IESHD_ADDR, BIT(1) | BIT(0), 0x3); ++ } else { ++ st_warn(ST_VIN, "isp shadow_lock locked. skip this frame\n"); ++ } ++ ++ /* clear interrupt */ ++ reg_write(ispbase, ISP_REG_ISP_CTRL_0, ++ (int_status & ~EN_INT_ALL) | EN_INT_LINE_INT); ++ } else ++ st_debug(ST_VIN, "%s, Unknown interrupt!!!\n", __func__); ++ ++ return IRQ_HANDLED; ++} ++ ++static int stf_vin_clk_enable(struct stf_vin2_dev *vin_dev) ++{ ++ struct stfcamss *stfcamss = vin_dev->stfcamss; ++ ++ clk_prepare_enable(stfcamss->sys_clk[STFCLK_PCLK].clk); ++ clk_set_rate(stfcamss->sys_clk[STFCLK_APB_FUNC].clk, 49500000); ++ clk_set_rate(stfcamss->sys_clk[STFCLK_SYS_CLK].clk, 297000000); ++ ++ reset_control_deassert(stfcamss->sys_rst[STFRST_PCLK].rstc); ++ reset_control_deassert(stfcamss->sys_rst[STFRST_SYS_CLK].rstc); ++ ++ return 0; ++} ++ ++ ++static int stf_vin_clk_disable(struct stf_vin2_dev *vin_dev) ++{ ++ struct stfcamss *stfcamss = vin_dev->stfcamss; ++ ++ reset_control_assert(stfcamss->sys_rst[STFRST_PCLK].rstc); ++ reset_control_assert(stfcamss->sys_rst[STFRST_SYS_CLK].rstc); ++ ++ clk_disable_unprepare(stfcamss->sys_clk[STFCLK_PCLK].clk); ++ ++ return 0; ++} ++ ++static int stf_vin_config_set(struct stf_vin2_dev *vin_dev) ++{ ++ return 0; ++} ++ ++static int stf_vin_wr_stream_set(struct stf_vin2_dev *vin_dev, int on) ++{ ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ ++ //make the axiwr alway on ++ if (on) ++ reg_set(vin->sysctrl_base, SYSCONSAIF_SYSCFG_20, U0_VIN_CNFG_AXIWR0_EN); ++ ++ return 0; ++} ++ ++static void stf_vin_wr_irq_enable(struct stf_vin2_dev *vin_dev, ++ int enable) ++{ ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ unsigned int value = 0; ++ ++ if (enable) { ++ value = ~(0x1 << 1); ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_28, ++ U0_VIN_CNFG_AXIWR0_MASK, ++ value); ++ } else { ++ /* clear vin interrupt */ ++ value = 0x1 << 1; ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_28, ++ U0_VIN_CNFG_AXIWR0_INTR_CLEAN, ++ 0x1); ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_28, ++ U0_VIN_CNFG_AXIWR0_INTR_CLEAN, ++ 0x0); ++ reg_set_bit(vin->sysctrl_base, SYSCONSAIF_SYSCFG_28, ++ U0_VIN_CNFG_AXIWR0_MASK, ++ value); ++ } ++} ++ ++static void stf_vin_wr_rd_set_addr(struct stf_vin2_dev *vin_dev, ++ dma_addr_t wr_addr, dma_addr_t rd_addr) ++{ ++#ifdef UNUSED_CODE ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ ++ /* set the start address*/ ++ reg_write(vin->sysctrl_base, ++ SYSCTRL_VIN_WR_START_ADDR, (long)wr_addr); ++ reg_write(vin->sysctrl_base, ++ SYSCTRL_VIN_RD_END_ADDR, (long)rd_addr); ++#endif ++} ++ ++void stf_vin_wr_set_ping_addr(struct stf_vin2_dev *vin_dev, ++ dma_addr_t addr) ++{ ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ ++ /* set the start address */ ++ reg_write(vin->sysctrl_base, SYSCONSAIF_SYSCFG_24, (long)addr); ++} ++ ++void stf_vin_wr_set_pong_addr(struct stf_vin2_dev *vin_dev, dma_addr_t addr) ++{ ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ ++ /* set the start address */ ++ reg_write(vin->sysctrl_base, SYSCONSAIF_SYSCFG_32, (long)addr); ++} ++ ++void stf_vin_isp_set_yuv_addr(struct stf_vin2_dev *vin_dev, ++ dma_addr_t y_addr, dma_addr_t uv_addr) ++{ ++ ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ void __iomem *ispbase = stf_vin_get_ispbase(vin); ++ ++ reg_write(ispbase, ISP_REG_Y_PLANE_START_ADDR, y_addr); ++ reg_write(ispbase, ISP_REG_UV_PLANE_START_ADDR, uv_addr); ++ // reg_set_bit(ispbase, ISP_REG_ISP_CTRL_0, BIT(0), 1); ++} ++ ++void stf_vin_isp_set_raw_addr(struct stf_vin2_dev *vin_dev, ++ dma_addr_t raw_addr) ++{ ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ void __iomem *ispbase = stf_vin_get_ispbase(vin); ++ ++ reg_write(ispbase, ISP_REG_DUMP_CFG_0, raw_addr); ++} ++ ++void stf_vin_isp_set_ss0_addr(struct stf_vin2_dev *vin_dev, ++ dma_addr_t y_addr, dma_addr_t uv_addr) ++{ ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ void __iomem *ispbase = stf_vin_get_ispbase(vin); ++ ++ reg_write(ispbase, ISP_REG_SS0AY, y_addr); ++ reg_write(ispbase, ISP_REG_SS0AUV, uv_addr); ++} ++ ++void stf_vin_isp_set_ss1_addr(struct stf_vin2_dev *vin_dev, ++ dma_addr_t y_addr, dma_addr_t uv_addr) ++{ ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ void __iomem *ispbase = stf_vin_get_ispbase(vin); ++ ++ reg_write(ispbase, ISP_REG_SS1AY, y_addr); ++ reg_write(ispbase, ISP_REG_SS1AUV, uv_addr); ++} ++ ++void stf_vin_isp_set_itiw_addr(struct stf_vin2_dev *vin_dev, ++ dma_addr_t y_addr, dma_addr_t uv_addr) ++{ ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ void __iomem *ispbase = stf_vin_get_ispbase(vin); ++ ++ reg_write(ispbase, ISP_REG_ITIDWYSAR, y_addr); ++ reg_write(ispbase, ISP_REG_ITIDWUSAR, uv_addr); ++} ++ ++void stf_vin_isp_set_itir_addr(struct stf_vin2_dev *vin_dev, ++ dma_addr_t y_addr, dma_addr_t uv_addr) ++{ ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ void __iomem *ispbase = stf_vin_get_ispbase(vin); ++ ++ reg_write(ispbase, ISP_REG_ITIDRYSAR, y_addr); ++ reg_write(ispbase, ISP_REG_ITIDRUSAR, uv_addr); ++} ++ ++int stf_vin_isp_get_scd_type(struct stf_vin2_dev *vin_dev) ++{ ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ void __iomem *ispbase = stf_vin_get_ispbase(vin); ++ ++ return (reg_read(ispbase, ISP_REG_SC_CFG_1) & (0x3 << 30)) >> 30; ++} ++ ++void stf_vin_isp_set_scd_addr(struct stf_vin2_dev *vin_dev, ++ dma_addr_t yhist_addr, dma_addr_t scd_addr, int scd_type) ++{ ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ void __iomem *ispbase = stf_vin_get_ispbase(vin); ++ ++ reg_set_bit(ispbase, ISP_REG_SC_CFG_1, 0x3 << 30, scd_type << 30); ++ reg_write(ispbase, ISP_REG_SCD_CFG_0, scd_addr); ++ reg_write(ispbase, ISP_REG_YHIST_CFG_4, yhist_addr); ++} ++ ++void dump_vin_reg(void *__iomem regbase) ++{ ++ st_debug(ST_VIN, "DUMP VIN register:\n"); ++ print_reg(ST_VIN, regbase, 0x00); ++ print_reg(ST_VIN, regbase, 0x04); ++ print_reg(ST_VIN, regbase, 0x08); ++ print_reg(ST_VIN, regbase, 0x0c); ++ print_reg(ST_VIN, regbase, 0x10); ++ print_reg(ST_VIN, regbase, 0x14); ++ print_reg(ST_VIN, regbase, 0x18); ++ print_reg(ST_VIN, regbase, 0x1c); ++ print_reg(ST_VIN, regbase, 0x20); ++ print_reg(ST_VIN, regbase, 0x24); ++ print_reg(ST_VIN, regbase, 0x28); ++} ++ ++struct vin_hw_ops vin_ops = { ++ .vin_clk_enable = stf_vin_clk_enable, ++ .vin_clk_disable = stf_vin_clk_disable, ++ .vin_config_set = stf_vin_config_set, ++ .vin_wr_stream_set = stf_vin_wr_stream_set, ++ .vin_wr_irq_enable = stf_vin_wr_irq_enable, ++ .wr_rd_set_addr = stf_vin_wr_rd_set_addr, ++ .vin_wr_set_ping_addr = stf_vin_wr_set_ping_addr, ++ .vin_wr_set_pong_addr = stf_vin_wr_set_pong_addr, ++ .vin_isp_set_yuv_addr = stf_vin_isp_set_yuv_addr, ++ .vin_isp_set_raw_addr = stf_vin_isp_set_raw_addr, ++ .vin_isp_set_ss0_addr = stf_vin_isp_set_ss0_addr, ++ .vin_isp_set_ss1_addr = stf_vin_isp_set_ss1_addr, ++ .vin_isp_set_itiw_addr = stf_vin_isp_set_itiw_addr, ++ .vin_isp_set_itir_addr = stf_vin_isp_set_itir_addr, ++ .vin_isp_set_scd_addr = stf_vin_isp_set_scd_addr, ++ .vin_isp_get_scd_type = stf_vin_isp_get_scd_type, ++ .vin_wr_irq_handler = stf_vin_wr_irq_handler, ++ .vin_isp_irq_handler = stf_vin_isp_irq_handler, ++ .vin_isp_csi_irq_handler = stf_vin_isp_csi_irq_handler, ++ .vin_isp_scd_irq_handler = stf_vin_isp_scd_irq_handler, ++ .vin_isp_irq_csiline_handler = stf_vin_isp_irq_csiline_handler, ++}; +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stfcamss.c +@@ -0,0 +1,1369 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#include <linux/completion.h> ++#include <linux/delay.h> ++#include <linux/dmaengine.h> ++#include <linux/init.h> ++#include <linux/interrupt.h> ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/of_device.h> ++#include <linux/of_reserved_mem.h> ++#include <linux/of_graph.h> ++#include <linux/of_address.h> ++#include <linux/pinctrl/consumer.h> ++#include <linux/platform_device.h> ++#include <linux/pm_runtime.h> ++#include <linux/io.h> ++#include <linux/dma-mapping.h> ++#include <linux/uaccess.h> ++#include <linux/mfd/syscon.h> ++ ++#include <linux/videodev2.h> ++ ++#include <media/media-device.h> ++#include <media/v4l2-async.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-mc.h> ++#include <media/v4l2-fwnode.h> ++#include <linux/debugfs.h> ++ ++#include "stfcamss.h" ++ ++#ifdef STF_DEBUG ++unsigned int stdbg_level = ST_DEBUG; ++unsigned int stdbg_mask = 0x7F; ++#else ++unsigned int stdbg_level = ST_ERR; ++unsigned int stdbg_mask = 0x7F; ++#endif ++EXPORT_SYMBOL_GPL(stdbg_level); ++EXPORT_SYMBOL_GPL(stdbg_mask); ++ ++static const struct reg_name mem_reg_name[] = { ++ {"csi2rx"}, ++ {"vclk"}, ++ {"vrst"}, ++ {"sctrl"}, ++ {"isp"}, ++ {"trst"}, ++ {"pmu"}, ++ {"syscrg"}, ++}; ++ ++static struct clk_bulk_data stfcamss_clocks[] = { ++ { .id = "clk_apb_func" }, ++ { .id = "clk_pclk" }, ++ { .id = "clk_sys_clk" }, ++ { .id = "clk_wrapper_clk_c" }, ++ { .id = "clk_dvp_inv" }, ++ { .id = "clk_axiwr" }, ++ { .id = "clk_mipi_rx0_pxl" }, ++ { .id = "clk_pixel_clk_if0" }, ++ { .id = "clk_pixel_clk_if1" }, ++ { .id = "clk_pixel_clk_if2" }, ++ { .id = "clk_pixel_clk_if3" }, ++ { .id = "clk_m31dphy_cfgclk_in" }, ++ { .id = "clk_m31dphy_refclk_in" }, ++ { .id = "clk_m31dphy_txclkesc_lan0" }, ++ { .id = "clk_ispcore_2x" }, ++ { .id = "clk_isp_axi" }, ++}; ++ ++static struct reset_control_bulk_data stfcamss_resets[] = { ++ { .id = "rst_wrapper_p" }, ++ { .id = "rst_wrapper_c" }, ++ { .id = "rst_pclk" }, ++ { .id = "rst_sys_clk" }, ++ { .id = "rst_axird" }, ++ { .id = "rst_axiwr" }, ++ { .id = "rst_pixel_clk_if0" }, ++ { .id = "rst_pixel_clk_if1" }, ++ { .id = "rst_pixel_clk_if2" }, ++ { .id = "rst_pixel_clk_if3" }, ++ { .id = "rst_m31dphy_hw" }, ++ { .id = "rst_m31dphy_b09_always_on" }, ++ { .id = "rst_isp_top_n" }, ++ { .id = "rst_isp_top_axi" }, ++}; ++ ++int stfcamss_get_mem_res(struct platform_device *pdev, struct stf_vin_dev *vin) ++{ ++ struct device *dev = &pdev->dev; ++ struct resource *res; ++ char *name; ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(mem_reg_name); i++) { ++ name = (char *)(&mem_reg_name[i]); ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); ++ ++ if (!res) ++ return -EINVAL; ++ ++ if (!strcmp(name, "csi2rx")) { ++ vin->csi2rx_base = devm_ioremap_resource(dev, res); ++ if (IS_ERR(vin->csi2rx_base)) ++ return PTR_ERR(vin->csi2rx_base); ++ } else if (!strcmp(name, "vclk")) { ++ vin->clkgen_base = ioremap(res->start, resource_size(res)); ++ if (!vin->clkgen_base) ++ return -ENOMEM; ++ } else if (!strcmp(name, "vrst")) { ++ vin->rstgen_base = devm_ioremap_resource(dev, res); ++ if (IS_ERR(vin->rstgen_base)) ++ return PTR_ERR(vin->rstgen_base); ++ } else if (!strcmp(name, "sctrl")) { ++ vin->sysctrl_base = devm_ioremap_resource(dev, res); ++ if (IS_ERR(vin->sysctrl_base)) ++ return PTR_ERR(vin->sysctrl_base); ++ } else if (!strcmp(name, "isp")) { ++ vin->isp_base = devm_ioremap_resource(dev, res); ++ if (IS_ERR(vin->isp_base)) ++ return PTR_ERR(vin->isp_base); ++ } else if (!strcmp(name, "trst")) { ++ vin->vin_top_rstgen_base = devm_ioremap_resource(dev, res); ++ if (IS_ERR(vin->vin_top_rstgen_base)) ++ return PTR_ERR(vin->vin_top_rstgen_base); ++ } else if (!strcmp(name, "pmu")) { ++ vin->pmu_test = ioremap(res->start, resource_size(res)); ++ if (!vin->pmu_test) ++ return -ENOMEM; ++ } else if (!strcmp(name, "syscrg")) { ++ vin->sys_crg = ioremap(res->start, resource_size(res)); ++ if (!vin->sys_crg) ++ return -ENOMEM; ++ } else { ++ st_err(ST_CAMSS, "Could not match resource name\n"); ++ } ++ } ++ ++ return 0; ++} ++ ++int vin_parse_dt(struct device *dev, struct stf_vin_dev *vin) ++{ ++ int ret = 0; ++ struct device_node *np = dev->of_node; ++ ++ if (!np) ++ return -EINVAL; ++ ++ return ret; ++} ++ ++struct media_entity *stfcamss_find_sensor(struct media_entity *entity) ++{ ++ struct media_pad *pad; ++ ++ while (1) { ++ if (!entity->pads) ++ return NULL; ++ ++ pad = &entity->pads[0]; ++ if (!(pad->flags & MEDIA_PAD_FL_SINK)) ++ return NULL; ++ ++ pad = media_pad_remote_pad_first(pad); ++ if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) ++ return NULL; ++ ++ entity = pad->entity; ++ ++ if (entity->function == MEDIA_ENT_F_CAM_SENSOR) ++ return entity; ++ } ++} ++ ++static int stfcamss_of_parse_endpoint_node(struct device *dev, ++ struct device_node *node, ++ struct stfcamss_async_subdev *csd) ++{ ++ struct v4l2_fwnode_endpoint vep = { { 0 } }; ++ struct v4l2_mbus_config_parallel *parallel_bus = &vep.bus.parallel; ++ struct v4l2_mbus_config_mipi_csi2 *csi2_bus = &vep.bus.mipi_csi2; ++ struct dvp_cfg *dvp = &csd->interface.dvp; ++ struct csi2phy_cfg *csiphy = &csd->interface.csiphy; ++ ++ v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &vep); ++ st_debug(ST_CAMSS, "%s: vep.base.port = 0x%x, id = 0x%x\n", ++ __func__, vep.base.port, vep.base.id); ++ ++ csd->port = vep.base.port; ++ switch (csd->port) { ++ case DVP_SENSOR_PORT_NUMBER: ++ st_debug(ST_CAMSS, "%s, flags = 0x%x\n", __func__, ++ parallel_bus->flags); ++ dvp->flags = parallel_bus->flags; ++ dvp->bus_width = parallel_bus->bus_width; ++ dvp->data_shift = parallel_bus->data_shift; ++ break; ++ case CSI2RX_SENSOR_PORT_NUMBER: ++ st_debug(ST_CAMSS, "%s, CSI2 flags = 0x%x\n", ++ __func__, parallel_bus->flags); ++ csiphy->flags = csi2_bus->flags; ++ memcpy(csiphy->data_lanes, ++ csi2_bus->data_lanes, csi2_bus->num_data_lanes); ++ csiphy->clock_lane = csi2_bus->clock_lane; ++ csiphy->num_data_lanes = csi2_bus->num_data_lanes; ++ memcpy(csiphy->lane_polarities, ++ csi2_bus->lane_polarities, ++ csi2_bus->num_data_lanes + 1); ++ break; ++ default: ++ break; ++ }; ++ ++ return 0; ++} ++ ++static int stfcamss_of_parse_ports(struct stfcamss *stfcamss) ++{ ++ struct device *dev = stfcamss->dev; ++ struct device_node *node = NULL; ++ struct device_node *remote = NULL; ++ int ret, num_subdevs = 0; ++ ++ for_each_endpoint_of_node(dev->of_node, node) { ++ struct stfcamss_async_subdev *csd; ++ ++ if (!of_device_is_available(node)) ++ continue; ++ ++ remote = of_graph_get_remote_port_parent(node); ++ if (!remote) { ++ st_err(ST_CAMSS, "Cannot get remote parent\n"); ++ ret = -EINVAL; ++ goto err_cleanup; ++ } ++ ++ csd = v4l2_async_nf_add_fwnode(&stfcamss->notifier, ++ of_fwnode_handle(remote), ++ struct stfcamss_async_subdev); ++ of_node_put(remote); ++ if (IS_ERR(csd)) { ++ ret = PTR_ERR(csd); ++ goto err_cleanup; ++ } ++ ++ ret = stfcamss_of_parse_endpoint_node(dev, node, csd); ++ if (ret < 0) ++ goto err_cleanup; ++ ++ num_subdevs++; ++ } ++ ++ return num_subdevs; ++ ++err_cleanup: ++ of_node_put(node); ++ return ret; ++} ++ ++static int stfcamss_init_subdevices(struct stfcamss *stfcamss) ++{ ++ int ret; ++ ++ ret = stf_dvp_subdev_init(stfcamss); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to init stf_dvp sub-device: %d\n", ++ ret); ++ return ret; ++ } ++ ++ ret = stf_csiphy_subdev_init(stfcamss); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to init stf_csiphy sub-device: %d\n", ++ ret); ++ return ret; ++ } ++ ++ ret = stf_csi_subdev_init(stfcamss); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to init stf_csi sub-device: %d\n", ++ ret); ++ return ret; ++ } ++ ++ ret = stf_isp_subdev_init(stfcamss); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to init stf_isp sub-device: %d\n", ++ ret); ++ return ret; ++ } ++ ++ ret = stf_vin_subdev_init(stfcamss); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to init stf_vin sub-device: %d\n", ++ ret); ++ return ret; ++ } ++ return ret; ++} ++ ++static int stfcamss_register_subdevices(struct stfcamss *stfcamss) ++{ ++ int ret; ++ struct stf_vin2_dev *vin_dev = stfcamss->vin_dev; ++ struct stf_dvp_dev *dvp_dev = stfcamss->dvp_dev; ++ struct stf_csiphy_dev *csiphy_dev = stfcamss->csiphy_dev; ++ struct stf_csi_dev *csi_dev = stfcamss->csi_dev; ++ struct stf_isp_dev *isp_dev = stfcamss->isp_dev; ++ ++ ret = stf_dvp_register(dvp_dev, &stfcamss->v4l2_dev); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to register stf dvp%d entity: %d\n", ++ 0, ret); ++ goto err_reg_dvp; ++ } ++ ++ ret = stf_csiphy_register(csiphy_dev, &stfcamss->v4l2_dev); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to register stf csiphy%d entity: %d\n", ++ 0, ret); ++ goto err_reg_csiphy; ++ } ++ ++ ret = stf_csi_register(csi_dev, &stfcamss->v4l2_dev); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to register stf csi%d entity: %d\n", ++ 0, ret); ++ goto err_reg_csi; ++ } ++ ++ ret = stf_isp_register(isp_dev, &stfcamss->v4l2_dev); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to register stf isp%d entity: %d\n", ++ 0, ret); ++ goto err_reg_isp; ++ } ++ ++ ret = stf_vin_register(vin_dev, &stfcamss->v4l2_dev); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to register vin entity: %d\n", ++ ret); ++ goto err_reg_vin; ++ } ++ ++ ret = media_create_pad_link( ++ &dvp_dev->subdev.entity, ++ STF_DVP_PAD_SRC, ++ &vin_dev->line[VIN_LINE_WR].subdev.entity, ++ STF_VIN_PAD_SINK, ++ 0); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->vin entities: %d\n", ++ dvp_dev->subdev.entity.name, ++ ret); ++ goto err_link; ++ } ++ ++ ret = media_create_pad_link( ++ &csi_dev->subdev.entity, ++ STF_CSI_PAD_SRC, ++ &vin_dev->line[VIN_LINE_WR].subdev.entity, ++ STF_VIN_PAD_SINK, ++ 0); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->vin entities: %d\n", ++ csi_dev->subdev.entity.name, ++ ret); ++ goto err_link; ++ } ++ ++ ret = media_create_pad_link( ++ &csiphy_dev->subdev.entity, ++ STF_CSIPHY_PAD_SRC, ++ &csi_dev->subdev.entity, ++ STF_CSI_PAD_SINK, ++ MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->%s entities: %d\n", ++ csiphy_dev->subdev.entity.name, ++ csi_dev->subdev.entity.name, ++ ret); ++ goto err_link; ++ } ++ ++ ret = media_create_pad_link( ++ &isp_dev->subdev.entity, ++ STF_ISP_PAD_SRC, ++ &vin_dev->line[VIN_LINE_ISP].subdev.entity, ++ STF_VIN_PAD_SINK, ++ 0); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->%s entities: %d\n", ++ isp_dev->subdev.entity.name, ++ vin_dev->line[VIN_LINE_ISP] ++ .subdev.entity.name, ++ ret); ++ goto err_link; ++ } ++ ++ ret = media_create_pad_link( ++ &isp_dev->subdev.entity, ++ STF_ISP_PAD_SRC_SS0, ++ &vin_dev->line[VIN_LINE_ISP_SS0].subdev.entity, ++ STF_VIN_PAD_SINK, ++ 0); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->%s entities: %d\n", ++ isp_dev->subdev.entity.name, ++ vin_dev->line[VIN_LINE_ISP_SS0] ++ .subdev.entity.name, ++ ret); ++ goto err_link; ++ } ++ ++ ret = media_create_pad_link( ++ &isp_dev->subdev.entity, ++ STF_ISP_PAD_SRC_SS1, ++ &vin_dev->line[VIN_LINE_ISP_SS1].subdev.entity, ++ STF_VIN_PAD_SINK, ++ 0); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->%s entities: %d\n", ++ isp_dev->subdev.entity.name, ++ vin_dev->line[VIN_LINE_ISP_SS1] ++ .subdev.entity.name, ++ ret); ++ goto err_link; ++ } ++ ++#ifndef STF_CAMSS_SKIP_ITI ++ ret = media_create_pad_link( ++ &isp_dev->subdev.entity, ++ STF_ISP_PAD_SRC_ITIW, ++ &vin_dev->line[VIN_LINE_ISP_ITIW].subdev.entity, ++ STF_VIN_PAD_SINK, ++ 0); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->%s entities: %d\n", ++ isp_dev->subdev.entity.name, ++ vin_dev->line[VIN_LINE_ISP_ITIW] ++ .subdev.entity.name, ++ ret); ++ goto err_link; ++ } ++ ++ ret = media_create_pad_link( ++ &isp_dev->subdev.entity, ++ STF_ISP_PAD_SRC_ITIR, ++ &vin_dev->line[VIN_LINE_ISP_ITIR].subdev.entity, ++ STF_VIN_PAD_SINK, ++ 0); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->%s entities: %d\n", ++ isp_dev->subdev.entity.name, ++ vin_dev->line[VIN_LINE_ISP_ITIR] ++ .subdev.entity.name, ++ ret); ++ goto err_link; ++ } ++#endif ++ ++ ret = media_create_pad_link( ++ &isp_dev->subdev.entity, ++ STF_ISP_PAD_SRC_RAW, ++ &vin_dev->line[VIN_LINE_ISP_RAW].subdev.entity, ++ STF_VIN_PAD_SINK, ++ 0); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->%s entities: %d\n", ++ isp_dev->subdev.entity.name, ++ vin_dev->line[VIN_LINE_ISP_RAW] ++ .subdev.entity.name, ++ ret); ++ goto err_link; ++ } ++ ++ ret = media_create_pad_link( ++ &isp_dev->subdev.entity, ++ STF_ISP_PAD_SRC_SCD_Y, ++ &vin_dev->line[VIN_LINE_ISP_SCD_Y].subdev.entity, ++ STF_VIN_PAD_SINK, ++ 0); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->%s entities: %d\n", ++ isp_dev->subdev.entity.name, ++ vin_dev->line[VIN_LINE_ISP_SCD_Y] ++ .subdev.entity.name, ++ ret); ++ goto err_link; ++ } ++ ++ ret = media_create_pad_link( ++ &dvp_dev->subdev.entity, ++ STF_DVP_PAD_SRC, ++ &isp_dev->subdev.entity, ++ STF_ISP_PAD_SINK, ++ 0); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->%s entities: %d\n", ++ dvp_dev->subdev.entity.name, ++ isp_dev->subdev.entity.name, ++ ret); ++ goto err_link; ++ } ++ ++ ret = media_create_pad_link( ++ &csi_dev->subdev.entity, ++ STF_CSI_PAD_SRC, ++ &isp_dev->subdev.entity, ++ STF_ISP_PAD_SINK, ++ 0); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->%s entities: %d\n", ++ csi_dev->subdev.entity.name, ++ isp_dev->subdev.entity.name, ++ ret); ++ goto err_link; ++ } ++ ++ return ret; ++ ++err_link: ++ stf_vin_unregister(stfcamss->vin_dev); ++err_reg_vin: ++ stf_isp_unregister(stfcamss->isp_dev); ++err_reg_isp: ++ stf_csi_unregister(stfcamss->csi_dev); ++err_reg_csi: ++ stf_csiphy_unregister(stfcamss->csiphy_dev); ++err_reg_csiphy: ++ stf_dvp_unregister(stfcamss->dvp_dev); ++err_reg_dvp: ++ return ret; ++} ++ ++static void stfcamss_unregister_subdevices(struct stfcamss *stfcamss) ++{ ++ stf_dvp_unregister(stfcamss->dvp_dev); ++ stf_csiphy_unregister(stfcamss->csiphy_dev); ++ stf_csi_unregister(stfcamss->csi_dev); ++ stf_isp_unregister(stfcamss->isp_dev); ++ stf_vin_unregister(stfcamss->vin_dev); ++} ++ ++static int stfcamss_register_mediadevice_subdevnodes( ++ struct v4l2_async_notifier *async, ++ struct v4l2_subdev *sd) ++{ ++ struct stfcamss *stfcamss = ++ container_of(async, struct stfcamss, notifier); ++ int ret; ++ ++ if (sd->host_priv) { ++ struct media_entity *sensor = &sd->entity; ++ struct media_entity *input = sd->host_priv; ++ unsigned int i; ++ ++ for (i = 0; i < sensor->num_pads; i++) { ++ if (sensor->pads[i].flags & MEDIA_PAD_FL_SOURCE) ++ break; ++ } ++ if (i == sensor->num_pads) { ++ st_err(ST_CAMSS, ++ "No source pad in external entity\n"); ++ return -EINVAL; ++ } ++ ++ ret = media_create_pad_link(sensor, i, ++ input, STF_PAD_SINK, ++ MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->%s entities: %d\n", ++ sensor->name, input->name, ret); ++ return ret; ++ } ++ } ++ ++ ret = v4l2_device_register_subdev_nodes(&stfcamss->v4l2_dev); ++ if (ret < 0) ++ return ret; ++ ++ if (stfcamss->media_dev.devnode) ++ return ret; ++ ++ st_debug(ST_CAMSS, "stfcamss register media device\n"); ++ return media_device_register(&stfcamss->media_dev); ++} ++ ++static int stfcamss_subdev_notifier_bound(struct v4l2_async_notifier *async, ++ struct v4l2_subdev *subdev, ++ struct v4l2_async_connection *asd) ++{ ++ struct stfcamss *stfcamss = ++ container_of(async, struct stfcamss, notifier); ++ struct stfcamss_async_subdev *csd = ++ container_of(asd, struct stfcamss_async_subdev, asd); ++ enum port_num port = csd->port; ++ struct stf_dvp_dev *dvp_dev = stfcamss->dvp_dev; ++ struct stf_csiphy_dev *csiphy_dev = stfcamss->csiphy_dev; ++ ++ switch (port) { ++ case DVP_SENSOR_PORT_NUMBER: ++ dvp_dev->dvp = &csd->interface.dvp; ++ subdev->host_priv = &dvp_dev->subdev.entity; ++ break; ++ case CSI2RX_SENSOR_PORT_NUMBER: ++ csiphy_dev->csiphy = &csd->interface.csiphy; ++ subdev->host_priv = &csiphy_dev->subdev.entity; ++ break; ++ default: ++ break; ++ }; ++ ++ stfcamss_register_mediadevice_subdevnodes(async, subdev); ++ ++ return 0; ++} ++ ++#ifdef UNUSED_CODE ++static int stfcamss_subdev_notifier_complete( ++ struct v4l2_async_notifier *async) ++{ ++ struct stfcamss *stfcamss = ++ container_of(async, struct stfcamss, notifier); ++ struct v4l2_device *v4l2_dev = &stfcamss->v4l2_dev; ++ struct v4l2_subdev *sd; ++ int ret; ++ ++ list_for_each_entry(sd, &v4l2_dev->subdevs, list) { ++ if (sd->host_priv) { ++ struct media_entity *sensor = &sd->entity; ++ struct media_entity *input = sd->host_priv; ++ unsigned int i; ++ ++ for (i = 0; i < sensor->num_pads; i++) { ++ if (sensor->pads[i].flags & MEDIA_PAD_FL_SOURCE) ++ break; ++ } ++ if (i == sensor->num_pads) { ++ st_err(ST_CAMSS, ++ "No source pad in external entity\n"); ++ return -EINVAL; ++ } ++ ++ ret = media_create_pad_link(sensor, i, ++ input, STF_PAD_SINK, ++ MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to link %s->%s entities: %d\n", ++ sensor->name, input->name, ret); ++ return ret; ++ } ++ } ++ } ++ ++ ret = v4l2_device_register_subdev_nodes(&stfcamss->v4l2_dev); ++ if (ret < 0) ++ return ret; ++ ++ return media_device_register(&stfcamss->media_dev); ++} ++#endif ++ ++static const struct v4l2_async_notifier_operations ++stfcamss_subdev_notifier_ops = { ++ .bound = stfcamss_subdev_notifier_bound, ++}; ++ ++static const struct media_device_ops stfcamss_media_ops = { ++ .link_notify = v4l2_pipeline_link_notify, ++}; ++ ++#ifdef CONFIG_DEBUG_FS ++enum module_id { ++ VIN_MODULE = 0, ++ ISP_MODULE, ++ CSI_MODULE, ++ CSIPHY_MODULE, ++ DVP_MODULE, ++ CLK_MODULE, ++}; ++ ++static enum module_id id_num = ISP_MODULE; ++ ++void dump_clk_reg(void __iomem *reg_base) ++{ ++ int i; ++ ++ st_info(ST_CAMSS, "DUMP Clk register:\n"); ++ for (i = 0; i <= CLK_C_ISP_CTRL; i += 4) ++ print_reg(ST_CAMSS, reg_base, i); ++} ++ ++static ssize_t vin_debug_read(struct file *file, char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct device *dev = file->private_data; ++ void __iomem *reg_base; ++ struct stfcamss *stfcamss = dev_get_drvdata(dev); ++ struct stf_vin_dev *vin = stfcamss->vin; ++ struct stf_vin2_dev *vin_dev = stfcamss->vin_dev; ++ struct stf_isp_dev *isp_dev = stfcamss->isp_dev; ++ struct stf_csi_dev *csi0_dev = stfcamss->csi_dev; ++ ++ switch (id_num) { ++ case VIN_MODULE: ++ case CSIPHY_MODULE: ++ case DVP_MODULE: ++ mutex_lock(&vin_dev->power_lock); ++ if (vin_dev->power_count > 0) { ++ reg_base = vin->sysctrl_base; ++ dump_vin_reg(reg_base); ++ } ++ mutex_unlock(&vin_dev->power_lock); ++ break; ++ case ISP_MODULE: ++ mutex_lock(&isp_dev->stream_lock); ++ if (isp_dev->stream_count > 0) { ++ reg_base = vin->isp_base; ++ dump_isp_reg(reg_base); ++ } ++ mutex_unlock(&isp_dev->stream_lock); ++ break; ++ case CSI_MODULE: ++ mutex_lock(&csi0_dev->stream_lock); ++ if (csi0_dev->stream_count > 0) { ++ reg_base = vin->csi2rx_base; ++ dump_csi_reg(reg_base); ++ } ++ mutex_unlock(&csi0_dev->stream_lock); ++ break; ++ case CLK_MODULE: ++ mutex_lock(&vin_dev->power_lock); ++ if (vin_dev->power_count > 0) { ++ reg_base = vin->clkgen_base; ++ dump_clk_reg(reg_base); ++ } ++ mutex_unlock(&vin_dev->power_lock); ++ break; ++ default: ++ break; ++ } ++ ++ return 0; ++} ++ ++static void set_reg_val(struct stfcamss *stfcamss, int id, u32 offset, u32 val) ++{ ++ struct stf_vin_dev *vin = stfcamss->vin; ++ struct stf_vin2_dev *vin_dev = stfcamss->vin_dev; ++ struct stf_isp_dev *isp_dev = stfcamss->isp_dev; ++ struct stf_csi_dev *csi_dev = stfcamss->csi_dev; ++ void __iomem *reg_base; ++ ++ switch (id) { ++ case VIN_MODULE: ++ case CSIPHY_MODULE: ++ case DVP_MODULE: ++ mutex_lock(&vin_dev->power_lock); ++ if (vin_dev->power_count > 0) { ++ reg_base = vin->sysctrl_base; ++ print_reg(ST_VIN, reg_base, offset); ++ reg_write(reg_base, offset, val); ++ print_reg(ST_VIN, reg_base, offset); ++ } ++ mutex_unlock(&vin_dev->power_lock); ++ break; ++ case ISP_MODULE: ++ mutex_lock(&isp_dev->stream_lock); ++ if (isp_dev->stream_count > 0) { ++ reg_base = vin->isp_base; ++ print_reg(ST_ISP, reg_base, offset); ++ reg_write(reg_base, offset, val); ++ print_reg(ST_ISP, reg_base, offset); ++ } ++ mutex_unlock(&isp_dev->stream_lock); ++ break; ++ case CSI_MODULE: ++ mutex_lock(&csi_dev->stream_lock); ++ if (csi_dev->stream_count > 0) { ++ reg_base = vin->csi2rx_base; ++ print_reg(ST_CSI, reg_base, offset); ++ reg_write(reg_base, offset, val); ++ print_reg(ST_CSI, reg_base, offset); ++ } ++ mutex_unlock(&csi_dev->stream_lock); ++ break; ++ case CLK_MODULE: ++ mutex_lock(&vin_dev->power_lock); ++ if (vin_dev->power_count > 0) { ++ reg_base = vin->clkgen_base; ++ print_reg(ST_CAMSS, reg_base, offset); ++ reg_write(reg_base, offset, val); ++ print_reg(ST_CAMSS, reg_base, offset); ++ } ++ mutex_unlock(&vin_dev->power_lock); ++ break; ++ default: ++ break; ++ ++ } ++} ++ ++static u32 atoi(const char *s) ++{ ++ u32 ret = 0, d = 0; ++ char ch; ++ int hex = 0; ++ ++ if ((*s == '0') && (*(s+1) == 'x')) { ++ hex = 1; ++ s += 2; ++ } ++ ++ while (1) { ++ if (!hex) { ++ d = (*s++) - '0'; ++ if (d > 9) ++ break; ++ ret *= 10; ++ ret += d; ++ } else { ++ ch = tolower(*s++); ++ if (isdigit(ch)) ++ d = ch - '0'; ++ else if (islower(ch)) ++ d = ch - 'a' + 10; ++ else ++ break; ++ if (d > 15) ++ break; ++ ret *= 16; ++ ret += d; ++ } ++ } ++ ++ return ret; ++} ++ ++static ssize_t vin_debug_write(struct file *file, const char __user *user_buf, ++ size_t count, loff_t *ppos) ++{ ++ struct device *dev = file->private_data; ++ struct stfcamss *stfcamss = dev_get_drvdata(dev); ++ char *buf; ++ char *line; ++ char *p; ++ static const char *delims = " \t\r"; ++ char *token; ++ u32 offset, val; ++ ++ buf = memdup_user_nul(user_buf, min_t(size_t, PAGE_SIZE, count)); ++ if (IS_ERR(buf)) ++ return PTR_ERR(buf); ++ p = buf; ++ st_debug(ST_CAMSS, "dup buf: %s, len: %lu, count: %lu\n", p, strlen(p), count); ++ while (p && *p) { ++ p = skip_spaces(p); ++ line = strsep(&p, "\n"); ++ if (!*line || *line == '#') ++ break; ++ token = strsep(&line, delims); ++ if (!token) ++ goto out; ++ id_num = atoi(token); ++ token = strsep(&line, delims); ++ if (!token) ++ goto out; ++ offset = atoi(token); ++ token = strsep(&line, delims); ++ if (!token) ++ goto out; ++ val = atoi(token); ++ } ++ set_reg_val(stfcamss, id_num, offset, val); ++out: ++ kfree(buf); ++ st_info(ST_CAMSS, "id_num = %d, offset = 0x%x, 0x%x\n", id_num, offset, val); ++ return count; ++} ++ ++static const struct file_operations vin_debug_fops = { ++ .open = simple_open, ++ .read = vin_debug_read, ++ .write = vin_debug_write, ++}; ++#endif /* CONFIG_DEBUG_FS */ ++ ++ ++static int stfcamss_probe(struct platform_device *pdev) ++{ ++ struct stfcamss *stfcamss; ++ struct stf_vin_dev *vin; ++ struct device *dev = &pdev->dev; ++ struct of_phandle_args args; ++ int ret = 0, num_subdevs; ++ ++ dev_info(dev, "stfcamss probe enter!\n"); ++ ++ stfcamss = devm_kzalloc(dev, sizeof(struct stfcamss), GFP_KERNEL); ++ if (!stfcamss) ++ return -ENOMEM; ++ ++ stfcamss->dvp_dev = devm_kzalloc(dev, ++ sizeof(*stfcamss->dvp_dev), GFP_KERNEL); ++ if (!stfcamss->dvp_dev) { ++ ret = -ENOMEM; ++ goto err_cam; ++ } ++ ++ stfcamss->csiphy_dev = devm_kzalloc(dev, ++ sizeof(*stfcamss->csiphy_dev), ++ GFP_KERNEL); ++ if (!stfcamss->csiphy_dev) { ++ ret = -ENOMEM; ++ goto err_cam; ++ } ++ ++ stfcamss->csi_dev = devm_kzalloc(dev, ++ sizeof(*stfcamss->csi_dev), ++ GFP_KERNEL); ++ if (!stfcamss->csi_dev) { ++ ret = -ENOMEM; ++ goto err_cam; ++ } ++ ++ stfcamss->isp_dev = devm_kzalloc(dev, ++ sizeof(*stfcamss->isp_dev), ++ GFP_KERNEL); ++ if (!stfcamss->isp_dev) { ++ ret = -ENOMEM; ++ goto err_cam; ++ } ++ ++ stfcamss->vin_dev = devm_kzalloc(dev, ++ sizeof(*stfcamss->vin_dev), ++ GFP_KERNEL); ++ if (!stfcamss->vin_dev) { ++ ret = -ENOMEM; ++ goto err_cam; ++ } ++ ++ stfcamss->vin = devm_kzalloc(dev, ++ sizeof(struct stf_vin_dev), ++ GFP_KERNEL); ++ if (!stfcamss->vin) { ++ ret = -ENOMEM; ++ goto err_cam; ++ } ++ ++ vin = stfcamss->vin; ++ ++ vin->irq = platform_get_irq(pdev, 0); ++ if (vin->irq <= 0) { ++ st_err(ST_CAMSS, "Could not get irq\n"); ++ goto err_cam; ++ } ++ ++ vin->isp_irq = platform_get_irq(pdev, 1); ++ if (vin->isp_irq <= 0) { ++ st_err(ST_CAMSS, "Could not get isp irq\n"); ++ goto err_cam; ++ } ++ ++ vin->isp_csi_irq = platform_get_irq(pdev, 2); ++ if (vin->isp_csi_irq <= 0) { ++ st_err(ST_CAMSS, "Could not get isp csi irq\n"); ++ goto err_cam; ++ } ++ ++ vin->isp_scd_irq = platform_get_irq(pdev, 3); ++ if (vin->isp_scd_irq <= 0) { ++ st_err(ST_CAMSS, "Could not get isp scd irq\n"); ++ goto err_cam; ++ } ++ ++ vin->isp_irq_csiline = platform_get_irq(pdev, 4); ++ if (vin->isp_irq_csiline <= 0) { ++ st_err(ST_CAMSS, "Could not get isp irq csiline\n"); ++ goto err_cam; ++ } ++ ++ pm_runtime_enable(dev); ++ ++ stfcamss->nclks = ARRAY_SIZE(stfcamss_clocks); ++ stfcamss->sys_clk = stfcamss_clocks; ++ ++ ret = devm_clk_bulk_get(dev, stfcamss->nclks, stfcamss->sys_clk); ++ if (ret) { ++ st_err(ST_CAMSS, "Failed to get clk controls\n"); ++ return ret; ++ } ++ ++ stfcamss->nrsts = ARRAY_SIZE(stfcamss_resets); ++ stfcamss->sys_rst = stfcamss_resets; ++ ++ ret = devm_reset_control_bulk_get_shared(dev, stfcamss->nrsts, ++ stfcamss->sys_rst); ++ if (ret) { ++ st_err(ST_CAMSS, "Failed to get reset controls\n"); ++ return ret; ++ } ++ ++ ret = of_parse_phandle_with_fixed_args(dev->of_node, ++ "starfive,aon-syscon", 1, 0, &args); ++ if (ret < 0) { ++ st_err(ST_CAMSS, "Failed to parse starfive,aon-syscon\n"); ++ return -EINVAL; ++ } ++ ++ stfcamss->stf_aon_syscon = syscon_node_to_regmap(args.np); ++ of_node_put(args.np); ++ if (IS_ERR(stfcamss->stf_aon_syscon)) ++ return PTR_ERR(stfcamss->stf_aon_syscon); ++ ++ stfcamss->aon_gp_reg = args.args[0]; ++ ++ ret = stfcamss_get_mem_res(pdev, vin); ++ if (ret) { ++ st_err(ST_CAMSS, "Could not map registers\n"); ++ goto err_cam; ++ } ++ ++ ret = vin_parse_dt(dev, vin); ++ if (ret) ++ goto err_cam; ++ ++ vin->dev = dev; ++ stfcamss->dev = dev; ++ platform_set_drvdata(pdev, stfcamss); ++ ++ v4l2_async_nf_init(&stfcamss->notifier, &stfcamss->v4l2_dev); ++ ++ num_subdevs = stfcamss_of_parse_ports(stfcamss); ++ if (num_subdevs < 0) { ++ ret = num_subdevs; ++ goto err_cam_noti; ++ } ++ ++ ret = stfcamss_init_subdevices(stfcamss); ++ if (ret < 0) { ++ st_err(ST_CAMSS, "Failed to init subdevice: %d\n", ret); ++ goto err_cam_noti; ++ } ++ ++ stfcamss->media_dev.dev = stfcamss->dev; ++ strscpy(stfcamss->media_dev.model, "Starfive Camera Subsystem", ++ sizeof(stfcamss->media_dev.model)); ++ strscpy(stfcamss->media_dev.serial, "0123456789ABCDEF", ++ sizeof(stfcamss->media_dev.serial)); ++ snprintf(stfcamss->media_dev.bus_info, sizeof(stfcamss->media_dev.bus_info), ++ "%s:%s", dev_bus_name(dev), pdev->name); ++ stfcamss->media_dev.hw_revision = 0x01; ++ stfcamss->media_dev.ops = &stfcamss_media_ops; ++ media_device_init(&stfcamss->media_dev); ++ ++ stfcamss->v4l2_dev.mdev = &stfcamss->media_dev; ++ ++ ret = v4l2_device_register(stfcamss->dev, &stfcamss->v4l2_dev); ++ if (ret < 0) { ++ st_err(ST_CAMSS, "Failed to register V4L2 device: %d\n", ret); ++ goto err_cam_noti_med; ++ } ++ ++ ret = stfcamss_register_subdevices(stfcamss); ++ if (ret < 0) { ++ st_err(ST_CAMSS, "Failed to register subdevice: %d\n", ret); ++ goto err_cam_noti_med_vreg; ++ } ++ ++ if (num_subdevs) { ++ stfcamss->notifier.ops = &stfcamss_subdev_notifier_ops; ++ ret = v4l2_async_nf_register(&stfcamss->notifier); ++ if (ret) { ++ st_err(ST_CAMSS, ++ "Failed to register async subdev nodes: %d\n", ++ ret); ++ goto err_cam_noti_med_vreg_sub; ++ } ++ } else { ++ ret = v4l2_device_register_subdev_nodes(&stfcamss->v4l2_dev); ++ if (ret < 0) { ++ st_err(ST_CAMSS, ++ "Failed to register subdev nodes: %d\n", ++ ret); ++ goto err_cam_noti_med_vreg_sub; ++ } ++ ++ ret = media_device_register(&stfcamss->media_dev); ++ if (ret < 0) { ++ st_err(ST_CAMSS, "Failed to register media device: %d\n", ++ ret); ++ goto err_cam_noti_med_vreg_sub_medreg; ++ } ++ } ++ ++#ifdef CONFIG_DEBUG_FS ++ stfcamss->debugfs_entry = debugfs_create_dir("stfcamss", NULL); ++ stfcamss->vin_debugfs = debugfs_create_file("stf_vin", ++ 0644, stfcamss->debugfs_entry, ++ (void *)dev, &vin_debug_fops); ++ debugfs_create_u32("dbg_level", ++ 0644, stfcamss->debugfs_entry, ++ &stdbg_level); ++ debugfs_create_u32("dbg_mask", ++ 0644, stfcamss->debugfs_entry, ++ &stdbg_mask); ++#endif ++ dev_info(dev, "stfcamss probe success!\n"); ++ ++ return 0; ++ ++#ifdef CONFIG_DEBUG_FS ++ debugfs_remove(stfcamss->vin_debugfs); ++ debugfs_remove_recursive(stfcamss->debugfs_entry); ++ stfcamss->debugfs_entry = NULL; ++#endif ++ ++err_cam_noti_med_vreg_sub_medreg: ++err_cam_noti_med_vreg_sub: ++ stfcamss_unregister_subdevices(stfcamss); ++err_cam_noti_med_vreg: ++ v4l2_device_unregister(&stfcamss->v4l2_dev); ++err_cam_noti_med: ++ media_device_cleanup(&stfcamss->media_dev); ++err_cam_noti: ++ v4l2_async_nf_cleanup(&stfcamss->notifier); ++err_cam: ++ // kfree(stfcamss); ++ return ret; ++} ++ ++static int stfcamss_remove(struct platform_device *pdev) ++{ ++ struct stfcamss *stfcamss = platform_get_drvdata(pdev); ++ ++ dev_info(&pdev->dev, "remove done\n"); ++ ++#ifdef CONFIG_DEBUG_FS ++ debugfs_remove(stfcamss->vin_debugfs); ++ debugfs_remove_recursive(stfcamss->debugfs_entry); ++ stfcamss->debugfs_entry = NULL; ++#endif ++ ++ stfcamss_unregister_subdevices(stfcamss); ++ v4l2_device_unregister(&stfcamss->v4l2_dev); ++ media_device_cleanup(&stfcamss->media_dev); ++ pm_runtime_disable(&pdev->dev); ++ ++ kfree(stfcamss); ++ ++ return 0; ++} ++ ++static const struct of_device_id stfcamss_of_match[] = { ++ { .compatible = "starfive,jh7110-vin" }, ++ { /* end node */ }, ++}; ++ ++MODULE_DEVICE_TABLE(of, stfcamss_of_match); ++ ++#ifdef CONFIG_PM_SLEEP ++static int stfcamss_suspend(struct device *dev) ++{ ++ struct stfcamss *stfcamss = dev_get_drvdata(dev); ++ struct stf_vin2_dev *vin_dev = stfcamss->vin_dev; ++ struct media_entity *entity; ++ struct media_pad *pad; ++ struct v4l2_subdev *subdev; ++ struct stfcamss_video *video; ++ struct video_device *vdev; ++ int i = 0; ++ int pm_power_count; ++ int pm_stream_count; ++ ++ for (i = 0; i < VIN_LINE_MAX; i++) { ++ video = &vin_dev->line[i].video_out; ++ vdev = &vin_dev->line[i].video_out.vdev; ++ vin_dev->line[i].pm_power_count = vin_dev->line[i].power_count; ++ vin_dev->line[i].pm_stream_count = vin_dev->line[i].stream_count; ++ pm_power_count = vin_dev->line[i].pm_power_count; ++ pm_stream_count = vin_dev->line[i].pm_stream_count; ++ ++ if (pm_stream_count) { ++ while (pm_stream_count--) { ++ entity = &vdev->entity; ++ while (1) { ++ pad = &entity->pads[0]; ++ if (!(pad->flags & MEDIA_PAD_FL_SINK)) ++ break; ++ ++ pad = media_pad_remote_pad_first(pad); ++ if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) ++ break; ++ ++ entity = pad->entity; ++ subdev = media_entity_to_v4l2_subdev(entity); ++ ++ v4l2_subdev_call(subdev, video, s_stream, 0); ++ } ++ } ++ video_device_pipeline_stop(vdev); ++ video->ops->flush_buffers(video, VB2_BUF_STATE_ERROR); ++ } ++ ++ if (!pm_power_count) ++ continue; ++ ++ v4l2_pipeline_pm_put(&vdev->entity); ++ } ++ ++ return pm_runtime_force_suspend(dev); ++} ++ ++static int stfcamss_resume(struct device *dev) ++{ ++ struct stfcamss *stfcamss = dev_get_drvdata(dev); ++ struct stf_vin2_dev *vin_dev = stfcamss->vin_dev; ++ struct media_entity *entity; ++ struct media_pad *pad; ++ struct v4l2_subdev *subdev; ++ struct stfcamss_video *video; ++ struct video_device *vdev; ++ int i = 0; ++ int pm_power_count; ++ int pm_stream_count; ++ int ret = 0; ++ ++ pm_runtime_force_resume(dev); ++ ++ for (i = 0; i < VIN_LINE_MAX; i++) { ++ video = &vin_dev->line[i].video_out; ++ vdev = &vin_dev->line[i].video_out.vdev; ++ pm_power_count = vin_dev->line[i].pm_power_count; ++ pm_stream_count = vin_dev->line[i].pm_stream_count; ++ ++ if (!pm_power_count) ++ continue; ++ ++ ret = v4l2_pipeline_pm_get(&vdev->entity); ++ if (ret < 0) ++ goto err; ++ ++ if (pm_stream_count) { ++ ret = video_device_pipeline_start(vdev, &video->stfcamss->pipe); ++ if (ret < 0) ++ goto err_pm_put; ++ ++ while (pm_stream_count--) { ++ entity = &vdev->entity; ++ while (1) { ++ pad = &entity->pads[0]; ++ if (!(pad->flags & MEDIA_PAD_FL_SINK)) ++ break; ++ ++ pad = media_pad_remote_pad_first(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, 1); ++ if (ret < 0 && ret != -ENOIOCTLCMD) ++ goto err_pipeline_stop; ++ } ++ } ++ } ++ } ++ ++ return 0; ++ ++err_pipeline_stop: ++ video_device_pipeline_stop(vdev); ++err_pm_put: ++ v4l2_pipeline_pm_put(&vdev->entity); ++err: ++ return ret; ++} ++#endif /* CONFIG_PM_SLEEP */ ++ ++#ifdef CONFIG_PM ++static int stfcamss_runtime_suspend(struct device *dev) ++{ ++ struct stfcamss *stfcamss = dev_get_drvdata(dev); ++ ++ reset_control_assert(stfcamss->sys_rst[STFRST_ISP_TOP_AXI].rstc); ++ reset_control_assert(stfcamss->sys_rst[STFRST_ISP_TOP_N].rstc); ++ clk_disable_unprepare(stfcamss->sys_clk[STFCLK_ISP_AXI].clk); ++ clk_disable_unprepare(stfcamss->sys_clk[STFCLK_ISPCORE_2X].clk); ++ ++ return 0; ++} ++ ++static int stfcamss_runtime_resume(struct device *dev) ++{ ++ struct stfcamss *stfcamss = dev_get_drvdata(dev); ++ ++ clk_prepare_enable(stfcamss->sys_clk[STFCLK_ISPCORE_2X].clk); ++ clk_prepare_enable(stfcamss->sys_clk[STFCLK_ISP_AXI].clk); ++ reset_control_deassert(stfcamss->sys_rst[STFRST_ISP_TOP_N].rstc); ++ reset_control_deassert(stfcamss->sys_rst[STFRST_ISP_TOP_AXI].rstc); ++ ++ return 0; ++} ++#endif /* CONFIG_PM */ ++ ++static const struct dev_pm_ops stfcamss_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(stfcamss_suspend, stfcamss_resume) ++ SET_RUNTIME_PM_OPS(stfcamss_runtime_suspend, stfcamss_runtime_resume, NULL) ++}; ++ ++static struct platform_driver stfcamss_driver = { ++ .probe = stfcamss_probe, ++ .remove = stfcamss_remove, ++ .driver = { ++ .name = DRV_NAME, ++ .pm = &stfcamss_pm_ops, ++ .of_match_table = of_match_ptr(stfcamss_of_match), ++ }, ++}; ++ ++static int __init stfcamss_init(void) ++{ ++ return platform_driver_register(&stfcamss_driver); ++} ++ ++static void __exit stfcamss_cleanup(void) ++{ ++ platform_driver_unregister(&stfcamss_driver); ++} ++ ++module_init(stfcamss_init); ++//fs_initcall(stfcamss_init); ++module_exit(stfcamss_cleanup); ++ ++MODULE_LICENSE("GPL"); +--- /dev/null ++++ b/drivers/media/platform/starfive/v4l2_driver/stfcamss.h +@@ -0,0 +1,117 @@ ++/* SPDX-License-Identifier: GPL-2.0 ++ * ++ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. ++ * ++ */ ++ ++#ifndef STFCAMSS_H ++#define STFCAMSS_H ++ ++#include <linux/io.h> ++#include <linux/delay.h> ++#include <linux/reset.h> ++#include <linux/clk.h> ++ ++enum sensor_type { ++ SENSOR_VIN, ++ /* need replace sensor */ ++ SENSOR_ISP, ++}; ++ ++enum subdev_type { ++ VIN_DEV_TYPE, ++ ISP_DEV_TYPE, ++}; ++ ++#include "stf_common.h" ++#include "stf_dvp.h" ++#include "stf_csi.h" ++#include "stf_csiphy.h" ++#include "stf_isp.h" ++#include "stf_vin.h" ++ ++#define STF_PAD_SINK 0 ++#define STF_PAD_SRC 1 ++#define STF_PADS_NUM 2 ++ ++#define STF_CAMSS_SKIP_ITI ++ ++enum port_num { ++ DVP_SENSOR_PORT_NUMBER = 0, ++ CSI2RX_SENSOR_PORT_NUMBER ++}; ++ ++enum stf_clk_num { ++ STFCLK_APB_FUNC = 0, ++ STFCLK_PCLK, ++ STFCLK_SYS_CLK, ++ STFCLK_WRAPPER_CLK_C, ++ STFCLK_DVP_INV, ++ STFCLK_AXIWR, ++ STFCLK_MIPI_RX0_PXL, ++ STFCLK_PIXEL_CLK_IF0, ++ STFCLK_PIXEL_CLK_IF1, ++ STFCLK_PIXEL_CLK_IF2, ++ STFCLK_PIXEL_CLK_IF3, ++ STFCLK_M31DPHY_CFGCLK_IN, ++ STFCLK_M31DPHY_REFCLK_IN, ++ STFCLK_M31DPHY_TXCLKESC_LAN0, ++ STFCLK_ISPCORE_2X, ++ STFCLK_ISP_AXI, ++ STFCLK_NUM ++}; ++ ++enum stf_rst_num { ++ STFRST_WRAPPER_P = 0, ++ STFRST_WRAPPER_C, ++ STFRST_PCLK, ++ STFRST_SYS_CLK, ++ STFRST_AXIRD, ++ STFRST_AXIWR, ++ STFRST_PIXEL_CLK_IF0, ++ STFRST_PIXEL_CLK_IF1, ++ STFRST_PIXEL_CLK_IF2, ++ STFRST_PIXEL_CLK_IF3, ++ STFRST_M31DPHY_HW, ++ STFRST_M31DPHY_B09_ALWAYS_ON, ++ STFRST_ISP_TOP_N, ++ STFRST_ISP_TOP_AXI, ++ STFRST_NUM ++}; ++ ++struct stfcamss { ++ struct stf_vin_dev *vin; // stfcamss phy res ++ struct v4l2_device v4l2_dev; ++ struct media_device media_dev; ++ struct media_pipeline pipe; ++ struct device *dev; ++ struct stf_vin2_dev *vin_dev; // subdev ++ struct stf_dvp_dev *dvp_dev; // subdev ++ struct stf_csi_dev *csi_dev; // subdev ++ struct stf_csiphy_dev *csiphy_dev; // subdev ++ struct stf_isp_dev *isp_dev; // subdev ++ struct v4l2_async_notifier notifier; ++ struct clk_bulk_data *sys_clk; ++ int nclks; ++ struct reset_control_bulk_data *sys_rst; ++ int nrsts; ++ struct regmap *stf_aon_syscon; ++ uint32_t aon_gp_reg; ++#ifdef CONFIG_DEBUG_FS ++ struct dentry *debugfs_entry; ++ struct dentry *vin_debugfs; ++#endif ++}; ++ ++struct stfcamss_async_subdev { ++ struct v4l2_async_connection asd; // must be first ++ enum port_num port; ++ struct { ++ struct dvp_cfg dvp; ++ struct csi2phy_cfg csiphy; ++ } interface; ++}; ++ ++extern struct media_entity *stfcamss_find_sensor(struct media_entity *entity); ++ ++#endif /* STFCAMSS_H */ +--- /dev/null ++++ b/include/uapi/linux/jh7110-isp.h +@@ -0,0 +1,253 @@ ++/* SPDX-License-Identifier: ((GPL-2.0+ WITH Linux-syscall-note) OR BSD-3-Clause) */ ++/* ++ * jh7110-isp.h ++ * ++ * JH7110 ISP driver - user space header file. ++ * ++ * Copyright © 2023 Starfive Technology Co., Ltd. ++ * ++ * Author: Su Zejian (zejian.su@starfivetech.com) ++ * ++ */ ++ ++#ifndef __JH7110_ISP_H_ ++#define __JH7110_ISP_H_ ++ ++#include <linux/v4l2-controls.h> ++ ++#define V4L2_CID_USER_JH7110_ISP_WB_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x0001) ++#define V4L2_CID_USER_JH7110_ISP_CAR_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x0002) ++#define V4L2_CID_USER_JH7110_ISP_CCM_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x0003) ++#define V4L2_CID_USER_JH7110_ISP_CFA_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x0004) ++#define V4L2_CID_USER_JH7110_ISP_CTC_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x0005) ++#define V4L2_CID_USER_JH7110_ISP_DBC_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x0006) ++#define V4L2_CID_USER_JH7110_ISP_DNYUV_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x0007) ++#define V4L2_CID_USER_JH7110_ISP_GMARGB_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x0008) ++#define V4L2_CID_USER_JH7110_ISP_LCCF_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x0009) ++#define V4L2_CID_USER_JH7110_ISP_OBC_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x000a) ++#define V4L2_CID_USER_JH7110_ISP_OECF_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x000b) ++#define V4L2_CID_USER_JH7110_ISP_R2Y_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x000c) ++#define V4L2_CID_USER_JH7110_ISP_SAT_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x000d) ++#define V4L2_CID_USER_JH7110_ISP_SHRP_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x000e) ++#define V4L2_CID_USER_JH7110_ISP_YCRV_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x000f) ++ ++struct jh7110_isp_wb_gain { ++ __u16 gain_r; ++ __u16 gain_g; ++ __u16 gain_b; ++}; ++ ++struct jh7110_isp_wb_setting { ++ __u32 enabled; ++ struct jh7110_isp_wb_gain gains; ++}; ++ ++struct jh7110_isp_car_setting { ++ __u32 enabled; ++}; ++ ++struct jh7110_isp_ccm_smlow { ++ __s32 ccm[3][3]; ++ __s32 offsets[3]; ++}; ++ ++struct jh7110_isp_ccm_setting { ++ __u32 enabled; ++ struct jh7110_isp_ccm_smlow ccm_smlow; ++}; ++ ++struct jh7110_isp_cfa_params { ++ __s32 hv_width; ++ __s32 cross_cov; ++}; ++ ++struct jh7110_isp_cfa_setting { ++ __u32 enabled; ++ struct jh7110_isp_cfa_params settings; ++}; ++ ++struct jh7110_isp_ctc_params { ++ __u8 saf_mode; ++ __u8 daf_mode; ++ __s32 max_gt; ++ __s32 min_gt; ++}; ++ ++struct jh7110_isp_ctc_setting { ++ __u32 enabled; ++ struct jh7110_isp_ctc_params settings; ++}; ++ ++struct jh7110_isp_dbc_params { ++ __s32 bad_gt; ++ __s32 bad_xt; ++}; ++ ++struct jh7110_isp_dbc_setting { ++ __u32 enabled; ++ struct jh7110_isp_dbc_params settings; ++}; ++ ++struct jh7110_isp_dnyuv_params { ++ __u8 y_sweight[10]; ++ __u16 y_curve[7]; ++ __u8 uv_sweight[10]; ++ __u16 uv_curve[7]; ++}; ++ ++struct jh7110_isp_dnyuv_setting { ++ __u32 enabled; ++ struct jh7110_isp_dnyuv_params settings; ++}; ++ ++struct jh7110_isp_gmargb_point { ++ __u16 g_val; ++ __u16 sg_val; ++}; ++ ++struct jh7110_isp_gmargb_setting { ++ __u32 enabled; ++ struct jh7110_isp_gmargb_point curve[15]; ++}; ++ ++struct jh7110_isp_lccf_circle { ++ __s16 center_x; ++ __s16 center_y; ++ __u8 radius; ++}; ++ ++struct jh7110_isp_lccf_curve_param { ++ __s16 f1; ++ __s16 f2; ++}; ++ ++struct jh7110_isp_lccf_setting { ++ __u32 enabled; ++ struct jh7110_isp_lccf_circle circle; ++ struct jh7110_isp_lccf_curve_param r_param; ++ struct jh7110_isp_lccf_curve_param gr_param; ++ struct jh7110_isp_lccf_curve_param gb_param; ++ struct jh7110_isp_lccf_curve_param b_param; ++}; ++ ++struct jh7110_isp_blacklevel_win_size { ++ __u32 width; ++ __u32 height; ++}; ++ ++struct jh7110_isp_blacklevel_gain { ++ __u8 tl_gain; ++ __u8 tr_gain; ++ __u8 bl_gain; ++ __u8 br_gain; ++}; ++ ++struct jh7110_isp_blacklevel_offset { ++ __u8 tl_offset; ++ __u8 tr_offset; ++ __u8 bl_offset; ++ __u8 br_offset; ++}; ++ ++struct jh7110_isp_blacklevel_setting { ++ __u32 enabled; ++ struct jh7110_isp_blacklevel_win_size win_size; ++ struct jh7110_isp_blacklevel_gain gain[4]; ++ struct jh7110_isp_blacklevel_offset offset[4]; ++}; ++ ++struct jh7110_isp_oecf_point { ++ __u16 x; ++ __u16 y; ++ __s16 slope; ++}; ++ ++struct jh7110_isp_oecf_setting { ++ __u32 enabled; ++ struct jh7110_isp_oecf_point r_curve[16]; ++ struct jh7110_isp_oecf_point gr_curve[16]; ++ struct jh7110_isp_oecf_point gb_curve[16]; ++ struct jh7110_isp_oecf_point b_curve[16]; ++}; ++ ++struct jh7110_isp_r2y_matrix { ++ __s16 m[9]; ++}; ++ ++struct jh7110_isp_r2y_setting { ++ __u32 enabled; ++ struct jh7110_isp_r2y_matrix matrix; ++}; ++ ++struct jh7110_isp_sat_curve { ++ __s16 yi_min; ++ __s16 yo_ir; ++ __s16 yo_min; ++ __s16 yo_max; ++}; ++ ++struct jh7110_isp_sat_hue_info { ++ __s16 sin; ++ __s16 cos; ++}; ++ ++struct jh7110_isp_sat_info { ++ __s16 gain_cmab; ++ __s16 gain_cmad; ++ __s16 threshold_cmb; ++ __s16 threshold_cmd; ++ __s16 offset_u; ++ __s16 offset_v; ++ __s16 cmsf; ++}; ++ ++struct jh7110_isp_sat_setting { ++ __u32 enabled; ++ struct jh7110_isp_sat_curve curve; ++ struct jh7110_isp_sat_hue_info hue_info; ++ struct jh7110_isp_sat_info sat_info; ++}; ++ ++struct jh7110_isp_sharp_weight { ++ __u8 weight[15]; ++ __u32 recip_wei_sum; ++}; ++ ++struct jh7110_isp_sharp_strength { ++ __s16 diff[4]; ++ __s16 f[4]; ++}; ++ ++struct jh7110_isp_sharp_setting { ++ __u32 enabled; ++ struct jh7110_isp_sharp_weight weight; ++ struct jh7110_isp_sharp_strength strength; ++ __s8 pdirf; ++ __s8 ndirf; ++}; ++ ++struct jh7110_isp_ycrv_curve { ++ __s16 y[64]; ++}; ++ ++struct jh7110_isp_ycrv_setting { ++ __u32 enabled; ++ struct jh7110_isp_ycrv_curve curve; ++}; ++ ++#endif +--- a/include/uapi/linux/v4l2-controls.h ++++ b/include/uapi/linux/v4l2-controls.h +@@ -203,6 +203,12 @@ enum v4l2_colorfx { + */ + #define V4L2_CID_USER_ASPEED_BASE (V4L2_CID_USER_BASE + 0x11a0) + ++/* ++ * The base for the jh7110-isp driver controls. ++ * We reserve 16 controls for this driver. ++ */ ++#define V4L2_CID_USER_JH7110_ISP_BASE (V4L2_CID_USER_BASE + 0x1170) ++ + /* MPEG-class control IDs */ + /* The MPEG controls are applicable to all codec controls + * and the 'MPEG' part of the define is historical */ +--- /dev/null ++++ b/include/video/stf-vin.h +@@ -0,0 +1,443 @@ ++/* include/video/stf-vin.h ++ * ++ * Copyright 2020 starfive tech. ++ * Eric Tang <eric.tang@starfivetech.com> ++ * ++ * Generic vin notifier interface ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ * ++*/ ++#ifndef _VIDEO_VIN_H ++#define _VIDEO_VIN_H ++ ++#include <linux/cdev.h> ++ ++#define DRV_NAME "jh7110-vin" ++#define FB_FIRST_ADDR 0xf9000000 ++#define FB_SECOND_ADDR 0xf97e9000 ++ ++#define RESERVED_MEM_SIZE 0x1000000 ++ ++#define VIN_MIPI_CONTROLLER0_OFFSET 0x00000 ++#define VIN_CLKGEN_OFFSET 0x10000 ++#define VIN_RSTGEN_OFFSET 0x20000 ++#define VIN_MIPI_CONTROLLER1_OFFSET 0x30000 ++#define VIN_SYSCONTROLLER_OFFSET 0x40000 ++ ++#define VD_1080P 1080 ++#define VD_720P 720 ++#define VD_PAL 480 ++ ++#define VD_HEIGHT_1080P VD_1080P ++#define VD_WIDTH_1080P 1920 ++ ++#define VD_HEIGHT_720P VD_720P ++#define VD_WIDTH_720P 1080 ++ ++#define VD_HEIGHT_480 480 ++#define VD_WIDTH_640 640 ++ ++#define SEEED_WIDTH_800 800 ++#define SEEED_HIGH_480 480 ++ ++#define VIN_TOP_CLKGEN_BASE_ADDR 0x11800000 ++#define VIN_TOP_RSTGEN_BASE_ADDR 0x11840000 ++#define VIN_TOP_IOPAD_BASE_ADDR 0x11858000 ++ ++#define ISP_BASE_MIPI0_ADDR 0x19800000 ++#define ISP_BASE_CLKGEN_ADDR 0x19810000 ++#define ISP_BASE_RSTGEN_ADDR 0x19820000 ++#define ISP_BASE_MIPI1_ADDR 0x19830000 ++#define ISP_BASE_SYSCTRL_ADDR 0x19840000 ++#define ISP_BASE_ISP0_ADDR 0x19870000 ++#define ISP_BASE_ISP1_ADDR 0x198a0000 ++ ++ ++//vin clk registers ++#define CLK_VIN_SRC_CTRL 0x188 ++#define CLK_ISP0_AXI_CTRL 0x190 ++#define CLK_ISP0NOC_AXI_CTRL 0x194 ++#define CLK_ISPSLV_AXI_CTRL 0x198 ++#define CLK_ISP1_AXI_CTRL 0x1A0 ++#define CLK_ISP1NOC_AXI_CTRL 0x1A4 ++#define CLK_VIN_AXI 0x1AC ++#define CLK_VINNOC_AXI 0x1B0 ++ ++ ++#define CLK_DOM4_APB_FUNC 0x0 ++#define CLK_MUX_SEL 0xffffff ++ ++#define CLK_MIPI_RX0_PXL 0x4 ++ ++#define CLK_DVP_INV 0x8 ++#define CLK_U0_VIN_PCLK 0x18 ++#define CLK_U0_VIN_PCLK_ICG (0x1<<31) ++ ++#define CLK_U0_VIN_SYS_CLK 0x1c ++#define CLK_U0_VIN_CLK_P_AXIWR 0x30 ++#define CLK_U0_VIN_MUX_SEL (BIT(24) | BIT(25) | BIT(26) | BIT(27) | BIT(28) | BIT(29)) ++ ++#define CLK_U0_VIN_PIXEL_CLK_IF0 0x20 ++#define CLK_U0_VIN_PIXEL_CLK_IF1 0x24 ++#define CLK_U0_VIN_PIXEL_CLK_IF2 0x28 ++#define CLK_U0_VIN_PIXEL_CLK_IF3 0x2c ++ ++#define CLK_U0_VIN_CLK_P_AXIWR 0x30 ++ ++#define CLK_U0_ISPV2_TOP_WRAPPER_CLK_C 0x34u ++#define CLK_U0_ISPV2_MUX_SEL (0x1<<24 | 0x1<<25 | 0x1<<26 | 0x1<<27 | 0x1<<28 | 0x1<< 29) ++ ++#define CLK_U0_ISPV2_CLK_ICG (0x1<<31) ++ ++#define SOFTWARE_RESET_ASSERT0_ASSERT_SET 0x38U ++#define SOFTWARE_RESET_ASSERT0_ASSERT_SET_STATE 0x3CU ++#define RST_U0_ISPV2_TOP_WRAPPER_RST_P BIT(0) ++#define RST_U0_ISPV2_TOP_WRAPPER_RST_C BIT(1) ++#define RSTN_U0_VIN_RST_N_PCLK BIT(4) ++#define RSTN_U0_VIN_RST_N_SYS_CLK BIT(9) ++#define RSTN_U0_VIN_RST_P_AXIRD BIT(10) ++#define RSTN_U0_VIN_RST_P_AXIWR BIT(11) ++ ++ ++#define CLK_POLARITY (0x1<<30) ++ ++#define M31DPHY_APBCFGSAIF__SYSCFG_0 0x0 ++#define M31DPHY_APBCFGSAIF__SYSCFG_4 0x4 ++#define M31DPHY_APBCFGSAIF__SYSCFG_8 0x8 ++#define M31DPHY_APBCFGSAIF__SYSCFG_12 0xc ++#define M31DPHY_APBCFGSAIF__SYSCFG_16 0x10 ++#define M31DPHY_APBCFGSAIF__SYSCFG_20 0x14 ++#define M31DPHY_APBCFGSAIF__SYSCFG_24 0x18 ++#define M31DPHY_APBCFGSAIF__SYSCFG_28 0x1c ++#define M31DPHY_APBCFGSAIF__SYSCFG_32 0x20 ++#define M31DPHY_APBCFGSAIF__SYSCFG_36 0x24 ++#define M31DPHY_APBCFGSAIF__SYSCFG_40 0x28 ++#define M31DPHY_APBCFGSAIF__SYSCFG_44 0x2c ++#define M31DPHY_APBCFGSAIF__SYSCFG_48 0x30 ++#define M31DPHY_APBCFGSAIF__SYSCFG_52 0x34 ++#define M31DPHY_APBCFGSAIF__SYSCFG_56 0x38 ++#define M31DPHY_APBCFGSAIF__SYSCFG_60 0x3c ++#define M31DPHY_APBCFGSAIF__SYSCFG_64 0x40 ++#define M31DPHY_APBCFGSAIF__SYSCFG_68 0x44 ++#define M31DPHY_APBCFGSAIF__SYSCFG_72 0x48 ++#define M31DPHY_APBCFGSAIF__SYSCFG_76 0x4c ++#define M31DPHY_APBCFGSAIF__SYSCFG_80 0x50 ++#define M31DPHY_APBCFGSAIF__SYSCFG_84 0x54 ++#define M31DPHY_APBCFGSAIF__SYSCFG_88 0x58 ++#define M31DPHY_APBCFGSAIF__SYSCFG_92 0x5c ++#define M31DPHY_APBCFGSAIF__SYSCFG_96 0x60 ++#define M31DPHY_APBCFGSAIF__SYSCFG_100 0x64 ++#define M31DPHY_APBCFGSAIF__SYSCFG_104 0x68 ++#define M31DPHY_APBCFGSAIF__SYSCFG_108 0x6c ++#define M31DPHY_APBCFGSAIF__SYSCFG_112 0x70 ++#define M31DPHY_APBCFGSAIF__SYSCFG_116 0x74 ++#define M31DPHY_APBCFGSAIF__SYSCFG_120 0x78 ++#define M31DPHY_APBCFGSAIF__SYSCFG_124 0x7c ++#define M31DPHY_APBCFGSAIF__SYSCFG_128 0x80 ++#define M31DPHY_APBCFGSAIF__SYSCFG_132 0x84 ++#define M31DPHY_APBCFGSAIF__SYSCFG_136 0x88 ++#define M31DPHY_APBCFGSAIF__SYSCFG_140 0x8c ++#define M31DPHY_APBCFGSAIF__SYSCFG_144 0x90 ++#define M31DPHY_APBCFGSAIF__SYSCFG_184 0xb8 ++ ++//pmu registers ++#define SW_DEST_POWER_ON 0x0C ++#define SW_DEST_POWER_OFF 0x10 ++#define SW_ENCOURAGE 0x44 ++ ++ ++//isp clk registers ++#define CLK_DPHY_CFGCLK_ISPCORE_2X_CTRL 0x00 ++#define CLK_DPHY_REFCLK_ISPCORE_2X_CTRL 0x04 ++#define CLK_DPHY_TXCLKESC_IN_CTRL 0x08 ++#define CLK_MIPI_RX0_PXL_CTRL 0x0c ++#define CLK_MIPI_RX1_PXL_CTRL 0x10 ++#define CLK_MIPI_RX0_PXL_0_CTRL 0X14 ++#define CLK_MIPI_RX0_PXL_1_CTRL 0X18 ++#define CLK_MIPI_RX0_PXL_2_CTRL 0X1C ++#define CLK_MIPI_RX0_PXL_3_CTRL 0X20 ++#define CLK_MIPI_RX0_SYS0_CTRL 0x24 ++#define CLK_MIPI_RX1_PXL_0_CTRL 0X28 ++#define CLK_MIPI_RX1_PXL_1_CTRL 0X2C ++#define CLK_MIPI_RX1_PXL_2_CTRL 0X30 ++#define CLK_MIPI_RX1_PXL_3_CTRL 0X34 ++#define CLK_MIPI_RX1_SYS1_CTRL 0x38 ++#define CLK_ISP_CTRL 0x3c ++#define CLK_ISP_2X_CTRL 0x40 ++#define CLK_ISP_MIPI_CTRL 0x44 ++#define CLK_C_ISP_CTRL 0x64 ++#define CLK_CSI2RX0_APB_CTRL 0x58 ++ ++ ++#define CLK_VIN_AXI_WR_CTRL 0x5C ++ ++#define SOFTWARE_RESET_ASSERT0 0x0 ++#define SOFTWARE_RESET_ASSERT1 0x4 ++#define SOFTWARE_RESET_STATUS 0x4 ++ ++#define IOPAD_REG81 0x144 ++#define IOPAD_REG82 0x148 ++#define IOPAD_REG83 0x14C ++#define IOPAD_REG84 0x150 ++#define IOPAD_REG85 0x154 ++#define IOPAD_REG86 0x158 ++#define IOPAD_REG87 0x15C ++#define IOPAD_REG88 0x160 ++#define IOPAD_REG89 0x164 ++ ++//sys control REG DEFINE ++#define SYSCONSAIF_SYSCFG_0 0X0 ++#define U0_VIN_SCFG_SRAM_CONFIG (BIT(0) | BIT(1)) ++ ++#define SYSCONSAIF_SYSCFG_4 0x4 ++#define U0_VIN_CNFG_AXIRD_END_ADDR 0xffffffff ++#define SYSCONSAIF_SYSCFG_8 0x8 ++#define U0_VIN_CNFG_AXIRD_LINE_CNT_END (BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9) | BIT(10) | BIT(11) | BIT(12) | BIT(13)) ++#define U0_VIN_CNFG_AXIRD_LINE_CNT_START (BIT(14) | BIT(15) | BIT(16) | BIT(17) | BIT(18) | BIT(19) | BIT(20) | BIT(21) | BIT(22) | BIT(23) | BIT(24) | BIT(25)) ++#define SYSCONSAIF_SYSCFG_12 0xc ++#define U0_VIN_CNFG_AXIRD_PIX_CNT_END (BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9) | BIT(10) | BIT(11) | BIT(12)) ++#define U0_VIN_CNFG_AXIRD_PIX_CNT_START (BIT(13) | BIT(14) | BIT(15) | BIT(16) | BIT(17) | BIT(18) | BIT(19) | BIT(20) | BIT(21) | BIT(22) | BIT(23) | BIT(24) | BIT(25)) ++#define U0_VIN_CNFG_AXIRD_PIX_CT (BIT(26) | BIT(27)) ++#define SYSCONSAIF_SYSCFG_16 0x10 ++#define U0_VIN_CNFG_AXIRD_START_ADDR 0xFFFFFFFF ++#define SYSCONSAIF_SYSCFG_20 0x14 ++#define U0_VIN_CNFG_AXIWR0_EN BIT(4) ++#define U0_VIN_CNFG_AXIWR0_CHANNEL_SEL (BIT(0) | BIT(1) | BIT(2) | BIT(3)) ++#define SYSCONSAIF_SYSCFG_24 0x18 ++#define U0_VIN_CNFG_AXIWR0_END_ADDR 0xFFFFFFFF ++ ++#define SYSCONSAIF_SYSCFG_28 0x1c ++#define U0_VIN_CNFG_AXIWR0_INTR_CLEAN BIT(0) ++#define U0_VIN_CNFG_AXIWR0_MASK BIT(1) ++#define U0_VIN_CNFG_AXIWR0_PIX_CNT_END (BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9) | BIT(10) | BIT(11) | BIT(12)) ++#define U0_VIN_CNFG_AXIWR0_PIX_CT (BIT(13) | BIT(14)) ++#define UO_VIN_CNFG_AXIWR0_PIXEL_HEIGH_BIT_SEL (BIT(15) | BIT(16)) ++#define SYSCONSAIF_SYSCFG_32 0x20 ++ ++#define SYSCONSAIF_SYSCFG_36 0x24 ++#define UO_VIN_CNFG_COLOR_BAR_EN BIT(0) ++#define U0_VIN_CNFG_DVP_HS_POS (0x1<<1) ++#define U0_VIN_CNFG_DVP_SWAP_EN BIT(2) ++#define U0_VIN_CNFG_DVP_VS_POS (0x1<<3) ++#define U0_VIN_CNFG_GEN_EN_AXIRD BIT(4) ++#define U0_VIN_CNFG_ISP_DVP_EN0 BIT(5) ++#define U0_VIN_CNFG_MIPI_BYTE_EN_ISP0 (BIT(6) |BIT(7)) ++#define U0_VIN_CNFG_P_I_MIPI_CHANNEL_SEL0 (BIT(8) |BIT(9) | BIT(10) | BIT(11)) ++#define U0_VIN_CNFG_P_I_MIPI_HEADER_EN0 BIT(12) ++ ++#define U0_VIN_CNFG_PIX_NUM (0x1<<13 | 0x1<<14 | 0x1<<15 | 0x1<<16) ++#define U0_VIN_CNFG_AXIRD_AXI_CNT_END (BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9) | BIT(10) | BIT(11) | BIT(12) | BIT(13)) ++ ++#define U0_VIN_CNFG_AXI_DVP_EN BIT(2) ++#define U0_VIN_CNFG_AXIRD_INTR_MASK BIT(1) ++#define U0_VIN_CNFG_AXIWRD_INTR_MASK BIT(1) ++#define U0_VIN_CNFG_AXIWR0_START_ADDR 0xffffffff ++#define U0_VIN_CNFG_COLOR_BAR_EN 0X0 ++#define U0_VIN_CNFG_AXIWR0_PIX_CNT_CT (BIT(13) | BIT(14)) ++#define U0_VIN_CNFG_AXIWR0_PIX_CNT_CNT_END (BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9) | BIT(10) | BIT(11) | BIT(12)) ++#define U0_VIN_CNFG_AXIWR0_PIXEL_HITH_BIT_SEL (BIT(15) | BIT(16)) ++ ++#define SYSCTRL_REG4 0x10 ++#define SYSCTRL_DPHY_CTRL 0x14 ++#define SYSCTRL_VIN_AXI_CTRL 0x18 ++#define SYSCTRL_VIN_WR_START_ADDR 0x28 ++#define SYSCTRL_VIN_RD_END_ADDR 0x2C ++#define SYSCTRL_VIN_WR_PIX_TOTAL 0x30 ++#define SYSCTRL_VIN_RD_PIX_TOTAL 0x34 ++#define SYSCTRL_VIN_RW_CTRL 0x38 ++#define SYSCTRL_VIN_SRC_CHAN_SEL 0x24 ++#define SYSCTRL_VIN_SRC_DW_SEL 0x40 ++#define SYSCTRL_VIN_RD_VBLANK 0x44 ++#define SYSCTRL_VIN_RD_VEND 0x48 ++#define SYSCTRL_VIN_RD_HBLANK 0x4C ++#define SYSCTRL_VIN_RD_HEND 0x50 ++#define SYSCTRL_VIN_INTP_CTRL 0x54 ++ ++#define ISP_NO_SCALE_ENABLE (0x1<<20) ++#define ISP_MULTI_FRAME_ENABLE (0x1<<17) ++#define ISP_SS0_ENABLE (0x1<<11) ++#define ISP_SS1_ENABLE (0x1<<12) ++#define ISP_RESET (0x1<<1) ++#define ISP_ENBALE (0x1) ++ ++ ++ ++ //ISP REG DEFINE ++#define ISP_REG_DVP_POLARITY_CFG 0x00000014 ++#define ISP_REG_RAW_FORMAT_CFG 0x00000018 ++#define ISP_REG_CFA_MODE 0x00000A1C ++#define ISP_REG_PIC_CAPTURE_START_CFG 0x0000001C ++#define ISP_REG_PIC_CAPTURE_END_CFG 0x00000020 ++#define ISP_REG_PIPELINE_XY_SIZE 0x00000A0C ++#define ISP_REG_Y_PLANE_START_ADDR 0x00000A80 ++#define ISP_REG_UV_PLANE_START_ADDR 0x00000A84 ++#define ISP_REG_STRIDE 0x00000A88 ++#define ISP_REG_PIXEL_COORDINATE_GEN 0x00000A8C ++#define ISP_REG_PIXEL_AXI_CONTROL 0x00000A90 ++#define ISP_REG_SS_AXI_CONTROL 0x00000AC4 ++#define ISP_REG_RGB_TO_YUV_COVERSION0 0x00000E40 ++#define ISP_REG_RGB_TO_YUV_COVERSION1 0x00000E44 ++#define ISP_REG_RGB_TO_YUV_COVERSION2 0x00000E48 ++#define ISP_REG_RGB_TO_YUV_COVERSION3 0x00000E4C ++#define ISP_REG_RGB_TO_YUV_COVERSION4 0x00000E50 ++#define ISP_REG_RGB_TO_YUV_COVERSION5 0x00000E54 ++#define ISP_REG_RGB_TO_YUV_COVERSION6 0x00000E58 ++#define ISP_REG_RGB_TO_YUV_COVERSION7 0x00000E5C ++#define ISP_REG_RGB_TO_YUV_COVERSION8 0x00000E60 ++#define ISP_REG_CSI_MODULE_CFG 0x00000010 ++#define ISP_REG_ISP_CTRL_1 0x00000A08 ++#define ISP_REG_ISP_CTRL_0 0x00000A00 ++#define ISP_REG_DC_AXI_ID 0x00000044 ++#define ISP_REG_CSI_INPUT_EN_AND_STATUS 0x00000000 ++ ++//CSI registers ++#define DEVICE_CONFIG 0x00 ++#define SOFT_RESET 0x04 ++#define STATIC_CFG 0x08 ++#define ERROR_BYPASS_CFG 0x10 ++#define MONITOR_IRQS 0x18 ++#define MONITOR_IRQS_MASK_CFG 0x1c ++#define INFO_IRQS 0x20 ++#define INFO_IRQS_MASK_CFG 0x24 ++#define ERROR_IRQS 0x28 ++#define ERROR_IRQS_MASK_CFG 0x2c ++#define DPHY_LANE_CONTROL 0x40 ++#define DPHY_STATUS 0x48 ++#define DPHY_ERR_STATUS_IRQ 0x4C ++#define DPHY_ERR_IRQ_MASK_CFG 0x50 ++#define INTEGRATION_DEBUG 0x60 ++#define ERROR_DEBUG 0x74 ++ ++#define STREAM0_CTRL 0x100 ++#define STREAM0_STATUS 0x104 ++#define STREAM0_DATA_CFG 0x108 ++#define STREAM0_CFG 0x10c ++#define STREAM0_MONITOR_CTRL 0x110 ++#define STREAM0_MONITOR_FRAME 0x114 ++#define STREAM0_MONITOR_LB 0x118 ++#define STREAM0_TIMER 0x11c ++#define STREAM0_FCC_CFG 0x120 ++#define STREAM0_FCC_CTRL 0x124 ++#define STREAM0_FIFO_FILL_LVL 0x128 ++ ++//m31_dphy registers ++#define M31DPHY_APBCFGSAIF__SYSCFG_188 0xbc ++#define M31DPHY_APBCFGSAIF__SYSCFG_192 0xc0 ++#define M31DPHY_APBCFGSAIF__SYSCFG_196 0xc4 ++#define M31DPHY_APBCFGSAIF__SYSCFG_200 0xc8 ++ ++typedef enum ++{ ++ DT_RAW6 = 0x28, ++ DT_RAW7 = 0x29, ++ DT_RAW8 = 0x2a, ++ DT_RAW10 = 0x2b, ++ DT_RAW12 = 0x2c, ++ DT_RAW14 = 0x2d, ++} mipicam_data_type_t; ++ ++ ++enum VIN_SOURCE_FORMAT { ++ SRC_COLORBAR_VIN_ISP = 0, ++ SRC_DVP_SENSOR_VIN, ++ SRC_DVP_SENSOR_VIN_ISP,//need replace sensor ++ SRC_CSI2RX_VIN_ISP, ++ SRC_DVP_SENSOR_VIN_OV5640, ++}; ++ ++struct reg_name { ++ char name[10]; ++}; ++ ++typedef struct ++{ ++ int dlane_nb; ++ int dlane_map[4]; ++ int dlane_en[4]; ++ int dlane_pn_swap[4]; ++ int clane_nb; ++ int clane_map[2]; ++ int clane_pn_swap[2]; ++} csi2rx_dphy_cfg_t; ++ ++typedef struct ++{ ++ int lane_nb; ++ int dlane_map[4]; ++ int dt; ++ int hsize; ++ int vsize; ++} csi2rx_cfg_t; ++ ++ ++typedef struct ++{ ++ int mipi_id, w, h, dt, bpp, fps,lane; ++ u8 clane_swap; ++ u8 clane_pn_swap; ++ u8 dlane_swap[4]; ++ u8 dlane_pn_swap[4]; ++} csi_format; ++ ++struct vin_params { ++ void *paddr; ++ unsigned long size; ++}; ++ ++struct vin_buf { ++ void *vaddr; ++ dma_addr_t paddr; ++ u32 size; ++}; ++ ++struct vin_framesize { ++ u32 width; ++ u32 height; ++}; ++ ++struct vin_format { ++ enum VIN_SOURCE_FORMAT format; ++ u8 fps; ++}; ++ ++struct stf_vin_dev { ++ /* Protects the access of variables shared within the interrupt */ ++ spinlock_t irqlock; ++ int irq; ++ struct device *dev; ++ struct cdev vin_cdev; ++ void __iomem *base; ++ void __iomem *csi2rx_base; ++ void __iomem *clkgen_base; ++ void __iomem *rstgen_base; ++ void __iomem *sysctrl_base; ++ void __iomem *isp_base; ++ void __iomem *vin_top_clkgen_base; ++ void __iomem *vin_top_rstgen_base; ++ void __iomem *vin_top_iopad_base; ++ void __iomem *pmu_test; ++ void __iomem *sys_crg; ++ struct vin_framesize frame; ++ struct vin_format format; ++ bool isp; ++ int isp_irq; ++ int isp_csi_irq; ++ int isp_scd_irq; ++ int isp_irq_csiline; ++ u32 major; ++ struct vin_buf buf; ++ ++ wait_queue_head_t wq; ++ bool condition; ++ int odd; ++ ++ csi_format csi_fmt; ++}; ++ ++extern int vin_notifier_register(struct notifier_block *nb); ++extern void vin_notifier_unregister(struct notifier_block *nb); ++extern int vin_notifier_call(unsigned long e, void *v); ++#endif diff --git a/target/linux/starfive/patches-6.6/0086-dt-bindings-media-i2c-Add-IMX708-CMOS-sensor-binding.patch b/target/linux/starfive/patches-6.6/0086-dt-bindings-media-i2c-Add-IMX708-CMOS-sensor-binding.patch new file mode 100644 index 0000000000..c2a97a1873 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0086-dt-bindings-media-i2c-Add-IMX708-CMOS-sensor-binding.patch @@ -0,0 +1,135 @@ +From baec350a0994467584e7e390746e0bb365957a89 Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Tue, 13 Jun 2023 16:55:23 +0800 +Subject: [PATCH 086/116] dt-bindings: media: i2c: Add IMX708 CMOS sensor + binding + +Add YAML devicetree binding for IMX708 CMOS image sensor. +Let's also add a MAINTAINERS entry for the binding and driver. + +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +--- + .../devicetree/bindings/media/i2c/imx708.yaml | 117 ++++++++++++++++++ + 1 file changed, 117 insertions(+) + create mode 100644 Documentation/devicetree/bindings/media/i2c/imx708.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/media/i2c/imx708.yaml +@@ -0,0 +1,117 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/media/i2c/imx708.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Sony 1/2.3-Inch 12Mpixel CMOS Digital Image Sensor ++ ++maintainers: ++ - Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.om> ++ ++description: |- ++ The Sony IMX708 is a 1/2.3-inch CMOS active pixel digital mage sensor ++ with an active array size of 4608H x 2592V. It is rogrammable through ++ I2C interface. The I2C address is fixed to 0x1A as per ensor data sheet. ++ Image data is sent through MIPI CSI-2, which is configured s either 2 or ++ 4 data lanes. ++ ++properties: ++ compatible: ++ const: sony,imx708 ++ ++ reg: ++ description: I2C device address ++ maxItems: 1 ++ ++ clocks: ++ maxItems: 1 ++ ++ VDIG-supply: ++ description: ++ Digital I/O voltage supply, 1.1 volts ++ ++ VANA1-supply: ++ description: ++ Analog1 voltage supply, 2.8 volts ++ ++ VANA2-supply: ++ description: ++ Analog2 voltage supply, 1.8 volts ++ ++ VDDL-supply: ++ description: ++ Digital core voltage supply, 1.8 volts ++ ++ reset-gpios: ++ description: |- ++ Reference to the GPIO connected to the xclr pin, if any. ++ Must be released (set high) after all supplies and INCK re applied. ++ ++ # See ../video-interfaces.txt for more details ++ port: ++ type: object ++ properties: ++ endpoint: ++ type: object ++ properties: ++ data-lanes: ++ description: |- ++ The sensor supports either two-lane, or our-lane operation. ++ For two-lane operation the property must be set o <1 2>. ++ items: ++ - const: 1 ++ - const: 2 ++ ++ clock-noncontinuous: ++ type: boolean ++ description: |- ++ MIPI CSI-2 clock is non-continuous if this roperty is present, ++ otherwise it's continuous. ++ ++ link-frequencies: ++ allOf: ++ - $ref: /schemas/types.yaml#/definitions/int64-array ++ description: ++ Allowed data bus frequencies. ++ ++ required: ++ - link-frequencies ++ ++required: ++ - compatible ++ - reg ++ - clocks ++ - VANA1-supply ++ - VANA2-supply ++ - VDIG-supply ++ - VDDL-supply ++ - port ++ ++additionalProperties: false ++ ++examples: ++ - | ++ i2c0 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ imx708: sensor@1a { ++ compatible = "sony,imx708"; ++ reg = <0x1a>; ++ clocks = <&imx708_clk>; ++ VANA1-supply = <&imx708_vana1>; /* 1.8v */ ++ VANA2-supply = <&imx708_vana2>; /* 2.8v */ ++ VDIG-supply = <&imx708_vdig>; /* 1.1v */ ++ VDDL-supply = <&imx708_vddl>; /* 1.8v */ ++ ++ port { ++ imx708_0: endpoint { ++ remote-endpoint = <&csi1_ep>; ++ data-lanes = <1 2>; ++ clock-noncontinuous; ++ link-frequencies = /bits/ 64 <450000000>; ++ }; ++ }; ++ }; ++ }; diff --git a/target/linux/starfive/patches-6.6/0087-media-i2c-Add-imx708-support.patch b/target/linux/starfive/patches-6.6/0087-media-i2c-Add-imx708-support.patch new file mode 100644 index 0000000000..51e61817f8 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0087-media-i2c-Add-imx708-support.patch @@ -0,0 +1,1971 @@ +From e89556802c5d29a80f5887995ab257d4e826a90f Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Mon, 3 Apr 2023 13:52:17 +0800 +Subject: [PATCH 087/116] media: i2c: Add imx708 support + +Add imx708 support. + +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +--- + drivers/media/i2c/Kconfig | 13 + + drivers/media/i2c/Makefile | 1 + + drivers/media/i2c/imx708.c | 1921 ++++++++++++++++++++++++++++++++++++ + 3 files changed, 1935 insertions(+) + create mode 100644 drivers/media/i2c/imx708.c + +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -201,6 +201,19 @@ config VIDEO_IMX415 + To compile this driver as a module, choose M here: the + module will be called imx415. + ++config VIDEO_IMX708 ++ tristate "Sony IMX708 sensor support" ++ depends on I2C && VIDEO_DEV ++ select MEDIA_CONTROLLER ++ select VIDEO_V4L2_SUBDEV_API ++ select V4L2_FWNODE ++ help ++ This is a Video4Linux2 sensor driver for the Sony ++ IMX708 camera. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called imx708. ++ + config VIDEO_MAX9271_LIB + tristate + +--- a/drivers/media/i2c/Makefile ++++ b/drivers/media/i2c/Makefile +@@ -53,6 +53,7 @@ obj-$(CONFIG_VIDEO_IMX335) += imx335.o + obj-$(CONFIG_VIDEO_IMX355) += imx355.o + obj-$(CONFIG_VIDEO_IMX412) += imx412.o + obj-$(CONFIG_VIDEO_IMX415) += imx415.o ++obj-$(CONFIG_VIDEO_IMX708) += imx708.o + obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o + obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o + obj-$(CONFIG_VIDEO_KS0127) += ks0127.o +--- /dev/null ++++ b/drivers/media/i2c/imx708.c +@@ -0,0 +1,1921 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * A V4L2 driver for Sony IMX708 cameras. ++ * Copyright (C) 2022-2023, Raspberry Pi Ltd ++ * ++ * Based on Sony imx477 camera driver ++ * Copyright (C) 2020 Raspberry Pi Ltd ++ */ ++#include <asm/unaligned.h> ++#include <linux/clk.h> ++#include <linux/delay.h> ++#include <linux/gpio/consumer.h> ++#include <linux/i2c.h> ++#include <linux/module.h> ++#include <linux/pm_runtime.h> ++#include <linux/regulator/consumer.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-event.h> ++#include <media/v4l2-fwnode.h> ++#include <media/v4l2-mediabus.h> ++ ++#define IMX708_REG_VALUE_08BIT 1 ++#define IMX708_REG_VALUE_16BIT 2 ++ ++/* Chip ID */ ++#define IMX708_REG_CHIP_ID 0x0016 ++#define IMX708_CHIP_ID 0x0708 ++ ++#define IMX708_REG_MODE_SELECT 0x0100 ++#define IMX708_MODE_STANDBY 0x00 ++#define IMX708_MODE_STREAMING 0x01 ++ ++#define IMX708_REG_ORIENTATION 0x101 ++ ++#define IMX708_XCLK_FREQ 24000000 ++ ++#define IMX708_DEFAULT_LINK_FREQ 450000000 ++ ++/* V_TIMING internal */ ++#define IMX708_REG_FRAME_LENGTH 0x0340 ++#define IMX708_FRAME_LENGTH_MAX 0xffff ++ ++/* Long exposure multiplier */ ++#define IMX708_LONG_EXP_SHIFT_MAX 7 ++#define IMX708_LONG_EXP_SHIFT_REG 0x3100 ++ ++/* Exposure control */ ++#define IMX708_REG_EXPOSURE 0x0202 ++#define IMX708_EXPOSURE_OFFSET 48 ++#define IMX708_EXPOSURE_DEFAULT 0x640 ++#define IMX708_EXPOSURE_STEP 1 ++#define IMX708_EXPOSURE_MIN 1 ++#define IMX708_EXPOSURE_MAX (IMX708_FRAME_LENGTH_MAX - \ ++ IMX708_EXPOSURE_OFFSET) ++ ++/* Analog gain control */ ++#define IMX708_REG_ANALOG_GAIN 0x0204 ++#define IMX708_ANA_GAIN_MIN 112 ++#define IMX708_ANA_GAIN_MAX 960 ++#define IMX708_ANA_GAIN_STEP 1 ++#define IMX708_ANA_GAIN_DEFAULT IMX708_ANA_GAIN_MIN ++ ++/* Digital gain control */ ++#define IMX708_REG_DIGITAL_GAIN 0x020e ++#define IMX708_DGTL_GAIN_MIN 0x0100 ++#define IMX708_DGTL_GAIN_MAX 0xffff ++#define IMX708_DGTL_GAIN_DEFAULT 0x0100 ++#define IMX708_DGTL_GAIN_STEP 1 ++ ++/* Colour balance controls */ ++#define IMX708_REG_COLOUR_BALANCE_RED 0x0b90 ++#define IMX708_REG_COLOUR_BALANCE_BLUE 0x0b92 ++#define IMX708_COLOUR_BALANCE_MIN 0x01 ++#define IMX708_COLOUR_BALANCE_MAX 0xffff ++#define IMX708_COLOUR_BALANCE_STEP 0x01 ++#define IMX708_COLOUR_BALANCE_DEFAULT 0x100 ++ ++/* Test Pattern Control */ ++#define IMX708_REG_TEST_PATTERN 0x0600 ++#define IMX708_TEST_PATTERN_DISABLE 0 ++#define IMX708_TEST_PATTERN_SOLID_COLOR 1 ++#define IMX708_TEST_PATTERN_COLOR_BARS 2 ++#define IMX708_TEST_PATTERN_GREY_COLOR 3 ++#define IMX708_TEST_PATTERN_PN9 4 ++ ++/* Test pattern colour components */ ++#define IMX708_REG_TEST_PATTERN_R 0x0602 ++#define IMX708_REG_TEST_PATTERN_GR 0x0604 ++#define IMX708_REG_TEST_PATTERN_B 0x0606 ++#define IMX708_REG_TEST_PATTERN_GB 0x0608 ++#define IMX708_TEST_PATTERN_COLOUR_MIN 0 ++#define IMX708_TEST_PATTERN_COLOUR_MAX 0x0fff ++#define IMX708_TEST_PATTERN_COLOUR_STEP 1 ++ ++#define IMX708_REG_BASE_SPC_GAINS_L 0x7b10 ++#define IMX708_REG_BASE_SPC_GAINS_R 0x7c00 ++ ++/* HDR exposure ratio (long:med == med:short) */ ++#define IMX708_HDR_EXPOSURE_RATIO 4 ++#define IMX708_REG_MID_EXPOSURE 0x3116 ++#define IMX708_REG_SHT_EXPOSURE 0x0224 ++#define IMX708_REG_MID_ANALOG_GAIN 0x3118 ++#define IMX708_REG_SHT_ANALOG_GAIN 0x0216 ++ ++/* IMX708 native and active pixel array size. */ ++#define IMX708_NATIVE_WIDTH 4640U ++#define IMX708_NATIVE_HEIGHT 2658U ++#define IMX708_PIXEL_ARRAY_LEFT 16U ++#define IMX708_PIXEL_ARRAY_TOP 24U ++#define IMX708_PIXEL_ARRAY_WIDTH 4608U ++#define IMX708_PIXEL_ARRAY_HEIGHT 2592U ++ ++struct imx708_reg { ++ u16 address; ++ u8 val; ++}; ++ ++struct imx708_reg_list { ++ unsigned int num_of_regs; ++ const struct imx708_reg *regs; ++}; ++ ++/* Mode : resolution and related config&values */ ++struct imx708_mode { ++ /* Frame width */ ++ unsigned int width; ++ ++ /* Frame height */ ++ unsigned int height; ++ ++ /* H-timing in pixels */ ++ unsigned int line_length_pix; ++ ++ /* Analog crop rectangle. */ ++ struct v4l2_rect crop; ++ ++ /* Highest possible framerate. */ ++ unsigned int vblank_min; ++ ++ /* Default framerate. */ ++ unsigned int vblank_default; ++ ++ /* Default register values */ ++ struct imx708_reg_list reg_list; ++ ++ /* Not all modes have the same pixel rate. */ ++ u64 pixel_rate; ++ ++ /* Not all modes have the same minimum exposure. */ ++ u32 exposure_lines_min; ++ ++ /* Not all modes have the same exposure lines step. */ ++ u32 exposure_lines_step; ++ ++ /* HDR flag, currently not used at runtime */ ++ bool hdr; ++}; ++ ++/* Default PDAF pixel correction gains */ ++static const u8 pdaf_gains[2][9] = { ++ { 0x4c, 0x4c, 0x4c, 0x46, 0x3e, 0x38, 0x35, 0x35, 0x35 }, ++ { 0x35, 0x35, 0x35, 0x38, 0x3e, 0x46, 0x4c, 0x4c, 0x4c } ++}; ++ ++static const struct imx708_reg mode_common_regs[] = { ++ {0x0100, 0x00}, ++ {0x0136, 0x18}, ++ {0x0137, 0x00}, ++ {0x33f0, 0x02}, ++ {0x33f1, 0x05}, ++ {0x3062, 0x00}, ++ {0x3063, 0x12}, ++ {0x3068, 0x00}, ++ {0x3069, 0x12}, ++ {0x306a, 0x00}, ++ {0x306b, 0x30}, ++ {0x3076, 0x00}, ++ {0x3077, 0x30}, ++ {0x3078, 0x00}, ++ {0x3079, 0x30}, ++ {0x5e54, 0x0c}, ++ {0x6e44, 0x00}, ++ {0xb0b6, 0x01}, ++ {0xe829, 0x00}, ++ {0xf001, 0x08}, ++ {0xf003, 0x08}, ++ {0xf00d, 0x10}, ++ {0xf00f, 0x10}, ++ {0xf031, 0x08}, ++ {0xf033, 0x08}, ++ {0xf03d, 0x10}, ++ {0xf03f, 0x10}, ++ {0x0112, 0x0a}, ++ {0x0113, 0x0a}, ++ {0x0114, 0x01}, ++ {0x0b8e, 0x01}, ++ {0x0b8f, 0x00}, ++ {0x0b94, 0x01}, ++ {0x0b95, 0x00}, ++ {0x3400, 0x01}, ++ {0x3478, 0x01}, ++ {0x3479, 0x1c}, ++ {0x3091, 0x01}, ++ {0x3092, 0x00}, ++ {0x3419, 0x00}, ++ {0xbcf1, 0x02}, ++ {0x3094, 0x01}, ++ {0x3095, 0x01}, ++ {0x3362, 0x00}, ++ {0x3363, 0x00}, ++ {0x3364, 0x00}, ++ {0x3365, 0x00}, ++ {0x0138, 0x01}, ++}; ++ ++/* 10-bit. */ ++static const struct imx708_reg mode_4608x2592_regs[] = { ++ {0x0342, 0x3d}, ++ {0x0343, 0x20}, ++ {0x0340, 0x0a}, ++ {0x0341, 0x59}, ++ {0x0344, 0x00}, ++ {0x0345, 0x00}, ++ {0x0346, 0x00}, ++ {0x0347, 0x00}, ++ {0x0348, 0x11}, ++ {0x0349, 0xff}, ++ {0x034a, 0x0a}, ++ {0x034b, 0x1f}, ++ {0x0220, 0x62}, ++ {0x0222, 0x01}, ++ {0x0900, 0x00}, ++ {0x0901, 0x11}, ++ {0x0902, 0x0a}, ++ {0x3200, 0x01}, ++ {0x3201, 0x01}, ++ {0x32d5, 0x01}, ++ {0x32d6, 0x00}, ++ {0x32db, 0x01}, ++ {0x32df, 0x00}, ++ {0x350c, 0x00}, ++ {0x350d, 0x00}, ++ {0x0408, 0x00}, ++ {0x0409, 0x00}, ++ {0x040a, 0x00}, ++ {0x040b, 0x00}, ++ {0x040c, 0x12}, ++ {0x040d, 0x00}, ++ {0x040e, 0x0a}, ++ {0x040f, 0x20}, ++ {0x034c, 0x12}, ++ {0x034d, 0x00}, ++ {0x034e, 0x0a}, ++ {0x034f, 0x20}, ++ {0x0301, 0x05}, ++ {0x0303, 0x02}, ++ {0x0305, 0x02}, ++ {0x0306, 0x00}, ++ {0x0307, 0x7c}, ++ {0x030b, 0x02}, ++ {0x030d, 0x04}, ++ {0x030e, 0x01}, ++ {0x030f, 0x2c}, ++ {0x0310, 0x01}, ++ {0x3ca0, 0x00}, ++ {0x3ca1, 0x64}, ++ {0x3ca4, 0x00}, ++ {0x3ca5, 0x00}, ++ {0x3ca6, 0x00}, ++ {0x3ca7, 0x00}, ++ {0x3caa, 0x00}, ++ {0x3cab, 0x00}, ++ {0x3cb8, 0x00}, ++ {0x3cb9, 0x08}, ++ {0x3cba, 0x00}, ++ {0x3cbb, 0x00}, ++ {0x3cbc, 0x00}, ++ {0x3cbd, 0x3c}, ++ {0x3cbe, 0x00}, ++ {0x3cbf, 0x00}, ++ {0x0202, 0x0a}, ++ {0x0203, 0x29}, ++ {0x0224, 0x01}, ++ {0x0225, 0xf4}, ++ {0x3116, 0x01}, ++ {0x3117, 0xf4}, ++ {0x0204, 0x00}, ++ {0x0205, 0x00}, ++ {0x0216, 0x00}, ++ {0x0217, 0x00}, ++ {0x0218, 0x01}, ++ {0x0219, 0x00}, ++ {0x020e, 0x01}, ++ {0x020f, 0x00}, ++ {0x3118, 0x00}, ++ {0x3119, 0x00}, ++ {0x311a, 0x01}, ++ {0x311b, 0x00}, ++ {0x341a, 0x00}, ++ {0x341b, 0x00}, ++ {0x341c, 0x00}, ++ {0x341d, 0x00}, ++ {0x341e, 0x01}, ++ {0x341f, 0x20}, ++ {0x3420, 0x00}, ++ {0x3421, 0xd8}, ++ {0xc428, 0x00}, ++ {0xc429, 0x04}, ++ {0x3366, 0x00}, ++ {0x3367, 0x00}, ++ {0x3368, 0x00}, ++ {0x3369, 0x00}, ++}; ++ ++static const struct imx708_reg mode_2x2binned_regs[] = { ++ {0x0342, 0x1e}, ++ {0x0343, 0x90}, ++ {0x0340, 0x05}, ++ {0x0341, 0x38}, ++ {0x0344, 0x00}, ++ {0x0345, 0x00}, ++ {0x0346, 0x00}, ++ {0x0347, 0x00}, ++ {0x0348, 0x11}, ++ {0x0349, 0xff}, ++ {0x034a, 0x0a}, ++ {0x034b, 0x1f}, ++ {0x0220, 0x62}, ++ {0x0222, 0x01}, ++ {0x0900, 0x01}, ++ {0x0901, 0x22}, ++ {0x0902, 0x08}, ++ {0x3200, 0x41}, ++ {0x3201, 0x41}, ++ {0x32d5, 0x00}, ++ {0x32d6, 0x00}, ++ {0x32db, 0x01}, ++ {0x32df, 0x00}, ++ {0x350c, 0x00}, ++ {0x350d, 0x00}, ++ {0x0408, 0x00}, ++ {0x0409, 0x00}, ++ {0x040a, 0x00}, ++ {0x040b, 0x00}, ++ {0x040c, 0x09}, ++ {0x040d, 0x00}, ++ {0x040e, 0x05}, ++ {0x040f, 0x10}, ++ {0x034c, 0x09}, ++ {0x034d, 0x00}, ++ {0x034e, 0x05}, ++ {0x034f, 0x10}, ++ {0x0301, 0x05}, ++ {0x0303, 0x02}, ++ {0x0305, 0x02}, ++ {0x0306, 0x00}, ++ {0x0307, 0x7a}, ++ {0x030b, 0x02}, ++ {0x030d, 0x04}, ++ {0x030e, 0x01}, ++ {0x030f, 0x2c}, ++ {0x0310, 0x01}, ++ {0x3ca0, 0x00}, ++ {0x3ca1, 0x3c}, ++ {0x3ca4, 0x00}, ++ {0x3ca5, 0x3c}, ++ {0x3ca6, 0x00}, ++ {0x3ca7, 0x00}, ++ {0x3caa, 0x00}, ++ {0x3cab, 0x00}, ++ {0x3cb8, 0x00}, ++ {0x3cb9, 0x1c}, ++ {0x3cba, 0x00}, ++ {0x3cbb, 0x08}, ++ {0x3cbc, 0x00}, ++ {0x3cbd, 0x1e}, ++ {0x3cbe, 0x00}, ++ {0x3cbf, 0x0a}, ++ {0x0202, 0x05}, ++ {0x0203, 0x08}, ++ {0x0224, 0x01}, ++ {0x0225, 0xf4}, ++ {0x3116, 0x01}, ++ {0x3117, 0xf4}, ++ {0x0204, 0x00}, ++ {0x0205, 0x70}, ++ {0x0216, 0x00}, ++ {0x0217, 0x70}, ++ {0x0218, 0x01}, ++ {0x0219, 0x00}, ++ {0x020e, 0x01}, ++ {0x020f, 0x00}, ++ {0x3118, 0x00}, ++ {0x3119, 0x70}, ++ {0x311a, 0x01}, ++ {0x311b, 0x00}, ++ {0x341a, 0x00}, ++ {0x341b, 0x00}, ++ {0x341c, 0x00}, ++ {0x341d, 0x00}, ++ {0x341e, 0x00}, ++ {0x341f, 0x90}, ++ {0x3420, 0x00}, ++ {0x3421, 0x6c}, ++ {0x3366, 0x00}, ++ {0x3367, 0x00}, ++ {0x3368, 0x00}, ++ {0x3369, 0x00}, ++}; ++ ++static const struct imx708_reg mode_2x2binned_720p_regs[] = { ++ {0x0342, 0x14}, ++ {0x0343, 0x60}, ++ {0x0340, 0x04}, ++ {0x0341, 0xb6}, ++ {0x0344, 0x03}, ++ {0x0345, 0x00}, ++ {0x0346, 0x01}, ++ {0x0347, 0xb0}, ++ {0x0348, 0x0e}, ++ {0x0349, 0xff}, ++ {0x034a, 0x08}, ++ {0x034b, 0x6f}, ++ {0x0220, 0x62}, ++ {0x0222, 0x01}, ++ {0x0900, 0x01}, ++ {0x0901, 0x22}, ++ {0x0902, 0x08}, ++ {0x3200, 0x41}, ++ {0x3201, 0x41}, ++ {0x32d5, 0x00}, ++ {0x32d6, 0x00}, ++ {0x32db, 0x01}, ++ {0x32df, 0x01}, ++ {0x350c, 0x00}, ++ {0x350d, 0x00}, ++ {0x0408, 0x00}, ++ {0x0409, 0x00}, ++ {0x040a, 0x00}, ++ {0x040b, 0x00}, ++ {0x040c, 0x06}, ++ {0x040d, 0x00}, ++ {0x040e, 0x03}, ++ {0x040f, 0x60}, ++ {0x034c, 0x06}, ++ {0x034d, 0x00}, ++ {0x034e, 0x03}, ++ {0x034f, 0x60}, ++ {0x0301, 0x05}, ++ {0x0303, 0x02}, ++ {0x0305, 0x02}, ++ {0x0306, 0x00}, ++ {0x0307, 0x76}, ++ {0x030b, 0x02}, ++ {0x030d, 0x04}, ++ {0x030e, 0x01}, ++ {0x030f, 0x2c}, ++ {0x0310, 0x01}, ++ {0x3ca0, 0x00}, ++ {0x3ca1, 0x3c}, ++ {0x3ca4, 0x01}, ++ {0x3ca5, 0x5e}, ++ {0x3ca6, 0x00}, ++ {0x3ca7, 0x00}, ++ {0x3caa, 0x00}, ++ {0x3cab, 0x00}, ++ {0x3cb8, 0x00}, ++ {0x3cb9, 0x0c}, ++ {0x3cba, 0x00}, ++ {0x3cbb, 0x04}, ++ {0x3cbc, 0x00}, ++ {0x3cbd, 0x1e}, ++ {0x3cbe, 0x00}, ++ {0x3cbf, 0x05}, ++ {0x0202, 0x04}, ++ {0x0203, 0x86}, ++ {0x0224, 0x01}, ++ {0x0225, 0xf4}, ++ {0x3116, 0x01}, ++ {0x3117, 0xf4}, ++ {0x0204, 0x00}, ++ {0x0205, 0x70}, ++ {0x0216, 0x00}, ++ {0x0217, 0x70}, ++ {0x0218, 0x01}, ++ {0x0219, 0x00}, ++ {0x020e, 0x01}, ++ {0x020f, 0x00}, ++ {0x3118, 0x00}, ++ {0x3119, 0x70}, ++ {0x311a, 0x01}, ++ {0x311b, 0x00}, ++ {0x341a, 0x00}, ++ {0x341b, 0x00}, ++ {0x341c, 0x00}, ++ {0x341d, 0x00}, ++ {0x341e, 0x00}, ++ {0x341f, 0x60}, ++ {0x3420, 0x00}, ++ {0x3421, 0x48}, ++ {0x3366, 0x00}, ++ {0x3367, 0x00}, ++ {0x3368, 0x00}, ++ {0x3369, 0x00}, ++}; ++ ++static const struct imx708_reg mode_hdr_regs[] = { ++ {0x0342, 0x14}, ++ {0x0343, 0x60}, ++ {0x0340, 0x0a}, ++ {0x0341, 0x5b}, ++ {0x0344, 0x00}, ++ {0x0345, 0x00}, ++ {0x0346, 0x00}, ++ {0x0347, 0x00}, ++ {0x0348, 0x11}, ++ {0x0349, 0xff}, ++ {0x034a, 0x0a}, ++ {0x034b, 0x1f}, ++ {0x0220, 0x01}, ++ {0x0222, IMX708_HDR_EXPOSURE_RATIO}, ++ {0x0900, 0x00}, ++ {0x0901, 0x11}, ++ {0x0902, 0x0a}, ++ {0x3200, 0x01}, ++ {0x3201, 0x01}, ++ {0x32d5, 0x00}, ++ {0x32d6, 0x00}, ++ {0x32db, 0x01}, ++ {0x32df, 0x00}, ++ {0x350c, 0x00}, ++ {0x350d, 0x00}, ++ {0x0408, 0x00}, ++ {0x0409, 0x00}, ++ {0x040a, 0x00}, ++ {0x040b, 0x00}, ++ {0x040c, 0x09}, ++ {0x040d, 0x00}, ++ {0x040e, 0x05}, ++ {0x040f, 0x10}, ++ {0x034c, 0x09}, ++ {0x034d, 0x00}, ++ {0x034e, 0x05}, ++ {0x034f, 0x10}, ++ {0x0301, 0x05}, ++ {0x0303, 0x02}, ++ {0x0305, 0x02}, ++ {0x0306, 0x00}, ++ {0x0307, 0xa2}, ++ {0x030b, 0x02}, ++ {0x030d, 0x04}, ++ {0x030e, 0x01}, ++ {0x030f, 0x2c}, ++ {0x0310, 0x01}, ++ {0x3ca0, 0x00}, ++ {0x3ca1, 0x00}, ++ {0x3ca4, 0x00}, ++ {0x3ca5, 0x00}, ++ {0x3ca6, 0x00}, ++ {0x3ca7, 0x28}, ++ {0x3caa, 0x00}, ++ {0x3cab, 0x00}, ++ {0x3cb8, 0x00}, ++ {0x3cb9, 0x30}, ++ {0x3cba, 0x00}, ++ {0x3cbb, 0x00}, ++ {0x3cbc, 0x00}, ++ {0x3cbd, 0x32}, ++ {0x3cbe, 0x00}, ++ {0x3cbf, 0x00}, ++ {0x0202, 0x0a}, ++ {0x0203, 0x2b}, ++ {0x0224, 0x0a}, ++ {0x0225, 0x2b}, ++ {0x3116, 0x0a}, ++ {0x3117, 0x2b}, ++ {0x0204, 0x00}, ++ {0x0205, 0x00}, ++ {0x0216, 0x00}, ++ {0x0217, 0x00}, ++ {0x0218, 0x01}, ++ {0x0219, 0x00}, ++ {0x020e, 0x01}, ++ {0x020f, 0x00}, ++ {0x3118, 0x00}, ++ {0x3119, 0x00}, ++ {0x311a, 0x01}, ++ {0x311b, 0x00}, ++ {0x341a, 0x00}, ++ {0x341b, 0x00}, ++ {0x341c, 0x00}, ++ {0x341d, 0x00}, ++ {0x341e, 0x00}, ++ {0x341f, 0x90}, ++ {0x3420, 0x00}, ++ {0x3421, 0x6c}, ++ {0x3360, 0x01}, ++ {0x3361, 0x01}, ++ {0x3366, 0x09}, ++ {0x3367, 0x00}, ++ {0x3368, 0x05}, ++ {0x3369, 0x10}, ++}; ++ ++/* Mode configs. Keep separate lists for when HDR is enabled or not. */ ++static const struct imx708_mode supported_modes_10bit_no_hdr[] = { ++ { ++ /* Full resolution. */ ++ .width = 4608, ++ .height = 2592, ++ .line_length_pix = 0x3d20, ++ .crop = { ++ .left = IMX708_PIXEL_ARRAY_LEFT, ++ .top = IMX708_PIXEL_ARRAY_TOP, ++ .width = 4608, ++ .height = 2592, ++ }, ++ .vblank_min = 58, ++ .vblank_default = 58, ++ .reg_list = { ++ .num_of_regs = ARRAY_SIZE(mode_4608x2592_regs), ++ .regs = mode_4608x2592_regs, ++ }, ++ .pixel_rate = 595200000, ++ .exposure_lines_min = 8, ++ .exposure_lines_step = 1, ++ .hdr = false ++ }, ++ { ++ /* regular 2x2 binned. */ ++ .width = 2304, ++ .height = 1296, ++ .line_length_pix = 0x1e90, ++ .crop = { ++ .left = IMX708_PIXEL_ARRAY_LEFT, ++ .top = IMX708_PIXEL_ARRAY_TOP, ++ .width = 4608, ++ .height = 2592, ++ }, ++ .vblank_min = 40, ++ .vblank_default = 1198, ++ .reg_list = { ++ .num_of_regs = ARRAY_SIZE(mode_2x2binned_regs), ++ .regs = mode_2x2binned_regs, ++ }, ++ .pixel_rate = 585600000, ++ .exposure_lines_min = 4, ++ .exposure_lines_step = 2, ++ .hdr = false ++ }, ++ { ++ /* 2x2 binned and cropped for 720p. */ ++ .width = 1536, ++ .height = 864, ++ .line_length_pix = 0x1460, ++ .crop = { ++ .left = IMX708_PIXEL_ARRAY_LEFT + 768, ++ .top = IMX708_PIXEL_ARRAY_TOP + 432, ++ .width = 3072, ++ .height = 1728, ++ }, ++ .vblank_min = 40, ++ .vblank_default = 2755, ++ .reg_list = { ++ .num_of_regs = ARRAY_SIZE(mode_2x2binned_720p_regs), ++ .regs = mode_2x2binned_720p_regs, ++ }, ++ .pixel_rate = 566400000, ++ .exposure_lines_min = 4, ++ .exposure_lines_step = 2, ++ .hdr = false ++ }, ++}; ++ ++static const struct imx708_mode supported_modes_10bit_hdr[] = { ++ { ++ /* There's only one HDR mode, which is 2x2 downscaled */ ++ .width = 2304, ++ .height = 1296, ++ .line_length_pix = 0x1460, ++ .crop = { ++ .left = IMX708_PIXEL_ARRAY_LEFT, ++ .top = IMX708_PIXEL_ARRAY_TOP, ++ .width = 4608, ++ .height = 2592, ++ }, ++ .vblank_min = 3673, ++ .vblank_default = 3673, ++ .reg_list = { ++ .num_of_regs = ARRAY_SIZE(mode_hdr_regs), ++ .regs = mode_hdr_regs, ++ }, ++ .pixel_rate = 777600000, ++ .exposure_lines_min = 8 * IMX708_HDR_EXPOSURE_RATIO * ++ IMX708_HDR_EXPOSURE_RATIO, ++ .exposure_lines_step = 2 * IMX708_HDR_EXPOSURE_RATIO * ++ IMX708_HDR_EXPOSURE_RATIO, ++ .hdr = true ++ } ++}; ++ ++/* ++ * The supported formats. ++ * This table MUST contain 4 entries per format, to cover the various flip ++ * combinations in the order ++ * - no flip ++ * - h flip ++ * - v flip ++ * - h&v flips ++ */ ++static const u32 codes[] = { ++ /* 10-bit modes. */ ++ MEDIA_BUS_FMT_SRGGB10_1X10, ++ MEDIA_BUS_FMT_SGRBG10_1X10, ++ MEDIA_BUS_FMT_SGBRG10_1X10, ++ MEDIA_BUS_FMT_SBGGR10_1X10, ++}; ++ ++static const char * const imx708_test_pattern_menu[] = { ++ "Disabled", ++ "Color Bars", ++ "Solid Color", ++ "Grey Color Bars", ++ "PN9" ++}; ++ ++static const int imx708_test_pattern_val[] = { ++ IMX708_TEST_PATTERN_DISABLE, ++ IMX708_TEST_PATTERN_COLOR_BARS, ++ IMX708_TEST_PATTERN_SOLID_COLOR, ++ IMX708_TEST_PATTERN_GREY_COLOR, ++ IMX708_TEST_PATTERN_PN9, ++}; ++ ++/* regulator supplies */ ++static const char * const imx708_supply_name[] = { ++ /* Supplies can be enabled in any order */ ++ "VANA1", /* Analog1 (2.8V) supply */ ++ "VANA2", /* Analog2 (1.8V) supply */ ++ "VDIG", /* Digital Core (1.1V) supply */ ++ "VDDL", /* IF (1.8V) supply */ ++}; ++ ++#define IMX708_NUM_SUPPLIES ARRAY_SIZE(imx708_supply_name) ++ ++/* ++ * Initialisation delay between XCLR low->high and the moment when the sensor ++ * can start capture (i.e. can leave software standby), given by T7 in the ++ * datasheet is 8ms. This does include I2C setup time as well. ++ * ++ * Note, that delay between XCLR low->high and reading the CCI ID register (T6 ++ * in the datasheet) is much smaller - 600us. ++ */ ++#define IMX708_XCLR_MIN_DELAY_US 8000 ++#define IMX708_XCLR_DELAY_RANGE_US 1000 ++ ++struct imx708 { ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ ++ struct v4l2_mbus_framefmt fmt; ++ ++ struct clk *xclk; ++ u32 xclk_freq; ++ ++ struct gpio_desc *reset_gpio; ++ struct regulator_bulk_data supplies[IMX708_NUM_SUPPLIES]; ++ ++ struct v4l2_ctrl_handler ctrl_handler; ++ /* V4L2 Controls */ ++ struct v4l2_ctrl *pixel_rate; ++ struct v4l2_ctrl *exposure; ++ struct v4l2_ctrl *vflip; ++ struct v4l2_ctrl *hflip; ++ struct v4l2_ctrl *vblank; ++ struct v4l2_ctrl *hblank; ++ struct v4l2_ctrl *red_balance; ++ struct v4l2_ctrl *blue_balance; ++ struct v4l2_ctrl *notify_gains; ++ struct v4l2_ctrl *hdr_mode; ++ ++ /* Current mode */ ++ const struct imx708_mode *mode; ++ ++ /* Mutex for serialized access */ ++ struct mutex mutex; ++ ++ /* Streaming on/off */ ++ bool streaming; ++ ++ /* Rewrite common registers on stream on? */ ++ bool common_regs_written; ++ ++ /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */ ++ unsigned int long_exp_shift; ++}; ++ ++static inline struct imx708 *to_imx708(struct v4l2_subdev *_sd) ++{ ++ return container_of(_sd, struct imx708, sd); ++} ++ ++static inline void get_mode_table(const struct imx708_mode **mode_list, ++ unsigned int *num_modes, ++ bool hdr_enable) ++{ ++ if (hdr_enable) { ++ *mode_list = supported_modes_10bit_hdr; ++ *num_modes = ARRAY_SIZE(supported_modes_10bit_hdr); ++ } else { ++ *mode_list = supported_modes_10bit_no_hdr; ++ *num_modes = ARRAY_SIZE(supported_modes_10bit_no_hdr); ++ } ++} ++ ++/* Read registers up to 2 at a time */ ++static int imx708_read_reg(struct imx708 *imx708, u16 reg, u32 len, u32 *val) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); ++ struct i2c_msg msgs[2]; ++ u8 addr_buf[2] = { reg >> 8, reg & 0xff }; ++ u8 data_buf[4] = { 0, }; ++ int ret; ++ ++ if (len > 4) ++ return -EINVAL; ++ ++ /* Write register address */ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = 0; ++ msgs[0].len = ARRAY_SIZE(addr_buf); ++ msgs[0].buf = addr_buf; ++ ++ /* Read data from register */ ++ msgs[1].addr = client->addr; ++ msgs[1].flags = I2C_M_RD; ++ msgs[1].len = len; ++ msgs[1].buf = &data_buf[4 - len]; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret != ARRAY_SIZE(msgs)) ++ return -EIO; ++ ++ *val = get_unaligned_be32(data_buf); ++ ++ return 0; ++} ++ ++/* Write registers up to 2 at a time */ ++static int imx708_write_reg(struct imx708 *imx708, u16 reg, u32 len, u32 val) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); ++ u8 buf[6]; ++ ++ if (len > 4) ++ return -EINVAL; ++ ++ put_unaligned_be16(reg, buf); ++ put_unaligned_be32(val << (8 * (4 - len)), buf + 2); ++ if (i2c_master_send(client, buf, len + 2) != len + 2) ++ return -EIO; ++ ++ return 0; ++} ++ ++/* Write a list of registers */ ++static int imx708_write_regs(struct imx708 *imx708, ++ const struct imx708_reg *regs, u32 len) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); ++ unsigned int i; ++ int ret; ++ ++ for (i = 0; i < len; i++) { ++ ret = imx708_write_reg(imx708, regs[i].address, 1, regs[i].val); ++ if (ret) { ++ dev_err_ratelimited(&client->dev, ++ "Failed to write reg 0x%4.4x. error = %d\n", ++ regs[i].address, ret); ++ ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++/* Get bayer order based on flip setting. */ ++static u32 imx708_get_format_code(struct imx708 *imx708) ++{ ++ unsigned int i; ++ ++ lockdep_assert_held(&imx708->mutex); ++ ++ i = (imx708->vflip->val ? 2 : 0) | ++ (imx708->hflip->val ? 1 : 0); ++ ++ return codes[i]; ++} ++ ++static void imx708_set_default_format(struct imx708 *imx708) ++{ ++ struct v4l2_mbus_framefmt *fmt = &imx708->fmt; ++ ++ /* Set default mode to max resolution */ ++ imx708->mode = &supported_modes_10bit_no_hdr[0]; ++ ++ /* fmt->code not set as it will always be computed based on flips */ ++ fmt->colorspace = V4L2_COLORSPACE_RAW; ++ 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 = imx708->mode->width; ++ fmt->height = imx708->mode->height; ++ fmt->field = V4L2_FIELD_NONE; ++} ++ ++static int imx708_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) ++{ ++ struct imx708 *imx708 = to_imx708(sd); ++ struct v4l2_mbus_framefmt *try_fmt_img = ++ v4l2_subdev_get_try_format(sd, fh->state, 0); ++ struct v4l2_rect *try_crop; ++ ++ mutex_lock(&imx708->mutex); ++ ++ /* Initialize try_fmt for the image pad */ ++ if (imx708->hdr_mode->val) { ++ try_fmt_img->width = supported_modes_10bit_hdr[0].width; ++ try_fmt_img->height = supported_modes_10bit_hdr[0].height; ++ } else { ++ try_fmt_img->width = supported_modes_10bit_no_hdr[0].width; ++ try_fmt_img->height = supported_modes_10bit_no_hdr[0].height; ++ } ++ try_fmt_img->code = imx708_get_format_code(imx708); ++ try_fmt_img->field = V4L2_FIELD_NONE; ++ ++ /* Initialize try_crop */ ++ try_crop = v4l2_subdev_get_try_crop(sd, fh->state, 0); ++ try_crop->left = IMX708_PIXEL_ARRAY_LEFT; ++ try_crop->top = IMX708_PIXEL_ARRAY_TOP; ++ try_crop->width = IMX708_PIXEL_ARRAY_WIDTH; ++ try_crop->height = IMX708_PIXEL_ARRAY_HEIGHT; ++ ++ mutex_unlock(&imx708->mutex); ++ ++ return 0; ++} ++ ++static int imx708_set_exposure(struct imx708 *imx708, unsigned int val) ++{ ++ int ret; ++ ++ val = max(val, imx708->mode->exposure_lines_min); ++ val -= val % imx708->mode->exposure_lines_step; ++ ++ /* ++ * In HDR mode this will set the longest exposure. The sensor ++ * will automatically divide the medium and short ones by 4,16. ++ */ ++ ret = imx708_write_reg(imx708, IMX708_REG_EXPOSURE, ++ IMX708_REG_VALUE_16BIT, ++ val >> imx708->long_exp_shift); ++ ++ return ret; ++} ++ ++static void imx708_adjust_exposure_range(struct imx708 *imx708, ++ struct v4l2_ctrl *ctrl) ++{ ++ int exposure_max, exposure_def; ++ ++ /* Honour the VBLANK limits when setting exposure. */ ++ exposure_max = imx708->mode->height + imx708->vblank->val - ++ IMX708_EXPOSURE_OFFSET; ++ exposure_def = min(exposure_max, imx708->exposure->val); ++ __v4l2_ctrl_modify_range(imx708->exposure, imx708->exposure->minimum, ++ exposure_max, imx708->exposure->step, ++ exposure_def); ++} ++ ++static int imx708_set_analogue_gain(struct imx708 *imx708, unsigned int val) ++{ ++ int ret; ++ ++ /* ++ * In HDR mode this will set the gain for the longest exposure, ++ * and by default the sensor uses the same gain for all of them. ++ */ ++ ret = imx708_write_reg(imx708, IMX708_REG_ANALOG_GAIN, ++ IMX708_REG_VALUE_16BIT, val); ++ ++ return ret; ++} ++ ++static int imx708_set_frame_length(struct imx708 *imx708, unsigned int val) ++{ ++ int ret = 0; ++ ++ imx708->long_exp_shift = 0; ++ ++ while (val > IMX708_FRAME_LENGTH_MAX) { ++ imx708->long_exp_shift++; ++ val >>= 1; ++ } ++ ++ ret = imx708_write_reg(imx708, IMX708_REG_FRAME_LENGTH, ++ IMX708_REG_VALUE_16BIT, val); ++ if (ret) ++ return ret; ++ ++ return imx708_write_reg(imx708, IMX708_LONG_EXP_SHIFT_REG, ++ IMX708_REG_VALUE_08BIT, imx708->long_exp_shift); ++} ++ ++static void imx708_set_framing_limits(struct imx708 *imx708) ++{ ++ unsigned int hblank; ++ const struct imx708_mode *mode = imx708->mode; ++ ++ /* Default to no long exposure multiplier */ ++ imx708->long_exp_shift = 0; ++ ++ __v4l2_ctrl_modify_range(imx708->pixel_rate, ++ mode->pixel_rate, mode->pixel_rate, ++ 1, mode->pixel_rate); ++ ++ /* Update limits and set FPS to default */ ++ __v4l2_ctrl_modify_range(imx708->vblank, mode->vblank_min, ++ ((1 << IMX708_LONG_EXP_SHIFT_MAX) * ++ IMX708_FRAME_LENGTH_MAX) - mode->height, ++ 1, mode->vblank_default); ++ ++ /* ++ * Currently PPL is fixed to the mode specified value, so hblank ++ * depends on mode->width only, and is not changeable in any ++ * way other than changing the mode. ++ */ ++ hblank = mode->line_length_pix - mode->width; ++ __v4l2_ctrl_modify_range(imx708->hblank, hblank, hblank, 1, hblank); ++} ++ ++static int imx708_set_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct imx708 *imx708 = ++ container_of(ctrl->handler, struct imx708, ctrl_handler); ++ struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); ++ const struct imx708_mode *mode_list; ++ unsigned int num_modes; ++ int ret; ++ ++ /* ++ * The VBLANK control may change the limits of usable exposure, so check ++ * and adjust if necessary. ++ */ ++ if (ctrl->id == V4L2_CID_VBLANK) ++ imx708_adjust_exposure_range(imx708, ctrl); ++ ++ /* ++ * Applying V4L2 control value only happens ++ * when power is up for streaming ++ */ ++ if (!pm_runtime_get_if_in_use(&client->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = imx708_set_analogue_gain(imx708, ctrl->val); ++ break; ++ case V4L2_CID_EXPOSURE: ++ ret = imx708_set_exposure(imx708, ctrl->val); ++ break; ++ case V4L2_CID_DIGITAL_GAIN: ++ ret = imx708_write_reg(imx708, IMX708_REG_DIGITAL_GAIN, ++ IMX708_REG_VALUE_16BIT, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN: ++ ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN, ++ IMX708_REG_VALUE_16BIT, ++ imx708_test_pattern_val[ctrl->val]); ++ break; ++ case V4L2_CID_TEST_PATTERN_RED: ++ ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_R, ++ IMX708_REG_VALUE_16BIT, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN_GREENR: ++ ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GR, ++ IMX708_REG_VALUE_16BIT, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN_BLUE: ++ ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_B, ++ IMX708_REG_VALUE_16BIT, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN_GREENB: ++ ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GB, ++ IMX708_REG_VALUE_16BIT, ctrl->val); ++ break; ++ case V4L2_CID_HFLIP: ++ case V4L2_CID_VFLIP: ++ ret = imx708_write_reg(imx708, IMX708_REG_ORIENTATION, 1, ++ imx708->hflip->val | ++ imx708->vflip->val << 1); ++ break; ++ case V4L2_CID_VBLANK: ++ ret = imx708_set_frame_length(imx708, ++ imx708->mode->height + ctrl->val); ++ break; ++ case V4L2_CID_NOTIFY_GAINS: ++ ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_BLUE, ++ IMX708_REG_VALUE_16BIT, ++ imx708->notify_gains->p_new.p_u32[0]); ++ if (ret) ++ break; ++ ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_RED, ++ IMX708_REG_VALUE_16BIT, ++ imx708->notify_gains->p_new.p_u32[3]); ++ break; ++ case V4L2_CID_WIDE_DYNAMIC_RANGE: ++ get_mode_table(&mode_list, &num_modes, ctrl->val); ++ imx708->mode = v4l2_find_nearest_size(mode_list, ++ num_modes, ++ width, height, ++ imx708->mode->width, ++ imx708->mode->height); ++ imx708_set_framing_limits(imx708); ++ ret = 0; ++ break; ++ default: ++ dev_info(&client->dev, ++ "ctrl(id:0x%x,val:0x%x) is not handled\n", ++ ctrl->id, ctrl->val); ++ ret = -EINVAL; ++ break; ++ } ++ ++ pm_runtime_mark_last_busy(&client->dev); ++ pm_runtime_put_autosuspend(&client->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops imx708_ctrl_ops = { ++ .s_ctrl = imx708_set_ctrl, ++}; ++ ++static int imx708_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct imx708 *imx708 = to_imx708(sd); ++ ++ if (code->index >= 1) ++ return -EINVAL; ++ ++ code->code = imx708_get_format_code(imx708); ++ ++ return 0; ++} ++ ++static int imx708_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ struct imx708 *imx708 = to_imx708(sd); ++ const struct imx708_mode *mode_list; ++ unsigned int num_modes; ++ ++ get_mode_table(&mode_list, &num_modes, imx708->hdr_mode->val); ++ ++ if (fse->index >= num_modes) ++ return -EINVAL; ++ ++ if (fse->code != imx708_get_format_code(imx708)) ++ return -EINVAL; ++ ++ fse->min_width = mode_list[fse->index].width; ++ fse->max_width = fse->min_width; ++ fse->min_height = mode_list[fse->index].height; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static void imx708_reset_colorspace(struct v4l2_mbus_framefmt *fmt) ++{ ++ fmt->colorspace = V4L2_COLORSPACE_RAW; ++ 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 imx708_update_image_pad_format(struct imx708 *imx708, ++ const struct imx708_mode *mode, ++ struct v4l2_subdev_format *fmt) ++{ ++ fmt->format.width = mode->width; ++ fmt->format.height = mode->height; ++ fmt->format.field = V4L2_FIELD_NONE; ++ imx708_reset_colorspace(&fmt->format); ++} ++ ++static int imx708_get_pad_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct imx708 *imx708 = to_imx708(sd); ++ ++ mutex_lock(&imx708->mutex); ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { ++ struct v4l2_mbus_framefmt *try_fmt = ++ v4l2_subdev_get_try_format(&imx708->sd, sd_state, ++ fmt->pad); ++ /* update the code which could change due to vflip or hflip */ ++ try_fmt->code = imx708_get_format_code(imx708); ++ fmt->format = *try_fmt; ++ } else { ++ imx708_update_image_pad_format(imx708, imx708->mode, fmt); ++ fmt->format.code = imx708_get_format_code(imx708); ++ } ++ ++ mutex_unlock(&imx708->mutex); ++ return 0; ++} ++ ++static int imx708_set_pad_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct v4l2_mbus_framefmt *framefmt; ++ const struct imx708_mode *mode; ++ struct imx708 *imx708 = to_imx708(sd); ++ const struct imx708_mode *mode_list; ++ unsigned int num_modes; ++ ++ mutex_lock(&imx708->mutex); ++ ++ /* Bayer order varies with flips */ ++ fmt->format.code = imx708_get_format_code(imx708); ++ ++ get_mode_table(&mode_list, &num_modes, imx708->hdr_mode->val); ++ ++ mode = v4l2_find_nearest_size(mode_list, num_modes, width, height, ++ fmt->format.width, fmt->format.height); ++ imx708_update_image_pad_format(imx708, mode, fmt); ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { ++ framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); ++ *framefmt = fmt->format; ++ } else { ++ imx708->mode = mode; ++ imx708_set_framing_limits(imx708); ++ } ++ ++ mutex_unlock(&imx708->mutex); ++ ++ return 0; ++} ++ ++static const struct v4l2_rect * ++__imx708_get_pad_crop(struct imx708 *imx708, struct v4l2_subdev_state *sd_state, ++ unsigned int pad, enum v4l2_subdev_format_whence which) ++{ ++ switch (which) { ++ case V4L2_SUBDEV_FORMAT_TRY: ++ return v4l2_subdev_get_try_crop(&imx708->sd, sd_state, pad); ++ case V4L2_SUBDEV_FORMAT_ACTIVE: ++ return &imx708->mode->crop; ++ } ++ ++ return NULL; ++} ++ ++static int imx708_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_selection *sel) ++{ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: { ++ struct imx708 *imx708 = to_imx708(sd); ++ ++ mutex_lock(&imx708->mutex); ++ sel->r = *__imx708_get_pad_crop(imx708, sd_state, sel->pad, ++ sel->which); ++ mutex_unlock(&imx708->mutex); ++ ++ return 0; ++ } ++ ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.left = 0; ++ sel->r.top = 0; ++ sel->r.width = IMX708_NATIVE_WIDTH; ++ sel->r.height = IMX708_NATIVE_HEIGHT; ++ ++ return 0; ++ ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ sel->r.left = IMX708_PIXEL_ARRAY_LEFT; ++ sel->r.top = IMX708_PIXEL_ARRAY_TOP; ++ sel->r.width = IMX708_PIXEL_ARRAY_WIDTH; ++ sel->r.height = IMX708_PIXEL_ARRAY_HEIGHT; ++ ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++/* Start streaming */ ++static int imx708_start_streaming(struct imx708 *imx708) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); ++ const struct imx708_reg_list *reg_list; ++ int i, ret; ++ u32 val; ++ ++ if (!imx708->common_regs_written) { ++ ret = imx708_write_regs(imx708, mode_common_regs, ++ ARRAY_SIZE(mode_common_regs)); ++ if (ret) { ++ dev_err(&client->dev, "%s failed to set common settings\n", ++ __func__); ++ return ret; ++ } ++ ++ ret = imx708_read_reg(imx708, IMX708_REG_BASE_SPC_GAINS_L, ++ IMX708_REG_VALUE_08BIT, &val); ++ if (ret == 0 && val == 0x40) { ++ for (i = 0; i < 54 && ret == 0; i++) { ++ u16 reg = IMX708_REG_BASE_SPC_GAINS_L + i; ++ ++ ret = imx708_write_reg(imx708, reg, ++ IMX708_REG_VALUE_08BIT, ++ pdaf_gains[0][i % 9]); ++ } ++ for (i = 0; i < 54 && ret == 0; i++) { ++ u16 reg = IMX708_REG_BASE_SPC_GAINS_R + i; ++ ++ ret = imx708_write_reg(imx708, reg, ++ IMX708_REG_VALUE_08BIT, ++ pdaf_gains[1][i % 9]); ++ } ++ } ++ if (ret) { ++ dev_err(&client->dev, "%s failed to set PDAF gains\n", ++ __func__); ++ return ret; ++ } ++ ++ imx708->common_regs_written = true; ++ } ++ ++ /* Apply default values of current mode */ ++ reg_list = &imx708->mode->reg_list; ++ ret = imx708_write_regs(imx708, reg_list->regs, reg_list->num_of_regs); ++ if (ret) { ++ dev_err(&client->dev, "%s failed to set mode\n", __func__); ++ return ret; ++ } ++ ++ /* Apply customized values from user */ ++ ret = __v4l2_ctrl_handler_setup(imx708->sd.ctrl_handler); ++ if (ret) ++ return ret; ++ ++ /* set stream on register */ ++ return imx708_write_reg(imx708, IMX708_REG_MODE_SELECT, ++ IMX708_REG_VALUE_08BIT, IMX708_MODE_STREAMING); ++} ++ ++/* Stop streaming */ ++static void imx708_stop_streaming(struct imx708 *imx708) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); ++ int ret; ++ ++ /* set stream off register */ ++ ret = imx708_write_reg(imx708, IMX708_REG_MODE_SELECT, ++ IMX708_REG_VALUE_08BIT, IMX708_MODE_STANDBY); ++ if (ret) ++ dev_err(&client->dev, "%s failed to set stream\n", __func__); ++} ++ ++static int imx708_set_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct imx708 *imx708 = to_imx708(sd); ++ struct i2c_client *client = v4l2_get_subdevdata(sd); ++ int ret = 0; ++ ++ mutex_lock(&imx708->mutex); ++ if (imx708->streaming == enable) { ++ mutex_unlock(&imx708->mutex); ++ return 0; ++ } ++ ++ if (enable) { ++ ret = pm_runtime_resume_and_get(&client->dev); ++ if (ret < 0) ++ goto err_unlock; ++ ++ /* ++ * Apply default & customized values ++ * and then start streaming. ++ */ ++ ret = imx708_start_streaming(imx708); ++ if (ret) ++ goto err_rpm_put; ++ } else { ++ imx708_stop_streaming(imx708); ++ pm_runtime_mark_last_busy(&client->dev); ++ pm_runtime_put_autosuspend(&client->dev); ++ } ++ ++ imx708->streaming = enable; ++ ++ /* vflip/hflip and hdr mode cannot change during streaming */ ++ __v4l2_ctrl_grab(imx708->vflip, enable); ++ __v4l2_ctrl_grab(imx708->hflip, enable); ++ __v4l2_ctrl_grab(imx708->hdr_mode, enable); ++ ++ mutex_unlock(&imx708->mutex); ++ ++ return ret; ++ ++err_rpm_put: ++ pm_runtime_put_sync(&client->dev); ++err_unlock: ++ mutex_unlock(&imx708->mutex); ++ ++ return ret; ++} ++ ++/* Power/clock management functions */ ++static int imx708_power_on(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct imx708 *imx708 = to_imx708(sd); ++ int ret; ++ ++ ret = regulator_bulk_enable(IMX708_NUM_SUPPLIES, ++ imx708->supplies); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to enable regulators\n", ++ __func__); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(imx708->xclk); ++ if (ret) { ++ dev_err(&client->dev, "%s: failed to enable clock\n", ++ __func__); ++ goto reg_off; ++ } ++ ++ gpiod_set_value_cansleep(imx708->reset_gpio, 1); ++ usleep_range(IMX708_XCLR_MIN_DELAY_US, ++ IMX708_XCLR_MIN_DELAY_US + IMX708_XCLR_DELAY_RANGE_US); ++ ++ return 0; ++ ++reg_off: ++ regulator_bulk_disable(IMX708_NUM_SUPPLIES, imx708->supplies); ++ return ret; ++} ++ ++static int imx708_power_off(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct imx708 *imx708 = to_imx708(sd); ++ ++ gpiod_set_value_cansleep(imx708->reset_gpio, 0); ++ clk_disable_unprepare(imx708->xclk); ++ regulator_bulk_disable(IMX708_NUM_SUPPLIES, imx708->supplies); ++ ++ /* Force reprogramming of the common registers when powered up again. */ ++ imx708->common_regs_written = false; ++ ++ return 0; ++} ++ ++static int __maybe_unused imx708_suspend(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct imx708 *imx708 = to_imx708(sd); ++ ++ if (imx708->streaming) ++ imx708_stop_streaming(imx708); ++ ++ return 0; ++} ++ ++static int __maybe_unused imx708_resume(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct imx708 *imx708 = to_imx708(sd); ++ int ret; ++ ++ if (imx708->streaming) { ++ ret = imx708_start_streaming(imx708); ++ if (ret) ++ goto error; ++ } ++ ++ return 0; ++ ++error: ++ imx708_stop_streaming(imx708); ++ imx708->streaming = 0; ++ return ret; ++} ++ ++static int imx708_get_regulators(struct imx708 *imx708) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); ++ unsigned int i; ++ ++ for (i = 0; i < IMX708_NUM_SUPPLIES; i++) ++ imx708->supplies[i].supply = imx708_supply_name[i]; ++ ++ return devm_regulator_bulk_get(&client->dev, ++ IMX708_NUM_SUPPLIES, ++ imx708->supplies); ++} ++ ++/* Verify chip ID */ ++static int imx708_identify_module(struct imx708 *imx708) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); ++ int ret; ++ u32 val; ++ ++ ret = imx708_read_reg(imx708, IMX708_REG_CHIP_ID, ++ IMX708_REG_VALUE_16BIT, &val); ++ if (ret) { ++ dev_err(&client->dev, "failed to read chip id %x, with error %d\n", ++ IMX708_CHIP_ID, ret); ++ return ret; ++ } ++ ++ if (val != IMX708_CHIP_ID) { ++ dev_err(&client->dev, "chip id mismatch: %x!=%x\n", ++ IMX708_CHIP_ID, val); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_core_ops imx708_core_ops = { ++ .subscribe_event = v4l2_ctrl_subdev_subscribe_event, ++ .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++}; ++ ++static const struct v4l2_subdev_video_ops imx708_video_ops = { ++ .s_stream = imx708_set_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops imx708_pad_ops = { ++ .enum_mbus_code = imx708_enum_mbus_code, ++ .get_fmt = imx708_get_pad_format, ++ .set_fmt = imx708_set_pad_format, ++ .get_selection = imx708_get_selection, ++ .enum_frame_size = imx708_enum_frame_size, ++}; ++ ++static const struct v4l2_subdev_ops imx708_subdev_ops = { ++ .core = &imx708_core_ops, ++ .video = &imx708_video_ops, ++ .pad = &imx708_pad_ops, ++}; ++ ++static const struct v4l2_subdev_internal_ops imx708_internal_ops = { ++ .open = imx708_open, ++}; ++ ++static const struct v4l2_ctrl_config imx708_notify_gains_ctrl = { ++ .ops = &imx708_ctrl_ops, ++ .id = V4L2_CID_NOTIFY_GAINS, ++ .type = V4L2_CTRL_TYPE_U32, ++ .min = IMX708_COLOUR_BALANCE_MIN, ++ .max = IMX708_COLOUR_BALANCE_MAX, ++ .step = IMX708_COLOUR_BALANCE_STEP, ++ .def = IMX708_COLOUR_BALANCE_DEFAULT, ++ .dims = { 4 }, ++ .elem_size = sizeof(u32), ++}; ++ ++/* Initialize control handlers */ ++static int imx708_init_controls(struct imx708 *imx708) ++{ ++ struct v4l2_ctrl_handler *ctrl_hdlr; ++ struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); ++ struct v4l2_fwnode_device_properties props; ++ unsigned int i; ++ int ret; ++ ++ ctrl_hdlr = &imx708->ctrl_handler; ++ ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16); ++ if (ret) ++ return ret; ++ ++ mutex_init(&imx708->mutex); ++ ctrl_hdlr->lock = &imx708->mutex; ++ ++ /* By default, PIXEL_RATE is read only */ ++ imx708->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, ++ V4L2_CID_PIXEL_RATE, ++ imx708->mode->pixel_rate, ++ imx708->mode->pixel_rate, 1, ++ imx708->mode->pixel_rate); ++ ++ /* ++ * Create the controls here, but mode specific limits are setup ++ * in the imx708_set_framing_limits() call below. ++ */ ++ imx708->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, ++ V4L2_CID_VBLANK, 0, 0xffff, 1, 0); ++ imx708->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, ++ V4L2_CID_HBLANK, 0, 0xffff, 1, 0); ++ ++ imx708->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, ++ V4L2_CID_EXPOSURE, ++ IMX708_EXPOSURE_MIN, ++ IMX708_EXPOSURE_MAX, ++ IMX708_EXPOSURE_STEP, ++ IMX708_EXPOSURE_DEFAULT); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, ++ IMX708_ANA_GAIN_MIN, IMX708_ANA_GAIN_MAX, ++ IMX708_ANA_GAIN_STEP, IMX708_ANA_GAIN_DEFAULT); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_DIGITAL_GAIN, ++ IMX708_DGTL_GAIN_MIN, IMX708_DGTL_GAIN_MAX, ++ IMX708_DGTL_GAIN_STEP, IMX708_DGTL_GAIN_DEFAULT); ++ ++ imx708->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, ++ V4L2_CID_HFLIP, 0, 1, 1, 0); ++ ++ imx708->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, ++ V4L2_CID_VFLIP, 0, 1, 1, 0); ++ ++ v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx708_ctrl_ops, ++ V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(imx708_test_pattern_menu) - 1, ++ 0, 0, imx708_test_pattern_menu); ++ for (i = 0; i < 4; i++) { ++ /* ++ * The assumption is that ++ * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1 ++ * V4L2_CID_TEST_PATTERN_BLUE == V4L2_CID_TEST_PATTERN_RED + 2 ++ * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3 ++ */ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, ++ V4L2_CID_TEST_PATTERN_RED + i, ++ IMX708_TEST_PATTERN_COLOUR_MIN, ++ IMX708_TEST_PATTERN_COLOUR_MAX, ++ IMX708_TEST_PATTERN_COLOUR_STEP, ++ IMX708_TEST_PATTERN_COLOUR_MAX); ++ /* The "Solid color" pattern is white by default */ ++ } ++ ++ imx708->notify_gains = v4l2_ctrl_new_custom(ctrl_hdlr, ++ &imx708_notify_gains_ctrl, ++ NULL); ++ ++ imx708->hdr_mode = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, ++ V4L2_CID_WIDE_DYNAMIC_RANGE, ++ 0, 1, 1, 0); ++ ++ ret = v4l2_fwnode_device_parse(&client->dev, &props); ++ if (ret) ++ goto error; ++ ++ v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx708_ctrl_ops, &props); ++ ++ if (ctrl_hdlr->error) { ++ ret = ctrl_hdlr->error; ++ dev_err(&client->dev, "%s control init failed (%d)\n", ++ __func__, ret); ++ goto error; ++ } ++ ++ imx708->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ imx708->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ++ imx708->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ++ imx708->hdr_mode->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ++ ++ imx708->sd.ctrl_handler = ctrl_hdlr; ++ ++ /* Setup exposure and frame/line length limits. */ ++ imx708_set_framing_limits(imx708); ++ ++ return 0; ++ ++error: ++ v4l2_ctrl_handler_free(ctrl_hdlr); ++ mutex_destroy(&imx708->mutex); ++ ++ return ret; ++} ++ ++static void imx708_free_controls(struct imx708 *imx708) ++{ ++ v4l2_ctrl_handler_free(imx708->sd.ctrl_handler); ++ mutex_destroy(&imx708->mutex); ++} ++ ++static int imx708_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; ++ } ++ ++ /* Check the link frequency set in device tree */ ++ if (!ep_cfg.nr_of_link_frequencies) { ++ dev_err(dev, "link-frequency property not found in DT\n"); ++ goto error_out; ++ } ++ ++ if (ep_cfg.nr_of_link_frequencies != 1 || ++ ep_cfg.link_frequencies[0] != IMX708_DEFAULT_LINK_FREQ) { ++ dev_err(dev, "Link frequency not supported: %lld\n", ++ ep_cfg.link_frequencies[0]); ++ goto error_out; ++ } ++ ++ ret = 0; ++ ++error_out: ++ v4l2_fwnode_endpoint_free(&ep_cfg); ++ fwnode_handle_put(endpoint); ++ ++ return ret; ++} ++ ++static int imx708_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct imx708 *imx708; ++ int ret; ++ ++ imx708 = devm_kzalloc(&client->dev, sizeof(*imx708), GFP_KERNEL); ++ if (!imx708) ++ return -ENOMEM; ++ ++ v4l2_i2c_subdev_init(&imx708->sd, client, &imx708_subdev_ops); ++ ++ /* Check the hardware configuration in device tree */ ++ if (imx708_check_hwcfg(dev)) ++ return -EINVAL; ++ ++ /* Get system clock (xclk) */ ++ imx708->xclk = devm_clk_get(dev, NULL); ++ if (IS_ERR(imx708->xclk)) { ++ dev_err(dev, "failed to get xclk\n"); ++ return PTR_ERR(imx708->xclk); ++ } ++ ++ imx708->xclk_freq = clk_get_rate(imx708->xclk); ++ if (imx708->xclk_freq != IMX708_XCLK_FREQ) { ++ dev_err(dev, "xclk frequency not supported: %d Hz\n", ++ imx708->xclk_freq); ++ return -EINVAL; ++ } ++ ++ ret = imx708_get_regulators(imx708); ++ if (ret) { ++ dev_err(dev, "failed to get regulators\n"); ++ return ret; ++ } ++ ++ /* Request optional enable pin */ ++ imx708->reset_gpio = devm_gpiod_get_optional(dev, "reset", ++ GPIOD_OUT_HIGH); ++ ++ /* ++ * The sensor must be powered for imx708_identify_module() ++ * to be able to read the CHIP_ID register ++ */ ++ ret = imx708_power_on(dev); ++ if (ret) ++ return ret; ++ ++ ret = imx708_identify_module(imx708); ++ if (ret) ++ goto error_power_off; ++ ++ /* Initialize default format */ ++ imx708_set_default_format(imx708); ++ ++ /* ++ * Enable runtime PM with autosuspend. As the device has been powered ++ * manually, mark it as active, and increase the usage count without ++ * resuming the device. ++ */ ++ pm_runtime_set_active(dev); ++ pm_runtime_get_noresume(dev); ++ pm_runtime_enable(dev); ++ pm_runtime_set_autosuspend_delay(dev, 1000); ++ pm_runtime_use_autosuspend(dev); ++ ++ /* This needs the pm runtime to be registered. */ ++ ret = imx708_init_controls(imx708); ++ if (ret) ++ goto error_power_off; ++ ++ /* Initialize subdev */ ++ imx708->sd.internal_ops = &imx708_internal_ops; ++ imx708->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | ++ V4L2_SUBDEV_FL_HAS_EVENTS; ++ imx708->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ++ /* Initialize source pad */ ++ imx708->pad.flags = MEDIA_PAD_FL_SOURCE; ++ ++ ret = media_entity_pads_init(&imx708->sd.entity, 1, &imx708->pad); ++ if (ret) { ++ dev_err(dev, "failed to init entity pads: %d\n", ret); ++ goto error_handler_free; ++ } ++ ++ ret = v4l2_async_register_subdev_sensor(&imx708->sd); ++ if (ret < 0) { ++ dev_err(dev, "failed to register sensor sub-device: %d\n", ret); ++ goto error_media_entity; ++ } ++ ++ /* ++ * Decrease the PM usage count. The device will get suspended after the ++ * autosuspend delay, turning the power off. ++ */ ++ pm_runtime_mark_last_busy(dev); ++ pm_runtime_put_autosuspend(dev); ++ ++ return 0; ++ ++error_media_entity: ++ media_entity_cleanup(&imx708->sd.entity); ++ ++error_handler_free: ++ imx708_free_controls(imx708); ++ ++error_power_off: ++ pm_runtime_disable(&client->dev); ++ pm_runtime_put_noidle(&client->dev); ++ imx708_power_off(&client->dev); ++ ++ return ret; ++} ++ ++static void imx708_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct imx708 *imx708 = to_imx708(sd); ++ ++ v4l2_async_unregister_subdev(sd); ++ media_entity_cleanup(&sd->entity); ++ imx708_free_controls(imx708); ++ ++ pm_runtime_disable(&client->dev); ++ if (!pm_runtime_status_suspended(&client->dev)) ++ imx708_power_off(&client->dev); ++ pm_runtime_set_suspended(&client->dev); ++} ++ ++static const struct of_device_id imx708_dt_ids[] = { ++ { .compatible = "sony,imx708" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, imx708_dt_ids); ++ ++static const struct dev_pm_ops imx708_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(imx708_suspend, imx708_resume) ++ SET_RUNTIME_PM_OPS(imx708_power_off, imx708_power_on, NULL) ++}; ++ ++static struct i2c_driver imx708_i2c_driver = { ++ .driver = { ++ .name = "imx708", ++ .of_match_table = imx708_dt_ids, ++ .pm = &imx708_pm_ops, ++ }, ++ .probe = imx708_probe, ++ .remove = imx708_remove, ++}; ++ ++module_i2c_driver(imx708_i2c_driver); ++ ++MODULE_AUTHOR("David Plowman <david.plowman@raspberrypi.com>"); ++MODULE_DESCRIPTION("Sony IMX708 sensor driver"); ++MODULE_LICENSE("GPL"); diff --git a/target/linux/starfive/patches-6.6/0088-media-i2c-imx708-Delete-gain.patch b/target/linux/starfive/patches-6.6/0088-media-i2c-imx708-Delete-gain.patch new file mode 100644 index 0000000000..c3971ed62b --- /dev/null +++ b/target/linux/starfive/patches-6.6/0088-media-i2c-imx708-Delete-gain.patch @@ -0,0 +1,69 @@ +From 280ab217867d0b934454a837540eab665e854b47 Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Mon, 3 Apr 2023 15:28:11 +0800 +Subject: [PATCH 088/116] media: i2c: imx708: Delete gain + +Delete gain. + +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +--- + drivers/media/i2c/imx708.c | 27 --------------------------- + 1 file changed, 27 deletions(-) + +--- a/drivers/media/i2c/imx708.c ++++ b/drivers/media/i2c/imx708.c +@@ -777,7 +777,6 @@ struct imx708 { + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *red_balance; + struct v4l2_ctrl *blue_balance; +- struct v4l2_ctrl *notify_gains; + struct v4l2_ctrl *hdr_mode; + + /* Current mode */ +@@ -1108,16 +1107,6 @@ static int imx708_set_ctrl(struct v4l2_c + ret = imx708_set_frame_length(imx708, + imx708->mode->height + ctrl->val); + break; +- case V4L2_CID_NOTIFY_GAINS: +- ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_BLUE, +- IMX708_REG_VALUE_16BIT, +- imx708->notify_gains->p_new.p_u32[0]); +- if (ret) +- break; +- ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_RED, +- IMX708_REG_VALUE_16BIT, +- imx708->notify_gains->p_new.p_u32[3]); +- break; + case V4L2_CID_WIDE_DYNAMIC_RANGE: + get_mode_table(&mode_list, &num_modes, ctrl->val); + imx708->mode = v4l2_find_nearest_size(mode_list, +@@ -1584,18 +1573,6 @@ static const struct v4l2_subdev_internal + .open = imx708_open, + }; + +-static const struct v4l2_ctrl_config imx708_notify_gains_ctrl = { +- .ops = &imx708_ctrl_ops, +- .id = V4L2_CID_NOTIFY_GAINS, +- .type = V4L2_CTRL_TYPE_U32, +- .min = IMX708_COLOUR_BALANCE_MIN, +- .max = IMX708_COLOUR_BALANCE_MAX, +- .step = IMX708_COLOUR_BALANCE_STEP, +- .def = IMX708_COLOUR_BALANCE_DEFAULT, +- .dims = { 4 }, +- .elem_size = sizeof(u32), +-}; +- + /* Initialize control handlers */ + static int imx708_init_controls(struct imx708 *imx708) + { +@@ -1670,10 +1647,6 @@ static int imx708_init_controls(struct i + /* The "Solid color" pattern is white by default */ + } + +- imx708->notify_gains = v4l2_ctrl_new_custom(ctrl_hdlr, +- &imx708_notify_gains_ctrl, +- NULL); +- + imx708->hdr_mode = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_WIDE_DYNAMIC_RANGE, + 0, 1, 1, 0); diff --git a/target/linux/starfive/patches-6.6/0089-dt-bindings-display-Add-yamls-for-JH7110-display-sys.patch b/target/linux/starfive/patches-6.6/0089-dt-bindings-display-Add-yamls-for-JH7110-display-sys.patch new file mode 100644 index 0000000000..e8baa80eb9 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0089-dt-bindings-display-Add-yamls-for-JH7110-display-sys.patch @@ -0,0 +1,270 @@ +From aa4febf074cbaad81c981a5c6b55324a6f676fb7 Mon Sep 17 00:00:00 2001 +From: "shengyang.chen" <shengyang.chen@starfivetech.com> +Date: Tue, 13 Jun 2023 14:22:29 +0800 +Subject: [PATCH 089/116] dt-bindings: display: Add yamls for JH7110 display + system and hdmi + +StarFive SoCs like the jh7110 use display system based on verisilicon IP, use hdmi +base on innosilicon IP. Add bindings for them. + +Signed-off-by: Shengyang Chen <shengyang.chen@starfivetech.com> +--- + .../display/verisilicon/starfive-hdmi.yaml | 92 +++++++++++++++ + .../display/verisilicon/verisilicon-dc.yaml | 109 ++++++++++++++++++ + .../display/verisilicon/verisilicon-drm.yaml | 41 +++++++ + 3 files changed, 242 insertions(+) + create mode 100644 Documentation/devicetree/bindings/display/verisilicon/starfive-hdmi.yaml + create mode 100644 Documentation/devicetree/bindings/display/verisilicon/verisilicon-dc.yaml + create mode 100644 Documentation/devicetree/bindings/display/verisilicon/verisilicon-drm.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/display/verisilicon/starfive-hdmi.yaml +@@ -0,0 +1,92 @@ ++# SPDX-License-Identifier: GPL-2.0 ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/display/verisilicon/starfive-hdmi.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: StarFive SoC HDMI transmiter ++ ++description: ++ The StarFive SoC uses the HDMI signal transmiter based on innosilicon IP ++ to generate HDMI signal from its input and transmit the signal to the screen. ++ ++maintainers: ++ - Keith Zhao <keith.zhao@starfivetech.com> ++ ++properties: ++ compatible: ++ const: starfive,hdmi ++ ++ reg: ++ minItems: 1 ++ ++ interrupts: ++ items: ++ - description: The HDMI hot plug detection interrupt. ++ ++ clocks: ++ items: ++ - description: System clock of HDMI module. ++ - description: Mclk clock of HDMI audio. ++ - description: Bclk clock of HDMI audio. ++ - description: Pixel clock generated by HDMI module. ++ ++ clock-names: ++ items: ++ - const: sysclk ++ - const: mclk ++ - const: bclk ++ - const: pclk ++ ++ resets: ++ items: ++ - description: Reset for HDMI module. ++ ++ reset-names: ++ items: ++ - const: hdmi_tx ++ ++ '#sound-dai-cells': ++ const: 0 ++ ++ port: ++ $ref: /schemas/graph.yaml#/properties/port ++ description: ++ Port node with one endpoint connected to a display connector node. ++ ++required: ++ - compatible ++ - reg ++ - interrupts ++ - clocks ++ - clock-names ++ - resets ++ - reset-names ++ - '#sound-dai-cells' ++ - port ++ ++additionalProperties: false ++ ++examples: ++ - | ++ hdmi: hdmi@29590000 { ++ compatible = "starfive,hdmi"; ++ reg = <0x29590000 0x4000>; ++ interrupts = <99>; ++ clocks = <&voutcrg 17>, ++ <&voutcrg 15>, ++ <&voutcrg 16>, ++ <&hdmitx0_pixelclk>; ++ clock-names = "sysclk", "mclk","bclk","pclk"; ++ resets = <&voutcrg 9>; ++ reset-names = "hdmi_tx"; ++ #sound-dai-cells = <0>; ++ hdmi_in: port { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ hdmi_input: endpoint@0 { ++ reg = <0>; ++ remote-endpoint = <&dc_out_dpi0>; ++ }; ++ }; ++ }; +--- /dev/null ++++ b/Documentation/devicetree/bindings/display/verisilicon/verisilicon-dc.yaml +@@ -0,0 +1,109 @@ ++# SPDX-License-Identifier: GPL-2.0 ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/display/verisilicon/verisilicon-dc.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: StarFive SoC display controller ++ ++description: ++ The StarFive SoC uses the display controller based on Verisilicon IP ++ to transfer the image data from a video memory ++ buffer to an external LCD interface. ++ ++maintainers: ++ - Keith Zhao <keith.zhao@starfivetech.com> ++ ++properties: ++ compatible: ++ const: verisilicon,dc8200 ++ ++ reg: ++ maxItems: 3 ++ ++ interrupts: ++ items: ++ - description: The interrupt will be generated when DC finish one frame ++ ++ clocks: ++ items: ++ - description: Clock for display system noc bus. ++ - description: Pixel clock for display channel 0. ++ - description: Pixel clock for display channel 1. ++ - description: Clock for axi interface of display controller. ++ - description: Core clock for display controller. ++ - description: Clock for ahb interface of display controller. ++ - description: External HDMI pixel clock. ++ - description: Parent clock for pixel clock ++ ++ clock-names: ++ items: ++ - const: clk_vout_noc_disp ++ - const: clk_vout_pix0 ++ - const: clk_vout_pix1 ++ - const: clk_vout_axi ++ - const: clk_vout_core ++ - const: clk_vout_vout_ahb ++ - const: hdmitx0_pixel ++ - const: clk_vout_dc8200 ++ ++ resets: ++ items: ++ - description: Reset for axi interface of display controller. ++ - description: Reset for ahb interface of display controller. ++ - description: Core reset of display controller. ++ ++ reset-names: ++ items: ++ - const: rst_vout_axi ++ - const: rst_vout_ahb ++ - const: rst_vout_core ++ ++ port: ++ $ref: /schemas/graph.yaml#/properties/port ++ description: ++ Port node with one endpoint connected to a hdmi node. ++ ++required: ++ - compatible ++ - reg ++ - interrupts ++ - clocks ++ - clock-names ++ - resets ++ - reset-names ++ - port ++ ++additionalProperties: false ++ ++examples: ++ - | ++ dc8200: dc8200@29400000 { ++ compatible = "verisilicon,dc8200"; ++ reg = <0x29400000 0x100>, ++ <0x29400800 0x2000>, ++ <0x295B0000 0x90>; ++ interrupts = <95>; ++ clocks = <&syscrg 60>, ++ <&voutcrg 7>, ++ <&voutcrg 8>, ++ <&voutcrg 4>, ++ <&voutcrg 5>, ++ <&voutcrg 6>, ++ <&hdmitx0_pixelclk>, ++ <&voutcrg 1>; ++ clock-names = "clk_vout_noc_disp", "clk_vout_pix0", "clk_vout_pix1", "clk_vout_axi", ++ "clk_vout_core", "clk_vout_vout_ahb", "hdmitx0_pixel","clk_vout_dc8200"; ++ resets = <&voutcrg 0>, ++ <&voutcrg 1>, ++ <&voutcrg 2>; ++ reset-names = "rst_vout_axi","rst_vout_ahb","rst_vout_core"; ++ dc_out: port { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ dc_out_dpi0: endpoint@0 { ++ reg = <0>; ++ remote-endpoint = <&hdmi_input>; ++ }; ++ }; ++ }; +--- /dev/null ++++ b/Documentation/devicetree/bindings/display/verisilicon/verisilicon-drm.yaml +@@ -0,0 +1,41 @@ ++# SPDX-License-Identifier: (GPL-2.0-only) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/display/verisilicon/verisilicon-drm.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Verisilicon DRM master device ++ ++maintainers: ++ - Keith Zhao <keith.zhao@starfivetech.com> ++ ++description: | ++ The Verisilicon DRM master device is a virtual device needed to list all ++ display controller or other display interface nodes that comprise the ++ graphics subsystem. ++ ++properties: ++ compatible: ++ const: verisilicon,display-subsystem ++ ++ ports: ++ $ref: /schemas/types.yaml#/definitions/phandle-array ++ items: ++ maxItems: 1 ++ description: | ++ Should contain a list of phandles pointing to display interface ports ++ of display controller devices. Display controller definitions as defined in ++ Documentation/devicetree/bindings/display/verisilicon/verisilicon-dc.yaml ++ ++required: ++ - compatible ++ - ports ++ ++additionalProperties: false ++ ++examples: ++ - | ++ display-subsystem { ++ compatible = "verisilicon,display-subsystem"; ++ ports = <&dc_out>; ++ }; diff --git a/target/linux/starfive/patches-6.6/0090-soc-starfive-jh71xx_pmu-Add-EVENT_TURN_OFF-register-.patch b/target/linux/starfive/patches-6.6/0090-soc-starfive-jh71xx_pmu-Add-EVENT_TURN_OFF-register-.patch new file mode 100644 index 0000000000..7a6bfa8c4f --- /dev/null +++ b/target/linux/starfive/patches-6.6/0090-soc-starfive-jh71xx_pmu-Add-EVENT_TURN_OFF-register-.patch @@ -0,0 +1,82 @@ +From 5cbf4154da600af8a2e6a2f8d57d1f212abf3f92 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Fri, 13 Oct 2023 14:10:24 +0800 +Subject: [PATCH 090/116] soc: starfive: jh71xx_pmu: Add EVENT_TURN_OFF + register writing support + +Add and export starfive_pmu_hw_event_turn_off_mask() to +write EVENT_TURN_OFF register. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/pmdomain/starfive/jh71xx-pmu.c | 11 ++++++++++ + include/soc/starfive/jh7110_pmu.h | 29 ++++++++++++++++++++++++++ + 2 files changed, 40 insertions(+) + create mode 100644 include/soc/starfive/jh7110_pmu.h + +--- a/drivers/pmdomain/starfive/jh71xx-pmu.c ++++ b/drivers/pmdomain/starfive/jh71xx-pmu.c +@@ -16,6 +16,7 @@ + #include <dt-bindings/power/starfive,jh7110-pmu.h> + + /* register offset */ ++#define JH71XX_PMU_HW_EVENT_TURN_OFF 0x08 + #define JH71XX_PMU_SW_TURN_ON_POWER 0x0C + #define JH71XX_PMU_SW_TURN_OFF_POWER 0x10 + #define JH71XX_PMU_SW_ENCOURAGE 0x44 +@@ -83,6 +84,14 @@ struct jh71xx_pmu_dev { + struct generic_pm_domain genpd; + }; + ++static void __iomem *pmu_base; ++ ++void starfive_pmu_hw_event_turn_off_mask(u32 mask) ++{ ++ writel(mask, pmu_base + JH71XX_PMU_HW_EVENT_TURN_OFF); ++} ++EXPORT_SYMBOL(starfive_pmu_hw_event_turn_off_mask); ++ + static int jh71xx_pmu_get_state(struct jh71xx_pmu_dev *pmd, u32 mask, bool *is_on) + { + struct jh71xx_pmu *pmu = pmd->pmu; +@@ -334,6 +343,8 @@ static int jh71xx_pmu_probe(struct platf + if (IS_ERR(pmu->base)) + return PTR_ERR(pmu->base); + ++ pmu_base = pmu->base; ++ + spin_lock_init(&pmu->lock); + + match_data = of_device_get_match_data(dev); +--- /dev/null ++++ b/include/soc/starfive/jh7110_pmu.h +@@ -0,0 +1,29 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * PMU driver for the StarFive JH7110 SoC ++ * ++ * Copyright (C) 2022 samin <samin.guo@starfivetech.com> ++ */ ++ ++#ifndef __SOC_STARFIVE_JH7110_PMU_H__ ++#define __SOC_STARFIVE_JH7110_PMU_H__ ++ ++#include <linux/bits.h> ++#include <linux/types.h> ++ ++enum PMU_HARD_EVENT { ++ PMU_HW_EVENT_RTC = BIT(0), ++ PMU_HW_EVENT_GMAC = BIT(1), ++ PMU_HW_EVENT_RFU = BIT(2), ++ PMU_HW_EVENT_RGPIO0 = BIT(3), ++ PMU_HW_EVENT_RGPIO1 = BIT(4), ++ PMU_HW_EVENT_RGPIO2 = BIT(5), ++ PMU_HW_EVENT_RGPIO3 = BIT(6), ++ PMU_HW_EVENT_GPU = BIT(7), ++ PMU_HW_EVENT_ALL = GENMASK(7, 0), ++}; ++ ++void starfive_pmu_hw_event_turn_off_mask(u32 mask); ++ ++#endif /* __SOC_STARFIVE_JH7110_PMU_H__ */ ++ diff --git a/target/linux/starfive/patches-6.6/0091-workqueue-Enable-flush_scheduled_work.patch b/target/linux/starfive/patches-6.6/0091-workqueue-Enable-flush_scheduled_work.patch new file mode 100644 index 0000000000..a6b69660c3 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0091-workqueue-Enable-flush_scheduled_work.patch @@ -0,0 +1,22 @@ +From ab436ea7ffb6c464616addad98632c81a321844a Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Tue, 12 Dec 2023 17:56:58 +0800 +Subject: [PATCH 091/116] workqueue: Enable flush_scheduled_work() + +Enable flush_scheduled_work() for JH7110 GPU. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + include/linux/workqueue.h | 1 - + 1 file changed, 1 deletion(-) + +--- a/include/linux/workqueue.h ++++ b/include/linux/workqueue.h +@@ -636,7 +636,6 @@ extern void __warn_flushing_systemwide_w + /* Please stop using this function, for this function will be removed in near future. */ + #define flush_scheduled_work() \ + ({ \ +- __warn_flushing_systemwide_wq(); \ + __flush_workqueue(system_wq); \ + }) + diff --git a/target/linux/starfive/patches-6.6/0092-riscv-Optimize-memcpy-with-aligned-version.patch b/target/linux/starfive/patches-6.6/0092-riscv-Optimize-memcpy-with-aligned-version.patch new file mode 100644 index 0000000000..39c27237b0 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0092-riscv-Optimize-memcpy-with-aligned-version.patch @@ -0,0 +1,506 @@ +From dedbbfd891e4d0cdb825454eddfbf8386d0025b3 Mon Sep 17 00:00:00 2001 +From: Mason Huo <mason.huo@starfivetech.com> +Date: Tue, 20 Jun 2023 13:37:52 +0800 +Subject: [PATCH 092/116] riscv: Optimize memcpy with aligned version + +Optimizing the 128 byte align case, this will improve the +performance of large block memcpy. + +Here we combine the memcpy of glibc and kernel. + +Signed-off-by: Mason Huo <mason.huo@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + arch/riscv/lib/Makefile | 3 +- + arch/riscv/lib/{memcpy.S => memcpy_aligned.S} | 36 +-- + arch/riscv/lib/string.c | 266 ++++++++++++++++++ + 3 files changed, 273 insertions(+), 32 deletions(-) + rename arch/riscv/lib/{memcpy.S => memcpy_aligned.S} (67%) + create mode 100644 arch/riscv/lib/string.c + +--- a/arch/riscv/lib/Makefile ++++ b/arch/riscv/lib/Makefile +@@ -1,6 +1,5 @@ + # SPDX-License-Identifier: GPL-2.0-only + lib-y += delay.o +-lib-y += memcpy.o + lib-y += memset.o + lib-y += memmove.o + lib-y += strcmp.o +@@ -9,5 +8,7 @@ lib-y += strncmp.o + lib-$(CONFIG_MMU) += uaccess.o + lib-$(CONFIG_64BIT) += tishift.o + lib-$(CONFIG_RISCV_ISA_ZICBOZ) += clear_page.o ++lib-y += string.o ++lib-y += memcpy_aligned.o + + obj-$(CONFIG_FUNCTION_ERROR_INJECTION) += error-inject.o +--- a/arch/riscv/lib/memcpy.S ++++ /dev/null +@@ -1,110 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/* +- * Copyright (C) 2013 Regents of the University of California +- */ +- +-#include <linux/linkage.h> +-#include <asm/asm.h> +- +-/* void *memcpy(void *, const void *, size_t) */ +-ENTRY(__memcpy) +-WEAK(memcpy) +- move t6, a0 /* Preserve return value */ +- +- /* Defer to byte-oriented copy for small sizes */ +- sltiu a3, a2, 128 +- bnez a3, 4f +- /* Use word-oriented copy only if low-order bits match */ +- andi a3, t6, SZREG-1 +- andi a4, a1, SZREG-1 +- bne a3, a4, 4f +- +- beqz a3, 2f /* Skip if already aligned */ +- /* +- * Round to nearest double word-aligned address +- * greater than or equal to start address +- */ +- andi a3, a1, ~(SZREG-1) +- addi a3, a3, SZREG +- /* Handle initial misalignment */ +- sub a4, a3, a1 +-1: +- lb a5, 0(a1) +- addi a1, a1, 1 +- sb a5, 0(t6) +- addi t6, t6, 1 +- bltu a1, a3, 1b +- sub a2, a2, a4 /* Update count */ +- +-2: +- andi a4, a2, ~((16*SZREG)-1) +- beqz a4, 4f +- add a3, a1, a4 +-3: +- REG_L a4, 0(a1) +- REG_L a5, SZREG(a1) +- REG_L a6, 2*SZREG(a1) +- REG_L a7, 3*SZREG(a1) +- REG_L t0, 4*SZREG(a1) +- REG_L t1, 5*SZREG(a1) +- REG_L t2, 6*SZREG(a1) +- REG_L t3, 7*SZREG(a1) +- REG_L t4, 8*SZREG(a1) +- REG_L t5, 9*SZREG(a1) +- REG_S a4, 0(t6) +- REG_S a5, SZREG(t6) +- REG_S a6, 2*SZREG(t6) +- REG_S a7, 3*SZREG(t6) +- REG_S t0, 4*SZREG(t6) +- REG_S t1, 5*SZREG(t6) +- REG_S t2, 6*SZREG(t6) +- REG_S t3, 7*SZREG(t6) +- REG_S t4, 8*SZREG(t6) +- REG_S t5, 9*SZREG(t6) +- REG_L a4, 10*SZREG(a1) +- REG_L a5, 11*SZREG(a1) +- REG_L a6, 12*SZREG(a1) +- REG_L a7, 13*SZREG(a1) +- REG_L t0, 14*SZREG(a1) +- REG_L t1, 15*SZREG(a1) +- addi a1, a1, 16*SZREG +- REG_S a4, 10*SZREG(t6) +- REG_S a5, 11*SZREG(t6) +- REG_S a6, 12*SZREG(t6) +- REG_S a7, 13*SZREG(t6) +- REG_S t0, 14*SZREG(t6) +- REG_S t1, 15*SZREG(t6) +- addi t6, t6, 16*SZREG +- bltu a1, a3, 3b +- andi a2, a2, (16*SZREG)-1 /* Update count */ +- +-4: +- /* Handle trailing misalignment */ +- beqz a2, 6f +- add a3, a1, a2 +- +- /* Use word-oriented copy if co-aligned to word boundary */ +- or a5, a1, t6 +- or a5, a5, a3 +- andi a5, a5, 3 +- bnez a5, 5f +-7: +- lw a4, 0(a1) +- addi a1, a1, 4 +- sw a4, 0(t6) +- addi t6, t6, 4 +- bltu a1, a3, 7b +- +- ret +- +-5: +- lb a4, 0(a1) +- addi a1, a1, 1 +- sb a4, 0(t6) +- addi t6, t6, 1 +- bltu a1, a3, 5b +-6: +- ret +-END(__memcpy) +-SYM_FUNC_ALIAS(__pi_memcpy, __memcpy) +-SYM_FUNC_ALIAS(__pi___memcpy, __memcpy) +--- /dev/null ++++ b/arch/riscv/lib/memcpy_aligned.S +@@ -0,0 +1,84 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * Copyright (C) 2013 Regents of the University of California ++ */ ++ ++#include <linux/linkage.h> ++#include <asm/asm.h> ++ ++/* void *__memcpy_aligned(void *, const void *, size_t) */ ++ENTRY(__memcpy_aligned) ++ move t6, a0 /* Preserve return value */ ++ ++2: ++ andi a4, a2, ~((16*SZREG)-1) ++ beqz a4, 4f ++ add a3, a1, a4 ++3: ++ REG_L a4, 0(a1) ++ REG_L a5, SZREG(a1) ++ REG_L a6, 2*SZREG(a1) ++ REG_L a7, 3*SZREG(a1) ++ REG_L t0, 4*SZREG(a1) ++ REG_L t1, 5*SZREG(a1) ++ REG_L t2, 6*SZREG(a1) ++ REG_L t3, 7*SZREG(a1) ++ REG_L t4, 8*SZREG(a1) ++ REG_L t5, 9*SZREG(a1) ++ REG_S a4, 0(t6) ++ REG_S a5, SZREG(t6) ++ REG_S a6, 2*SZREG(t6) ++ REG_S a7, 3*SZREG(t6) ++ REG_S t0, 4*SZREG(t6) ++ REG_S t1, 5*SZREG(t6) ++ REG_S t2, 6*SZREG(t6) ++ REG_S t3, 7*SZREG(t6) ++ REG_S t4, 8*SZREG(t6) ++ REG_S t5, 9*SZREG(t6) ++ REG_L a4, 10*SZREG(a1) ++ REG_L a5, 11*SZREG(a1) ++ REG_L a6, 12*SZREG(a1) ++ REG_L a7, 13*SZREG(a1) ++ REG_L t0, 14*SZREG(a1) ++ REG_L t1, 15*SZREG(a1) ++ addi a1, a1, 16*SZREG ++ REG_S a4, 10*SZREG(t6) ++ REG_S a5, 11*SZREG(t6) ++ REG_S a6, 12*SZREG(t6) ++ REG_S a7, 13*SZREG(t6) ++ REG_S t0, 14*SZREG(t6) ++ REG_S t1, 15*SZREG(t6) ++ addi t6, t6, 16*SZREG ++ bltu a1, a3, 3b ++ andi a2, a2, (16*SZREG)-1 /* Update count */ ++ ++4: ++ /* Handle trailing misalignment */ ++ beqz a2, 6f ++ add a3, a1, a2 ++ ++ /* Use word-oriented copy if co-aligned to word boundary */ ++ or a5, a1, t6 ++ or a5, a5, a3 ++ andi a5, a5, 3 ++ bnez a5, 5f ++7: ++ lw a4, 0(a1) ++ addi a1, a1, 4 ++ sw a4, 0(t6) ++ addi t6, t6, 4 ++ bltu a1, a3, 7b ++ ++ ret ++ ++5: ++ lb a4, 0(a1) ++ addi a1, a1, 1 ++ sb a4, 0(t6) ++ addi t6, t6, 1 ++ bltu a1, a3, 5b ++6: ++ ret ++END(__memcpy_aligned) ++SYM_FUNC_ALIAS(__pi_memcpy, __memcpy_aligned) ++SYM_FUNC_ALIAS(__pi___memcpy, __memcpy_aligned) +--- /dev/null ++++ b/arch/riscv/lib/string.c +@@ -0,0 +1,266 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copy memory to memory until the specified number of bytes ++ * has been copied. Overlap is NOT handled correctly. ++ * Copyright (C) 1991-2020 Free Software Foundation, Inc. ++ * This file is part of the GNU C Library. ++ * Contributed by Torbjorn Granlund (tege@sics.se). ++ * ++ * The GNU C Library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * The GNU C Library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with the GNU C Library; if not, see ++ * <https://www.gnu.org/licenses/>. ++ * ++ */ ++ ++#define __NO_FORTIFY ++#include <linux/types.h> ++#include <linux/module.h> ++ ++#define MERGE(w0, sh_1, w1, sh_2) (((w0) >> (sh_1)) | ((w1) << (sh_2))) ++#define OP_T_THRES 16 ++#define op_t unsigned long ++#define OPSIZ (sizeof(op_t)) ++#define OPSIZ_MASK (sizeof(op_t) - 1) ++#define FAST_COPY_THRES (128) ++#define byte unsigned char ++ ++static void _wordcopy_fwd_aligned(long dstp, long srcp, size_t len) ++{ ++ op_t a0, a1; ++ ++ switch (len % 8) { ++ case 2: ++ a0 = ((op_t *) srcp)[0]; ++ srcp -= 6 * OPSIZ; ++ dstp -= 7 * OPSIZ; ++ len += 6; ++ goto do1; ++ case 3: ++ a1 = ((op_t *) srcp)[0]; ++ srcp -= 5 * OPSIZ; ++ dstp -= 6 * OPSIZ; ++ len += 5; ++ goto do2; ++ case 4: ++ a0 = ((op_t *) srcp)[0]; ++ srcp -= 4 * OPSIZ; ++ dstp -= 5 * OPSIZ; ++ len += 4; ++ goto do3; ++ case 5: ++ a1 = ((op_t *) srcp)[0]; ++ srcp -= 3 * OPSIZ; ++ dstp -= 4 * OPSIZ; ++ len += 3; ++ goto do4; ++ case 6: ++ a0 = ((op_t *) srcp)[0]; ++ srcp -= 2 * OPSIZ; ++ dstp -= 3 * OPSIZ; ++ len += 2; ++ goto do5; ++ case 7: ++ a1 = ((op_t *) srcp)[0]; ++ srcp -= 1 * OPSIZ; ++ dstp -= 2 * OPSIZ; ++ len += 1; ++ goto do6; ++ ++ case 0: ++ if (OP_T_THRES <= 3 * OPSIZ && len == 0) ++ return; ++ a0 = ((op_t *) srcp)[0]; ++ srcp -= 0 * OPSIZ; ++ dstp -= 1 * OPSIZ; ++ goto do7; ++ case 1: ++ a1 = ((op_t *) srcp)[0]; ++ srcp -= -1 * OPSIZ; ++ dstp -= 0 * OPSIZ; ++ len -= 1; ++ if (OP_T_THRES <= 3 * OPSIZ && len == 0) ++ goto do0; ++ goto do8; /* No-op. */ ++ } ++ ++ do { ++do8: ++ a0 = ((op_t *) srcp)[0]; ++ ((op_t *) dstp)[0] = a1; ++do7: ++ a1 = ((op_t *) srcp)[1]; ++ ((op_t *) dstp)[1] = a0; ++do6: ++ a0 = ((op_t *) srcp)[2]; ++ ((op_t *) dstp)[2] = a1; ++do5: ++ a1 = ((op_t *) srcp)[3]; ++ ((op_t *) dstp)[3] = a0; ++do4: ++ a0 = ((op_t *) srcp)[4]; ++ ((op_t *) dstp)[4] = a1; ++do3: ++ a1 = ((op_t *) srcp)[5]; ++ ((op_t *) dstp)[5] = a0; ++do2: ++ a0 = ((op_t *) srcp)[6]; ++ ((op_t *) dstp)[6] = a1; ++do1: ++ a1 = ((op_t *) srcp)[7]; ++ ((op_t *) dstp)[7] = a0; ++ ++ srcp += 8 * OPSIZ; ++ dstp += 8 * OPSIZ; ++ len -= 8; ++ } while (len != 0); ++ ++ /* This is the right position for do0. Please don't move ++ * it into the loop. ++ */ ++do0: ++ ((op_t *) dstp)[0] = a1; ++} ++ ++static void _wordcopy_fwd_dest_aligned(long dstp, long srcp, size_t len) ++{ ++ op_t a0, a1, a2, a3; ++ int sh_1, sh_2; ++ ++ /* Calculate how to shift a word read at the memory operation ++ * aligned srcp to make it aligned for copy. ++ */ ++ ++ sh_1 = 8 * (srcp % OPSIZ); ++ sh_2 = 8 * OPSIZ - sh_1; ++ ++ /* Make SRCP aligned by rounding it down to the beginning of the `op_t' ++ * it points in the middle of. ++ */ ++ srcp &= -OPSIZ; ++ ++ switch (len % 4) { ++ case 2: ++ a1 = ((op_t *) srcp)[0]; ++ a2 = ((op_t *) srcp)[1]; ++ srcp -= 1 * OPSIZ; ++ dstp -= 3 * OPSIZ; ++ len += 2; ++ goto do1; ++ case 3: ++ a0 = ((op_t *) srcp)[0]; ++ a1 = ((op_t *) srcp)[1]; ++ srcp -= 0 * OPSIZ; ++ dstp -= 2 * OPSIZ; ++ len += 1; ++ goto do2; ++ case 0: ++ if (OP_T_THRES <= 3 * OPSIZ && len == 0) ++ return; ++ a3 = ((op_t *) srcp)[0]; ++ a0 = ((op_t *) srcp)[1]; ++ srcp -= -1 * OPSIZ; ++ dstp -= 1 * OPSIZ; ++ len += 0; ++ goto do3; ++ case 1: ++ a2 = ((op_t *) srcp)[0]; ++ a3 = ((op_t *) srcp)[1]; ++ srcp -= -2 * OPSIZ; ++ dstp -= 0 * OPSIZ; ++ len -= 1; ++ if (OP_T_THRES <= 3 * OPSIZ && len == 0) ++ goto do0; ++ goto do4; /* No-op. */ ++ } ++ ++ do { ++do4: ++ a0 = ((op_t *) srcp)[0]; ++ ((op_t *) dstp)[0] = MERGE(a2, sh_1, a3, sh_2); ++do3: ++ a1 = ((op_t *) srcp)[1]; ++ ((op_t *) dstp)[1] = MERGE(a3, sh_1, a0, sh_2); ++do2: ++ a2 = ((op_t *) srcp)[2]; ++ ((op_t *) dstp)[2] = MERGE(a0, sh_1, a1, sh_2); ++do1: ++ a3 = ((op_t *) srcp)[3]; ++ ((op_t *) dstp)[3] = MERGE(a1, sh_1, a2, sh_2); ++ ++ srcp += 4 * OPSIZ; ++ dstp += 4 * OPSIZ; ++ len -= 4; ++ } while (len != 0); ++ ++ /* This is the right position for do0. Please don't move ++ * it into the loop. ++ */ ++do0: ++ ((op_t *) dstp)[0] = MERGE(a2, sh_1, a3, sh_2); ++} ++ ++#define BYTE_COPY_FWD(dst_bp, src_bp, nbytes) \ ++do { \ ++ size_t __nbytes = (nbytes); \ ++ while (__nbytes > 0) { \ ++ byte __x = ((byte *) src_bp)[0]; \ ++ src_bp += 1; \ ++ __nbytes -= 1; \ ++ ((byte *) dst_bp)[0] = __x; \ ++ dst_bp += 1; \ ++ } \ ++} while (0) ++ ++#define WORD_COPY_FWD(dst_bp, src_bp, nbytes_left, nbytes) \ ++do { \ ++ if (src_bp % OPSIZ == 0) \ ++ _wordcopy_fwd_aligned(dst_bp, src_bp, (nbytes) / OPSIZ); \ ++ else \ ++ _wordcopy_fwd_dest_aligned(dst_bp, src_bp, (nbytes) / OPSIZ); \ ++ src_bp += (nbytes) & -OPSIZ; \ ++ dst_bp += (nbytes) & -OPSIZ; \ ++ (nbytes_left) = (nbytes) % OPSIZ; \ ++} while (0) ++ ++extern void *__memcpy_aligned(void *dest, const void *src, size_t len); ++void *__memcpy(void *dest, const void *src, size_t len) ++{ ++ unsigned long dstp = (long) dest; ++ unsigned long srcp = (long) src; ++ ++ /* If there not too few bytes to copy, use word copy. */ ++ if (len >= OP_T_THRES) { ++ if ((len >= FAST_COPY_THRES) && ((dstp & OPSIZ_MASK) == 0) && ++ ((srcp & OPSIZ_MASK) == 0)) { ++ __memcpy_aligned(dest, src, len); ++ return dest; ++ } ++ /* Copy just a few bytes to make DSTP aligned. */ ++ len -= (-dstp) % OPSIZ; ++ BYTE_COPY_FWD(dstp, srcp, (-dstp) % OPSIZ); ++ ++ /* Copy from SRCP to DSTP taking advantage of the known alignment of ++ * DSTP. Number of bytes remaining is put in the third argument, ++ * i.e. in LEN. This number may vary from machine to machine. ++ */ ++ WORD_COPY_FWD(dstp, srcp, len, len); ++ /* Fall out and copy the tail. */ ++ } ++ ++ /* There are just a few bytes to copy. Use byte memory operations. */ ++ BYTE_COPY_FWD(dstp, srcp, len); ++ ++ return dest; ++} ++ ++void *memcpy(void *dest, const void *src, size_t len) __weak __alias(__memcpy); diff --git a/target/linux/starfive/patches-6.6/0093-riscv-purgatory-Change-memcpy-to-the-aligned-version.patch b/target/linux/starfive/patches-6.6/0093-riscv-purgatory-Change-memcpy-to-the-aligned-version.patch new file mode 100644 index 0000000000..0b7418fcf9 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0093-riscv-purgatory-Change-memcpy-to-the-aligned-version.patch @@ -0,0 +1,37 @@ +From 41c9e97bb70321f7848bd489e45246a9dc985974 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Sun, 4 Feb 2024 15:27:09 +0800 +Subject: [PATCH 093/116] riscv/purgatory: Change memcpy to the aligned version + +Change memcpy to the aligned version, for purgatory. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + arch/riscv/purgatory/Makefile | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +--- a/arch/riscv/purgatory/Makefile ++++ b/arch/riscv/purgatory/Makefile +@@ -1,7 +1,7 @@ + # SPDX-License-Identifier: GPL-2.0 + OBJECT_FILES_NON_STANDARD := y + +-purgatory-y := purgatory.o sha256.o entry.o string.o ctype.o memcpy.o memset.o ++purgatory-y := purgatory.o sha256.o entry.o string.o ctype.o memcpy_aligned.o memcpy.o memset.o + purgatory-y += strcmp.o strlen.o strncmp.o + + targets += $(purgatory-y) +@@ -13,9 +13,12 @@ $(obj)/string.o: $(srctree)/lib/string.c + $(obj)/ctype.o: $(srctree)/lib/ctype.c FORCE + $(call if_changed_rule,cc_o_c) + +-$(obj)/memcpy.o: $(srctree)/arch/riscv/lib/memcpy.S FORCE ++$(obj)/memcpy_aligned.o: $(srctree)/arch/riscv/lib/memcpy_aligned.S FORCE + $(call if_changed_rule,as_o_S) + ++$(obj)/memcpy.o: $(srctree)/arch/riscv/lib/string.c FORCE ++ $(call if_changed_rule,cc_o_c) ++ + $(obj)/memset.o: $(srctree)/arch/riscv/lib/memset.S FORCE + $(call if_changed_rule,as_o_S) + diff --git a/target/linux/starfive/patches-6.6/0094-Add-16-ISP-controls-remove-the-frame-SYNC-event-to-v.patch b/target/linux/starfive/patches-6.6/0094-Add-16-ISP-controls-remove-the-frame-SYNC-event-to-v.patch new file mode 100644 index 0000000000..7ce12c6d58 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0094-Add-16-ISP-controls-remove-the-frame-SYNC-event-to-v.patch @@ -0,0 +1,856 @@ +From f36d458104101050478e290919ef4f05fbde0b3e Mon Sep 17 00:00:00 2001 +From: "zejian.su" <zejian.su@starfivetech.com> +Date: Mon, 3 Jul 2023 16:52:13 +0800 +Subject: [PATCH 094/116] Add 16 ISP controls, remove the frame SYNC event to + video7 (SC) These controls are: WB, CAR, CCM, CFA, CTC, DBC, DNYUV, GMARGB, + LCCF, OBC, OECF, R2Y, SAT, SHRP, YCRV, SC + +--- + .../platform/starfive/v4l2_driver/stf_isp.c | 628 ++++++++++++++++++ + .../platform/starfive/v4l2_driver/stf_video.c | 22 + + .../platform/starfive/v4l2_driver/stf_vin.c | 16 +- + include/uapi/linux/jh7110-isp.h | 48 +- + 4 files changed, 706 insertions(+), 8 deletions(-) + +--- a/drivers/media/platform/starfive/v4l2_driver/stf_isp.c ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_isp.c +@@ -195,6 +195,41 @@ static const char * const test_pattern_m + "Color squares w/ rolling bar", + }; + ++enum isp_modules_index { ++ imi_obc = 0, ++ imi_oecf, ++ imi_lccf, ++ imi_awb, ++ imi_dbc, ++ imi_ctc, ++ imi_cfa, ++ imi_car, ++ imi_ccm, ++ imi_gmargb, ++ imi_r2y, ++ imi_ycrv, ++ imi_shrp, ++ imi_dnyuv, ++ imi_sat, ++ imi_sc ++}; ++ ++#define MODULE_ENABLE_REGISTER0 0x10 ++#define MODULE_ENABLE_REGISTER1 0xa08 ++ ++struct module_register_info { ++ __u32 en_reg; ++ __u32 en_nbit; ++ __u32 cfg_reg; ++}; ++ ++static const struct module_register_info mod_reg_info[] = { ++ {MODULE_ENABLE_REGISTER0, 2, 0x034}, {MODULE_ENABLE_REGISTER0, 4, 0x100}, {MODULE_ENABLE_REGISTER0, 6, 0x050}, {MODULE_ENABLE_REGISTER0, 7, 0x280}, ++ {MODULE_ENABLE_REGISTER1, 22, 0xa14}, {MODULE_ENABLE_REGISTER1, 21, 0xa10}, {MODULE_ENABLE_REGISTER1, 1, 0xa1c}, {MODULE_ENABLE_REGISTER1, 2, 0x000}, ++ {MODULE_ENABLE_REGISTER1, 3, 0xc40}, {MODULE_ENABLE_REGISTER1, 4, 0xe00}, {MODULE_ENABLE_REGISTER1, 5, 0xe40}, {MODULE_ENABLE_REGISTER1, 19, 0xf00}, ++ {MODULE_ENABLE_REGISTER1, 7, 0xe80}, {MODULE_ENABLE_REGISTER1, 17, 0xc00}, {MODULE_ENABLE_REGISTER1, 8, 0xa30}, {MODULE_ENABLE_REGISTER0, 17, 0x09c} ++}; ++ + #define ISP_TEST_ENABLE BIT(7) + #define ISP_TEST_ROLLING BIT(6) /* rolling horizontal bar */ + #define ISP_TEST_TRANSPARENT BIT(5) +@@ -260,6 +295,401 @@ static int isp_g_volatile_ctrl(struct v4 + return 0; + } + ++#define CREATE_REG_VALUE_FUNCTION(type) \ ++ static u32 create_reg_value_##type(const type * value, s32 size, u32 mask, s32 nbits) { \ ++ s32 npos = 0; \ ++ u32 res = 0; \ ++ s32 sz = size; \ ++ s32 i = 0; \ ++ if(size * nbits > 32) sz = 32 / nbits; \ ++ for(i = 0; i < sz; i++, npos += nbits, value++) res |= (u32)(value[0] & mask) << npos; \ ++ return res; \ ++} ++ ++CREATE_REG_VALUE_FUNCTION(u8); ++CREATE_REG_VALUE_FUNCTION(u16); ++#define CREATE_REG_VALUE(type, value, size, mask, nbits) create_reg_value_##type(value, size, mask, nbits) ++ ++#define FILL_ISP_REGS_FUNC(type) \ ++static void fill_isp_regs_##type(void __iomem *ispbase, u32 offset, const type * value, s32 size, u32 mask, u32 nbits) { \ ++ s32 i; \ ++ for(i = 0; i < size; i++, value++) \ ++ reg_write(ispbase, offset + i * 4, (u32)(value[0] & mask) << nbits); \ ++} ++FILL_ISP_REGS_FUNC(u32); ++FILL_ISP_REGS_FUNC(u8); ++FILL_ISP_REGS_FUNC(u16); ++ ++#define FILL_ISP_REGS(type, ispbase, offset, value, size, mask, nbits) \ ++ fill_isp_regs_##type(ispbase, offset, value, size, mask, nbits) ++ ++static int isp_set_ctrl_wb(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_awb]; ++ const struct jh7110_isp_wb_setting * setting = (const struct jh7110_isp_wb_setting *)value; ++ const struct jh7110_isp_wb_gain * gains = &setting->gains; ++ u32 r_g = (((u32)gains->gain_r << 16) | gains->gain_r) & 0x03ff03ff; ++ u32 g_g = (((u32)gains->gain_g << 16) | gains->gain_g) & 0x03ff03ff; ++ u32 b_g = (((u32)gains->gain_b << 16) | gains->gain_b) & 0x03ff03ff; ++ u32 reg_addr = reg_info->cfg_reg + 16 * sizeof(u32); ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ ++ reg_write(ispbase, reg_addr, r_g); ++ reg_write(ispbase, reg_addr + 1 * 4, r_g); ++ reg_write(ispbase, reg_addr + 2 * 4, g_g); ++ reg_write(ispbase, reg_addr + 3 * 4, g_g); ++ reg_write(ispbase, reg_addr + 4 * 4, g_g); ++ reg_write(ispbase, reg_addr + 5 * 4, g_g); ++ reg_write(ispbase, reg_addr + 6 * 4, b_g); ++ reg_write(ispbase, reg_addr + 7 * 4, b_g); ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_car(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_car]; ++ const struct jh7110_isp_car_setting * setting = (const struct jh7110_isp_car_setting *)value; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_ccm(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_ccm]; ++ const struct jh7110_isp_ccm_setting * setting = (const struct jh7110_isp_ccm_setting *)value; ++ const struct jh7110_isp_ccm_smlow * ccm = (const struct jh7110_isp_ccm_smlow *)(&(setting->ccm_smlow)); ++ u32 reg_addr = reg_info->cfg_reg + 12 * sizeof(u32); ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ ++ reg_write(ispbase, reg_info->cfg_reg, 6 << 16); ++ FILL_ISP_REGS(u32, ispbase, reg_addr, (u32 *)ccm, 12, 0x7ff, 0); ++ ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_cfa(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_cfa]; ++ const struct jh7110_isp_cfa_setting * setting = (const struct jh7110_isp_cfa_setting *)value; ++ const struct jh7110_isp_cfa_params * cfg = (const struct jh7110_isp_cfa_params *)(&(setting->settings)); ++ u32 reg_addr = reg_info->cfg_reg; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ ++ reg_write(ispbase, reg_addr, ((u32)(cfg->cross_cov & 0x3) << 4) | (cfg->hv_width & 0xf)); ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_ctc(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_ctc]; ++ const struct jh7110_isp_ctc_setting * setting = (const struct jh7110_isp_ctc_setting *)value; ++ const struct jh7110_isp_ctc_params * cfg = (const struct jh7110_isp_ctc_params *)(&(setting->settings)); ++ u32 reg_addr = reg_info->cfg_reg; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ u32 reg_value = (u32)(((cfg->saf_mode & 1) << 1) | (cfg->daf_mode & 1)) << 30; ++ ++ reg_value |= ((u32)(cfg->max_gt & 0x3ff) << 16) | (cfg->min_gt & 0x3ff); ++ reg_write(ispbase, reg_addr, reg_value); ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_dbc(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_dbc]; ++ const struct jh7110_isp_dbc_setting * setting = (const struct jh7110_isp_dbc_setting *)value; ++ const struct jh7110_isp_dbc_params * cfg = (const struct jh7110_isp_dbc_params *)(&(setting->settings)); ++ u32 reg_addr = reg_info->cfg_reg; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ ++ reg_write(ispbase, reg_addr, ((u32)(cfg->bad_gt & 0x3ff) << 16) | (cfg->bad_xt & 0x3ff)); ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_dnyuv(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_dnyuv]; ++ const struct jh7110_isp_dnyuv_setting * setting = (const struct jh7110_isp_dnyuv_setting *)value; ++ const struct jh7110_isp_dnyuv_params * cfg = (const struct jh7110_isp_dnyuv_params *)(&(setting->settings)); ++ u32 reg_addr = reg_info->cfg_reg; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ ++ reg_write(ispbase, reg_addr, CREATE_REG_VALUE(u8, cfg->y_sweight, 6, 0x7, 4)); ++ reg_write(ispbase, reg_addr + 4, CREATE_REG_VALUE(u8, &cfg->y_sweight[6], 4, 0x7, 4)); ++ reg_write(ispbase, reg_addr + 8, CREATE_REG_VALUE(u8, cfg->uv_sweight, 6, 0x7, 4)); ++ reg_write(ispbase, reg_addr + 12, CREATE_REG_VALUE(u8, &cfg->uv_sweight[6], 4, 0x7, 4)); ++ reg_write(ispbase, reg_addr + 16, CREATE_REG_VALUE(u16, &cfg->y_curve[0], 2, 0x3ff, 16)); ++ reg_write(ispbase, reg_addr + 20, CREATE_REG_VALUE(u16, &cfg->y_curve[2], 2, 0x3ff, 16)); ++ reg_write(ispbase, reg_addr + 24, CREATE_REG_VALUE(u16, &cfg->y_curve[4], 2, 0x3ff, 16)); ++ reg_write(ispbase, reg_addr + 28, CREATE_REG_VALUE(u16, &cfg->uv_curve[0], 2, 0x3ff, 16)); ++ reg_write(ispbase, reg_addr + 32, CREATE_REG_VALUE(u16, &cfg->uv_curve[2], 2, 0x3ff, 16)); ++ reg_write(ispbase, reg_addr + 36, CREATE_REG_VALUE(u16, &cfg->uv_curve[4], 2, 0x3ff, 16)); ++ ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_gmargb(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_gmargb]; ++ const struct jh7110_isp_gmargb_setting * setting = (const struct jh7110_isp_gmargb_setting *)value; ++ const struct jh7110_isp_gmargb_point * curve = setting->curve; ++ u32 reg_addr = reg_info->cfg_reg; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ s32 i; ++ ++ for(i = 0; i < 15; i++, curve++, reg_addr += 4) ++ reg_write(ispbase, reg_addr, ((u32)curve->sg_val << 16) | (curve->g_val & 0x3ff)); ++ ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_lccf(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_lccf]; ++ const struct jh7110_isp_lccf_setting * setting = (const struct jh7110_isp_lccf_setting *)value; ++ const s16 * params = (s16 *)(&setting->r_param); ++ u32 reg_addr = reg_info->cfg_reg; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ s32 i; ++ ++ reg_write(ispbase, reg_addr, ((u32)(setting->circle.center_y & 0x7fff) << 16) | (setting->circle.center_x & 0x7fff)); ++ reg_write(ispbase, reg_addr + 8, setting->circle.radius & 0xf); ++ reg_addr += 0x90; ++ for(i = 0; i < 4; i++, reg_addr += 4, params += 2) ++ reg_write(ispbase, reg_addr, CREATE_REG_VALUE(u16, params, 2, 0x1fff, 16)); ++ ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_obc(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_obc]; ++ const struct jh7110_isp_blacklevel_setting * setting = (const struct jh7110_isp_blacklevel_setting *)value; ++ const u8 * params = (u8 *)setting->gain; ++ u32 reg_addr = reg_info->cfg_reg; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ s32 i; ++ ++ reg_write(ispbase, reg_addr, ((u32)(setting->win_size.height & 0xf) << 4) | (setting->win_size.width & 0xf)); ++ ++ reg_addr += 0x2ac; //0x2e0 ++ for(i = 0; i < 8; i++, reg_addr += 4, params += 4) ++ reg_write(ispbase, reg_addr, CREATE_REG_VALUE(u8, params, 4, 0xff, 8)); ++ ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_oecf(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_oecf]; ++ const struct jh7110_isp_oecf_setting * setting = (const struct jh7110_isp_oecf_setting *)value; ++ const struct jh7110_isp_oecf_point * pts = (struct jh7110_isp_oecf_point *)(setting->r_curve); ++ u32 reg_x_addr = reg_info->cfg_reg; ++ u32 reg_y_addr = reg_info->cfg_reg + 0x080; ++ u32 reg_s_addr = reg_info->cfg_reg + 0x100; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ u32 x, y, slope; ++ s32 i; ++ ++ for(i = 0; i < 32; i++, reg_x_addr += 4, reg_y_addr += 4, reg_s_addr += 4) { ++ x = pts->x & 0x3ff; ++ y = pts->y & 0x3ff; ++ slope = pts->slope & 0x3ff; ++ pts++; ++ x |= ((pts->x & 0x3ff) << 16); ++ y |= ((pts->y & 0x3ff) << 16); ++ slope |= ((pts->slope & 0x3ff) << 16); ++ pts++; ++ ++ reg_write(ispbase, reg_x_addr, x); ++ reg_write(ispbase, reg_y_addr, y); ++ reg_write(ispbase, reg_s_addr, slope); ++ } ++ ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_r2y(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_r2y]; ++ const struct jh7110_isp_r2y_setting * setting = (const struct jh7110_isp_r2y_setting *)value; ++ const s16 * params = setting->matrix.m; ++ u32 reg_addr = reg_info->cfg_reg; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ s32 i; ++ ++ for(i = 0; i < 9; i++, reg_addr += 4) ++ reg_write(ispbase, reg_addr, (u32)(params[i] & 0x1ff)); ++ ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++ ++static int isp_set_ctrl_sat(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_sat]; ++ const struct jh7110_isp_sat_setting * setting = (const struct jh7110_isp_sat_setting *)value; ++ const u16 * params = (u16 *)(&setting->sat_info); ++ u32 reg_addr = reg_info->cfg_reg; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ s32 i; ++ ++ for(i = 0; i < 3; i++, reg_addr += 4, params += 2) ++ reg_write(ispbase, reg_addr, CREATE_REG_VALUE(u16, params, 2, 0xfff, 16)); ++ reg_write(ispbase, reg_addr, CREATE_REG_VALUE(u16, &setting->hue_info.cos, 2, 0x3ff, 16)); ++ reg_addr += 4; ++ reg_write(ispbase, reg_addr, setting->sat_info.cmsf & 0xf); ++ ++ params = (u16 *)(&setting->curve); ++ reg_addr += 0x14; // 0xa54 ++ for(i = 0; i < 2; i++, reg_addr += 4, params += 2) ++ reg_write(ispbase, reg_addr, CREATE_REG_VALUE(u16, params, 2, 0x3fff, 16)); ++ ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_shrp(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_shrp]; ++ const struct jh7110_isp_sharp_setting * setting = (const struct jh7110_isp_sharp_setting *)value; ++ u32 reg_addr = reg_info->cfg_reg; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ s32 i; ++ ++ for(i = 0; i < 4; i++, reg_addr += 4) ++ reg_write(ispbase, reg_addr, ((u32)(setting->strength.diff[i] & 0x3ff) << 16) | ((u32)(setting->weight.weight[i] & 0xf) << 8)); ++ FILL_ISP_REGS(u8, ispbase, reg_addr, (u8 *)(&setting->weight.weight[4]), 15 - 4, 0xf, 8); ++ reg_addr += (15 - 4) * 4; ++ ++ for(i = 0; i < 3; i++, reg_addr += 4) ++ reg_write(ispbase, reg_addr, ((u32)(setting->strength.f[i] & 0x7f) << 24) | (setting->strength.s[i] & 0x1fffff)); ++ ++ reg_addr += 3 * 4; ++ reg_write(ispbase, reg_addr, ((u32)(setting->pdirf & 0xf) << 28) | ((u32)(setting->ndirf & 0xf) << 24) | (setting->weight.recip_wei_sum & 0x3fffff)); ++ ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_ycrv(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_ycrv]; ++ const struct jh7110_isp_ycrv_setting * setting = (const struct jh7110_isp_ycrv_setting *)value; ++ u32 reg_addr = reg_info->cfg_reg; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ ++ FILL_ISP_REGS(u16, ispbase, reg_addr, (u16 *)(setting->curve.y), 64, 0x3ff, 0); ++ ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ ++static int isp_set_ctrl_sc(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct module_register_info * reg_info = &mod_reg_info[imi_sc]; ++ const struct jh7110_isp_sc_setting * setting = (const struct jh7110_isp_sc_setting *)value; ++ const u8 * params = setting->awb_config.awb_cw; ++ u32 reg_addr = 0x00; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ u32 weight_cfg[6] = {0}; ++ u32 * weight = weight_cfg; ++ u32 * w_diff = weight_cfg + 2; ++ s32 i; ++ ++ // AF register ++ reg_write(ispbase, 0xc0, ++ ((u32)(setting->af_config.es_hor_thr & 0x1ff) << 16) | ++ ((u32)(setting->af_config.es_ver_thr & 0xff) << 8) | ++ ((setting->af_config.ver_en & 0x1) << 3) | ++ ((setting->af_config.hor_en & 0x1) << 2) | ++ ((setting->af_config.es_sum_mode & 0x1) << 1) | ++ (setting->af_config.es_hor_mode & 0x1)); ++ ++ // AWB weight sum register ++ reg_write(ispbase, 0x5d0, CREATE_REG_VALUE(u8, &setting->awb_config.ws_config.awb_ws_rl, 4, 0xff, 8)); ++ reg_write(ispbase, 0x5d4, CREATE_REG_VALUE(u8, &setting->awb_config.ws_config.awb_ws_gbl, 4, 0xff, 8)); ++ ++ // AWB weight value point ++ reg_addr = 0x4d0; ++ for(i = 0; i < 13; i++) { ++ reg_write(ispbase, reg_addr, CREATE_REG_VALUE(u8, params, 8, 0xf, 4)); ++ reg_addr += 4; ++ params += 8; ++ reg_write(ispbase, reg_addr, CREATE_REG_VALUE(u8, params, 5, 0xf, 4)); ++ reg_addr += 4; ++ params += 5; ++ } ++ ++ // AWB intensity weight curve register ++ reg_addr = 0x538; ++ for(i = 0; i < 4; i++) { ++ weight[0] |= (setting->awb_config.pts[i].weight & 0xf) << (i * 4); ++ w_diff[0] |= ((((s16)(setting->awb_config.pts[i + 1].weight & 0xf) - (s16)(setting->awb_config.pts[i].weight & 0xf)) * 2) & 0xff) << (i * 8); ++ } ++ for(w_diff++; i < 8; i++) { ++ weight[0] |= (setting->awb_config.pts[i].weight & 0xf) << (i * 4); ++ w_diff[0] |= ((((s16)(setting->awb_config.pts[i + 1].weight & 0xf) - (s16)(setting->awb_config.pts[i].weight & 0xf)) * 2) & 0xff) << (i * 8); ++ } ++ for(weight++, w_diff++; i < 12; i++) { ++ weight[0] |= (setting->awb_config.pts[i].weight & 0xf) << (i * 4); ++ w_diff[0] |= ((((s16)(setting->awb_config.pts[i + 1].weight & 0xf) - (s16)(setting->awb_config.pts[i].weight & 0xf)) * 2) & 0xff) << (i * 8); ++ } ++ for(w_diff++; i < 16; i++) { ++ weight[0] |= (setting->awb_config.pts[i].weight & 0xf) << (i * 4); ++ w_diff[0] |= ((((s16)(setting->awb_config.pts[i + 1].weight & 0xf) - (s16)(setting->awb_config.pts[i].weight & 0xf)) * 2) & 0xff) << (i * 8); ++ } ++ ++ FILL_ISP_REGS(u32, ispbase, reg_addr, weight_cfg, 6, 0xffffffff, 0); ++ ++ reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); ++ ++ return 0; ++} ++ + static int isp_s_ctrl(struct v4l2_ctrl *ctrl) + { + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); +@@ -310,10 +740,52 @@ static int isp_s_ctrl(struct v4l2_ctrl * + ret = isp_set_ctrl_vflip(isp_dev, ctrl->val); + break; + case V4L2_CID_USER_JH7110_ISP_WB_SETTING: ++ ret = isp_set_ctrl_wb(isp_dev, ctrl->p_new.p_u8); + break; + case V4L2_CID_USER_JH7110_ISP_CAR_SETTING: ++ ret = isp_set_ctrl_car(isp_dev, ctrl->p_new.p_u8); + break; + case V4L2_CID_USER_JH7110_ISP_CCM_SETTING: ++ ret = isp_set_ctrl_ccm(isp_dev, ctrl->p_new.p_u8); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_CFA_SETTING: ++ ret = isp_set_ctrl_cfa(isp_dev, ctrl->p_new.p_u8); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_CTC_SETTING: ++ ret = isp_set_ctrl_ctc(isp_dev, ctrl->p_new.p_u8); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_DBC_SETTING: ++ ret = isp_set_ctrl_dbc(isp_dev, ctrl->p_new.p_u8); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_DNYUV_SETTING: ++ ret = isp_set_ctrl_dnyuv(isp_dev, ctrl->p_new.p_u8); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_GMARGB_SETTING: ++ ret = isp_set_ctrl_gmargb(isp_dev, ctrl->p_new.p_u8); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_LCCF_SETTING: ++ ret = isp_set_ctrl_lccf(isp_dev, ctrl->p_new.p_u8); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_OBC_SETTING: ++ ret = isp_set_ctrl_obc(isp_dev, ctrl->p_new.p_u8); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_OECF_SETTING: ++ ret = isp_set_ctrl_oecf(isp_dev, ctrl->p_new.p_u8); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_R2Y_SETTING: ++ ret = isp_set_ctrl_r2y(isp_dev, ctrl->p_new.p_u8); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_SAT_SETTING: ++ ret = isp_set_ctrl_sat(isp_dev, ctrl->p_new.p_u8); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_SHRP_SETTING: ++ ret = isp_set_ctrl_shrp(isp_dev, ctrl->p_new.p_u8); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_YCRV_SETTING: ++ ret = isp_set_ctrl_ycrv(isp_dev, ctrl->p_new.p_u8); ++ break; ++ case V4L2_CID_USER_JH7110_ISP_STAT_SETTING: ++ ret = isp_set_ctrl_sc(isp_dev, ctrl->p_new.p_u8); + break; + default: + ret = -EINVAL; +@@ -365,6 +837,162 @@ struct v4l2_ctrl_config isp_ctrl[] = { + .dims[0] = sizeof(struct jh7110_isp_ccm_setting), + .flags = 0, + }, ++ [3] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "CFA Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_CFA_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_cfa_setting), ++ .flags = 0, ++ }, ++ [4] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "CTC Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_CTC_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_ctc_setting), ++ .flags = 0, ++ }, ++ [5] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "CTC Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_DBC_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_dbc_setting), ++ .flags = 0, ++ }, ++ [6] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "DNYUV Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_DNYUV_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_dnyuv_setting), ++ .flags = 0, ++ }, ++ [7] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "DNYUV Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_GMARGB_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_gmargb_setting), ++ .flags = 0, ++ }, ++ [8] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "LCCF Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_LCCF_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_lccf_setting), ++ .flags = 0, ++ }, ++ [9] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "OBC Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_OBC_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_blacklevel_setting), ++ .flags = 0, ++ }, ++ [10] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "OECF Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_OECF_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_oecf_setting), ++ .flags = 0, ++ }, ++ [11] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "R2Y Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_R2Y_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_r2y_setting), ++ .flags = 0, ++ }, ++ [12] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "SAT Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_SAT_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_sat_setting), ++ .flags = 0, ++ }, ++ [13] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "SAT Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_SHRP_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_sharp_setting), ++ .flags = 0, ++ }, ++ [14] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "YCRV Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_YCRV_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_ycrv_setting), ++ .flags = 0, ++ }, ++ [15] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "SC Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_STAT_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_sc_setting), ++ .flags = 0, ++ }, + }; + + static int isp_init_controls(struct stf_isp_dev *isp_dev) +--- a/drivers/media/platform/starfive/v4l2_driver/stf_video.c ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_video.c +@@ -1281,6 +1281,22 @@ int video_s_selection(struct file *file, + return ret; + } + ++static int stf_video_subscribe_event(struct v4l2_fh *fh, ++ const struct v4l2_event_subscription *sub) ++{ ++ switch (sub->type) { ++ case V4L2_EVENT_FRAME_SYNC: ++ return v4l2_event_subscribe(fh, sub, 2, NULL); ++ //int ret = v4l2_event_subscribe(fh, sub, 2, NULL); ++ //pr_info("subscribe ret: %d\n", ret); ++ //return ret; ++ default: ++ return v4l2_ctrl_subscribe_event(fh, sub); ++ //st_debug(ST_VIN, "unsupport subscribe_event\n"); ++ //return -EINVAL; ++ } ++} ++ + static const struct v4l2_ioctl_ops stf_vid_ioctl_ops = { + .vidioc_querycap = video_querycap, + .vidioc_enum_fmt_vid_cap = video_enum_fmt, +@@ -1305,6 +1321,8 @@ static const struct v4l2_ioctl_ops stf_v + .vidioc_s_parm = video_s_parm, + .vidioc_s_selection = video_s_selection, + .vidioc_g_selection = video_g_selection, ++ .vidioc_subscribe_event = stf_video_subscribe_event, ++ .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + }; + + static const struct v4l2_ioctl_ops stf_vid_ioctl_ops_mp = { +@@ -1331,6 +1349,8 @@ static const struct v4l2_ioctl_ops stf_v + .vidioc_s_parm = video_s_parm, + .vidioc_s_selection = video_s_selection, + .vidioc_g_selection = video_g_selection, ++ .vidioc_subscribe_event = stf_video_subscribe_event, ++ .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + }; + + static const struct v4l2_ioctl_ops stf_vid_ioctl_ops_out = { +@@ -1350,6 +1370,8 @@ static const struct v4l2_ioctl_ops stf_v + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, ++ .vidioc_subscribe_event = stf_video_subscribe_event, ++ .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + }; + + static int video_open(struct file *file) +--- a/drivers/media/platform/starfive/v4l2_driver/stf_vin.c ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_vin.c +@@ -1145,9 +1145,12 @@ static void vin_buffer_done(struct vin_l + spin_lock_irqsave(&line->output_lock, flags); + + while ((ready_buf = vin_buf_get_ready(output))) { +- if (line->id >= VIN_LINE_ISP && line->id <= VIN_LINE_ISP_SS1) { ++ //if (line->id >= VIN_LINE_ISP && line->id <= VIN_LINE_ISP_SS1) { ++ if (line->id == VIN_LINE_ISP_SCD_Y) { + event.u.frame_sync.frame_sequence = output->sequence; +- v4l2_event_queue(line->subdev.devnode, &event); ++ v4l2_event_queue(&(line->video_out.vdev), &event); ++ //v4l2_event_queue(line->subdev.devnode, &event); ++ //pr_info("----------frame sync-----------\n"); + } + + ready_buf->vb.vb2_buf.timestamp = ts; +@@ -1346,7 +1349,10 @@ static int stf_vin_subscribe_event(struc + { + switch (sub->type) { + case V4L2_EVENT_FRAME_SYNC: +- return v4l2_event_subscribe(fh, sub, 0, NULL); ++ //return v4l2_event_subscribe(fh, sub, 2, NULL); ++ int ret = v4l2_event_subscribe(fh, sub, 2, NULL); ++ pr_info("subscribe ret: %d\n", ret); ++ return ret; + default: + st_debug(ST_VIN, "unsupport subscribe_event\n"); + return -EINVAL; +@@ -1355,8 +1361,8 @@ static int stf_vin_subscribe_event(struc + + static const struct v4l2_subdev_core_ops vin_core_ops = { + .s_power = vin_set_power, +- .subscribe_event = stf_vin_subscribe_event, +- .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++ //.subscribe_event = stf_vin_subscribe_event, ++ //.unsubscribe_event = v4l2_event_subdev_unsubscribe, + }; + + static const struct v4l2_subdev_video_ops vin_video_ops = { +--- a/include/uapi/linux/jh7110-isp.h ++++ b/include/uapi/linux/jh7110-isp.h +@@ -15,6 +15,8 @@ + + #include <linux/v4l2-controls.h> + ++#define V4L2_CID_USER_JH7110_ISP_BASE (V4L2_CID_USER_BASE + 0x1170) ++ + #define V4L2_CID_USER_JH7110_ISP_WB_SETTING \ + (V4L2_CID_USER_JH7110_ISP_BASE + 0x0001) + #define V4L2_CID_USER_JH7110_ISP_CAR_SETTING \ +@@ -45,6 +47,8 @@ + (V4L2_CID_USER_JH7110_ISP_BASE + 0x000e) + #define V4L2_CID_USER_JH7110_ISP_YCRV_SETTING \ + (V4L2_CID_USER_JH7110_ISP_BASE + 0x000f) ++#define V4L2_CID_USER_JH7110_ISP_STAT_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x0010) + + struct jh7110_isp_wb_gain { + __u16 gain_r; +@@ -202,13 +206,13 @@ struct jh7110_isp_sat_curve { + }; + + struct jh7110_isp_sat_hue_info { +- __s16 sin; + __s16 cos; ++ __s16 sin; + }; + + struct jh7110_isp_sat_info { + __s16 gain_cmab; +- __s16 gain_cmad; ++ __s16 gain_cmmd; + __s16 threshold_cmb; + __s16 threshold_cmd; + __s16 offset_u; +@@ -230,7 +234,8 @@ struct jh7110_isp_sharp_weight { + + struct jh7110_isp_sharp_strength { + __s16 diff[4]; +- __s16 f[4]; ++ __s16 f[3]; ++ __s32 s[3]; + }; + + struct jh7110_isp_sharp_setting { +@@ -250,4 +255,41 @@ struct jh7110_isp_ycrv_setting { + struct jh7110_isp_ycrv_curve curve; + }; + ++struct jh7110_isp_sc_af_config { ++ __u8 es_hor_mode; ++ __u8 es_sum_mode; ++ __u8 hor_en; ++ __u8 ver_en; ++ __u8 es_ver_thr; ++ __u16 es_hor_thr; ++}; ++ ++struct jh7110_isp_sc_awb_ws { ++ __u8 awb_ws_rl; ++ __u8 awb_ws_ru; ++ __u8 awb_ws_grl; ++ __u8 awb_ws_gru; ++ __u8 awb_ws_gbl; ++ __u8 awb_ws_gbu; ++ __u8 awb_ws_bl; ++ __u8 awb_ws_bu; ++}; ++ ++struct jh7110_isp_sc_awb_point { ++ __u16 intensity; ++ __u8 weight; ++}; ++ ++struct jh7110_isp_sc_awb_config { ++ struct jh7110_isp_sc_awb_ws ws_config; ++ __u8 awb_cw[169]; ++ struct jh7110_isp_sc_awb_point pts[17]; ++}; ++ ++struct jh7110_isp_sc_setting { ++ __u32 enabled; ++ struct jh7110_isp_sc_af_config af_config; ++ struct jh7110_isp_sc_awb_config awb_config; ++}; ++ + #endif diff --git a/target/linux/starfive/patches-6.6/0095-Expand-2-bytes-after-the-SC-buffer-for-the-AE-AWB-fl.patch b/target/linux/starfive/patches-6.6/0095-Expand-2-bytes-after-the-SC-buffer-for-the-AE-AWB-fl.patch new file mode 100644 index 0000000000..d023674f79 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0095-Expand-2-bytes-after-the-SC-buffer-for-the-AE-AWB-fl.patch @@ -0,0 +1,249 @@ +From 5d61c6fd10144605238311d68c99449c4667a345 Mon Sep 17 00:00:00 2001 +From: "zejian.su" <zejian.su@starfivetech.com> +Date: Mon, 7 Aug 2023 10:38:36 +0800 +Subject: [PATCH 095/116] Expand 2 bytes after the SC buffer for the AE/AWB + flag and copy the histogram data to the SC buffer. + +--- + .../platform/starfive/v4l2_driver/stf_isp.c | 37 ++++++++++++++++++- + .../platform/starfive/v4l2_driver/stf_isp.h | 2 +- + .../platform/starfive/v4l2_driver/stf_video.c | 5 --- + .../platform/starfive/v4l2_driver/stf_vin.c | 36 +++++++++--------- + include/uapi/linux/jh7110-isp.h | 33 +++++++++++++++++ + 5 files changed, 87 insertions(+), 26 deletions(-) + +--- a/drivers/media/platform/starfive/v4l2_driver/stf_isp.c ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_isp.c +@@ -323,6 +323,13 @@ FILL_ISP_REGS_FUNC(u16); + #define FILL_ISP_REGS(type, ispbase, offset, value, size, mask, nbits) \ + fill_isp_regs_##type(ispbase, offset, value, size, mask, nbits) + ++static void fill_regs_with_zero(void __iomem *ispbase, u32 offset, u32 size) ++{ ++ u32 i; ++ for(i = 0; i < size; i++, offset += 4) ++ reg_write(ispbase, offset, 0); ++} ++ + static int isp_set_ctrl_wb(struct stf_isp_dev *isp_dev, const void * value) + { + const struct module_register_info * reg_info = &mod_reg_info[imi_awb]; +@@ -335,6 +342,8 @@ static int isp_set_ctrl_wb(struct stf_is + struct stf_vin_dev *vin = isp_dev->stfcamss->vin; + void __iomem *ispbase = vin->isp_base; + ++ fill_regs_with_zero(ispbase, reg_info->cfg_reg, 16); ++ + reg_write(ispbase, reg_addr, r_g); + reg_write(ispbase, reg_addr + 1 * 4, r_g); + reg_write(ispbase, reg_addr + 2 * 4, g_g); +@@ -370,8 +379,13 @@ static int isp_set_ctrl_ccm(struct stf_i + void __iomem *ispbase = vin->isp_base; + + reg_write(ispbase, reg_info->cfg_reg, 6 << 16); ++ fill_regs_with_zero(ispbase, reg_info->cfg_reg + 4, 11); ++ + FILL_ISP_REGS(u32, ispbase, reg_addr, (u32 *)ccm, 12, 0x7ff, 0); + ++ reg_addr += 12 * 4; ++ fill_regs_with_zero(ispbase, reg_addr, 2); ++ + reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); + + return 0; +@@ -640,6 +654,27 @@ static int isp_set_ctrl_sc(struct stf_is + u32 * w_diff = weight_cfg + 2; + s32 i; + ++ // SC dumping axi id ++ reg_write(ispbase, 0x9c, 1 << 24); ++ ++ // SC frame crop ++ reg_write(ispbase, 0xb8, ((u32)(setting->crop_config.v_start) << 16) | setting->crop_config.h_start); ++ ++ // SC config1 ++ reg_write(ispbase, 0xbc, ((u32)(setting->awb_config.sel) << 30) | ((u32)(setting->awb_config.awb_ps_grb_ba) << 16) | ++ ((u32)(setting->crop_config.sw_height) << 8) | setting->crop_config.sw_width); ++ ++ // SC decimation config ++ reg_write(ispbase, 0xd8, ((u32)(setting->crop_config.vkeep) << 24) | ((u32)(setting->crop_config.vperiod) << 16) | ++ ((u32)(setting->crop_config.hkeep) << 8) | setting->crop_config.hperiod); ++ ++ // SC AWB pixel sum config ++ reg_write(ispbase, 0xc4, CREATE_REG_VALUE(u8, &setting->awb_config.ws_ps_config.awb_ps_rl, 4, 0xff, 8)); ++ reg_write(ispbase, 0xc8, CREATE_REG_VALUE(u8, &setting->awb_config.ws_ps_config.awb_ps_bl, 4, 0xff, 8)); ++ reg_write(ispbase, 0xcc, CREATE_REG_VALUE(u16, &setting->awb_config.ws_ps_config.awb_ps_grl, 2, 0xffff, 16)); ++ reg_write(ispbase, 0xd0, CREATE_REG_VALUE(u16, &setting->awb_config.ws_ps_config.awb_ps_gbl, 2, 0xffff, 16)); ++ reg_write(ispbase, 0xd4, CREATE_REG_VALUE(u16, &setting->awb_config.ws_ps_config.awb_ps_grbl, 2, 0xffff, 16)); ++ + // AF register + reg_write(ispbase, 0xc0, + ((u32)(setting->af_config.es_hor_thr & 0x1ff) << 16) | +@@ -686,7 +721,7 @@ static int isp_set_ctrl_sc(struct stf_is + FILL_ISP_REGS(u32, ispbase, reg_addr, weight_cfg, 6, 0xffffffff, 0); + + reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); +- ++ + return 0; + } + +--- a/drivers/media/platform/starfive/v4l2_driver/stf_isp.h ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_isp.h +@@ -18,7 +18,7 @@ + + #define ISP_SCD_BUFFER_SIZE (19 * 256 * 4) // align 128 + #define ISP_YHIST_BUFFER_SIZE (64 * 4) +-#define ISP_SCD_Y_BUFFER_SIZE (ISP_SCD_BUFFER_SIZE + ISP_YHIST_BUFFER_SIZE) ++#define ISP_SCD_Y_BUFFER_SIZE (ISP_SCD_BUFFER_SIZE + ISP_YHIST_BUFFER_SIZE + 2) + #define ISP_RAW_DATA_BITS 12 + #define SCALER_RATIO_MAX 1 // no compose function + #define STF_ISP_REG_OFFSET_MAX 0x0FFF +--- a/drivers/media/platform/starfive/v4l2_driver/stf_video.c ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_video.c +@@ -1287,13 +1287,8 @@ static int stf_video_subscribe_event(str + switch (sub->type) { + case V4L2_EVENT_FRAME_SYNC: + return v4l2_event_subscribe(fh, sub, 2, NULL); +- //int ret = v4l2_event_subscribe(fh, sub, 2, NULL); +- //pr_info("subscribe ret: %d\n", ret); +- //return ret; + default: + return v4l2_ctrl_subscribe_event(fh, sub); +- //st_debug(ST_VIN, "unsupport subscribe_event\n"); +- //return -EINVAL; + } + } + +--- a/drivers/media/platform/starfive/v4l2_driver/stf_vin.c ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_vin.c +@@ -1147,6 +1147,17 @@ static void vin_buffer_done(struct vin_l + while ((ready_buf = vin_buf_get_ready(output))) { + //if (line->id >= VIN_LINE_ISP && line->id <= VIN_LINE_ISP_SS1) { + if (line->id == VIN_LINE_ISP_SCD_Y) { ++#define ADDR_REG_YHIST_ACC_0 0x0D00 ++ struct stf_vin2_dev *vin_dev = line_to_vin2_dev(line); ++ struct stf_vin_dev *vin = vin_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ u32 y_hist_reg_addr = ADDR_REG_YHIST_ACC_0; ++ u32 * y_hist_addr = (u32 *)ready_buf->vaddr_sc; ++ s32 i = 0; ++ ++ for(i = 0; i < 64; i++, y_hist_reg_addr += 4) ++ y_hist_addr[i] = reg_read(ispbase, y_hist_reg_addr); ++ + event.u.frame_sync.frame_sequence = output->sequence; + v4l2_event_queue(&(line->video_out.vdev), &event); + //v4l2_event_queue(line->subdev.devnode, &event); +@@ -1246,9 +1257,14 @@ static void vin_change_buffer(struct vin + scd_type = vin_dev->hw_ops->vin_isp_get_scd_type(vin_dev); + ready_buf->vb.flags &= ~(V4L2_BUF_FLAG_PFRAME | V4L2_BUF_FLAG_BFRAME); + if (scd_type == AWB_TYPE) ++ { + ready_buf->vb.flags |= V4L2_BUF_FLAG_PFRAME; +- else ++ *((u16 *)(ready_buf->vaddr_sc + ISP_SCD_BUFFER_SIZE + ISP_YHIST_BUFFER_SIZE)) = 0xffff; ++ }else{ + ready_buf->vb.flags |= V4L2_BUF_FLAG_BFRAME; ++ *((u16 *)(ready_buf->vaddr_sc + ISP_SCD_BUFFER_SIZE + ISP_YHIST_BUFFER_SIZE)) = 0; ++ } ++ + if (!output->frame_skip) { + output->frame_skip = ISP_AWB_OECF_SKIP_FRAME; + scd_type = scd_type == AWB_TYPE ? OECF_TYPE : AWB_TYPE; +@@ -1343,26 +1359,8 @@ static int vin_link_setup(struct media_e + return 0; + } + +-static int stf_vin_subscribe_event(struct v4l2_subdev *sd, +- struct v4l2_fh *fh, +- struct v4l2_event_subscription *sub) +-{ +- switch (sub->type) { +- case V4L2_EVENT_FRAME_SYNC: +- //return v4l2_event_subscribe(fh, sub, 2, NULL); +- int ret = v4l2_event_subscribe(fh, sub, 2, NULL); +- pr_info("subscribe ret: %d\n", ret); +- return ret; +- default: +- st_debug(ST_VIN, "unsupport subscribe_event\n"); +- return -EINVAL; +- } +-} +- + static const struct v4l2_subdev_core_ops vin_core_ops = { + .s_power = vin_set_power, +- //.subscribe_event = stf_vin_subscribe_event, +- //.unsubscribe_event = v4l2_event_subdev_unsubscribe, + }; + + static const struct v4l2_subdev_video_ops vin_video_ops = { +--- a/include/uapi/linux/jh7110-isp.h ++++ b/include/uapi/linux/jh7110-isp.h +@@ -255,6 +255,17 @@ struct jh7110_isp_ycrv_setting { + struct jh7110_isp_ycrv_curve curve; + }; + ++struct jh7110_isp_sc_config { ++ __u16 h_start; ++ __u16 v_start; ++ __u8 sw_width; ++ __u8 sw_height; ++ __u8 hperiod; ++ __u8 hkeep; ++ __u8 vperiod; ++ __u8 vkeep; ++}; ++ + struct jh7110_isp_sc_af_config { + __u8 es_hor_mode; + __u8 es_sum_mode; +@@ -264,6 +275,23 @@ struct jh7110_isp_sc_af_config { + __u16 es_hor_thr; + }; + ++struct jh7110_isp_sc_awb_ps { ++ __u8 awb_ps_rl; ++ __u8 awb_ps_ru; ++ __u8 awb_ps_gl; ++ __u8 awb_ps_gu; ++ __u8 awb_ps_bl; ++ __u8 awb_ps_bu; ++ __u8 awb_ps_yl; ++ __u8 awb_ps_yu; ++ __u16 awb_ps_grl; ++ __u16 awb_ps_gru; ++ __u16 awb_ps_gbl; ++ __u16 awb_ps_gbu; ++ __u16 awb_ps_grbl; ++ __u16 awb_ps_grbu; ++}; ++ + struct jh7110_isp_sc_awb_ws { + __u8 awb_ws_rl; + __u8 awb_ws_ru; +@@ -275,12 +303,16 @@ struct jh7110_isp_sc_awb_ws { + __u8 awb_ws_bu; + }; + ++ + struct jh7110_isp_sc_awb_point { + __u16 intensity; + __u8 weight; + }; + + struct jh7110_isp_sc_awb_config { ++ struct jh7110_isp_sc_awb_ps ws_ps_config; ++ __u8 awb_ps_grb_ba; ++ __u8 sel; + struct jh7110_isp_sc_awb_ws ws_config; + __u8 awb_cw[169]; + struct jh7110_isp_sc_awb_point pts[17]; +@@ -288,6 +320,7 @@ struct jh7110_isp_sc_awb_config { + + struct jh7110_isp_sc_setting { + __u32 enabled; ++ struct jh7110_isp_sc_config crop_config; + struct jh7110_isp_sc_af_config af_config; + struct jh7110_isp_sc_awb_config awb_config; + }; diff --git a/target/linux/starfive/patches-6.6/0096-Add-ISP-control-for-video2-and-video3.patch b/target/linux/starfive/patches-6.6/0096-Add-ISP-control-for-video2-and-video3.patch new file mode 100644 index 0000000000..5737fb255e --- /dev/null +++ b/target/linux/starfive/patches-6.6/0096-Add-ISP-control-for-video2-and-video3.patch @@ -0,0 +1,117 @@ +From 04eff0f76091015cbecd39de41d45c493e7a91db Mon Sep 17 00:00:00 2001 +From: "zejian.su" <zejian.su@starfivetech.com> +Date: Mon, 30 Oct 2023 16:09:58 +0800 +Subject: [PATCH 096/116] Add ISP control for video2 and video3. + +Signed-off-by: zejian.su <zejian.su@starfivetech.com> +--- + .../platform/starfive/v4l2_driver/stf_isp.c | 46 +++++++++++++++++++ + include/uapi/linux/jh7110-isp.h | 23 ++++++++++ + 2 files changed, 69 insertions(+) + +--- a/drivers/media/platform/starfive/v4l2_driver/stf_isp.c ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_isp.c +@@ -725,6 +725,24 @@ static int isp_set_ctrl_sc(struct stf_is + return 0; + } + ++static int isp_set_ctrl_outss(struct stf_isp_dev *isp_dev, const void * value) ++{ ++ const struct jh7110_isp_outss_setting * setting = (const struct jh7110_isp_outss_setting *)value; ++ struct stf_vin_dev *vin = isp_dev->stfcamss->vin; ++ void __iomem *ispbase = vin->isp_base; ++ u32 reg_addr = !setting->which ? 0xa9c : 0xab4; ++ ++ if(!setting->stride) ++ return 0; ++ ++ // Output Image Stride Register, 8-byte(64bit) granularity. ++ reg_write(ispbase, reg_addr, setting->stride); ++ reg_write(ispbase, reg_addr + 4, ((setting->hsm << 16) | (setting->hsm & 0x3))); ++ reg_write(ispbase, reg_addr + 8, ((setting->vsm << 16) | (setting->vsm & 0x3))); ++ ++ return 0; ++} ++ + static int isp_s_ctrl(struct v4l2_ctrl *ctrl) + { + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); +@@ -822,6 +840,10 @@ static int isp_s_ctrl(struct v4l2_ctrl * + case V4L2_CID_USER_JH7110_ISP_STAT_SETTING: + ret = isp_set_ctrl_sc(isp_dev, ctrl->p_new.p_u8); + break; ++ case V4L2_CID_USER_JH7110_ISP_OUTSS0_SETTING: ++ case V4L2_CID_USER_JH7110_ISP_OUTSS1_SETTING: ++ ret = isp_set_ctrl_outss(isp_dev, ctrl->p_new.p_u8); ++ break; + default: + ret = -EINVAL; + break; +@@ -1028,6 +1050,30 @@ struct v4l2_ctrl_config isp_ctrl[] = { + .dims[0] = sizeof(struct jh7110_isp_sc_setting), + .flags = 0, + }, ++ [16] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "OUTSS Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_OUTSS0_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_outss_setting), ++ .flags = 0, ++ }, ++ [17] = { ++ .ops = &isp_ctrl_ops, ++ .type = V4L2_CTRL_TYPE_U8, ++ .def = 0, ++ .min = 0x00, ++ .max = 0xff, ++ .step = 1, ++ .name = "OUTSS Setting", ++ .id = V4L2_CID_USER_JH7110_ISP_OUTSS1_SETTING, ++ .dims[0] = sizeof(struct jh7110_isp_outss_setting), ++ .flags = 0, ++ }, + }; + + static int isp_init_controls(struct stf_isp_dev *isp_dev) +--- a/include/uapi/linux/jh7110-isp.h ++++ b/include/uapi/linux/jh7110-isp.h +@@ -49,6 +49,10 @@ + (V4L2_CID_USER_JH7110_ISP_BASE + 0x000f) + #define V4L2_CID_USER_JH7110_ISP_STAT_SETTING \ + (V4L2_CID_USER_JH7110_ISP_BASE + 0x0010) ++#define V4L2_CID_USER_JH7110_ISP_OUTSS0_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x0011) ++#define V4L2_CID_USER_JH7110_ISP_OUTSS1_SETTING \ ++ (V4L2_CID_USER_JH7110_ISP_BASE + 0x0012) + + struct jh7110_isp_wb_gain { + __u16 gain_r; +@@ -325,4 +329,23 @@ struct jh7110_isp_sc_setting { + struct jh7110_isp_sc_awb_config awb_config; + }; + ++struct jh7110_isp_outss_setting { ++ __u8 which; ++ __u16 stride; // Output Image Stride Register, 8-byte(64bit) granularity. ++ __u8 hsm; // horizontal scale mode ++ __u32 hsf; // horizontal scale factor (time 4096) ++ __u8 vsm; // vertical scale mode ++ __u32 vsf; // vertical scale factor (time 4096) ++}; ++ ++struct jh7110_isp_sc_buffer { ++ __u32 y_histogram[64]; ++ __u32 reserv0[33]; ++ __u32 bright_sc[4096]; ++ __u32 reserv1[96]; ++ __u32 ae_hist_y[128]; ++ __u32 reserv2[511]; ++ __u16 flag; ++}; ++ + #endif diff --git a/target/linux/starfive/patches-6.6/0097-media-starfive-Update-ISP-initialzation.patch b/target/linux/starfive/patches-6.6/0097-media-starfive-Update-ISP-initialzation.patch new file mode 100644 index 0000000000..b1d857e3cf --- /dev/null +++ b/target/linux/starfive/patches-6.6/0097-media-starfive-Update-ISP-initialzation.patch @@ -0,0 +1,226 @@ +From 883745d4728e524babfe7d04cbb8a925c22aa6b5 Mon Sep 17 00:00:00 2001 +From: Changhuang Liang <changhuang.liang@starfivetech.com> +Date: Mon, 13 Nov 2023 14:46:50 +0800 +Subject: [PATCH 097/116] media: starfive: Update ISP initialzation + +ISP compatible stf_isp_ctrl and libcamera. + +Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com> +--- + .../platform/starfive/v4l2_driver/stf_isp.c | 55 ++++++++----------- + 1 file changed, 24 insertions(+), 31 deletions(-) + +--- a/drivers/media/platform/starfive/v4l2_driver/stf_isp.c ++++ b/drivers/media/platform/starfive/v4l2_driver/stf_isp.c +@@ -402,7 +402,7 @@ static int isp_set_ctrl_cfa(struct stf_i + + reg_write(ispbase, reg_addr, ((u32)(cfg->cross_cov & 0x3) << 4) | (cfg->hv_width & 0xf)); + reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); +- ++ + return 0; + } + +@@ -419,7 +419,7 @@ static int isp_set_ctrl_ctc(struct stf_i + reg_value |= ((u32)(cfg->max_gt & 0x3ff) << 16) | (cfg->min_gt & 0x3ff); + reg_write(ispbase, reg_addr, reg_value); + reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); +- ++ + return 0; + } + +@@ -434,7 +434,7 @@ static int isp_set_ctrl_dbc(struct stf_i + + reg_write(ispbase, reg_addr, ((u32)(cfg->bad_gt & 0x3ff) << 16) | (cfg->bad_xt & 0x3ff)); + reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); +- ++ + return 0; + } + +@@ -459,7 +459,7 @@ static int isp_set_ctrl_dnyuv(struct stf + reg_write(ispbase, reg_addr + 36, CREATE_REG_VALUE(u16, &cfg->uv_curve[4], 2, 0x3ff, 16)); + + reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); +- ++ + return 0; + } + +@@ -472,7 +472,7 @@ static int isp_set_ctrl_gmargb(struct st + struct stf_vin_dev *vin = isp_dev->stfcamss->vin; + void __iomem *ispbase = vin->isp_base; + s32 i; +- ++ + for(i = 0; i < 15; i++, curve++, reg_addr += 4) + reg_write(ispbase, reg_addr, ((u32)curve->sg_val << 16) | (curve->g_val & 0x3ff)); + +@@ -490,7 +490,7 @@ static int isp_set_ctrl_lccf(struct stf_ + struct stf_vin_dev *vin = isp_dev->stfcamss->vin; + void __iomem *ispbase = vin->isp_base; + s32 i; +- ++ + reg_write(ispbase, reg_addr, ((u32)(setting->circle.center_y & 0x7fff) << 16) | (setting->circle.center_x & 0x7fff)); + reg_write(ispbase, reg_addr + 8, setting->circle.radius & 0xf); + reg_addr += 0x90; +@@ -498,7 +498,7 @@ static int isp_set_ctrl_lccf(struct stf_ + reg_write(ispbase, reg_addr, CREATE_REG_VALUE(u16, params, 2, 0x1fff, 16)); + + reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); +- ++ + return 0; + } + +@@ -511,7 +511,7 @@ static int isp_set_ctrl_obc(struct stf_i + struct stf_vin_dev *vin = isp_dev->stfcamss->vin; + void __iomem *ispbase = vin->isp_base; + s32 i; +- ++ + reg_write(ispbase, reg_addr, ((u32)(setting->win_size.height & 0xf) << 4) | (setting->win_size.width & 0xf)); + + reg_addr += 0x2ac; //0x2e0 +@@ -519,7 +519,7 @@ static int isp_set_ctrl_obc(struct stf_i + reg_write(ispbase, reg_addr, CREATE_REG_VALUE(u8, params, 4, 0xff, 8)); + + reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); +- ++ + return 0; + } + +@@ -535,7 +535,7 @@ static int isp_set_ctrl_oecf(struct stf_ + void __iomem *ispbase = vin->isp_base; + u32 x, y, slope; + s32 i; +- ++ + for(i = 0; i < 32; i++, reg_x_addr += 4, reg_y_addr += 4, reg_s_addr += 4) { + x = pts->x & 0x3ff; + y = pts->y & 0x3ff; +@@ -565,12 +565,12 @@ static int isp_set_ctrl_r2y(struct stf_i + struct stf_vin_dev *vin = isp_dev->stfcamss->vin; + void __iomem *ispbase = vin->isp_base; + s32 i; +- ++ + for(i = 0; i < 9; i++, reg_addr += 4) + reg_write(ispbase, reg_addr, (u32)(params[i] & 0x1ff)); + + reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); +- ++ + return 0; + } + +@@ -584,7 +584,7 @@ static int isp_set_ctrl_sat(struct stf_i + struct stf_vin_dev *vin = isp_dev->stfcamss->vin; + void __iomem *ispbase = vin->isp_base; + s32 i; +- ++ + for(i = 0; i < 3; i++, reg_addr += 4, params += 2) + reg_write(ispbase, reg_addr, CREATE_REG_VALUE(u16, params, 2, 0xfff, 16)); + reg_write(ispbase, reg_addr, CREATE_REG_VALUE(u16, &setting->hue_info.cos, 2, 0x3ff, 16)); +@@ -597,7 +597,7 @@ static int isp_set_ctrl_sat(struct stf_i + reg_write(ispbase, reg_addr, CREATE_REG_VALUE(u16, params, 2, 0x3fff, 16)); + + reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); +- ++ + return 0; + } + +@@ -609,7 +609,7 @@ static int isp_set_ctrl_shrp(struct stf_ + struct stf_vin_dev *vin = isp_dev->stfcamss->vin; + void __iomem *ispbase = vin->isp_base; + s32 i; +- ++ + for(i = 0; i < 4; i++, reg_addr += 4) + reg_write(ispbase, reg_addr, ((u32)(setting->strength.diff[i] & 0x3ff) << 16) | ((u32)(setting->weight.weight[i] & 0xf) << 8)); + FILL_ISP_REGS(u8, ispbase, reg_addr, (u8 *)(&setting->weight.weight[4]), 15 - 4, 0xf, 8); +@@ -622,7 +622,7 @@ static int isp_set_ctrl_shrp(struct stf_ + reg_write(ispbase, reg_addr, ((u32)(setting->pdirf & 0xf) << 28) | ((u32)(setting->ndirf & 0xf) << 24) | (setting->weight.recip_wei_sum & 0x3fffff)); + + reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); +- ++ + return 0; + } + +@@ -633,11 +633,11 @@ static int isp_set_ctrl_ycrv(struct stf_ + u32 reg_addr = reg_info->cfg_reg; + struct stf_vin_dev *vin = isp_dev->stfcamss->vin; + void __iomem *ispbase = vin->isp_base; +- ++ + FILL_ISP_REGS(u16, ispbase, reg_addr, (u16 *)(setting->curve.y), 64, 0x3ff, 0); + + reg_set_bit(ispbase, reg_info->en_reg, 1 << reg_info->en_nbit, setting->enabled ? 1 << reg_info->en_nbit : 0); +- ++ + return 0; + } + +@@ -661,11 +661,11 @@ static int isp_set_ctrl_sc(struct stf_is + reg_write(ispbase, 0xb8, ((u32)(setting->crop_config.v_start) << 16) | setting->crop_config.h_start); + + // SC config1 +- reg_write(ispbase, 0xbc, ((u32)(setting->awb_config.sel) << 30) | ((u32)(setting->awb_config.awb_ps_grb_ba) << 16) | ++ reg_write(ispbase, 0xbc, ((u32)(setting->awb_config.sel) << 30) | ((u32)(setting->awb_config.awb_ps_grb_ba) << 16) | + ((u32)(setting->crop_config.sw_height) << 8) | setting->crop_config.sw_width); + + // SC decimation config +- reg_write(ispbase, 0xd8, ((u32)(setting->crop_config.vkeep) << 24) | ((u32)(setting->crop_config.vperiod) << 16) | ++ reg_write(ispbase, 0xd8, ((u32)(setting->crop_config.vkeep) << 24) | ((u32)(setting->crop_config.vperiod) << 16) | + ((u32)(setting->crop_config.hkeep) << 8) | setting->crop_config.hperiod); + + // SC AWB pixel sum config +@@ -676,7 +676,7 @@ static int isp_set_ctrl_sc(struct stf_is + reg_write(ispbase, 0xd4, CREATE_REG_VALUE(u16, &setting->awb_config.ws_ps_config.awb_ps_grbl, 2, 0xffff, 16)); + + // AF register +- reg_write(ispbase, 0xc0, ++ reg_write(ispbase, 0xc0, + ((u32)(setting->af_config.es_hor_thr & 0x1ff) << 16) | + ((u32)(setting->af_config.es_ver_thr & 0xff) << 8) | + ((setting->af_config.ver_en & 0x1) << 3) | +@@ -1217,7 +1217,7 @@ static int isp_get_interface_type(struct + static int isp_set_stream(struct v4l2_subdev *sd, int enable) + { + struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd); +- int ret = 0, interface_type; ++ int interface_type; + struct v4l2_mbus_framefmt *fmt; + struct v4l2_event src_ch = { 0 }; + +@@ -1225,6 +1225,7 @@ static int isp_set_stream(struct v4l2_su + mutex_lock(&isp_dev->stream_lock); + if (enable) { + if (isp_dev->stream_count == 0) { ++ v4l2_ctrl_handler_setup(&isp_dev->ctrls.handler); + isp_dev->hw_ops->isp_clk_enable(isp_dev); + if (!user_config_isp) + isp_dev->hw_ops->isp_config_set(isp_dev); +@@ -1256,15 +1257,7 @@ static int isp_set_stream(struct v4l2_su + exit: + mutex_unlock(&isp_dev->stream_lock); + +- mutex_lock(&isp_dev->power_lock); +- /* restore controls */ +- if (enable && isp_dev->power_count == 1) { +- mutex_unlock(&isp_dev->power_lock); +- ret = v4l2_ctrl_handler_setup(&isp_dev->ctrls.handler); +- } else +- mutex_unlock(&isp_dev->power_lock); +- +- return ret; ++ return 0; + } + + /*Try to match sensor format with sink, and then get the index as default.*/ diff --git a/target/linux/starfive/patches-6.6/0098-crypto-jh7110-Comment-RSA-algo-register.patch b/target/linux/starfive/patches-6.6/0098-crypto-jh7110-Comment-RSA-algo-register.patch new file mode 100644 index 0000000000..ca36e5bf94 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0098-crypto-jh7110-Comment-RSA-algo-register.patch @@ -0,0 +1,36 @@ +From 02f84ff43453b0f8dc2a1e4885e7003929349ee6 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Tue, 5 Mar 2024 11:00:21 +0800 +Subject: [PATCH 098/116] crypto: jh7110: Comment RSA algo register + +There are some issues in RSA algo, which will cause kernel crash. +So comment RSA algo register temporarily. +This commit should be reverted after the RSA issues are fixed. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/crypto/starfive/jh7110-cryp.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +--- a/drivers/crypto/starfive/jh7110-cryp.c ++++ b/drivers/crypto/starfive/jh7110-cryp.c +@@ -194,14 +194,14 @@ static int starfive_cryp_probe(struct pl + if (ret) + goto err_algs_hash; + +- ret = starfive_rsa_register_algs(); +- if (ret) +- goto err_algs_rsa; ++// ret = starfive_rsa_register_algs(); ++// if (ret) ++// goto err_algs_rsa; + + return 0; + +-err_algs_rsa: +- starfive_hash_unregister_algs(); ++// err_algs_rsa: ++// starfive_hash_unregister_algs(); + err_algs_hash: + starfive_aes_unregister_algs(); + err_algs_aes: diff --git a/target/linux/starfive/patches-6.6/0099-riscv-dts-starfive-jh7110-evb-Add-qspi-norflash-part.patch b/target/linux/starfive/patches-6.6/0099-riscv-dts-starfive-jh7110-evb-Add-qspi-norflash-part.patch new file mode 100644 index 0000000000..d04b082d68 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0099-riscv-dts-starfive-jh7110-evb-Add-qspi-norflash-part.patch @@ -0,0 +1,26 @@ +From a7ff2b51e2941526d6924dcf8f1760187d7e5d03 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Mon, 11 Mar 2024 11:10:45 +0800 +Subject: [PATCH 099/116] riscv: dts: starfive: jh7110-evb: Add qspi norflash + partition for uboot-env + +Add qspi norflash partition "uboot-env@f0000", +for synchronizing with other branches. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + arch/riscv/boot/dts/starfive/jh7110-evb.dtsi | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/arch/riscv/boot/dts/starfive/jh7110-evb.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb.dtsi +@@ -568,6 +568,9 @@ + spl@0 { + reg = <0x0 0x40000>; + }; ++ uboot-env@f0000 { ++ reg = <0xf0000 0x10000>; ++ }; + uboot@100000 { + reg = <0x100000 0x300000>; + }; diff --git a/target/linux/starfive/patches-6.6/0100-driver-regulator-pmic-driver-support-kernel-6.6.patch b/target/linux/starfive/patches-6.6/0100-driver-regulator-pmic-driver-support-kernel-6.6.patch new file mode 100644 index 0000000000..ad8b98d17b --- /dev/null +++ b/target/linux/starfive/patches-6.6/0100-driver-regulator-pmic-driver-support-kernel-6.6.patch @@ -0,0 +1,22 @@ +From f96ba2eb39ba950f67edf43e0c5c88ac660bc2a0 Mon Sep 17 00:00:00 2001 +From: Ziv Xu <ziv.xu@starfivetech.com> +Date: Wed, 13 Mar 2024 18:43:27 +0800 +Subject: [PATCH 100/116] driver: regulator: pmic driver support kernel 6.6 + +pmic driver support kernel 6.6 + +Signed-off-by: Ziv Xu <ziv.xu@starfivetech.com> +--- + drivers/regulator/axp20x-regulator.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/drivers/regulator/axp20x-regulator.c ++++ b/drivers/regulator/axp20x-regulator.c +@@ -20,6 +20,7 @@ + #include <linux/mfd/axp20x.h> + #include <linux/module.h> + #include <linux/of.h> ++#include <linux/of_device.h> + #include <linux/platform_device.h> + #include <linux/regmap.h> + #include <linux/regulator/driver.h> diff --git a/target/linux/starfive/patches-6.6/0101-spi-pl022-starfive-Add-platform-bus-register-to-adap.patch b/target/linux/starfive/patches-6.6/0101-spi-pl022-starfive-Add-platform-bus-register-to-adap.patch new file mode 100644 index 0000000000..8112243bc7 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0101-spi-pl022-starfive-Add-platform-bus-register-to-adap.patch @@ -0,0 +1,232 @@ +From c609818850807a1ae5fa17e165f2b66b914188b4 Mon Sep 17 00:00:00 2001 +From: "xingyu.wu" <xingyu.wu@starfivetech.com> +Date: Tue, 28 Jun 2022 22:48:15 +0800 +Subject: [PATCH 101/116] spi-pl022:starfive:Add platform bus register to adapt + overlay + +Add platform bus register to adapt dtbo overlay. + +Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/spi/spi-pl022.c | 137 ++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 131 insertions(+), 6 deletions(-) + +--- a/drivers/spi/spi-pl022.c ++++ b/drivers/spi/spi-pl022.c +@@ -34,6 +34,7 @@ + #include <linux/of.h> + #include <linux/pinctrl/consumer.h> + #include <linux/reset.h> ++#include <linux/platform_device.h> + + /* + * This macro is used to define some register default values. +@@ -2088,7 +2089,10 @@ pl022_platform_data_dt_get(struct device + return NULL; + } + +- pd = devm_kzalloc(dev, sizeof(struct pl022_ssp_controller), GFP_KERNEL); ++ if (strncmp(dev->bus->name, "platform", strlen("platform"))) ++ pd = devm_kzalloc(dev, sizeof(struct pl022_ssp_controller), GFP_KERNEL); ++ else ++ pd = kzalloc(sizeof(struct pl022_ssp_controller), GFP_KERNEL); + if (!pd) + return NULL; + +@@ -2108,6 +2112,14 @@ static int pl022_probe(struct amba_devic + struct spi_controller *host; + struct pl022 *pl022 = NULL; /*Data for this driver */ + int status = 0; ++ int platform_flag = 0; ++ ++ if (strncmp(dev->bus->name, "platform", strlen("platform"))) ++ platform_flag = 0; ++ else ++ platform_flag = 1; ++ dev_dbg(&adev->dev, "bus name:%s platform flag:%d", ++ dev->bus->name, platform_flag); + + dev_info(&adev->dev, + "ARM PL022 driver, device ID: 0x%08x\n", adev->periphid); +@@ -2161,7 +2173,11 @@ static int pl022_probe(struct amba_devic + goto err_no_ioregion; + + pl022->phybase = adev->res.start; +- pl022->virtbase = devm_ioremap(dev, adev->res.start, ++ if (platform_flag) ++ pl022->virtbase = ioremap(adev->res.start, ++ resource_size(&adev->res)); ++ else ++ pl022->virtbase = devm_ioremap(dev, adev->res.start, + resource_size(&adev->res)); + if (pl022->virtbase == NULL) { + status = -ENOMEM; +@@ -2170,7 +2186,10 @@ static int pl022_probe(struct amba_devic + dev_info(&adev->dev, "mapped registers from %pa to %p\n", + &adev->res.start, pl022->virtbase); + +- pl022->clk = devm_clk_get(&adev->dev, NULL); ++ if (platform_flag) ++ pl022->clk = clk_get(&adev->dev, NULL); ++ else ++ pl022->clk = devm_clk_get(&adev->dev, NULL); + if (IS_ERR(pl022->clk)) { + status = PTR_ERR(pl022->clk); + dev_err(&adev->dev, "could not retrieve SSP/SPI bus clock\n"); +@@ -2183,7 +2202,10 @@ static int pl022_probe(struct amba_devic + goto err_no_clk_en; + } + +- pl022->rst = devm_reset_control_get(&adev->dev, NULL); ++ if (platform_flag) ++ pl022->rst = reset_control_get_exclusive(&adev->dev, NULL); ++ else ++ pl022->rst = devm_reset_control_get(&adev->dev, NULL); + if (IS_ERR(pl022->rst)) { + status = PTR_ERR(pl022->rst); + dev_err(&adev->dev, "could not retrieve SSP/SPI bus reset\n"); +@@ -2205,7 +2227,11 @@ static int pl022_probe(struct amba_devic + SSP_CR1(pl022->virtbase)); + load_ssp_default_config(pl022); + +- status = devm_request_irq(dev, adev->irq[0], pl022_interrupt_handler, ++ if (platform_flag) ++ status = request_irq(adev->irq[0], pl022_interrupt_handler, ++ 0, "pl022", pl022); ++ else ++ status = devm_request_irq(dev, adev->irq[0], pl022_interrupt_handler, + 0, "pl022", pl022); + if (status < 0) { + dev_err(&adev->dev, "probe - cannot get IRQ (%d)\n", status); +@@ -2230,7 +2256,10 @@ static int pl022_probe(struct amba_devic + + /* Register with the SPI framework */ + amba_set_drvdata(adev, pl022); +- status = devm_spi_register_controller(&adev->dev, host); ++ if (platform_flag) ++ status = spi_register_controller(host); ++ else ++ status = devm_spi_register_controller(&adev->dev, host); + if (status != 0) { + dev_err_probe(&adev->dev, status, + "problem registering spi host\n"); +@@ -2255,15 +2284,26 @@ static int pl022_probe(struct amba_devic + if (platform_info->enable_dma) + pl022_dma_remove(pl022); + err_no_irq: ++ if (platform_flag) ++ free_irq(adev->irq[0], pl022); ++ reset_control_assert(pl022->rst); + err_no_rst_de: ++ if (platform_flag) ++ reset_control_put(pl022->rst); + err_no_rst: + clk_disable_unprepare(pl022->clk); + err_no_clk_en: ++ if (platform_flag) ++ clk_put(pl022->clk); + err_no_clk: ++ if (platform_flag) ++ iounmap(pl022->virtbase); + err_no_ioremap: + amba_release_regions(adev); + err_no_ioregion: + spi_controller_put(host); ++ if (platform_flag) ++ kfree(platform_info); + return status; + } + +@@ -2464,6 +2504,91 @@ static void __exit pl022_exit(void) + } + module_exit(pl022_exit); + ++/* ++ * Register PL022 in platform bus to accommodate overlay use. ++ * Because overlay only trigger response from the platform bus ++ * not amba bus. ++ */ ++static int starfive_of_pl022_probe(struct platform_device *pdev) ++{ ++ int ret; ++ const struct amba_id id = { ++ .id = 0x00041022, ++ .mask = 0x000fffff, ++ .data = &vendor_arm ++ }; ++ struct amba_device *pcdev; ++ struct device *dev = &pdev->dev; ++ ++ pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL); ++ if (!pcdev) ++ return -ENOMEM; ++ ++ pcdev->dev = pdev->dev; ++ pcdev->periphid = id.id; ++ pcdev->res = *(pdev->resource); ++ ++ pcdev->irq[0] = platform_get_irq(pdev, 0); ++ if (pcdev->irq[0] < 0) { ++ dev_err(dev, "failed to get irq\n"); ++ ret = -EINVAL; ++ } ++ ++ ret = pl022_probe(pcdev, &id); ++ ++ return ret; ++} ++ ++static int starfive_of_pl022_remove(struct platform_device *pdev) ++{ ++ u32 size; ++ int irq; ++ struct pl022 *pl022 = dev_get_drvdata(&pdev->dev); ++ ++ if (!pl022) ++ return 0; ++ ++ pm_runtime_get_noresume(&pdev->dev); ++ ++ load_ssp_default_config(pl022); ++ if (pl022->host_info->enable_dma) ++ pl022_dma_remove(pl022); ++ ++ irq = platform_get_irq(pdev, 0); ++ free_irq(irq, pl022); ++ reset_control_assert(pl022->rst); ++ reset_control_put(pl022->rst); ++ clk_disable_unprepare(pl022->clk); ++ clk_put(pl022->clk); ++ iounmap(pl022->virtbase); ++ kfree(pl022->host_info); ++ ++ size = resource_size(pdev->resource); ++ release_mem_region(pdev->resource->start, size); ++ tasklet_disable(&pl022->pump_transfers); ++ return 0; ++} ++ ++static const struct of_device_id starfive_of_pl022_match[] = { ++ { .compatible = "starfive,jh7110-spi-pl022" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, starfive_of_pl022_match); ++ ++static struct platform_driver starfive_of_pl022_driver = { ++ .driver = { ++ .name = "starfive-spi-pl022", ++ .of_match_table = starfive_of_pl022_match, ++ .pm = &pl022_dev_pm_ops, ++ }, ++ .probe = starfive_of_pl022_probe, ++ .remove = starfive_of_pl022_remove, ++}; ++ ++module_platform_driver(starfive_of_pl022_driver); ++/* platform register end */ ++ ++MODULE_AUTHOR("xingyu.wu <xingyu.wu@starfivetech.com>"); + MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); + MODULE_DESCRIPTION("PL022 SSP Controller Driver"); + MODULE_LICENSE("GPL"); diff --git a/target/linux/starfive/patches-6.6/0102-spi-pl022-starfive-Avoid-power-device-error-when-CON.patch b/target/linux/starfive/patches-6.6/0102-spi-pl022-starfive-Avoid-power-device-error-when-CON.patch new file mode 100644 index 0000000000..bd4709f01d --- /dev/null +++ b/target/linux/starfive/patches-6.6/0102-spi-pl022-starfive-Avoid-power-device-error-when-CON.patch @@ -0,0 +1,92 @@ +From f94a07310f18720faa0cc773a1aec5a7b1bf3928 Mon Sep 17 00:00:00 2001 +From: "xingyu.wu" <xingyu.wu@starfivetech.com> +Date: Tue, 19 Jul 2022 14:49:20 +0800 +Subject: [PATCH 102/116] spi:pl022-starfive:Avoid power device error when + CONFIG_PM enable + +It would be error when CONFIG_PM enable and use overlay by of-platform to register. + +Add some power manager operation in platform probe function. + +Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com> +Signed-off-by: Ziv Xu <ziv.xu@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/spi/spi-pl022.c | 36 ++++++++++++++++++++++++++++++++++-- + 1 file changed, 34 insertions(+), 2 deletions(-) + +--- a/drivers/spi/spi-pl022.c ++++ b/drivers/spi/spi-pl022.c +@@ -35,6 +35,8 @@ + #include <linux/pinctrl/consumer.h> + #include <linux/reset.h> + #include <linux/platform_device.h> ++#include <linux/clk/clk-conf.h> ++#include <linux/pm_domain.h> + + /* + * This macro is used to define some register default values. +@@ -2266,7 +2268,8 @@ static int pl022_probe(struct amba_devic + goto err_spi_register; + } + dev_dbg(dev, "probe succeeded\n"); +- ++ if (!platform_flag) ++ platform_info->autosuspend_delay = 100; + /* let runtime pm put suspend */ + if (platform_info->autosuspend_delay > 0) { + dev_info(&adev->dev, +@@ -2276,7 +2279,10 @@ static int pl022_probe(struct amba_devic + platform_info->autosuspend_delay); + pm_runtime_use_autosuspend(dev); + } +- pm_runtime_put(dev); ++ if (platform_flag) ++ clk_disable_unprepare(pl022->clk); ++ else ++ pm_runtime_put(dev); + + return 0; + +@@ -2534,8 +2540,33 @@ static int starfive_of_pl022_probe(struc + ret = -EINVAL; + } + ++ ret = of_clk_set_defaults(dev->of_node, false); ++ if (ret < 0) ++ goto err_probe; ++ ++ ret = dev_pm_domain_attach(dev, true); ++ if (ret) ++ goto err_probe; ++ + ret = pl022_probe(pcdev, &id); + ++ struct pl022 *pl022 = amba_get_drvdata(pcdev); ++ ++ pl022->host->dev.parent = &pdev->dev; ++ platform_set_drvdata(pdev, pl022); ++ ++ pm_runtime_enable(&pdev->dev); ++ pm_runtime_set_autosuspend_delay(&pdev->dev, 100); ++ pm_runtime_use_autosuspend(&pdev->dev); ++ ++ if (ret) { ++ pm_runtime_disable(dev); ++ pm_runtime_set_suspended(dev); ++ pm_runtime_put_noidle(dev); ++ dev_pm_domain_detach(dev, true); ++ } ++ ++err_probe: + return ret; + } + +@@ -2566,6 +2597,7 @@ static int starfive_of_pl022_remove(stru + size = resource_size(pdev->resource); + release_mem_region(pdev->resource->start, size); + tasklet_disable(&pl022->pump_transfers); ++ pm_runtime_disable(&pdev->dev); + return 0; + } + diff --git a/target/linux/starfive/patches-6.6/0103-spi-pl022-starfive-fix-the-problem-of-spi-overlay-re.patch b/target/linux/starfive/patches-6.6/0103-spi-pl022-starfive-fix-the-problem-of-spi-overlay-re.patch new file mode 100644 index 0000000000..3e36e84641 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0103-spi-pl022-starfive-fix-the-problem-of-spi-overlay-re.patch @@ -0,0 +1,403 @@ +From 733e7bd23a1efad15a724fbdbce8d9f06aa6813a Mon Sep 17 00:00:00 2001 +From: "ziv.xu" <ziv.xu@starfive.com> +Date: Wed, 23 Nov 2022 14:53:58 +0800 +Subject: [PATCH 103/116] spi-pl022-starfive:fix the problem of spi overlay + reload + +fix the problem of spi overlay reload + +Signed-off-by: ziv.xu <ziv.xu@starfive.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/spi/spi-pl022.c | 270 ++++++++++++++++++++++++++++------------ + 1 file changed, 188 insertions(+), 82 deletions(-) + +--- a/drivers/spi/spi-pl022.c ++++ b/drivers/spi/spi-pl022.c +@@ -2106,6 +2106,172 @@ pl022_platform_data_dt_get(struct device + return pd; + } + ++static int pl022_platform_probe(struct platform_device *pdev, const struct amba_id *id) ++{ ++ struct device *dev = &pdev->dev; ++ struct spi_controller *host; ++ struct pl022_ssp_controller *platform_info; ++ struct amba_device *adev; ++ struct pl022 *pl022 = NULL; ++ struct resource *res; ++ int status = 0; ++ int irq; ++ ++ dev_info(dev, ++ "ARM PL022 driver for StarFive SoC platform, device ID: 0x%08x\n", ++ id->id); ++ ++ adev = devm_kzalloc(dev, sizeof(*adev), GFP_KERNEL); ++ adev->dev = pdev->dev; ++ platform_info = pl022_platform_data_dt_get(dev); ++ if (!platform_info) { ++ dev_err(dev, "probe: no platform data defined\n"); ++ return -ENODEV; ++ } ++ /* Allocate host with space for data */ ++ host = spi_alloc_host(dev, sizeof(struct pl022)); ++ if (host == NULL) { ++ dev_err(dev, "probe - cannot alloc SPI host\n"); ++ return -ENOMEM; ++ } ++ ++ pl022 = spi_controller_get_devdata(host); ++ pl022->host = host; ++ pl022->host_info = platform_info; ++ pl022->adev = adev; ++ pl022->vendor = id->data; ++ pl022->host->dev.parent = &pdev->dev; ++ /* ++ * Bus Number Which has been Assigned to this SSP controller ++ * on this board ++ */ ++ host->bus_num = platform_info->bus_id; ++ host->cleanup = pl022_cleanup; ++ host->setup = pl022_setup; ++ /* If open CONFIG_PM, auto_runtime_pm should be false when of-platform.*/ ++ host->auto_runtime_pm = true; ++ host->transfer_one_message = pl022_transfer_one_message; ++ host->unprepare_transfer_hardware = pl022_unprepare_transfer_hardware; ++ host->rt = platform_info->rt; ++ host->dev.of_node = dev->of_node; ++ host->use_gpio_descriptors = true; ++ ++ /* ++ * Supports mode 0-3, loopback, and active low CS. Transfers are ++ * always MS bit first on the original pl022. ++ */ ++ host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LOOP; ++ if (pl022->vendor->extended_cr) ++ host->mode_bits |= SPI_LSB_FIRST; ++ ++ dev_dbg(dev, "BUSNO: %d\n", host->bus_num); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ pl022->phybase = res->start; ++ pl022->virtbase = devm_ioremap_resource(dev, res); ++ if (pl022->virtbase == NULL) { ++ status = -ENOMEM; ++ goto err_no_ioremap; ++ } ++ dev_info(dev, "mapped registers from %llx to %llx\n", ++ pdev->resource->start, pdev->resource->end); ++ ++ pl022->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(pl022->clk)) { ++ status = PTR_ERR(pl022->clk); ++ dev_err(dev, "could not retrieve SSP/SPI bus clock\n"); ++ goto err_no_clk; ++ } ++ status = clk_prepare_enable(pl022->clk); ++ if (status) { ++ dev_err(dev, "could not enable SSP/SPI bus clock\n"); ++ goto err_no_clk_en; ++ } ++ ++ pl022->rst = devm_reset_control_get_exclusive(dev, NULL); ++ if (!IS_ERR(pl022->rst)) { ++ status = reset_control_deassert(pl022->rst); ++ if (status) { ++ dev_err(dev, "could not deassert SSP/SPI bus reset\n"); ++ goto err_no_rst_clr; ++ } ++ } else { ++ status = PTR_ERR(pl022->rst); ++ dev_err(dev, "could not retrieve SSP/SPI bus reset\n"); ++ goto err_no_rst; ++ } ++ ++ /* Initialize transfer pump */ ++ tasklet_init(&pl022->pump_transfers, pump_transfers, ++ (unsigned long)pl022); ++ ++ /* Disable SSP */ ++ writew((readw(SSP_CR1(pl022->virtbase)) & (~SSP_CR1_MASK_SSE)), ++ SSP_CR1(pl022->virtbase)); ++ load_ssp_default_config(pl022); ++ ++ /* Obtain IRQ line. */ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) { ++ status = -ENXIO; ++ goto err_no_irq; ++ } ++ status = devm_request_irq(dev, irq, pl022_interrupt_handler, ++ 0, "pl022", pl022); ++ if (status < 0) { ++ dev_err(dev, "probe - cannot get IRQ (%d)\n", status); ++ goto err_no_irq; ++ } ++ ++ /* Get DMA channels, try autoconfiguration first */ ++ status = pl022_dma_autoprobe(pl022); ++ if (status == -EPROBE_DEFER) { ++ dev_dbg(dev, "deferring probe to get DMA channel\n"); ++ goto err_no_irq; ++ } ++ ++ /* dma is not used unless configured in the device tree */ ++ platform_info->enable_dma = 0; ++ ++ /* If that failed, use channels from platform_info */ ++ if (status == 0) ++ platform_info->enable_dma = 1; ++ else if (platform_info->enable_dma) { ++ status = pl022_dma_probe(pl022); ++ if (status != 0) ++ platform_info->enable_dma = 0; ++ } ++ ++ /* Register with the SPI framework */ ++ dev_set_drvdata(dev, pl022); ++ ++ status = devm_spi_register_controller(dev, host); ++ if (status != 0) { ++ dev_err(dev, ++ "probe - problem registering spi host\n"); ++ goto err_spi_register; ++ } ++ dev_dbg(dev, "probe succeeded\n"); ++ ++ clk_disable_unprepare(pl022->clk); ++ ++ return 0; ++ err_spi_register: ++ if (platform_info->enable_dma) ++ pl022_dma_remove(pl022); ++ err_no_irq: ++ reset_control_assert(pl022->rst); ++ err_no_rst_clr: ++ err_no_rst: ++ clk_disable_unprepare(pl022->clk); ++ err_no_clk_en: ++ err_no_clk: ++ err_no_ioremap: ++ release_mem_region(pdev->resource->start, resource_size(pdev->resource)); ++ spi_controller_put(host); ++ return status; ++} ++ + static int pl022_probe(struct amba_device *adev, const struct amba_id *id) + { + struct device *dev = &adev->dev; +@@ -2114,14 +2280,6 @@ static int pl022_probe(struct amba_devic + struct spi_controller *host; + struct pl022 *pl022 = NULL; /*Data for this driver */ + int status = 0; +- int platform_flag = 0; +- +- if (strncmp(dev->bus->name, "platform", strlen("platform"))) +- platform_flag = 0; +- else +- platform_flag = 1; +- dev_dbg(&adev->dev, "bus name:%s platform flag:%d", +- dev->bus->name, platform_flag); + + dev_info(&adev->dev, + "ARM PL022 driver, device ID: 0x%08x\n", adev->periphid); +@@ -2175,11 +2333,7 @@ static int pl022_probe(struct amba_devic + goto err_no_ioregion; + + pl022->phybase = adev->res.start; +- if (platform_flag) +- pl022->virtbase = ioremap(adev->res.start, +- resource_size(&adev->res)); +- else +- pl022->virtbase = devm_ioremap(dev, adev->res.start, ++ pl022->virtbase = devm_ioremap(dev, adev->res.start, + resource_size(&adev->res)); + if (pl022->virtbase == NULL) { + status = -ENOMEM; +@@ -2188,10 +2342,7 @@ static int pl022_probe(struct amba_devic + dev_info(&adev->dev, "mapped registers from %pa to %p\n", + &adev->res.start, pl022->virtbase); + +- if (platform_flag) +- pl022->clk = clk_get(&adev->dev, NULL); +- else +- pl022->clk = devm_clk_get(&adev->dev, NULL); ++ pl022->clk = devm_clk_get(&adev->dev, NULL); + if (IS_ERR(pl022->clk)) { + status = PTR_ERR(pl022->clk); + dev_err(&adev->dev, "could not retrieve SSP/SPI bus clock\n"); +@@ -2204,10 +2355,7 @@ static int pl022_probe(struct amba_devic + goto err_no_clk_en; + } + +- if (platform_flag) +- pl022->rst = reset_control_get_exclusive(&adev->dev, NULL); +- else +- pl022->rst = devm_reset_control_get(&adev->dev, NULL); ++ pl022->rst = devm_reset_control_get(&adev->dev, NULL); + if (IS_ERR(pl022->rst)) { + status = PTR_ERR(pl022->rst); + dev_err(&adev->dev, "could not retrieve SSP/SPI bus reset\n"); +@@ -2229,11 +2377,7 @@ static int pl022_probe(struct amba_devic + SSP_CR1(pl022->virtbase)); + load_ssp_default_config(pl022); + +- if (platform_flag) +- status = request_irq(adev->irq[0], pl022_interrupt_handler, +- 0, "pl022", pl022); +- else +- status = devm_request_irq(dev, adev->irq[0], pl022_interrupt_handler, ++ status = devm_request_irq(dev, adev->irq[0], pl022_interrupt_handler, + 0, "pl022", pl022); + if (status < 0) { + dev_err(&adev->dev, "probe - cannot get IRQ (%d)\n", status); +@@ -2258,18 +2402,16 @@ static int pl022_probe(struct amba_devic + + /* Register with the SPI framework */ + amba_set_drvdata(adev, pl022); +- if (platform_flag) +- status = spi_register_controller(host); +- else +- status = devm_spi_register_controller(&adev->dev, host); ++ ++ status = devm_spi_register_controller(&adev->dev, host); + if (status != 0) { + dev_err_probe(&adev->dev, status, + "problem registering spi host\n"); + goto err_spi_register; + } + dev_dbg(dev, "probe succeeded\n"); +- if (!platform_flag) +- platform_info->autosuspend_delay = 100; ++ ++ platform_info->autosuspend_delay = 100; + /* let runtime pm put suspend */ + if (platform_info->autosuspend_delay > 0) { + dev_info(&adev->dev, +@@ -2279,10 +2421,8 @@ static int pl022_probe(struct amba_devic + platform_info->autosuspend_delay); + pm_runtime_use_autosuspend(dev); + } +- if (platform_flag) +- clk_disable_unprepare(pl022->clk); +- else +- pm_runtime_put(dev); ++ ++ pm_runtime_put(dev); + + return 0; + +@@ -2290,26 +2430,17 @@ static int pl022_probe(struct amba_devic + if (platform_info->enable_dma) + pl022_dma_remove(pl022); + err_no_irq: +- if (platform_flag) +- free_irq(adev->irq[0], pl022); + reset_control_assert(pl022->rst); + err_no_rst_de: +- if (platform_flag) +- reset_control_put(pl022->rst); + err_no_rst: + clk_disable_unprepare(pl022->clk); + err_no_clk_en: +- if (platform_flag) +- clk_put(pl022->clk); + err_no_clk: +- if (platform_flag) +- iounmap(pl022->virtbase); + err_no_ioremap: + amba_release_regions(adev); + err_no_ioregion: + spi_controller_put(host); +- if (platform_flag) +- kfree(platform_info); ++ + return status; + } + +@@ -2523,23 +2654,8 @@ static int starfive_of_pl022_probe(struc + .mask = 0x000fffff, + .data = &vendor_arm + }; +- struct amba_device *pcdev; + struct device *dev = &pdev->dev; + +- pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL); +- if (!pcdev) +- return -ENOMEM; +- +- pcdev->dev = pdev->dev; +- pcdev->periphid = id.id; +- pcdev->res = *(pdev->resource); +- +- pcdev->irq[0] = platform_get_irq(pdev, 0); +- if (pcdev->irq[0] < 0) { +- dev_err(dev, "failed to get irq\n"); +- ret = -EINVAL; +- } +- + ret = of_clk_set_defaults(dev->of_node, false); + if (ret < 0) + goto err_probe; +@@ -2548,16 +2664,11 @@ static int starfive_of_pl022_probe(struc + if (ret) + goto err_probe; + +- ret = pl022_probe(pcdev, &id); ++ ret = pl022_platform_probe(pdev, &id); + +- struct pl022 *pl022 = amba_get_drvdata(pcdev); +- +- pl022->host->dev.parent = &pdev->dev; +- platform_set_drvdata(pdev, pl022); +- +- pm_runtime_enable(&pdev->dev); +- pm_runtime_set_autosuspend_delay(&pdev->dev, 100); +- pm_runtime_use_autosuspend(&pdev->dev); ++ pm_runtime_enable(dev); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); + + if (ret) { + pm_runtime_disable(dev); +@@ -2572,32 +2683,27 @@ err_probe: + + static int starfive_of_pl022_remove(struct platform_device *pdev) + { +- u32 size; +- int irq; + struct pl022 *pl022 = dev_get_drvdata(&pdev->dev); + + if (!pl022) + return 0; + ++ pm_runtime_get_sync(&pdev->dev); + pm_runtime_get_noresume(&pdev->dev); + + load_ssp_default_config(pl022); + if (pl022->host_info->enable_dma) + pl022_dma_remove(pl022); + +- irq = platform_get_irq(pdev, 0); +- free_irq(irq, pl022); +- reset_control_assert(pl022->rst); +- reset_control_put(pl022->rst); + clk_disable_unprepare(pl022->clk); +- clk_put(pl022->clk); +- iounmap(pl022->virtbase); +- kfree(pl022->host_info); +- +- size = resource_size(pdev->resource); +- release_mem_region(pdev->resource->start, size); + tasklet_disable(&pl022->pump_transfers); ++ ++ pm_runtime_put_noidle(&pdev->dev); + pm_runtime_disable(&pdev->dev); ++ pm_runtime_set_suspended(&pdev->dev); ++ pm_runtime_put_noidle(&pdev->dev); ++ dev_pm_domain_detach(&pdev->dev, true); ++ + return 0; + } + diff --git a/target/linux/starfive/patches-6.6/0104-spi-pl022-starfive-Enable-spi-to-be-compiled-into-mo.patch b/target/linux/starfive/patches-6.6/0104-spi-pl022-starfive-Enable-spi-to-be-compiled-into-mo.patch new file mode 100644 index 0000000000..aecce737d9 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0104-spi-pl022-starfive-Enable-spi-to-be-compiled-into-mo.patch @@ -0,0 +1,38 @@ +From 11a917bf429a5714e34584f90ab1ac376d399d8f Mon Sep 17 00:00:00 2001 +From: "ziv.xu" <ziv.xu@starfive.com> +Date: Wed, 18 Jan 2023 15:50:47 +0800 +Subject: [PATCH 104/116] spi-pl022-starfive:Enable spi to be compiled into + modules + +Enable spi to be compiled into modules + +Signed-off-by: ziv.xu <ziv.xu@starfive.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/spi/spi-pl022.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +--- a/drivers/spi/spi-pl022.c ++++ b/drivers/spi/spi-pl022.c +@@ -2633,7 +2633,11 @@ static int __init pl022_init(void) + { + return amba_driver_register(&pl022_driver); + } ++#if !IS_MODULE(CONFIG_SPI_PL022) + subsys_initcall(pl022_init); ++#else ++module_init(pl022_init); ++#endif + + static void __exit pl022_exit(void) + { +@@ -2723,7 +2727,9 @@ static struct platform_driver starfive_o + .remove = starfive_of_pl022_remove, + }; + ++#if !IS_MODULE(CONFIG_SPI_PL022) + module_platform_driver(starfive_of_pl022_driver); ++#endif + /* platform register end */ + + MODULE_AUTHOR("xingyu.wu <xingyu.wu@starfivetech.com>"); diff --git a/target/linux/starfive/patches-6.6/0105-riscv-configs-add-visionfive2-defconfig-to-kernel-6..patch b/target/linux/starfive/patches-6.6/0105-riscv-configs-add-visionfive2-defconfig-to-kernel-6..patch new file mode 100644 index 0000000000..bc1d836c11 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0105-riscv-configs-add-visionfive2-defconfig-to-kernel-6..patch @@ -0,0 +1,444 @@ +From e7b01c0a9ad79c1933ce85c4aeca6a037cc4f77f Mon Sep 17 00:00:00 2001 +From: Ziv Xu <ziv.xu@starfivetech.com> +Date: Wed, 13 Mar 2024 18:37:31 +0800 +Subject: [PATCH 105/116] riscv: configs: add visionfive2 defconfig to kernel + 6.6 + +add visionfive2 defconfig to kernel 6.6 + +Signed-off-by: Ziv Xu <ziv.xu@starfivetech.com> +--- + .../configs/starfive_visionfive2_defconfig | 427 ++++++++++++++++++ + 1 file changed, 427 insertions(+) + create mode 100644 arch/riscv/configs/starfive_visionfive2_defconfig + +--- /dev/null ++++ b/arch/riscv/configs/starfive_visionfive2_defconfig +@@ -0,0 +1,427 @@ ++CONFIG_COMPILE_TEST=y ++# CONFIG_WERROR is not set ++CONFIG_DEFAULT_HOSTNAME="StarFive" ++CONFIG_SYSVIPC=y ++CONFIG_POSIX_MQUEUE=y ++CONFIG_USELIB=y ++CONFIG_NO_HZ_IDLE=y ++CONFIG_HIGH_RES_TIMERS=y ++CONFIG_BPF_SYSCALL=y ++CONFIG_IKCONFIG=y ++CONFIG_IKCONFIG_PROC=y ++CONFIG_CGROUPS=y ++CONFIG_CGROUP_SCHED=y ++CONFIG_CFS_BANDWIDTH=y ++CONFIG_CGROUP_BPF=y ++CONFIG_NAMESPACES=y ++CONFIG_USER_NS=y ++CONFIG_CHECKPOINT_RESTORE=y ++CONFIG_BLK_DEV_INITRD=y ++CONFIG_EXPERT=y ++CONFIG_PERF_EVENTS=y ++CONFIG_SOC_STARFIVE=y ++CONFIG_NONPORTABLE=y ++CONFIG_SMP=y ++CONFIG_HZ_100=y ++CONFIG_HIBERNATION=y ++CONFIG_PM_DEBUG=y ++CONFIG_PM_ADVANCED_DEBUG=y ++CONFIG_PM_TEST_SUSPEND=y ++CONFIG_CPU_IDLE=y ++CONFIG_RISCV_SBI_CPUIDLE=y ++CONFIG_CPU_FREQ=y ++CONFIG_CPU_FREQ_STAT=y ++CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y ++CONFIG_CPU_FREQ_GOV_POWERSAVE=y ++CONFIG_CPU_FREQ_GOV_USERSPACE=y ++CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y ++CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y ++CONFIG_CPUFREQ_DT=y ++# CONFIG_SECCOMP is not set ++CONFIG_MODULES=y ++CONFIG_MODULE_UNLOAD=y ++CONFIG_BINFMT_MISC=y ++CONFIG_PAGE_REPORTING=y ++CONFIG_CMA=y ++CONFIG_NET=y ++CONFIG_PACKET=y ++CONFIG_IP_MULTICAST=y ++CONFIG_IP_ADVANCED_ROUTER=y ++CONFIG_IP_PNP=y ++CONFIG_IP_PNP_DHCP=y ++CONFIG_IP_PNP_BOOTP=y ++CONFIG_IP_PNP_RARP=y ++CONFIG_NETFILTER=y ++CONFIG_NETFILTER_NETLINK_ACCT=y ++CONFIG_NETFILTER_NETLINK_QUEUE=y ++CONFIG_NF_CONNTRACK=y ++CONFIG_NF_TABLES=y ++CONFIG_NFT_CT=y ++CONFIG_NFT_COMPAT=y ++CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y ++CONFIG_NETFILTER_XT_MATCH_IPCOMP=y ++CONFIG_NETFILTER_XT_MATCH_IPRANGE=y ++CONFIG_NETFILTER_XT_MATCH_MAC=y ++CONFIG_NETFILTER_XT_MATCH_MARK=y ++CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y ++CONFIG_NETFILTER_XT_MATCH_SOCKET=y ++CONFIG_NETFILTER_XT_MATCH_STATE=y ++CONFIG_NETFILTER_XT_MATCH_STRING=y ++CONFIG_NETFILTER_XT_MATCH_U32=y ++CONFIG_NF_TABLES_IPV4=y ++CONFIG_NFT_DUP_IPV4=y ++CONFIG_NFT_FIB_IPV4=y ++CONFIG_IP_NF_IPTABLES=y ++CONFIG_IP_NF_FILTER=y ++CONFIG_IP_NF_TARGET_REJECT=y ++CONFIG_IP_NF_NAT=y ++CONFIG_IP_NF_TARGET_MASQUERADE=y ++CONFIG_IP_NF_TARGET_NETMAP=y ++CONFIG_IP_NF_TARGET_REDIRECT=y ++CONFIG_NETLINK_DIAG=y ++CONFIG_CAN=y ++CONFIG_BT=y ++CONFIG_BT_RFCOMM=y ++CONFIG_BT_RFCOMM_TTY=y ++CONFIG_BT_BNEP=y ++CONFIG_BT_BNEP_MC_FILTER=y ++CONFIG_BT_BNEP_PROTO_FILTER=y ++CONFIG_BT_HCIBTUSB=m ++# CONFIG_BT_HCIBTUSB_BCM is not set ++CONFIG_CFG80211=y ++CONFIG_MAC80211=y ++CONFIG_RFKILL=y ++CONFIG_NET_9P=y ++CONFIG_NET_9P_VIRTIO=y ++CONFIG_PCI=y ++# CONFIG_PCIEASPM is not set ++CONFIG_PCIE_STARFIVE_HOST=y ++CONFIG_DEVTMPFS=y ++CONFIG_DEVTMPFS_MOUNT=y ++CONFIG_MTD=y ++CONFIG_MTD_BLOCK=y ++CONFIG_MTD_CFI=y ++CONFIG_MTD_CFI_ADV_OPTIONS=y ++CONFIG_MTD_SPI_NOR=y ++CONFIG_OF_CONFIGFS=y ++CONFIG_BLK_DEV_LOOP=y ++CONFIG_VIRTIO_BLK=y ++CONFIG_BLK_DEV_NVME=y ++CONFIG_EEPROM_AT24=y ++CONFIG_BLK_DEV_SD=y ++CONFIG_BLK_DEV_SR=y ++CONFIG_SCSI_VIRTIO=y ++CONFIG_ATA=y ++CONFIG_SATA_AHCI=y ++CONFIG_MD=y ++CONFIG_BLK_DEV_DM=m ++CONFIG_NETDEVICES=y ++CONFIG_VIRTIO_NET=y ++# CONFIG_NET_VENDOR_ALACRITECH is not set ++# CONFIG_NET_VENDOR_AMAZON is not set ++# CONFIG_NET_VENDOR_AQUANTIA is not set ++# CONFIG_NET_VENDOR_ARC is not set ++# CONFIG_NET_VENDOR_BROADCOM is not set ++# CONFIG_NET_VENDOR_CADENCE is not set ++# CONFIG_NET_VENDOR_CAVIUM is not set ++# CONFIG_NET_VENDOR_CORTINA is not set ++# CONFIG_NET_VENDOR_EZCHIP is not set ++# CONFIG_NET_VENDOR_GOOGLE is not set ++# CONFIG_NET_VENDOR_HUAWEI is not set ++# CONFIG_NET_VENDOR_INTEL is not set ++# CONFIG_NET_VENDOR_MARVELL is not set ++# CONFIG_NET_VENDOR_MELLANOX is not set ++# CONFIG_NET_VENDOR_MICREL is not set ++# CONFIG_NET_VENDOR_MICROCHIP is not set ++# CONFIG_NET_VENDOR_MICROSEMI is not set ++# CONFIG_NET_VENDOR_NI is not set ++# CONFIG_NET_VENDOR_NATSEMI is not set ++# CONFIG_NET_VENDOR_NETRONOME is not set ++# CONFIG_NET_VENDOR_PENSANDO is not set ++# CONFIG_NET_VENDOR_QUALCOMM is not set ++CONFIG_R8169=y ++# CONFIG_NET_VENDOR_RENESAS is not set ++# CONFIG_NET_VENDOR_ROCKER is not set ++# CONFIG_NET_VENDOR_SAMSUNG is not set ++# CONFIG_NET_VENDOR_SEEQ is not set ++# CONFIG_NET_VENDOR_SOLARFLARE is not set ++# CONFIG_NET_VENDOR_SOCIONEXT is not set ++CONFIG_STMMAC_ETH=y ++CONFIG_STMMAC_SELFTESTS=y ++CONFIG_DWMAC_DWC_QOS_ETH=y ++CONFIG_DWMAC_STARFIVE=y ++# CONFIG_NET_VENDOR_SYNOPSYS is not set ++# CONFIG_NET_VENDOR_VIA is not set ++# CONFIG_NET_VENDOR_WIZNET is not set ++# CONFIG_NET_VENDOR_XILINX is not set ++CONFIG_MARVELL_PHY=y ++CONFIG_MICREL_PHY=y ++CONFIG_MICROCHIP_PHY=y ++CONFIG_MOTORCOMM_PHY=y ++CONFIG_IPMS_CAN=y ++CONFIG_IWLWIFI=y ++CONFIG_IWLDVM=y ++CONFIG_IWLMVM=y ++CONFIG_HOSTAP=y ++# CONFIG_RTL_CARDS is not set ++CONFIG_USB_WIFI_ECR6600U=y ++CONFIG_AIC_WLAN_SUPPORT=y ++CONFIG_AIC8800_WLAN_SUPPORT=m ++CONFIG_AIC_LOADFW_SUPPORT=m ++CONFIG_INPUT_EVDEV=y ++# CONFIG_INPUT_KEYBOARD is not set ++# CONFIG_INPUT_MOUSE is not set ++CONFIG_INPUT_TOUCHSCREEN=y ++CONFIG_TOUCHSCREEN_GOODIX=y ++CONFIG_TOUCHSCREEN_TINKER_FT5406=y ++CONFIG_SERIO_LIBPS2=y ++CONFIG_SERIAL_8250=y ++CONFIG_SERIAL_8250_CONSOLE=y ++CONFIG_SERIAL_8250_NR_UARTS=6 ++CONFIG_SERIAL_8250_RUNTIME_UARTS=6 ++CONFIG_SERIAL_8250_EXTENDED=y ++CONFIG_SERIAL_8250_MANY_PORTS=y ++CONFIG_SERIAL_8250_DW=y ++CONFIG_SERIAL_OF_PLATFORM=y ++CONFIG_TTY_PRINTK=y ++CONFIG_VIRTIO_CONSOLE=y ++CONFIG_HW_RANDOM=y ++CONFIG_HW_RANDOM_JH7110=y ++CONFIG_I2C_CHARDEV=y ++CONFIG_I2C_DESIGNWARE_PLATFORM=y ++CONFIG_SPI=y ++CONFIG_SPI_CADENCE_QUADSPI=y ++CONFIG_SPI_PL022=y ++CONFIG_SPI_SIFIVE=y ++CONFIG_SPI_SPIDEV=y ++# CONFIG_PTP_1588_CLOCK is not set ++CONFIG_GPIO_SYSFS=y ++CONFIG_POWER_RESET=y ++CONFIG_POWER_RESET_GPIO_RESTART=y ++CONFIG_POWER_RESET_SYSCON=y ++CONFIG_POWER_RESET_SYSCON_POWEROFF=y ++CONFIG_SENSORS_SFCTEMP=y ++CONFIG_THERMAL=y ++CONFIG_THERMAL_WRITABLE_TRIPS=y ++CONFIG_CPU_THERMAL=y ++CONFIG_DEVFREQ_THERMAL=y ++CONFIG_THERMAL_EMULATION=y ++# CONFIG_HISI_THERMAL is not set ++CONFIG_WATCHDOG=y ++CONFIG_WATCHDOG_SYSFS=y ++CONFIG_MFD_AXP20X_I2C=y ++CONFIG_REGULATOR=y ++CONFIG_REGULATOR_AXP20X=y ++CONFIG_REGULATOR_GPIO=y ++CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY=y ++# CONFIG_MEDIA_CEC_SUPPORT is not set ++CONFIG_MEDIA_SUPPORT=y ++CONFIG_MEDIA_USB_SUPPORT=y ++CONFIG_USB_VIDEO_CLASS=y ++CONFIG_V4L_PLATFORM_DRIVERS=y ++CONFIG_V4L_MEM2MEM_DRIVERS=y ++CONFIG_VIDEO_CADENCE_CSI2RX=y ++CONFIG_VIDEO_WAVE_VPU=m ++CONFIG_VIN_SENSOR_OV4689=y ++CONFIG_VIN_SENSOR_IMX219=y ++CONFIG_VIDEO_STF_VIN=y ++CONFIG_VIDEO_IMX219=y ++CONFIG_VIDEO_IMX708=y ++CONFIG_DRM_PANEL_SIMPLE=y ++CONFIG_DRM_PANEL_JADARD_JD9365DA_H3=y ++CONFIG_DRM_TOSHIBA_TC358762=y ++CONFIG_DRM_VERISILICON=y ++CONFIG_STARFIVE_INNO_HDMI=y ++CONFIG_STARFIVE_DSI=y ++CONFIG_DRM_IMG_ROGUE=y ++CONFIG_DRM_LEGACY=y ++CONFIG_FB=y ++CONFIG_BACKLIGHT_CLASS_DEVICE=y ++CONFIG_SOUND=y ++CONFIG_SND=y ++CONFIG_SND_USB_AUDIO=y ++CONFIG_SND_SOC=y ++CONFIG_SND_DESIGNWARE_I2S=y ++CONFIG_SND_SOC_RZ=m ++CONFIG_SND_SOC_STARFIVE=y ++CONFIG_SND_SOC_JH7110_PWMDAC=y ++CONFIG_SND_SOC_JH7110_TDM=y ++CONFIG_SND_SOC_AC108=y ++CONFIG_SND_SOC_WM8960=y ++CONFIG_SND_SIMPLE_CARD=y ++CONFIG_UHID=y ++CONFIG_USB=y ++CONFIG_USB_OTG=y ++CONFIG_USB_XHCI_HCD=y ++CONFIG_USB_RENESAS_USBHS=y ++CONFIG_USB_STORAGE=y ++CONFIG_USB_UAS=y ++CONFIG_USB_CDNS_SUPPORT=y ++CONFIG_USB_CDNS3=y ++CONFIG_USB_CDNS3_GADGET=y ++CONFIG_USB_CDNS3_HOST=y ++CONFIG_USB_CDNS3_STARFIVE=y ++CONFIG_USB_SERIAL=m ++CONFIG_USB_SERIAL_GENERIC=y ++CONFIG_USB_SERIAL_AIRCABLE=m ++CONFIG_USB_SERIAL_ARK3116=m ++CONFIG_USB_SERIAL_BELKIN=m ++CONFIG_USB_SERIAL_CH341=m ++CONFIG_USB_SERIAL_WHITEHEAT=m ++CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m ++CONFIG_USB_SERIAL_CP210X=m ++CONFIG_USB_SERIAL_CYPRESS_M8=m ++CONFIG_USB_SERIAL_EMPEG=m ++CONFIG_USB_SERIAL_FTDI_SIO=m ++CONFIG_USB_SERIAL_VISOR=m ++CONFIG_USB_SERIAL_IPAQ=m ++CONFIG_USB_SERIAL_IR=m ++CONFIG_USB_SERIAL_EDGEPORT=m ++CONFIG_USB_SERIAL_EDGEPORT_TI=m ++CONFIG_USB_SERIAL_F81232=m ++CONFIG_USB_SERIAL_GARMIN=m ++CONFIG_USB_SERIAL_IPW=m ++CONFIG_USB_SERIAL_IUU=m ++CONFIG_USB_SERIAL_KEYSPAN_PDA=m ++CONFIG_USB_SERIAL_KEYSPAN=m ++CONFIG_USB_SERIAL_KLSI=m ++CONFIG_USB_SERIAL_KOBIL_SCT=m ++CONFIG_USB_SERIAL_MCT_U232=m ++CONFIG_USB_SERIAL_METRO=m ++CONFIG_USB_SERIAL_MOS7720=m ++CONFIG_USB_SERIAL_MOS7840=m ++CONFIG_USB_SERIAL_NAVMAN=m ++CONFIG_USB_SERIAL_PL2303=m ++CONFIG_USB_SERIAL_OTI6858=m ++CONFIG_USB_SERIAL_QCAUX=m ++CONFIG_USB_SERIAL_QUALCOMM=m ++CONFIG_USB_SERIAL_SPCP8X5=m ++CONFIG_USB_SERIAL_SAFE=m ++CONFIG_USB_SERIAL_SIERRAWIRELESS=m ++CONFIG_USB_SERIAL_SYMBOL=m ++CONFIG_USB_SERIAL_TI=m ++CONFIG_USB_SERIAL_CYBERJACK=m ++CONFIG_USB_SERIAL_OMNINET=m ++CONFIG_USB_SERIAL_OPTICON=m ++CONFIG_USB_SERIAL_XSENS_MT=m ++CONFIG_USB_SERIAL_WISHBONE=m ++CONFIG_USB_SERIAL_SSU100=m ++CONFIG_USB_SERIAL_DEBUG=m ++CONFIG_USB_GADGET=y ++CONFIG_USB_RENESAS_USBHS_UDC=m ++CONFIG_USB_CONFIGFS=y ++CONFIG_USB_CONFIGFS_SERIAL=y ++CONFIG_USB_CONFIGFS_ACM=y ++CONFIG_USB_CONFIGFS_OBEX=y ++CONFIG_USB_CONFIGFS_NCM=y ++CONFIG_USB_CONFIGFS_ECM=y ++CONFIG_USB_CONFIGFS_ECM_SUBSET=y ++CONFIG_USB_CONFIGFS_RNDIS=y ++CONFIG_USB_CONFIGFS_EEM=y ++CONFIG_USB_CONFIGFS_MASS_STORAGE=y ++CONFIG_USB_CONFIGFS_F_FS=y ++CONFIG_MMC=y ++CONFIG_MMC_DEBUG=y ++CONFIG_MMC_SDHCI=y ++CONFIG_MMC_SDHCI_PLTFM=y ++CONFIG_MMC_SDHCI_OF_DWCMSHC=y ++CONFIG_MMC_SPI=y ++CONFIG_MMC_SDHI=y ++CONFIG_MMC_DW=y ++CONFIG_MMC_DW_STARFIVE=y ++CONFIG_NEW_LEDS=y ++CONFIG_LEDS_CLASS=y ++CONFIG_LEDS_GPIO=y ++CONFIG_LEDS_TRIGGER_HEARTBEAT=y ++CONFIG_RTC_CLASS=y ++CONFIG_RTC_DRV_STARFIVE=y ++CONFIG_RTC_DRV_GOLDFISH=y ++CONFIG_DMADEVICES=y ++CONFIG_AMBA_PL08X=y ++CONFIG_DW_AXI_DMAC=y ++CONFIG_DMATEST=y ++# CONFIG_VIRTIO_MENU is not set ++# CONFIG_VHOST_MENU is not set ++CONFIG_GOLDFISH=y ++CONFIG_CLK_STARFIVE_JH7110_AON=y ++CONFIG_CLK_STARFIVE_JH7110_STG=y ++CONFIG_CLK_STARFIVE_JH7110_ISP=y ++CONFIG_CLK_STARFIVE_JH7110_VOUT=y ++CONFIG_MAILBOX=y ++CONFIG_STARFIVE_MBOX=m ++CONFIG_STARFIVE_MBOX_TEST=m ++# CONFIG_IOMMU_SUPPORT is not set ++CONFIG_RPMSG_CHAR=y ++CONFIG_RPMSG_VIRTIO=y ++CONFIG_SIFIVE_CCACHE=y ++CONFIG_PM_DEVFREQ=y ++CONFIG_IIO=y ++CONFIG_IIO_ST_ACCEL_3AXIS=y ++CONFIG_PWM=y ++CONFIG_PWM_OCORES=y ++CONFIG_PHY_STARFIVE_JH7110_PCIE=y ++CONFIG_PHY_STARFIVE_JH7110_USB=y ++CONFIG_PHY_M31_DPHY_RX0=y ++CONFIG_RAS=y ++CONFIG_EXT4_FS=y ++CONFIG_EXT4_FS_POSIX_ACL=y ++CONFIG_BTRFS_FS=m ++CONFIG_BTRFS_FS_POSIX_ACL=y ++CONFIG_AUTOFS_FS=y ++CONFIG_FUSE_FS=y ++CONFIG_CUSE=y ++CONFIG_VIRTIO_FS=y ++CONFIG_FSCACHE=y ++CONFIG_FSCACHE_STATS=y ++CONFIG_MSDOS_FS=y ++CONFIG_VFAT_FS=y ++CONFIG_FAT_DEFAULT_UTF8=y ++CONFIG_EXFAT_FS=y ++CONFIG_NTFS_FS=y ++CONFIG_NTFS_RW=y ++CONFIG_TMPFS=y ++CONFIG_TMPFS_POSIX_ACL=y ++CONFIG_HUGETLBFS=y ++CONFIG_JFFS2_FS=y ++CONFIG_NFS_FS=y ++CONFIG_NFS_V4=y ++CONFIG_NFS_V4_1=y ++CONFIG_NFS_V4_2=y ++CONFIG_ROOT_NFS=y ++CONFIG_CIFS=m ++# CONFIG_CIFS_STATS2 is not set ++CONFIG_CIFS_UPCALL=y ++CONFIG_CIFS_XATTR=y ++CONFIG_CIFS_POSIX=y ++# CONFIG_CIFS_DEBUG is not set ++CONFIG_CIFS_DFS_UPCALL=y ++CONFIG_CIFS_FSCACHE=y ++CONFIG_SMB_SERVER=m ++CONFIG_NLS_CODEPAGE_437=y ++CONFIG_NLS_ISO8859_1=y ++CONFIG_INIT_STACK_NONE=y ++CONFIG_CRYPTO_USER=y ++CONFIG_CRYPTO_TEST=m ++CONFIG_CRYPTO_USER_API_HASH=y ++CONFIG_CRYPTO_USER_API_SKCIPHER=y ++CONFIG_CRYPTO_USER_API_RNG=y ++CONFIG_CRYPTO_USER_API_AEAD=y ++CONFIG_CRYPTO_DEV_VIRTIO=y ++CONFIG_CRYPTO_DEV_JH7110=y ++CONFIG_DMA_CMA=y ++CONFIG_PRINTK_TIME=y ++CONFIG_DEBUG_FS=y ++CONFIG_SOFTLOCKUP_DETECTOR=y ++CONFIG_WQ_WATCHDOG=y ++CONFIG_DEBUG_TIMEKEEPING=y ++CONFIG_DEBUG_RT_MUTEXES=y ++CONFIG_DEBUG_SPINLOCK=y ++CONFIG_DEBUG_RWSEMS=y ++CONFIG_DEBUG_LIST=y ++CONFIG_DEBUG_PLIST=y ++CONFIG_DEBUG_SG=y ++# CONFIG_RCU_TRACE is not set ++CONFIG_RCU_EQS_DEBUG=y ++# CONFIG_FTRACE is not set ++# CONFIG_RUNTIME_TESTING_MENU is not set ++CONFIG_MEMTEST=y diff --git a/target/linux/starfive/patches-6.6/0106-riscv-dts-starfive-update-dts-to-kernel-6.6.patch b/target/linux/starfive/patches-6.6/0106-riscv-dts-starfive-update-dts-to-kernel-6.6.patch new file mode 100644 index 0000000000..defc0e669a --- /dev/null +++ b/target/linux/starfive/patches-6.6/0106-riscv-dts-starfive-update-dts-to-kernel-6.6.patch @@ -0,0 +1,759 @@ +From 51c1cccb202d741eeb1de57e0ecf8fcaa8f059e8 Mon Sep 17 00:00:00 2001 +From: Ziv Xu <ziv.xu@starfivetech.com> +Date: Wed, 13 Mar 2024 18:36:31 +0800 +Subject: [PATCH 106/116] riscv: dts: starfive: update dts to kernel 6.6 + +update dts to kernel 6.6 + +Signed-off-by: Ziv Xu <ziv.xu@starfivetech.com> +--- + arch/riscv/boot/dts/starfive/Makefile | 5 +- + .../jh7110-starfive-visionfive-2-ac108.dts | 64 +++ + .../jh7110-starfive-visionfive-2-tdm.dts | 59 +++ + .../jh7110-starfive-visionfive-2-wm8960.dts | 71 +++ + .../jh7110-starfive-visionfive-2.dtsi | 437 +++++++++++++++++- + 5 files changed, 633 insertions(+), 3 deletions(-) + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-ac108.dts + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-tdm.dts + create mode 100644 arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-wm8960.dts + +--- a/arch/riscv/boot/dts/starfive/Makefile ++++ b/arch/riscv/boot/dts/starfive/Makefile +@@ -10,7 +10,10 @@ dtb-$(CONFIG_ARCH_STARFIVE) += jh7100-be + dtb-$(CONFIG_ARCH_STARFIVE) += jh7100-starfive-visionfive-v1.dtb + + dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-starfive-visionfive-2-v1.2a.dtb +-dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-starfive-visionfive-2-v1.3b.dtb ++dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-starfive-visionfive-2-v1.3b.dtb \ ++ jh7110-starfive-visionfive-2-ac108.dtb \ ++ jh7110-starfive-visionfive-2-tdm.dtb \ ++ jh7110-starfive-visionfive-2-wm8960.dtb + + subdir-y += evb-overlay + dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-evb.dtb \ +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-ac108.dts +@@ -0,0 +1,64 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ * Copyright (C) 2022 Hal Feng <hal.feng@starfivetech.com> ++ */ ++ ++/dts-v1/; ++#include "jh7110-starfive-visionfive-2-v1.3b.dts" ++ ++/ { ++ /* i2s + ac108 */ ++ sound0: snd-card0 { ++ compatible = "simple-audio-card"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ simple-audio-card,name = "Starfive-AC108-Sound-Card"; ++ simple-audio-card,dai-link@0 { ++ reg = <0>; ++ format = "i2s"; ++ bitclock-master = <&sndcodec1>; ++ frame-master = <&sndcodec1>; ++ ++ widgets = ++ "Microphone", "Mic Jack", ++ "Line", "Line In", ++ "Line", "Line Out", ++ "Speaker", "Speaker", ++ "Headphone", "Headphone Jack"; ++ routing = ++ "Headphone Jack", "HP_L", ++ "Headphone Jack", "HP_R", ++ "Speaker", "SPK_LP", ++ "Speaker", "SPK_LN", ++ "LINPUT1", "Mic Jack", ++ "LINPUT3", "Mic Jack", ++ "RINPUT1", "Mic Jack", ++ "RINPUT2", "Mic Jack"; ++ ++ cpu { ++ sound-dai = <&i2srx_mst>; ++ }; ++ ++ sndcodec1: codec { ++ sound-dai = <&ac108>; ++ clocks = <&ac108_mclk>; ++ clock-names = "mclk"; ++ }; ++ }; ++ }; ++}; ++ ++&i2c0 { ++ ac108: ac108@3b { ++ compatible = "x-power,ac108_0"; ++ reg = <0x3b>; ++ #sound-dai-cells = <0>; ++ data-protocol = <0>; ++ }; ++}; ++ ++&i2srx_mst { ++ status = "okay"; ++}; +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-tdm.dts +@@ -0,0 +1,59 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ * Copyright (C) 2022 Hal Feng <hal.feng@starfivetech.com> ++ */ ++ ++/dts-v1/; ++#include "jh7110-starfive-visionfive-2-v1.3b.dts" ++ ++/ { ++ sound-tdm { ++ compatible = "simple-audio-card"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ simple-audio-card,name = "Starfive-TDM-Sound-Card"; ++ simple-audio-card,widgets = "Microphone", "Mic Jack", ++ "Line", "Line In", ++ "Line", "Line Out", ++ "Speaker", "Speaker", ++ "Headphone", "Headphone Jack"; ++ simple-audio-card,routing = "Headphone Jack", "HP_L", ++ "Headphone Jack", "HP_R", ++ "Speaker", "SPK_LP", ++ "Speaker", "SPK_LN", ++ "LINPUT1", "Mic Jack", ++ "LINPUT3", "Mic Jack", ++ "RINPUT1", "Mic Jack", ++ "RINPUT2", "Mic Jack"; ++ ++ simple-audio-card,dai-link@0 { ++ reg = <0>; ++ format = "dsp_a"; ++ bitclock-master = <&dailink_master>; ++ frame-master = <&dailink_master>; ++ ++ cpu { ++ sound-dai = <&tdm>; ++ }; ++ dailink_master: codec { ++ sound-dai = <&wm8960>; ++ clocks = <&wm8960_mclk>; ++ }; ++ }; ++ }; ++}; ++ ++&i2c0 { ++ wm8960: codec@1a { ++ compatible = "wlf,wm8960"; ++ reg = <0x1a>; ++ wlf,shared-lrclk; ++ #sound-dai-cells = <0>; ++ }; ++}; ++ ++&tdm { ++ status = "okay"; ++}; +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-wm8960.dts +@@ -0,0 +1,71 @@ ++// SPDX-License-Identifier: GPL-2.0 OR MIT ++/* ++ * Copyright (C) 2022 StarFive Technology Co., Ltd. ++ * Copyright (C) 2022 Hal Feng <hal.feng@starfivetech.com> ++ */ ++ ++/dts-v1/; ++#include "jh7110-starfive-visionfive-2-v1.3b.dts" ++ ++/ { ++ /* i2s + wm8960 */ ++ sound-wm8960 { ++ compatible = "simple-audio-card"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ simple-audio-card,name = "Starfive-WM8960-Sound-Card"; ++ simple-audio-card,dai-link@0 { ++ reg = <0>; ++ status = "okay"; ++ format = "i2s"; ++ bitclock-master = <&sndcodec1>; ++ frame-master = <&sndcodec1>; ++ ++ widgets = ++ "Microphone", "Mic Jack", ++ "Line", "Line In", ++ "Line", "Line Out", ++ "Speaker", "Speaker", ++ "Headphone", "Headphone Jack"; ++ routing = ++ "Headphone Jack", "HP_L", ++ "Headphone Jack", "HP_R", ++ "Speaker", "SPK_LP", ++ "Speaker", "SPK_LN", ++ "LINPUT1", "Mic Jack", ++ "LINPUT3", "Mic Jack", ++ "RINPUT1", "Mic Jack", ++ "RINPUT2", "Mic Jack"; ++ cpu0 { ++ sound-dai = <&i2srx>; ++ }; ++ cpu1 { ++ sound-dai = <&i2stx1>; ++ }; ++ ++ sndcodec1:codec { ++ sound-dai = <&wm8960>; ++ clocks = <&wm8960_mclk>; ++ clock-names = "mclk"; ++ }; ++ }; ++ }; ++}; ++ ++&i2c0 { ++ wm8960: codec@1a { ++ compatible = "wlf,wm8960"; ++ reg = <0x1a>; ++ wlf,shared-lrclk; ++ #sound-dai-cells = <0>; ++ }; ++}; ++ ++&i2srx { ++ status = "okay"; ++}; ++ ++&i2stx1 { ++ status = "okay"; ++}; +--- a/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi +@@ -7,6 +7,7 @@ + /dts-v1/; + #include "jh7110.dtsi" + #include "jh7110-pinfunc.h" ++#include <dt-bindings/leds/common.h> + #include <dt-bindings/gpio/gpio.h> + + / { +@@ -37,6 +38,44 @@ + reg = <0x0 0x40000000 0x1 0x0>; + }; + ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ linux,cma { ++ compatible = "shared-dma-pool"; ++ reusable; ++ size = <0x0 0x20000000>; ++ alignment = <0x0 0x1000>; ++ alloc-ranges = <0x0 0x70000000 0x0 0x20000000>; ++ linux,cma-default; ++ }; ++ ++ e24_mem: e24@c0000000 { ++ reg = <0x0 0x6ce00000 0x0 0x1600000>; ++ }; ++ ++ xrp_reserved: xrpbuffer@f0000000 { ++ reg = <0x0 0x69c00000 0x0 0x01ffffff ++ 0x0 0x6bc00000 0x0 0x00001000 ++ 0x0 0x6bc01000 0x0 0x00fff000 ++ 0x0 0x6cc00000 0x0 0x00001000>; ++ }; ++ }; ++ ++ leds { ++ compatible = "gpio-leds"; ++ ++ led-ack { ++ gpios = <&aongpio 3 GPIO_ACTIVE_HIGH>; ++ color = <LED_COLOR_ID_GREEN>; ++ function = LED_FUNCTION_HEARTBEAT; ++ linux,default-trigger = "heartbeat"; ++ label = "ack"; ++ }; ++ }; ++ + gpio-restart { + compatible = "gpio-restart"; + gpios = <&sysgpio 35 GPIO_ACTIVE_HIGH>; +@@ -69,6 +108,48 @@ + }; + }; + }; ++ ++ sound-hdmi { ++ compatible = "simple-audio-card"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ simple-audio-card,name = "StarFive-HDMI-Sound-Card"; ++ simple-audio-card,dai-link@0 { ++ reg = <0>; ++ format = "i2s"; ++ bitclock-master = <&sndi2s0>; ++ frame-master = <&sndi2s0>; ++ mclk-fs = <256>; ++ status = "okay"; ++ ++ sndi2s0: cpu { ++ sound-dai = <&i2stx0>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi>; ++ }; ++ }; ++ }; ++ ++ ac108_mclk: ac108_mclk { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ clock-frequency = <24000000>; ++ }; ++ ++ clk_ext_camera: clk-ext-camera { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ clock-frequency = <24000000>; ++ }; ++ ++ wm8960_mclk: wm8960_mclk { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ clock-frequency = <24576000>; ++ }; + }; + + &dvp_clk { +@@ -177,6 +258,42 @@ + pinctrl-names = "default"; + pinctrl-0 = <&i2c2_pins>; + status = "okay"; ++ ++ seeed_plane_i2c@45 { ++ compatible = "seeed_panel"; ++ reg = <0x45>; ++ ++ port { ++ panel_out0: endpoint { ++ remote-endpoint = <&dsi0_output>; ++ }; ++ }; ++ }; ++ ++ tinker_ft5406: tinker_ft5406@38 { ++ compatible = "tinker_ft5406"; ++ reg = <0x38>; ++ }; ++ ++ touchscreen@14 { ++ compatible = "goodix,gt911"; ++ reg = <0x14>; ++ irq-gpios = <&sysgpio 30 GPIO_ACTIVE_HIGH>; ++ reset-gpios = <&sysgpio 31 GPIO_ACTIVE_HIGH>; ++ }; ++ ++ panel_radxa@19 { ++ compatible ="starfive_jadard"; ++ reg = <0x19>; ++ reset-gpio = <&sysgpio 23 0>; ++ enable-gpio = <&sysgpio 22 0>; ++ ++ port { ++ panel_out1: endpoint { ++ remote-endpoint = <&dsi1_output>; ++ }; ++ }; ++ }; + }; + + &i2c5 { +@@ -195,11 +312,36 @@ + #interrupt-cells = <1>; + + regulators { ++ mipi_0p9: ALDO1 { ++ regulator-boot-on; ++ regulator-compatible = "aldo1"; ++ regulator-name = "mipi_0p9"; ++ regulator-min-microvolt = <900000>; ++ regulator-max-microvolt = <900000>; ++ }; ++ ++ hdmi_0p9: ALDO5 { ++ regulator-boot-on; ++ regulator-compatible = "aldo5"; ++ regulator-name = "hdmi_0p9"; ++ regulator-min-microvolt = <900000>; ++ regulator-max-microvolt = <900000>; ++ }; ++ ++ hdmi_1p8: ALDO3 { ++ regulator-boot-on; ++ regulator-compatible = "aldo3"; ++ regulator-name = "hdmi_1p8"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <1800000>; ++ }; ++ + vcc_3v3: dcdc1 { + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; ++ regulator-compatible = "dcdc1"; + regulator-name = "vcc_3v3"; + }; + +@@ -207,6 +349,7 @@ + regulator-always-on; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1540000>; ++ regulator-compatible = "dcdc2"; + regulator-name = "vdd-cpu"; + }; + +@@ -215,6 +358,7 @@ + regulator-always-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; ++ regulator-compatible = "aldo4"; + regulator-name = "emmc_vdd"; + }; + }; +@@ -229,12 +373,68 @@ + pinctrl-names = "default"; + pinctrl-0 = <&i2c6_pins>; + status = "okay"; ++ ++ imx219: imx219@10 { ++ compatible = "sony,imx219"; ++ reg = <0x10>; ++ clocks = <&clk_ext_camera>; ++ clock-names = "xclk"; ++ reset-gpio = <&sysgpio 18 0>; ++ rotation = <0>; ++ orientation = <1>; //CAMERA_ORIENTATION_BACK ++ ++ port { ++ /* CSI2 bus endpoint */ ++ imx219_to_csi2rx0: endpoint { ++ remote-endpoint = <&csi2rx0_from_imx219>; ++ bus-type = <4>; /* MIPI CSI-2 D-PHY */ ++ clock-lanes = <4>; ++ data-lanes = <0 1>; ++ lane-polarities = <0 0 0>; ++ link-frequencies = /bits/ 64 <456000000>; ++ }; ++ }; ++ }; ++ ++ imx708: imx708@1a { ++ compatible = "sony,imx708"; ++ reg = <0x1a>; ++ clocks = <&clk_ext_camera>; ++ reset-gpio = <&sysgpio 18 0>; ++ ++ port { ++ imx708_to_csi2rx0: endpoint { ++ remote-endpoint = <&csi2rx0_from_imx708>; ++ data-lanes = <1 2>; ++ clock-noncontinuous; ++ link-frequencies = /bits/ 64 <450000000>; ++ }; ++ }; ++ }; ++ ++ ov4689: ov4689@36 { ++ compatible = "ovti,ov4689"; ++ reg = <0x36>; ++ clocks = <&clk_ext_camera>; ++ clock-names = "xclk"; ++ rotation = <180>; ++ ++ port { ++ /* Parallel bus endpoint */ ++ ov4689_to_csi2rx0: endpoint { ++ remote-endpoint = <&csi2rx0_from_ov4689>; ++ bus-type = <4>; /* MIPI CSI-2 D-PHY */ ++ clock-lanes = <0>; ++ data-lanes = <1 2>; ++ }; ++ }; ++ }; + }; + + &i2srx { + pinctrl-names = "default"; + pinctrl-0 = <&i2srx_pins>; +- status = "okay"; ++ status = "disabled"; + }; + + &i2stx0 { +@@ -246,7 +446,7 @@ + &i2stx1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2stx1_pins>; +- status = "okay"; ++ status = "disabled"; + }; + + &mmc0 { +@@ -661,6 +861,40 @@ + slew-rate = <0>; + }; + }; ++ ++ hdmi_pins: hdmi-0 { ++ scl-pins { ++ pinmux = <GPIOMUX(0, GPOUT_SYS_HDMI_DDC_SCL, ++ GPOEN_SYS_HDMI_DDC_SCL, ++ GPI_SYS_HDMI_DDC_SCL)>; ++ bias-pull-up; ++ input-enable; ++ }; ++ ++ sda-pins { ++ pinmux = <GPIOMUX(1, GPOUT_SYS_HDMI_DDC_SDA, ++ GPOEN_SYS_HDMI_DDC_SDA, ++ GPI_SYS_HDMI_DDC_SDA)>; ++ bias-pull-up; ++ input-enable; ++ }; ++ ++ cec-pins { ++ pinmux = <GPIOMUX(14, GPOUT_SYS_HDMI_CEC_SDA, ++ GPOEN_SYS_HDMI_CEC_SDA, ++ GPI_SYS_HDMI_CEC_SDA)>; ++ bias-pull-up; ++ input-enable; ++ }; ++ ++ hpd-pins { ++ pinmux = <GPIOMUX(15, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_HDMI_HPD)>; ++ bias-disable; /* external pull-up */ ++ input-enable; ++ }; ++ }; + }; + + &uart0 { +@@ -689,3 +923,202 @@ + &U74_4 { + cpu-supply = <&vdd_cpu>; + }; ++ ++ ++&display { ++ ports = <&dc_out_dpi0>; ++ status = "okay"; ++}; ++ ++&dc8200 { ++ status = "okay"; ++ ++ dc_out: port { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ dc_out_dpi0: endpoint@0 { ++ reg = <0>; ++ remote-endpoint = <&hdmi_input0>; ++ }; ++ dc_out_dpi1: endpoint@1 { ++ reg = <1>; ++ remote-endpoint = <&hdmi_in_lcdc>; ++ }; ++ ++ dc_out_dpi2: endpoint@2 { ++ reg = <2>; ++ remote-endpoint = <&mipi_in>; ++ }; ++ }; ++}; ++ ++&hdmi { ++ status = "okay"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&hdmi_pins>; ++ hpd-gpio = <&sysgpio 15 GPIO_ACTIVE_HIGH>; ++ ++ hdmi_in: port { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ hdmi_in_lcdc: endpoint@0 { ++ reg = <0>; ++ remote-endpoint = <&dc_out_dpi1>; ++ }; ++ }; ++}; ++ ++&rgb_output { ++ status = "disabled"; ++ ++ ports { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ port@0 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0>; ++ hdmi_input0:endpoint@0 { ++ reg = <0>; ++ remote-endpoint = <&dc_out_dpi0>; ++ }; ++ }; ++ }; ++}; ++ ++&dsi_output { ++ status = "okay"; ++ ++ ports { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@0 { ++ reg = <0>; ++ mipi_in: endpoint { ++ remote-endpoint = <&dc_out_dpi2>; ++ }; ++ }; ++ ++ port@1 { ++ reg = <1>; ++ sf_dpi_output: endpoint { ++ remote-endpoint = <&dsi_in_port>; ++ }; ++ }; ++ }; ++}; ++ ++&mipi_dsi { ++ status = "okay"; ++ ++ ports { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@0 { ++ reg = <0>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ dsi0_output: endpoint@0 { ++ reg = <0>; ++ remote-endpoint = <&panel_out0>; ++ }; ++ ++ dsi1_output: endpoint@1 { ++ reg = <1>; ++ remote-endpoint = <&panel_out1>; ++ }; ++ ++ }; ++ ++ port@1{ ++ reg = <1>; ++ dsi_in_port: endpoint { ++ remote-endpoint = <&sf_dpi_output>; ++ }; ++ }; ++ ++ }; ++}; ++ ++&mipi_dphy { ++ status = "okay"; ++}; ++ ++&co_process { ++ memory-region = <&e24_mem>; ++ status = "okay"; ++}; ++ ++ ++&mailbox_contrl0 { ++ status = "okay"; ++}; ++ ++&mailbox_client0 { ++ status = "okay"; ++}; ++ ++&vpu_dec { ++ status = "okay"; ++}; ++ ++&vpu_enc { ++ status = "okay"; ++}; ++ ++&jpu { ++ status = "okay"; ++}; ++ ++&gpu { ++ status = "okay"; ++}; ++ ++&vin_sysctl { ++ /* when use dvp open this pinctrl*/ ++ status = "okay"; ++ ++ ports { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@1 { ++ reg = <1>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ /* CSI2 bus endpoint */ ++ csi2rx0_from_imx219: endpoint@0 { ++ reg = <0>; ++ remote-endpoint = <&imx219_to_csi2rx0>; ++ bus-type = <4>; /* MIPI CSI-2 D-PHY */ ++ clock-lanes = <4>; ++ data-lanes = <0 1>; ++ lane-polarities = <0 0 0>; ++ status = "okay"; ++ }; ++ ++ csi2rx0_from_imx708: endpoint@1 { ++ reg = <1>; ++ remote-endpoint = <&imx708_to_csi2rx0>; ++ bus-type = <4>; /* MIPI CSI-2 D-PHY */ ++ clock-lanes = <4>; ++ data-lanes = <0 1>; ++ lane-polarities = <0 0 0>; ++ status = "okay"; ++ }; ++ ++ csi2rx0_from_ov4689: endpoint@2 { ++ reg = <2>; ++ remote-endpoint = <&ov4689_to_csi2rx0>; ++ bus-type = <4>; /* MIPI CSI-2 D-PHY */ ++ clock-lanes = <4>; ++ data-lanes = <0 1>; ++ status = "okay"; ++ }; ++ }; ++ }; ++}; diff --git a/target/linux/starfive/patches-6.6/0107-riscv-dts-starfive-evb-overlay-Support-SPI-overlay.patch b/target/linux/starfive/patches-6.6/0107-riscv-dts-starfive-evb-overlay-Support-SPI-overlay.patch new file mode 100644 index 0000000000..873883101f --- /dev/null +++ b/target/linux/starfive/patches-6.6/0107-riscv-dts-starfive-evb-overlay-Support-SPI-overlay.patch @@ -0,0 +1,71 @@ +From 17a781895079f8f9b2c089d13a1e4d9789e018c5 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Wed, 20 Mar 2024 17:36:14 +0800 +Subject: [PATCH 107/116] riscv: dts: starfive: evb-overlay: Support SPI + overlay + +Add new compatibles to support SPI overlay. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + .../dts/starfive/evb-overlay/jh7110-evb-overlay-spi.dtso | 7 +++++++ + 1 file changed, 7 insertions(+) + +--- a/arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-spi.dtso ++++ b/arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-spi.dtso +@@ -9,6 +9,7 @@ + fragment@0 { + target-path = "/soc/spi@10060000"; + __overlay__ { ++ compatible = "starfive,jh7110-spi-pl022"; + status = "okay"; + }; + }; +@@ -17,6 +18,7 @@ + fragment@1 { + target-path = "/soc/spi@10070000"; + __overlay__ { ++ compatible = "starfive,jh7110-spi-pl022"; + status = "okay"; + }; + }; +@@ -25,6 +27,7 @@ + fragment@2 { + target-path = "/soc/spi@10080000"; + __overlay__ { ++ compatible = "starfive,jh7110-spi-pl022"; + status = "okay"; + }; + }; +@@ -33,6 +36,7 @@ + fragment@3 { + target-path = "/soc/spi@12070000"; + __overlay__ { ++ compatible = "starfive,jh7110-spi-pl022"; + status = "okay"; + }; + }; +@@ -41,6 +45,7 @@ + fragment@4 { + target-path = "/soc/spi@12080000"; + __overlay__ { ++ compatible = "starfive,jh7110-spi-pl022"; + status = "okay"; + }; + }; +@@ -49,6 +54,7 @@ + fragment@5 { + target-path = "/soc/spi@12090000"; + __overlay__ { ++ compatible = "starfive,jh7110-spi-pl022"; + status = "okay"; + }; + }; +@@ -57,6 +63,7 @@ + fragment@6 { + target-path = "/soc/spi@120a0000"; + __overlay__ { ++ compatible = "starfive,jh7110-spi-pl022"; + status = "okay"; + }; + }; diff --git a/target/linux/starfive/patches-6.6/0108-riscv-configs-visionfive2-Add-standard-partition-for.patch b/target/linux/starfive/patches-6.6/0108-riscv-configs-visionfive2-Add-standard-partition-for.patch new file mode 100644 index 0000000000..a4b2e14f9b --- /dev/null +++ b/target/linux/starfive/patches-6.6/0108-riscv-configs-visionfive2-Add-standard-partition-for.patch @@ -0,0 +1,23 @@ +From 49b818cdd08fca7ff761277635c0e5ab659becb8 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Thu, 21 Mar 2024 16:53:51 +0800 +Subject: [PATCH 108/116] riscv: configs: visionfive2: Add standard partition + for hibernation + +Add CONFIG_PM_STD_PARTITION="PARTLABEL=hibernation". + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + arch/riscv/configs/starfive_visionfive2_defconfig | 1 + + 1 file changed, 1 insertion(+) + +--- a/arch/riscv/configs/starfive_visionfive2_defconfig ++++ b/arch/riscv/configs/starfive_visionfive2_defconfig +@@ -24,6 +24,7 @@ CONFIG_NONPORTABLE=y + CONFIG_SMP=y + CONFIG_HZ_100=y + CONFIG_HIBERNATION=y ++CONFIG_PM_STD_PARTITION="PARTLABEL=hibernation" + CONFIG_PM_DEBUG=y + CONFIG_PM_ADVANCED_DEBUG=y + CONFIG_PM_TEST_SUSPEND=y diff --git a/target/linux/starfive/patches-6.6/0109-usb-xhci-To-improve-performance-usb-using-lowmem-for.patch b/target/linux/starfive/patches-6.6/0109-usb-xhci-To-improve-performance-usb-using-lowmem-for.patch new file mode 100644 index 0000000000..44c6fe6126 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0109-usb-xhci-To-improve-performance-usb-using-lowmem-for.patch @@ -0,0 +1,297 @@ +From 146eb94a08d12b5831e1d30455469750f7c5f2a3 Mon Sep 17 00:00:00 2001 +From: "minda.chen" <minda.chen@starfivetech.com> +Date: Tue, 18 Oct 2022 09:57:39 +0800 +Subject: [PATCH 109/116] usb:xhci:To improve performance,usb using lowmem for + bulk xfer. + +Generate an usb low memory pool for usb 3.0 host +read/write transfer, default size is 8M. + +Signed-off-by: minda.chen <minda.chen@starfivetech.com> +--- + arch/riscv/boot/dts/starfive/jh7110-evb.dts | 1 + + drivers/usb/core/hcd.c | 4 +- + drivers/usb/host/xhci-mem.c | 64 +++++++++++++++++++++ + drivers/usb/host/xhci-plat.c | 8 +++ + drivers/usb/host/xhci-ring.c | 3 +- + drivers/usb/host/xhci.c | 57 +++++++++++++++++- + drivers/usb/host/xhci.h | 11 ++++ + 7 files changed, 145 insertions(+), 3 deletions(-) + +--- a/arch/riscv/boot/dts/starfive/jh7110-evb.dts ++++ b/arch/riscv/boot/dts/starfive/jh7110-evb.dts +@@ -31,5 +31,6 @@ + }; + + &usb0 { ++ xhci-lowmem-pool; + status = "okay"; + }; +--- a/drivers/usb/core/hcd.c ++++ b/drivers/usb/core/hcd.c +@@ -1439,7 +1439,9 @@ int usb_hcd_map_urb_for_dma(struct usb_h + if (ret == 0) + urb->transfer_flags |= URB_MAP_LOCAL; + } else if (hcd_uses_dma(hcd)) { +- if (urb->num_sgs) { ++ if (urb->transfer_flags & URB_MAP_LOCAL) ++ return ret; ++ else if (urb->num_sgs) { + int n; + + /* We don't support sg for isoc transfers ! */ +--- a/drivers/usb/host/xhci-mem.c ++++ b/drivers/usb/host/xhci-mem.c +@@ -14,6 +14,7 @@ + #include <linux/slab.h> + #include <linux/dmapool.h> + #include <linux/dma-mapping.h> ++#include <linux/genalloc.h> + + #include "xhci.h" + #include "xhci-trace.h" +@@ -1842,6 +1843,7 @@ xhci_free_interrupter(struct xhci_hcd *x + void xhci_mem_cleanup(struct xhci_hcd *xhci) + { + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; ++ struct xhci_lowmem_pool *pool; + int i, j, num_ports; + + cancel_delayed_work_sync(&xhci->cmd_timer); +@@ -1887,6 +1889,13 @@ void xhci_mem_cleanup(struct xhci_hcd *x + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "Freed medium stream array pool"); + ++ if (xhci->lowmem_pool.pool) { ++ pool = &xhci->lowmem_pool; ++ dma_free_coherent(dev, pool->size, (void *)pool->cached_base, pool->dma_addr); ++ gen_pool_destroy(pool->pool); ++ pool->pool = NULL; ++ } ++ + if (xhci->dcbaa) + dma_free_coherent(dev, sizeof(*xhci->dcbaa), + xhci->dcbaa, xhci->dcbaa->dma); +@@ -2300,6 +2309,55 @@ xhci_add_interrupter(struct xhci_hcd *xh + return 0; + } + ++int xhci_setup_local_lowmem(struct xhci_hcd *xhci, size_t size) ++{ ++ int err; ++ void *buffer; ++ dma_addr_t dma_addr; ++ struct usb_hcd *hcd = xhci_to_hcd(xhci); ++ struct xhci_lowmem_pool *pool = &xhci->lowmem_pool; ++ ++ if (!pool->pool) { ++ /* minimal alloc one page */ ++ pool->pool = gen_pool_create(PAGE_SHIFT, dev_to_node(hcd->self.sysdev)); ++ if (IS_ERR(pool->pool)) ++ return PTR_ERR(pool->pool); ++ } ++ ++ buffer = dma_alloc_coherent(hcd->self.sysdev, size, &dma_addr, ++ GFP_KERNEL | GFP_DMA32); ++ ++ if (IS_ERR(buffer)) { ++ err = PTR_ERR(buffer); ++ goto destroy_pool; ++ } ++ ++ /* ++ * Here we pass a dma_addr_t but the arg type is a phys_addr_t. ++ * It's not backed by system memory and thus there's no kernel mapping ++ * for it. ++ */ ++ err = gen_pool_add_virt(pool->pool, (unsigned long)buffer, ++ dma_addr, size, dev_to_node(hcd->self.sysdev)); ++ if (err < 0) { ++ dev_err(hcd->self.sysdev, "gen_pool_add_virt failed with %d\n", ++ err); ++ dma_free_coherent(hcd->self.sysdev, size, buffer, dma_addr); ++ goto destroy_pool; ++ } ++ ++ pool->cached_base = (u64)buffer; ++ pool->dma_addr = dma_addr; ++ ++ return 0; ++ ++destroy_pool: ++ gen_pool_destroy(pool->pool); ++ pool->pool = NULL; ++ return err; ++} ++EXPORT_SYMBOL_GPL(xhci_setup_local_lowmem); ++ + int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) + { + dma_addr_t dma; +@@ -2436,6 +2494,12 @@ int xhci_mem_init(struct xhci_hcd *xhci, + + xhci->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX; + ++ if (xhci->quirks & XHCI_LOCAL_BUFFER) { ++ if (xhci_setup_local_lowmem(xhci, ++ xhci->lowmem_pool.size)) ++ goto fail; ++ } ++ + /* + * XXX: Might need to set the Interrupter Moderation Register to + * something other than the default (~1ms minimum between interrupts). +--- a/drivers/usb/host/xhci-plat.c ++++ b/drivers/usb/host/xhci-plat.c +@@ -253,6 +253,14 @@ int xhci_plat_probe(struct platform_devi + if (device_property_read_bool(tmpdev, "xhci-sg-trb-cache-size-quirk")) + xhci->quirks |= XHCI_SG_TRB_CACHE_SIZE_QUIRK; + ++ if (device_property_read_bool(tmpdev, "xhci-lowmem-pool")) { ++ xhci->quirks |= XHCI_LOCAL_BUFFER; ++ if (device_property_read_u32(tmpdev, "lowmem-pool-size", ++ &xhci->lowmem_pool.size)) { ++ xhci->lowmem_pool.size = 8 << 20; ++ } else ++ xhci->lowmem_pool.size <<= 20; ++ } + device_property_read_u32(tmpdev, "imod-interval-ns", + &xhci->imod_interval); + } +--- a/drivers/usb/host/xhci-ring.c ++++ b/drivers/usb/host/xhci-ring.c +@@ -3664,7 +3664,8 @@ int xhci_queue_bulk_tx(struct xhci_hcd * + + full_len = urb->transfer_buffer_length; + /* If we have scatter/gather list, we use it. */ +- if (urb->num_sgs && !(urb->transfer_flags & URB_DMA_MAP_SINGLE)) { ++ if (urb->num_sgs && !(urb->transfer_flags & URB_DMA_MAP_SINGLE) ++ && !(urb->transfer_flags & URB_MAP_LOCAL)) { + num_sgs = urb->num_mapped_sgs; + sg = urb->sg; + addr = (u64) sg_dma_address(sg); +--- a/drivers/usb/host/xhci.c ++++ b/drivers/usb/host/xhci.c +@@ -18,6 +18,8 @@ + #include <linux/slab.h> + #include <linux/dmi.h> + #include <linux/dma-mapping.h> ++#include <linux/dma-map-ops.h> ++#include <linux/genalloc.h> + + #include "xhci.h" + #include "xhci-trace.h" +@@ -1285,6 +1287,55 @@ static void xhci_unmap_temp_buf(struct u + urb->transfer_buffer = NULL; + } + ++static void xhci_map_urb_local(struct usb_hcd *hcd, struct urb *urb, ++ gfp_t mem_flags) ++{ ++ void *buffer; ++ dma_addr_t dma_handle; ++ struct xhci_hcd *xhci = hcd_to_xhci(hcd); ++ struct xhci_lowmem_pool *lowmem_pool = &xhci->lowmem_pool; ++ ++ if (lowmem_pool->pool ++ && (usb_endpoint_type(&urb->ep->desc) == USB_ENDPOINT_XFER_BULK) ++ && (urb->transfer_buffer_length > PAGE_SIZE) ++ && urb->num_sgs && urb->sg && (sg_phys(urb->sg) > 0xffffffff)) { ++ buffer = gen_pool_dma_alloc(lowmem_pool->pool, ++ urb->transfer_buffer_length, &dma_handle); ++ if (buffer) { ++ urb->transfer_dma = dma_handle; ++ urb->transfer_buffer = buffer; ++ urb->transfer_flags |= URB_MAP_LOCAL; ++ if (usb_urb_dir_out(urb)) ++ sg_copy_to_buffer(urb->sg, urb->num_sgs, ++ (void *)buffer, ++ urb->transfer_buffer_length); ++ } ++ } ++ ++} ++ ++static void xhci_unmap_urb_local(struct usb_hcd *hcd, struct urb *urb) ++{ ++ dma_addr_t dma_handle; ++ u64 cached_buffer; ++ struct xhci_hcd *xhci = hcd_to_xhci(hcd); ++ struct xhci_lowmem_pool *lowmem_pool = &xhci->lowmem_pool; ++ ++ if (urb->transfer_flags & URB_MAP_LOCAL) { ++ dma_handle = urb->transfer_dma; ++ cached_buffer = lowmem_pool->cached_base + ++ ((u32)urb->transfer_dma & (lowmem_pool->size - 1)); ++ if (usb_urb_dir_in(urb)) ++ sg_copy_from_buffer(urb->sg, urb->num_sgs, ++ (void *)cached_buffer, urb->transfer_buffer_length); ++ gen_pool_free(lowmem_pool->pool, (unsigned long)urb->transfer_buffer, ++ urb->transfer_buffer_length); ++ urb->transfer_flags &= ~URB_MAP_LOCAL; ++ urb->transfer_buffer = NULL; ++ } ++} ++ ++ + /* + * Bypass the DMA mapping if URB is suitable for Immediate Transfer (IDT), + * we'll copy the actual data into the TRB address register. This is limited to +@@ -1305,9 +1356,11 @@ static int xhci_map_urb_for_dma(struct u + if (xhci_urb_temp_buffer_required(hcd, urb)) + return xhci_map_temp_buffer(hcd, urb); + } ++ xhci_map_urb_local(hcd, urb, mem_flags); + return usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); + } + ++ + static void xhci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) + { + struct xhci_hcd *xhci; +@@ -1320,8 +1373,10 @@ static void xhci_unmap_urb_for_dma(struc + + if ((xhci->quirks & XHCI_SG_TRB_CACHE_SIZE_QUIRK) && unmap_temp_buf) + xhci_unmap_temp_buf(hcd, urb); +- else ++ else { ++ xhci_unmap_urb_local(hcd, urb); + usb_hcd_unmap_urb_for_dma(hcd, urb); ++ } + } + + /** +--- a/drivers/usb/host/xhci.h ++++ b/drivers/usb/host/xhci.h +@@ -1508,6 +1508,13 @@ struct xhci_hub { + u8 min_rev; + }; + ++struct xhci_lowmem_pool { ++ struct gen_pool *pool; ++ u64 cached_base; ++ dma_addr_t dma_addr; ++ unsigned int size; ++}; ++ + /* There is one xhci_hcd structure per controller */ + struct xhci_hcd { + struct usb_hcd *main_hcd; +@@ -1661,6 +1668,8 @@ struct xhci_hcd { + #define XHCI_WRITE_64_HI_LO BIT_ULL(47) + #define XHCI_CDNS_SCTX_QUIRK BIT_ULL(48) + ++#define XHCI_LOCAL_BUFFER BIT_ULL(63) ++ + unsigned int num_active_eps; + unsigned int limit_active_eps; + struct xhci_port *hw_ports; +@@ -1690,6 +1699,8 @@ struct xhci_hcd { + struct list_head regset_list; + + void *dbc; ++ struct xhci_lowmem_pool lowmem_pool; ++ + /* platform-specific data -- must come last */ + unsigned long priv[] __aligned(sizeof(s64)); + }; diff --git a/target/linux/starfive/patches-6.6/0110-usb-xhci-using-dma_alloc_noncoherent-to-alloc-low-me.patch b/target/linux/starfive/patches-6.6/0110-usb-xhci-using-dma_alloc_noncoherent-to-alloc-low-me.patch new file mode 100644 index 0000000000..a9c03feecf --- /dev/null +++ b/target/linux/starfive/patches-6.6/0110-usb-xhci-using-dma_alloc_noncoherent-to-alloc-low-me.patch @@ -0,0 +1,106 @@ +From a170cb9936bb0b00d58aaea40984dbce1169fe42 Mon Sep 17 00:00:00 2001 +From: Minda Chen <minda.chen@starfivetech.com> +Date: Mon, 3 Jul 2023 16:14:20 +0800 +Subject: [PATCH 110/116] usb: xhci: using dma_alloc_noncoherent to alloc low + memory pool + +For RISCV_NONCACHEHERENT is set, using dma_alloc_noncoherent +to alloc cached large block low memory buffer. And set default +size to 4M. (largest size of continuous memory can be supported) + +Signed-off-by: Minda Chen <minda.chen@starfivetech.com> +--- + drivers/usb/host/xhci-mem.c | 29 ++++++++++++++++------------- + drivers/usb/host/xhci-plat.c | 9 +++++---- + 2 files changed, 21 insertions(+), 17 deletions(-) + +--- a/drivers/usb/host/xhci-mem.c ++++ b/drivers/usb/host/xhci-mem.c +@@ -1891,7 +1891,8 @@ void xhci_mem_cleanup(struct xhci_hcd *x + + if (xhci->lowmem_pool.pool) { + pool = &xhci->lowmem_pool; +- dma_free_coherent(dev, pool->size, (void *)pool->cached_base, pool->dma_addr); ++ dma_free_noncoherent(dev, pool->size, (void *)pool->cached_base, ++ pool->dma_addr, DMA_BIDIRECTIONAL); + gen_pool_destroy(pool->pool); + pool->pool = NULL; + } +@@ -2320,15 +2321,15 @@ int xhci_setup_local_lowmem(struct xhci_ + if (!pool->pool) { + /* minimal alloc one page */ + pool->pool = gen_pool_create(PAGE_SHIFT, dev_to_node(hcd->self.sysdev)); +- if (IS_ERR(pool->pool)) +- return PTR_ERR(pool->pool); ++ if (!pool->pool) ++ return -ENOMEM; + } + +- buffer = dma_alloc_coherent(hcd->self.sysdev, size, &dma_addr, +- GFP_KERNEL | GFP_DMA32); ++ buffer = dma_alloc_noncoherent(hcd->self.sysdev, size, &dma_addr, ++ DMA_BIDIRECTIONAL, GFP_ATOMIC); + +- if (IS_ERR(buffer)) { +- err = PTR_ERR(buffer); ++ if (!buffer) { ++ err = -ENOMEM; + goto destroy_pool; + } + +@@ -2338,11 +2339,11 @@ int xhci_setup_local_lowmem(struct xhci_ + * for it. + */ + err = gen_pool_add_virt(pool->pool, (unsigned long)buffer, +- dma_addr, size, dev_to_node(hcd->self.sysdev)); ++ dma_addr, size, dev_to_node(hcd->self.sysdev)); + if (err < 0) { + dev_err(hcd->self.sysdev, "gen_pool_add_virt failed with %d\n", + err); +- dma_free_coherent(hcd->self.sysdev, size, buffer, dma_addr); ++ dma_free_noncoherent(hcd->self.sysdev, size, buffer, dma_addr, DMA_BIDIRECTIONAL); + goto destroy_pool; + } + +@@ -2365,7 +2366,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, + unsigned int val, val2; + u64 val_64; + u32 page_size, temp; +- int i; ++ int i, ret; + + INIT_LIST_HEAD(&xhci->cmd_list); + +@@ -2495,9 +2496,11 @@ int xhci_mem_init(struct xhci_hcd *xhci, + xhci->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX; + + if (xhci->quirks & XHCI_LOCAL_BUFFER) { +- if (xhci_setup_local_lowmem(xhci, +- xhci->lowmem_pool.size)) +- goto fail; ++ ret = xhci_setup_local_lowmem(xhci, xhci->lowmem_pool.size); ++ if (ret) { ++ xhci->quirks &= ~XHCI_LOCAL_BUFFER; ++ xhci_warn(xhci, "WARN: Can't alloc lowmem pool\n"); ++ } + } + + /* +--- a/drivers/usb/host/xhci-plat.c ++++ b/drivers/usb/host/xhci-plat.c +@@ -255,10 +255,11 @@ int xhci_plat_probe(struct platform_devi + + if (device_property_read_bool(tmpdev, "xhci-lowmem-pool")) { + xhci->quirks |= XHCI_LOCAL_BUFFER; +- if (device_property_read_u32(tmpdev, "lowmem-pool-size", +- &xhci->lowmem_pool.size)) { +- xhci->lowmem_pool.size = 8 << 20; +- } else ++ ret = device_property_read_u32(tmpdev, "lowmem-pool-size", ++ &xhci->lowmem_pool.size); ++ if (ret || xhci->lowmem_pool.size >= 4) ++ xhci->lowmem_pool.size = 4 << 20; ++ else + xhci->lowmem_pool.size <<= 20; + } + device_property_read_u32(tmpdev, "imod-interval-ns", diff --git a/target/linux/starfive/patches-6.6/0111-riscv-dts-starfive-Add-vf2-overlay-dtso-subdir.patch b/target/linux/starfive/patches-6.6/0111-riscv-dts-starfive-Add-vf2-overlay-dtso-subdir.patch new file mode 100644 index 0000000000..451b1c16fd --- /dev/null +++ b/target/linux/starfive/patches-6.6/0111-riscv-dts-starfive-Add-vf2-overlay-dtso-subdir.patch @@ -0,0 +1,139 @@ +From fc86405992c37b1898fd9b9bc077be673d774269 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Fri, 22 Mar 2024 09:54:28 +0800 +Subject: [PATCH 111/116] riscv: dts: starfive: Add vf2-overlay dtso subdir + +Create subdir vf2-overlay/ and add overlay .dtso for VF2 board. +The code is ported from tag JH7110_VF2_6.1_v5.11.4 + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + arch/riscv/boot/dts/starfive/Makefile | 1 + + .../boot/dts/starfive/vf2-overlay/Makefile | 3 + + .../starfive/vf2-overlay/vf2-overlay-can.dtso | 23 ++++++ + .../vf2-overlay/vf2-overlay-uart3-i2c.dtso | 75 +++++++++++++++++++ + 4 files changed, 102 insertions(+) + create mode 100644 arch/riscv/boot/dts/starfive/vf2-overlay/Makefile + create mode 100644 arch/riscv/boot/dts/starfive/vf2-overlay/vf2-overlay-can.dtso + create mode 100644 arch/riscv/boot/dts/starfive/vf2-overlay/vf2-overlay-uart3-i2c.dtso + +--- a/arch/riscv/boot/dts/starfive/Makefile ++++ b/arch/riscv/boot/dts/starfive/Makefile +@@ -9,6 +9,7 @@ DTC_FLAGS_jh7110-evb := -@ + dtb-$(CONFIG_ARCH_STARFIVE) += jh7100-beaglev-starlight.dtb + dtb-$(CONFIG_ARCH_STARFIVE) += jh7100-starfive-visionfive-v1.dtb + ++subdir-y += vf2-overlay + dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-starfive-visionfive-2-v1.2a.dtb + dtb-$(CONFIG_ARCH_STARFIVE) += jh7110-starfive-visionfive-2-v1.3b.dtb \ + jh7110-starfive-visionfive-2-ac108.dtb \ +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/vf2-overlay/Makefile +@@ -0,0 +1,3 @@ ++# SPDX-License-Identifier: GPL-2.0 ++dtb-$(CONFIG_ARCH_STARFIVE) += vf2-overlay-uart3-i2c.dtbo \ ++ vf2-overlay-can.dtbo +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/vf2-overlay/vf2-overlay-can.dtso +@@ -0,0 +1,23 @@ ++/dts-v1/; ++/plugin/; ++#include <dt-bindings/gpio/gpio.h> ++#include "../jh7110-pinfunc.h" ++/ { ++ compatible = "starfive,jh7110"; ++ ++ //can0 ++ fragment@0 { ++ target-path = "/soc/can@130d0000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++ ++ //can1 ++ fragment@1 { ++ target-path = "/soc/can@130e0000"; ++ __overlay__ { ++ status = "okay"; ++ }; ++ }; ++}; +--- /dev/null ++++ b/arch/riscv/boot/dts/starfive/vf2-overlay/vf2-overlay-uart3-i2c.dtso +@@ -0,0 +1,75 @@ ++/dts-v1/; ++/plugin/; ++#include <dt-bindings/gpio/gpio.h> ++#include "../jh7110-pinfunc.h" ++/ { ++ compatible = "starfive,jh7110"; ++ ++ //sysgpio ++ fragment@0 { ++ target-path = "/soc/pinctrl@13040000"; ++ __overlay__ { ++ dt_uart3_pins: dt-uart3-0 { ++ tx-pins { ++ pinmux = <GPIOMUX(60, GPOUT_SYS_UART3_TX, ++ GPOEN_ENABLE, ++ GPI_NONE)>; ++ bias-disable; ++ drive-strength = <12>; ++ input-disable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ ++ rx-pins { ++ pinmux = <GPIOMUX(63, GPOUT_LOW, ++ GPOEN_DISABLE, ++ GPI_SYS_UART3_RX)>; ++ bias-pull-up; ++ drive-strength = <2>; ++ input-enable; ++ input-schmitt-enable; ++ slew-rate = <0>; ++ }; ++ }; ++ ++ dt_i2c1_pins: dt-i2c1-0 { ++ i2c-pins { ++ pinmux = <GPIOMUX(42, GPOUT_LOW, ++ GPOEN_SYS_I2C1_CLK, ++ GPI_SYS_I2C1_CLK)>, ++ <GPIOMUX(43, GPOUT_LOW, ++ GPOEN_SYS_I2C1_DATA, ++ GPI_SYS_I2C1_DATA)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ }; ++ }; ++ }; ++ ++ //uart3 ++ fragment@1 { ++ target-path = "/soc/serial@12000000"; ++ __overlay__ { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&dt_uart3_pins>; ++ status = "okay"; ++ }; ++ }; ++ ++ //i2c1 ++ fragment@2 { ++ target-path = "/soc/i2c@10040000"; ++ __overlay__ { ++ clock-frequency = <100000>; ++ i2c-sda-hold-time-ns = <300>; ++ i2c-sda-falling-time-ns = <510>; ++ i2c-scl-falling-time-ns = <510>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&dt_i2c1_pins>; ++ status = "okay"; ++ }; ++ }; ++}; diff --git a/target/linux/starfive/patches-6.6/0112-riscv-dts-starfive-visionfive-2-Add-aliases-for-i2c-.patch b/target/linux/starfive/patches-6.6/0112-riscv-dts-starfive-visionfive-2-Add-aliases-for-i2c-.patch new file mode 100644 index 0000000000..17dcf726ef --- /dev/null +++ b/target/linux/starfive/patches-6.6/0112-riscv-dts-starfive-visionfive-2-Add-aliases-for-i2c-.patch @@ -0,0 +1,34 @@ +From 2b098da69f9497e725683caf67fbecf79202b7fc Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Fri, 22 Mar 2024 11:33:40 +0800 +Subject: [PATCH 112/116] riscv: dts: starfive: visionfive 2: Add aliases for + i2c* and uart3 + +Fix the numbers of i2c and uart3. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + .../riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi | 4 ++++ + 1 file changed, 4 insertions(+) + +--- a/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi +@@ -15,7 +15,10 @@ + ethernet0 = &gmac0; + ethernet1 = &gmac1; + i2c0 = &i2c0; ++ i2c1 = &i2c1; + i2c2 = &i2c2; ++ i2c3 = &i2c3; ++ i2c4 = &i2c4; + i2c5 = &i2c5; + i2c6 = &i2c6; + mmc0 = &mmc0; +@@ -23,6 +26,7 @@ + pcie0 = &pcie0; + pcie1 = &pcie1; + serial0 = &uart0; ++ serial3 = &uart3; + }; + + chosen { diff --git a/target/linux/starfive/patches-6.6/0113-driver-bluetooth-add-aic8800-driver-support.patch b/target/linux/starfive/patches-6.6/0113-driver-bluetooth-add-aic8800-driver-support.patch new file mode 100644 index 0000000000..3651976081 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0113-driver-bluetooth-add-aic8800-driver-support.patch @@ -0,0 +1,6063 @@ +From 18b70b6f5192ee5363515b57e1f213d0698224ed Mon Sep 17 00:00:00 2001 +From: "ziv.xu" <ziv.xu@starfive.com> +Date: Tue, 28 Nov 2023 15:37:14 +0800 +Subject: [PATCH 113/116] driver: bluetooth: add aic8800 driver support + +add aic8800 driver that can support sco profile + +Signed-off-by: ziv.xu <ziv.xu@starfive.com> +--- + .../configs/starfive_visionfive2_defconfig | 3 +- + drivers/bluetooth/Kconfig | 4 + + drivers/bluetooth/Makefile | 1 + + drivers/bluetooth/aic_btusb/Makefile | 80 + + drivers/bluetooth/aic_btusb/aic_btusb.c | 5031 +++++++++++++++++ + drivers/bluetooth/aic_btusb/aic_btusb.h | 753 +++ + .../aic_btusb/aic_btusb_external_featrue.c | 126 + + .../aic_btusb/aic_btusb_external_featrue.h | 3 + + 8 files changed, 5999 insertions(+), 2 deletions(-) + create mode 100644 drivers/bluetooth/aic_btusb/Makefile + create mode 100644 drivers/bluetooth/aic_btusb/aic_btusb.c + create mode 100644 drivers/bluetooth/aic_btusb/aic_btusb.h + create mode 100644 drivers/bluetooth/aic_btusb/aic_btusb_external_featrue.c + create mode 100644 drivers/bluetooth/aic_btusb/aic_btusb_external_featrue.h + +--- a/arch/riscv/configs/starfive_visionfive2_defconfig ++++ b/arch/riscv/configs/starfive_visionfive2_defconfig +@@ -87,8 +87,7 @@ CONFIG_BT_RFCOMM_TTY=y + CONFIG_BT_BNEP=y + CONFIG_BT_BNEP_MC_FILTER=y + CONFIG_BT_BNEP_PROTO_FILTER=y +-CONFIG_BT_HCIBTUSB=m +-# CONFIG_BT_HCIBTUSB_BCM is not set ++CONFIG_BT_AICUSB=y + CONFIG_CFG80211=y + CONFIG_MAC80211=y + CONFIG_RFKILL=y +--- a/drivers/bluetooth/Kconfig ++++ b/drivers/bluetooth/Kconfig +@@ -478,5 +478,9 @@ config BT_NXPUART + Say Y here to compile support for NXP Bluetooth UART device into + the kernel, or say M here to compile as a module (btnxpuart). + ++config BT_AICUSB ++ tristate "AIC8800 btusb driver" ++ help ++ AIC8800 usb dongle bluetooth support driver + + endmenu +--- a/drivers/bluetooth/Makefile ++++ b/drivers/bluetooth/Makefile +@@ -51,3 +51,4 @@ hci_uart-$(CONFIG_BT_HCIUART_QCA) += hci + hci_uart-$(CONFIG_BT_HCIUART_AG6XX) += hci_ag6xx.o + hci_uart-$(CONFIG_BT_HCIUART_MRVL) += hci_mrvl.o + hci_uart-objs := $(hci_uart-y) ++obj-$(CONFIG_BT_AICUSB) += aic_btusb/ +--- /dev/null ++++ b/drivers/bluetooth/aic_btusb/Makefile +@@ -0,0 +1,80 @@ ++MODULE_NAME = aic_btusb ++ ++CONFIG_AIC8800_BTUSB_SUPPORT = m ++CONFIG_SUPPORT_VENDOR_APCF = n ++# Need to set fw path in BOARD_KERNEL_CMDLINE ++CONFIG_USE_FW_REQUEST = n ++ ++ifeq ($(CONFIG_SUPPORT_VENDOR_APCF), y) ++obj-$(CONFIG_AIC8800_BTUSB_SUPPORT) := $(MODULE_NAME).o aic_btusb_external_featrue.o ++else ++obj-$(CONFIG_AIC8800_BTUSB_SUPPORT) := $(MODULE_NAME).o ++endif ++ ++ccflags-$(CONFIG_SUPPORT_VENDOR_APCF) += -DCONFIG_SUPPORT_VENDOR_APCF ++#$(MODULE_NAME)-y := aic_btusb_ioctl.o\ ++# aic_btusb.o \ ++ ++ccflags-$(CONFIG_USE_FW_REQUEST) += -DCONFIG_USE_FW_REQUEST ++ ++ ++# Platform support list ++CONFIG_PLATFORM_ROCKCHIP ?= n ++CONFIG_PLATFORM_ALLWINNER ?= n ++CONFIG_PLATFORM_AMLOGIC ?= n ++CONFIG_PLATFORM_UBUNTU ?= y ++CONFIG_PLATFORM_ING ?= n ++ ++ifeq ($(CONFIG_PLATFORM_ING), y) ++ARCH := mips ++KDIR ?= /home/yaya/E/Ingenic/T31/Ingenic-SDK-T31-1.1.5-20220428/opensource/kernel ++CROSS_COMPILE := /home/yaya/E/Ingenic/T31/Ingenic-SDK-T31-1.1.5-20220428/resource/toolchain/gcc_472/mips-gcc472-glibc216-32bit/bin/mips-linux-gnu- ++endif ++ ++ifeq ($(CONFIG_PLATFORM_ROCKCHIP), y) ++ccflags-$(CONFIG_PLATFORM_ROCKCHIP) += -DCONFIG_PLATFORM_ROCKCHIP ++#KDIR := /home/yaya/E/Rockchip/3229/Android9/rk3229_android9.0_box/kernel ++#ARCH ?= arm ++#CROSS_COMPILE ?= /home/yaya/E/Rockchip/3229/Android9/rk3229_android9.0_box/prebuilts/gcc/linux-x86/arm/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- ++#KDIR := /home/yaya/E/Rockchip/3229/Android7/RK3229_ANDROID7.1_v1.01_20170914/rk3229_Android7.1_v1.01_xml0914/kernel ++#ARCH ?= arm ++#CROSS_COMPILE ?= /home/yaya/E/Rockchip/3229/Android7/RK3229_ANDROID7.1_v1.01_20170914/rk3229_Android7.1_v1.01_xml0914/prebuilts/gcc/linux-x86/arm/arm-eabi-4.6/bin/arm-eabi- ++ARCH := arm64 ++KDIR ?= /home/yaya/E/Rockchip/3566/firefly/Android11.0/Firefly-RK356X_Android11.0_git_20210824/RK356X_Android11.0/kernel ++CROSS_COMPILE := /home/yaya/E/Rockchip/3566/firefly/Android11.0/Firefly-RK356X_Android11.0_git_20210824/RK356X_Android11.0/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu- ++ ++endif ++ ++ifeq ($(CONFIG_PLATFORM_ALLWINNER), y) ++ccflags-$(CONFIG_PLATFORM_ALLWINNER) += -DCONFIG_PLATFORM_ALLWINNER ++KDIR := /home/yaya/E/Allwinner/R818/R818/AndroidQ/lichee/kernel/linux-4.9 ++ARCH ?= arm64 ++CROSS_COMPILE ?= /home/yaya/E/Allwinner/R818/R818/AndroidQ/lichee/out/gcc-linaro-5.3.1-2016.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu- ++endif ++ ++ifeq ($(CONFIG_PLATFORM_AMLOGIC), y) ++ ccflags-$(CONFIG_PLATFORM_AMLOGIC) += -DCONFIG_PLATFORM_AMLOGIC ++endif ++ ++ifeq ($(CONFIG_PLATFORM_UBUNTU), y) ++ccflags-$(CONFIG_PLATFORM_UBUNTU) += -DCONFIG_PLATFORM_UBUNTU ++endif ++ ++all: modules ++modules: ++ make -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules ++ ++install: ++ mkdir -p $(MODDESTDIR) ++ install -p -m 644 $(MODULE_NAME).ko $(MODDESTDIR)/ ++ /sbin/depmod -a ${KVER} ++ echo $(MODULE_NAME) >> /etc/modules-load.d/aic_bt.conf ++ ++uninstall: ++ rm -rfv $(MODDESTDIR)/$(MODULE_NAME).ko ++ /sbin/depmod -a ${KVER} ++ rm -f /etc/modules-load.d/aic_bt.conf ++ ++clean: ++ rm -rf *.o *.ko *.o.* *.mod.* modules.* Module.* .a* .o* .*.o.* *.mod .tmp* .cache.mk .Module.symvers.cmd .modules.order.cmd ++ +--- /dev/null ++++ b/drivers/bluetooth/aic_btusb/aic_btusb.c +@@ -0,0 +1,5031 @@ ++/* ++ * ++ * AicSemi Bluetooth USB driver ++ * ++ * ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ * ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/init.h> ++#include <linux/slab.h> ++#include <linux/types.h> ++#include <linux/sched.h> ++#include <linux/errno.h> ++#include <linux/skbuff.h> ++#include <linux/usb.h> ++ ++#include <linux/ioctl.h> ++#include <linux/io.h> ++#include <linux/firmware.h> ++#include <linux/vmalloc.h> ++#include <linux/fs.h> ++#include <linux/uaccess.h> ++#include <linux/reboot.h> ++ ++#include "aic_btusb.h" ++ ++#ifdef CONFIG_USE_FW_REQUEST ++#include <linux/firmware.h> ++#endif ++ ++#define AICBT_RELEASE_NAME "202012_ANDROID" ++#define VERSION "2.1.0" ++ ++#define SUSPNED_DW_FW 0 ++ ++ ++static spinlock_t queue_lock; ++static spinlock_t dlfw_lock; ++static volatile uint16_t dlfw_dis_state = 0; ++ ++/* USB Device ID */ ++#define USB_VENDOR_ID_AIC 0xA69C ++#define USB_PRODUCT_ID_AIC8801 0x8801 ++#define USB_PRODUCT_ID_AIC8800DC 0x88dc ++#define USB_PRODUCT_ID_AIC8800D80 0x8d81 ++ ++enum AICWF_IC{ ++ PRODUCT_ID_AIC8801 = 0, ++ PRODUCT_ID_AIC8800DC, ++ PRODUCT_ID_AIC8800DW, ++ PRODUCT_ID_AIC8800D80 ++}; ++ ++u16 g_chipid = PRODUCT_ID_AIC8801; ++u8 chip_id = 0; ++u8 sub_chip_id = 0; ++ ++struct btusb_data { ++ struct hci_dev *hdev; ++ struct usb_device *udev; ++ struct usb_interface *intf; ++ struct usb_interface *isoc; ++ ++ spinlock_t lock; ++ ++ unsigned long flags; ++ ++ struct work_struct work; ++ struct work_struct waker; ++ ++ struct usb_anchor tx_anchor; ++ struct usb_anchor intr_anchor; ++ struct usb_anchor bulk_anchor; ++ struct usb_anchor isoc_anchor; ++ struct usb_anchor deferred; ++ int tx_in_flight; ++ spinlock_t txlock; ++ ++#if (CONFIG_BLUEDROID == 0) ++#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) ++ spinlock_t rxlock; ++ struct sk_buff *evt_skb; ++ struct sk_buff *acl_skb; ++ struct sk_buff *sco_skb; ++#endif ++#endif ++ ++ struct usb_endpoint_descriptor *intr_ep; ++ struct usb_endpoint_descriptor *bulk_tx_ep; ++ struct usb_endpoint_descriptor *bulk_rx_ep; ++ struct usb_endpoint_descriptor *isoc_tx_ep; ++ struct usb_endpoint_descriptor *isoc_rx_ep; ++ ++ __u8 cmdreq_type; ++ ++ unsigned int sco_num; ++ int isoc_altsetting; ++ int suspend_count; ++ uint16_t sco_handle; ++ ++#if (CONFIG_BLUEDROID == 0) ++#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) ++ int (*recv_bulk) (struct btusb_data * data, void *buffer, int count); ++#endif ++#endif ++ ++//#ifdef CONFIG_HAS_EARLYSUSPEND ++#if 0 ++ struct early_suspend early_suspend; ++#else ++ struct notifier_block pm_notifier; ++ struct notifier_block reboot_notifier; ++#endif ++ firmware_info *fw_info; ++ ++#ifdef CONFIG_SCO_OVER_HCI ++ AIC_sco_card_t *pSCOSnd; ++#endif ++}; ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 1) ++static bool reset_on_close = 0; ++#endif ++ ++#ifdef CONFIG_SCO_OVER_HCI ++struct snd_sco_cap_timer { ++ struct timer_list cap_timer; ++ struct timer_list play_timer; ++ struct btusb_data snd_usb_data; ++ int snd_sco_length; ++}; ++static struct snd_sco_cap_timer snd_cap_timer; ++#endif ++ ++ ++int bt_support = 0; ++module_param(bt_support, int, 0660); ++ ++#ifdef CONFIG_SUPPORT_VENDOR_APCF ++int vendor_apcf_sent_done = 0; ++#endif ++ ++static inline int check_set_dlfw_state_value(uint16_t change_value) ++{ ++ spin_lock(&dlfw_lock); ++ if(!dlfw_dis_state) { ++ dlfw_dis_state = change_value; ++ } ++ spin_unlock(&dlfw_lock); ++ return dlfw_dis_state; ++} ++ ++static inline void set_dlfw_state_value(uint16_t change_value) ++{ ++ spin_lock(&dlfw_lock); ++ dlfw_dis_state = change_value; ++ spin_unlock(&dlfw_lock); ++} ++ ++ ++ ++ ++static void aic_free( struct btusb_data *data) ++{ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 1) ++ kfree(data); ++#endif ++ return; ++} ++ ++static struct btusb_data *aic_alloc(struct usb_interface *intf) ++{ ++ struct btusb_data *data; ++#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 1) ++ data = kzalloc(sizeof(*data), GFP_KERNEL); ++#else ++ data = devm_kzalloc(&intf->dev, sizeof(*data), GFP_KERNEL); ++#endif ++ return data; ++} ++ ++static void print_acl(struct sk_buff *skb, int direction) ++{ ++#if PRINT_ACL_DATA ++ //uint wlength = skb->len; ++ u16 *handle = (u16 *)(skb->data); ++ u16 len = *(handle+1); ++ //u8 *acl_data = (u8 *)(skb->data); ++ ++ AICBT_INFO("aic %s: direction %d, handle %04x, len %d", ++ __func__, direction, *handle, len); ++#endif ++} ++ ++static void print_sco(struct sk_buff *skb, int direction) ++{ ++#if PRINT_SCO_DATA ++ uint wlength = skb->len; ++ u16 *handle = (u16 *)(skb->data); ++ u8 len = *(u8 *)(handle+1); ++ //u8 *sco_data =(u8 *)(skb->data); ++ ++ AICBT_INFO("aic %s: direction %d, handle %04x, len %d,wlength %d", ++ __func__, direction, *handle, len,wlength); ++#endif ++} ++ ++static void print_error_command(struct sk_buff *skb) ++{ ++ u16 *opcode = (u16*)(skb->data); ++ u8 *cmd_data = (u8*)(skb->data); ++ u8 len = *(cmd_data+2); ++ ++ printk(" 0x%04x,len:%d,", *opcode, len); ++#if CONFIG_BLUEDROID ++ switch (*opcode) { ++ case HCI_OP_INQUIRY: ++ printk("HCI_OP_INQUIRY"); ++ break; ++ case HCI_OP_INQUIRY_CANCEL: ++ printk("HCI_OP_INQUIRY_CANCEL"); ++ break; ++ case HCI_OP_EXIT_PERIODIC_INQ: ++ printk("HCI_OP_EXIT_PERIODIC_INQ"); ++ break; ++ case HCI_OP_CREATE_CONN: ++ printk("HCI_OP_CREATE_CONN"); ++ break; ++ case HCI_OP_DISCONNECT: ++ printk("HCI_OP_DISCONNECT"); ++ break; ++ case HCI_OP_CREATE_CONN_CANCEL: ++ printk("HCI_OP_CREATE_CONN_CANCEL"); ++ break; ++ case HCI_OP_ACCEPT_CONN_REQ: ++ printk("HCI_OP_ACCEPT_CONN_REQ"); ++ break; ++ case HCI_OP_REJECT_CONN_REQ: ++ printk("HCI_OP_REJECT_CONN_REQ"); ++ break; ++ case HCI_OP_AUTH_REQUESTED: ++ printk("HCI_OP_AUTH_REQUESTED"); ++ break; ++ case HCI_OP_SET_CONN_ENCRYPT: ++ printk("HCI_OP_SET_CONN_ENCRYPT"); ++ break; ++ case HCI_OP_REMOTE_NAME_REQ: ++ printk("HCI_OP_REMOTE_NAME_REQ"); ++ break; ++ case HCI_OP_READ_REMOTE_FEATURES: ++ printk("HCI_OP_READ_REMOTE_FEATURES"); ++ break; ++ case HCI_OP_SNIFF_MODE: ++ printk("HCI_OP_SNIFF_MODE"); ++ break; ++ case HCI_OP_EXIT_SNIFF_MODE: ++ printk("HCI_OP_EXIT_SNIFF_MODE"); ++ break; ++ case HCI_OP_SWITCH_ROLE: ++ printk("HCI_OP_SWITCH_ROLE"); ++ break; ++ case HCI_OP_SNIFF_SUBRATE: ++ printk("HCI_OP_SNIFF_SUBRATE"); ++ break; ++ case HCI_OP_RESET: ++ printk("HCI_OP_RESET"); ++ break; ++ case HCI_OP_Write_Extended_Inquiry_Response: ++ printk("HCI_Write_Extended_Inquiry_Response"); ++ break; ++ case HCI_OP_Write_Simple_Pairing_Mode: ++ printk("HCI_OP_Write_Simple_Pairing_Mode"); ++ break; ++ case HCI_OP_Read_Buffer_Size: ++ printk("HCI_OP_Read_Buffer_Size"); ++ break; ++ case HCI_OP_Host_Buffer_Size: ++ printk("HCI_OP_Host_Buffer_Size"); ++ break; ++ case HCI_OP_Read_Local_Version_Information: ++ printk("HCI_OP_Read_Local_Version_Information"); ++ break; ++ case HCI_OP_Read_BD_ADDR: ++ printk("HCI_OP_Read_BD_ADDR"); ++ break; ++ case HCI_OP_Read_Local_Supported_Commands: ++ printk("HCI_OP_Read_Local_Supported_Commands"); ++ break; ++ case HCI_OP_Write_Scan_Enable: ++ printk("HCI_OP_Write_Scan_Enable"); ++ break; ++ case HCI_OP_Write_Current_IAC_LAP: ++ printk("HCI_OP_Write_Current_IAC_LAP"); ++ break; ++ case HCI_OP_Write_Inquiry_Scan_Activity: ++ printk("HCI_OP_Write_Inquiry_Scan_Activity"); ++ break; ++ case HCI_OP_Write_Class_of_Device: ++ printk("HCI_OP_Write_Class_of_Device"); ++ break; ++ case HCI_OP_LE_Rand: ++ printk("HCI_OP_LE_Rand"); ++ break; ++ case HCI_OP_LE_Set_Random_Address: ++ printk("HCI_OP_LE_Set_Random_Address"); ++ break; ++ case HCI_OP_LE_Set_Extended_Scan_Enable: ++ printk("HCI_OP_LE_Set_Extended_Scan_Enable"); ++ break; ++ case HCI_OP_LE_Set_Extended_Scan_Parameters: ++ printk("HCI_OP_LE_Set_Extended_Scan_Parameters"); ++ break; ++ case HCI_OP_Set_Event_Filter: ++ printk("HCI_OP_Set_Event_Filter"); ++ break; ++ case HCI_OP_Write_Voice_Setting: ++ printk("HCI_OP_Write_Voice_Setting"); ++ break; ++ case HCI_OP_Change_Local_Name: ++ printk("HCI_OP_Change_Local_Name"); ++ break; ++ case HCI_OP_Read_Local_Name: ++ printk("HCI_OP_Read_Local_Name"); ++ break; ++ case HCI_OP_Wirte_Page_Timeout: ++ printk("HCI_OP_Wirte_Page_Timeout"); ++ break; ++ case HCI_OP_LE_Clear_Resolving_List: ++ printk("HCI_OP_LE_Clear_Resolving_List"); ++ break; ++ case HCI_OP_LE_Set_Addres_Resolution_Enable_Command: ++ printk("HCI_OP_LE_Set_Addres_Resolution_Enable_Command"); ++ break; ++ case HCI_OP_Write_Inquiry_mode: ++ printk("HCI_OP_Write_Inquiry_mode"); ++ break; ++ case HCI_OP_Write_Page_Scan_Type: ++ printk("HCI_OP_Write_Page_Scan_Type"); ++ break; ++ case HCI_OP_Write_Inquiry_Scan_Type: ++ printk("HCI_OP_Write_Inquiry_Scan_Type"); ++ break; ++ case HCI_OP_Delete_Stored_Link_Key: ++ printk("HCI_OP_Delete_Stored_Link_Key"); ++ break; ++ case HCI_OP_LE_Read_Local_Resolvable_Address: ++ printk("HCI_OP_LE_Read_Local_Resolvable_Address"); ++ break; ++ case HCI_OP_LE_Extended_Create_Connection: ++ printk("HCI_OP_LE_Extended_Create_Connection"); ++ break; ++ case HCI_OP_Read_Remote_Version_Information: ++ printk("HCI_OP_Read_Remote_Version_Information"); ++ break; ++ case HCI_OP_LE_Start_Encryption: ++ printk("HCI_OP_LE_Start_Encryption"); ++ break; ++ case HCI_OP_LE_Add_Device_to_Resolving_List: ++ printk("HCI_OP_LE_Add_Device_to_Resolving_List"); ++ break; ++ case HCI_OP_LE_Set_Privacy_Mode: ++ printk("HCI_OP_LE_Set_Privacy_Mode"); ++ break; ++ case HCI_OP_LE_Connection_Update: ++ printk("HCI_OP_LE_Connection_Update"); ++ break; ++ default: ++ printk("UNKNOW_HCI_COMMAND"); ++ break; ++ } ++#endif //CONFIG_BLUEDROID ++} ++ ++static void print_command(struct sk_buff *skb) ++{ ++#if PRINT_CMD_EVENT ++ print_error_command(skb); ++#endif ++} ++ ++ ++enum CODEC_TYPE{ ++ CODEC_CVSD, ++ CODEC_MSBC, ++}; ++ ++static enum CODEC_TYPE codec_type = CODEC_CVSD; ++static void set_select_msbc(enum CODEC_TYPE type); ++static enum CODEC_TYPE check_select_msbc(void); ++ ++ ++#if CONFIG_BLUEDROID ++ ++/* Global parameters for bt usb char driver */ ++#define BT_CHAR_DEVICE_NAME "aicbt_dev" ++struct mutex btchr_mutex; ++static struct sk_buff_head btchr_readq; ++static wait_queue_head_t btchr_read_wait; ++static wait_queue_head_t bt_dlfw_wait; ++static int bt_char_dev_registered; ++static dev_t bt_devid; /* bt char device number */ ++static struct cdev bt_char_dev; /* bt character device structure */ ++static struct class *bt_char_class; /* device class for usb char driver */ ++static int bt_reset = 0; ++ ++/* HCI device & lock */ ++DEFINE_RWLOCK(hci_dev_lock); ++struct hci_dev *ghdev = NULL; ++ ++#ifdef CONFIG_SUPPORT_VENDOR_APCF ++static int bypass_event(struct sk_buff *skb) ++{ ++ int ret = 0; ++ u8 *opcode = (u8*)(skb->data); ++ //u8 len = *(opcode+1); ++ u16 sub_opcpde; ++ ++ switch(*opcode) { ++ case HCI_EV_CMD_COMPLETE: ++ sub_opcpde = ((u16)opcode[3]|(u16)(opcode[4])<<8); ++ if(sub_opcpde == 0xfd57){ ++ if(vendor_apcf_sent_done){ ++ vendor_apcf_sent_done--; ++ printk("apcf bypass\r\n"); ++ ret = 1; ++ } ++ } ++ break; ++ default: ++ break; ++ } ++ return ret; ++} ++#endif//CONFIG_SUPPORT_VENDOR_APCF ++static void print_event(struct sk_buff *skb) ++{ ++#if PRINT_CMD_EVENT ++ //uint wlength = skb->len; ++ //uint icount = 0; ++ u8 *opcode = (u8*)(skb->data); ++ //u8 len = *(opcode+1); ++ ++ printk("aic %s ", __func__); ++ switch (*opcode) { ++ case HCI_EV_INQUIRY_COMPLETE: ++ printk("HCI_EV_INQUIRY_COMPLETE"); ++ break; ++ case HCI_EV_INQUIRY_RESULT: ++ printk("HCI_EV_INQUIRY_RESULT"); ++ break; ++ case HCI_EV_CONN_COMPLETE: ++ printk("HCI_EV_CONN_COMPLETE"); ++ break; ++ case HCI_EV_CONN_REQUEST: ++ printk("HCI_EV_CONN_REQUEST"); ++ break; ++ case HCI_EV_DISCONN_COMPLETE: ++ printk("HCI_EV_DISCONN_COMPLETE"); ++ break; ++ case HCI_EV_AUTH_COMPLETE: ++ printk("HCI_EV_AUTH_COMPLETE"); ++ break; ++ case HCI_EV_REMOTE_NAME: ++ printk("HCI_EV_REMOTE_NAME"); ++ break; ++ case HCI_EV_ENCRYPT_CHANGE: ++ printk("HCI_EV_ENCRYPT_CHANGE"); ++ break; ++ case HCI_EV_CHANGE_LINK_KEY_COMPLETE: ++ printk("HCI_EV_CHANGE_LINK_KEY_COMPLETE"); ++ break; ++ case HCI_EV_REMOTE_FEATURES: ++ printk("HCI_EV_REMOTE_FEATURES"); ++ break; ++ case HCI_EV_REMOTE_VERSION: ++ printk("HCI_EV_REMOTE_VERSION"); ++ break; ++ case HCI_EV_QOS_SETUP_COMPLETE: ++ printk("HCI_EV_QOS_SETUP_COMPLETE"); ++ break; ++ case HCI_EV_CMD_COMPLETE: ++ printk("HCI_EV_CMD_COMPLETE"); ++ break; ++ case HCI_EV_CMD_STATUS: ++ printk("HCI_EV_CMD_STATUS"); ++ break; ++ case HCI_EV_ROLE_CHANGE: ++ printk("HCI_EV_ROLE_CHANGE"); ++ break; ++ case HCI_EV_NUM_COMP_PKTS: ++ printk("HCI_EV_NUM_COMP_PKTS"); ++ break; ++ case HCI_EV_MODE_CHANGE: ++ printk("HCI_EV_MODE_CHANGE"); ++ break; ++ case HCI_EV_PIN_CODE_REQ: ++ printk("HCI_EV_PIN_CODE_REQ"); ++ break; ++ case HCI_EV_LINK_KEY_REQ: ++ printk("HCI_EV_LINK_KEY_REQ"); ++ break; ++ case HCI_EV_LINK_KEY_NOTIFY: ++ printk("HCI_EV_LINK_KEY_NOTIFY"); ++ break; ++ case HCI_EV_CLOCK_OFFSET: ++ printk("HCI_EV_CLOCK_OFFSET"); ++ break; ++ case HCI_EV_PKT_TYPE_CHANGE: ++ printk("HCI_EV_PKT_TYPE_CHANGE"); ++ break; ++ case HCI_EV_PSCAN_REP_MODE: ++ printk("HCI_EV_PSCAN_REP_MODE"); ++ break; ++ case HCI_EV_INQUIRY_RESULT_WITH_RSSI: ++ printk("HCI_EV_INQUIRY_RESULT_WITH_RSSI"); ++ break; ++ case HCI_EV_REMOTE_EXT_FEATURES: ++ printk("HCI_EV_REMOTE_EXT_FEATURES"); ++ break; ++ case HCI_EV_SYNC_CONN_COMPLETE: ++ printk("HCI_EV_SYNC_CONN_COMPLETE"); ++ break; ++ case HCI_EV_SYNC_CONN_CHANGED: ++ printk("HCI_EV_SYNC_CONN_CHANGED"); ++ break; ++ case HCI_EV_SNIFF_SUBRATE: ++ printk("HCI_EV_SNIFF_SUBRATE"); ++ break; ++ case HCI_EV_EXTENDED_INQUIRY_RESULT: ++ printk("HCI_EV_EXTENDED_INQUIRY_RESULT"); ++ break; ++ case HCI_EV_IO_CAPA_REQUEST: ++ printk("HCI_EV_IO_CAPA_REQUEST"); ++ break; ++ case HCI_EV_SIMPLE_PAIR_COMPLETE: ++ printk("HCI_EV_SIMPLE_PAIR_COMPLETE"); ++ break; ++ case HCI_EV_REMOTE_HOST_FEATURES: ++ printk("HCI_EV_REMOTE_HOST_FEATURES"); ++ break; ++ default: ++ printk("unknow event"); ++ break; ++ } ++ printk("\n"); ++#if 0 ++ printk("%02x,len:%d,", *opcode,len); ++ for (icount = 2; (icount < wlength) && (icount < 24); icount++) ++ printk("%02x ", *(opcode+icount)); ++ printk("\n"); ++#endif ++#endif ++} ++ ++static inline ssize_t usb_put_user(struct sk_buff *skb, ++ char __user *buf, int count) ++{ ++ char __user *ptr = buf; ++ int len = min_t(unsigned int, skb->len, count); ++ ++ if (copy_to_user(ptr, skb->data, len)) ++ return -EFAULT; ++ ++ return len; ++} ++ ++static struct sk_buff *aic_skb_queue[QUEUE_SIZE]; ++static int aic_skb_queue_front = 0; ++static int aic_skb_queue_rear = 0; ++ ++static void aic_enqueue(struct sk_buff *skb) ++{ ++ spin_lock(&queue_lock); ++ if (aic_skb_queue_front == (aic_skb_queue_rear + 1) % QUEUE_SIZE) { ++ /* ++ * If queue is full, current solution is to drop ++ * the following entries. ++ */ ++ AICBT_WARN("%s: Queue is full, entry will be dropped", __func__); ++ } else { ++ aic_skb_queue[aic_skb_queue_rear] = skb; ++ ++ aic_skb_queue_rear++; ++ aic_skb_queue_rear %= QUEUE_SIZE; ++ ++ } ++ spin_unlock(&queue_lock); ++} ++ ++static struct sk_buff *aic_dequeue_try(unsigned int deq_len) ++{ ++ struct sk_buff *skb; ++ struct sk_buff *skb_copy; ++ ++ if (aic_skb_queue_front == aic_skb_queue_rear) { ++ AICBT_WARN("%s: Queue is empty", __func__); ++ return NULL; ++ } ++ ++ skb = aic_skb_queue[aic_skb_queue_front]; ++ if (deq_len >= skb->len) { ++ ++ aic_skb_queue_front++; ++ aic_skb_queue_front %= QUEUE_SIZE; ++ ++ /* ++ * Return skb addr to be dequeued, and the caller ++ * should free the skb eventually. ++ */ ++ return skb; ++ } else { ++ skb_copy = pskb_copy(skb, GFP_ATOMIC); ++ skb_pull(skb, deq_len); ++ /* Return its copy to be freed */ ++ return skb_copy; ++ } ++} ++ ++static inline int is_queue_empty(void) ++{ ++ return (aic_skb_queue_front == aic_skb_queue_rear) ? 1 : 0; ++} ++ ++static void aic_clear_queue(void) ++{ ++ struct sk_buff *skb; ++ spin_lock(&queue_lock); ++ while(!is_queue_empty()) { ++ skb = aic_skb_queue[aic_skb_queue_front]; ++ aic_skb_queue[aic_skb_queue_front] = NULL; ++ aic_skb_queue_front++; ++ aic_skb_queue_front %= QUEUE_SIZE; ++ if (skb) { ++ kfree_skb(skb); ++ } ++ } ++ spin_unlock(&queue_lock); ++} ++ ++/* ++ * AicSemi - Integrate from hci_core.c ++ */ ++ ++/* Get HCI device by index. ++ * Device is held on return. */ ++static struct hci_dev *hci_dev_get(int index) ++{ ++ if (index != 0) ++ return NULL; ++ ++ return ghdev; ++} ++ ++/* ---- HCI ioctl helpers ---- */ ++static int hci_dev_open(__u16 dev) ++{ ++ struct hci_dev *hdev; ++ int ret = 0; ++ ++ AICBT_DBG("%s: dev %d", __func__, dev); ++ ++ hdev = hci_dev_get(dev); ++ if (!hdev) { ++ AICBT_ERR("%s: Failed to get hci dev[Null]", __func__); ++ return -ENODEV; ++ } ++ ++ if (test_bit(HCI_UNREGISTER, &hdev->dev_flags)) { ++ ret = -ENODEV; ++ goto done; ++ } ++ ++ if (test_bit(HCI_UP, &hdev->flags)) { ++ ret = -EALREADY; ++ goto done; ++ } ++ ++done: ++ return ret; ++} ++ ++static int hci_dev_do_close(struct hci_dev *hdev) ++{ ++ if (hdev->flush) ++ hdev->flush(hdev); ++ /* After this point our queues are empty ++ * and no tasks are scheduled. */ ++ hdev->close(hdev); ++ /* Clear flags */ ++ hdev->flags = 0; ++ return 0; ++} ++ ++static int hci_dev_close(__u16 dev) ++{ ++ struct hci_dev *hdev; ++ int err; ++ hdev = hci_dev_get(dev); ++ if (!hdev) { ++ AICBT_ERR("%s: failed to get hci dev[Null]", __func__); ++ return -ENODEV; ++ } ++ ++ err = hci_dev_do_close(hdev); ++ ++ return err; ++} ++ ++#ifdef CONFIG_SCO_OVER_HCI ++/* copy data from the URB buffer into the ALSA ring buffer */ ++static bool aic_copy_capture_data_to_alsa(struct btusb_data *data, uint8_t* p_data, unsigned int frames) ++{ ++ struct snd_pcm_runtime *runtime; ++ unsigned int frame_bytes, frames1; ++ u8 *dest; ++ AIC_sco_card_t *pSCOSnd = data->pSCOSnd; ++ ++ runtime = pSCOSnd->capture.substream->runtime; ++ frame_bytes = 2; ++ ++ dest = runtime->dma_area + pSCOSnd->capture.buffer_pos * frame_bytes; ++ if (pSCOSnd->capture.buffer_pos + frames <= runtime->buffer_size) { ++ memcpy(dest, p_data, frames * frame_bytes); ++ } else { ++ /* wrap around at end of ring buffer */ ++ frames1 = runtime->buffer_size - pSCOSnd->capture.buffer_pos; ++ memcpy(dest, p_data, frames1 * frame_bytes); ++ memcpy(runtime->dma_area, ++ p_data + frames1 * frame_bytes, ++ (frames - frames1) * frame_bytes); ++ } ++ ++ pSCOSnd->capture.buffer_pos += frames; ++ if (pSCOSnd->capture.buffer_pos >= runtime->buffer_size) { ++ pSCOSnd->capture.buffer_pos -= runtime->buffer_size; ++ } ++ ++ if((pSCOSnd->capture.buffer_pos%runtime->period_size) == 0) { ++ snd_pcm_period_elapsed(pSCOSnd->capture.substream); ++ } ++ ++ return false; ++} ++ ++ ++static void hci_send_to_alsa_ringbuffer(struct hci_dev *hdev, struct sk_buff *skb) ++{ ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ AIC_sco_card_t *pSCOSnd = data->pSCOSnd; ++ uint8_t* p_data; ++ int sco_length = skb->len - HCI_SCO_HDR_SIZE; ++ u16 *handle = (u16 *) (skb->data); ++ //u8 errflg = (u8)((*handle & 0x3000) >> 12); ++ ++ pSCOSnd->usb_data->sco_handle = (*handle & 0x0fff); ++ ++ AICBT_DBG("%s, %x, %x %x\n", __func__,pSCOSnd->usb_data->sco_handle, *handle, errflg); ++ ++ if (!hdev) { ++ AICBT_INFO("%s: Frame for unknown HCI device", __func__); ++ return; ++ } ++ ++ if (!test_bit(ALSA_CAPTURE_RUNNING, &pSCOSnd->states)) { ++ AICBT_INFO("%s: ALSA is not running", __func__); ++ return; ++ } ++ snd_cap_timer.snd_sco_length = sco_length; ++ p_data = (uint8_t *)skb->data + HCI_SCO_HDR_SIZE; ++ aic_copy_capture_data_to_alsa(data, p_data, sco_length/2); ++} ++ ++#endif ++ ++#if CONFIG_BLUEDROID ++static struct hci_dev *hci_alloc_dev(void) ++{ ++ struct hci_dev *hdev; ++ ++ hdev = kzalloc(sizeof(struct hci_dev), GFP_KERNEL); ++ if (!hdev) ++ return NULL; ++ ++ return hdev; ++} ++ ++/* Free HCI device */ ++static void hci_free_dev(struct hci_dev *hdev) ++{ ++ kfree(hdev); ++} ++ ++/* Register HCI device */ ++static int hci_register_dev(struct hci_dev *hdev) ++{ ++ int i, id; ++ ++ AICBT_DBG("%s: %p name %s bus %d", __func__, hdev, hdev->name, hdev->bus); ++ /* Do not allow HCI_AMP devices to register at index 0, ++ * so the index can be used as the AMP controller ID. ++ */ ++ id = (hdev->dev_type == HCI_BREDR) ? 0 : 1; ++ ++ write_lock(&hci_dev_lock); ++ ++ sprintf(hdev->name, "hci%d", id); ++ hdev->id = id; ++ hdev->flags = 0; ++ hdev->dev_flags = 0; ++ mutex_init(&hdev->lock); ++ ++ AICBT_DBG("%s: id %d, name %s", __func__, hdev->id, hdev->name); ++ ++ ++ for (i = 0; i < NUM_REASSEMBLY; i++) ++ hdev->reassembly[i] = NULL; ++ ++ memset(&hdev->stat, 0, sizeof(struct hci_dev_stats)); ++ atomic_set(&hdev->promisc, 0); ++ ++ if (ghdev) { ++ write_unlock(&hci_dev_lock); ++ AICBT_ERR("%s: Hci device has been registered already", __func__); ++ return -1; ++ } else ++ ghdev = hdev; ++ ++ write_unlock(&hci_dev_lock); ++ ++ return id; ++} ++ ++/* Unregister HCI device */ ++static void hci_unregister_dev(struct hci_dev *hdev) ++{ ++ int i; ++ ++ AICBT_DBG("%s: hdev %p name %s bus %d", __func__, hdev, hdev->name, hdev->bus); ++ set_bit(HCI_UNREGISTER, &hdev->dev_flags); ++ ++ write_lock(&hci_dev_lock); ++ ghdev = NULL; ++ write_unlock(&hci_dev_lock); ++ ++ hci_dev_do_close(hdev); ++ for (i = 0; i < NUM_REASSEMBLY; i++) ++ kfree_skb(hdev->reassembly[i]); ++} ++ ++static void hci_send_to_stack(struct hci_dev *hdev, struct sk_buff *skb) ++{ ++ struct sk_buff *aic_skb_copy = NULL; ++ ++ //AICBT_DBG("%s", __func__); ++ ++ if (!hdev) { ++ AICBT_ERR("%s: Frame for unknown HCI device", __func__); ++ return; ++ } ++ ++ if (!test_bit(HCI_RUNNING, &hdev->flags)) { ++ AICBT_ERR("%s: HCI not running", __func__); ++ return; ++ } ++ ++ aic_skb_copy = pskb_copy(skb, GFP_ATOMIC); ++ if (!aic_skb_copy) { ++ AICBT_ERR("%s: Copy skb error", __func__); ++ return; ++ } ++ ++ memcpy(skb_push(aic_skb_copy, 1), &bt_cb(skb)->pkt_type, 1); ++ aic_enqueue(aic_skb_copy); ++ ++ /* Make sure bt char device existing before wakeup read queue */ ++ hdev = hci_dev_get(0); ++ if (hdev) { ++ //AICBT_DBG("%s: Try to wakeup read queue", __func__); ++ AICBT_DBG("%s", __func__); ++ wake_up_interruptible(&btchr_read_wait); ++ } ++ ++ ++ return; ++} ++ ++/* Receive frame from HCI drivers */ ++static int hci_recv_frame(struct sk_buff *skb) ++{ ++ struct hci_dev *hdev = (struct hci_dev *) skb->dev; ++ ++ if (!hdev || ++ (!test_bit(HCI_UP, &hdev->flags) && !test_bit(HCI_INIT, &hdev->flags))) { ++ kfree_skb(skb); ++ return -ENXIO; ++ } ++ ++ /* Incomming skb */ ++ bt_cb(skb)->incoming = 1; ++ ++ /* Time stamp */ ++ __net_timestamp(skb); ++ ++ if (atomic_read(&hdev->promisc)) { ++#ifdef CONFIG_SCO_OVER_HCI ++ if(bt_cb(skb)->pkt_type == HCI_SCODATA_PKT){ ++ hci_send_to_alsa_ringbuffer(hdev, skb); ++ }else{ ++#ifdef CONFIG_SUPPORT_VENDOR_APCF ++ if(bt_cb(skb)->pkt_type == HCI_EVENT_PKT){ ++ if(bypass_event(skb)){ ++ kfree_skb(skb); ++ return 0; ++ } ++ } ++#endif //CONFIG_SUPPORT_VENDOR_APCF ++ hci_send_to_stack(hdev, skb); ++ } ++#else ++#ifdef CONFIG_SUPPORT_VENDOR_APCF ++ if(bt_cb(skb)->pkt_type == HCI_EVENT_PKT){ ++ if(bypass_event(skb)){ ++ kfree_skb(skb); ++ return 0; ++ } ++ } ++#endif //CONFIG_SUPPORT_VENDOR_APCF ++ /* Send copy to the sockets */ ++ hci_send_to_stack(hdev, skb); ++#endif ++ ++ } ++ ++ kfree_skb(skb); ++ return 0; ++} ++ ++ ++ ++static int hci_reassembly(struct hci_dev *hdev, int type, void *data, ++ int count, __u8 index) ++{ ++ int len = 0; ++ int hlen = 0; ++ int remain = count; ++ struct sk_buff *skb; ++ struct bt_skb_cb *scb; ++ ++ //AICBT_DBG("%s", __func__); ++ ++ if ((type < HCI_ACLDATA_PKT || type > HCI_EVENT_PKT) || ++ index >= NUM_REASSEMBLY) ++ return -EILSEQ; ++ ++ skb = hdev->reassembly[index]; ++ ++ if (!skb) { ++ switch (type) { ++ case HCI_ACLDATA_PKT: ++ len = HCI_MAX_FRAME_SIZE; ++ hlen = HCI_ACL_HDR_SIZE; ++ break; ++ case HCI_EVENT_PKT: ++ len = HCI_MAX_EVENT_SIZE; ++ hlen = HCI_EVENT_HDR_SIZE; ++ break; ++ case HCI_SCODATA_PKT: ++ len = HCI_MAX_SCO_SIZE; ++ hlen = HCI_SCO_HDR_SIZE; ++ break; ++ } ++ ++ skb = bt_skb_alloc(len, GFP_ATOMIC); ++ if (!skb) ++ return -ENOMEM; ++ ++ scb = (void *) skb->cb; ++ scb->expect = hlen; ++ scb->pkt_type = type; ++ ++ skb->dev = (void *) hdev; ++ hdev->reassembly[index] = skb; ++ } ++ ++ while (count) { ++ scb = (void *) skb->cb; ++ len = min_t(uint, scb->expect, count); ++ ++ memcpy(skb_put(skb, len), data, len); ++ ++ count -= len; ++ data += len; ++ scb->expect -= len; ++ remain = count; ++ ++ switch (type) { ++ case HCI_EVENT_PKT: ++ if (skb->len == HCI_EVENT_HDR_SIZE) { ++ struct hci_event_hdr *h = hci_event_hdr(skb); ++ scb->expect = h->plen; ++ ++ if (skb_tailroom(skb) < scb->expect) { ++ kfree_skb(skb); ++ hdev->reassembly[index] = NULL; ++ return -ENOMEM; ++ } ++ } ++ break; ++ ++ case HCI_ACLDATA_PKT: ++ if (skb->len == HCI_ACL_HDR_SIZE) { ++ struct hci_acl_hdr *h = hci_acl_hdr(skb); ++ scb->expect = __le16_to_cpu(h->dlen); ++ ++ if (skb_tailroom(skb) < scb->expect) { ++ kfree_skb(skb); ++ hdev->reassembly[index] = NULL; ++ return -ENOMEM; ++ } ++ } ++ break; ++ ++ case HCI_SCODATA_PKT: ++ if (skb->len == HCI_SCO_HDR_SIZE) { ++ struct hci_sco_hdr *h = hci_sco_hdr(skb); ++ scb->expect = h->dlen; ++ ++ if (skb_tailroom(skb) < scb->expect) { ++ kfree_skb(skb); ++ hdev->reassembly[index] = NULL; ++ return -ENOMEM; ++ } ++ } ++ break; ++ } ++ ++ if (scb->expect == 0) { ++ /* Complete frame */ ++ if(HCI_ACLDATA_PKT == type) ++ print_acl(skb,0); ++ if(HCI_SCODATA_PKT == type) ++ print_sco(skb,0); ++ if(HCI_EVENT_PKT == type) ++ print_event(skb); ++ ++ bt_cb(skb)->pkt_type = type; ++ hci_recv_frame(skb); ++ ++ hdev->reassembly[index] = NULL; ++ return remain; ++ } ++ } ++ ++ return remain; ++} ++ ++static int hci_recv_fragment(struct hci_dev *hdev, int type, void *data, int count) ++{ ++ int rem = 0; ++ ++ if (type < HCI_ACLDATA_PKT || type > HCI_EVENT_PKT) ++ return -EILSEQ; ++ ++ while (count) { ++ rem = hci_reassembly(hdev, type, data, count, type - 1); ++ if (rem < 0) ++ return rem; ++ ++ data += (count - rem); ++ count = rem; ++ } ++ ++ return rem; ++} ++#endif //CONFIG_BLUEDROID ++ ++void hci_hardware_error(void) ++{ ++ struct sk_buff *aic_skb_copy = NULL; ++ int len = 3; ++ uint8_t hardware_err_pkt[4] = {HCI_EVENT_PKT, 0x10, 0x01, HCI_VENDOR_USB_DISC_HARDWARE_ERROR}; ++ ++ aic_skb_copy = alloc_skb(len, GFP_ATOMIC); ++ if (!aic_skb_copy) { ++ AICBT_ERR("%s: Failed to allocate mem", __func__); ++ return; ++ } ++ ++ memcpy(skb_put(aic_skb_copy, len), hardware_err_pkt, len); ++ aic_enqueue(aic_skb_copy); ++ ++ wake_up_interruptible(&btchr_read_wait); ++} ++ ++static int btchr_open(struct inode *inode_p, struct file *file_p) ++{ ++ struct btusb_data *data; ++ struct hci_dev *hdev; ++ ++ AICBT_DBG("%s: BT usb char device is opening", __func__); ++ /* Not open unless wanna tracing log */ ++ /* trace_printk("%s: open....\n", __func__); */ ++ ++ hdev = hci_dev_get(0); ++ if (!hdev) { ++ AICBT_DBG("%s: Failed to get hci dev[NULL]", __func__); ++ return -ENODEV; ++ } ++ data = GET_DRV_DATA(hdev); ++ ++ atomic_inc(&hdev->promisc); ++ /* ++ * As bt device is not re-opened when hotplugged out, we cannot ++ * trust on file's private data(may be null) when other file ops ++ * are invoked. ++ */ ++ file_p->private_data = data; ++ ++ mutex_lock(&btchr_mutex); ++ hci_dev_open(0); ++ mutex_unlock(&btchr_mutex); ++ ++ aic_clear_queue(); ++ return nonseekable_open(inode_p, file_p); ++} ++ ++static int btchr_close(struct inode *inode_p, struct file *file_p) ++{ ++ struct btusb_data *data; ++ struct hci_dev *hdev; ++ ++ AICBT_INFO("%s: BT usb char device is closing", __func__); ++ /* Not open unless wanna tracing log */ ++ /* trace_printk("%s: close....\n", __func__); */ ++ ++ data = file_p->private_data; ++ file_p->private_data = NULL; ++ ++#if CONFIG_BLUEDROID ++ /* ++ * If the upper layer closes bt char interfaces, no reset ++ * action required even bt device hotplugged out. ++ */ ++ bt_reset = 0; ++#endif ++ ++ hdev = hci_dev_get(0); ++ if (hdev) { ++ atomic_set(&hdev->promisc, 0); ++ mutex_lock(&btchr_mutex); ++ hci_dev_close(0); ++ mutex_unlock(&btchr_mutex); ++ } ++ ++ return 0; ++} ++ ++static ssize_t btchr_read(struct file *file_p, ++ char __user *buf_p, ++ size_t count, ++ loff_t *pos_p) ++{ ++ struct hci_dev *hdev; ++ struct sk_buff *skb; ++ ssize_t ret = 0; ++ ++ while (count) { ++ hdev = hci_dev_get(0); ++ if (!hdev) { ++ /* ++ * Note: Only when BT device hotplugged out, we wil get ++ * into such situation. In order to keep the upper layer ++ * stack alive (blocking the read), we should never return ++ * EFAULT or break the loop. ++ */ ++ AICBT_ERR("%s: Failed to get hci dev[Null]", __func__); ++ } ++ ++ ret = wait_event_interruptible(btchr_read_wait, !is_queue_empty()); ++ if (ret < 0) { ++ AICBT_ERR("%s: wait event is signaled %d", __func__, (int)ret); ++ break; ++ } ++ ++ skb = aic_dequeue_try(count); ++ if (skb) { ++ ret = usb_put_user(skb, buf_p, count); ++ if (ret < 0) ++ AICBT_ERR("%s: Failed to put data to user space", __func__); ++ kfree_skb(skb); ++ break; ++ } ++ } ++ ++ return ret; ++} ++ ++#ifdef CONFIG_SUPPORT_VENDOR_APCF ++void btchr_external_write(char* buff, int len){ ++ struct hci_dev *hdev; ++ struct sk_buff *skb; ++ int i; ++ struct btusb_data *data; ++ ++ AICBT_INFO("%s \r\n", __func__); ++ for(i=0;i<len;i++){ ++ printk("0x%x ",(u8)buff[i]); ++ } ++ printk("\r\n"); ++ hdev = hci_dev_get(0); ++ if (!hdev) { ++ AICBT_WARN("%s: Failed to get hci dev[Null]", __func__); ++ return; ++ } ++ /* Never trust on btusb_data, as bt device may be hotplugged out */ ++ data = GET_DRV_DATA(hdev); ++ if (!data) { ++ AICBT_WARN("%s: Failed to get bt usb driver data[Null]", __func__); ++ return; ++ } ++ vendor_apcf_sent_done++; ++ ++ skb = bt_skb_alloc(len, GFP_ATOMIC); ++ if (!skb) ++ return; ++ skb_reserve(skb, -1); // Add this line ++ skb->dev = (void *)hdev; ++ memcpy((__u8 *)skb->data,(__u8 *)buff,len); ++ skb_put(skb, len); ++ bt_cb(skb)->pkt_type = *((__u8 *)skb->data); ++ skb_pull(skb, 1); ++ data->hdev->send(skb); ++} ++ ++EXPORT_SYMBOL(btchr_external_write); ++#endif //CONFIG_SUPPORT_VENDOR_APCF ++ ++static ssize_t btchr_write(struct file *file_p, ++ const char __user *buf_p, ++ size_t count, ++ loff_t *pos_p) ++{ ++ struct btusb_data *data = file_p->private_data; ++ struct hci_dev *hdev; ++ struct sk_buff *skb; ++ ++ //AICBT_DBG("%s: BT usb char device is writing", __func__); ++ AICBT_DBG("%s", __func__); ++ ++ hdev = hci_dev_get(0); ++ if (!hdev) { ++ AICBT_WARN("%s: Failed to get hci dev[Null]", __func__); ++ /* ++ * Note: we bypass the data from the upper layer if bt device ++ * is hotplugged out. Fortunatelly, H4 or H5 HCI stack does ++ * NOT check btchr_write's return value. However, returning ++ * count instead of EFAULT is preferable. ++ */ ++ /* return -EFAULT; */ ++ return count; ++ } ++ ++ /* Never trust on btusb_data, as bt device may be hotplugged out */ ++ data = GET_DRV_DATA(hdev); ++ if (!data) { ++ AICBT_WARN("%s: Failed to get bt usb driver data[Null]", __func__); ++ return count; ++ } ++ ++ if (count > HCI_MAX_FRAME_SIZE) ++ return -EINVAL; ++ ++ skb = bt_skb_alloc(count, GFP_ATOMIC); ++ if (!skb) ++ return -ENOMEM; ++ skb_reserve(skb, -1); // Add this line ++ ++ if (copy_from_user(skb_put(skb, count), buf_p, count)) { ++ AICBT_ERR("%s: Failed to get data from user space", __func__); ++ kfree_skb(skb); ++ return -EFAULT; ++ } ++ ++ skb->dev = (void *)hdev; ++ bt_cb(skb)->pkt_type = *((__u8 *)skb->data); ++ skb_pull(skb, 1); ++ data->hdev->send(skb); ++ ++ return count; ++} ++ ++static unsigned int btchr_poll(struct file *file_p, poll_table *wait) ++{ ++ struct btusb_data *data = file_p->private_data; ++ struct hci_dev *hdev; ++ ++ //AICBT_DBG("%s: BT usb char device is polling", __func__); ++ ++ if(!bt_char_dev_registered) { ++ AICBT_ERR("%s: char device has not registered!", __func__); ++ return POLLERR | POLLHUP; ++ } ++ ++ poll_wait(file_p, &btchr_read_wait, wait); ++ ++ hdev = hci_dev_get(0); ++ if (!hdev) { ++ AICBT_ERR("%s: Failed to get hci dev[Null]", __func__); ++ mdelay(URB_CANCELING_DELAY_MS); ++ return POLLERR | POLLHUP; ++ return POLLOUT | POLLWRNORM; ++ } ++ ++ /* Never trust on btusb_data, as bt device may be hotplugged out */ ++ data = GET_DRV_DATA(hdev); ++ if (!data) { ++ /* ++ * When bt device is hotplugged out, btusb_data will ++ * be freed in disconnect. ++ */ ++ AICBT_ERR("%s: Failed to get bt usb driver data[Null]", __func__); ++ mdelay(URB_CANCELING_DELAY_MS); ++ return POLLOUT | POLLWRNORM; ++ } ++ ++ if (!is_queue_empty()) ++ return POLLIN | POLLRDNORM; ++ ++ return POLLOUT | POLLWRNORM; ++} ++static long btchr_ioctl(struct file *file_p,unsigned int cmd, unsigned long arg) ++{ ++ int ret = 0; ++ struct hci_dev *hdev; ++ struct btusb_data *data; ++ firmware_info *fw_info; ++ ++ if(!bt_char_dev_registered) { ++ return -ENODEV; ++ } ++ ++ if(check_set_dlfw_state_value(1) != 1) { ++ AICBT_ERR("%s bt controller is disconnecting!", __func__); ++ return 0; ++ } ++ ++ hdev = hci_dev_get(0); ++ if(!hdev) { ++ AICBT_ERR("%s device is NULL!", __func__); ++ set_dlfw_state_value(0); ++ return 0; ++ } ++ data = GET_DRV_DATA(hdev); ++ fw_info = data->fw_info; ++ ++ AICBT_INFO(" btchr_ioctl DOWN_FW_CFG with Cmd:%d",cmd); ++ switch (cmd) { ++ case DOWN_FW_CFG: ++ AICBT_INFO(" btchr_ioctl DOWN_FW_CFG"); ++ ret = usb_autopm_get_interface(data->intf); ++ if (ret < 0){ ++ goto failed; ++ } ++ ++ //ret = download_patch(fw_info,1); ++ usb_autopm_put_interface(data->intf); ++ if(ret < 0){ ++ AICBT_ERR("%s:Failed in download_patch with ret:%d",__func__,ret); ++ goto failed; ++ } ++ ++ ret = hdev->open(hdev); ++ if(ret < 0){ ++ AICBT_ERR("%s:Failed in hdev->open(hdev):%d",__func__,ret); ++ goto failed; ++ } ++ set_bit(HCI_UP, &hdev->flags); ++ set_dlfw_state_value(0); ++ wake_up_interruptible(&bt_dlfw_wait); ++ return 1; ++ case DWFW_CMPLT: ++ AICBT_INFO(" btchr_ioctl DWFW_CMPLT"); ++#if 1 ++ case SET_ISO_CFG: ++ AICBT_INFO("btchr_ioctl SET_ISO_CFG"); ++ if(copy_from_user(&(hdev->voice_setting), (__u16*)arg, sizeof(__u16))){ ++ AICBT_INFO(" voice settings err"); ++ } ++ //hdev->voice_setting = *(uint16_t*)arg; ++ AICBT_INFO(" voice settings = %d", hdev->voice_setting); ++ //return 1; ++#endif ++ case GET_USB_INFO: ++ //ret = download_patch(fw_info,1); ++ AICBT_INFO(" btchr_ioctl GET_USB_INFO"); ++ ret = hdev->open(hdev); ++ if(ret < 0){ ++ AICBT_ERR("%s:Failed in hdev->open(hdev):%d",__func__,ret); ++ //goto done; ++ } ++ set_bit(HCI_UP, &hdev->flags); ++ set_dlfw_state_value(0); ++ wake_up_interruptible(&bt_dlfw_wait); ++ return 1; ++ case RESET_CONTROLLER: ++ AICBT_INFO(" btchr_ioctl RESET_CONTROLLER"); ++ //reset_controller(fw_info); ++ return 1; ++ default: ++ AICBT_ERR("%s:Failed with wrong Cmd:%d",__func__,cmd); ++ goto failed; ++ } ++ failed: ++ set_dlfw_state_value(0); ++ wake_up_interruptible(&bt_dlfw_wait); ++ return ret; ++ ++} ++ ++#ifdef CONFIG_PLATFORM_UBUNTU//AIDEN ++typedef u32 compat_uptr_t; ++static inline void __user *compat_ptr(compat_uptr_t uptr) ++{ ++ return (void __user *)(unsigned long)uptr; ++} ++#endif ++ ++#ifdef CONFIG_COMPAT ++static long compat_btchr_ioctl (struct file *filp, unsigned int cmd, unsigned long arg) ++{ ++ AICBT_DBG("%s: enter",__func__); ++ return btchr_ioctl(filp, cmd, (unsigned long) compat_ptr(arg)); ++} ++#endif ++static struct file_operations bt_chrdev_ops = { ++ open : btchr_open, ++ release : btchr_close, ++ read : btchr_read, ++ write : btchr_write, ++ poll : btchr_poll, ++ unlocked_ioctl : btchr_ioctl, ++#ifdef CONFIG_COMPAT ++ compat_ioctl : compat_btchr_ioctl, ++#endif ++}; ++ ++static int btchr_init(void) ++{ ++ int res = 0; ++ struct device *dev; ++ ++ AICBT_INFO("Register usb char device interface for BT driver"); ++ /* ++ * btchr mutex is used to sync between ++ * 1) downloading patch and opening bt char driver ++ * 2) the file operations of bt char driver ++ */ ++ mutex_init(&btchr_mutex); ++ ++ skb_queue_head_init(&btchr_readq); ++ init_waitqueue_head(&btchr_read_wait); ++ init_waitqueue_head(&bt_dlfw_wait); ++ ++ bt_char_class = class_create(THIS_MODULE, BT_CHAR_DEVICE_NAME); ++ if (IS_ERR(bt_char_class)) { ++ AICBT_ERR("Failed to create bt char class"); ++ return PTR_ERR(bt_char_class); ++ } ++ ++ res = alloc_chrdev_region(&bt_devid, 0, 1, BT_CHAR_DEVICE_NAME); ++ if (res < 0) { ++ AICBT_ERR("Failed to allocate bt char device"); ++ goto err_alloc; ++ } ++ ++ dev = device_create(bt_char_class, NULL, bt_devid, NULL, BT_CHAR_DEVICE_NAME); ++ if (IS_ERR(dev)) { ++ AICBT_ERR("Failed to create bt char device"); ++ res = PTR_ERR(dev); ++ goto err_create; ++ } ++ ++ cdev_init(&bt_char_dev, &bt_chrdev_ops); ++ res = cdev_add(&bt_char_dev, bt_devid, 1); ++ if (res < 0) { ++ AICBT_ERR("Failed to add bt char device"); ++ goto err_add; ++ } ++ ++ return 0; ++ ++err_add: ++ device_destroy(bt_char_class, bt_devid); ++err_create: ++ unregister_chrdev_region(bt_devid, 1); ++err_alloc: ++ class_destroy(bt_char_class); ++ return res; ++} ++ ++static void btchr_exit(void) ++{ ++ AICBT_INFO("Unregister usb char device interface for BT driver"); ++ ++ device_destroy(bt_char_class, bt_devid); ++ cdev_del(&bt_char_dev); ++ unregister_chrdev_region(bt_devid, 1); ++ class_destroy(bt_char_class); ++ ++ return; ++} ++#endif ++ ++int send_hci_cmd(firmware_info *fw_info) ++{ ++ ++ int len = 0; ++ int ret_val = -1; ++ int i = 0; ++ ++ if(g_chipid == PRODUCT_ID_AIC8801 || g_chipid == PRODUCT_ID_AIC8800D80){ ++ ret_val = usb_bulk_msg(fw_info->udev, fw_info->pipe_out, fw_info->send_pkt, fw_info->pkt_len, ++ &len, 3000); ++ if (ret_val || (len != fw_info->pkt_len)) { ++ AICBT_INFO("Error in send hci cmd = %d," ++ "len = %d, size = %d", ret_val, len, fw_info->pkt_len); ++ } ++ }else if(g_chipid == PRODUCT_ID_AIC8800DC){ ++ while((ret_val<0)&&(i++<3)) ++ { ++ ret_val = usb_control_msg( ++ fw_info->udev, fw_info->pipe_out, ++ 0, USB_TYPE_CLASS, 0, 0, ++ (void *)(fw_info->send_pkt), ++ fw_info->pkt_len, MSG_TO); ++ } ++ ++ } ++ return ret_val; ++ ++} ++ ++int rcv_hci_evt(firmware_info *fw_info) ++{ ++ int ret_len = 0, ret_val = 0; ++ int i; ++ ++ while (1) { ++ for(i = 0; i < 5; i++) { ++ ret_val = usb_interrupt_msg( ++ fw_info->udev, fw_info->pipe_in, ++ (void *)(fw_info->rcv_pkt), RCV_PKT_LEN, ++ &ret_len, MSG_TO); ++ if (ret_val >= 0) ++ break; ++ } ++ ++ if (ret_val < 0) ++ return ret_val; ++ ++ if (CMD_CMP_EVT == fw_info->evt_hdr->evt) { ++ if (fw_info->cmd_hdr->opcode == fw_info->cmd_cmp->opcode) ++ return ret_len; ++ } ++ } ++} ++ ++int set_bt_onoff(firmware_info *fw_info, uint8_t onoff) ++{ ++ int ret_val; ++ ++ AICBT_INFO("%s: %s", __func__, onoff != 0 ? "on" : "off"); ++ ++ fw_info->cmd_hdr->opcode = cpu_to_le16(BTOFF_OPCODE); ++ fw_info->cmd_hdr->plen = 1; ++ fw_info->pkt_len = CMD_HDR_LEN + 1; ++ fw_info->send_pkt[CMD_HDR_LEN] = onoff; ++ ++ ret_val = send_hci_cmd(fw_info); ++ if (ret_val < 0) { ++ AICBT_ERR("%s: Failed to send bt %s cmd, errno %d", ++ __func__, onoff != 0 ? "on" : "off", ret_val); ++ return ret_val; ++ } ++ ++ ret_val = rcv_hci_evt(fw_info); ++ if (ret_val < 0) { ++ AICBT_ERR("%s: Failed to receive bt %s event, errno %d", ++ __func__, onoff != 0 ? "on" : "off", ret_val); ++ return ret_val; ++ } ++ ++ return ret_val; ++} ++ ++//for 8800DC start ++u32 fwcfg_tbl[][2] = { ++ {0x40200028, 0x0021047e}, ++ {0x40200024, 0x0000011d}, ++}; ++ ++int fw_config(firmware_info* fw_info) ++{ ++ int ret_val = -1; ++ struct hci_dbg_rd_mem_cmd *rd_cmd; ++ struct hci_dbg_rd_mem_cmd_evt *evt_para; ++ int len = 0, i = 0; ++ struct fw_status *evt_status; ++ ++ rd_cmd = (struct hci_dbg_rd_mem_cmd *)(fw_info->req_para); ++ if (!rd_cmd) ++ return -ENOMEM; ++ ++ rd_cmd->start_addr = 0x40200024; ++ rd_cmd->type = 32; ++ rd_cmd->length = 4; ++ fw_info->cmd_hdr->opcode = cpu_to_le16(HCI_VSC_DBG_RD_MEM_CMD); ++ fw_info->cmd_hdr->plen = sizeof(struct hci_dbg_rd_mem_cmd); ++ fw_info->pkt_len = CMD_HDR_LEN + sizeof(struct hci_dbg_rd_mem_cmd); ++ ++ ret_val = send_hci_cmd(fw_info); ++ if (ret_val < 0) { ++ printk("%s: Failed to send hci cmd 0x%04x, errno %d", ++ __func__, fw_info->cmd_hdr->opcode, ret_val); ++ return ret_val; ++ } ++ ++ ret_val = rcv_hci_evt(fw_info); ++ if (ret_val < 0) { ++ printk("%s: Failed to receive hci event, errno %d", ++ __func__, ret_val); ++ return ret_val; ++ } ++ ++ evt_para = (struct hci_dbg_rd_mem_cmd_evt *)(fw_info->rsp_para); ++ ++ printk("%s: fw status = 0x%04x, length %d, %x %x %x %x", ++ __func__, evt_para->status, evt_para->length, ++ evt_para->data[0], ++ evt_para->data[1], ++ evt_para->data[2], ++ evt_para->data[3]); ++ ++ ret_val = evt_para->status; ++ if (evt_para->status == 0) { ++ uint16_t rd_data = (evt_para->data[0] | (evt_para->data[1] << 8)); ++ printk("%s rd_data is %x\n", __func__, rd_data); ++ if (rd_data == 0x119) { ++ struct aicbt_patch_table_cmd *patch_table_cmd = (struct aicbt_patch_table_cmd *)(fw_info->req_para); ++ len = sizeof(fwcfg_tbl) / sizeof(u32) / 2; ++ patch_table_cmd->patch_num = len; ++ for (i = 0; i < len; i++) { ++ memcpy(&patch_table_cmd->patch_table_addr[i], &fwcfg_tbl[i][0], sizeof(uint32_t)); ++ memcpy(&patch_table_cmd->patch_table_data[i], &fwcfg_tbl[i][1], sizeof(uint32_t)); ++ printk("%s [%d] data: %08x %08x\n", __func__, i, patch_table_cmd->patch_table_addr[i],patch_table_cmd->patch_table_data[i]); ++ } ++ fw_info->cmd_hdr->opcode = cpu_to_le16(HCI_VSC_UPDATE_PT_CMD); ++ fw_info->cmd_hdr->plen = HCI_VSC_UPDATE_PT_SIZE; ++ fw_info->pkt_len = fw_info->cmd_hdr->plen + 3; ++ ret_val = send_hci_cmd(fw_info); ++ if (ret_val < 0) { ++ AICBT_ERR("%s: rcv_hci_evt err %d", __func__, ret_val); ++ return ret_val; ++ } ++ ret_val = rcv_hci_evt(fw_info); ++ if (ret_val < 0) { ++ printk("%s: Failed to receive hci event, errno %d", ++ __func__, ret_val); ++ return ret_val; ++ } ++ evt_status = (struct fw_status *)fw_info->rsp_para; ++ ret_val = evt_status->status; ++ if (0 != evt_status->status) { ++ ret_val = -1; ++ } else { ++ ret_val = 0; ++ } ++ ++ } ++ } ++ return ret_val; ++} ++ ++int system_config(firmware_info *fw_info) ++{ ++ int ret_val = -1; ++ struct hci_dbg_rd_mem_cmd *rd_cmd; ++ struct hci_dbg_rd_mem_cmd_evt *evt_para; ++ //int len = 0, i = 0; ++ //struct fw_status *evt_status; ++ ++ rd_cmd = (struct hci_dbg_rd_mem_cmd *)(fw_info->req_para); ++ if (!rd_cmd) ++ return -ENOMEM; ++ ++ rd_cmd->start_addr = 0x40500000; ++ rd_cmd->type = 32; ++ rd_cmd->length = 4; ++ fw_info->cmd_hdr->opcode = cpu_to_le16(HCI_VSC_DBG_RD_MEM_CMD); ++ fw_info->cmd_hdr->plen = sizeof(struct hci_dbg_rd_mem_cmd); ++ fw_info->pkt_len = CMD_HDR_LEN + sizeof(struct hci_dbg_rd_mem_cmd); ++ ++ ret_val = send_hci_cmd(fw_info); ++ if (ret_val < 0) ++ { ++ printk("%s: Failed to send hci cmd 0x%04x, errno %d", ++ __func__, fw_info->cmd_hdr->opcode, ret_val); ++ return ret_val; ++ } ++ ++ ret_val = rcv_hci_evt(fw_info); ++ if (ret_val < 0) ++ { ++ printk("%s: Failed to receive hci event, errno %d", ++ __func__, ret_val); ++ return ret_val; ++ } ++ ++ evt_para = (struct hci_dbg_rd_mem_cmd_evt *)(fw_info->rsp_para); ++ ++ printk("%s: fw status = 0x%04x, length %d, %x %x %x %x", ++ __func__, evt_para->status, evt_para->length, ++ evt_para->data[0], ++ evt_para->data[1], ++ evt_para->data[2], ++ evt_para->data[3]); ++ ++ ret_val = evt_para->status; ++ if (evt_para->status == 0) ++ { ++ uint32_t rd_data = (evt_para->data[0] | (evt_para->data[1] << 8) | (evt_para->data[2] << 16) | (evt_para->data[3] << 24)); ++ //printk("%s 0x40500000 rd_data is %x\n", __func__, rd_data); ++ chip_id = (u8) (rd_data >> 16); ++ } ++ ++ rd_cmd->start_addr = 0x20; ++ rd_cmd->type = 32; ++ rd_cmd->length = 4; ++ fw_info->cmd_hdr->opcode = cpu_to_le16(HCI_VSC_DBG_RD_MEM_CMD); ++ fw_info->cmd_hdr->plen = sizeof(struct hci_dbg_rd_mem_cmd); ++ fw_info->pkt_len = CMD_HDR_LEN + sizeof(struct hci_dbg_rd_mem_cmd); ++ ++ ret_val = send_hci_cmd(fw_info); ++ if (ret_val < 0) ++ { ++ printk("%s: Failed to send hci cmd 0x%04x, errno %d", ++ __func__, fw_info->cmd_hdr->opcode, ret_val); ++ return ret_val; ++ } ++ ++ ret_val = rcv_hci_evt(fw_info); ++ if (ret_val < 0) ++ { ++ printk("%s: Failed to receive hci event, errno %d", ++ __func__, ret_val); ++ return ret_val; ++ } ++ ++ evt_para = (struct hci_dbg_rd_mem_cmd_evt *)(fw_info->rsp_para); ++ ++ printk("%s: fw status = 0x%04x, length %d, %x %x %x %x", ++ __func__, evt_para->status, evt_para->length, ++ evt_para->data[0], ++ evt_para->data[1], ++ evt_para->data[2], ++ evt_para->data[3]); ++ ++ ret_val = evt_para->status; ++ if (evt_para->status == 0) ++ { ++ uint32_t rd_data = (evt_para->data[0] | (evt_para->data[1] << 8) | (evt_para->data[2] << 16) | (evt_para->data[3] << 24)); ++ //printk("%s 0x02 rd_data is %x\n", __func__, rd_data); ++ sub_chip_id = (u8) (rd_data); ++ } ++ printk("chip_id = %x, sub_chip_id = %x\n", chip_id, sub_chip_id); ++ return ret_val; ++} ++ ++int check_fw_status(firmware_info* fw_info) ++{ ++ struct fw_status *read_ver_rsp; ++ int ret_val = -1; ++ ++ fw_info->cmd_hdr->opcode = cpu_to_le16(HCI_VSC_FW_STATUS_GET_CMD); ++ fw_info->cmd_hdr->plen = 0; ++ fw_info->pkt_len = CMD_HDR_LEN; ++ ++ ret_val = send_hci_cmd(fw_info); ++ if (ret_val < 0) { ++ printk("%s: Failed to send hci cmd 0x%04x, errno %d", ++ __func__, fw_info->cmd_hdr->opcode, ret_val); ++ return ret_val; ++ } ++ ++ ret_val = rcv_hci_evt(fw_info); ++ if (ret_val < 0) { ++ printk("%s: Failed to receive hci event, errno %d", ++ __func__, ret_val); ++ return ret_val; ++ } ++ ++ read_ver_rsp = (struct fw_status *)(fw_info->rsp_para); ++ ++ printk("%s: fw status = 0x%04x", ++ __func__, read_ver_rsp->status); ++ return read_ver_rsp->status; ++} ++ ++int download_data(firmware_info *fw_info, u32 fw_addr, char *filename) ++{ ++ unsigned int i=0; ++ int size; ++ u8 *dst=NULL; ++ int err=0; ++ struct hci_dbg_wr_mem_cmd *dl_cmd; ++ int hdr_len = sizeof(__le32) + sizeof(__u8) + sizeof(__u8); ++ int data_len = HCI_VSC_MEM_WR_SIZE; ++ int frag_len = data_len + hdr_len; ++ int ret_val; ++ int ncmd = 1; ++ struct fw_status *evt_para; ++ ++ /* load aic firmware */ ++ size = aic_load_firmware(&dst, filename, NULL); ++ if(size <= 0){ ++ printk("wrong size of firmware file\n"); ++ vfree(dst); ++ dst = NULL; ++ return -1; ++ } ++ ++ dl_cmd = (struct hci_dbg_wr_mem_cmd *)(fw_info->req_para); ++ if (!dl_cmd) ++ return -ENOMEM; ++ evt_para = (struct fw_status *)fw_info->rsp_para; ++ ++ /* Copy the file on the Embedded side */ ++ printk("### Upload %s firmware, @ = %x size=%d\n", filename, fw_addr, size); ++ ++ if (size > HCI_VSC_MEM_WR_SIZE) {// > 1KB data ++ for (i = 0; i < (size - HCI_VSC_MEM_WR_SIZE); i += HCI_VSC_MEM_WR_SIZE) {//each time write 240 bytes ++ data_len = HCI_VSC_MEM_WR_SIZE; ++ frag_len = data_len + hdr_len; ++ memcpy(dl_cmd->data, dst + i, data_len); ++ dl_cmd->length = data_len; ++ dl_cmd->type = 32; ++ dl_cmd->start_addr = fw_addr + i; ++ fw_info->cmd_hdr->opcode = cpu_to_le16(DOWNLOAD_OPCODE); ++ fw_info->cmd_hdr->plen = frag_len; ++ fw_info->pkt_len = frag_len + 3; ++ #if 0 ++ printk("[%d] data_len %d, src %x, dst %x\n", i, data_len, dst + i, fw_addr + i); ++ printk("%p , %d\n", dl_cmd, fw_info->pkt_len); ++ print_hex_dump(KERN_ERR,"payload:",DUMP_PREFIX_NONE,16,1,dl_cmd->data,32,false); ++ /* Send download command */ ++ print_hex_dump(KERN_ERR,"data:",DUMP_PREFIX_NONE,16,1,fw_info->send_pkt,32,false); ++ #endif ++ ret_val = send_hci_cmd(fw_info); ++ ++ while (ncmd > 0) { ++ ret_val = rcv_hci_evt(fw_info); ++ printk("rcv_hci_evt %d\n", ret_val); ++ if (ret_val < 0) { ++ AICBT_ERR("%s: rcv_hci_evt err %d", __func__, ret_val); ++ goto out; ++ } else { ++ AICBT_DBG("%s: Receive acked frag num %d", __func__, evt_para->status); ++ ncmd--; ++ } ++ if (0 != evt_para->status) { ++ AICBT_ERR("%s: Receive acked frag num %d, err status %d", ++ __func__, ret_val, evt_para->status); ++ ret_val = -1; ++ goto out; ++ } else { ++ ret_val = 0; ++ } ++ } ++ ncmd = 1; ++ } ++ } ++ ++ if (!err && (i < size)) {// <1KB data ++ data_len = size - i; ++ frag_len = data_len + hdr_len; ++ memcpy(dl_cmd->data, dst + i, data_len); ++ dl_cmd->length = data_len; ++ dl_cmd->type = 32; ++ dl_cmd->start_addr = fw_addr + i; ++ fw_info->cmd_hdr->opcode = cpu_to_le16(DOWNLOAD_OPCODE); ++ fw_info->cmd_hdr->plen = frag_len; ++ fw_info->pkt_len = frag_len + 3; ++ ret_val = send_hci_cmd(fw_info); ++ //printk("(%d) data_len %d, src %x, dst %x\n", i, data_len, (dst + i), fw_addr + i); ++ //printk("%p , %d\n", dl_cmd, fw_info->pkt_len); ++ while (ncmd > 0) { ++ ret_val = rcv_hci_evt(fw_info); ++ if (ret_val < 0) { ++ AICBT_ERR("%s: rcv_hci_evt err %d", __func__, ret_val); ++ goto out; ++ } else { ++ AICBT_DBG("%s: Receive acked frag num %d", __func__, evt_para->status); ++ ncmd--; ++ } ++ if (0 != evt_para->status) { ++ AICBT_ERR("%s: Receive acked frag num %d, err status %d", ++ __func__, ret_val, evt_para->status); ++ ret_val = -1; ++ goto out; ++ } else { ++ ret_val = 0; ++ } ++ } ++ ncmd = 0; ++ } ++ ++out: ++ if (dst) { ++ vfree(dst); ++ dst = NULL; ++ } ++ ++ printk("fw download complete\n\n"); ++ return ret_val; ++ ++} ++ ++ ++struct aicbt_info_t { ++ uint32_t btmode; ++ uint32_t btport; ++ uint32_t uart_baud; ++ uint32_t uart_flowctrl; ++ uint32_t lpm_enable; ++ uint32_t txpwr_lvl; ++}; ++ ++struct aicbsp_info_t { ++ int hwinfo; ++ uint32_t cpmode; ++}; ++ ++enum aicbsp_cpmode_type { ++ AICBSP_CPMODE_WORK, ++ AICBSP_CPMODE_TEST, ++}; ++ ++/* btmode ++ * used for force bt mode,if not AICBSP_MODE_NULL ++ * efuse valid and vendor_info will be invalid, even has beed set valid ++*/ ++enum aicbt_btmode_type { ++ AICBT_BTMODE_BT_ONLY_SW = 0x0, // bt only mode with switch ++ AICBT_BTMODE_BT_WIFI_COMBO, // wifi/bt combo mode ++ AICBT_BTMODE_BT_ONLY, // bt only mode without switch ++ AICBT_BTMODE_BT_ONLY_TEST, // bt only test mode ++ AICBT_BTMODE_BT_WIFI_COMBO_TEST, // wifi/bt combo test mode ++ AICBT_MODE_NULL = 0xFF, // invalid value ++}; ++ ++enum aicbt_btport_type { ++ AICBT_BTPORT_NULL, ++ AICBT_BTPORT_MB, ++ AICBT_BTPORT_UART, ++}; ++ ++enum aicbt_uart_baud_type { ++ AICBT_UART_BAUD_115200 = 115200, ++ AICBT_UART_BAUD_921600 = 921600, ++ AICBT_UART_BAUD_1_5M = 1500000, ++ AICBT_UART_BAUD_3_25M = 3250000, ++}; ++ ++enum aicbt_uart_flowctrl_type { ++ AICBT_UART_FLOWCTRL_DISABLE = 0x0, // uart without flow ctrl ++ AICBT_UART_FLOWCTRL_ENABLE, // uart with flow ctrl ++}; ++ ++#define AICBSP_HWINFO_DEFAULT (-1) ++#define AICBSP_CPMODE_DEFAULT AICBSP_CPMODE_WORK ++#define AICBT_TXPWR_DFT 0x6F2F ++ ++ ++#define AICBT_BTMODE_DEFAULT AICBT_BTMODE_BT_WIFI_COMBO ++#define AICBT_BTPORT_DEFAULT AICBT_BTPORT_MB ++#define AICBT_UART_BAUD_DEFAULT AICBT_UART_BAUD_1_5M ++#define AICBT_UART_FC_DEFAULT AICBT_UART_FLOWCTRL_ENABLE ++#define AICBT_LPM_ENABLE_DEFAULT 0 ++#define AICBT_TXPWR_LVL_DEFAULT AICBT_TXPWR_DFT ++ ++struct aicbsp_info_t aicbsp_info = { ++ .hwinfo = AICBSP_HWINFO_DEFAULT, ++ .cpmode = AICBSP_CPMODE_DEFAULT, ++}; ++ ++#ifndef CONFIG_USE_FW_REQUEST ++#define FW_PATH_MAX 200 ++ ++char aic_fw_path[FW_PATH_MAX]; ++#if (CONFIG_BLUEDROID == 0) ++static const char* aic_default_fw_path = "/lib/firmware/aic8800DC"; ++#else ++static const char* aic_default_fw_path = "/vendor/etc/firmware"; ++#endif ++#endif //CONFIG_USE_FW_REQUEST ++ ++static struct aicbt_info_t aicbt_info = { ++ .btmode = AICBT_BTMODE_DEFAULT, ++ .btport = AICBT_BTPORT_DEFAULT, ++ .uart_baud = AICBT_UART_BAUD_DEFAULT, ++ .uart_flowctrl = AICBT_UART_FC_DEFAULT, ++ .lpm_enable = AICBT_LPM_ENABLE_DEFAULT, ++ .txpwr_lvl = AICBT_TXPWR_LVL_DEFAULT, ++}; ++ ++int patch_table_load(firmware_info *fw_info, struct aicbt_patch_table *_head) ++{ ++ struct aicbt_patch_table *head, *p; ++ int i; ++ uint32_t *data = NULL; ++ struct aicbt_patch_table_cmd *patch_table_cmd = (struct aicbt_patch_table_cmd *)(fw_info->req_para); ++ struct fw_status *evt_para; ++ int ret_val = 0; ++ int ncmd = 1; ++ uint32_t len = 0; ++ uint32_t tot_len = 0; ++ head = _head; ++ for (p = head; p != NULL; p = p->next) { ++ data = p->data; ++ if(AICBT_PT_BTMODE == p->type){ ++ *(data + 1) = aicbsp_info.hwinfo < 0; ++ *(data + 3) = aicbsp_info.hwinfo; ++ *(data + 5) = aicbsp_info.cpmode; ++ ++ *(data + 7) = aicbt_info.btmode; ++ *(data + 9) = aicbt_info.btport; ++ *(data + 11) = aicbt_info.uart_baud; ++ *(data + 13) = aicbt_info.uart_flowctrl; ++ *(data + 15) = aicbt_info.lpm_enable; ++ *(data + 17) = aicbt_info.txpwr_lvl; ++ ++ } ++ if (p->type == AICBT_PT_NULL || p->type == AICBT_PT_PWRON) { ++ continue; ++ } ++ if (p->type == AICBT_PT_VER) { ++ char *data_s = (char *)p->data; ++ printk("patch version %s\n", data_s); ++ continue; ++ } ++ if (p->len == 0) { ++ printk("len is 0\n"); ++ continue; ++ } ++ tot_len = p->len; ++ while (tot_len) { ++ if (tot_len > HCI_PT_MAX_LEN) { ++ len = HCI_PT_MAX_LEN; ++ } else { ++ len = tot_len; ++ } ++ for (i = 0; i < len; i++) { ++ patch_table_cmd->patch_num = len; ++ memcpy(&patch_table_cmd->patch_table_addr[i], data, sizeof(uint32_t)); ++ memcpy(&patch_table_cmd->patch_table_data[i], data + 1, sizeof(uint32_t)); ++ printk("[%d] data: %08x %08x\n", i, patch_table_cmd->patch_table_addr[i],patch_table_cmd->patch_table_data[i]); ++ data += 2; ++ } ++ tot_len -= len; ++ evt_para = (struct fw_status *)fw_info->rsp_para; ++ //print_hex_dump(KERN_ERR,"data0:",DUMP_PREFIX_NONE,16,1,patch_table_cmd,sizeof(struct aicbt_patch_table_cmd),false); ++ ++ //printk("patch num %x %d\n", patch_table_cmd->patch_num, sizeof(struct aicbt_patch_table_cmd)); ++ fw_info->cmd_hdr->opcode = cpu_to_le16(HCI_VSC_UPDATE_PT_CMD); ++ fw_info->cmd_hdr->plen = HCI_VSC_UPDATE_PT_SIZE; ++ fw_info->pkt_len = fw_info->cmd_hdr->plen + 3; ++ AICBT_DBG("patch num 0x%x, plen 0x%x\n", patch_table_cmd->patch_num, fw_info->cmd_hdr->plen ); ++ //print_hex_dump(KERN_ERR,"patch table:",DUMP_PREFIX_NONE,16,1,fw_info->send_pkt,32,false); ++ ret_val = send_hci_cmd(fw_info); ++ while (ncmd > 0) { ++ ret_val = rcv_hci_evt(fw_info); ++ if (ret_val < 0) { ++ AICBT_ERR("%s: rcv_hci_evt err %d", __func__, ret_val); ++ goto out; ++ } else { ++ AICBT_DBG("%s: Receive acked frag num %d", __func__, evt_para->status); ++ ncmd--; ++ } ++ if (0 != evt_para->status) { ++ AICBT_ERR("%s: Receive acked frag num %d, err status %d", ++ __func__, ret_val, evt_para->status); ++ ret_val = -1; ++ goto out; ++ } ++ } ++ ncmd = 1; ++ } ++ } ++out: ++ aicbt_patch_table_free(&head); ++ return ret_val; ++} ++ ++int aic_load_firmware(u8 ** fw_buf, const char *name, struct device *device) ++{ ++ ++#ifdef CONFIG_USE_FW_REQUEST ++ const struct firmware *fw = NULL; ++ u32 *dst = NULL; ++ void *buffer=NULL; ++ int size = 0; ++ int ret = 0; ++ ++ printk("%s: request firmware = %s \n", __func__ ,name); ++ ++ ++ ret = request_firmware(&fw, name, NULL); ++ ++ if (ret < 0) { ++ printk("Load %s fail\n", name); ++ release_firmware(fw); ++ return -1; ++ } ++ ++ size = fw->size; ++ dst = (u32 *)fw->data; ++ ++ if (size <= 0) { ++ printk("wrong size of firmware file\n"); ++ release_firmware(fw); ++ return -1; ++ } ++ ++ ++ buffer = vmalloc(size); ++ memset(buffer, 0, size); ++ memcpy(buffer, dst, size); ++ ++ *fw_buf = buffer; ++ ++ release_firmware(fw); ++ ++ return size; ++ ++#else ++ u8 *buffer=NULL; ++ char *path=NULL; ++ struct file *fp=NULL; ++ int size = 0, len=0; ++ ssize_t rdlen=0; ++ ++ /* get the firmware path */ ++ path = __getname(); ++ if (!path){ ++ *fw_buf=NULL; ++ return -1; ++ } ++ ++ if (strlen(aic_fw_path) > 0) { ++ printk("%s: use customer define fw_path\n", __func__); ++ len = snprintf(path, FW_PATH_MAX, "%s/%s", aic_fw_path, name); ++ } else { ++ len = snprintf(path, FW_PATH_MAX, "%s/%s",aic_default_fw_path, name); ++ } ++ ++ if (len >= FW_PATH_MAX) { ++ printk("%s: %s file's path too long\n", __func__, name); ++ *fw_buf=NULL; ++ __putname(path); ++ return -1; ++ } ++ ++ printk("%s :firmware path = %s \n", __func__ ,path); ++ ++ ++ /* open the firmware file */ ++ fp=filp_open(path, O_RDONLY, 0); ++ if(IS_ERR(fp) || (!fp)){ ++ printk("%s: %s file failed to open\n", __func__, name); ++ if(IS_ERR(fp)) ++ printk("is_Err\n"); ++ if((!fp)) ++ printk("null\n"); ++ *fw_buf=NULL; ++ __putname(path); ++ fp=NULL; ++ return -1; ++ } ++ ++ size = i_size_read(file_inode(fp)); ++ if(size<=0){ ++ printk("%s: %s file size invalid %d\n", __func__, name, size); ++ *fw_buf=NULL; ++ __putname(path); ++ filp_close(fp,NULL); ++ fp=NULL; ++ return -1; ++} ++ ++ /* start to read from firmware file */ ++ buffer = vmalloc(size); ++ memset(buffer, 0, size); ++ if(!buffer){ ++ *fw_buf=NULL; ++ __putname(path); ++ filp_close(fp,NULL); ++ fp=NULL; ++ return -1; ++ } ++ ++ ++ #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 13, 16) ++ rdlen = kernel_read(fp, buffer, size, &fp->f_pos); ++ #else ++ rdlen = kernel_read(fp, fp->f_pos, buffer, size); ++ #endif ++ ++ if(size != rdlen){ ++ printk("%s: %s file rdlen invalid %d %d\n", __func__, name, (int)rdlen, size); ++ *fw_buf=NULL; ++ __putname(path); ++ filp_close(fp,NULL); ++ fp=NULL; ++ vfree(buffer); ++ buffer=NULL; ++ return -1; ++ } ++ if(rdlen > 0){ ++ fp->f_pos += rdlen; ++ //printk("f_pos=%d\n", (int)fp->f_pos); ++ } ++ *fw_buf = buffer; ++ ++#if 0 ++ MD5Init(&md5); ++ MD5Update(&md5, (unsigned char *)dst, size); ++ MD5Final(&md5, decrypt); ++ ++ printk(MD5PINRT, MD5(decrypt)); ++ ++#endif ++ return size; ++#endif ++} ++ ++int aicbt_patch_table_free(struct aicbt_patch_table **head) ++{ ++ struct aicbt_patch_table *p = *head, *n = NULL; ++ while (p) { ++ n = p->next; ++ kfree(p->name); ++ kfree(p->data); ++ kfree(p); ++ p = n; ++ } ++ *head = NULL; ++ return 0; ++} ++ ++int get_patch_addr_from_patch_table(firmware_info *fw_info, char *filename, uint32_t *fw_patch_base_addr) ++{ ++ int size; ++ int ret = 0; ++ uint8_t *rawdata=NULL; ++ uint8_t *p = NULL; ++ uint32_t *data = NULL; ++ uint32_t type = 0, len = 0; ++ int j; ++ ++ /* load aic firmware */ ++ size = aic_load_firmware((u8 **)&rawdata, filename, NULL); ++ ++ /* Copy the file on the Embedded side */ ++ printk("### Upload %s fw_patch_table, size=%d\n", filename, size); ++ ++ if (size <= 0) { ++ printk("wrong size of firmware file\n"); ++ ret = -1; ++ goto err; ++ } ++ ++ p = rawdata; ++ ++ if (memcmp(p, AICBT_PT_TAG, sizeof(AICBT_PT_TAG) < 16 ? sizeof(AICBT_PT_TAG) : 16)) { ++ printk("TAG err\n"); ++ ret = -1; ++ goto err; ++ } ++ p += 16; ++ ++ while (p - rawdata < size) { ++ printk("size = %d p - rawdata = 0x%0lx \r\n", size, p - rawdata); ++ p += 16; ++ ++ type = *(uint32_t *)p; ++ p += 4; ++ ++ len = *(uint32_t *)p; ++ p += 4; ++ printk("cur->type %x, len %d\n", type, len); ++ ++ if(type >= 1000 ) {//Temp Workaround ++ len = 0; ++ }else{ ++ data = (uint32_t *)p; ++ if (type == AICBT_PT_NULL) { ++ *(fw_patch_base_addr) = *(data + 3); ++ printk("addr found %x\n", *(fw_patch_base_addr)); ++ for (j = 0; j < len; j++) { ++ printk("addr %x\n", *(data+j)); ++ } ++ break; ++ } ++ p += len * 8; ++ } ++ } ++ ++ vfree(rawdata); ++ return ret; ++err: ++ //aicbt_patch_table_free(&head); ++ ++ if (rawdata){ ++ vfree(rawdata); ++ } ++ return ret; ++} ++ ++ ++ ++int patch_table_download(firmware_info *fw_info, char *filename) ++{ ++ struct aicbt_patch_table *head = NULL; ++ struct aicbt_patch_table *new = NULL; ++ struct aicbt_patch_table *cur = NULL; ++ int size; ++ int ret = 0; ++ uint8_t *rawdata=NULL; ++ uint8_t *p = NULL; ++ ++ /* load aic firmware */ ++ size = aic_load_firmware((u8 **)&rawdata, filename, NULL); ++ ++ /* Copy the file on the Embedded side */ ++ printk("### Upload %s fw_patch_table, size=%d\n", filename, size); ++ ++ if (size <= 0) { ++ printk("wrong size of firmware file\n"); ++ ret = -1; ++ goto err; ++ } ++ ++ p = rawdata; ++ ++ if (memcmp(p, AICBT_PT_TAG, sizeof(AICBT_PT_TAG) < 16 ? sizeof(AICBT_PT_TAG) : 16)) { ++ printk("TAG err\n"); ++ ret = -1; ++ goto err; ++ } ++ p += 16; ++ ++ while (p - rawdata < size) { ++ printk("size = %d p - rawdata = 0x%0lx \r\n", size, p - rawdata); ++ new = (struct aicbt_patch_table *)kmalloc(sizeof(struct aicbt_patch_table), GFP_KERNEL); ++ memset(new, 0, sizeof(struct aicbt_patch_table)); ++ if (head == NULL) { ++ head = new; ++ cur = new; ++ } else { ++ cur->next = new; ++ cur = cur->next; ++ } ++ ++ cur->name = (char *)kmalloc(sizeof(char) * 16, GFP_KERNEL); ++ memset(cur->name, 0, sizeof(char) * 16); ++ memcpy(cur->name, p, 16); ++ p += 16; ++ ++ cur->type = *(uint32_t *)p; ++ p += 4; ++ ++ cur->len = *(uint32_t *)p; ++ p += 4; ++ printk("cur->type %x, len %d\n", cur->type, cur->len); ++ ++ if((cur->type ) >= 1000 ) {//Temp Workaround ++ cur->len = 0; ++ }else{ ++ cur->data = (uint32_t *)kmalloc(sizeof(uint8_t) * cur->len * 8, GFP_KERNEL); ++ memset(cur->data, 0, sizeof(uint8_t) * cur->len * 8); ++ memcpy(cur->data, p, cur->len * 8); ++ p += cur->len * 8; ++ } ++ } ++ ++ vfree(rawdata); ++ patch_table_load(fw_info, head); ++ printk("fw_patch_table download complete\n\n"); ++ ++ return ret; ++err: ++ //aicbt_patch_table_free(&head); ++ ++ if (rawdata){ ++ vfree(rawdata); ++ } ++ return ret; ++} ++ ++ ++int download_patch(firmware_info *fw_info, int cached) ++{ ++ int ret_val = 0; ++ ++ printk("%s: Download fw patch start, cached %d", __func__, cached); ++ ++ if (!fw_info) { ++ printk("%s: No patch entry exists(fw_info %p)", __func__, fw_info); ++ ret_val = -1; ++ goto end; ++ } ++ ++ ret_val = fw_config(fw_info); ++ if (ret_val) { ++ printk("%s: fw config failed %d", __func__, ret_val); ++ goto free; ++ } ++ ++ ret_val = system_config(fw_info); ++ if (ret_val) ++ { ++ printk("%s: system config failed %d", __func__, ret_val); ++ goto free; ++ } ++ ++ /* ++ * step1: check firmware statis ++ * step2: download firmware if updated ++ */ ++ ++ ++ ret_val = check_fw_status(fw_info); ++ ++ ++ if (ret_val) { ++ #if 0 ++ ret_val = download_data(fw_info, FW_RAM_ADID_BASE_ADDR, FW_ADID_BASE_NAME); ++ if (ret_val) { ++ printk("aic load adid fail %d\n", ret_val); ++ goto free; ++ } ++ #endif ++ if (sub_chip_id == 0) { ++ ret_val= download_data(fw_info, FW_RAM_PATCH_BASE_ADDR, FW_PATCH_BASE_NAME); ++ if (ret_val) { ++ printk("aic load patch fail %d\n", ret_val); ++ goto free; ++ } ++ ++ ret_val= patch_table_download(fw_info, FW_PATCH_TABLE_NAME); ++ if (ret_val) { ++ printk("aic load patch ftable ail %d\n", ret_val); ++ goto free; ++ } ++ } else if (sub_chip_id == 1) { ++ uint32_t fw_ram_patch_base_addr = FW_RAM_PATCH_BASE_ADDR; ++ ++ ret_val = get_patch_addr_from_patch_table(fw_info, FW_PATCH_TABLE_NAME_U02, &fw_ram_patch_base_addr); ++ if (ret_val) ++ { ++ printk("aic get patch addr fail %d\n", ret_val); ++ goto free; ++ } ++ printk("%s %x\n", __func__, fw_ram_patch_base_addr); ++ ret_val = download_data(fw_info, fw_ram_patch_base_addr, FW_PATCH_BASE_NAME_U02); ++ if (ret_val) ++ { ++ printk("aic load patch fail %d\n", ret_val); ++ goto free; ++ } ++ ++ ret_val = patch_table_download(fw_info, FW_PATCH_TABLE_NAME_U02); ++ if (ret_val) ++ { ++ printk("aic load patch ftable ail %d\n", ret_val); ++ goto free; ++ } ++ } else if (sub_chip_id == 2) { ++ uint32_t fw_ram_patch_base_addr = FW_RAM_PATCH_BASE_ADDR; ++ ++ ret_val = get_patch_addr_from_patch_table(fw_info, FW_PATCH_TABLE_NAME_U02H, &fw_ram_patch_base_addr); ++ if (ret_val) ++ { ++ printk("aic get patch addr fail %d\n", ret_val); ++ goto free; ++ } ++ printk("U02H %s %x\n", __func__, fw_ram_patch_base_addr); ++ ret_val = download_data(fw_info, fw_ram_patch_base_addr, FW_PATCH_BASE_NAME_U02H); ++ if (ret_val) ++ { ++ printk("aic load patch fail %d\n", ret_val); ++ goto free; ++ } ++ ++ ret_val = patch_table_download(fw_info, FW_PATCH_TABLE_NAME_U02H); ++ if (ret_val) ++ { ++ printk("aic load patch ftable ail %d\n", ret_val); ++ goto free; ++ } ++ } else { ++ printk("%s unsupported sub_chip_id %x\n", __func__, sub_chip_id); ++ } ++ } ++ ++free: ++ /* Free fw data after download finished */ ++ kfree(fw_info->fw_data); ++ fw_info->fw_data = NULL; ++ ++end: ++ return ret_val; ++} ++ ++//for 8800dc end ++ ++firmware_info *firmware_info_init(struct usb_interface *intf) ++{ ++ struct usb_device *udev = interface_to_usbdev(intf); ++ firmware_info *fw_info; ++ ++ AICBT_DBG("%s: start", __func__); ++ ++ fw_info = kzalloc(sizeof(*fw_info), GFP_KERNEL); ++ if (!fw_info) ++ return NULL; ++ ++ fw_info->send_pkt = kzalloc(SEND_PKT_LEN, GFP_KERNEL); ++ if (!fw_info->send_pkt) { ++ kfree(fw_info); ++ return NULL; ++ } ++ ++ fw_info->rcv_pkt = kzalloc(RCV_PKT_LEN, GFP_KERNEL); ++ if (!fw_info->rcv_pkt) { ++ kfree(fw_info->send_pkt); ++ kfree(fw_info); ++ return NULL; ++ } ++ ++ fw_info->intf = intf; ++ fw_info->udev = udev; ++if(g_chipid == PRODUCT_ID_AIC8801 || g_chipid == PRODUCT_ID_AIC8800D80){ ++ fw_info->pipe_in = usb_rcvbulkpipe(fw_info->udev, BULK_EP); ++ fw_info->pipe_out = usb_rcvbulkpipe(fw_info->udev, CTRL_EP); ++}else if(g_chipid == PRODUCT_ID_AIC8800DC){ ++ fw_info->pipe_in = usb_rcvintpipe(fw_info->udev, INTR_EP); ++ fw_info->pipe_out = usb_sndctrlpipe(fw_info->udev, CTRL_EP); ++} ++ fw_info->cmd_hdr = (struct hci_command_hdr *)(fw_info->send_pkt); ++ fw_info->evt_hdr = (struct hci_event_hdr *)(fw_info->rcv_pkt); ++ fw_info->cmd_cmp = (struct hci_ev_cmd_complete *)(fw_info->rcv_pkt + EVT_HDR_LEN); ++ fw_info->req_para = fw_info->send_pkt + CMD_HDR_LEN; ++ fw_info->rsp_para = fw_info->rcv_pkt + EVT_HDR_LEN + CMD_CMP_LEN; ++ ++#if BTUSB_RPM ++ AICBT_INFO("%s: Auto suspend is enabled", __func__); ++ usb_enable_autosuspend(udev); ++ pm_runtime_set_autosuspend_delay(&(udev->dev), 2000); ++#else ++ AICBT_INFO("%s: Auto suspend is disabled", __func__); ++ usb_disable_autosuspend(udev); ++#endif ++ ++#if BTUSB_WAKEUP_HOST ++ device_wakeup_enable(&udev->dev); ++#endif ++ ++ return fw_info; ++} ++ ++ ++void firmware_info_destroy(struct usb_interface *intf) ++{ ++ firmware_info *fw_info; ++ struct usb_device *udev; ++ struct btusb_data *data; ++ ++ udev = interface_to_usbdev(intf); ++ data = usb_get_intfdata(intf); ++ ++ fw_info = data->fw_info; ++ if (!fw_info) ++ return; ++ ++#if BTUSB_RPM ++ usb_disable_autosuspend(udev); ++#endif ++ ++ /* ++ * In order to reclaim fw data mem, we free fw_data immediately ++ * after download patch finished instead of here. ++ */ ++ kfree(fw_info->rcv_pkt); ++ kfree(fw_info->send_pkt); ++ kfree(fw_info); ++ ++ ++} ++ ++static struct usb_driver btusb_driver; ++ ++static struct usb_device_id btusb_table[] = { ++ #if 0 ++ { .match_flags = USB_DEVICE_ID_MATCH_VENDOR | ++ USB_DEVICE_ID_MATCH_INT_INFO, ++ .idVendor = 0xa69d, ++ .bInterfaceClass = 0xe0, ++ .bInterfaceSubClass = 0x01, ++ .bInterfaceProtocol = 0x01 }, ++ #endif ++ {USB_DEVICE_AND_INTERFACE_INFO(USB_VENDOR_ID_AIC, USB_PRODUCT_ID_AIC8801, 0xe0, 0x01,0x01)}, ++ {USB_DEVICE_AND_INTERFACE_INFO(USB_VENDOR_ID_AIC, USB_PRODUCT_ID_AIC8800D80, 0xe0, 0x01,0x01)}, ++ {USB_DEVICE_AND_INTERFACE_INFO(USB_VENDOR_ID_AIC, USB_PRODUCT_ID_AIC8800DC, 0xe0, 0x01,0x01)}, ++ {} ++}; ++ ++MODULE_DEVICE_TABLE(usb, btusb_table); ++ ++static int inc_tx(struct btusb_data *data) ++{ ++ unsigned long flags; ++ int rv; ++ ++ spin_lock_irqsave(&data->txlock, flags); ++ rv = test_bit(BTUSB_SUSPENDING, &data->flags); ++ if (!rv) ++ data->tx_in_flight++; ++ spin_unlock_irqrestore(&data->txlock, flags); ++ ++ return rv; ++} ++ ++void check_sco_event(struct urb *urb) ++{ ++ u8* opcode = (u8*)(urb->transfer_buffer); ++ u8 status; ++ static uint16_t sco_handle = 0; ++ uint16_t handle; ++ u8 air_mode = 0; ++ struct hci_dev *hdev = urb->context; ++#ifdef CONFIG_SCO_OVER_HCI ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ AIC_sco_card_t *pSCOSnd = data->pSCOSnd; ++#endif ++ ++ switch (*opcode) { ++ case HCI_EV_SYNC_CONN_COMPLETE: ++ AICBT_INFO("%s: HCI_EV_SYNC_CONN_COMPLETE(0x%02x)", __func__, *opcode); ++ status = *(opcode + 2); ++ sco_handle = *(opcode + 3) | *(opcode + 4) << 8; ++ air_mode = *(opcode + 18); ++ printk("%s status:%d,air_mode:%d \r\n", __func__, status,air_mode); ++ if (status == 0) { ++ hdev->conn_hash.sco_num++; ++ hdev->notify(hdev, 0); ++ //schedule_work(&data->work); ++ if (air_mode == 0x03) { ++ set_select_msbc(CODEC_MSBC); ++ } ++ } ++ break; ++ case HCI_EV_DISCONN_COMPLETE: ++ AICBT_INFO("%s: HCI_EV_DISCONN_COMPLETE(0x%02x)", __func__, *opcode); ++ status = *(opcode + 2); ++ handle = *(opcode + 3) | *(opcode + 4) << 8; ++ if (status == 0 && sco_handle == handle) { ++ hdev->conn_hash.sco_num--; ++ hdev->notify(hdev, 0); ++ set_select_msbc(CODEC_CVSD); ++ //schedule_work(&data->work); ++#ifdef CONFIG_SCO_OVER_HCI ++ if (test_bit(ALSA_CAPTURE_RUNNING, &pSCOSnd->states)) { ++ mod_timer(&snd_cap_timer.cap_timer,jiffies + msecs_to_jiffies(3)); ++ } ++#endif ++ } ++ break; ++ default: ++ AICBT_DBG("%s: event 0x%02x", __func__, *opcode); ++ break; ++ } ++} ++ ++#if (CONFIG_BLUEDROID == 0) ++#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) ++static inline void btusb_free_frags(struct btusb_data *data) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&data->rxlock, flags); ++ ++ kfree_skb(data->evt_skb); ++ data->evt_skb = NULL; ++ ++ kfree_skb(data->acl_skb); ++ data->acl_skb = NULL; ++ ++ kfree_skb(data->sco_skb); ++ data->sco_skb = NULL; ++ ++ spin_unlock_irqrestore(&data->rxlock, flags); ++} ++ ++static int btusb_recv_intr(struct btusb_data *data, void *buffer, int count) ++{ ++ struct sk_buff *skb; ++ int err = 0; ++ ++ spin_lock(&data->rxlock); ++ skb = data->evt_skb; ++ //printk("%s count %d\n", __func__, count); ++ ++#if 1 ++ while (count) { ++ int len; ++ ++ if (!skb) { ++ skb = bt_skb_alloc(HCI_MAX_EVENT_SIZE, GFP_ATOMIC); ++ if (!skb) { ++ err = -ENOMEM; ++ break; ++ } ++ ++ bt_cb(skb)->pkt_type = HCI_EVENT_PKT; ++ bt_cb(skb)->expect = HCI_EVENT_HDR_SIZE; ++ } ++ ++ len = min_t(uint, bt_cb(skb)->expect, count); ++ memcpy(skb_put(skb, len), buffer, len); ++ ++ count -= len; ++ buffer += len; ++ bt_cb(skb)->expect -= len; ++ ++ if (skb->len == HCI_EVENT_HDR_SIZE) { ++ /* Complete event header */ ++ bt_cb(skb)->expect = hci_event_hdr(skb)->plen; ++ ++ if (skb_tailroom(skb) < bt_cb(skb)->expect) { ++ kfree_skb(skb); ++ skb = NULL; ++ ++ err = -EILSEQ; ++ break; ++ } ++ } ++ ++ if (bt_cb(skb)->expect == 0) { ++ /* Complete frame */ ++ hci_recv_frame(data->hdev, skb); ++ skb = NULL; ++ } ++ } ++#endif ++ ++ data->evt_skb = skb; ++ spin_unlock(&data->rxlock); ++ ++ return err; ++} ++ ++static int btusb_recv_bulk(struct btusb_data *data, void *buffer, int count) ++{ ++ struct sk_buff *skb; ++ int err = 0; ++ ++ spin_lock(&data->rxlock); ++ skb = data->acl_skb; ++ ++ while (count) { ++ int len; ++ ++ if (!skb) { ++ skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC); ++ if (!skb) { ++ err = -ENOMEM; ++ break; ++ } ++ ++ bt_cb(skb)->pkt_type = HCI_ACLDATA_PKT; ++ bt_cb(skb)->expect = HCI_ACL_HDR_SIZE; ++ } ++ ++ len = min_t(uint, bt_cb(skb)->expect, count); ++ memcpy(skb_put(skb, len), buffer, len); ++ ++ count -= len; ++ buffer += len; ++ bt_cb(skb)->expect -= len; ++ ++ if (skb->len == HCI_ACL_HDR_SIZE) { ++ __le16 dlen = hci_acl_hdr(skb)->dlen; ++ ++ /* Complete ACL header */ ++ bt_cb(skb)->expect = __le16_to_cpu(dlen); ++ ++ if (skb_tailroom(skb) < bt_cb(skb)->expect) { ++ kfree_skb(skb); ++ skb = NULL; ++ ++ err = -EILSEQ; ++ break; ++ } ++ } ++ ++ if (bt_cb(skb)->expect == 0) { ++ /* Complete frame */ ++ hci_recv_frame(data->hdev, skb); ++ skb = NULL; ++ } ++ } ++ ++ data->acl_skb = skb; ++ spin_unlock(&data->rxlock); ++ ++ return err; ++} ++ ++static int btusb_recv_isoc(struct btusb_data *data, void *buffer, int count) ++{ ++ struct sk_buff *skb; ++ int err = 0; ++ ++ spin_lock(&data->rxlock); ++ skb = data->sco_skb; ++ ++ while (count) { ++ int len; ++ ++ if (!skb) { ++ skb = bt_skb_alloc(HCI_MAX_SCO_SIZE, GFP_ATOMIC); ++ if (!skb) { ++ err = -ENOMEM; ++ break; ++ } ++ ++ bt_cb(skb)->pkt_type = HCI_SCODATA_PKT; ++ bt_cb(skb)->expect = HCI_SCO_HDR_SIZE; ++ } ++ ++ len = min_t(uint, bt_cb(skb)->expect, count); ++ memcpy(skb_put(skb, len), buffer, len); ++ ++ count -= len; ++ buffer += len; ++ bt_cb(skb)->expect -= len; ++ ++ if (skb->len == HCI_SCO_HDR_SIZE) { ++ /* Complete SCO header */ ++ bt_cb(skb)->expect = hci_sco_hdr(skb)->dlen; ++ ++ if (skb_tailroom(skb) < bt_cb(skb)->expect) { ++ kfree_skb(skb); ++ skb = NULL; ++ ++ err = -EILSEQ; ++ break; ++ } ++ } ++ ++ if (bt_cb(skb)->expect == 0) { ++ /* Complete frame */ ++ hci_recv_frame(data->hdev, skb); ++ skb = NULL; ++ } ++ } ++ ++ data->sco_skb = skb; ++ spin_unlock(&data->rxlock); ++ ++ return err; ++} ++#endif ++#endif // (CONFIG_BLUEDROID == 0) ++ ++ ++static void btusb_intr_complete(struct urb *urb) ++{ ++ struct hci_dev *hdev = urb->context; ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ int err; ++ ++ AICBT_DBG("%s: urb %p status %d count %d ", __func__, ++ urb, urb->status, urb->actual_length); ++ ++ if (!test_bit(HCI_RUNNING, &hdev->flags)) { ++ printk("%s return \n", __func__); ++ return; ++ } ++ if (urb->status == 0) { ++ hdev->stat.byte_rx += urb->actual_length; ++ ++#if (CONFIG_BLUEDROID) || (HCI_VERSION_CODE < KERNEL_VERSION(3, 18, 0)) ++ if (hci_recv_fragment(hdev, HCI_EVENT_PKT, ++ urb->transfer_buffer, ++ urb->actual_length) < 0) { ++ AICBT_ERR("%s: Corrupted event packet", __func__); ++ hdev->stat.err_rx++; ++ } ++#else ++ if (btusb_recv_intr(data, urb->transfer_buffer, ++ urb->actual_length) < 0) { ++ AICBT_ERR("%s corrupted event packet", hdev->name); ++ hdev->stat.err_rx++; ++ } ++#endif ++ ++#ifdef CONFIG_SCO_OVER_HCI ++ check_sco_event(urb); ++#endif ++#ifdef CONFIG_USB_AIC_UART_SCO_DRIVER ++ check_sco_event(urb); ++#endif ++ ++ } ++ /* Avoid suspend failed when usb_kill_urb */ ++ else if(urb->status == -ENOENT) { ++ return; ++ } ++ ++ ++ if (!test_bit(BTUSB_INTR_RUNNING, &data->flags)) ++ return; ++ ++ usb_mark_last_busy(data->udev); ++ usb_anchor_urb(urb, &data->intr_anchor); ++ ++ err = usb_submit_urb(urb, GFP_ATOMIC); ++ if (err < 0) { ++ if (err != -EPERM && err != -ENODEV) ++ AICBT_ERR("%s: Failed to re-submit urb %p, err %d", ++ __func__, urb, err); ++ usb_unanchor_urb(urb); ++ } ++} ++ ++static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags) ++{ ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ struct urb *urb; ++ unsigned char *buf; ++ unsigned int pipe; ++ int err, size; ++ ++ if (!data->intr_ep) ++ return -ENODEV; ++ ++ urb = usb_alloc_urb(0, mem_flags); ++ if (!urb) ++ return -ENOMEM; ++ ++ size = le16_to_cpu(data->intr_ep->wMaxPacketSize); ++ ++ buf = kmalloc(size, mem_flags); ++ if (!buf) { ++ usb_free_urb(urb); ++ return -ENOMEM; ++ } ++ ++ AICBT_DBG("%s: mMaxPacketSize %d, bEndpointAddress 0x%02x", ++ __func__, size, data->intr_ep->bEndpointAddress); ++ ++ pipe = usb_rcvintpipe(data->udev, data->intr_ep->bEndpointAddress); ++ ++ usb_fill_int_urb(urb, data->udev, pipe, buf, size, ++ btusb_intr_complete, hdev, ++ data->intr_ep->bInterval); ++ ++ urb->transfer_flags |= URB_FREE_BUFFER; ++ ++ usb_anchor_urb(urb, &data->intr_anchor); ++ ++ err = usb_submit_urb(urb, mem_flags); ++ if (err < 0) { ++ AICBT_ERR("%s: Failed to submit urb %p, err %d", ++ __func__, urb, err); ++ usb_unanchor_urb(urb); ++ } ++ ++ usb_free_urb(urb); ++ ++ return err; ++} ++ ++static void btusb_bulk_complete(struct urb *urb) ++{ ++ struct hci_dev *hdev = urb->context; ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ int err; ++ ++ AICBT_DBG("%s: urb %p status %d count %d", ++ __func__, urb, urb->status, urb->actual_length); ++ ++ if (!test_bit(HCI_RUNNING, &hdev->flags)) { ++ printk("%s HCI_RUNNING\n", __func__); ++ return; ++ } ++ if (urb->status == 0) { ++ hdev->stat.byte_rx += urb->actual_length; ++ ++#if (CONFIG_BLUEDROID) || (HCI_VERSION_CODE < KERNEL_VERSION(3, 18, 0)) ++ if (hci_recv_fragment(hdev, HCI_ACLDATA_PKT, ++ urb->transfer_buffer, ++ urb->actual_length) < 0) { ++ AICBT_ERR("%s: Corrupted ACL packet", __func__); ++ hdev->stat.err_rx++; ++ } ++#else ++ if (data->recv_bulk(data, urb->transfer_buffer, ++ urb->actual_length) < 0) { ++ AICBT_ERR("%s Corrupted ACL packet", hdev->name); ++ hdev->stat.err_rx++; ++ } ++#endif ++ ++ } ++ /* Avoid suspend failed when usb_kill_urb */ ++ else if(urb->status == -ENOENT) { ++ printk("%s ENOENT\n", __func__); ++ return; ++ } ++ AICBT_DBG("%s: OUT", __func__); ++ ++ if (!test_bit(BTUSB_BULK_RUNNING, &data->flags)) { ++ printk("%s BTUSB_BULK_RUNNING\n", __func__); ++ return; ++ } ++ usb_anchor_urb(urb, &data->bulk_anchor); ++ usb_mark_last_busy(data->udev); ++ ++ //printk("LIULI bulk submit\n"); ++ err = usb_submit_urb(urb, GFP_ATOMIC); ++ if (err < 0) { ++ /* -EPERM: urb is being killed; ++ * -ENODEV: device got disconnected */ ++ if (err != -EPERM && err != -ENODEV) ++ AICBT_ERR("btusb_bulk_complete %s urb %p failed to resubmit (%d)", ++ hdev->name, urb, -err); ++ usb_unanchor_urb(urb); ++ } ++} ++ ++static int btusb_submit_bulk_urb(struct hci_dev *hdev, gfp_t mem_flags) ++{ ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ struct urb *urb; ++ unsigned char *buf; ++ unsigned int pipe; ++ int err, size = HCI_MAX_FRAME_SIZE; ++ ++ AICBT_DBG("%s: hdev name %s", __func__, hdev->name); ++ AICBT_DBG("%s: mMaxPacketSize %d, bEndpointAddress 0x%02x", ++ __func__, size, data->bulk_rx_ep->bEndpointAddress); ++ ++ if (!data->bulk_rx_ep) ++ return -ENODEV; ++ ++ urb = usb_alloc_urb(0, mem_flags); ++ if (!urb) ++ return -ENOMEM; ++ ++ buf = kmalloc(size, mem_flags); ++ if (!buf) { ++ usb_free_urb(urb); ++ return -ENOMEM; ++ } ++ ++ pipe = usb_rcvbulkpipe(data->udev, data->bulk_rx_ep->bEndpointAddress); ++ ++ usb_fill_bulk_urb(urb, data->udev, pipe, ++ buf, size, btusb_bulk_complete, hdev); ++ ++ urb->transfer_flags |= URB_FREE_BUFFER; ++ ++ usb_mark_last_busy(data->udev); ++ usb_anchor_urb(urb, &data->bulk_anchor); ++ ++ err = usb_submit_urb(urb, mem_flags); ++ if (err < 0) { ++ AICBT_ERR("%s: Failed to submit urb %p, err %d", __func__, urb, err); ++ usb_unanchor_urb(urb); ++ } ++ ++ usb_free_urb(urb); ++ ++ return err; ++} ++ ++static void btusb_isoc_complete(struct urb *urb) ++{ ++ struct hci_dev *hdev = urb->context; ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ int i, err; ++ unsigned int total_length = 0; ++ ++ AICBT_DBG("%s: urb %p status %d count %d", ++ __func__, urb, urb->status, urb->actual_length); ++ ++ if (!test_bit(HCI_RUNNING, &hdev->flags)) ++ return; ++ ++ if (urb->status == 0) { ++ for (i = 0; i < urb->number_of_packets; i++) { ++ unsigned int offset = urb->iso_frame_desc[i].offset; ++ unsigned int length = urb->iso_frame_desc[i].actual_length; ++ //u8 *data = (u8 *)(urb->transfer_buffer + offset); ++ //AICBT_DBG("%d,%d ,%x,%x,%x s %d.", ++ //offset, length, data[0], data[1],data[2],urb->iso_frame_desc[i].status); ++ ++ if(total_length >= urb->actual_length){ ++ AICBT_ERR("total_len >= actual_length ,return"); ++ break; ++ } ++ total_length += length; ++ ++ if (urb->iso_frame_desc[i].status) ++ continue; ++ ++ hdev->stat.byte_rx += length; ++ if(length){ ++#if (CONFIG_BLUEDROID) || (HCI_VERSION_CODE < KERNEL_VERSION(3, 18, 0)) ++ if (hci_recv_fragment(hdev, HCI_SCODATA_PKT, ++ urb->transfer_buffer + offset, ++ length) < 0) { ++ AICBT_ERR("%s: Corrupted SCO packet", __func__); ++ hdev->stat.err_rx++; ++ } ++#else ++ if (btusb_recv_isoc(data, urb->transfer_buffer + offset, ++ length) < 0) { ++ AICBT_ERR("%s corrupted SCO packet", ++ hdev->name); ++ hdev->stat.err_rx++; ++ } ++#endif ++ ++ } ++ } ++ } ++ /* Avoid suspend failed when usb_kill_urb */ ++ else if(urb->status == -ENOENT) { ++ return; ++ } ++ ++ ++ if (!test_bit(BTUSB_ISOC_RUNNING, &data->flags)) ++ return; ++ ++ usb_anchor_urb(urb, &data->isoc_anchor); ++ i = 0; ++retry: ++ err = usb_submit_urb(urb, GFP_ATOMIC); ++ if (err < 0) { ++ /* -EPERM: urb is being killed; ++ * -ENODEV: device got disconnected */ ++ if (err != -EPERM && err != -ENODEV) ++ AICBT_ERR("%s: Failed to re-sumbit urb %p, retry %d, err %d", ++ __func__, urb, i, err); ++ if (i < 10) { ++ i++; ++ mdelay(1); ++ goto retry; ++ } ++ ++ usb_unanchor_urb(urb); ++ } ++} ++ ++static inline void fill_isoc_descriptor(struct urb *urb, int len, int mtu) ++{ ++ int i, offset = 0; ++ ++ AICBT_DBG("%s: len %d mtu %d", __func__, len, mtu); ++ ++ for (i = 0; i < BTUSB_MAX_ISOC_FRAMES && len >= mtu; ++ i++, offset += mtu, len -= mtu) { ++ urb->iso_frame_desc[i].offset = offset; ++ urb->iso_frame_desc[i].length = mtu; ++ } ++ ++ if (len && i < BTUSB_MAX_ISOC_FRAMES) { ++ urb->iso_frame_desc[i].offset = offset; ++ urb->iso_frame_desc[i].length = len; ++ i++; ++ } ++ ++ urb->number_of_packets = i; ++} ++ ++static int btusb_submit_isoc_urb(struct hci_dev *hdev, gfp_t mem_flags) ++{ ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ struct urb *urb; ++ unsigned char *buf; ++ unsigned int pipe; ++ int err, size; ++ int interval; ++ ++ if (!data->isoc_rx_ep) ++ return -ENODEV; ++ AICBT_DBG("%s: mMaxPacketSize %d, bEndpointAddress 0x%02x", ++ __func__, size, data->isoc_rx_ep->bEndpointAddress); ++ ++ urb = usb_alloc_urb(BTUSB_MAX_ISOC_FRAMES, mem_flags); ++ if (!urb) ++ return -ENOMEM; ++ ++ size = le16_to_cpu(data->isoc_rx_ep->wMaxPacketSize) * ++ BTUSB_MAX_ISOC_FRAMES; ++ ++ buf = kmalloc(size, mem_flags); ++ if (!buf) { ++ usb_free_urb(urb); ++ return -ENOMEM; ++ } ++ ++ pipe = usb_rcvisocpipe(data->udev, data->isoc_rx_ep->bEndpointAddress); ++ ++ urb->dev = data->udev; ++ urb->pipe = pipe; ++ urb->context = hdev; ++ urb->complete = btusb_isoc_complete; ++ if (urb->dev->speed == USB_SPEED_HIGH || urb->dev->speed >= USB_SPEED_SUPER) { ++ /* make sure interval is within allowed range */ ++ interval = clamp((int)data->isoc_rx_ep->bInterval, 1, 16); ++ urb->interval = 1 << (interval - 1); ++ } else { ++ urb->interval = data->isoc_rx_ep->bInterval; ++ } ++ ++ AICBT_INFO("urb->interval %d \r\n", urb->interval); ++ ++ urb->transfer_flags = URB_FREE_BUFFER | URB_ISO_ASAP; ++ urb->transfer_buffer = buf; ++ urb->transfer_buffer_length = size; ++ ++ fill_isoc_descriptor(urb, size, ++ le16_to_cpu(data->isoc_rx_ep->wMaxPacketSize)); ++ ++ usb_anchor_urb(urb, &data->isoc_anchor); ++ ++ err = usb_submit_urb(urb, mem_flags); ++ if (err < 0) { ++ AICBT_ERR("%s: Failed to submit urb %p, err %d", __func__, urb, err); ++ usb_unanchor_urb(urb); ++ } ++ ++ usb_free_urb(urb); ++ ++ return err; ++} ++ ++static void btusb_tx_complete(struct urb *urb) ++{ ++ struct sk_buff *skb = urb->context; ++ struct hci_dev *hdev = (struct hci_dev *) skb->dev; ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ ++ if (!test_bit(HCI_RUNNING, &hdev->flags)) ++ goto done; ++ ++ if (!urb->status) ++ hdev->stat.byte_tx += urb->transfer_buffer_length; ++ else ++ hdev->stat.err_tx++; ++ ++done: ++ spin_lock(&data->txlock); ++ data->tx_in_flight--; ++ spin_unlock(&data->txlock); ++ ++ kfree(urb->setup_packet); ++ ++ kfree_skb(skb); ++} ++ ++static void btusb_isoc_tx_complete(struct urb *urb) ++{ ++ struct sk_buff *skb = urb->context; ++ struct hci_dev *hdev = (struct hci_dev *) skb->dev; ++ ++ AICBT_DBG("%s: urb %p status %d count %d", ++ __func__, urb, urb->status, urb->actual_length); ++ ++ if (skb && hdev) { ++ if (!test_bit(HCI_RUNNING, &hdev->flags)) ++ goto done; ++ ++ if (!urb->status) ++ hdev->stat.byte_tx += urb->transfer_buffer_length; ++ else ++ hdev->stat.err_tx++; ++ } else ++ AICBT_ERR("%s: skb 0x%p hdev 0x%p", __func__, skb, hdev); ++ ++done: ++ kfree(urb->setup_packet); ++ ++ kfree_skb(skb); ++} ++ ++#if (CONFIG_BLUEDROID == 0) ++#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 0, 9) ++static int btusb_shutdown(struct hci_dev *hdev) ++{ ++ struct sk_buff *skb; ++ printk("aic %s\n", __func__); ++ ++ skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0, NULL, HCI_INIT_TIMEOUT); ++ if (IS_ERR(skb)) { ++ printk("HCI reset during shutdown failed\n"); ++ return PTR_ERR(skb); ++ } ++ kfree_skb(skb); ++ ++ return 0; ++} ++#endif ++#endif //(CONFIG_BLUEDROID == 0) ++ ++static int btusb_open(struct hci_dev *hdev) ++{ ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ int err = 0; ++ ++ AICBT_INFO("%s: Start", __func__); ++ ++ err = usb_autopm_get_interface(data->intf); ++ if (err < 0) ++ return err; ++ ++ data->intf->needs_remote_wakeup = 1; ++ ++#if (CONFIG_BLUEDROID == 0) ++ //err = download_patch(data->fw_info,1); ++ printk(" download_patch %d", err); ++ if (err < 0) { ++ goto failed; ++ } ++#endif ++ ++ ++ if (test_and_set_bit(HCI_RUNNING, &hdev->flags)){ ++ goto done; ++ } ++ ++ if (test_and_set_bit(BTUSB_INTR_RUNNING, &data->flags)){ ++ goto done; ++ } ++ ++ err = btusb_submit_intr_urb(hdev, GFP_KERNEL); ++ if (err < 0) ++ goto failed; ++ ++ err = btusb_submit_bulk_urb(hdev, GFP_KERNEL); ++ if (err < 0) { ++ mdelay(URB_CANCELING_DELAY_MS); ++ usb_kill_anchored_urbs(&data->intr_anchor); ++ goto failed; ++ } ++ ++ set_bit(BTUSB_BULK_RUNNING, &data->flags); ++ btusb_submit_bulk_urb(hdev, GFP_KERNEL); ++ ++done: ++ usb_autopm_put_interface(data->intf); ++ AICBT_INFO("%s: End", __func__); ++ return 0; ++ ++failed: ++ clear_bit(BTUSB_INTR_RUNNING, &data->flags); ++ clear_bit(HCI_RUNNING, &hdev->flags); ++ usb_autopm_put_interface(data->intf); ++ AICBT_ERR("%s: Failed", __func__); ++ return err; ++} ++ ++static void btusb_stop_traffic(struct btusb_data *data) ++{ ++ mdelay(URB_CANCELING_DELAY_MS); ++ usb_kill_anchored_urbs(&data->intr_anchor); ++ usb_kill_anchored_urbs(&data->bulk_anchor); ++ usb_kill_anchored_urbs(&data->isoc_anchor); ++} ++ ++static int btusb_close(struct hci_dev *hdev) ++{ ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++#if (CONFIG_BLUEDROID) || (HCI_VERSION_CODE < KERNEL_VERSION(4, 1, 0)) ++ int i; ++#endif ++ int err; ++ ++ AICBT_INFO("%s: hci running %lu", __func__, hdev->flags & HCI_RUNNING); ++ ++ if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)){ ++ return 0; ++ } ++ ++ if (!test_and_clear_bit(BTUSB_INTR_RUNNING, &data->flags)){ ++ return 0; ++ } ++ ++#if (CONFIG_BLUEDROID) || (HCI_VERSION_CODE < KERNEL_VERSION(4, 1, 0)) ++ for (i = 0; i < NUM_REASSEMBLY; i++) { ++ if (hdev->reassembly[i]) { ++ AICBT_DBG("%s: free ressembly[%d]", __func__, i); ++ kfree_skb(hdev->reassembly[i]); ++ hdev->reassembly[i] = NULL; ++ } ++ } ++#endif ++ ++ cancel_work_sync(&data->work); ++ cancel_work_sync(&data->waker); ++ ++ clear_bit(BTUSB_ISOC_RUNNING, &data->flags); ++ clear_bit(BTUSB_BULK_RUNNING, &data->flags); ++ clear_bit(BTUSB_INTR_RUNNING, &data->flags); ++ ++ btusb_stop_traffic(data); ++ err = usb_autopm_get_interface(data->intf); ++ if (err < 0) ++ goto failed; ++ ++ data->intf->needs_remote_wakeup = 0; ++ usb_autopm_put_interface(data->intf); ++ ++failed: ++ mdelay(URB_CANCELING_DELAY_MS); ++ usb_scuttle_anchored_urbs(&data->deferred); ++ return 0; ++} ++ ++static int btusb_flush(struct hci_dev *hdev) ++{ ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ ++ AICBT_DBG("%s", __func__); ++ ++ mdelay(URB_CANCELING_DELAY_MS); ++ usb_kill_anchored_urbs(&data->tx_anchor); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_SCO_OVER_HCI ++static void btusb_isoc_snd_tx_complete(struct urb *urb); ++ ++static int snd_send_sco_frame(struct sk_buff *skb) ++{ ++ struct hci_dev *hdev = (struct hci_dev *) skb->dev; ++ ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ //struct usb_ctrlrequest *dr; ++ struct urb *urb; ++ unsigned int pipe; ++ int err; ++ ++ AICBT_DBG("%s:pkt type %d, packet_len : %d", ++ __func__,bt_cb(skb)->pkt_type, skb->len); ++ ++ if (!hdev && !test_bit(HCI_RUNNING, &hdev->flags)) ++ return -EBUSY; ++ ++ if (!data->isoc_tx_ep || hdev->conn_hash.sco_num < 1) { ++ kfree(skb); ++ return -ENODEV; ++ } ++ ++ urb = usb_alloc_urb(BTUSB_MAX_ISOC_FRAMES, GFP_ATOMIC); ++ if (!urb) { ++ AICBT_ERR("%s: Failed to allocate mem for sco pkts", __func__); ++ kfree(skb); ++ return -ENOMEM; ++ } ++ ++ pipe = usb_sndisocpipe(data->udev, data->isoc_tx_ep->bEndpointAddress); ++ ++ usb_fill_int_urb(urb, data->udev, pipe, ++ skb->data, skb->len, btusb_isoc_snd_tx_complete, ++ skb, data->isoc_tx_ep->bInterval); ++ ++ urb->transfer_flags = URB_ISO_ASAP; ++ ++ fill_isoc_descriptor(urb, skb->len, ++ le16_to_cpu(data->isoc_tx_ep->wMaxPacketSize)); ++ ++ hdev->stat.sco_tx++; ++ ++ usb_anchor_urb(urb, &data->tx_anchor); ++ ++ err = usb_submit_urb(urb, GFP_ATOMIC); ++ if (err < 0) { ++ AICBT_ERR("%s: Failed to submit urb %p, pkt type %d, err %d", ++ __func__, urb, bt_cb(skb)->pkt_type, err); ++ kfree(urb->setup_packet); ++ usb_unanchor_urb(urb); ++ } else ++ usb_mark_last_busy(data->udev); ++ usb_free_urb(urb); ++ ++ return err; ++ ++} ++ ++static bool snd_copy_send_sco_data( AIC_sco_card_t *pSCOSnd) ++{ ++ struct snd_pcm_runtime *runtime = pSCOSnd->playback.substream->runtime; ++ unsigned int frame_bytes = 2, frames1; ++ const u8 *source; ++ ++ snd_pcm_uframes_t period_size = runtime->period_size; ++ int i, count; ++ u8 buffer[period_size * 3]; ++ int sco_packet_bytes = pSCOSnd->playback.sco_packet_bytes; ++ struct sk_buff *skb; ++ ++ count = frames_to_bytes(runtime, period_size)/sco_packet_bytes; ++ skb = bt_skb_alloc(((sco_packet_bytes + HCI_SCO_HDR_SIZE) * count), GFP_ATOMIC); ++ skb->dev = (void *)hci_dev_get(0); ++ bt_cb(skb)->pkt_type = HCI_SCODATA_PKT; ++ skb_put(skb, ((sco_packet_bytes + HCI_SCO_HDR_SIZE) * count)); ++ if(!skb) ++ return false; ++ ++ AICBT_DBG("%s, buffer_pos:%d sco_handle:%d sco_packet_bytes:%d count:%d", __FUNCTION__, pSCOSnd->playback.buffer_pos, pSCOSnd->usb_data->sco_handle, ++ sco_packet_bytes, count); ++ ++ source = runtime->dma_area + pSCOSnd->playback.buffer_pos * frame_bytes; ++ ++ if (pSCOSnd->playback.buffer_pos + period_size <= runtime->buffer_size) { ++ memcpy(buffer, source, period_size * frame_bytes); ++ } else { ++ /* wrap around at end of ring buffer */ ++ frames1 = runtime->buffer_size - pSCOSnd->playback.buffer_pos; ++ memcpy(buffer, source, frames1 * frame_bytes); ++ memcpy(&buffer[frames1 * frame_bytes], ++ runtime->dma_area, (period_size - frames1) * frame_bytes); ++ } ++ ++ pSCOSnd->playback.buffer_pos += period_size; ++ if ( pSCOSnd->playback.buffer_pos >= runtime->buffer_size) ++ pSCOSnd->playback.buffer_pos -= runtime->buffer_size; ++ ++ for(i = 0; i < count; i++) { ++ *((__u16 *)(skb->data + i * (sco_packet_bytes + HCI_SCO_HDR_SIZE))) = pSCOSnd->usb_data->sco_handle; ++ *((__u8 *)(skb->data + i*(sco_packet_bytes + HCI_SCO_HDR_SIZE) + 2)) = sco_packet_bytes; ++ memcpy((skb->data + i * (sco_packet_bytes + HCI_SCO_HDR_SIZE) + HCI_SCO_HDR_SIZE), ++ &buffer[sco_packet_bytes * i], sco_packet_bytes); ++ } ++ ++ if(test_bit(ALSA_PLAYBACK_RUNNING, &pSCOSnd->states)) { ++ snd_pcm_period_elapsed(pSCOSnd->playback.substream); ++ } ++ snd_send_sco_frame(skb); ++ return true; ++} ++ ++static void btusb_isoc_snd_tx_complete(struct urb *urb) ++{ ++ struct sk_buff *skb = urb->context; ++ struct hci_dev *hdev = (struct hci_dev *) skb->dev; ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ AIC_sco_card_t *pSCOSnd = data->pSCOSnd; ++ ++ AICBT_DBG("%s: status %d count %d", ++ __func__,urb->status, urb->actual_length); ++ ++ if (skb && hdev) { ++ if (!test_bit(HCI_RUNNING, &hdev->flags)) ++ goto done; ++ ++ if (!urb->status) ++ hdev->stat.byte_tx += urb->transfer_buffer_length; ++ else ++ hdev->stat.err_tx++; ++ } else ++ AICBT_ERR("%s: skb 0x%p hdev 0x%p", __func__, skb, hdev); ++ ++done: ++ kfree(urb->setup_packet); ++ kfree_skb(skb); ++ if(test_bit(ALSA_PLAYBACK_RUNNING, &pSCOSnd->states)){ ++ snd_copy_send_sco_data(pSCOSnd); ++ //schedule_work(&pSCOSnd->send_sco_work); ++ } ++} ++ ++static void playback_work(struct work_struct *work) ++{ ++ AIC_sco_card_t *pSCOSnd = container_of(work, AIC_sco_card_t, send_sco_work); ++ ++ snd_copy_send_sco_data(pSCOSnd); ++} ++ ++#endif ++ ++#if (CONFIG_BLUEDROID) || (HCI_VERSION_CODE < KERNEL_VERSION(3, 13, 0)) ++int btusb_send_frame(struct sk_buff *skb) ++{ ++ struct hci_dev *hdev = (struct hci_dev *) skb->dev; ++#else ++int btusb_send_frame(struct hci_dev *hdev, struct sk_buff *skb) ++{ ++#endif ++ //struct hci_dev *hdev = (struct hci_dev *) skb->dev; ++ ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ struct usb_ctrlrequest *dr; ++ struct urb *urb; ++ unsigned int pipe; ++ int err = 0; ++ int retries = 0; ++ u16 *opcode = NULL; ++ ++ AICBT_DBG("%s: hdev %p, btusb data %p, pkt type %d", ++ __func__, hdev, data, bt_cb(skb)->pkt_type); ++ ++ //printk("aic %d %d\r\n", bt_cb(skb)->pkt_type, skb->len); ++ if (!test_bit(HCI_RUNNING, &hdev->flags)) ++ return -EBUSY; ++ ++#if (CONFIG_BLUEDROID == 0) ++#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 13, 0) ++ skb->dev = (void *)hdev; ++#endif ++#endif ++ ++ switch (bt_cb(skb)->pkt_type) { ++ case HCI_COMMAND_PKT: ++ print_command(skb); ++ urb = usb_alloc_urb(0, GFP_ATOMIC); ++ if (!urb) ++ return -ENOMEM; ++ ++ dr = kmalloc(sizeof(*dr), GFP_ATOMIC); ++ if (!dr) { ++ usb_free_urb(urb); ++ return -ENOMEM; ++ } ++ ++ dr->bRequestType = data->cmdreq_type; ++ dr->bRequest = 0; ++ dr->wIndex = 0; ++ dr->wValue = 0; ++ dr->wLength = __cpu_to_le16(skb->len); ++ ++ pipe = usb_sndctrlpipe(data->udev, 0x00); ++ ++ usb_fill_control_urb(urb, data->udev, pipe, (void *) dr, ++ skb->data, skb->len, btusb_tx_complete, skb); ++ ++ hdev->stat.cmd_tx++; ++ break; ++ ++ case HCI_ACLDATA_PKT: ++ if (bt_cb(skb)->pkt_type == HCI_COMMAND_PKT) { ++ print_command(skb); ++ opcode = (u16*)(skb->data); ++ printk("aic cmd:0x%04x", *opcode); ++ } else { ++ print_acl(skb, 1); ++ } ++ if (!data->bulk_tx_ep) ++ return -ENODEV; ++ ++ urb = usb_alloc_urb(0, GFP_ATOMIC); ++ if (!urb) ++ return -ENOMEM; ++ ++ pipe = usb_sndbulkpipe(data->udev, ++ data->bulk_tx_ep->bEndpointAddress); ++ ++ usb_fill_bulk_urb(urb, data->udev, pipe, ++ skb->data, skb->len, btusb_tx_complete, skb); ++ ++ hdev->stat.acl_tx++; ++ break; ++ ++ case HCI_SCODATA_PKT: ++ print_sco(skb, 1); ++ if (!data->isoc_tx_ep || SCO_NUM < 1) { ++ kfree(skb); ++ return -ENODEV; ++ } ++ ++ urb = usb_alloc_urb(BTUSB_MAX_ISOC_FRAMES, GFP_ATOMIC); ++ if (!urb) { ++ AICBT_ERR("%s: Failed to allocate mem for sco pkts", __func__); ++ kfree(skb); ++ return -ENOMEM; ++ } ++ ++ pipe = usb_sndisocpipe(data->udev, data->isoc_tx_ep->bEndpointAddress); ++ ++ usb_fill_int_urb(urb, data->udev, pipe, ++ skb->data, skb->len, btusb_isoc_tx_complete, ++ skb, data->isoc_tx_ep->bInterval); ++ ++ urb->transfer_flags = URB_ISO_ASAP; ++ ++ fill_isoc_descriptor(urb, skb->len, ++ le16_to_cpu(data->isoc_tx_ep->wMaxPacketSize)); ++ ++ hdev->stat.sco_tx++; ++ goto skip_waking; ++ ++ default: ++ return -EILSEQ; ++ } ++ ++ err = inc_tx(data); ++ if (err) { ++ usb_anchor_urb(urb, &data->deferred); ++ schedule_work(&data->waker); ++ err = 0; ++ goto done; ++ } ++ ++skip_waking: ++ usb_anchor_urb(urb, &data->tx_anchor); ++retry: ++ err = usb_submit_urb(urb, GFP_ATOMIC); ++ if (err < 0) { ++ AICBT_ERR("%s: Failed to submit urb %p, pkt type %d, err %d, retries %d", ++ __func__, urb, bt_cb(skb)->pkt_type, err, retries); ++ if ((bt_cb(skb)->pkt_type != HCI_SCODATA_PKT) && (retries < 10)) { ++ mdelay(1); ++ ++ if (bt_cb(skb)->pkt_type == HCI_COMMAND_PKT) ++ print_error_command(skb); ++ retries++; ++ goto retry; ++ } ++ kfree(urb->setup_packet); ++ usb_unanchor_urb(urb); ++ } else ++ usb_mark_last_busy(data->udev); ++ usb_free_urb(urb); ++ ++done: ++ return err; ++} ++ ++#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0) ++static void btusb_destruct(struct hci_dev *hdev) ++{ ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ ++ AICBT_DBG("%s: name %s", __func__, hdev->name); ++ ++ kfree(data); ++} ++#endif ++ ++static void btusb_notify(struct hci_dev *hdev, unsigned int evt) ++{ ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ ++ AICBT_DBG("%s: name %s, evt %d", __func__, hdev->name, evt); ++ ++ if (SCO_NUM != data->sco_num) { ++ data->sco_num = SCO_NUM; ++ schedule_work(&data->work); ++ } ++} ++ ++static inline int set_isoc_interface(struct hci_dev *hdev, int altsetting) ++{ ++ struct btusb_data *data = GET_DRV_DATA(hdev); ++ struct usb_interface *intf = data->isoc; ++ struct usb_endpoint_descriptor *ep_desc; ++ int i, err; ++ ++ if (!data->isoc) ++ return -ENODEV; ++ ++ err = usb_set_interface(data->udev, 1, altsetting); ++ if (err < 0) { ++ AICBT_ERR("%s: Failed to set interface, altsetting %d, err %d", ++ __func__, altsetting, err); ++ return err; ++ } ++ ++ data->isoc_altsetting = altsetting; ++ ++ data->isoc_tx_ep = NULL; ++ data->isoc_rx_ep = NULL; ++ ++ for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) { ++ ep_desc = &intf->cur_altsetting->endpoint[i].desc; ++ ++ if (!data->isoc_tx_ep && usb_endpoint_is_isoc_out(ep_desc)) { ++ data->isoc_tx_ep = ep_desc; ++ continue; ++ } ++ ++ if (!data->isoc_rx_ep && usb_endpoint_is_isoc_in(ep_desc)) { ++ data->isoc_rx_ep = ep_desc; ++ continue; ++ } ++ } ++ ++ if (!data->isoc_tx_ep || !data->isoc_rx_ep) { ++ AICBT_ERR("%s: Invalid SCO descriptors", __func__); ++ return -ENODEV; ++ } ++ ++ AICBT_ERR("%s: hdev->reassembly implemant\r\n", ++ __func__); ++ ++#if CONFIG_BLUEDROID ++ if(hdev->reassembly[HCI_SCODATA_PKT - 1]) { ++ kfree_skb(hdev->reassembly[HCI_SCODATA_PKT - 1]); ++ hdev->reassembly[HCI_SCODATA_PKT - 1] = NULL; ++ } ++#endif ++ return 0; ++} ++ ++static void set_select_msbc(enum CODEC_TYPE type) ++{ ++ printk("%s codec type = %d", __func__, (int)type); ++ codec_type = type; ++} ++ ++static enum CODEC_TYPE check_select_msbc(void) ++{ ++ return codec_type; ++} ++ ++#ifdef CONFIG_SCO_OVER_HCI ++static int check_controller_support_msbc( struct usb_device *udev) ++{ ++ //fix this in the future,when new card support msbc decode and encode ++ AICBT_INFO("%s:pid = 0x%02x, vid = 0x%02x",__func__,udev->descriptor.idProduct, udev->descriptor.idVendor); ++ switch (udev->descriptor.idProduct) { ++ ++ default: ++ return 0; ++ } ++ return 0; ++} ++#endif ++static void btusb_work(struct work_struct *work) ++{ ++ struct btusb_data *data = container_of(work, struct btusb_data, work); ++ struct hci_dev *hdev = data->hdev; ++ int err; ++ int new_alts; ++#ifdef CONFIG_SCO_OVER_HCI ++ AIC_sco_card_t *pSCOSnd = data->pSCOSnd; ++#endif ++ printk("%s data->sco_num:%d \r\n", __func__, data->sco_num); ++ ++ if (data->sco_num > 0) { ++ if (!test_bit(BTUSB_DID_ISO_RESUME, &data->flags)) { ++ err = usb_autopm_get_interface(data->isoc ? data->isoc : data->intf); ++ if (err < 0) { ++ clear_bit(BTUSB_ISOC_RUNNING, &data->flags); ++ mdelay(URB_CANCELING_DELAY_MS); ++ usb_kill_anchored_urbs(&data->isoc_anchor); ++ printk("%s usb_kill_anchored_urbs after \r\n", __func__); ++ return; ++ } ++ ++ set_bit(BTUSB_DID_ISO_RESUME, &data->flags); ++ } ++ ++ hdev->voice_setting = 93; ++ AICBT_INFO("%s voice settings = 0x%04x", __func__, hdev->voice_setting); ++ if (!(hdev->voice_setting & 0x0003)) { ++ if(data->sco_num == 1) ++ if(check_select_msbc()) { ++ new_alts = 1; ++ } else { ++ new_alts = 2; ++ } ++ else { ++ AICBT_INFO("%s: we don't support mutiple sco link for cvsd", __func__); ++ return; ++ } ++ } else{ ++ if(check_select_msbc()) { ++ if(data->sco_num == 1) ++ new_alts = 1; ++ else { ++ AICBT_INFO("%s: we don't support mutiple sco link for msbc", __func__); ++ return; ++ } ++ } else { ++ new_alts = 2; ++ } ++ } ++ if (data->isoc_altsetting != new_alts) { ++ ++ clear_bit(BTUSB_ISOC_RUNNING, &data->flags); ++ mdelay(URB_CANCELING_DELAY_MS); ++ usb_kill_anchored_urbs(&data->isoc_anchor); ++ ++ printk("%s set_isoc_interface in \r\n", __func__); ++ if (set_isoc_interface(hdev, new_alts) < 0) ++ return; ++ ++ } ++ ++ printk("%s set_isoc_interface out \r\n", __func__); ++ ++ if (!test_and_set_bit(BTUSB_ISOC_RUNNING, &data->flags)) { ++ printk("%s btusb_submit_isoc_urb\r\n", __func__); ++ if (btusb_submit_isoc_urb(hdev, GFP_KERNEL) < 0) ++ clear_bit(BTUSB_ISOC_RUNNING, &data->flags); ++ else ++ btusb_submit_isoc_urb(hdev, GFP_KERNEL); ++ } ++#ifdef CONFIG_SCO_OVER_HCI ++ if(test_bit(BTUSB_ISOC_RUNNING, &data->flags)) { ++ set_bit(USB_CAPTURE_RUNNING, &data->pSCOSnd->states); ++ set_bit(USB_PLAYBACK_RUNNING, &data->pSCOSnd->states); ++ } ++ if (test_bit(ALSA_PLAYBACK_RUNNING, &pSCOSnd->states)) { ++ schedule_work(&pSCOSnd->send_sco_work); ++ AICBT_INFO("%s: play_timer restart", __func__); ++ } ++#endif ++ } else { ++ clear_bit(BTUSB_ISOC_RUNNING, &data->flags); ++#ifdef CONFIG_SCO_OVER_HCI ++ clear_bit(USB_CAPTURE_RUNNING, &data->pSCOSnd->states); ++ clear_bit(USB_PLAYBACK_RUNNING, &data->pSCOSnd->states); ++ //AIC_sco_card_t *pSCOSnd = data->pSCOSnd; ++ if (test_bit(ALSA_PLAYBACK_RUNNING, &pSCOSnd->states)) { ++ mod_timer(&snd_cap_timer.play_timer,jiffies + msecs_to_jiffies(30)); ++ AICBT_INFO("%s: play_timer start", __func__); ++ } ++#endif ++ mdelay(URB_CANCELING_DELAY_MS); ++ usb_kill_anchored_urbs(&data->isoc_anchor); ++ ++ set_isoc_interface(hdev, 0); ++ if (test_and_clear_bit(BTUSB_DID_ISO_RESUME, &data->flags)) ++ usb_autopm_put_interface(data->isoc ? data->isoc : data->intf); ++ } ++} ++ ++static void btusb_waker(struct work_struct *work) ++{ ++ struct btusb_data *data = container_of(work, struct btusb_data, waker); ++ int err; ++ ++ AICBT_DBG("%s", __func__); ++ ++ err = usb_autopm_get_interface(data->intf); ++ if (err < 0) ++ return; ++ ++ usb_autopm_put_interface(data->intf); ++} ++ ++int bt_pm_notify(struct notifier_block *notifier, ulong pm_event, void *unused) ++{ ++ struct btusb_data *data; ++ firmware_info *fw_info; ++ struct usb_device *udev; ++ ++ AICBT_INFO("%s: pm event %ld", __func__, pm_event); ++ ++ data = container_of(notifier, struct btusb_data, pm_notifier); ++ fw_info = data->fw_info; ++ udev = fw_info->udev; ++ ++ switch (pm_event) { ++ case PM_SUSPEND_PREPARE: ++ case PM_HIBERNATION_PREPARE: ++#if 0 ++ patch_entry->fw_len = load_firmware(fw_info, &patch_entry->fw_cache); ++ if (patch_entry->fw_len <= 0) { ++ /* We may encount failure in loading firmware, just give a warning */ ++ AICBT_WARN("%s: Failed to load firmware", __func__); ++ } ++#endif ++ if (!device_may_wakeup(&udev->dev)) { ++#if (CONFIG_RESET_RESUME || CONFIG_BLUEDROID) ++ AICBT_INFO("%s:remote wakeup not supported, reset resume supported", __func__); ++#else ++ fw_info->intf->needs_binding = 1; ++ AICBT_INFO("%s:remote wakeup not supported, binding needed", __func__); ++#endif ++ } ++ break; ++ ++ case PM_POST_SUSPEND: ++ case PM_POST_HIBERNATION: ++ case PM_POST_RESTORE: ++#if 0 ++ /* Reclaim fw buffer when bt usb resumed */ ++ if (patch_entry->fw_len > 0) { ++ kfree(patch_entry->fw_cache); ++ patch_entry->fw_cache = NULL; ++ patch_entry->fw_len = 0; ++ } ++#endif ++ ++#if BTUSB_RPM ++ usb_disable_autosuspend(udev); ++ usb_enable_autosuspend(udev); ++ pm_runtime_set_autosuspend_delay(&(udev->dev), 2000); ++#endif ++ break; ++ ++ default: ++ break; ++ } ++ ++ return NOTIFY_DONE; ++} ++ ++int bt_reboot_notify(struct notifier_block *notifier, ulong pm_event, void *unused) ++{ ++ struct btusb_data *data; ++ firmware_info *fw_info; ++ struct usb_device *udev; ++ ++ AICBT_INFO("%s: pm event %ld", __func__, pm_event); ++ ++ data = container_of(notifier, struct btusb_data, reboot_notifier); ++ fw_info = data->fw_info; ++ udev = fw_info->udev; ++ ++ switch (pm_event) { ++ case SYS_DOWN: ++ AICBT_DBG("%s:system down or restart", __func__); ++ break; ++ ++ case SYS_HALT: ++ case SYS_POWER_OFF: ++#if SUSPNED_DW_FW ++ cancel_work_sync(&data->work); ++ ++ btusb_stop_traffic(data); ++ mdelay(URB_CANCELING_DELAY_MS); ++ usb_kill_anchored_urbs(&data->tx_anchor); ++ ++ ++ if(fw_info_4_suspend) { ++ download_suspend_patch(fw_info_4_suspend,1); ++ } ++ else ++ AICBT_ERR("%s: Failed to download suspend fw", __func__); ++#endif ++ ++#ifdef SET_WAKEUP_DEVICE ++ set_wakeup_device_from_conf(fw_info_4_suspend); ++#endif ++ AICBT_DBG("%s:system halt or power off", __func__); ++ break; ++ ++ default: ++ break; ++ } ++ ++ return NOTIFY_DONE; ++} ++ ++ ++#ifdef CONFIG_SCO_OVER_HCI ++#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) ++void aic_snd_capture_timeout(ulong data) ++#else ++void aic_snd_capture_timeout(struct timer_list *t) ++#endif ++{ ++ uint8_t null_data[255]; ++ struct btusb_data *usb_data; ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) ++ usb_data = (struct btusb_data *)data; ++#else ++ usb_data = &snd_cap_timer.snd_usb_data; ++#endif ++ aic_copy_capture_data_to_alsa(usb_data, null_data, snd_cap_timer.snd_sco_length/2); ++ //printk("%s enter\r\n", __func__); ++ mod_timer(&snd_cap_timer.cap_timer,jiffies + msecs_to_jiffies(3)); ++} ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) ++void aic_snd_play_timeout(ulong data) ++#else ++void aic_snd_play_timeout(struct timer_list *t) ++#endif ++{ ++ AIC_sco_card_t *pSCOSnd; ++ struct snd_pcm_runtime *runtime; ++ snd_pcm_uframes_t period_size; ++ int count; ++ struct btusb_data *usb_data; ++ int sco_packet_bytes; ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) ++ usb_data = (struct btusb_data *)data; ++#else ++ usb_data = &snd_cap_timer.snd_usb_data; ++#endif ++ pSCOSnd = usb_data->pSCOSnd; ++ ++ if(test_bit(USB_PLAYBACK_RUNNING, &pSCOSnd->states)) { ++ return; ++ } ++ ++ if(!test_bit(ALSA_PLAYBACK_RUNNING, &pSCOSnd->states)) { ++ return; ++ } ++ ++ runtime = pSCOSnd->playback.substream->runtime; ++ period_size = runtime->period_size; ++ sco_packet_bytes = pSCOSnd->playback.sco_packet_bytes; ++ count = frames_to_bytes(runtime, period_size)/sco_packet_bytes; ++ ++ pSCOSnd->playback.buffer_pos += period_size; ++ if ( pSCOSnd->playback.buffer_pos >= runtime->buffer_size) ++ pSCOSnd->playback.buffer_pos -= runtime->buffer_size; ++ ++ if(test_bit(ALSA_PLAYBACK_RUNNING, &pSCOSnd->states)) { ++ snd_pcm_period_elapsed(pSCOSnd->playback.substream); ++ } ++ //AICBT_DBG("%s,play_timer restart buffer_pos:%d sco_handle:%d sco_packet_bytes:%d count:%d", __FUNCTION__, pSCOSnd->playback.buffer_pos, pSCOSnd->usb_data->sco_handle, ++ //sco_packet_bytes, count); ++ mod_timer(&snd_cap_timer.play_timer,jiffies + msecs_to_jiffies(3*count)); ++} ++ ++static const struct snd_pcm_hardware snd_card_sco_capture_default = ++{ ++ .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_NONINTERLEAVED | ++ SNDRV_PCM_ACCESS_RW_INTERLEAVED | SNDRV_PCM_INFO_FIFO_IN_FRAMES), ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, ++ .rates = (SNDRV_PCM_RATE_8000), ++ .rate_min = 8000, ++ .rate_max = 8000, ++ .channels_min = 1, ++ .channels_max = 1, ++ .buffer_bytes_max = 8 * 768, ++ .period_bytes_min = 48, ++ .period_bytes_max = 768, ++ .periods_min = 1, ++ .periods_max = 8, ++ .fifo_size = 8, ++ ++}; ++ ++static int snd_sco_capture_pcm_open(struct snd_pcm_substream * substream) ++{ ++ AIC_sco_card_t *pSCOSnd = substream->private_data; ++ ++ AICBT_INFO("%s", __FUNCTION__); ++ pSCOSnd->capture.substream = substream; ++ ++ memcpy(&substream->runtime->hw, &snd_card_sco_capture_default, sizeof(struct snd_pcm_hardware)); ++ pSCOSnd->capture.buffer_pos = 0; ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) ++ init_timer(&snd_cap_timer.cap_timer); ++ snd_cap_timer.cap_timer.data = (unsigned long)pSCOSnd->usb_data; ++ snd_cap_timer.cap_timer.function = aic_snd_capture_timeout; ++#else ++ timer_setup(&snd_cap_timer.cap_timer, aic_snd_capture_timeout, 0); ++ snd_cap_timer.snd_usb_data = *(pSCOSnd->usb_data); ++#endif ++ ++ if(check_controller_support_msbc(pSCOSnd->dev)) { ++ substream->runtime->hw.rates |= SNDRV_PCM_RATE_16000; ++ substream->runtime->hw.rate_max = 16000; ++ substream->runtime->hw.period_bytes_min = 96; ++ substream->runtime->hw.period_bytes_max = 16 * 96; ++ substream->runtime->hw.buffer_bytes_max = 8 * 16 * 96; ++ } ++ set_bit(ALSA_CAPTURE_OPEN, &pSCOSnd->states); ++ return 0; ++} ++ ++static int snd_sco_capture_pcm_close(struct snd_pcm_substream *substream) ++{ ++ AIC_sco_card_t *pSCOSnd = substream->private_data; ++ ++ del_timer(&snd_cap_timer.cap_timer); ++ clear_bit(ALSA_CAPTURE_OPEN, &pSCOSnd->states); ++ return 0; ++} ++ ++static int snd_sco_capture_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg) ++{ ++ AICBT_DBG("%s, cmd = %d", __FUNCTION__, cmd); ++ switch (cmd) ++ { ++ default: ++ return snd_pcm_lib_ioctl(substream, cmd, arg); ++ } ++ return 0; ++} ++ ++static int snd_sco_capture_pcm_hw_params(struct snd_pcm_substream * substream, struct snd_pcm_hw_params * hw_params) ++{ ++ ++ int err; ++ struct snd_pcm_runtime *runtime = substream->runtime; ++ err = snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); ++ AICBT_INFO("%s,err : %d, runtime state : %d", __FUNCTION__, err, runtime->status->state); ++ return err; ++} ++ ++static int snd_sco_capture_pcm_hw_free(struct snd_pcm_substream * substream) ++{ ++ AICBT_DBG("%s", __FUNCTION__); ++ return snd_pcm_lib_free_vmalloc_buffer(substream);; ++} ++ ++static int snd_sco_capture_pcm_prepare(struct snd_pcm_substream *substream) ++{ ++ AIC_sco_card_t *pSCOSnd = substream->private_data; ++ struct snd_pcm_runtime *runtime = substream->runtime; ++ ++ AICBT_INFO("%s %d\n", __FUNCTION__, (int)runtime->period_size); ++ if (test_bit(DISCONNECTED, &pSCOSnd->states)) ++ return -ENODEV; ++ if (!test_bit(USB_CAPTURE_RUNNING, &pSCOSnd->states)) ++ return -EIO; ++ ++ if(runtime->rate == 8000) { ++ if(pSCOSnd->usb_data->isoc_altsetting != 2) ++ return -ENOEXEC; ++ pSCOSnd->capture.sco_packet_bytes = 48; ++ } ++ else if(runtime->rate == 16000 && check_controller_support_msbc(pSCOSnd->dev)) { ++ if(pSCOSnd->usb_data->isoc_altsetting != 4) ++ return -ENOEXEC; ++ pSCOSnd->capture.sco_packet_bytes = 96; ++ } ++ else if(pSCOSnd->usb_data->isoc_altsetting == 2) { ++ pSCOSnd->capture.sco_packet_bytes = 48; ++ } ++ else if(pSCOSnd->usb_data->isoc_altsetting == 1) { ++ pSCOSnd->capture.sco_packet_bytes = 24; ++ } ++ return 0; ++} ++ ++static int snd_sco_capture_pcm_trigger(struct snd_pcm_substream *substream, int cmd) ++{ ++ AIC_sco_card_t *pSCOSnd = substream->private_data; ++ AICBT_INFO("%s, cmd : %d", __FUNCTION__, cmd); ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ if (!test_bit(USB_CAPTURE_RUNNING, &pSCOSnd->states)) ++ return -EIO; ++ set_bit(ALSA_CAPTURE_RUNNING, &pSCOSnd->states); ++ return 0; ++ case SNDRV_PCM_TRIGGER_STOP: ++ clear_bit(ALSA_CAPTURE_RUNNING, &pSCOSnd->states); ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static snd_pcm_uframes_t snd_sco_capture_pcm_pointer(struct snd_pcm_substream *substream) ++{ ++ AIC_sco_card_t *pSCOSnd = substream->private_data; ++ ++ return pSCOSnd->capture.buffer_pos; ++} ++ ++ ++static struct snd_pcm_ops snd_sco_capture_pcm_ops = { ++ .open = snd_sco_capture_pcm_open, ++ .close = snd_sco_capture_pcm_close, ++ .ioctl = snd_sco_capture_ioctl, ++ .hw_params = snd_sco_capture_pcm_hw_params, ++ .hw_free = snd_sco_capture_pcm_hw_free, ++ .prepare = snd_sco_capture_pcm_prepare, ++ .trigger = snd_sco_capture_pcm_trigger, ++ .pointer = snd_sco_capture_pcm_pointer, ++}; ++ ++ ++static const struct snd_pcm_hardware snd_card_sco_playback_default = ++{ ++ .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_NONINTERLEAVED | ++ SNDRV_PCM_ACCESS_RW_INTERLEAVED | SNDRV_PCM_INFO_FIFO_IN_FRAMES), ++ .formats = SNDRV_PCM_FMTBIT_S16_LE, ++ .rates = (SNDRV_PCM_RATE_8000), ++ .rate_min = 8000, ++ .rate_max = 8000, ++ .channels_min = 1, ++ .channels_max = 1, ++ .buffer_bytes_max = 8 * 768, ++ .period_bytes_min = 48, ++ .period_bytes_max = 768, ++ .periods_min = 1, ++ .periods_max = 8, ++ .fifo_size = 8, ++}; ++ ++static int snd_sco_playback_pcm_open(struct snd_pcm_substream * substream) ++{ ++ AIC_sco_card_t *pSCOSnd = substream->private_data; ++ int err = 0; ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) ++ init_timer(&snd_cap_timer.play_timer); ++ snd_cap_timer.play_timer.data = (unsigned long)pSCOSnd->usb_data; ++ snd_cap_timer.play_timer.function = aic_snd_play_timeout; ++#else ++ timer_setup(&snd_cap_timer.play_timer, aic_snd_play_timeout, 0); ++ snd_cap_timer.snd_usb_data = *(pSCOSnd->usb_data); ++#endif ++ pSCOSnd->playback.buffer_pos = 0; ++ ++ AICBT_INFO("%s, rate : %d", __FUNCTION__, substream->runtime->rate); ++ memcpy(&substream->runtime->hw, &snd_card_sco_playback_default, sizeof(struct snd_pcm_hardware)); ++ if(check_controller_support_msbc(pSCOSnd->dev)) { ++ substream->runtime->hw.rates |= SNDRV_PCM_RATE_16000; ++ substream->runtime->hw.rate_max = 16000; ++ substream->runtime->hw.period_bytes_min = 96; ++ substream->runtime->hw.period_bytes_max = 16 * 96; ++ substream->runtime->hw.buffer_bytes_max = 8 * 16 * 96; ++ } ++ pSCOSnd->playback.substream = substream; ++ set_bit(ALSA_PLAYBACK_OPEN, &pSCOSnd->states); ++ ++ return err; ++} ++ ++static int snd_sco_playback_pcm_close(struct snd_pcm_substream *substream) ++{ ++ AIC_sco_card_t *pSCOSnd = substream->private_data; ++ ++ del_timer(&snd_cap_timer.play_timer); ++ AICBT_INFO("%s: play_timer delete", __func__); ++ clear_bit(ALSA_PLAYBACK_OPEN, &pSCOSnd->states); ++ cancel_work_sync(&pSCOSnd->send_sco_work); ++ return 0; ++} ++ ++static int snd_sco_playback_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg) ++{ ++ AICBT_DBG("%s, cmd : %d", __FUNCTION__, cmd); ++ switch (cmd) ++ { ++ default: ++ return snd_pcm_lib_ioctl(substream, cmd, arg); ++ break; ++ } ++ return 0; ++} ++ ++static int snd_sco_playback_pcm_hw_params(struct snd_pcm_substream * substream, struct snd_pcm_hw_params * hw_params) ++{ ++ int err; ++ err = snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); ++ return err; ++} ++ ++static int snd_sco_palyback_pcm_hw_free(struct snd_pcm_substream * substream) ++{ ++ AICBT_DBG("%s", __FUNCTION__); ++ return snd_pcm_lib_free_vmalloc_buffer(substream); ++} ++ ++static int snd_sco_playback_pcm_prepare(struct snd_pcm_substream *substream) ++{ ++ AIC_sco_card_t *pSCOSnd = substream->private_data; ++ struct snd_pcm_runtime *runtime = substream->runtime; ++ ++ AICBT_INFO("%s, bound_rate = %d", __FUNCTION__, runtime->rate); ++ ++ if (test_bit(DISCONNECTED, &pSCOSnd->states)) ++ return -ENODEV; ++ if (!test_bit(USB_PLAYBACK_RUNNING, &pSCOSnd->states)) ++ return -EIO; ++ ++ if(runtime->rate == 8000) { ++ if(pSCOSnd->usb_data->isoc_altsetting != 2) ++ return -ENOEXEC; ++ pSCOSnd->playback.sco_packet_bytes = 48; ++ } ++ else if(runtime->rate == 16000) { ++ if(pSCOSnd->usb_data->isoc_altsetting != 4) ++ return -ENOEXEC; ++ pSCOSnd->playback.sco_packet_bytes = 96; ++ } ++ ++ return 0; ++} ++ ++static int snd_sco_playback_pcm_trigger(struct snd_pcm_substream *substream, int cmd) ++{ ++ AIC_sco_card_t *pSCOSnd = substream->private_data; ++ ++ AICBT_INFO("%s, cmd = %d", __FUNCTION__, cmd); ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ if (!test_bit(USB_PLAYBACK_RUNNING, &pSCOSnd->states)) ++ return -EIO; ++ set_bit(ALSA_PLAYBACK_RUNNING, &pSCOSnd->states); ++ schedule_work(&pSCOSnd->send_sco_work); ++#ifdef CONFIG_SCO_OVER_HCI ++ if (!test_bit(USB_PLAYBACK_RUNNING, &pSCOSnd->states)) { ++ AICBT_INFO("%s: play_timer cmd 1 start ", __func__); ++ mod_timer(&snd_cap_timer.play_timer,jiffies + msecs_to_jiffies(3)); ++ } ++#endif ++ return 0; ++ case SNDRV_PCM_TRIGGER_STOP: ++ clear_bit(ALSA_PLAYBACK_RUNNING, &pSCOSnd->states); ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static snd_pcm_uframes_t snd_sco_playback_pcm_pointer(struct snd_pcm_substream *substream) ++{ ++ AIC_sco_card_t *pSCOSnd = substream->private_data; ++ ++ return pSCOSnd->playback.buffer_pos; ++} ++ ++ ++static struct snd_pcm_ops snd_sco_playback_pcm_ops = { ++ .open = snd_sco_playback_pcm_open, ++ .close = snd_sco_playback_pcm_close, ++ .ioctl = snd_sco_playback_ioctl, ++ .hw_params = snd_sco_playback_pcm_hw_params, ++ .hw_free = snd_sco_palyback_pcm_hw_free, ++ .prepare = snd_sco_playback_pcm_prepare, ++ .trigger = snd_sco_playback_pcm_trigger, ++ .pointer = snd_sco_playback_pcm_pointer, ++}; ++ ++ ++static AIC_sco_card_t* btusb_snd_init(struct usb_interface *intf, const struct usb_device_id *id, struct btusb_data *data) ++{ ++ struct snd_card *card; ++ AIC_sco_card_t *pSCOSnd; ++ int err=0; ++ AICBT_INFO("%s", __func__); ++ err = snd_card_new(&intf->dev, ++ -1, AIC_SCO_ID, THIS_MODULE, ++ sizeof(AIC_sco_card_t), &card); ++ if (err < 0) { ++ AICBT_ERR("%s: sco snd card create fail", __func__); ++ return NULL; ++ } ++ // private data ++ pSCOSnd = (AIC_sco_card_t *)card->private_data; ++ pSCOSnd->card = card; ++ pSCOSnd->dev = interface_to_usbdev(intf); ++ pSCOSnd->usb_data = data; ++ ++ strcpy(card->driver, AIC_SCO_ID); ++ strcpy(card->shortname, "Aicsemi sco snd"); ++ sprintf(card->longname, "Aicsemi sco over hci: VID:0x%04x, PID:0x%04x", ++ id->idVendor, pSCOSnd->dev->descriptor.idProduct); ++ ++ err = snd_pcm_new(card, AIC_SCO_ID, 0, 1, 1, &pSCOSnd->pcm); ++ if (err < 0) { ++ AICBT_ERR("%s: sco snd card new pcm fail", __func__); ++ return NULL; ++ } ++ pSCOSnd->pcm->private_data = pSCOSnd; ++ sprintf(pSCOSnd->pcm->name, "sco_pcm:VID:0x%04x, PID:0x%04x", ++ id->idVendor, pSCOSnd->dev->descriptor.idProduct); ++ ++ snd_pcm_set_ops(pSCOSnd->pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sco_playback_pcm_ops); ++ snd_pcm_set_ops(pSCOSnd->pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sco_capture_pcm_ops); ++ ++ err = snd_card_register(card); ++ if (err < 0) { ++ AICBT_ERR("%s: sco snd card register card fail", __func__); ++ return NULL; ++ } ++ ++ spin_lock_init(&pSCOSnd->capture_lock); ++ spin_lock_init(&pSCOSnd->playback_lock); ++ INIT_WORK(&pSCOSnd->send_sco_work, playback_work); ++ return pSCOSnd; ++} ++#endif ++ ++static int aicwf_usb_chipmatch(u16 vid, u16 pid){ ++ ++ if(pid == USB_PRODUCT_ID_AIC8801){ ++ g_chipid = PRODUCT_ID_AIC8801; ++ printk("%s USE AIC8801\r\n", __func__); ++ return 0; ++ }else if(pid == USB_PRODUCT_ID_AIC8800DC){ ++ g_chipid = PRODUCT_ID_AIC8800DC; ++ printk("%s USE AIC8800DC\r\n", __func__); ++ return 0; ++ }else if(pid == USB_PRODUCT_ID_AIC8800D80){ ++ g_chipid = PRODUCT_ID_AIC8800D80; ++ printk("%s USE AIC8800D80\r\n", __func__); ++ return 0; ++ }else{ ++ return -1; ++ } ++} ++ ++ ++static int btusb_probe(struct usb_interface *intf, const struct usb_device_id *id) ++{ ++ struct usb_device *udev = interface_to_usbdev(intf); ++ struct usb_endpoint_descriptor *ep_desc; ++ u8 endpoint_num; ++ struct btusb_data *data; ++ struct hci_dev *hdev; ++ firmware_info *fw_info; ++ int i, err=0; ++ ++ bt_support = 1; ++ ++ AICBT_INFO("%s: usb_interface %p, bInterfaceNumber %d, idVendor 0x%04x, " ++ "idProduct 0x%04x", __func__, intf, ++ intf->cur_altsetting->desc.bInterfaceNumber, ++ id->idVendor, id->idProduct); ++ ++ aicwf_usb_chipmatch(id->idVendor, id->idProduct); ++ ++ /* interface numbers are hardcoded in the spec */ ++ if (intf->cur_altsetting->desc.bInterfaceNumber != 0) ++ return -ENODEV; ++ ++ AICBT_DBG("%s: can wakeup = %x, may wakeup = %x", __func__, ++ device_can_wakeup(&udev->dev), device_may_wakeup(&udev->dev)); ++ ++ data = aic_alloc(intf); ++ if (!data) ++ return -ENOMEM; ++ ++ for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) { ++ ep_desc = &intf->cur_altsetting->endpoint[i].desc; ++ ++ endpoint_num = usb_endpoint_num(ep_desc); ++ printk("endpoint num %d\n", endpoint_num); ++ ++ if (!data->intr_ep && usb_endpoint_is_int_in(ep_desc)) { ++ data->intr_ep = ep_desc; ++ continue; ++ } ++ ++ if (!data->bulk_tx_ep && usb_endpoint_is_bulk_out(ep_desc)) { ++ data->bulk_tx_ep = ep_desc; ++ continue; ++ } ++ ++ if (!data->bulk_rx_ep && usb_endpoint_is_bulk_in(ep_desc)) { ++ data->bulk_rx_ep = ep_desc; ++ continue; ++ } ++ } ++ ++ if (!data->intr_ep || !data->bulk_tx_ep || !data->bulk_rx_ep) { ++ aic_free(data); ++ return -ENODEV; ++ } ++ ++ data->cmdreq_type = USB_TYPE_CLASS; ++ ++ data->udev = udev; ++ data->intf = intf; ++ ++ dlfw_dis_state = 0; ++ spin_lock_init(&queue_lock); ++ spin_lock_init(&dlfw_lock); ++ spin_lock_init(&data->lock); ++ ++ INIT_WORK(&data->work, btusb_work); ++ INIT_WORK(&data->waker, btusb_waker); ++ spin_lock_init(&data->txlock); ++ ++ init_usb_anchor(&data->tx_anchor); ++ init_usb_anchor(&data->intr_anchor); ++ init_usb_anchor(&data->bulk_anchor); ++ init_usb_anchor(&data->isoc_anchor); ++ init_usb_anchor(&data->deferred); ++ ++#if (CONFIG_BLUEDROID == 0) ++#if HCI_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) ++ spin_lock_init(&data->rxlock); ++ data->recv_bulk = btusb_recv_bulk; ++#endif ++#endif ++ ++ ++ fw_info = firmware_info_init(intf); ++ if (fw_info) ++ data->fw_info = fw_info; ++ else { ++ AICBT_WARN("%s: Failed to initialize fw info", __func__); ++ /* Skip download patch */ ++ goto end; ++ } ++ ++ AICBT_INFO("%s: download begining...", __func__); ++ ++#if CONFIG_BLUEDROID ++ mutex_lock(&btchr_mutex); ++#endif ++ if(g_chipid == PRODUCT_ID_AIC8800DC){ ++ err = download_patch(data->fw_info,1); ++ } ++ ++#if CONFIG_BLUEDROID ++ mutex_unlock(&btchr_mutex); ++#endif ++ ++ AICBT_INFO("%s: download ending...", __func__); ++ if (err < 0) { ++ return err; ++ } ++ ++ ++ hdev = hci_alloc_dev(); ++ if (!hdev) { ++ aic_free(data); ++ data = NULL; ++ return -ENOMEM; ++ } ++ ++ HDEV_BUS = HCI_USB; ++ ++ data->hdev = hdev; ++ ++ SET_HCIDEV_DEV(hdev, &intf->dev); ++ ++ hdev->open = btusb_open; ++ hdev->close = btusb_close; ++ hdev->flush = btusb_flush; ++ hdev->send = btusb_send_frame; ++ hdev->notify = btusb_notify; ++#if (CONFIG_BLUEDROID == 0) ++#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 0, 9) ++ hdev->shutdown = btusb_shutdown; ++#endif ++#endif //(CONFIG_BLUEDROIF == 0) ++ ++#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 4, 0) ++ hci_set_drvdata(hdev, data); ++#else ++ hdev->driver_data = data; ++ hdev->destruct = btusb_destruct; ++ hdev->owner = THIS_MODULE; ++#endif ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 1) ++ if (!reset_on_close){ ++ /* set_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks); */ ++ AICBT_DBG("%s: Set HCI_QUIRK_RESET_ON_CLOSE", __func__); ++ } ++#endif ++ ++ /* Interface numbers are hardcoded in the specification */ ++ data->isoc = usb_ifnum_to_if(data->udev, 1); ++ if (data->isoc) { ++ err = usb_driver_claim_interface(&btusb_driver, ++ data->isoc, data); ++ if (err < 0) { ++ hci_free_dev(hdev); ++ hdev = NULL; ++ aic_free(data); ++ data = NULL; ++ return err; ++ } ++#ifdef CONFIG_SCO_OVER_HCI ++ data->pSCOSnd = btusb_snd_init(intf, id, data); ++#endif ++ } ++ ++ err = hci_register_dev(hdev); ++ if (err < 0) { ++ hci_free_dev(hdev); ++ hdev = NULL; ++ aic_free(data); ++ data = NULL; ++ return err; ++ } ++ ++ usb_set_intfdata(intf, data); ++ ++//#ifdef CONFIG_HAS_EARLYSUSPEND ++#if 0 ++ data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN; ++ data->early_suspend.suspend = btusb_early_suspend; ++ data->early_suspend.resume = btusb_late_resume; ++ register_early_suspend(&data->early_suspend); ++#else ++ data->pm_notifier.notifier_call = bt_pm_notify; ++ data->reboot_notifier.notifier_call = bt_reboot_notify; ++ register_pm_notifier(&data->pm_notifier); ++ register_reboot_notifier(&data->reboot_notifier); ++#endif ++ ++#if CONFIG_BLUEDROID ++ AICBT_INFO("%s: Check bt reset flag %d", __func__, bt_reset); ++ /* Report hci hardware error after everthing is ready, ++ * especially hci register is completed. Or, btchr_poll ++ * will get null hci dev when hotplug in. ++ */ ++ if (bt_reset == 1) { ++ hci_hardware_error(); ++ bt_reset = 0; ++ } else ++ bt_reset = 0; /* Clear and reset it anyway */ ++#endif ++ ++end: ++ return 0; ++} ++ ++static void btusb_disconnect(struct usb_interface *intf) ++{ ++ struct btusb_data *data; ++ struct hci_dev *hdev = NULL; ++#if CONFIG_BLUEDROID ++ wait_event_interruptible(bt_dlfw_wait, (check_set_dlfw_state_value(2) == 2)); ++#endif ++ ++ bt_support = 0; ++ ++ AICBT_INFO("%s: usb_interface %p, bInterfaceNumber %d", ++ __func__, intf, intf->cur_altsetting->desc.bInterfaceNumber); ++ ++ data = usb_get_intfdata(intf); ++ ++ if (intf->cur_altsetting->desc.bInterfaceNumber != 0) ++ return; ++ ++ if (data) ++ hdev = data->hdev; ++ else { ++ AICBT_WARN("%s: Failed to get bt usb data[Null]", __func__); ++ return; ++ } ++ ++#ifdef CONFIG_SCO_OVER_HCI ++ if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { ++ AIC_sco_card_t *pSCOSnd = data->pSCOSnd; ++ if(!pSCOSnd) { ++ AICBT_ERR("%s: sco private data is null", __func__); ++ return; ++ } ++ set_bit(DISCONNECTED, &pSCOSnd->states); ++ snd_card_disconnect(pSCOSnd->card); ++ snd_card_free_when_closed(pSCOSnd->card); ++ } ++#endif ++ ++//#ifdef CONFIG_HAS_EARLYSUSPEND ++#if 0 ++ unregister_early_suspend(&data->early_suspend); ++#else ++ unregister_pm_notifier(&data->pm_notifier); ++ unregister_reboot_notifier(&data->reboot_notifier); ++#endif ++ ++ firmware_info_destroy(intf); ++ ++#if CONFIG_BLUEDROID ++ if (test_bit(HCI_RUNNING, &hdev->flags)) { ++ AICBT_INFO("%s: Set BT reset flag", __func__); ++ bt_reset = 1; ++ } ++#endif ++ ++ usb_set_intfdata(data->intf, NULL); ++ ++ if (data->isoc) ++ usb_set_intfdata(data->isoc, NULL); ++ ++ hci_unregister_dev(hdev); ++ ++ if (intf == data->isoc) ++ usb_driver_release_interface(&btusb_driver, data->intf); ++ else if (data->isoc) ++ usb_driver_release_interface(&btusb_driver, data->isoc); ++ ++#if !CONFIG_BLUEDROID ++#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0) ++ __hci_dev_put(hdev); ++#endif ++#endif ++ ++ hci_free_dev(hdev); ++ aic_free(data); ++ data = NULL; ++ set_dlfw_state_value(0); ++} ++ ++#ifdef CONFIG_PM ++static int btusb_suspend(struct usb_interface *intf, pm_message_t message) ++{ ++ struct btusb_data *data = usb_get_intfdata(intf); ++ //firmware_info *fw_info = data->fw_info; ++ ++ AICBT_INFO("%s: event 0x%x, suspend count %d", __func__, ++ message.event, data->suspend_count); ++ ++ if (intf->cur_altsetting->desc.bInterfaceNumber != 0) ++ return 0; ++#if 0 ++ if (!test_bit(HCI_RUNNING, &data->hdev->flags)) ++ set_bt_onoff(fw_info, 1); ++#endif ++ if (data->suspend_count++) ++ return 0; ++ ++ spin_lock_irq(&data->txlock); ++ if (!((message.event & PM_EVENT_AUTO) && data->tx_in_flight)) { ++ set_bit(BTUSB_SUSPENDING, &data->flags); ++ spin_unlock_irq(&data->txlock); ++ } else { ++ spin_unlock_irq(&data->txlock); ++ data->suspend_count--; ++ AICBT_ERR("%s: Failed to enter suspend", __func__); ++ return -EBUSY; ++ } ++ ++ cancel_work_sync(&data->work); ++ ++ btusb_stop_traffic(data); ++ mdelay(URB_CANCELING_DELAY_MS); ++ usb_kill_anchored_urbs(&data->tx_anchor); ++ ++ return 0; ++} ++ ++static void play_deferred(struct btusb_data *data) ++{ ++ struct urb *urb; ++ int err; ++ ++ while ((urb = usb_get_from_anchor(&data->deferred))) { ++ usb_anchor_urb(urb, &data->tx_anchor); ++ err = usb_submit_urb(urb, GFP_ATOMIC); ++ if (err < 0) { ++ AICBT_ERR("%s: Failed to submit urb %p, err %d", ++ __func__, urb, err); ++ kfree(urb->setup_packet); ++ usb_unanchor_urb(urb); ++ } else { ++ usb_mark_last_busy(data->udev); ++ } ++ usb_free_urb(urb); ++ ++ data->tx_in_flight++; ++ } ++ mdelay(URB_CANCELING_DELAY_MS); ++ usb_scuttle_anchored_urbs(&data->deferred); ++} ++ ++static int btusb_resume(struct usb_interface *intf) ++{ ++ struct btusb_data *data = usb_get_intfdata(intf); ++ struct hci_dev *hdev = data->hdev; ++ int err = 0; ++ ++ AICBT_INFO("%s: Suspend count %d", __func__, data->suspend_count); ++ ++ if (intf->cur_altsetting->desc.bInterfaceNumber != 0) ++ return 0; ++ ++ if (--data->suspend_count) ++ return 0; ++ ++ #if 0 ++ /*check_fw_version to check the status of the BT Controller after USB Resume*/ ++ err = check_fw_version(fw_info); ++ if (err !=0) ++ { ++ AICBT_INFO("%s: BT Controller Power OFF And Return hci_hardware_error:%d", __func__, err); ++ hci_hardware_error(); ++ } ++ #endif ++ ++ AICBT_INFO("%s g_chipid %x\n", __func__, g_chipid); ++ if(g_chipid == PRODUCT_ID_AIC8800DC){ ++ if(data->fw_info){ ++ err = download_patch(data->fw_info,1); ++ }else{ ++ AICBT_WARN("%s: Failed to initialize fw info", __func__); ++ } ++ } ++ ++ #if 1 ++ if (test_bit(BTUSB_INTR_RUNNING, &data->flags)) { ++ err = btusb_submit_intr_urb(hdev, GFP_NOIO); ++ if (err < 0) { ++ clear_bit(BTUSB_INTR_RUNNING, &data->flags); ++ goto failed; ++ } ++ } ++ #endif ++ ++ if (test_bit(BTUSB_BULK_RUNNING, &data->flags)) { ++ err = btusb_submit_bulk_urb(hdev, GFP_NOIO); ++ if (err < 0) { ++ clear_bit(BTUSB_BULK_RUNNING, &data->flags); ++ goto failed; ++ } ++ ++ btusb_submit_bulk_urb(hdev, GFP_NOIO); ++ } ++ ++ if (test_bit(BTUSB_ISOC_RUNNING, &data->flags)) { ++ if (btusb_submit_isoc_urb(hdev, GFP_NOIO) < 0) ++ clear_bit(BTUSB_ISOC_RUNNING, &data->flags); ++ else ++ btusb_submit_isoc_urb(hdev, GFP_NOIO); ++ } ++ ++ spin_lock_irq(&data->txlock); ++ play_deferred(data); ++ clear_bit(BTUSB_SUSPENDING, &data->flags); ++ spin_unlock_irq(&data->txlock); ++ schedule_work(&data->work); ++ ++ return 0; ++ ++failed: ++ mdelay(URB_CANCELING_DELAY_MS); ++ usb_scuttle_anchored_urbs(&data->deferred); ++ spin_lock_irq(&data->txlock); ++ clear_bit(BTUSB_SUSPENDING, &data->flags); ++ spin_unlock_irq(&data->txlock); ++ ++ return err; ++} ++#endif ++ ++static struct usb_driver btusb_driver = { ++ .name = "aic_btusb", ++ .probe = btusb_probe, ++ .disconnect = btusb_disconnect, ++#ifdef CONFIG_PM ++ .suspend = btusb_suspend, ++ .resume = btusb_resume, ++#if CONFIG_RESET_RESUME ++ .reset_resume = btusb_resume, ++#endif ++#endif ++ .id_table = btusb_table, ++ .supports_autosuspend = 1, ++#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 7, 1) ++ .disable_hub_initiated_lpm = 1, ++#endif ++}; ++ ++static int __init btusb_init(void) ++{ ++ int err; ++ ++ AICBT_INFO("AICBT_RELEASE_NAME: %s",AICBT_RELEASE_NAME); ++ AICBT_INFO("AicSemi Bluetooth USB driver module init, version %s", VERSION); ++ AICBT_INFO("RELEASE DATE: 2023_0506_1635 \r\n"); ++#if CONFIG_BLUEDROID ++ err = btchr_init(); ++ if (err < 0) { ++ /* usb register will go on, even bt char register failed */ ++ AICBT_ERR("Failed to register usb char device interfaces"); ++ } else ++ bt_char_dev_registered = 1; ++#endif ++ err = usb_register(&btusb_driver); ++ if (err < 0) ++ AICBT_ERR("Failed to register aic bluetooth USB driver"); ++ return err; ++} ++ ++static void __exit btusb_exit(void) ++{ ++ AICBT_INFO("AicSemi Bluetooth USB driver module exit"); ++#if CONFIG_BLUEDROID ++ if (bt_char_dev_registered > 0) ++ btchr_exit(); ++#endif ++ usb_deregister(&btusb_driver); ++} ++ ++module_init(btusb_init); ++module_exit(btusb_exit); ++ ++ ++module_param(mp_drv_mode, int, 0644); ++MODULE_PARM_DESC(mp_drv_mode, "0: NORMAL; 1: MP MODE"); ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) ++MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver); ++#endif ++ ++MODULE_AUTHOR("AicSemi Corporation"); ++MODULE_DESCRIPTION("AicSemi Bluetooth USB driver version"); ++MODULE_VERSION(VERSION); ++MODULE_LICENSE("GPL"); +--- /dev/null ++++ b/drivers/bluetooth/aic_btusb/aic_btusb.h +@@ -0,0 +1,753 @@ ++/* ++ * ++ * Aic Bluetooth USB driver ++ * ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ * ++ */ ++#include <linux/interrupt.h> ++#include <linux/module.h> ++#include <linux/slab.h> ++#include <linux/types.h> ++#include <linux/sched.h> ++#include <linux/skbuff.h> ++#include <linux/errno.h> ++#include <linux/usb.h> ++#include <linux/cdev.h> ++#include <linux/device.h> ++#include <linux/poll.h> ++ ++#include <linux/version.h> ++#include <linux/pm_runtime.h> ++#include <linux/firmware.h> ++#include <linux/suspend.h> ++ ++ ++#ifdef CONFIG_PLATFORM_UBUNTU ++#define CONFIG_BLUEDROID 0 /* bleuz 0, bluedroid 1 */ ++#else ++#define CONFIG_BLUEDROID 1 /* bleuz 0, bluedroid 1 */ ++#endif ++ ++ ++//#define CONFIG_SCO_OVER_HCI ++#define CONFIG_USB_AIC_UART_SCO_DRIVER ++ ++#ifdef CONFIG_SCO_OVER_HCI ++#include <linux/usb/audio.h> ++#include <sound/core.h> ++#include <sound/initval.h> ++#include <sound/pcm.h> ++#include <sound/pcm_params.h> ++ ++#define AIC_SCO_ID "snd_sco_aic" ++enum { ++ USB_CAPTURE_RUNNING, ++ USB_PLAYBACK_RUNNING, ++ ALSA_CAPTURE_OPEN, ++ ALSA_PLAYBACK_OPEN, ++ ALSA_CAPTURE_RUNNING, ++ ALSA_PLAYBACK_RUNNING, ++ CAPTURE_URB_COMPLETED, ++ PLAYBACK_URB_COMPLETED, ++ DISCONNECTED, ++}; ++ ++// AIC sound card ++typedef struct AIC_sco_card { ++ struct snd_card *card; ++ struct snd_pcm *pcm; ++ struct usb_device *dev; ++ struct btusb_data *usb_data; ++ unsigned long states; ++ struct aic_sco_stream { ++ struct snd_pcm_substream *substream; ++ unsigned int sco_packet_bytes; ++ snd_pcm_uframes_t buffer_pos; ++ } capture, playback; ++ spinlock_t capture_lock; ++ spinlock_t playback_lock; ++ struct work_struct send_sco_work; ++} AIC_sco_card_t; ++#endif ++/* Some Android system may use standard Linux kernel, while ++ * standard Linux may also implement early suspend feature. ++ * So exclude earysuspend.h from CONFIG_BLUEDROID. ++ */ ++#ifdef CONFIG_HAS_EARLYSUSPEND ++#include <linux/earlysuspend.h> ++#endif ++ ++#if CONFIG_BLUEDROID ++#else ++#include <net/bluetooth/bluetooth.h> ++#include <net/bluetooth/hci_core.h> ++#include <net/bluetooth/hci.h> ++#endif ++ ++ ++/*********************************** ++** AicSemi - For aic_btusb driver ** ++***********************************/ ++#define URB_CANCELING_DELAY_MS 10 ++/* when OS suspended, module is still powered,usb is not powered, ++ * this may set to 1, and must comply with special patch code. ++ */ ++#define CONFIG_RESET_RESUME 1 ++#define PRINT_CMD_EVENT 0 ++#define PRINT_ACL_DATA 0 ++#define PRINT_SCO_DATA 0 ++ ++#define AICBT_DBG_FLAG 0 ++ ++#if AICBT_DBG_FLAG ++#define AICBT_DBG(fmt, arg...) printk( "aic_btusb: " fmt "\n" , ## arg) ++#else ++#define AICBT_DBG(fmt, arg...) ++#endif ++ ++#define AICBT_INFO(fmt, arg...) printk("aic_btusb: " fmt "\n" , ## arg) ++#define AICBT_WARN(fmt, arg...) printk("aic_btusb: " fmt "\n" , ## arg) ++#define AICBT_ERR(fmt, arg...) printk("aic_btusb: " fmt "\n" , ## arg) ++ ++ ++#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 33) ++#define HDEV_BUS hdev->bus ++#define USB_RPM 1 ++#else ++#define HDEV_BUS hdev->type ++#define USB_RPM 0 ++#endif ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 38) ++#define NUM_REASSEMBLY 3 ++#endif ++ ++#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 4, 0) ++#define GET_DRV_DATA(x) hci_get_drvdata(x) ++#else ++#define GET_DRV_DATA(x) x->driver_data ++#endif ++ ++#define SCO_NUM hdev->conn_hash.sco_num ++ ++ ++#define BTUSB_RPM (0 * USB_RPM) /* 1 SS enable; 0 SS disable */ ++#define BTUSB_WAKEUP_HOST 0 /* 1 enable; 0 disable */ ++#define BTUSB_MAX_ISOC_FRAMES 48 ++#define BTUSB_INTR_RUNNING 0 ++#define BTUSB_BULK_RUNNING 1 ++#define BTUSB_ISOC_RUNNING 2 ++#define BTUSB_SUSPENDING 3 ++#define BTUSB_DID_ISO_RESUME 4 ++ ++#define HCI_VENDOR_USB_DISC_HARDWARE_ERROR 0xFF ++ ++#define HCI_CMD_READ_BD_ADDR 0x1009 ++#define HCI_VENDOR_READ_LMP_VERISION 0x1001 ++#define HCI_VENDOR_RESET 0x0C03 ++ ++#define DRV_NORMAL_MODE 0 ++#define DRV_MP_MODE 1 ++int mp_drv_mode = 0; /* 1 Mptool Fw; 0 Normal Fw */ ++ ++ ++#if CONFIG_BLUEDROID ++#define QUEUE_SIZE 500 ++ ++/*************************************** ++** AicSemi - Integrate from bluetooth.h ** ++*****************************************/ ++/* Reserv for core and drivers use */ ++#define BT_SKB_RESERVE 8 ++ ++/* BD Address */ ++typedef struct { ++ __u8 b[6]; ++} __packed bdaddr_t; ++ ++/* Skb helpers */ ++struct bt_skb_cb { ++ __u8 pkt_type; ++ __u8 incoming; ++ __u16 expect; ++ __u16 tx_seq; ++ __u8 retries; ++ __u8 sar; ++ __u8 force_active; ++}; ++ ++#define bt_cb(skb) ((struct bt_skb_cb *)((skb)->cb)) ++ ++static inline struct sk_buff *bt_skb_alloc(unsigned int len, gfp_t how) ++{ ++ struct sk_buff *skb; ++ ++ if ((skb = alloc_skb(len + BT_SKB_RESERVE, how))) { ++ skb_reserve(skb, BT_SKB_RESERVE); ++ bt_cb(skb)->incoming = 0; ++ } ++ return skb; ++} ++/* AicSemi - Integrate from bluetooth.h end */ ++ ++/*********************************** ++** AicSemi - Integrate from hci.h ** ++***********************************/ ++#define HCI_MAX_ACL_SIZE 1024 ++#define HCI_MAX_SCO_SIZE 255 ++#define HCI_MAX_EVENT_SIZE 260 ++#define HCI_MAX_FRAME_SIZE (HCI_MAX_ACL_SIZE + 4) ++ ++/* HCI bus types */ ++#define HCI_VIRTUAL 0 ++#define HCI_USB 1 ++#define HCI_PCCARD 2 ++#define HCI_UART 3 ++#define HCI_RS232 4 ++#define HCI_PCI 5 ++#define HCI_SDIO 6 ++ ++/* HCI controller types */ ++#define HCI_BREDR 0x00 ++#define HCI_AMP 0x01 ++ ++/* HCI device flags */ ++enum { ++ HCI_UP, ++ HCI_INIT, ++ HCI_RUNNING, ++ ++ HCI_PSCAN, ++ HCI_ISCAN, ++ HCI_AUTH, ++ HCI_ENCRYPT, ++ HCI_INQUIRY, ++ ++ HCI_RAW, ++ ++ HCI_RESET, ++}; ++ ++/* ++ * BR/EDR and/or LE controller flags: the flags defined here should represent ++ * states from the controller. ++ */ ++enum { ++ HCI_SETUP, ++ HCI_AUTO_OFF, ++ HCI_MGMT, ++ HCI_PAIRABLE, ++ HCI_SERVICE_CACHE, ++ HCI_LINK_KEYS, ++ HCI_DEBUG_KEYS, ++ HCI_UNREGISTER, ++ ++ HCI_LE_SCAN, ++ HCI_SSP_ENABLED, ++ HCI_HS_ENABLED, ++ HCI_LE_ENABLED, ++ HCI_CONNECTABLE, ++ HCI_DISCOVERABLE, ++ HCI_LINK_SECURITY, ++ HCI_PENDING_CLASS, ++}; ++ ++/* HCI data types */ ++#define HCI_COMMAND_PKT 0x01 ++#define HCI_ACLDATA_PKT 0x02 ++#define HCI_SCODATA_PKT 0x03 ++#define HCI_EVENT_PKT 0x04 ++#define HCI_VENDOR_PKT 0xff ++ ++#define HCI_MAX_NAME_LENGTH 248 ++#define HCI_MAX_EIR_LENGTH 240 ++ ++#define HCI_OP_READ_LOCAL_VERSION 0x1001 ++struct hci_rp_read_local_version { ++ __u8 status; ++ __u8 hci_ver; ++ __le16 hci_rev; ++ __u8 lmp_ver; ++ __le16 manufacturer; ++ __le16 lmp_subver; ++} __packed; ++ ++#define HCI_EV_CMD_COMPLETE 0x0e ++struct hci_ev_cmd_complete { ++ __u8 ncmd; ++ __le16 opcode; ++} __packed; ++ ++/* ---- HCI Packet structures ---- */ ++#define HCI_COMMAND_HDR_SIZE 3 ++#define HCI_EVENT_HDR_SIZE 2 ++#define HCI_ACL_HDR_SIZE 4 ++#define HCI_SCO_HDR_SIZE 3 ++ ++struct hci_command_hdr { ++ __le16 opcode; /* OCF & OGF */ ++ __u8 plen; ++} __packed; ++ ++struct hci_event_hdr { ++ __u8 evt; ++ __u8 plen; ++} __packed; ++ ++struct hci_acl_hdr { ++ __le16 handle; /* Handle & Flags(PB, BC) */ ++ __le16 dlen; ++} __packed; ++ ++struct hci_sco_hdr { ++ __le16 handle; ++ __u8 dlen; ++} __packed; ++ ++static inline struct hci_event_hdr *hci_event_hdr(const struct sk_buff *skb) ++{ ++ return (struct hci_event_hdr *) skb->data; ++} ++ ++static inline struct hci_acl_hdr *hci_acl_hdr(const struct sk_buff *skb) ++{ ++ return (struct hci_acl_hdr *) skb->data; ++} ++ ++static inline struct hci_sco_hdr *hci_sco_hdr(const struct sk_buff *skb) ++{ ++ return (struct hci_sco_hdr *) skb->data; ++} ++ ++/* ---- HCI Ioctl requests structures ---- */ ++struct hci_dev_stats { ++ __u32 err_rx; ++ __u32 err_tx; ++ __u32 cmd_tx; ++ __u32 evt_rx; ++ __u32 acl_tx; ++ __u32 acl_rx; ++ __u32 sco_tx; ++ __u32 sco_rx; ++ __u32 byte_rx; ++ __u32 byte_tx; ++}; ++/* AicSemi - Integrate from hci.h end */ ++ ++/***************************************** ++** AicSemi - Integrate from hci_core.h ** ++*****************************************/ ++struct hci_conn_hash { ++ struct list_head list; ++ unsigned int acl_num; ++ unsigned int sco_num; ++ unsigned int le_num; ++}; ++ ++#define HCI_MAX_SHORT_NAME_LENGTH 10 ++ ++#define NUM_REASSEMBLY 4 ++struct hci_dev { ++ struct mutex lock; ++ ++ char name[8]; ++ unsigned long flags; ++ __u16 id; ++ __u8 bus; ++ __u8 dev_type; ++ ++ struct sk_buff *reassembly[NUM_REASSEMBLY]; ++ ++ struct hci_conn_hash conn_hash; ++ ++ struct hci_dev_stats stat; ++ ++#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0) ++ atomic_t refcnt; ++ struct module *owner; ++ void *driver_data; ++#endif ++ ++ atomic_t promisc; ++ ++ struct device *parent; ++ struct device dev; ++ ++ unsigned long dev_flags; ++ ++ int (*open)(struct hci_dev *hdev); ++ int (*close)(struct hci_dev *hdev); ++ int (*flush)(struct hci_dev *hdev); ++ int (*send)(struct sk_buff *skb); ++#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0) ++ void (*destruct)(struct hci_dev *hdev); ++#endif ++#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 7, 1) ++ __u16 voice_setting; ++#endif ++ void (*notify)(struct hci_dev *hdev, unsigned int evt); ++ int (*ioctl)(struct hci_dev *hdev, unsigned int cmd, unsigned long arg); ++ u8 *align_data; ++}; ++ ++#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0) ++static inline struct hci_dev *__hci_dev_hold(struct hci_dev *d) ++{ ++ atomic_inc(&d->refcnt); ++ return d; ++} ++ ++static inline void __hci_dev_put(struct hci_dev *d) ++{ ++ if (atomic_dec_and_test(&d->refcnt)) ++ d->destruct(d); ++} ++#endif ++ ++static inline void *hci_get_drvdata(struct hci_dev *hdev) ++{ ++ return dev_get_drvdata(&hdev->dev); ++} ++ ++static inline void hci_set_drvdata(struct hci_dev *hdev, void *data) ++{ ++ dev_set_drvdata(&hdev->dev, data); ++} ++ ++#define SET_HCIDEV_DEV(hdev, pdev) ((hdev)->parent = (pdev)) ++/* AicSemi - Integrate from hci_core.h end */ ++ ++/* ----- HCI Commands ---- */ ++#define HCI_OP_INQUIRY 0x0401 ++#define HCI_OP_INQUIRY_CANCEL 0x0402 ++#define HCI_OP_EXIT_PERIODIC_INQ 0x0404 ++#define HCI_OP_CREATE_CONN 0x0405 ++#define HCI_OP_DISCONNECT 0x0406 ++#define HCI_OP_ADD_SCO 0x0407 ++#define HCI_OP_CREATE_CONN_CANCEL 0x0408 ++#define HCI_OP_ACCEPT_CONN_REQ 0x0409 ++#define HCI_OP_REJECT_CONN_REQ 0x040a ++#define HCI_OP_LINK_KEY_REPLY 0x040b ++#define HCI_OP_LINK_KEY_NEG_REPLY 0x040c ++#define HCI_OP_PIN_CODE_REPLY 0x040d ++#define HCI_OP_PIN_CODE_NEG_REPLY 0x040e ++#define HCI_OP_CHANGE_CONN_PTYPE 0x040f ++#define HCI_OP_AUTH_REQUESTED 0x0411 ++#define HCI_OP_SET_CONN_ENCRYPT 0x0413 ++#define HCI_OP_CHANGE_CONN_LINK_KEY 0x0415 ++#define HCI_OP_REMOTE_NAME_REQ 0x0419 ++#define HCI_OP_REMOTE_NAME_REQ_CANCEL 0x041a ++#define HCI_OP_READ_REMOTE_FEATURES 0x041b ++#define HCI_OP_READ_REMOTE_EXT_FEATURES 0x041c ++#define HCI_OP_READ_REMOTE_VERSION 0x041d ++#define HCI_OP_SETUP_SYNC_CONN 0x0428 ++#define HCI_OP_ACCEPT_SYNC_CONN_REQ 0x0429 ++#define HCI_OP_REJECT_SYNC_CONN_REQ 0x042a ++#define HCI_OP_SNIFF_MODE 0x0803 ++#define HCI_OP_EXIT_SNIFF_MODE 0x0804 ++#define HCI_OP_ROLE_DISCOVERY 0x0809 ++#define HCI_OP_SWITCH_ROLE 0x080b ++#define HCI_OP_READ_LINK_POLICY 0x080c ++#define HCI_OP_WRITE_LINK_POLICY 0x080d ++#define HCI_OP_READ_DEF_LINK_POLICY 0x080e ++#define HCI_OP_WRITE_DEF_LINK_POLICY 0x080f ++#define HCI_OP_SNIFF_SUBRATE 0x0811 ++#define HCI_OP_Write_Link_Policy_Settings 0x080d ++#define HCI_OP_SET_EVENT_MASK 0x0c01 ++#define HCI_OP_RESET 0x0c03 ++#define HCI_OP_SET_EVENT_FLT 0x0c05 ++#define HCI_OP_Write_Extended_Inquiry_Response 0x0c52 ++#define HCI_OP_Write_Simple_Pairing_Mode 0x0c56 ++#define HCI_OP_Read_Buffer_Size 0x1005 ++#define HCI_OP_Host_Buffer_Size 0x0c33 ++#define HCI_OP_Read_Local_Version_Information 0x1001 ++#define HCI_OP_Read_BD_ADDR 0x1009 ++#define HCI_OP_Read_Local_Supported_Commands 0x1002 ++#define HCI_OP_Write_Scan_Enable 0x0c1a ++#define HCI_OP_Write_Current_IAC_LAP 0x0c3a ++#define HCI_OP_Write_Inquiry_Scan_Activity 0x0c1e ++#define HCI_OP_Write_Class_of_Device 0x0c24 ++#define HCI_OP_LE_Rand 0x2018 ++#define HCI_OP_LE_Set_Random_Address 0x2005 ++#define HCI_OP_LE_Set_Extended_Scan_Enable 0x2042 ++#define HCI_OP_LE_Set_Extended_Scan_Parameters 0x2041 ++#define HCI_OP_Set_Event_Filter 0x0c05 ++#define HCI_OP_Write_Voice_Setting 0x0c26 ++#define HCI_OP_Change_Local_Name 0x0c13 ++#define HCI_OP_Read_Local_Name 0x0c14 ++#define HCI_OP_Wirte_Page_Timeout 0x0c18 ++#define HCI_OP_LE_Clear_Resolving_List 0x0c29 ++#define HCI_OP_LE_Set_Addres_Resolution_Enable_Command 0x0c2e ++#define HCI_OP_Write_Inquiry_mode 0x0c45 ++#define HCI_OP_Write_Page_Scan_Type 0x0c47 ++#define HCI_OP_Write_Inquiry_Scan_Type 0x0c43 ++ ++#define HCI_OP_Delete_Stored_Link_Key 0x0c12 ++#define HCI_OP_LE_Read_Local_Resolvable_Address 0x202d ++#define HCI_OP_LE_Extended_Create_Connection 0x2043 ++#define HCI_OP_Read_Remote_Version_Information 0x041d ++#define HCI_OP_LE_Start_Encryption 0x2019 ++#define HCI_OP_LE_Add_Device_to_Resolving_List 0x2027 ++#define HCI_OP_LE_Set_Privacy_Mode 0x204e ++#define HCI_OP_LE_Connection_Update 0x2013 ++ ++/* ----- HCI events---- */ ++#define HCI_OP_DISCONNECT 0x0406 ++#define HCI_EV_INQUIRY_COMPLETE 0x01 ++#define HCI_EV_INQUIRY_RESULT 0x02 ++#define HCI_EV_CONN_COMPLETE 0x03 ++#define HCI_EV_CONN_REQUEST 0x04 ++#define HCI_EV_DISCONN_COMPLETE 0x05 ++#define HCI_EV_AUTH_COMPLETE 0x06 ++#define HCI_EV_REMOTE_NAME 0x07 ++#define HCI_EV_ENCRYPT_CHANGE 0x08 ++#define HCI_EV_CHANGE_LINK_KEY_COMPLETE 0x09 ++ ++#define HCI_EV_REMOTE_FEATURES 0x0b ++#define HCI_EV_REMOTE_VERSION 0x0c ++#define HCI_EV_QOS_SETUP_COMPLETE 0x0d ++#define HCI_EV_CMD_COMPLETE 0x0e ++#define HCI_EV_CMD_STATUS 0x0f ++ ++#define HCI_EV_ROLE_CHANGE 0x12 ++#define HCI_EV_NUM_COMP_PKTS 0x13 ++#define HCI_EV_MODE_CHANGE 0x14 ++#define HCI_EV_PIN_CODE_REQ 0x16 ++#define HCI_EV_LINK_KEY_REQ 0x17 ++#define HCI_EV_LINK_KEY_NOTIFY 0x18 ++#define HCI_EV_CLOCK_OFFSET 0x1c ++#define HCI_EV_PKT_TYPE_CHANGE 0x1d ++#define HCI_EV_PSCAN_REP_MODE 0x20 ++ ++#define HCI_EV_INQUIRY_RESULT_WITH_RSSI 0x22 ++#define HCI_EV_REMOTE_EXT_FEATURES 0x23 ++#define HCI_EV_SYNC_CONN_COMPLETE 0x2c ++#define HCI_EV_SYNC_CONN_CHANGED 0x2d ++#define HCI_EV_SNIFF_SUBRATE 0x2e ++#define HCI_EV_EXTENDED_INQUIRY_RESULT 0x2f ++#define HCI_EV_IO_CAPA_REQUEST 0x31 ++#define HCI_EV_SIMPLE_PAIR_COMPLETE 0x36 ++#define HCI_EV_REMOTE_HOST_FEATURES 0x3d ++#define HCI_EV_LE_Meta 0x3e ++ ++#define CONFIG_MAC_OFFSET_GEN_1_2 (0x3C) //MAC's OFFSET in config/efuse for aic generation 1~2 bluetooth chip ++#define CONFIG_MAC_OFFSET_GEN_3PLUS (0x44) //MAC's OFFSET in config/efuse for aic generation 3+ bluetooth chip ++ ++ ++typedef struct { ++ uint16_t vid; ++ uint16_t pid; ++ uint16_t lmp_sub_default; ++ uint16_t lmp_sub; ++ uint16_t eversion; ++ char *mp_patch_name; ++ char *patch_name; ++ char *config_name; ++ uint8_t *fw_cache; ++ int fw_len; ++ uint16_t mac_offset; ++ uint32_t max_patch_size; ++} patch_info; ++ ++//Define ioctl cmd the same as HCIDEVUP in the kernel ++#define DOWN_FW_CFG _IOW('E', 176, int) ++//#ifdef CONFIG_SCO_OVER_HCI ++//#define SET_ISO_CFG _IOW('H', 202, int) ++//#else ++#define SET_ISO_CFG _IOW('E', 177, int) ++//#endif ++#define RESET_CONTROLLER _IOW('E', 178, int) ++#define DWFW_CMPLT _IOW('E', 179, int) ++ ++#define GET_USB_INFO _IOR('E', 180, int) ++ ++/* for altsettings*/ ++#include <linux/fs.h> ++#define BDADDR_FILE "/data/misc/bluetooth/bdaddr" ++#define FACTORY_BT_BDADDR_STORAGE_LEN 17 ++#if 0 ++static inline int getmacaddr(uint8_t * vnd_local_bd_addr) ++{ ++ struct file *bdaddr_file; ++ mm_segment_t oldfs; ++ char buf[FACTORY_BT_BDADDR_STORAGE_LEN]; ++ int32_t i = 0; ++ memset(buf, 0, FACTORY_BT_BDADDR_STORAGE_LEN); ++ bdaddr_file = filp_open(BDADDR_FILE, O_RDONLY, 0); ++ if (IS_ERR(bdaddr_file)){ ++ AICBT_INFO("No Mac Config for BT\n"); ++ return -1; ++ } ++ oldfs = get_fs(); ++ set_fs(KERNEL_DS); ++ bdaddr_file->f_op->llseek(bdaddr_file, 0, 0); ++ bdaddr_file->f_op->read(bdaddr_file, buf, FACTORY_BT_BDADDR_STORAGE_LEN, &bdaddr_file->f_pos); ++ for (i = 0; i < 6; i++) { ++ if(buf[3*i]>'9') ++ { ++ if(buf[3*i]>'Z') ++ buf[3*i] -=('a'-'A'); //change a to A ++ buf[3*i] -= ('A'-'9'-1); ++ } ++ if(buf[3*i+1]>'9') ++ { ++ if(buf[3*i+1]>'Z') ++ buf[3*i+1] -=('a'-'A'); //change a to A ++ buf[3*i+1] -= ('A'-'9'-1); ++ } ++ vnd_local_bd_addr[5-i] = ((uint8_t)buf[3*i]-'0')*16 + ((uint8_t)buf[3*i+1]-'0'); ++ } ++ set_fs(oldfs); ++ filp_close(bdaddr_file, NULL); ++ return 0; ++} ++#endif ++ ++#endif /* CONFIG_BLUEDROID */ ++ ++ ++typedef struct { ++ struct usb_interface *intf; ++ struct usb_device *udev; ++ int pipe_in, pipe_out; ++ uint8_t *send_pkt; ++ uint8_t *rcv_pkt; ++ struct hci_command_hdr *cmd_hdr; ++ struct hci_event_hdr *evt_hdr; ++ struct hci_ev_cmd_complete *cmd_cmp; ++ uint8_t *req_para, *rsp_para; ++ uint8_t *fw_data; ++ int pkt_len; ++ int fw_len; ++} firmware_info; ++ ++/******************************* ++** Reasil patch code ++********************************/ ++#define CMD_CMP_EVT 0x0e ++#define RCV_PKT_LEN 64 ++#define SEND_PKT_LEN 300 ++#define MSG_TO 1000 ++#define PATCH_SEG_MAX 252 ++#define DATA_END 0x80 ++#define DOWNLOAD_OPCODE 0xfc02 ++#define HCI_VSC_UPDATE_PT_CMD 0xFC75 ++#define BTOFF_OPCODE 0xfc28 ++#define TRUE 1 ++#define FALSE 0 ++#define CMD_HDR_LEN sizeof(struct hci_command_hdr) ++#define EVT_HDR_LEN sizeof(struct hci_event_hdr) ++#define CMD_CMP_LEN sizeof(struct hci_ev_cmd_complete) ++#define MAX_PATCH_SIZE_24K (1024*24) ++#define MAX_PATCH_SIZE_40K (1024*40) ++ ++ ++#define FW_RAM_ADID_BASE_ADDR 0x101788 ++#define FW_RAM_PATCH_BASE_ADDR 0x184000 ++#define FW_ADID_BASE_NAME "fw_adid_8800dc.bin" ++#define FW_PATCH_TABLE_NAME "fw_patch_table_8800dc.bin" ++#define FW_PATCH_BASE_NAME "fw_patch_8800dc.bin" ++#define FW_PATCH_TABLE_NAME_U02 "fw_patch_table_8800dc_u02.bin" ++#define FW_PATCH_BASE_NAME_U02 "fw_patch_8800dc_u02.bin" ++#define FW_PATCH_TABLE_NAME_U02H "fw_patch_table_8800dc_u02h.bin" ++#define FW_PATCH_BASE_NAME_U02H "fw_patch_8800dc_u02h.bin" ++#define AICBT_PT_TAG "AICBT_PT_TAG" ++ ++enum aicbt_patch_table_type { ++ AICBT_PT_NULL = 0x00, ++ AICBT_PT_TRAP, ++ AICBT_PT_B4, ++ AICBT_PT_BTMODE, ++ AICBT_PT_PWRON, ++ AICBT_PT_AF, ++ AICBT_PT_VER, ++ AICBT_PT_MAX, ++}; ++ ++#define HCI_VSC_FW_STATUS_GET_CMD 0xFC78 ++ ++struct fw_status { ++ u8 status; ++} __packed; ++ ++#define HCI_PATCH_DATA_MAX_LEN 240 ++#define HCI_VSC_MEM_WR_SIZE 240 ++#define HCI_VSC_MEM_RD_SIZE 128 ++#define HCI_VSC_UPDATE_PT_SIZE 249 ++#define HCI_PT_MAX_LEN 31 ++ ++#define HCI_VSC_DBG_RD_MEM_CMD 0xFC01 ++ ++struct hci_dbg_rd_mem_cmd { ++ __le32 start_addr; ++ __u8 type; ++ __u8 length; ++}__attribute__ ((packed)); ++ ++struct hci_dbg_rd_mem_cmd_evt { ++ __u8 status; ++ __u8 length; ++ __u8 data[HCI_VSC_MEM_RD_SIZE]; ++}__attribute__ ((packed)); ++ ++struct long_buffer_tag { ++ __u8 length; ++ __u8 data[HCI_VSC_MEM_WR_SIZE]; ++}; ++ ++struct hci_dbg_wr_mem_cmd { ++ __le32 start_addr; ++ __u8 type; ++ __u8 length; ++ __u8 data[HCI_VSC_MEM_WR_SIZE]; ++}; ++ ++struct aicbt_patch_table { ++ char *name; ++ uint32_t type; ++ uint32_t *data; ++ uint32_t len; ++ struct aicbt_patch_table *next; ++}; ++ ++struct aicbt_patch_table_cmd { ++ uint8_t patch_num; ++ uint32_t patch_table_addr[31]; ++ uint32_t patch_table_data[31]; ++}__attribute__ ((packed)); ++ ++ ++enum aic_endpoit { ++ CTRL_EP = 0, ++ INTR_EP = 3, ++ BULK_EP = 1, ++ ISOC_EP = 4 ++}; ++ ++/* #define HCI_VERSION_CODE KERNEL_VERSION(3, 14, 41) */ ++#define HCI_VERSION_CODE LINUX_VERSION_CODE ++ ++int aic_load_firmware(u8 ** fw_buf, const char *name, struct device *device); ++int aicbt_patch_table_free(struct aicbt_patch_table **head); ++int download_patch(firmware_info *fw_info, int cached); ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 38) ++#define NUM_REASSEMBLY 3 ++#else ++#define NUM_REASSEMBLY 4 ++#endif ++ +--- /dev/null ++++ b/drivers/bluetooth/aic_btusb/aic_btusb_external_featrue.c +@@ -0,0 +1,126 @@ ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/init.h> ++#include <linux/slab.h> ++#include <linux/types.h> ++#include <linux/sched.h> ++#include <linux/errno.h> ++#include <linux/skbuff.h> ++#include <linux/usb.h> ++#include <linux/poll.h> ++#include <linux/cdev.h> ++#include <linux/device.h> ++ ++ ++ ++#define IOCTL_CHAR_DEVICE_NAME "aic_btusb_ex_dev" ++ ++#define SET_APCF_PARAMETER _IOR('E', 181, int) ++ ++static dev_t ioctl_devid; /* bt char device number */ ++static struct cdev ioctl_char_dev; /* bt character device structure */ ++static struct class *ioctl_char_class; /* device class for usb char driver */ ++ ++extern struct file_operations ioctl_chrdev_ops; ++ ++extern void btchr_external_write(char* data, int len); ++ ++static long ioctl_ioctl(struct file *file_p,unsigned int cmd, unsigned long arg) ++{ ++ char data[1024]; ++ int ret = 0; ++ ++ printk("%s enter\r\n", __func__); ++ memset(data, 0, 1024); ++ switch(cmd) ++ { ++ case SET_APCF_PARAMETER: ++ printk("set apcf parameter\r\n"); ++ ret = copy_from_user(data, (int __user *)arg, 1024); ++ btchr_external_write(&data[1], (int)data[0]); ++ break; ++ ++ default: ++ printk("unknow cmdr\r\n"); ++ break; ++ } ++ return 0; ++} ++ ++ ++#ifdef CONFIG_COMPAT ++static long compat_ioctlchr_ioctl (struct file *filp, unsigned int cmd, unsigned long arg) ++{ ++ return ioctl_ioctl(filp, cmd, (unsigned long) compat_ptr(arg)); ++} ++#endif ++ ++ ++struct file_operations ioctl_chrdev_ops = { ++ unlocked_ioctl : ioctl_ioctl, ++#ifdef CONFIG_COMPAT ++ compat_ioctl : compat_ioctlchr_ioctl, ++#endif ++ ++}; ++ ++static int __init init_extenal_ioctl(void){ ++ int res = 0; ++ struct device *dev; ++ ++ printk("%s enter\r\n", __func__); ++ ++ ioctl_char_class = class_create(THIS_MODULE, IOCTL_CHAR_DEVICE_NAME); ++ if (IS_ERR(ioctl_char_class)) { ++ printk("Failed to create ioctl char class"); ++ } ++ ++ res = alloc_chrdev_region(&ioctl_devid, 0, 1, IOCTL_CHAR_DEVICE_NAME); ++ if (res < 0) { ++ printk("Failed to allocate ioctl char device"); ++ goto err_alloc; ++ } ++ ++ dev = device_create(ioctl_char_class, NULL, ioctl_devid, NULL, IOCTL_CHAR_DEVICE_NAME); ++ if (IS_ERR(dev)) { ++ printk("Failed to create ioctl char device"); ++ res = PTR_ERR(dev); ++ goto err_create; ++ } ++ ++ cdev_init(&ioctl_char_dev, &ioctl_chrdev_ops); ++ res = cdev_add(&ioctl_char_dev, ioctl_devid, 1); ++ if (res < 0) { ++ printk("Failed to add ioctl char device"); ++ goto err_add; ++ } ++ ++ return res; ++ ++err_add: ++ device_destroy(ioctl_char_class, ioctl_devid); ++err_create: ++ unregister_chrdev_region(ioctl_devid, 1); ++err_alloc: ++ class_destroy(ioctl_char_class); ++ ++ return res; ++ ++} ++static void __exit deinit_extenal_ioctl(void){ ++ printk("%s enter\r\n", __func__); ++ device_destroy(ioctl_char_class, ioctl_devid); ++ cdev_del(&ioctl_char_dev); ++ unregister_chrdev_region(ioctl_devid, 1); ++ class_destroy(ioctl_char_class); ++ ++} ++ ++module_init(init_extenal_ioctl); ++module_exit(deinit_extenal_ioctl); ++ ++ ++MODULE_AUTHOR("AicSemi Corporation"); ++MODULE_DESCRIPTION("AicSemi Bluetooth USB driver version"); ++MODULE_LICENSE("GPL"); ++ +--- /dev/null ++++ b/drivers/bluetooth/aic_btusb/aic_btusb_external_featrue.h +@@ -0,0 +1,3 @@ ++ ++void btchr_external_write(char* data, int len); ++ diff --git a/target/linux/starfive/patches-6.6/0114-riscv-dts-starfive-visionfive-2-Sync-the-sound-card-.patch b/target/linux/starfive/patches-6.6/0114-riscv-dts-starfive-visionfive-2-Sync-the-sound-card-.patch new file mode 100644 index 0000000000..c599a44cf7 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0114-riscv-dts-starfive-visionfive-2-Sync-the-sound-card-.patch @@ -0,0 +1,67 @@ +From fdb2822982887c7048f34601688ae2406ccbcfcc Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Mon, 15 Apr 2024 11:29:17 +0800 +Subject: [PATCH 114/116] riscv: dts: starfive: visionfive 2: Sync the sound + card names with v5.15 and v6.1 + +Sync the sound card names. So we can build Debian images with the +same process. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + .../dts/starfive/jh7110-starfive-visionfive-2-tdm.dts | 2 +- + .../dts/starfive/jh7110-starfive-visionfive-2-wm8960.dts | 2 +- + .../boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi | 8 ++++---- + 3 files changed, 6 insertions(+), 6 deletions(-) + +--- a/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-tdm.dts ++++ b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-tdm.dts +@@ -8,7 +8,7 @@ + #include "jh7110-starfive-visionfive-2-v1.3b.dts" + + / { +- sound-tdm { ++ sound5: snd-card5 { + compatible = "simple-audio-card"; + #address-cells = <1>; + #size-cells = <0>; +--- a/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-wm8960.dts ++++ b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-wm8960.dts +@@ -9,7 +9,7 @@ + + / { + /* i2s + wm8960 */ +- sound-wm8960 { ++ sound6: snd-card6 { + compatible = "simple-audio-card"; + #address-cells = <1>; + #size-cells = <0>; +--- a/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi +@@ -91,9 +91,9 @@ + #sound-dai-cells = <0>; + }; + +- sound-pwmdac { ++ sound3: snd-card3 { + compatible = "simple-audio-card"; +- simple-audio-card,name = "StarFive-PWMDAC-Sound-Card"; ++ simple-audio-card,name = "Starfive-PWMDAC-Sound-Card"; + #address-cells = <1>; + #size-cells = <0>; + +@@ -113,12 +113,12 @@ + }; + }; + +- sound-hdmi { ++ sound1: snd-card1 { + compatible = "simple-audio-card"; + #address-cells = <1>; + #size-cells = <0>; + +- simple-audio-card,name = "StarFive-HDMI-Sound-Card"; ++ simple-audio-card,name = "Starfive-HDMI-Sound-Card"; + simple-audio-card,dai-link@0 { + reg = <0>; + format = "i2s"; diff --git a/target/linux/starfive/patches-6.6/0115-clk-starfive-jh7110-Change-uart3-uart5-clk-register-.patch b/target/linux/starfive/patches-6.6/0115-clk-starfive-jh7110-Change-uart3-uart5-clk-register-.patch new file mode 100644 index 0000000000..6b34f0ec56 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0115-clk-starfive-jh7110-Change-uart3-uart5-clk-register-.patch @@ -0,0 +1,53 @@ +From dac6f3021895e7678552789edff22f1dc909e274 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Mon, 15 Apr 2024 20:59:35 +0800 +Subject: [PATCH 115/116] clk: starfive: jh7110: Change uart3-uart5 clk + register info + +The core_clk division register of uart3-uart5 include fractional and +integral parts, but now only use the integral part, so include shift +operation. The integral part include 8 bit, so the max value can be +configed is 255. + +Signed-off-by: Yanhong Wang <yanhong.wang@starfivetech.com> +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + drivers/clk/starfive/clk-starfive-jh71x0.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +--- a/drivers/clk/starfive/clk-starfive-jh71x0.c ++++ b/drivers/clk/starfive/clk-starfive-jh71x0.c +@@ -10,6 +10,8 @@ + #include <linux/device.h> + #include <linux/io.h> + ++#include <dt-bindings/clock/starfive,jh7110-crg.h> ++ + #include "clk-starfive-jh71x0.h" + + static struct jh71x0_clk *jh71x0_clk_from(struct clk_hw *hw) +@@ -70,6 +72,11 @@ static unsigned long jh71x0_clk_recalc_r + struct jh71x0_clk *clk = jh71x0_clk_from(hw); + u32 div = jh71x0_clk_reg_get(clk) & JH71X0_CLK_DIV_MASK; + ++ if (clk->idx == JH7110_SYSCLK_UART3_CORE || ++ clk->idx == JH7110_SYSCLK_UART4_CORE || ++ clk->idx == JH7110_SYSCLK_UART5_CORE) ++ div >>= 8; ++ + return div ? parent_rate / div : 0; + } + +@@ -110,6 +117,12 @@ static int jh71x0_clk_set_rate(struct cl + unsigned long div = clamp(DIV_ROUND_CLOSEST(parent_rate, rate), + 1UL, (unsigned long)clk->max_div); + ++ /* UART3-5: [15:8]: integer part of the divisor. [7:0] fraction part of the divisor */ ++ if (clk->idx == JH7110_SYSCLK_UART3_CORE || ++ clk->idx == JH7110_SYSCLK_UART4_CORE || ++ clk->idx == JH7110_SYSCLK_UART5_CORE) ++ div <<= 8; ++ + jh71x0_clk_reg_rmw(clk, JH71X0_CLK_DIV_MASK, div); + return 0; + } diff --git a/target/linux/starfive/patches-6.6/0116-riscv-dts-starfive-visionfive-2-Quote-corresponding-.patch b/target/linux/starfive/patches-6.6/0116-riscv-dts-starfive-visionfive-2-Quote-corresponding-.patch new file mode 100644 index 0000000000..659c442c82 --- /dev/null +++ b/target/linux/starfive/patches-6.6/0116-riscv-dts-starfive-visionfive-2-Quote-corresponding-.patch @@ -0,0 +1,32 @@ +From 1ea169bb83d3b556eb9054f1a3e8ce0411370293 Mon Sep 17 00:00:00 2001 +From: Hal Feng <hal.feng@starfivetech.com> +Date: Wed, 24 Apr 2024 14:23:07 +0800 +Subject: [PATCH 116/116] riscv: dts: starfive: visionfive 2: Quote + corresponding regulators in hdmi and vin + +So PMIC can start to work before HDMI and VIN. + +Signed-off-by: Hal Feng <hal.feng@starfivetech.com> +--- + arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi +@@ -961,6 +961,8 @@ + pinctrl-names = "default"; + pinctrl-0 = <&hdmi_pins>; + hpd-gpio = <&sysgpio 15 GPIO_ACTIVE_HIGH>; ++ hdmi_0p9-supply = <&hdmi_0p9>; ++ hdmi_1p8-supply = <&hdmi_1p8>; + + hdmi_in: port { + #address-cells = <1>; +@@ -1084,6 +1086,7 @@ + &vin_sysctl { + /* when use dvp open this pinctrl*/ + status = "okay"; ++ mipi_0p9-supply = <&mipi_0p9>; + + ports { + #address-cells = <1>; diff --git a/target/linux/starfive/patches-6.6/1000-serial-8250_dw-Add-starfive-jh7100-hsuart-compatible.patch b/target/linux/starfive/patches-6.6/1000-serial-8250_dw-Add-starfive-jh7100-hsuart-compatible.patch new file mode 100644 index 0000000000..463c7eb7b6 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1000-serial-8250_dw-Add-starfive-jh7100-hsuart-compatible.patch @@ -0,0 +1,25 @@ +From b7b4fad8784d7ab4fcd929a56c3e5366046fe7fc Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <kernel@esmil.dk> +Date: Thu, 14 Oct 2021 20:56:54 +0200 +Subject: [PATCH 1000/1024] serial: 8250_dw: Add starfive,jh7100-hsuart + compatible + +This adds a compatible for the high speed UARTs on the StarFive JH7100 +RISC-V SoC. Just like the regular uarts we also need to keep the input +clocks at their default rate and rely only on the divisor in the UART. + +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +--- + drivers/tty/serial/8250/8250_dw.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/drivers/tty/serial/8250/8250_dw.c ++++ b/drivers/tty/serial/8250/8250_dw.c +@@ -802,6 +802,7 @@ static const struct of_device_id dw8250_ + { .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data }, + { .compatible = "marvell,armada-38x-uart", .data = &dw8250_armada_38x_data }, + { .compatible = "renesas,rzn1-uart", .data = &dw8250_renesas_rzn1_data }, ++ { .compatible = "starfive,jh7100-hsuart", .data = &dw8250_starfive_jh7100_data }, + { .compatible = "starfive,jh7100-uart", .data = &dw8250_starfive_jh7100_data }, + { /* Sentinel */ } + }; diff --git a/target/linux/starfive/patches-6.6/1001-RISC-V-Add-StarFive-JH7100-audio-clock-node.patch b/target/linux/starfive/patches-6.6/1001-RISC-V-Add-StarFive-JH7100-audio-clock-node.patch new file mode 100644 index 0000000000..4cfa2a1678 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1001-RISC-V-Add-StarFive-JH7100-audio-clock-node.patch @@ -0,0 +1,32 @@ +From 3051ab0102aaf969dd85f07b6ab731dadf97ef6c Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <kernel@esmil.dk> +Date: Sat, 20 Nov 2021 17:13:22 +0100 +Subject: [PATCH 1001/1024] RISC-V: Add StarFive JH7100 audio clock node + +Add device tree node for the audio clocks on the StarFive JH7100 RISC-V +SoC. + +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +--- + arch/riscv/boot/dts/starfive/jh7100.dtsi | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +--- a/arch/riscv/boot/dts/starfive/jh7100.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7100.dtsi +@@ -158,6 +158,16 @@ + riscv,ndev = <133>; + }; + ++ audclk: clock-controller@10480000 { ++ compatible = "starfive,jh7100-audclk"; ++ reg = <0x0 0x10480000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_AUDIO_SRC>, ++ <&clkgen JH7100_CLK_AUDIO_12288>, ++ <&clkgen JH7100_CLK_DOM7AHB_BUS>; ++ clock-names = "audio_src", "audio_12288", "dom7ahb_bus"; ++ #clock-cells = <1>; ++ }; ++ + clkgen: clock-controller@11800000 { + compatible = "starfive,jh7100-clkgen"; + reg = <0x0 0x11800000 0x0 0x10000>; diff --git a/target/linux/starfive/patches-6.6/1002-drivers-tty-serial-8250-update-driver-for-JH7100.patch b/target/linux/starfive/patches-6.6/1002-drivers-tty-serial-8250-update-driver-for-JH7100.patch new file mode 100644 index 0000000000..73d25f5163 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1002-drivers-tty-serial-8250-update-driver-for-JH7100.patch @@ -0,0 +1,28 @@ +From 8ef30667e97363ca921416a60e50eb28bf88cc4f Mon Sep 17 00:00:00 2001 +From: Samin Guo <samin.guo@starfivetech.com> +Date: Fri, 8 Jan 2021 03:11:04 +0800 +Subject: [PATCH 1002/1024] drivers/tty/serial/8250: update driver for JH7100 + +--- + drivers/tty/serial/8250/8250_port.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +--- a/drivers/tty/serial/8250/8250_port.c ++++ b/drivers/tty/serial/8250/8250_port.c +@@ -72,8 +72,16 @@ static const struct serial8250_config ua + }, + [PORT_16550] = { + .name = "16550", ++#ifdef CONFIG_SOC_STARFIVE ++ .fifo_size = 16, ++ .tx_loadsz = 16, ++ .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_00, ++ .rxtrig_bytes = {1, 4, 8, 14}, ++ .flags = UART_CAP_FIFO, ++#else + .fifo_size = 1, + .tx_loadsz = 1, ++#endif + }, + [PORT_16550A] = { + .name = "16550A", diff --git a/target/linux/starfive/patches-6.6/1003-dmaengine-dw-axi-dmac-Handle-xfer-start-while-non-id.patch b/target/linux/starfive/patches-6.6/1003-dmaengine-dw-axi-dmac-Handle-xfer-start-while-non-id.patch new file mode 100644 index 0000000000..d0c692e1a5 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1003-dmaengine-dw-axi-dmac-Handle-xfer-start-while-non-id.patch @@ -0,0 +1,55 @@ +From 2d5b71006c6a93e26eb86b7efd37440c020bab46 Mon Sep 17 00:00:00 2001 +From: Samin Guo <samin.guo@starfivetech.com> +Date: Wed, 17 Nov 2021 14:50:45 +0800 +Subject: [PATCH 1003/1024] dmaengine: dw-axi-dmac: Handle xfer start while + non-idle + +Signed-off-by: Samin Guo <samin.guo@starfivetech.com> +Signed-off-by: Curry Zhang <curry.zhang@starfivetech.com> +--- + drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 12 +++++++++++- + drivers/dma/dw-axi-dmac/dw-axi-dmac.h | 1 + + 2 files changed, 12 insertions(+), 1 deletion(-) + +--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c ++++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +@@ -382,11 +382,13 @@ static void axi_chan_block_xfer_start(st + u32 irq_mask; + u8 lms = 0; /* Select AXI0 master for LLI fetching */ + ++ chan->is_err = false; + if (unlikely(axi_chan_is_hw_enable(chan))) { + dev_err(chan2dev(chan), "%s is non-idle!\n", + axi_chan_name(chan)); + +- return; ++ axi_chan_disable(chan); ++ chan->is_err = true; + } + + axi_dma_enable(chan->chip); +@@ -1028,6 +1030,14 @@ static noinline void axi_chan_handle_err + axi_chan_name(chan)); + goto out; + } ++ if (chan->is_err) { ++ struct axi_dma_desc *desc = vd_to_axi_desc(vd); ++ ++ axi_chan_block_xfer_start(chan, desc); ++ chan->is_err = false; ++ goto out; ++ } ++ + /* Remove the completed descriptor from issued list */ + list_del(&vd->node); + +--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac.h ++++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac.h +@@ -50,6 +50,7 @@ struct axi_dma_chan { + struct dma_slave_config config; + enum dma_transfer_direction direction; + bool cyclic; ++ bool is_err; + /* these other elements are all protected by vc.lock */ + bool is_paused; + }; diff --git a/target/linux/starfive/patches-6.6/1004-dmaengine-dw-axi-dmac-Add-StarFive-JH7100-support.patch b/target/linux/starfive/patches-6.6/1004-dmaengine-dw-axi-dmac-Add-StarFive-JH7100-support.patch new file mode 100644 index 0000000000..74a05cf80a --- /dev/null +++ b/target/linux/starfive/patches-6.6/1004-dmaengine-dw-axi-dmac-Add-StarFive-JH7100-support.patch @@ -0,0 +1,64 @@ +From 933f8c33d466de20e9894ef8de5b6afe478a420d Mon Sep 17 00:00:00 2001 +From: Samin Guo <samin.guo@starfivetech.com> +Date: Wed, 17 Nov 2021 14:50:45 +0800 +Subject: [PATCH 1004/1024] dmaengine: dw-axi-dmac: Add StarFive JH7100 support + +Signed-off-by: Samin Guo <samin.guo@starfivetech.com> +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +--- + drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 12 ++++++++++++ + drivers/dma/dw-axi-dmac/dw-axi-dmac.h | 4 ++++ + 2 files changed, 16 insertions(+) + +--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c ++++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +@@ -677,8 +677,13 @@ static int dw_axi_dma_set_hw_desc(struct + + hw_desc->lli->block_ts_lo = cpu_to_le32(block_ts - 1); + ++#ifdef CONFIG_SOC_STARFIVE ++ ctllo |= DWAXIDMAC_BURST_TRANS_LEN_16 << CH_CTL_L_DST_MSIZE_POS | ++ DWAXIDMAC_BURST_TRANS_LEN_16 << CH_CTL_L_SRC_MSIZE_POS; ++#else + ctllo |= DWAXIDMAC_BURST_TRANS_LEN_4 << CH_CTL_L_DST_MSIZE_POS | + DWAXIDMAC_BURST_TRANS_LEN_4 << CH_CTL_L_SRC_MSIZE_POS; ++#endif + hw_desc->lli->ctl_lo = cpu_to_le32(ctllo); + + set_desc_src_master(hw_desc); +@@ -1502,7 +1507,11 @@ static int dw_probe(struct platform_devi + * Therefore, set constraint to 1024 * 4. + */ + dw->dma.dev->dma_parms = &dw->dma_parms; ++#ifdef CONFIG_SOC_STARFIVE ++ dma_set_max_seg_size(&pdev->dev, DMAC_MAX_BLK_SIZE); ++#else + dma_set_max_seg_size(&pdev->dev, MAX_BLOCK_SIZE); ++#endif + platform_set_drvdata(pdev, chip); + + pm_runtime_enable(chip->dev); +@@ -1587,6 +1596,9 @@ static const struct of_device_id dw_dma_ + .compatible = "intel,kmb-axi-dma", + .data = (void *)AXI_DMA_FLAG_HAS_APB_REGS, + }, { ++ .compatible = "starfive,jh7100-axi-dma", ++ .data = (void *)(AXI_DMA_FLAG_HAS_RESETS | AXI_DMA_FLAG_USE_CFG2), ++ }, { + .compatible = "starfive,jh7110-axi-dma", + .data = (void *)(AXI_DMA_FLAG_HAS_RESETS | AXI_DMA_FLAG_USE_CFG2), + }, +--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac.h ++++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac.h +@@ -284,7 +284,11 @@ enum { + #define CH_CTL_L_SRC_MAST BIT(0) + + /* CH_CFG_H */ ++#ifdef CONFIG_SOC_STARFIVE ++#define CH_CFG_H_PRIORITY_POS 15 ++#else + #define CH_CFG_H_PRIORITY_POS 17 ++#endif + #define CH_CFG_H_DST_PER_POS 12 + #define CH_CFG_H_SRC_PER_POS 7 + #define CH_CFG_H_HS_SEL_DST_POS 4 diff --git a/target/linux/starfive/patches-6.6/1005-pinctrl-starfive-Reset-pinmux-settings.patch b/target/linux/starfive/patches-6.6/1005-pinctrl-starfive-Reset-pinmux-settings.patch new file mode 100644 index 0000000000..63a7a0cee9 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1005-pinctrl-starfive-Reset-pinmux-settings.patch @@ -0,0 +1,127 @@ +From c5019bf73d1e178beb6055cca254e7d3c916db7c Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <kernel@esmil.dk> +Date: Sat, 17 Jul 2021 21:50:38 +0200 +Subject: [PATCH 1005/1024] pinctrl: starfive: Reset pinmux settings + +Current u-boot doesn't seem to take into account that some GPIOs are +configured as inputs/outputs of certain peripherals on power-up. This +means it ends up configuring some GPIOs as inputs to more than one +peripheral which the documentation explicitly says is illegal. Similarly +it also ends up configuring more than one GPIO as output of the same +peripheral. While not explicitly mentioned by the documentation this +also seems like a bad idea. + +The easiest way to remedy this mess is to just disconnect all GPIOs from +peripherals and have our pinmux configuration set everything up +properly. This, however, means that we'd disconnect the serial console +from its pins for a while, so add a device tree property to keep +certain GPIOs from being reset. + +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +--- + .../pinctrl/starfive,jh7100-pinctrl.yaml | 4 ++ + .../starfive/pinctrl-starfive-jh7100.c | 66 +++++++++++++++++++ + 2 files changed, 70 insertions(+) + +--- a/Documentation/devicetree/bindings/pinctrl/starfive,jh7100-pinctrl.yaml ++++ b/Documentation/devicetree/bindings/pinctrl/starfive,jh7100-pinctrl.yaml +@@ -88,6 +88,10 @@ properties: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2, 3, 4, 5, 6] + ++ starfive,keep-gpiomux: ++ description: Keep pinmux for these GPIOs from being reset at boot. ++ $ref: /schemas/types.yaml#/definitions/uint32-array ++ + required: + - compatible + - reg +--- a/drivers/pinctrl/starfive/pinctrl-starfive-jh7100.c ++++ b/drivers/pinctrl/starfive/pinctrl-starfive-jh7100.c +@@ -203,6 +203,10 @@ static u16 starfive_drive_strength_from_ + return (clamp(i, 14U, 63U) - 14) / 7; + } + ++static bool keepmux; ++module_param(keepmux, bool, 0644); ++MODULE_PARM_DESC(keepmux, "Keep pinmux settings from previous boot stage"); ++ + struct starfive_pinctrl { + struct gpio_chip gc; + struct pinctrl_gpio_range gpios; +@@ -1225,6 +1229,65 @@ static void starfive_disable_clock(void + clk_disable_unprepare(data); + } + ++#define GPI_END (GPI_USB_OVER_CURRENT + 1) ++static void starfive_pinmux_reset(struct starfive_pinctrl *sfp) ++{ ++ static const DECLARE_BITMAP(defaults, GPI_END) = { ++ BIT_MASK(GPI_I2C0_PAD_SCK_IN) | ++ BIT_MASK(GPI_I2C0_PAD_SDA_IN) | ++ BIT_MASK(GPI_I2C1_PAD_SCK_IN) | ++ BIT_MASK(GPI_I2C1_PAD_SDA_IN) | ++ BIT_MASK(GPI_I2C2_PAD_SCK_IN) | ++ BIT_MASK(GPI_I2C2_PAD_SDA_IN) | ++ BIT_MASK(GPI_I2C3_PAD_SCK_IN) | ++ BIT_MASK(GPI_I2C3_PAD_SDA_IN) | ++ BIT_MASK(GPI_SDIO0_PAD_CARD_DETECT_N) | ++ ++ BIT_MASK(GPI_SDIO1_PAD_CARD_DETECT_N) | ++ BIT_MASK(GPI_SPI0_PAD_SS_IN_N) | ++ BIT_MASK(GPI_SPI1_PAD_SS_IN_N) | ++ BIT_MASK(GPI_SPI2_PAD_SS_IN_N) | ++ BIT_MASK(GPI_SPI2AHB_PAD_SS_N) | ++ BIT_MASK(GPI_SPI3_PAD_SS_IN_N), ++ ++ BIT_MASK(GPI_UART0_PAD_SIN) | ++ BIT_MASK(GPI_UART1_PAD_SIN) | ++ BIT_MASK(GPI_UART2_PAD_SIN) | ++ BIT_MASK(GPI_UART3_PAD_SIN) | ++ BIT_MASK(GPI_USB_OVER_CURRENT) ++ }; ++ DECLARE_BITMAP(keep, NR_GPIOS) = {}; ++ struct device_node *np = sfp->gc.parent->of_node; ++ int len = of_property_count_u32_elems(np, "starfive,keep-gpiomux"); ++ int i; ++ ++ for (i = 0; i < len; i++) { ++ u32 gpio; ++ ++ of_property_read_u32_index(np, "starfive,keep-gpiomux", i, &gpio); ++ if (gpio < NR_GPIOS) ++ set_bit(gpio, keep); ++ } ++ ++ for (i = 0; i < NR_GPIOS; i++) { ++ if (test_bit(i, keep)) ++ continue; ++ ++ writel_relaxed(GPO_DISABLE, sfp->base + GPON_DOEN_CFG + 8 * i); ++ writel_relaxed(GPO_LOW, sfp->base + GPON_DOUT_CFG + 8 * i); ++ } ++ ++ for (i = 0; i < GPI_END; i++) { ++ void __iomem *reg = sfp->base + GPI_CFG_OFFSET + 4 * i; ++ u32 din = readl_relaxed(reg); ++ ++ if (din >= 2 && din < (NR_GPIOS + 2) && test_bit(din - 2, keep)) ++ continue; ++ ++ writel_relaxed(test_bit(i, defaults), reg); ++ } ++} ++ + static int starfive_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev; +@@ -1286,6 +1349,9 @@ static int starfive_probe(struct platfor + writel(value, sfp->padctl + IO_PADSHARE_SEL); + } + ++ if (!keepmux) ++ starfive_pinmux_reset(sfp); ++ + value = readl(sfp->padctl + IO_PADSHARE_SEL); + switch (value) { + case 0: diff --git a/target/linux/starfive/patches-6.6/1006-clk-starfive-Add-flags-argument-to-JH71X0__MUX-macro.patch b/target/linux/starfive/patches-6.6/1006-clk-starfive-Add-flags-argument-to-JH71X0__MUX-macro.patch new file mode 100644 index 0000000000..a6a1bb827e --- /dev/null +++ b/target/linux/starfive/patches-6.6/1006-clk-starfive-Add-flags-argument-to-JH71X0__MUX-macro.patch @@ -0,0 +1,302 @@ +From a4a2a8886c97e14643fe3e0ab8cf67a75c1bf14d Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Date: Sat, 25 Mar 2023 22:57:06 +0100 +Subject: [PATCH 1006/1024] clk: starfive: Add flags argument to JH71X0__MUX + macro + +This flag is needed to add the CLK_SET_RATE_PARENT flag on the gmac_tx +clock on the JH7100, which in turn is needed by the dwmac-starfive +driver to set the clock properly for 1000, 100 and 10 Mbps links. + +This change was mostly made using coccinelle: + +@ match @ +expression idx, name, nparents; +@@ + JH71X0__MUX( +-idx, name, nparents, ++idx, name, 0, nparents, + ...) + +Signed-off-by: Emil Renner Berthing <emil.renner.berthing@canonical.com> +--- + .../clk/starfive/clk-starfive-jh7100-audio.c | 2 +- + drivers/clk/starfive/clk-starfive-jh7100.c | 32 +++++++++---------- + .../clk/starfive/clk-starfive-jh7110-aon.c | 6 ++-- + .../clk/starfive/clk-starfive-jh7110-isp.c | 2 +- + .../clk/starfive/clk-starfive-jh7110-sys.c | 26 +++++++-------- + drivers/clk/starfive/clk-starfive-jh71x0.h | 4 +-- + 6 files changed, 36 insertions(+), 36 deletions(-) + +--- a/drivers/clk/starfive/clk-starfive-jh7100-audio.c ++++ b/drivers/clk/starfive/clk-starfive-jh7100-audio.c +@@ -79,7 +79,7 @@ static const struct jh71x0_clk_data jh71 + JH71X0_GDIV(JH7100_AUDCLK_USB_LPM, "usb_lpm", CLK_IGNORE_UNUSED, 4, JH7100_AUDCLK_USB_APB), + JH71X0_GDIV(JH7100_AUDCLK_USB_STB, "usb_stb", CLK_IGNORE_UNUSED, 3, JH7100_AUDCLK_USB_APB), + JH71X0__DIV(JH7100_AUDCLK_APB_EN, "apb_en", 8, JH7100_AUDCLK_DOM7AHB_BUS), +- JH71X0__MUX(JH7100_AUDCLK_VAD_MEM, "vad_mem", 2, ++ JH71X0__MUX(JH7100_AUDCLK_VAD_MEM, "vad_mem", 0, 2, + JH7100_AUDCLK_VAD_INTMEM, + JH7100_AUDCLK_AUDIO_12288), + }; +--- a/drivers/clk/starfive/clk-starfive-jh7100.c ++++ b/drivers/clk/starfive/clk-starfive-jh7100.c +@@ -24,48 +24,48 @@ + #define JH7100_CLK_GMAC_GR_MII_RX (JH7100_CLK_END + 3) + + static const struct jh71x0_clk_data jh7100_clk_data[] __initconst = { +- JH71X0__MUX(JH7100_CLK_CPUNDBUS_ROOT, "cpundbus_root", 4, ++ JH71X0__MUX(JH7100_CLK_CPUNDBUS_ROOT, "cpundbus_root", 0, 4, + JH7100_CLK_OSC_SYS, + JH7100_CLK_PLL0_OUT, + JH7100_CLK_PLL1_OUT, + JH7100_CLK_PLL2_OUT), +- JH71X0__MUX(JH7100_CLK_DLA_ROOT, "dla_root", 3, ++ JH71X0__MUX(JH7100_CLK_DLA_ROOT, "dla_root", 0, 3, + JH7100_CLK_OSC_SYS, + JH7100_CLK_PLL1_OUT, + JH7100_CLK_PLL2_OUT), +- JH71X0__MUX(JH7100_CLK_DSP_ROOT, "dsp_root", 4, ++ JH71X0__MUX(JH7100_CLK_DSP_ROOT, "dsp_root", 0, 4, + JH7100_CLK_OSC_SYS, + JH7100_CLK_PLL0_OUT, + JH7100_CLK_PLL1_OUT, + JH7100_CLK_PLL2_OUT), +- JH71X0__MUX(JH7100_CLK_GMACUSB_ROOT, "gmacusb_root", 3, ++ JH71X0__MUX(JH7100_CLK_GMACUSB_ROOT, "gmacusb_root", 0, 3, + JH7100_CLK_OSC_SYS, + JH7100_CLK_PLL0_OUT, + JH7100_CLK_PLL2_OUT), +- JH71X0__MUX(JH7100_CLK_PERH0_ROOT, "perh0_root", 2, ++ JH71X0__MUX(JH7100_CLK_PERH0_ROOT, "perh0_root", 0, 2, + JH7100_CLK_OSC_SYS, + JH7100_CLK_PLL0_OUT), +- JH71X0__MUX(JH7100_CLK_PERH1_ROOT, "perh1_root", 2, ++ JH71X0__MUX(JH7100_CLK_PERH1_ROOT, "perh1_root", 0, 2, + JH7100_CLK_OSC_SYS, + JH7100_CLK_PLL2_OUT), +- JH71X0__MUX(JH7100_CLK_VIN_ROOT, "vin_root", 3, ++ JH71X0__MUX(JH7100_CLK_VIN_ROOT, "vin_root", 0, 3, + JH7100_CLK_OSC_SYS, + JH7100_CLK_PLL1_OUT, + JH7100_CLK_PLL2_OUT), +- JH71X0__MUX(JH7100_CLK_VOUT_ROOT, "vout_root", 3, ++ JH71X0__MUX(JH7100_CLK_VOUT_ROOT, "vout_root", 0, 3, + JH7100_CLK_OSC_AUD, + JH7100_CLK_PLL0_OUT, + JH7100_CLK_PLL2_OUT), + JH71X0_GDIV(JH7100_CLK_AUDIO_ROOT, "audio_root", 0, 8, JH7100_CLK_PLL0_OUT), +- JH71X0__MUX(JH7100_CLK_CDECHIFI4_ROOT, "cdechifi4_root", 3, ++ JH71X0__MUX(JH7100_CLK_CDECHIFI4_ROOT, "cdechifi4_root", 0, 3, + JH7100_CLK_OSC_SYS, + JH7100_CLK_PLL1_OUT, + JH7100_CLK_PLL2_OUT), +- JH71X0__MUX(JH7100_CLK_CDEC_ROOT, "cdec_root", 3, ++ JH71X0__MUX(JH7100_CLK_CDEC_ROOT, "cdec_root", 0, 3, + JH7100_CLK_OSC_SYS, + JH7100_CLK_PLL0_OUT, + JH7100_CLK_PLL1_OUT), +- JH71X0__MUX(JH7100_CLK_VOUTBUS_ROOT, "voutbus_root", 3, ++ JH71X0__MUX(JH7100_CLK_VOUTBUS_ROOT, "voutbus_root", 0, 3, + JH7100_CLK_OSC_AUD, + JH7100_CLK_PLL0_OUT, + JH7100_CLK_PLL2_OUT), +@@ -76,7 +76,7 @@ static const struct jh71x0_clk_data jh71 + JH71X0_GDIV(JH7100_CLK_PLL0_TESTOUT, "pll0_testout", 0, 31, JH7100_CLK_PERH0_SRC), + JH71X0_GDIV(JH7100_CLK_PLL1_TESTOUT, "pll1_testout", 0, 31, JH7100_CLK_DLA_ROOT), + JH71X0_GDIV(JH7100_CLK_PLL2_TESTOUT, "pll2_testout", 0, 31, JH7100_CLK_PERH1_SRC), +- JH71X0__MUX(JH7100_CLK_PLL2_REF, "pll2_refclk", 2, ++ JH71X0__MUX(JH7100_CLK_PLL2_REF, "pll2_refclk", 0, 2, + JH7100_CLK_OSC_SYS, + JH7100_CLK_OSC_AUD), + JH71X0__DIV(JH7100_CLK_CPU_CORE, "cpu_core", 8, JH7100_CLK_CPUNBUS_ROOT_DIV), +@@ -142,7 +142,7 @@ static const struct jh71x0_clk_data jh71 + JH71X0__DIV(JH7100_CLK_NOC_COG, "noc_cog", 8, JH7100_CLK_DLA_ROOT), + JH71X0_GATE(JH7100_CLK_NNE_AHB, "nne_ahb", 0, JH7100_CLK_AHB_BUS), + JH71X0__DIV(JH7100_CLK_NNEBUS_SRC1, "nnebus_src1", 4, JH7100_CLK_DSP_ROOT), +- JH71X0__MUX(JH7100_CLK_NNE_BUS, "nne_bus", 2, ++ JH71X0__MUX(JH7100_CLK_NNE_BUS, "nne_bus", 0, 2, + JH7100_CLK_CPU_AXI, + JH7100_CLK_NNEBUS_SRC1), + JH71X0_GATE(JH7100_CLK_NNE_AXI, "nne_axi", 0, JH7100_CLK_NNE_BUS), +@@ -166,7 +166,7 @@ static const struct jh71x0_clk_data jh71 + JH71X0_GDIV(JH7100_CLK_USBPHY_125M, "usbphy_125m", 0, 8, JH7100_CLK_USBPHY_ROOTDIV), + JH71X0_GDIV(JH7100_CLK_USBPHY_PLLDIV25M, "usbphy_plldiv25m", 0, 32, + JH7100_CLK_USBPHY_ROOTDIV), +- JH71X0__MUX(JH7100_CLK_USBPHY_25M, "usbphy_25m", 2, ++ JH71X0__MUX(JH7100_CLK_USBPHY_25M, "usbphy_25m", 0, 2, + JH7100_CLK_OSC_SYS, + JH7100_CLK_USBPHY_PLLDIV25M), + JH71X0_FDIV(JH7100_CLK_AUDIO_DIV, "audio_div", JH7100_CLK_AUDIO_ROOT), +@@ -200,12 +200,12 @@ static const struct jh71x0_clk_data jh71 + JH71X0_GDIV(JH7100_CLK_GMAC_GTX, "gmac_gtxclk", 0, 255, JH7100_CLK_GMAC_ROOT_DIV), + JH71X0_GDIV(JH7100_CLK_GMAC_RMII_TX, "gmac_rmii_txclk", 0, 8, JH7100_CLK_GMAC_RMII_REF), + JH71X0_GDIV(JH7100_CLK_GMAC_RMII_RX, "gmac_rmii_rxclk", 0, 8, JH7100_CLK_GMAC_RMII_REF), +- JH71X0__MUX(JH7100_CLK_GMAC_TX, "gmac_tx", 3, ++ JH71X0__MUX(JH7100_CLK_GMAC_TX, "gmac_tx", 0, 3, + JH7100_CLK_GMAC_GTX, + JH7100_CLK_GMAC_TX_INV, + JH7100_CLK_GMAC_RMII_TX), + JH71X0__INV(JH7100_CLK_GMAC_TX_INV, "gmac_tx_inv", JH7100_CLK_GMAC_TX), +- JH71X0__MUX(JH7100_CLK_GMAC_RX_PRE, "gmac_rx_pre", 2, ++ JH71X0__MUX(JH7100_CLK_GMAC_RX_PRE, "gmac_rx_pre", 0, 2, + JH7100_CLK_GMAC_GR_MII_RX, + JH7100_CLK_GMAC_RMII_RX), + JH71X0__INV(JH7100_CLK_GMAC_RX_INV, "gmac_rx_inv", JH7100_CLK_GMAC_RX_PRE), +--- a/drivers/clk/starfive/clk-starfive-jh7110-aon.c ++++ b/drivers/clk/starfive/clk-starfive-jh7110-aon.c +@@ -26,7 +26,7 @@ + static const struct jh71x0_clk_data jh7110_aonclk_data[] = { + /* source */ + JH71X0__DIV(JH7110_AONCLK_OSC_DIV4, "osc_div4", 4, JH7110_AONCLK_OSC), +- JH71X0__MUX(JH7110_AONCLK_APB_FUNC, "apb_func", 2, ++ JH71X0__MUX(JH7110_AONCLK_APB_FUNC, "apb_func", 0, 2, + JH7110_AONCLK_OSC_DIV4, + JH7110_AONCLK_OSC), + /* gmac0 */ +@@ -39,7 +39,7 @@ static const struct jh71x0_clk_data jh71 + JH7110_AONCLK_GMAC0_GTXCLK, + JH7110_AONCLK_GMAC0_RMII_RTX), + JH71X0__INV(JH7110_AONCLK_GMAC0_TX_INV, "gmac0_tx_inv", JH7110_AONCLK_GMAC0_TX), +- JH71X0__MUX(JH7110_AONCLK_GMAC0_RX, "gmac0_rx", 2, ++ JH71X0__MUX(JH7110_AONCLK_GMAC0_RX, "gmac0_rx", 0, 2, + JH7110_AONCLK_GMAC0_RGMII_RXIN, + JH7110_AONCLK_GMAC0_RMII_RTX), + JH71X0__INV(JH7110_AONCLK_GMAC0_RX_INV, "gmac0_rx_inv", JH7110_AONCLK_GMAC0_RX), +@@ -48,7 +48,7 @@ static const struct jh71x0_clk_data jh71 + /* rtc */ + JH71X0_GATE(JH7110_AONCLK_RTC_APB, "rtc_apb", 0, JH7110_AONCLK_APB_BUS), + JH71X0__DIV(JH7110_AONCLK_RTC_INTERNAL, "rtc_internal", 1022, JH7110_AONCLK_OSC), +- JH71X0__MUX(JH7110_AONCLK_RTC_32K, "rtc_32k", 2, ++ JH71X0__MUX(JH7110_AONCLK_RTC_32K, "rtc_32k", 0, 2, + JH7110_AONCLK_RTC_OSC, + JH7110_AONCLK_RTC_INTERNAL), + JH71X0_GATE(JH7110_AONCLK_RTC_CAL, "rtc_cal", 0, JH7110_AONCLK_OSC), +--- a/drivers/clk/starfive/clk-starfive-jh7110-isp.c ++++ b/drivers/clk/starfive/clk-starfive-jh7110-isp.c +@@ -53,7 +53,7 @@ static const struct jh71x0_clk_data jh71 + JH7110_ISPCLK_MIPI_RX0_PXL), + JH71X0_GATE(JH7110_ISPCLK_VIN_PIXEL_IF3, "vin_pixel_if3", 0, + JH7110_ISPCLK_MIPI_RX0_PXL), +- JH71X0__MUX(JH7110_ISPCLK_VIN_P_AXI_WR, "vin_p_axi_wr", 2, ++ JH71X0__MUX(JH7110_ISPCLK_VIN_P_AXI_WR, "vin_p_axi_wr", 0, 2, + JH7110_ISPCLK_MIPI_RX0_PXL, + JH7110_ISPCLK_DVP_INV), + /* ispv2_top_wrapper */ +--- a/drivers/clk/starfive/clk-starfive-jh7110-sys.c ++++ b/drivers/clk/starfive/clk-starfive-jh7110-sys.c +@@ -36,18 +36,18 @@ + + static const struct jh71x0_clk_data jh7110_sysclk_data[] __initconst = { + /* root */ +- JH71X0__MUX(JH7110_SYSCLK_CPU_ROOT, "cpu_root", 2, ++ JH71X0__MUX(JH7110_SYSCLK_CPU_ROOT, "cpu_root", 0, 2, + JH7110_SYSCLK_OSC, + JH7110_SYSCLK_PLL0_OUT), + JH71X0__DIV(JH7110_SYSCLK_CPU_CORE, "cpu_core", 7, JH7110_SYSCLK_CPU_ROOT), + JH71X0__DIV(JH7110_SYSCLK_CPU_BUS, "cpu_bus", 2, JH7110_SYSCLK_CPU_CORE), +- JH71X0__MUX(JH7110_SYSCLK_GPU_ROOT, "gpu_root", 2, ++ JH71X0__MUX(JH7110_SYSCLK_GPU_ROOT, "gpu_root", 0, 2, + JH7110_SYSCLK_PLL2_OUT, + JH7110_SYSCLK_PLL1_OUT), + JH71X0_MDIV(JH7110_SYSCLK_PERH_ROOT, "perh_root", 2, 2, + JH7110_SYSCLK_PLL0_OUT, + JH7110_SYSCLK_PLL2_OUT), +- JH71X0__MUX(JH7110_SYSCLK_BUS_ROOT, "bus_root", 2, ++ JH71X0__MUX(JH7110_SYSCLK_BUS_ROOT, "bus_root", 0, 2, + JH7110_SYSCLK_OSC, + JH7110_SYSCLK_PLL2_OUT), + JH71X0__DIV(JH7110_SYSCLK_NOCSTG_BUS, "nocstg_bus", 3, JH7110_SYSCLK_BUS_ROOT), +@@ -62,7 +62,7 @@ static const struct jh71x0_clk_data jh71 + JH71X0__DIV(JH7110_SYSCLK_PLL2_DIV2, "pll2_div2", 2, JH7110_SYSCLK_PLL2_OUT), + JH71X0__DIV(JH7110_SYSCLK_AUDIO_ROOT, "audio_root", 8, JH7110_SYSCLK_PLL2_OUT), + JH71X0__DIV(JH7110_SYSCLK_MCLK_INNER, "mclk_inner", 64, JH7110_SYSCLK_AUDIO_ROOT), +- JH71X0__MUX(JH7110_SYSCLK_MCLK, "mclk", 2, ++ JH71X0__MUX(JH7110_SYSCLK_MCLK, "mclk", 0, 2, + JH7110_SYSCLK_MCLK_INNER, + JH7110_SYSCLK_MCLK_EXT), + JH71X0_GATE(JH7110_SYSCLK_MCLK_OUT, "mclk_out", 0, JH7110_SYSCLK_MCLK_INNER), +@@ -96,7 +96,7 @@ static const struct jh71x0_clk_data jh71 + JH71X0__DIV(JH7110_SYSCLK_OSC_DIV2, "osc_div2", 2, JH7110_SYSCLK_OSC), + JH71X0__DIV(JH7110_SYSCLK_PLL1_DIV4, "pll1_div4", 2, JH7110_SYSCLK_PLL1_DIV2), + JH71X0__DIV(JH7110_SYSCLK_PLL1_DIV8, "pll1_div8", 2, JH7110_SYSCLK_PLL1_DIV4), +- JH71X0__MUX(JH7110_SYSCLK_DDR_BUS, "ddr_bus", 4, ++ JH71X0__MUX(JH7110_SYSCLK_DDR_BUS, "ddr_bus", 0, 4, + JH7110_SYSCLK_OSC_DIV2, + JH7110_SYSCLK_PLL1_DIV2, + JH7110_SYSCLK_PLL1_DIV4, +@@ -186,7 +186,7 @@ static const struct jh71x0_clk_data jh71 + JH71X0__DIV(JH7110_SYSCLK_GMAC1_RMII_RTX, "gmac1_rmii_rtx", 30, + JH7110_SYSCLK_GMAC1_RMII_REFIN), + JH71X0_GDIV(JH7110_SYSCLK_GMAC1_PTP, "gmac1_ptp", 0, 31, JH7110_SYSCLK_GMAC_SRC), +- JH71X0__MUX(JH7110_SYSCLK_GMAC1_RX, "gmac1_rx", 2, ++ JH71X0__MUX(JH7110_SYSCLK_GMAC1_RX, "gmac1_rx", 0, 2, + JH7110_SYSCLK_GMAC1_RGMII_RXIN, + JH7110_SYSCLK_GMAC1_RMII_RTX), + JH71X0__INV(JH7110_SYSCLK_GMAC1_RX_INV, "gmac1_rx_inv", JH7110_SYSCLK_GMAC1_RX), +@@ -270,11 +270,11 @@ static const struct jh71x0_clk_data jh71 + JH71X0_MDIV(JH7110_SYSCLK_I2STX0_LRCK_MST, "i2stx0_lrck_mst", 64, 2, + JH7110_SYSCLK_I2STX0_BCLK_MST_INV, + JH7110_SYSCLK_I2STX0_BCLK_MST), +- JH71X0__MUX(JH7110_SYSCLK_I2STX0_BCLK, "i2stx0_bclk", 2, ++ JH71X0__MUX(JH7110_SYSCLK_I2STX0_BCLK, "i2stx0_bclk", 0, 2, + JH7110_SYSCLK_I2STX0_BCLK_MST, + JH7110_SYSCLK_I2STX_BCLK_EXT), + JH71X0__INV(JH7110_SYSCLK_I2STX0_BCLK_INV, "i2stx0_bclk_inv", JH7110_SYSCLK_I2STX0_BCLK), +- JH71X0__MUX(JH7110_SYSCLK_I2STX0_LRCK, "i2stx0_lrck", 2, ++ JH71X0__MUX(JH7110_SYSCLK_I2STX0_LRCK, "i2stx0_lrck", 0, 2, + JH7110_SYSCLK_I2STX0_LRCK_MST, + JH7110_SYSCLK_I2STX_LRCK_EXT), + /* i2stx1 */ +@@ -285,11 +285,11 @@ static const struct jh71x0_clk_data jh71 + JH71X0_MDIV(JH7110_SYSCLK_I2STX1_LRCK_MST, "i2stx1_lrck_mst", 64, 2, + JH7110_SYSCLK_I2STX1_BCLK_MST_INV, + JH7110_SYSCLK_I2STX1_BCLK_MST), +- JH71X0__MUX(JH7110_SYSCLK_I2STX1_BCLK, "i2stx1_bclk", 2, ++ JH71X0__MUX(JH7110_SYSCLK_I2STX1_BCLK, "i2stx1_bclk", 0, 2, + JH7110_SYSCLK_I2STX1_BCLK_MST, + JH7110_SYSCLK_I2STX_BCLK_EXT), + JH71X0__INV(JH7110_SYSCLK_I2STX1_BCLK_INV, "i2stx1_bclk_inv", JH7110_SYSCLK_I2STX1_BCLK), +- JH71X0__MUX(JH7110_SYSCLK_I2STX1_LRCK, "i2stx1_lrck", 2, ++ JH71X0__MUX(JH7110_SYSCLK_I2STX1_LRCK, "i2stx1_lrck", 0, 2, + JH7110_SYSCLK_I2STX1_LRCK_MST, + JH7110_SYSCLK_I2STX_LRCK_EXT), + /* i2srx */ +@@ -300,11 +300,11 @@ static const struct jh71x0_clk_data jh71 + JH71X0_MDIV(JH7110_SYSCLK_I2SRX_LRCK_MST, "i2srx_lrck_mst", 64, 2, + JH7110_SYSCLK_I2SRX_BCLK_MST_INV, + JH7110_SYSCLK_I2SRX_BCLK_MST), +- JH71X0__MUX(JH7110_SYSCLK_I2SRX_BCLK, "i2srx_bclk", 2, ++ JH71X0__MUX(JH7110_SYSCLK_I2SRX_BCLK, "i2srx_bclk", 0, 2, + JH7110_SYSCLK_I2SRX_BCLK_MST, + JH7110_SYSCLK_I2SRX_BCLK_EXT), + JH71X0__INV(JH7110_SYSCLK_I2SRX_BCLK_INV, "i2srx_bclk_inv", JH7110_SYSCLK_I2SRX_BCLK), +- JH71X0__MUX(JH7110_SYSCLK_I2SRX_LRCK, "i2srx_lrck", 2, ++ JH71X0__MUX(JH7110_SYSCLK_I2SRX_LRCK, "i2srx_lrck", 0, 2, + JH7110_SYSCLK_I2SRX_LRCK_MST, + JH7110_SYSCLK_I2SRX_LRCK_EXT), + /* pdm */ +@@ -314,7 +314,7 @@ static const struct jh71x0_clk_data jh71 + JH71X0_GATE(JH7110_SYSCLK_TDM_AHB, "tdm_ahb", 0, JH7110_SYSCLK_AHB0), + JH71X0_GATE(JH7110_SYSCLK_TDM_APB, "tdm_apb", 0, JH7110_SYSCLK_APB0), + JH71X0_GDIV(JH7110_SYSCLK_TDM_INTERNAL, "tdm_internal", 0, 64, JH7110_SYSCLK_MCLK), +- JH71X0__MUX(JH7110_SYSCLK_TDM_TDM, "tdm_tdm", 2, ++ JH71X0__MUX(JH7110_SYSCLK_TDM_TDM, "tdm_tdm", 0, 2, + JH7110_SYSCLK_TDM_INTERNAL, + JH7110_SYSCLK_TDM_EXT), + JH71X0__INV(JH7110_SYSCLK_TDM_TDM_INV, "tdm_tdm_inv", JH7110_SYSCLK_TDM_TDM), +--- a/drivers/clk/starfive/clk-starfive-jh71x0.h ++++ b/drivers/clk/starfive/clk-starfive-jh71x0.h +@@ -61,10 +61,10 @@ struct jh71x0_clk_data { + .parents = { [0] = _parent }, \ + } + +-#define JH71X0__MUX(_idx, _name, _nparents, ...) \ ++#define JH71X0__MUX(_idx, _name, _flags, _nparents, ...) \ + [_idx] = { \ + .name = _name, \ +- .flags = 0, \ ++ .flags = _flags, \ + .max = ((_nparents) - 1) << JH71X0_CLK_MUX_SHIFT, \ + .parents = { __VA_ARGS__ }, \ + } diff --git a/target/linux/starfive/patches-6.6/1007-clk-starfive-jh7100-Add-CLK_SET_RATE_PARENT-to-gmac_.patch b/target/linux/starfive/patches-6.6/1007-clk-starfive-jh7100-Add-CLK_SET_RATE_PARENT-to-gmac_.patch new file mode 100644 index 0000000000..74e695c03c --- /dev/null +++ b/target/linux/starfive/patches-6.6/1007-clk-starfive-jh7100-Add-CLK_SET_RATE_PARENT-to-gmac_.patch @@ -0,0 +1,25 @@ +From 7dfed4b67bd6ba8c33caf90c01821b67cf3260dd Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Date: Sat, 25 Mar 2023 23:04:31 +0100 +Subject: [PATCH 1007/1024] clk: starfive: jh7100: Add CLK_SET_RATE_PARENT to + gmac_tx + +This is needed by the dwmac-starfive ethernet driver to set the clock +for 1000, 100 and 10 Mbps links properly. + +Signed-off-by: Emil Renner Berthing <emil.renner.berthing@canonical.com> +--- + drivers/clk/starfive/clk-starfive-jh7100.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/clk/starfive/clk-starfive-jh7100.c ++++ b/drivers/clk/starfive/clk-starfive-jh7100.c +@@ -200,7 +200,7 @@ static const struct jh71x0_clk_data jh71 + JH71X0_GDIV(JH7100_CLK_GMAC_GTX, "gmac_gtxclk", 0, 255, JH7100_CLK_GMAC_ROOT_DIV), + JH71X0_GDIV(JH7100_CLK_GMAC_RMII_TX, "gmac_rmii_txclk", 0, 8, JH7100_CLK_GMAC_RMII_REF), + JH71X0_GDIV(JH7100_CLK_GMAC_RMII_RX, "gmac_rmii_rxclk", 0, 8, JH7100_CLK_GMAC_RMII_REF), +- JH71X0__MUX(JH7100_CLK_GMAC_TX, "gmac_tx", 0, 3, ++ JH71X0__MUX(JH7100_CLK_GMAC_TX, "gmac_tx", CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT, 3, + JH7100_CLK_GMAC_GTX, + JH7100_CLK_GMAC_TX_INV, + JH7100_CLK_GMAC_RMII_TX), diff --git a/target/linux/starfive/patches-6.6/1008-clk-starfive-jh7100-Keep-more-clocks-alive.patch b/target/linux/starfive/patches-6.6/1008-clk-starfive-jh7100-Keep-more-clocks-alive.patch new file mode 100644 index 0000000000..27346b4df7 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1008-clk-starfive-jh7100-Keep-more-clocks-alive.patch @@ -0,0 +1,94 @@ +From f8d08ec17674e56c7c689f5ca43c4dd3a4b76d13 Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <kernel@esmil.dk> +Date: Thu, 14 Oct 2021 20:35:43 +0200 +Subject: [PATCH 1008/1024] clk: starfive: jh7100: Keep more clocks alive + +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +--- + drivers/clk/starfive/clk-starfive-jh7100.c | 37 +++++++++++----------- + 1 file changed, 19 insertions(+), 18 deletions(-) + +--- a/drivers/clk/starfive/clk-starfive-jh7100.c ++++ b/drivers/clk/starfive/clk-starfive-jh7100.c +@@ -94,9 +94,9 @@ static const struct jh71x0_clk_data jh71 + JH71X0_GATE(JH7100_CLK_DMA2PNOC_AXI, "dma2pnoc_axi", 0, JH7100_CLK_CPU_AXI), + JH71X0_GATE(JH7100_CLK_SGDMA2P_AHB, "sgdma2p_ahb", 0, JH7100_CLK_AHB_BUS), + JH71X0__DIV(JH7100_CLK_DLA_BUS, "dla_bus", 4, JH7100_CLK_DLA_ROOT), +- JH71X0_GATE(JH7100_CLK_DLA_AXI, "dla_axi", 0, JH7100_CLK_DLA_BUS), +- JH71X0_GATE(JH7100_CLK_DLANOC_AXI, "dlanoc_axi", 0, JH7100_CLK_DLA_BUS), +- JH71X0_GATE(JH7100_CLK_DLA_APB, "dla_apb", 0, JH7100_CLK_APB1_BUS), ++ JH71X0_GATE(JH7100_CLK_DLA_AXI, "dla_axi", CLK_IGNORE_UNUSED, JH7100_CLK_DLA_BUS), ++ JH71X0_GATE(JH7100_CLK_DLANOC_AXI, "dlanoc_axi", CLK_IGNORE_UNUSED, JH7100_CLK_DLA_BUS), ++ JH71X0_GATE(JH7100_CLK_DLA_APB, "dla_apb", CLK_IGNORE_UNUSED, JH7100_CLK_APB1_BUS), + JH71X0_GDIV(JH7100_CLK_VP6_CORE, "vp6_core", 0, 4, JH7100_CLK_DSP_ROOT_DIV), + JH71X0__DIV(JH7100_CLK_VP6BUS_SRC, "vp6bus_src", 4, JH7100_CLK_DSP_ROOT), + JH71X0_GDIV(JH7100_CLK_VP6_AXI, "vp6_axi", 0, 4, JH7100_CLK_VP6BUS_SRC), +@@ -160,11 +160,12 @@ static const struct jh71x0_clk_data jh71 + JH71X0_GATE(JH7100_CLK_DMA1P_AXI, "dma1p_axi", 0, JH7100_CLK_SGDMA1P_BUS), + JH71X0_GDIV(JH7100_CLK_X2C_AXI, "x2c_axi", CLK_IS_CRITICAL, 8, JH7100_CLK_CPUNBUS_ROOT_DIV), + JH71X0__DIV(JH7100_CLK_USB_BUS, "usb_bus", 8, JH7100_CLK_CPUNBUS_ROOT_DIV), +- JH71X0_GATE(JH7100_CLK_USB_AXI, "usb_axi", 0, JH7100_CLK_USB_BUS), +- JH71X0_GATE(JH7100_CLK_USBNOC_AXI, "usbnoc_axi", 0, JH7100_CLK_USB_BUS), ++ JH71X0_GATE(JH7100_CLK_USB_AXI, "usb_axi", CLK_IGNORE_UNUSED, JH7100_CLK_USB_BUS), ++ JH71X0_GATE(JH7100_CLK_USBNOC_AXI, "usbnoc_axi", CLK_IGNORE_UNUSED, JH7100_CLK_USB_BUS), + JH71X0__DIV(JH7100_CLK_USBPHY_ROOTDIV, "usbphy_rootdiv", 4, JH7100_CLK_GMACUSB_ROOT), +- JH71X0_GDIV(JH7100_CLK_USBPHY_125M, "usbphy_125m", 0, 8, JH7100_CLK_USBPHY_ROOTDIV), +- JH71X0_GDIV(JH7100_CLK_USBPHY_PLLDIV25M, "usbphy_plldiv25m", 0, 32, ++ JH71X0_GDIV(JH7100_CLK_USBPHY_125M, "usbphy_125m", CLK_IGNORE_UNUSED, 8, ++ JH7100_CLK_USBPHY_ROOTDIV), ++ JH71X0_GDIV(JH7100_CLK_USBPHY_PLLDIV25M, "usbphy_plldiv25m", CLK_IGNORE_UNUSED, 32, + JH7100_CLK_USBPHY_ROOTDIV), + JH71X0__MUX(JH7100_CLK_USBPHY_25M, "usbphy_25m", 0, 2, + JH7100_CLK_OSC_SYS, +@@ -183,23 +184,23 @@ static const struct jh71x0_clk_data jh71 + JH71X0__DIV(JH7100_CLK_VIN_BUS, "vin_bus", 8, JH7100_CLK_VIN_SRC), + JH71X0_GATE(JH7100_CLK_VIN_AXI, "vin_axi", 0, JH7100_CLK_VIN_BUS), + JH71X0_GATE(JH7100_CLK_VINNOC_AXI, "vinnoc_axi", 0, JH7100_CLK_VIN_BUS), +- JH71X0_GDIV(JH7100_CLK_VOUT_SRC, "vout_src", 0, 4, JH7100_CLK_VOUT_ROOT), ++ JH71X0_GDIV(JH7100_CLK_VOUT_SRC, "vout_src", CLK_IGNORE_UNUSED, 4, JH7100_CLK_VOUT_ROOT), + JH71X0__DIV(JH7100_CLK_DISPBUS_SRC, "dispbus_src", 4, JH7100_CLK_VOUTBUS_ROOT), + JH71X0__DIV(JH7100_CLK_DISP_BUS, "disp_bus", 4, JH7100_CLK_DISPBUS_SRC), +- JH71X0_GATE(JH7100_CLK_DISP_AXI, "disp_axi", 0, JH7100_CLK_DISP_BUS), +- JH71X0_GATE(JH7100_CLK_DISPNOC_AXI, "dispnoc_axi", 0, JH7100_CLK_DISP_BUS), ++ JH71X0_GATE(JH7100_CLK_DISP_AXI, "disp_axi", CLK_IGNORE_UNUSED, JH7100_CLK_DISP_BUS), ++ JH71X0_GATE(JH7100_CLK_DISPNOC_AXI, "dispnoc_axi", CLK_IGNORE_UNUSED, JH7100_CLK_DISP_BUS), + JH71X0_GATE(JH7100_CLK_SDIO0_AHB, "sdio0_ahb", 0, JH7100_CLK_AHB_BUS), + JH71X0_GDIV(JH7100_CLK_SDIO0_CCLKINT, "sdio0_cclkint", 0, 24, JH7100_CLK_PERH0_SRC), + JH71X0__INV(JH7100_CLK_SDIO0_CCLKINT_INV, "sdio0_cclkint_inv", JH7100_CLK_SDIO0_CCLKINT), + JH71X0_GATE(JH7100_CLK_SDIO1_AHB, "sdio1_ahb", 0, JH7100_CLK_AHB_BUS), + JH71X0_GDIV(JH7100_CLK_SDIO1_CCLKINT, "sdio1_cclkint", 0, 24, JH7100_CLK_PERH1_SRC), + JH71X0__INV(JH7100_CLK_SDIO1_CCLKINT_INV, "sdio1_cclkint_inv", JH7100_CLK_SDIO1_CCLKINT), +- JH71X0_GATE(JH7100_CLK_GMAC_AHB, "gmac_ahb", 0, JH7100_CLK_AHB_BUS), ++ JH71X0_GATE(JH7100_CLK_GMAC_AHB, "gmac_ahb", CLK_IGNORE_UNUSED, JH7100_CLK_AHB_BUS), + JH71X0__DIV(JH7100_CLK_GMAC_ROOT_DIV, "gmac_root_div", 8, JH7100_CLK_GMACUSB_ROOT), +- JH71X0_GDIV(JH7100_CLK_GMAC_PTP_REF, "gmac_ptp_refclk", 0, 31, JH7100_CLK_GMAC_ROOT_DIV), +- JH71X0_GDIV(JH7100_CLK_GMAC_GTX, "gmac_gtxclk", 0, 255, JH7100_CLK_GMAC_ROOT_DIV), +- JH71X0_GDIV(JH7100_CLK_GMAC_RMII_TX, "gmac_rmii_txclk", 0, 8, JH7100_CLK_GMAC_RMII_REF), +- JH71X0_GDIV(JH7100_CLK_GMAC_RMII_RX, "gmac_rmii_rxclk", 0, 8, JH7100_CLK_GMAC_RMII_REF), ++ JH71X0_GDIV(JH7100_CLK_GMAC_PTP_REF, "gmac_ptp_refclk", CLK_IGNORE_UNUSED, 31, JH7100_CLK_GMAC_ROOT_DIV), ++ JH71X0_GDIV(JH7100_CLK_GMAC_GTX, "gmac_gtxclk", CLK_IGNORE_UNUSED, 255, JH7100_CLK_GMAC_ROOT_DIV), ++ JH71X0_GDIV(JH7100_CLK_GMAC_RMII_TX, "gmac_rmii_txclk", CLK_IGNORE_UNUSED, 8, JH7100_CLK_GMAC_RMII_REF), ++ JH71X0_GDIV(JH7100_CLK_GMAC_RMII_RX, "gmac_rmii_rxclk", CLK_IGNORE_UNUSED, 8, JH7100_CLK_GMAC_RMII_REF), + JH71X0__MUX(JH7100_CLK_GMAC_TX, "gmac_tx", CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT, 3, + JH7100_CLK_GMAC_GTX, + JH7100_CLK_GMAC_TX_INV, +@@ -209,8 +210,8 @@ static const struct jh71x0_clk_data jh71 + JH7100_CLK_GMAC_GR_MII_RX, + JH7100_CLK_GMAC_RMII_RX), + JH71X0__INV(JH7100_CLK_GMAC_RX_INV, "gmac_rx_inv", JH7100_CLK_GMAC_RX_PRE), +- JH71X0_GATE(JH7100_CLK_GMAC_RMII, "gmac_rmii", 0, JH7100_CLK_GMAC_RMII_REF), +- JH71X0_GDIV(JH7100_CLK_GMAC_TOPHYREF, "gmac_tophyref", 0, 127, JH7100_CLK_GMAC_ROOT_DIV), ++ JH71X0_GATE(JH7100_CLK_GMAC_RMII, "gmac_rmii", CLK_IGNORE_UNUSED, JH7100_CLK_GMAC_RMII_REF), ++ JH71X0_GDIV(JH7100_CLK_GMAC_TOPHYREF, "gmac_tophyref", CLK_IGNORE_UNUSED, 127, JH7100_CLK_GMAC_ROOT_DIV), + JH71X0_GATE(JH7100_CLK_SPI2AHB_AHB, "spi2ahb_ahb", 0, JH7100_CLK_AHB_BUS), + JH71X0_GDIV(JH7100_CLK_SPI2AHB_CORE, "spi2ahb_core", 0, 31, JH7100_CLK_PERH0_SRC), + JH71X0_GATE(JH7100_CLK_EZMASTER_AHB, "ezmaster_ahb", 0, JH7100_CLK_AHB_BUS), +@@ -223,7 +224,7 @@ static const struct jh71x0_clk_data jh71 + JH71X0_GATE(JH7100_CLK_AES, "aes_clk", 0, JH7100_CLK_SEC_AHB), + JH71X0_GATE(JH7100_CLK_SHA, "sha_clk", 0, JH7100_CLK_SEC_AHB), + JH71X0_GATE(JH7100_CLK_PKA, "pka_clk", 0, JH7100_CLK_SEC_AHB), +- JH71X0_GATE(JH7100_CLK_TRNG_APB, "trng_apb", 0, JH7100_CLK_APB1_BUS), ++ JH71X0_GATE(JH7100_CLK_TRNG_APB, "trng_apb", CLK_IGNORE_UNUSED, JH7100_CLK_APB1_BUS), + JH71X0_GATE(JH7100_CLK_OTP_APB, "otp_apb", 0, JH7100_CLK_APB1_BUS), + JH71X0_GATE(JH7100_CLK_UART0_APB, "uart0_apb", 0, JH7100_CLK_APB1_BUS), + JH71X0_GDIV(JH7100_CLK_UART0_CORE, "uart0_core", 0, 63, JH7100_CLK_PERH1_SRC), diff --git a/target/linux/starfive/patches-6.6/1009-net-stmmac-use-GFP_DMA32.patch b/target/linux/starfive/patches-6.6/1009-net-stmmac-use-GFP_DMA32.patch new file mode 100644 index 0000000000..ad8b92f715 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1009-net-stmmac-use-GFP_DMA32.patch @@ -0,0 +1,30 @@ +From 980f1e9ef19d472e72c36e142db7fb4e224f0f3e Mon Sep 17 00:00:00 2001 +From: Matteo Croce <technoboy85@gmail.com> +Date: Fri, 21 May 2021 03:26:38 +0200 +Subject: [PATCH 1009/1024] net: stmmac: use GFP_DMA32 + +Signed-off-by: Matteo Croce <mcroce@microsoft.com> +--- + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +@@ -1434,7 +1434,7 @@ static int stmmac_init_rx_buffers(struct + { + struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue]; + struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i]; +- gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN); ++ gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN | GFP_DMA32); + + if (priv->dma_cap.host_dma_width <= 32) + gfp |= GFP_DMA32; +@@ -4673,7 +4673,7 @@ static inline void stmmac_rx_refill(stru + struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue]; + int dirty = stmmac_rx_dirty(priv, queue); + unsigned int entry = rx_q->dirty_rx; +- gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN); ++ gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN | GFP_DMA32); + + if (priv->dma_cap.host_dma_width <= 32) + gfp |= GFP_DMA32; diff --git a/target/linux/starfive/patches-6.6/1010-hwrng-Add-StarFive-JH7100-Random-Number-Generator-dr.patch b/target/linux/starfive/patches-6.6/1010-hwrng-Add-StarFive-JH7100-Random-Number-Generator-dr.patch new file mode 100644 index 0000000000..b625014dd9 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1010-hwrng-Add-StarFive-JH7100-Random-Number-Generator-dr.patch @@ -0,0 +1,477 @@ +From 4989e7aa5ed5ef9bc2532b3a47ff381572f389b5 Mon Sep 17 00:00:00 2001 +From: Huan Feng <huan.feng@starfivetech.com> +Date: Fri, 8 Jan 2021 03:35:42 +0800 +Subject: [PATCH 1010/1024] hwrng: Add StarFive JH7100 Random Number Generator + driver + +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +--- + drivers/char/hw_random/Kconfig | 13 ++ + drivers/char/hw_random/Makefile | 1 + + drivers/char/hw_random/starfive-vic-rng.c | 256 ++++++++++++++++++++++ + drivers/char/hw_random/starfive-vic-rng.h | 167 ++++++++++++++ + 4 files changed, 437 insertions(+) + create mode 100644 drivers/char/hw_random/starfive-vic-rng.c + create mode 100644 drivers/char/hw_random/starfive-vic-rng.h + +--- a/drivers/char/hw_random/Kconfig ++++ b/drivers/char/hw_random/Kconfig +@@ -322,6 +322,19 @@ config HW_RANDOM_POWERNV + + If unsure, say Y. + ++config HW_RANDOM_STARFIVE_VIC ++ tristate "Starfive VIC Random Number Generator support" ++ depends on HW_RANDOM && (SOC_STARFIVE || COMPILE_TEST) ++ default SOC_STARFIVE ++ help ++ This driver provides kernel-side support for the Random Number ++ Generator hardware found on Starfive VIC SoC. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called starfive-vic-rng. ++ ++ If unsure, say Y. ++ + config HW_RANDOM_HISI + tristate "Hisilicon Random Number Generator support" + depends on ARCH_HISI || COMPILE_TEST +--- a/drivers/char/hw_random/Makefile ++++ b/drivers/char/hw_random/Makefile +@@ -28,6 +28,7 @@ obj-$(CONFIG_HW_RANDOM_OCTEON) += octeon + obj-$(CONFIG_HW_RANDOM_NOMADIK) += nomadik-rng.o + obj-$(CONFIG_HW_RANDOM_PSERIES) += pseries-rng.o + obj-$(CONFIG_HW_RANDOM_POWERNV) += powernv-rng.o ++obj-$(CONFIG_HW_RANDOM_STARFIVE_VIC) += starfive-vic-rng.o + obj-$(CONFIG_HW_RANDOM_HISI) += hisi-rng.o + obj-$(CONFIG_HW_RANDOM_HISTB) += histb-rng.o + obj-$(CONFIG_HW_RANDOM_BCM2835) += bcm2835-rng.o +--- /dev/null ++++ b/drivers/char/hw_random/starfive-vic-rng.c +@@ -0,0 +1,256 @@ ++/* ++ ****************************************************************************** ++ * @file starfive-vic-rng.c ++ * @author StarFive Technology ++ * @version V1.0 ++ * @date 08/13/2020 ++ * @brief ++ ****************************************************************************** ++ * @copy ++ * ++ * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS ++ * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE ++ * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY ++ * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING ++ * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE ++ * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. ++ * ++ * COPYRIGHT 2020 Shanghai StarFive Technology Co., Ltd. ++ */ ++#include <linux/err.h> ++#include <linux/kernel.h> ++#include <linux/hw_random.h> ++#include <linux/io.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/platform_device.h> ++#include <linux/interrupt.h> ++#include <linux/random.h> ++ ++#include "starfive-vic-rng.h" ++ ++#define to_vic_rng(p) container_of(p, struct vic_rng, rng) ++ ++struct vic_rng { ++ struct device *dev; ++ void __iomem *base; ++ struct hwrng rng; ++}; ++ ++static inline void vic_wait_till_idle(struct vic_rng *hrng) ++{ ++ while(readl(hrng->base + VIC_STAT) & VIC_STAT_BUSY) ++ ; ++} ++ ++static inline void vic_rng_irq_mask_clear(struct vic_rng *hrng) ++{ ++ // clear register: ISTAT ++ u32 data = readl(hrng->base + VIC_ISTAT); ++ writel(data, hrng->base + VIC_ISTAT); ++ writel(0, hrng->base + VIC_ALARM); ++} ++ ++static int vic_trng_cmd(struct vic_rng *hrng, u32 cmd) { ++ int res = 0; ++ // wait till idle ++ vic_wait_till_idle(hrng); ++ switch (cmd) { ++ case VIC_CTRL_CMD_NOP: ++ case VIC_CTRL_CMD_GEN_NOISE: ++ case VIC_CTRL_CMD_GEN_NONCE: ++ case VIC_CTRL_CMD_CREATE_STATE: ++ case VIC_CTRL_CMD_RENEW_STATE: ++ case VIC_CTRL_CMD_REFRESH_ADDIN: ++ case VIC_CTRL_CMD_GEN_RANDOM: ++ case VIC_CTRL_CMD_ADVANCE_STATE: ++ case VIC_CTRL_CMD_KAT: ++ case VIC_CTRL_CMD_ZEROIZE: ++ writel(cmd, hrng->base + VIC_CTRL); ++ break; ++ default: ++ res = -1; ++ break; ++ } ++ ++ return res; ++} ++ ++static int vic_rng_init(struct hwrng *rng) ++{ ++ struct vic_rng *hrng = to_vic_rng(rng); ++ ++ // wait till idle ++ ++ // clear register: ISTAT ++ vic_rng_irq_mask_clear(hrng); ++ ++ // set mission mode ++ writel(VIC_SMODE_SECURE_EN(1), hrng->base + VIC_SMODE); ++ ++ vic_trng_cmd(hrng, VIC_CTRL_CMD_GEN_NOISE); ++ vic_wait_till_idle(hrng); ++ ++ // set interrupt ++ writel(VIC_IE_ALL, hrng->base + VIC_IE); ++ ++ // zeroize ++ vic_trng_cmd(hrng, VIC_CTRL_CMD_ZEROIZE); ++ ++ vic_wait_till_idle(hrng); ++ ++ return 0; ++} ++ ++static irqreturn_t vic_rng_irq(int irq, void *priv) ++{ ++ u32 status, val; ++ struct vic_rng *hrng = (struct vic_rng *)priv; ++ ++ /* ++ * clearing the interrupt will also clear the error register ++ * read error and status before clearing ++ */ ++ status = readl(hrng->base + VIC_ISTAT); ++ ++ if (status & VIC_ISTAT_ALARMS) { ++ writel(VIC_ISTAT_ALARMS, hrng->base + VIC_ISTAT); ++ val = readl(hrng->base + VIC_ALARM); ++ if (val & VIC_ALARM_ILLEGAL_CMD_SEQ) { ++ writel(VIC_ALARM_ILLEGAL_CMD_SEQ, hrng->base + VIC_ALARM); ++ //dev_info(hrng->dev, "ILLEGAL CMD SEQ: LAST_CMD=0x%x\r\n", ++ //VIC_STAT_LAST_CMD(readl(hrng->base + VIC_STAT))); ++ } else { ++ dev_info(hrng->dev, "Failed test: %x\r\n", val); ++ } ++ } ++ ++ if (status & VIC_ISTAT_ZEROIZE) { ++ writel(VIC_ISTAT_ZEROIZE, hrng->base + VIC_ISTAT); ++ //dev_info(hrng->dev, "zeroized\r\n"); ++ } ++ ++ if (status & VIC_ISTAT_KAT_COMPLETE) { ++ writel(VIC_ISTAT_KAT_COMPLETE, hrng->base + VIC_ISTAT); ++ //dev_info(hrng->dev, "kat_completed\r\n"); ++ } ++ ++ if (status & VIC_ISTAT_NOISE_RDY) { ++ writel(VIC_ISTAT_NOISE_RDY, hrng->base + VIC_ISTAT); ++ //dev_info(hrng->dev, "noise_rdy\r\n"); ++ } ++ ++ if (status & VIC_ISTAT_DONE) { ++ writel(VIC_ISTAT_DONE, hrng->base + VIC_ISTAT); ++ //dev_info(hrng->dev, "done\r\n"); ++ /* ++ if (VIC_STAT_LAST_CMD(readl(hrng->base + VIC_STAT)) == ++ VIC_CTRL_CMD_GEN_RANDOM) { ++ dev_info(hrng->dev, "Need Update Buffer\r\n"); ++ } ++ */ ++ } ++ vic_rng_irq_mask_clear(hrng); ++ ++ return IRQ_HANDLED; ++} ++ ++static void vic_rng_cleanup(struct hwrng *rng) ++{ ++ struct vic_rng *hrng = to_vic_rng(rng); ++ ++ writel(0, hrng->base + VIC_CTRL); ++} ++ ++static int vic_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) ++{ ++ struct vic_rng *hrng = to_vic_rng(rng); ++ ++ vic_trng_cmd(hrng, VIC_CTRL_CMD_ZEROIZE); ++ vic_trng_cmd(hrng, VIC_CTRL_CMD_GEN_NOISE); ++ vic_trng_cmd(hrng, VIC_CTRL_CMD_CREATE_STATE); ++ ++ vic_wait_till_idle(hrng); ++ max = min_t(size_t, max, (VIC_RAND_LEN * 4)); ++ ++ writel(0x0, hrng->base + VIC_MODE); ++ vic_trng_cmd(hrng, VIC_CTRL_CMD_GEN_RANDOM); ++ ++ vic_wait_till_idle(hrng); ++ memcpy_fromio(buf, hrng->base + VIC_RAND0, max); ++ vic_trng_cmd(hrng, VIC_CTRL_CMD_ZEROIZE); ++ ++ vic_wait_till_idle(hrng); ++ return max; ++} ++ ++static int vic_rng_probe(struct platform_device *pdev) ++{ ++ int ret; ++ int irq; ++ struct vic_rng *rng; ++ struct resource *res; ++ ++ rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); ++ if (!rng){ ++ return -ENOMEM; ++ } ++ ++ platform_set_drvdata(pdev, rng); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ rng->base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(rng->base)){ ++ return PTR_ERR(rng->base); ++ } ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq <= 0) { ++ dev_err(&pdev->dev, "Couldn't get irq %d\n", irq); ++ return irq; ++ } ++ ++ ret = devm_request_irq(&pdev->dev, irq, vic_rng_irq, 0, pdev->name, ++ (void *)rng); ++ if (ret) { ++ dev_err(&pdev->dev, "Can't get interrupt working.\n"); ++ return ret; ++ } ++ ++ rng->rng.name = pdev->name; ++ rng->rng.init = vic_rng_init; ++ rng->rng.cleanup = vic_rng_cleanup; ++ rng->rng.read = vic_rng_read; ++ ++ rng->dev = &pdev->dev; ++ ++ ret = devm_hwrng_register(&pdev->dev, &rng->rng); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to register hwrng\n"); ++ return ret; ++ } ++ ++ dev_info(&pdev->dev, "Initialized\n"); ++ ++ return 0; ++} ++ ++static const struct of_device_id vic_rng_dt_ids[] = { ++ { .compatible = "starfive,vic-rng" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, vic_rng_dt_ids); ++ ++static struct platform_driver vic_rng_driver = { ++ .probe = vic_rng_probe, ++ .driver = { ++ .name = "vic-rng", ++ .of_match_table = vic_rng_dt_ids, ++ }, ++}; ++ ++module_platform_driver(vic_rng_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Huan Feng <huan.feng@starfivetech.com>"); ++MODULE_DESCRIPTION("Starfive VIC random number generator driver"); +--- /dev/null ++++ b/drivers/char/hw_random/starfive-vic-rng.h +@@ -0,0 +1,167 @@ ++/* ++ ****************************************************************************** ++ * @file starfive-vic-rng.h ++ * @author StarFive Technology ++ * @version V1.0 ++ * @date 08/13/2020 ++ * @brief ++ ****************************************************************************** ++ * @copy ++ * ++ * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS ++ * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE ++ * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY ++ * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING ++ * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE ++ * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. ++ * ++ * COPYRIGHT 2020 Shanghai StarFive Technology Co., Ltd. ++ */ ++ ++#define VIC_CTRL 0x00 ++#define VIC_MODE 0x04 ++#define VIC_SMODE 0x08 ++#define VIC_STAT 0x0C ++#define VIC_IE 0x10 ++#define VIC_ISTAT 0x14 ++#define VIC_ALARM 0x18 ++#define VIC_BUILD_ID 0x1C ++#define VIC_FEATURES 0x20 ++#define VIC_RAND0 0x24 ++#define VIC_NPA_DATA0 0x34 ++#define VIC_SEED0 0x74 ++#define VIC_IA_RDATA 0xA4 ++#define VIC_IA_WDATA 0xA8 ++#define VIC_IA_ADDR 0xAC ++#define VIC_IA_CMD 0xB0 ++ ++/* CTRL */ ++#define VIC_CTRL_CMD_NOP 0 ++#define VIC_CTRL_CMD_GEN_NOISE 1 ++#define VIC_CTRL_CMD_GEN_NONCE 2 ++#define VIC_CTRL_CMD_CREATE_STATE 3 ++#define VIC_CTRL_CMD_RENEW_STATE 4 ++#define VIC_CTRL_CMD_REFRESH_ADDIN 5 ++#define VIC_CTRL_CMD_GEN_RANDOM 6 ++#define VIC_CTRL_CMD_ADVANCE_STATE 7 ++#define VIC_CTRL_CMD_KAT 8 ++#define VIC_CTRL_CMD_ZEROIZE 15 ++ ++/* MODE */ ++#define _VIC_MODE_ADDIN_PRESENT 4 ++#define _VIC_MODE_PRED_RESIST 3 ++#define _VIC_MODE_KAT_SEL 2 ++#define _VIC_MODE_KAT_VEC 1 ++#define _VIC_MODE_SEC_ALG 0 ++ ++#define VIC_MODE_ADDIN_PRESENT (1UL << _VIC_MODE_ADDIN_PRESENT) ++#define VIC_MODE_PRED_RESIST (1UL << _VIC_MODE_PRED_RESIST) ++#define VIC_MODE_KAT_SEL (1UL << _VIC_MODE_KAT_SEL) ++#define VIC_MODE_KAT_VEC (1UL << _VIC_MODE_KAT_VEC) ++#define VIC_MODE_SEC_ALG (1UL << _VIC_MODE_SEC_ALG) ++ ++/* SMODE */ ++#define _VIC_SMODE_MAX_REJECTS 2 ++#define _VIC_SMODE_SECURE_EN 1 ++#define _VIC_SMODE_NONCE 0 ++ ++#define VIC_SMODE_MAX_REJECTS(x) ((x) << _VIC_SMODE_MAX_REJECTS) ++#define VIC_SMODE_SECURE_EN(x) ((x) << _VIC_SMODE_SECURE_EN) ++#define VIC_SMODE_NONCE (1UL << _VIC_SMODE_NONCE) ++ ++/* STAT */ ++#define _VIC_STAT_BUSY 31 ++#define _VIC_STAT_DRBG_STATE 7 ++#define _VIC_STAT_SECURE 6 ++#define _VIC_STAT_NONCE_MODE 5 ++#define _VIC_STAT_SEC_ALG 4 ++#define _VIC_STAT_LAST_CMD 0 ++ ++#define VIC_STAT_BUSY (1UL << _VIC_STAT_BUSY) ++#define VIC_STAT_DRBG_STATE (1UL << _VIC_STAT_DRBG_STATE) ++#define VIC_STAT_SECURE (1UL << _VIC_STAT_SECURE) ++#define VIC_STAT_NONCE_MODE (1UL << _VIC_STAT_NONCE_MODE) ++#define VIC_STAT_SEC_ALG (1UL << _VIC_STAT_SEC_ALG) ++#define VIC_STAT_LAST_CMD(x) (((x) >> _VIC_STAT_LAST_CMD) & 0xF) ++ ++/* IE */ ++#define _VIC_IE_GLBL 31 ++#define _VIC_IE_DONE 4 ++#define _VIC_IE_ALARMS 3 ++#define _VIC_IE_NOISE_RDY 2 ++#define _VIC_IE_KAT_COMPLETE 1 ++#define _VIC_IE_ZEROIZE 0 ++ ++#define VIC_IE_GLBL (1UL << _VIC_IE_GLBL) ++#define VIC_IE_DONE (1UL << _VIC_IE_DONE) ++#define VIC_IE_ALARMS (1UL << _VIC_IE_ALARMS) ++#define VIC_IE_NOISE_RDY (1UL << _VIC_IE_NOISE_RDY) ++#define VIC_IE_KAT_COMPLETE (1UL << _VIC_IE_KAT_COMPLETE) ++#define VIC_IE_ZEROIZE (1UL << _VIC_IE_ZEROIZE) ++#define VIC_IE_ALL (VIC_IE_GLBL | VIC_IE_DONE | VIC_IE_ALARMS | \ ++ VIC_IE_NOISE_RDY | VIC_IE_KAT_COMPLETE | VIC_IE_ZEROIZE) ++ ++/* ISTAT */ ++#define _VIC_ISTAT_DONE 4 ++#define _VIC_ISTAT_ALARMS 3 ++#define _VIC_ISTAT_NOISE_RDY 2 ++#define _VIC_ISTAT_KAT_COMPLETE 1 ++#define _VIC_ISTAT_ZEROIZE 0 ++ ++#define VIC_ISTAT_DONE (1UL << _VIC_ISTAT_DONE) ++#define VIC_ISTAT_ALARMS (1UL << _VIC_ISTAT_ALARMS) ++#define VIC_ISTAT_NOISE_RDY (1UL << _VIC_ISTAT_NOISE_RDY) ++#define VIC_ISTAT_KAT_COMPLETE (1UL << _VIC_ISTAT_KAT_COMPLETE) ++#define VIC_ISTAT_ZEROIZE (1UL << _VIC_ISTAT_ZEROIZE) ++ ++/* ALARMS */ ++#define VIC_ALARM_ILLEGAL_CMD_SEQ (1UL << 4) ++#define VIC_ALARM_FAILED_TEST_ID_OK 0 ++#define VIC_ALARM_FAILED_TEST_ID_KAT_STAT 1 ++#define VIC_ALARM_FAILED_TEST_ID_KAT 2 ++#define VIC_ALARM_FAILED_TEST_ID_MONOBIT 3 ++#define VIC_ALARM_FAILED_TEST_ID_RUN 4 ++#define VIC_ALARM_FAILED_TEST_ID_LONGRUN 5 ++#define VIC_ALARM_FAILED_TEST_ID_AUTOCORRELATION 6 ++#define VIC_ALARM_FAILED_TEST_ID_POKER 7 ++#define VIC_ALARM_FAILED_TEST_ID_REPETITION_COUNT 8 ++#define VIC_ALARM_FAILED_TEST_ID_ADAPATIVE_PROPORTION 9 ++ ++/* BUILD_ID */ ++#define VIC_BUILD_ID_STEPPING(x) (((x) >> 28) & 0xF) ++#define VIC_BUILD_ID_EPN(x) ((x) & 0xFFFF) ++ ++/* FEATURES */ ++#define VIC_FEATURES_AES_256(x) (((x) >> 9) & 1) ++#define VIC_FEATURES_EXTRA_PS_PRESENT(x) (((x) >> 8) & 1) ++#define VIC_FEATURES_DIAG_LEVEL_NS(x) (((x) >> 7) & 1) ++#define VIC_FEATURES_DIAG_LEVEL_CLP800(x) (((x) >> 4) & 7) ++#define VIC_FEATURES_DIAG_LEVEL_ST_HLT(x) (((x) >> 1) & 7) ++#define VIC_FEATURES_SECURE_RST_STATE(x) ((x) & 1) ++ ++/* IA_CMD */ ++#define VIC_IA_CMD_GO (1UL << 31) ++#define VIC_IA_CMD_WR (1) ++ ++#define _VIC_SMODE_MAX_REJECTS_MASK 255UL ++#define _VIC_SMODE_SECURE_EN_MASK 1UL ++#define _VIC_SMODE_NONCE_MASK 1UL ++#define _VIC_MODE_SEC_ALG_MASK 1UL ++#define _VIC_MODE_ADDIN_PRESENT_MASK 1UL ++#define _VIC_MODE_PRED_RESIST_MASK 1UL ++ ++#define VIC_SMODE_SET_MAX_REJECTS(y, x) (((y) & ~(_VIC_SMODE_MAX_REJECTS_MASK << _VIC_SMODE_MAX_REJECTS)) | ((x) << _VIC_SMODE_MAX_REJECTS)) ++#define VIC_SMODE_SET_SECURE_EN(y, x) (((y) & ~(_VIC_SMODE_SECURE_EN_MASK << _VIC_SMODE_SECURE_EN)) | ((x) << _VIC_SMODE_SECURE_EN)) ++#define VIC_SMODE_SET_NONCE(y, x) (((y) & ~(_VIC_SMODE_NONCE_MASK << _VIC_SMODE_NONCE)) | ((x) << _VIC_SMODE_NONCE)) ++#define VIC_SMODE_GET_MAX_REJECTS(x) (((x) >> _VIC_SMODE_MAX_REJECTS) & _VIC_SMODE_MAX_REJECTS_MASK) ++#define VIC_SMODE_GET_SECURE_EN(x) (((x) >> _VIC_SMODE_SECURE_EN) & _VIC_SMODE_SECURE_EN_MASK) ++#define VIC_SMODE_GET_NONCE(x) (((x) >> _VIC_SMODE_NONCE) & _VIC_SMODE_NONCE_MASK) ++ ++#define VIC_MODE_SET_SEC_ALG(y, x) (((y) & ~(_VIC_MODE_SEC_ALG_MASK << _VIC_MODE_SEC_ALG)) | ((x) << _VIC_MODE_SEC_ALG)) ++#define VIC_MODE_SET_PRED_RESIST(y, x) (((y) & ~(_VIC_MODE_PRED_RESIST_MASK << _VIC_MODE_PRED_RESIST)) | ((x) << _VIC_MODE_PRED_RESIST)) ++#define VIC_MODE_SET_ADDIN_PRESENT(y, x) (((y) & ~(_VIC_MODE_ADDIN_PRESENT_MASK << _VIC_MODE_ADDIN_PRESENT)) | ((x) << _VIC_MODE_ADDIN_PRESENT)) ++#define VIC_MODE_GET_SEC_ALG(x) (((x) >> _VIC_MODE_SEC_ALG) & _VIC_MODE_SEC_ALG_MASK) ++#define VIC_MODE_GET_PRED_RESIST(x) (((x) >> _VIC_MODE_PRED_RESIST) & _VIC_MODE_PRED_RESIST_MASK) ++#define VIC_MODE_GET_ADDIN_PRESENT(x) (((x) >> _VIC_MODE_ADDIN_PRESENT) & _VIC_MODE_ADDIN_PRESENT_MASK) ++ ++#define VIC_RAND_LEN 4 diff --git a/target/linux/starfive/patches-6.6/1011-pwm-sifive-ptc-Add-SiFive-PWM-PTC-driver.patch b/target/linux/starfive/patches-6.6/1011-pwm-sifive-ptc-Add-SiFive-PWM-PTC-driver.patch new file mode 100644 index 0000000000..7a588c27c4 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1011-pwm-sifive-ptc-Add-SiFive-PWM-PTC-driver.patch @@ -0,0 +1,309 @@ +From 34fd7b7f92deef97f03c80d056e2f51bc08b7dc6 Mon Sep 17 00:00:00 2001 +From: Chenjieqin <Jessica.Chen@starfivetech.com> +Date: Fri, 8 Jan 2021 03:56:54 +0800 +Subject: [PATCH 1011/1024] pwm: sifive-ptc: Add SiFive PWM PTC driver + +yiming.li: clear CNTR of PWM after setting period & duty_cycle +Emil: cleanups, clock, reset and div_u64 + +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +--- + drivers/pwm/Kconfig | 11 ++ + drivers/pwm/Makefile | 1 + + drivers/pwm/pwm-sifive-ptc.c | 260 +++++++++++++++++++++++++++++++++++ + 3 files changed, 272 insertions(+) + create mode 100644 drivers/pwm/pwm-sifive-ptc.c + +--- a/drivers/pwm/Kconfig ++++ b/drivers/pwm/Kconfig +@@ -549,6 +549,17 @@ config PWM_SIFIVE + To compile this driver as a module, choose M here: the module + will be called pwm-sifive. + ++config PWM_SIFIVE_PTC ++ tristate "SiFive PWM PTC support" ++ depends on SOC_SIFIVE || SOC_STARFIVE || COMPILE_TEST ++ depends on OF ++ depends on COMMON_CLK ++ help ++ Generic PWM framework driver for SiFive SoCs. ++ ++ To compile this driver as a module, choose M here: the module ++ will be called pwm-sifive-ptc. ++ + config PWM_SL28CPLD + tristate "Kontron sl28cpld PWM support" + depends on MFD_SL28CPLD || COMPILE_TEST +--- a/drivers/pwm/Makefile ++++ b/drivers/pwm/Makefile +@@ -50,6 +50,7 @@ obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockch + obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o + obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o + obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o ++obj-$(CONFIG_PWM_SIFIVE_PTC) += pwm-sifive-ptc.o + obj-$(CONFIG_PWM_SL28CPLD) += pwm-sl28cpld.o + obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o + obj-$(CONFIG_PWM_SPRD) += pwm-sprd.o +--- /dev/null ++++ b/drivers/pwm/pwm-sifive-ptc.c +@@ -0,0 +1,260 @@ ++/* ++ * Copyright (C) 2018 SiFive, Inc ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License version 2, as published by ++ * the Free Software Foundation. ++ */ ++ ++#include <linux/clk.h> ++#include <linux/io.h> ++#include <linux/math64.h> ++#include <linux/module.h> ++#include <linux/platform_device.h> ++#include <linux/pwm.h> ++#include <linux/reset.h> ++ ++#include <dt-bindings/pwm/pwm.h> ++ ++/* max channel of pwm */ ++#define MAX_PWM 8 ++ ++/* PTC Register offsets */ ++#define REG_RPTC_CNTR 0x0 ++#define REG_RPTC_HRC 0x4 ++#define REG_RPTC_LRC 0x8 ++#define REG_RPTC_CTRL 0xC ++ ++/* Bit for PWM clock */ ++#define BIT_PWM_CLOCK_EN 31 ++ ++/* Bit for clock gen soft reset */ ++#define BIT_CLK_GEN_SOFT_RESET 13 ++ ++#define NS_1 1000000000U ++ ++/* Access PTC register (cntr hrc lrc and ctrl), need to replace PWM_BASE_ADDR */ ++#define REG_PTC_BASE_ADDR_SUB(base, N) \ ++ ((base) + (((N) > 3) ? (((N) - 4) * 0x10 + (1 << 15)) : ((N) * 0x10))) ++#define REG_PTC_RPTC_CNTR(base, N) (REG_PTC_BASE_ADDR_SUB(base, N)) ++#define REG_PTC_RPTC_HRC(base, N) (REG_PTC_BASE_ADDR_SUB(base, N) + 0x4) ++#define REG_PTC_RPTC_LRC(base, N) (REG_PTC_BASE_ADDR_SUB(base, N) + 0x8) ++#define REG_PTC_RPTC_CTRL(base, N) (REG_PTC_BASE_ADDR_SUB(base, N) + 0xC) ++ ++/* pwm ptc device */ ++struct sifive_pwm_ptc_device { ++ struct pwm_chip chip; ++ struct clk *clk; ++ void __iomem *regs; ++}; ++ ++static inline struct sifive_pwm_ptc_device *chip_to_sifive_ptc(struct pwm_chip *c) ++{ ++ return container_of(c, struct sifive_pwm_ptc_device, chip); ++} ++ ++static int sifive_pwm_ptc_get_state(struct pwm_chip *chip, struct pwm_device *dev, ++ struct pwm_state *state) ++{ ++ struct sifive_pwm_ptc_device *pwm = chip_to_sifive_ptc(chip); ++ u32 data_lrc; ++ u32 data_hrc; ++ u32 pwm_clk_ns = 0; ++ ++ /* get lrc and hrc data from registe */ ++ data_lrc = ioread32(REG_PTC_RPTC_LRC(pwm->regs, dev->hwpwm)); ++ data_hrc = ioread32(REG_PTC_RPTC_HRC(pwm->regs, dev->hwpwm)); ++ ++ /* how many ns does apb clock elapse */ ++ pwm_clk_ns = NS_1 / clk_get_rate(pwm->clk); ++ ++ /* pwm period(ns) */ ++ state->period = data_lrc * pwm_clk_ns; ++ ++ /* duty cycle(ns) means high level eclapse ns if it is normal polarity */ ++ state->duty_cycle = data_hrc * pwm_clk_ns; ++ ++ /* polarity, we don't use it now because it is not in dts */ ++ state->polarity = PWM_POLARITY_NORMAL; ++ ++ /* enabled or not */ ++ state->enabled = 1; ++ ++ dev_dbg(pwm->chip.dev, "%s: no:%d\n", __func__, dev->hwpwm); ++ dev_dbg(pwm->chip.dev, "data_hrc:0x%x 0x%x\n", data_hrc, data_lrc); ++ dev_dbg(pwm->chip.dev, "period:%llu\n", state->period); ++ dev_dbg(pwm->chip.dev, "duty_cycle:%llu\n", state->duty_cycle); ++ dev_dbg(pwm->chip.dev, "polarity:%d\n", state->polarity); ++ dev_dbg(pwm->chip.dev, "enabled:%d\n", state->enabled); ++ ++ return 0; ++} ++ ++static int sifive_pwm_ptc_apply(struct pwm_chip *chip, struct pwm_device *dev, ++ const struct pwm_state *state) ++{ ++ struct sifive_pwm_ptc_device *pwm = chip_to_sifive_ptc(chip); ++ void __iomem *reg_addr; ++ u32 pwm_clk_ns = 0; ++ u32 data_hrc = 0; ++ u32 data_lrc = 0; ++ u32 period_data = 0; ++ u32 duty_data = 0; ++ ++ dev_dbg(pwm->chip.dev, "%s: no:%d\n", __func__, dev->hwpwm); ++ dev_dbg(pwm->chip.dev, "period:%llu\n", state->period); ++ dev_dbg(pwm->chip.dev, "duty_cycle:%llu\n", state->duty_cycle); ++ dev_dbg(pwm->chip.dev, "polarity:%d\n", state->polarity); ++ dev_dbg(pwm->chip.dev, "enabled:%d\n", state->enabled); ++ ++ /* duty_cycle should be less or equal than period */ ++ if (state->duty_cycle > state->period) ++ return -EINVAL; ++ ++ /* calculate pwm real period (ns) */ ++ pwm_clk_ns = NS_1 / clk_get_rate(pwm->clk); ++ ++ dev_dbg(pwm->chip.dev, "pwm_clk_ns:%u\n", pwm_clk_ns); ++ ++ /* calculate period count */ ++ period_data = div_u64(state->period, pwm_clk_ns); ++ ++ if (!state->enabled) ++ /* if disabled, just set duty_data to 0, which means low level always */ ++ duty_data = 0; ++ else ++ /* calculate duty count */ ++ duty_data = div_u64(state->duty_cycle, pwm_clk_ns); ++ ++ dev_dbg(pwm->chip.dev, "period_data:%u, duty_data:%u\n", ++ period_data, duty_data); ++ ++ if (state->polarity == PWM_POLARITY_NORMAL) ++ /* calculate data_hrc */ ++ data_hrc = period_data - duty_data; ++ else ++ /* calculate data_hrc */ ++ data_hrc = duty_data; ++ ++ data_lrc = period_data; ++ ++ /* set hrc */ ++ reg_addr = REG_PTC_RPTC_HRC(pwm->regs, dev->hwpwm); ++ dev_dbg(pwm->chip.dev, "%s: reg_addr:%p, data:%u\n", ++ __func__, reg_addr, data_hrc); ++ ++ iowrite32(data_hrc, reg_addr); ++ ++ dev_dbg(pwm->chip.dev, "%s: hrc ok\n", __func__); ++ ++ /* set lrc */ ++ reg_addr = REG_PTC_RPTC_LRC(pwm->regs, dev->hwpwm); ++ dev_dbg(pwm->chip.dev, "%s: reg_addr:%p, data:%u\n", ++ __func__, reg_addr, data_lrc); ++ ++ iowrite32(data_lrc, reg_addr); ++ dev_dbg(pwm->chip.dev, "%s: lrc ok\n", __func__); ++ ++ /* Clear REG_RPTC_CNTR after setting period & duty_cycle */ ++ reg_addr = REG_PTC_RPTC_CNTR(pwm->regs, dev->hwpwm); ++ iowrite32(0, reg_addr); ++ return 0; ++} ++ ++static const struct pwm_ops sifive_pwm_ptc_ops = { ++ .get_state = sifive_pwm_ptc_get_state, ++ .apply = sifive_pwm_ptc_apply, ++ .owner = THIS_MODULE, ++}; ++ ++static void sifive_pwm_ptc_disable_action(void *data) ++{ ++ clk_disable_unprepare(data); ++} ++ ++static int sifive_pwm_ptc_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct device_node *node = pdev->dev.of_node; ++ struct sifive_pwm_ptc_device *pwm; ++ struct pwm_chip *chip; ++ struct reset_control *rst; ++ int ret; ++ ++ pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL); ++ if (!pwm) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, pwm); ++ ++ chip = &pwm->chip; ++ chip->dev = dev; ++ chip->ops = &sifive_pwm_ptc_ops; ++ ++ /* how many parameters can be transferred to ptc, need to fix */ ++ chip->of_pwm_n_cells = 3; ++ chip->base = -1; ++ ++ /* get pwm channels count, max value is 8 */ ++ ret = of_property_read_u32(node, "starfive,npwm", &chip->npwm); ++ if (ret < 0 || chip->npwm > MAX_PWM) ++ chip->npwm = MAX_PWM; ++ ++ dev_dbg(dev, "%s: npwm:0x%x\n", __func__, chip->npwm); ++ ++ /* get IO base address */ ++ pwm->regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(pwm->regs)) ++ return dev_err_probe(dev, PTR_ERR(pwm->regs), ++ "Unable to map IO resources\n"); ++ ++ pwm->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(pwm->clk)) ++ return dev_err_probe(dev, PTR_ERR(pwm->clk), ++ "Unable to get controller clock\n"); ++ ++ ret = clk_prepare_enable(pwm->clk); ++ if (ret) ++ return dev_err_probe(dev, ret, "Unable to enable clock\n"); ++ ++ ret = devm_add_action_or_reset(dev, sifive_pwm_ptc_disable_action, pwm->clk); ++ if (ret) ++ return ret; ++ ++ rst = devm_reset_control_get_exclusive(dev, NULL); ++ if (IS_ERR(rst)) ++ return dev_err_probe(dev, PTR_ERR(rst), "Unable to get reset\n"); ++ ++ ret = reset_control_deassert(rst); ++ if (ret) ++ return dev_err_probe(dev, ret, "Unable to deassert reset\n"); ++ ++ /* ++ * after pwmchip_add it will show up as /sys/class/pwm/pwmchip0, ++ * 0 is chip->base, pwm0 can be seen after running echo 0 > export ++ */ ++ ret = devm_pwmchip_add(dev, chip); ++ if (ret) ++ return dev_err_probe(dev, ret, "cannot register PTC: %d\n", ret); ++ ++ dev_dbg(dev, "SiFive PWM PTC chip registered %d PWMs\n", chip->npwm); ++ return 0; ++} ++ ++static const struct of_device_id sifive_pwm_ptc_of_match[] = { ++ { .compatible = "starfive,pwm0" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, sifive_pwm_ptc_of_match); ++ ++static struct platform_driver sifive_pwm_ptc_driver = { ++ .probe = sifive_pwm_ptc_probe, ++ .driver = { ++ .name = "pwm-sifive-ptc", ++ .of_match_table = sifive_pwm_ptc_of_match, ++ }, ++}; ++module_platform_driver(sifive_pwm_ptc_driver); ++ ++MODULE_DESCRIPTION("SiFive PWM PTC driver"); ++MODULE_LICENSE("GPL v2"); diff --git a/target/linux/starfive/patches-6.6/1012-dt-bindings-reset-Add-StarFive-JH7100-audio-reset-de.patch b/target/linux/starfive/patches-6.6/1012-dt-bindings-reset-Add-StarFive-JH7100-audio-reset-de.patch new file mode 100644 index 0000000000..6986d4b1cd --- /dev/null +++ b/target/linux/starfive/patches-6.6/1012-dt-bindings-reset-Add-StarFive-JH7100-audio-reset-de.patch @@ -0,0 +1,48 @@ +From 4f3335c302b7f944b61f564095505b3c7a1b62ee Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <kernel@esmil.dk> +Date: Sat, 20 Nov 2021 19:29:25 +0100 +Subject: [PATCH 1012/1024] dt-bindings: reset: Add StarFive JH7100 audio reset + definitions + +Add all resets for the StarFive JH7100 audio reset controller. + +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +--- + .../dt-bindings/reset/starfive-jh7100-audio.h | 31 +++++++++++++++++++ + 1 file changed, 31 insertions(+) + create mode 100644 include/dt-bindings/reset/starfive-jh7100-audio.h + +--- /dev/null ++++ b/include/dt-bindings/reset/starfive-jh7100-audio.h +@@ -0,0 +1,31 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR MIT */ ++/* ++ * Copyright (C) 2021 Emil Renner Berthing ++ */ ++ ++#ifndef __DT_BINDINGS_RESET_STARFIVE_JH7100_AUDIO_H__ ++#define __DT_BINDINGS_RESET_STARFIVE_JH7100_AUDIO_H__ ++ ++#define JH7100_AUDRSTN_APB_BUS 0 ++#define JH7100_AUDRSTN_I2SADC_APB 1 ++#define JH7100_AUDRSTN_I2SADC_SRST 2 ++#define JH7100_AUDRSTN_PDM_APB 3 ++#define JH7100_AUDRSTN_I2SVAD_APB 4 ++#define JH7100_AUDRSTN_I2SVAD_SRST 5 ++#define JH7100_AUDRSTN_SPDIF_APB 6 ++#define JH7100_AUDRSTN_PWMDAC_APB 7 ++#define JH7100_AUDRSTN_I2SDAC_APB 8 ++#define JH7100_AUDRSTN_I2SDAC_SRST 9 ++#define JH7100_AUDRSTN_I2S1_APB 10 ++#define JH7100_AUDRSTN_I2S1_SRST 11 ++#define JH7100_AUDRSTN_I2SDAC16K_APB 12 ++#define JH7100_AUDRSTN_I2SDAC16K_SRST 13 ++#define JH7100_AUDRSTN_DMA1P_AHB 14 ++#define JH7100_AUDRSTN_USB_APB 15 ++#define JH7100_AUDRST_USB_AXI 16 ++#define JH7100_AUDRST_USB_PWRUP_RST_N 17 ++#define JH7100_AUDRST_USB_PONRST 18 ++ ++#define JH7100_AUDRSTN_END 19 ++ ++#endif /* __DT_BINDINGS_RESET_STARFIVE_JH7100_AUDIO_H__ */ diff --git a/target/linux/starfive/patches-6.6/1013-dt-bindings-reset-Add-starfive-jh7100-audrst-binding.patch b/target/linux/starfive/patches-6.6/1013-dt-bindings-reset-Add-starfive-jh7100-audrst-binding.patch new file mode 100644 index 0000000000..5eac29e006 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1013-dt-bindings-reset-Add-starfive-jh7100-audrst-binding.patch @@ -0,0 +1,56 @@ +From 1e428568e486b40f78febddf2958ba27289bd49f Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <kernel@esmil.dk> +Date: Tue, 7 Dec 2021 21:48:51 +0100 +Subject: [PATCH 1013/1024] dt-bindings: reset: Add starfive,jh7100-audrst + bindings + +Add bindings for the audio reset controller on the StarFive JH7100 +RISC-V SoC. + +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +--- + .../reset/starfive,jh7100-audrst.yaml | 38 +++++++++++++++++++ + 1 file changed, 38 insertions(+) + create mode 100644 Documentation/devicetree/bindings/reset/starfive,jh7100-audrst.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/reset/starfive,jh7100-audrst.yaml +@@ -0,0 +1,38 @@ ++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/reset/starfive,jh7100-audrst.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: StarFive JH7100 SoC Audio Reset Controller Device Tree Bindings ++ ++maintainers: ++ - Emil Renner Berthing <kernel@esmil.dk> ++ ++properties: ++ compatible: ++ enum: ++ - starfive,jh7100-audrst ++ ++ reg: ++ maxItems: 1 ++ ++ "#reset-cells": ++ const: 1 ++ ++required: ++ - compatible ++ - reg ++ - "#reset-cells" ++ ++additionalProperties: false ++ ++examples: ++ - | ++ reset-controller@10490000 { ++ compatible = "starfive,jh7100-audrst"; ++ reg = <0x10490000 0x10000>; ++ #reset-cells = <1>; ++ }; ++ ++... diff --git a/target/linux/starfive/patches-6.6/1014-reset-starfive-Add-JH7100-audio-reset-driver.patch b/target/linux/starfive/patches-6.6/1014-reset-starfive-Add-JH7100-audio-reset-driver.patch new file mode 100644 index 0000000000..bc33188a44 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1014-reset-starfive-Add-JH7100-audio-reset-driver.patch @@ -0,0 +1,144 @@ +From 91559ced47de9e6fb1e6dd65a5544fcc86dea806 Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <kernel@esmil.dk> +Date: Sat, 20 Nov 2021 19:30:49 +0100 +Subject: [PATCH 1014/1024] reset: starfive: Add JH7100 audio reset driver + +The audio resets are almost identical to the system resets, there are +just fewer of them. So factor out and export a generic probe function, +so most of the reset controller implementation can be shared. + +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +--- + MAINTAINERS | 2 +- + drivers/reset/starfive/Kconfig | 7 ++ + drivers/reset/starfive/Makefile | 2 + + .../starfive/reset-starfive-jh7100-audio.c | 66 +++++++++++++++++++ + .../reset/starfive/reset-starfive-jh7100.h | 16 +++++ + 5 files changed, 92 insertions(+), 1 deletion(-) + create mode 100644 drivers/reset/starfive/reset-starfive-jh7100-audio.c + create mode 100644 drivers/reset/starfive/reset-starfive-jh7100.h + +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -20554,7 +20554,7 @@ STARFIVE JH71X0 RESET CONTROLLER DRIVERS + M: Emil Renner Berthing <kernel@esmil.dk> + M: Hal Feng <hal.feng@starfivetech.com> + S: Maintained +-F: Documentation/devicetree/bindings/reset/starfive,jh7100-reset.yaml ++F: Documentation/devicetree/bindings/reset/starfive,jh7100-*.yaml + F: drivers/reset/starfive/reset-starfive-jh71* + F: include/dt-bindings/reset/starfive?jh71*.h + +--- a/drivers/reset/starfive/Kconfig ++++ b/drivers/reset/starfive/Kconfig +@@ -11,6 +11,13 @@ config RESET_STARFIVE_JH7100 + help + This enables the reset controller driver for the StarFive JH7100 SoC. + ++config RESET_STARFIVE_JH7100_AUDIO ++ tristate "StarFive JH7100 Audio Reset Driver" ++ depends on RESET_STARFIVE_JH7100 ++ default m if SOC_STARFIVE ++ help ++ This enables the audio reset driver for the StarFive JH7100 SoC. ++ + config RESET_STARFIVE_JH7110 + bool "StarFive JH7110 Reset Driver" + depends on CLK_STARFIVE_JH7110_SYS +--- a/drivers/reset/starfive/Makefile ++++ b/drivers/reset/starfive/Makefile +@@ -2,4 +2,6 @@ + obj-$(CONFIG_RESET_STARFIVE_JH71X0) += reset-starfive-jh71x0.o + + obj-$(CONFIG_RESET_STARFIVE_JH7100) += reset-starfive-jh7100.o ++obj-$(CONFIG_RESET_STARFIVE_JH7100_AUDIO) += reset-starfive-jh7100-audio.o ++ + obj-$(CONFIG_RESET_STARFIVE_JH7110) += reset-starfive-jh7110.o +--- /dev/null ++++ b/drivers/reset/starfive/reset-starfive-jh7100-audio.c +@@ -0,0 +1,66 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Audio reset driver for the StarFive JH7100 SoC ++ * ++ * Copyright (C) 2021 Emil Renner Berthing <kernel@esmil.dk> ++ */ ++ ++#include <linux/mod_devicetable.h> ++#include <linux/module.h> ++#include <linux/platform_device.h> ++ ++#include "reset-starfive-jh71x0.h" ++ ++#include <dt-bindings/reset/starfive-jh7100-audio.h> ++ ++/* register offsets */ ++#define JH7100_AUDRST_ASSERT0 0x00 ++#define JH7100_AUDRST_STATUS0 0x04 ++ ++/* ++ * Writing a 1 to the n'th bit of the ASSERT register asserts ++ * line n, and writing a 0 deasserts the same line. ++ * Most reset lines have their status inverted so a 0 bit in the STATUS ++ * register means the line is asserted and a 1 means it's deasserted. A few ++ * lines don't though, so store the expected value of the status registers when ++ * all lines are asserted. ++ */ ++static const u32 jh7100_audrst_asserted[1] = { ++ BIT(JH7100_AUDRST_USB_AXI) | ++ BIT(JH7100_AUDRST_USB_PWRUP_RST_N) | ++ BIT(JH7100_AUDRST_USB_PONRST) ++}; ++ ++static int jh7100_audrst_probe(struct platform_device *pdev) ++{ ++ void __iomem *base = devm_platform_ioremap_resource(pdev, 0); ++ ++ if (IS_ERR(base)) ++ return PTR_ERR(base); ++ ++ return reset_starfive_jh71x0_register(&pdev->dev, pdev->dev.of_node, ++ base + JH7100_AUDRST_ASSERT0, ++ base + JH7100_AUDRST_STATUS0, ++ jh7100_audrst_asserted, ++ JH7100_AUDRSTN_END, ++ THIS_MODULE); ++} ++ ++static const struct of_device_id jh7100_audrst_dt_ids[] = { ++ { .compatible = "starfive,jh7100-audrst" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, jh7100_audrst_dt_ids); ++ ++static struct platform_driver jh7100_audrst_driver = { ++ .probe = jh7100_audrst_probe, ++ .driver = { ++ .name = "jh7100-reset-audio", ++ .of_match_table = jh7100_audrst_dt_ids, ++ }, ++}; ++module_platform_driver(jh7100_audrst_driver); ++ ++MODULE_AUTHOR("Emil Renner Berthing"); ++MODULE_DESCRIPTION("StarFive JH7100 audio reset driver"); ++MODULE_LICENSE("GPL"); +--- /dev/null ++++ b/drivers/reset/starfive/reset-starfive-jh7100.h +@@ -0,0 +1,16 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2021 Emil Renner Berthing <kernel@esmil.dk> ++ */ ++ ++#ifndef _RESET_STARFIVE_JH7100_H_ ++#define _RESET_STARFIVE_JH7100_H_ ++ ++#include <linux/platform_device.h> ++ ++int reset_starfive_jh7100_generic_probe(struct platform_device *pdev, ++ const u32 *asserted, ++ unsigned int status_offset, ++ unsigned int nr_resets); ++ ++#endif diff --git a/target/linux/starfive/patches-6.6/1015-RISC-V-Add-StarFive-JH7100-audio-reset-node.patch b/target/linux/starfive/patches-6.6/1015-RISC-V-Add-StarFive-JH7100-audio-reset-node.patch new file mode 100644 index 0000000000..521144c9f0 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1015-RISC-V-Add-StarFive-JH7100-audio-reset-node.patch @@ -0,0 +1,28 @@ +From 418603fdce51212d4547aacfe2b4801fc5e61978 Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <kernel@esmil.dk> +Date: Sat, 20 Nov 2021 21:33:08 +0100 +Subject: [PATCH 1015/1024] RISC-V: Add StarFive JH7100 audio reset node + +Add device tree node for the audio resets on the StarFive JH7100 RISC-V +SoC. + +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +--- + arch/riscv/boot/dts/starfive/jh7100.dtsi | 6 ++++++ + 1 file changed, 6 insertions(+) + +--- a/arch/riscv/boot/dts/starfive/jh7100.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7100.dtsi +@@ -168,6 +168,12 @@ + #clock-cells = <1>; + }; + ++ audrst: reset-controller@10490000 { ++ compatible = "starfive,jh7100-audrst"; ++ reg = <0x0 0x10490000 0x0 0x10000>; ++ #reset-cells = <1>; ++ }; ++ + clkgen: clock-controller@11800000 { + compatible = "starfive,jh7100-clkgen"; + reg = <0x0 0x11800000 0x0 0x10000>; diff --git a/target/linux/starfive/patches-6.6/1016-soc-sifive-ccache-Add-StarFive-JH7100-support.patch b/target/linux/starfive/patches-6.6/1016-soc-sifive-ccache-Add-StarFive-JH7100-support.patch new file mode 100644 index 0000000000..2906e1605c --- /dev/null +++ b/target/linux/starfive/patches-6.6/1016-soc-sifive-ccache-Add-StarFive-JH7100-support.patch @@ -0,0 +1,160 @@ +From 8e090d271683d5869cdab0729f54a8af8c79c476 Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <kernel@esmil.dk> +Date: Tue, 31 Oct 2023 15:14:44 +0100 +Subject: [PATCH 1016/1024] soc: sifive: ccache: Add StarFive JH7100 support + +This adds support for the StarFive JH7100 SoC which also features this +SiFive cache controller. + +The JH7100 has non-coherent DMAs but predate the standard RISC-V Zicbom +exension, so instead we need to use this cache controller for +non-standard cache management operations. + +Unfortunately the interrupt for uncorrected data is broken on the JH7100 +and fires continuously, so add a quirk to not register a handler for it. + +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +Signed-off-by: Conor Dooley <conor.dooley@microchip.com> +--- + drivers/soc/sifive/sifive_ccache.c | 62 +++++++++++++++++++++++++++++- + 1 file changed, 60 insertions(+), 2 deletions(-) + +--- a/drivers/soc/sifive/sifive_ccache.c ++++ b/drivers/soc/sifive/sifive_ccache.c +@@ -8,13 +8,16 @@ + + #define pr_fmt(fmt) "CCACHE: " fmt + ++#include <linux/align.h> + #include <linux/debugfs.h> + #include <linux/interrupt.h> + #include <linux/of_irq.h> + #include <linux/of_address.h> + #include <linux/device.h> + #include <linux/bitfield.h> ++#include <asm/cacheflush.h> + #include <asm/cacheinfo.h> ++#include <asm/dma-noncoherent.h> + #include <soc/sifive/sifive_ccache.h> + + #define SIFIVE_CCACHE_DIRECCFIX_LOW 0x100 +@@ -39,10 +42,14 @@ + #define SIFIVE_CCACHE_CONFIG_SETS_MASK GENMASK_ULL(23, 16) + #define SIFIVE_CCACHE_CONFIG_BLKS_MASK GENMASK_ULL(31, 24) + ++#define SIFIVE_CCACHE_FLUSH64 0x200 ++#define SIFIVE_CCACHE_FLUSH32 0x240 ++ + #define SIFIVE_CCACHE_WAYENABLE 0x08 + #define SIFIVE_CCACHE_ECCINJECTERR 0x40 + + #define SIFIVE_CCACHE_MAX_ECCINTR 4 ++#define SIFIVE_CCACHE_LINE_SIZE 64 + + static void __iomem *ccache_base; + static int g_irq[SIFIVE_CCACHE_MAX_ECCINTR]; +@@ -56,6 +63,11 @@ enum { + DIR_UNCORR, + }; + ++enum { ++ QUIRK_NONSTANDARD_CACHE_OPS = BIT(0), ++ QUIRK_BROKEN_DATA_UNCORR = BIT(1), ++}; ++ + #ifdef CONFIG_DEBUG_FS + static struct dentry *sifive_test; + +@@ -106,6 +118,8 @@ static void ccache_config_read(void) + static const struct of_device_id sifive_ccache_ids[] = { + { .compatible = "sifive,fu540-c000-ccache" }, + { .compatible = "sifive,fu740-c000-ccache" }, ++ { .compatible = "starfive,jh7100-ccache", ++ .data = (void *)(QUIRK_NONSTANDARD_CACHE_OPS | QUIRK_BROKEN_DATA_UNCORR) }, + { .compatible = "sifive,ccache0" }, + { /* end of table */ } + }; +@@ -124,6 +138,34 @@ int unregister_sifive_ccache_error_notif + } + EXPORT_SYMBOL_GPL(unregister_sifive_ccache_error_notifier); + ++#ifdef CONFIG_RISCV_NONSTANDARD_CACHE_OPS ++static void ccache_flush_range(phys_addr_t start, size_t len) ++{ ++ phys_addr_t end = start + len; ++ phys_addr_t line; ++ ++ if (!len) ++ return; ++ ++ mb(); ++ for (line = ALIGN_DOWN(start, SIFIVE_CCACHE_LINE_SIZE); line < end; ++ line += SIFIVE_CCACHE_LINE_SIZE) { ++#ifdef CONFIG_32BIT ++ writel(line >> 4, ccache_base + SIFIVE_CCACHE_FLUSH32); ++#else ++ writeq(line, ccache_base + SIFIVE_CCACHE_FLUSH64); ++#endif ++ mb(); ++ } ++} ++ ++static const struct riscv_nonstd_cache_ops ccache_mgmt_ops __initconst = { ++ .wback = &ccache_flush_range, ++ .inv = &ccache_flush_range, ++ .wback_inv = &ccache_flush_range, ++}; ++#endif /* CONFIG_RISCV_NONSTANDARD_CACHE_OPS */ ++ + static int ccache_largest_wayenabled(void) + { + return readl(ccache_base + SIFIVE_CCACHE_WAYENABLE) & 0xFF; +@@ -210,11 +252,15 @@ static int __init sifive_ccache_init(voi + struct device_node *np; + struct resource res; + int i, rc, intr_num; ++ const struct of_device_id *match; ++ unsigned long quirks; + +- np = of_find_matching_node(NULL, sifive_ccache_ids); ++ np = of_find_matching_node_and_match(NULL, sifive_ccache_ids, &match); + if (!np) + return -ENODEV; + ++ quirks = (uintptr_t)match->data; ++ + if (of_address_to_resource(np, 0, &res)) { + rc = -ENODEV; + goto err_node_put; +@@ -240,6 +286,10 @@ static int __init sifive_ccache_init(voi + + for (i = 0; i < intr_num; i++) { + g_irq[i] = irq_of_parse_and_map(np, i); ++ ++ if (i == DATA_UNCORR && (quirks & QUIRK_BROKEN_DATA_UNCORR)) ++ continue; ++ + rc = request_irq(g_irq[i], ccache_int_handler, 0, "ccache_ecc", + NULL); + if (rc) { +@@ -249,6 +299,14 @@ static int __init sifive_ccache_init(voi + } + of_node_put(np); + ++#ifdef CONFIG_RISCV_NONSTANDARD_CACHE_OPS ++ if (quirks & QUIRK_NONSTANDARD_CACHE_OPS) { ++ riscv_cbom_block_size = SIFIVE_CCACHE_LINE_SIZE; ++ riscv_noncoherent_supported(); ++ riscv_noncoherent_register_cache_ops(&ccache_mgmt_ops); ++ } ++#endif ++ + ccache_config_read(); + + ccache_cache_ops.get_priv_group = ccache_get_priv_group; +@@ -269,4 +327,4 @@ err_node_put: + return rc; + } + +-device_initcall(sifive_ccache_init); ++arch_initcall(sifive_ccache_init); diff --git a/target/linux/starfive/patches-6.6/1017-riscv-errata-Add-StarFive-JH7100-errata.patch b/target/linux/starfive/patches-6.6/1017-riscv-errata-Add-StarFive-JH7100-errata.patch new file mode 100644 index 0000000000..91b3adfac1 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1017-riscv-errata-Add-StarFive-JH7100-errata.patch @@ -0,0 +1,44 @@ +From 30fb5963f4cf3b7d114a8212358147615480685c Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Date: Thu, 30 Nov 2023 16:19:25 +0100 +Subject: [PATCH 1017/1024] riscv: errata: Add StarFive JH7100 errata + +This not really an errata, but since the JH7100 was made before +the standard Zicbom extension it needs the DMA_GLOBAL_POOL and +RISCV_NONSTANDARD_CACHE_OPS enabled to work correctly. + +Acked-by: Conor Dooley <conor.dooley@microchip.com> +Signed-off-by: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Reviewed-by: Palmer Dabbelt <palmer@rivosinc.com> +Acked-by: Palmer Dabbelt <palmer@rivosinc.com> +Signed-off-by: Conor Dooley <conor.dooley@microchip.com> +--- + arch/riscv/Kconfig.errata | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +--- a/arch/riscv/Kconfig.errata ++++ b/arch/riscv/Kconfig.errata +@@ -53,6 +53,23 @@ config ERRATA_SIFIVE_CIP_1200 + + If you don't know what to do here, say "Y". + ++config ERRATA_STARFIVE_JH7100 ++ bool "StarFive JH7100 support" ++ depends on ARCH_STARFIVE && NONPORTABLE ++ select DMA_GLOBAL_POOL ++ select RISCV_DMA_NONCOHERENT ++ select RISCV_NONSTANDARD_CACHE_OPS ++ select SIFIVE_CCACHE ++ default n ++ help ++ The StarFive JH7100 was a test chip for the JH7110 and has ++ caches that are non-coherent with respect to peripheral DMAs. ++ It was designed before the Zicbom extension so needs non-standard ++ cache operations through the SiFive cache controller. ++ ++ Say "Y" if you want to support the BeagleV Starlight and/or ++ StarFive VisionFive V1 boards. ++ + config ERRATA_THEAD + bool "T-HEAD errata" + depends on RISCV_ALTERNATIVE diff --git a/target/linux/starfive/patches-6.6/1018-riscv-dts-starfive-Mark-the-JH7100-as-having-non-coh.patch b/target/linux/starfive/patches-6.6/1018-riscv-dts-starfive-Mark-the-JH7100-as-having-non-coh.patch new file mode 100644 index 0000000000..0b223c5925 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1018-riscv-dts-starfive-Mark-the-JH7100-as-having-non-coh.patch @@ -0,0 +1,26 @@ +From 29e4bc0fafd9add93acc967f3992948b3afe7176 Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <kernel@esmil.dk> +Date: Thu, 30 Nov 2023 16:19:27 +0100 +Subject: [PATCH 1018/1024] riscv: dts: starfive: Mark the JH7100 as having + non-coherent DMAs + +The StarFive JH7100 SoC has non-coherent device DMAs, so mark the +soc bus as such. + +Link: https://github.com/starfive-tech/JH7100_Docs/blob/main/JH7100%20Cache%20Coherence%20V1.0.pdf +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +Signed-off-by: Conor Dooley <conor.dooley@microchip.com> +--- + arch/riscv/boot/dts/starfive/jh7100.dtsi | 1 + + 1 file changed, 1 insertion(+) + +--- a/arch/riscv/boot/dts/starfive/jh7100.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7100.dtsi +@@ -138,6 +138,7 @@ + interrupt-parent = <&plic>; + #address-cells = <2>; + #size-cells = <2>; ++ dma-noncoherent; + ranges; + + clint: clint@2000000 { diff --git a/target/linux/starfive/patches-6.6/1019-riscv-dts-starfive-Add-JH7100-cache-controller.patch b/target/linux/starfive/patches-6.6/1019-riscv-dts-starfive-Add-JH7100-cache-controller.patch new file mode 100644 index 0000000000..cdc63fac65 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1019-riscv-dts-starfive-Add-JH7100-cache-controller.patch @@ -0,0 +1,50 @@ +From e1918356dcc285eb7c50f271795e6fcc18d6c092 Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Date: Thu, 30 Nov 2023 16:19:28 +0100 +Subject: [PATCH 1019/1024] riscv: dts: starfive: Add JH7100 cache controller + +The StarFive JH7100 SoC also features the SiFive L2 cache controller, +so add the device tree nodes for it. + +Signed-off-by: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Signed-off-by: Conor Dooley <conor.dooley@microchip.com> +--- + arch/riscv/boot/dts/starfive/jh7100.dtsi | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +--- a/arch/riscv/boot/dts/starfive/jh7100.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7100.dtsi +@@ -32,6 +32,7 @@ + i-tlb-sets = <1>; + i-tlb-size = <32>; + mmu-type = "riscv,sv39"; ++ next-level-cache = <&ccache>; + riscv,isa = "rv64imafdc"; + tlb-split; + +@@ -57,6 +58,7 @@ + i-tlb-sets = <1>; + i-tlb-size = <32>; + mmu-type = "riscv,sv39"; ++ next-level-cache = <&ccache>; + riscv,isa = "rv64imafdc"; + tlb-split; + +@@ -148,6 +150,17 @@ + &cpu1_intc 3 &cpu1_intc 7>; + }; + ++ ccache: cache-controller@2010000 { ++ compatible = "starfive,jh7100-ccache", "sifive,ccache0", "cache"; ++ reg = <0x0 0x2010000 0x0 0x1000>; ++ interrupts = <128>, <130>, <131>, <129>; ++ cache-block-size = <64>; ++ cache-level = <2>; ++ cache-sets = <2048>; ++ cache-size = <2097152>; ++ cache-unified; ++ }; ++ + plic: interrupt-controller@c000000 { + compatible = "starfive,jh7100-plic", "sifive,plic-1.0.0"; + reg = <0x0 0xc000000 0x0 0x4000000>; diff --git a/target/linux/starfive/patches-6.6/1020-riscv-dts-starfive-Add-pool-for-coherent-DMA-memory-.patch b/target/linux/starfive/patches-6.6/1020-riscv-dts-starfive-Add-pool-for-coherent-DMA-memory-.patch new file mode 100644 index 0000000000..c9ba3884f6 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1020-riscv-dts-starfive-Add-pool-for-coherent-DMA-memory-.patch @@ -0,0 +1,61 @@ +From 3cbd661b811bda9a33253f65b5cf0c25b8c5447f Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Date: Thu, 30 Nov 2023 16:19:29 +0100 +Subject: [PATCH 1020/1024] riscv: dts: starfive: Add pool for coherent DMA + memory on JH7100 boards + +The StarFive JH7100 SoC has non-coherent device DMAs, but most drivers +expect to be able to allocate coherent memory for DMA descriptors and +such. However on the JH7100 DDR memory appears twice in the physical +memory map, once cached and once uncached: + + 0x00_8000_0000 - 0x08_7fff_ffff : Off chip DDR memory, cached + 0x10_0000_0000 - 0x17_ffff_ffff : Off chip DDR memory, uncached + +To use this uncached region we create a global DMA memory pool there and +reserve the corresponding area in the cached region. + +However the uncached region is fully above the 32bit address limit, so add +a dma-ranges map so the DMA address used for peripherals is still in the +regular cached region below the limit. + +Link: https://github.com/starfive-tech/JH7100_Docs/blob/main/JH7100%20Data%20Sheet%20V01.01.04-EN%20(4-21-2021).pdf +Signed-off-by: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Signed-off-by: Conor Dooley <conor.dooley@microchip.com> +--- + .../boot/dts/starfive/jh7100-common.dtsi | 24 +++++++++++++++++++ + 1 file changed, 24 insertions(+) + +--- a/arch/riscv/boot/dts/starfive/jh7100-common.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7100-common.dtsi +@@ -39,6 +39,30 @@ + label = "ack"; + }; + }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ dma-reserved@fa000000 { ++ reg = <0x0 0xfa000000 0x0 0x1000000>; ++ no-map; ++ }; ++ ++ linux,dma@107a000000 { ++ compatible = "shared-dma-pool"; ++ reg = <0x10 0x7a000000 0x0 0x1000000>; ++ no-map; ++ linux,dma-default; ++ }; ++ }; ++ ++ soc { ++ dma-ranges = <0x00 0x80000000 0x00 0x80000000 0x00 0x7a000000>, ++ <0x00 0xfa000000 0x10 0x7a000000 0x00 0x01000000>, ++ <0x00 0xfb000000 0x00 0xfb000000 0x07 0x85000000>; ++ }; + }; + + &gpio { diff --git a/target/linux/starfive/patches-6.6/1021-riscv-dts-starfive-Add-JH7100-MMC-nodes.patch b/target/linux/starfive/patches-6.6/1021-riscv-dts-starfive-Add-JH7100-MMC-nodes.patch new file mode 100644 index 0000000000..fb3ba521c9 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1021-riscv-dts-starfive-Add-JH7100-MMC-nodes.patch @@ -0,0 +1,49 @@ +From 7be159c760aa8a1ece1354892af215b2f8c21152 Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Date: Thu, 30 Nov 2023 16:19:30 +0100 +Subject: [PATCH 1021/1024] riscv: dts: starfive: Add JH7100 MMC nodes + +Add device tree nodes for the Synopsis MMC controllers on the +StarFive JH7100 SoC. + +Signed-off-by: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Signed-off-by: Conor Dooley <conor.dooley@microchip.com> +--- + arch/riscv/boot/dts/starfive/jh7100.dtsi | 26 ++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +--- a/arch/riscv/boot/dts/starfive/jh7100.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7100.dtsi +@@ -188,6 +188,32 @@ + #reset-cells = <1>; + }; + ++ sdio0: mmc@10000000 { ++ compatible = "snps,dw-mshc"; ++ reg = <0x0 0x10000000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_SDIO0_AHB>, ++ <&clkgen JH7100_CLK_SDIO0_CCLKINT_INV>; ++ clock-names = "biu", "ciu"; ++ interrupts = <4>; ++ data-addr = <0>; ++ fifo-depth = <32>; ++ fifo-watermark-aligned; ++ status = "disabled"; ++ }; ++ ++ sdio1: mmc@10010000 { ++ compatible = "snps,dw-mshc"; ++ reg = <0x0 0x10010000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_SDIO1_AHB>, ++ <&clkgen JH7100_CLK_SDIO1_CCLKINT_INV>; ++ clock-names = "biu", "ciu"; ++ interrupts = <5>; ++ data-addr = <0>; ++ fifo-depth = <32>; ++ fifo-watermark-aligned; ++ status = "disabled"; ++ }; ++ + clkgen: clock-controller@11800000 { + compatible = "starfive,jh7100-clkgen"; + reg = <0x0 0x11800000 0x0 0x10000>; diff --git a/target/linux/starfive/patches-6.6/1022-riscv-dts-starfive-Enable-SD-card-on-JH7100-boards.patch b/target/linux/starfive/patches-6.6/1022-riscv-dts-starfive-Enable-SD-card-on-JH7100-boards.patch new file mode 100644 index 0000000000..bf380eb833 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1022-riscv-dts-starfive-Enable-SD-card-on-JH7100-boards.patch @@ -0,0 +1,85 @@ +From 015edaccef82200d913d5f1e99fd95641f526bc6 Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Date: Thu, 30 Nov 2023 16:19:31 +0100 +Subject: [PATCH 1022/1024] riscv: dts: starfive: Enable SD-card on JH7100 + boards + +Add pinctrl and MMC device tree nodes for the SD-card on the +BeagleV Starlight and StarFive VisionFive V1 boards. + +Signed-off-by: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Signed-off-by: Conor Dooley <conor.dooley@microchip.com> +--- + .../boot/dts/starfive/jh7100-common.dtsi | 47 +++++++++++++++++++ + 1 file changed, 47 insertions(+) + +--- a/arch/riscv/boot/dts/starfive/jh7100-common.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7100-common.dtsi +@@ -12,6 +12,7 @@ + + / { + aliases { ++ mmc0 = &sdio0; + serial0 = &uart3; + }; + +@@ -108,6 +109,43 @@ + }; + }; + ++ sdio0_pins: sdio0-0 { ++ clk-pins { ++ pinmux = <GPIOMUX(54, GPO_SDIO0_PAD_CCLK_OUT, ++ GPO_ENABLE, GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ sdio-pins { ++ pinmux = <GPIOMUX(55, GPO_LOW, GPO_DISABLE, ++ GPI_SDIO0_PAD_CARD_DETECT_N)>, ++ <GPIOMUX(53, ++ GPO_SDIO0_PAD_CCMD_OUT, ++ GPO_SDIO0_PAD_CCMD_OEN, ++ GPI_SDIO0_PAD_CCMD_IN)>, ++ <GPIOMUX(49, ++ GPO_SDIO0_PAD_CDATA_OUT_BIT0, ++ GPO_SDIO0_PAD_CDATA_OEN_BIT0, ++ GPI_SDIO0_PAD_CDATA_IN_BIT0)>, ++ <GPIOMUX(50, ++ GPO_SDIO0_PAD_CDATA_OUT_BIT1, ++ GPO_SDIO0_PAD_CDATA_OEN_BIT1, ++ GPI_SDIO0_PAD_CDATA_IN_BIT1)>, ++ <GPIOMUX(51, ++ GPO_SDIO0_PAD_CDATA_OUT_BIT2, ++ GPO_SDIO0_PAD_CDATA_OEN_BIT2, ++ GPI_SDIO0_PAD_CDATA_IN_BIT2)>, ++ <GPIOMUX(52, ++ GPO_SDIO0_PAD_CDATA_OUT_BIT3, ++ GPO_SDIO0_PAD_CDATA_OEN_BIT3, ++ GPI_SDIO0_PAD_CDATA_IN_BIT3)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ }; ++ + uart3_pins: uart3-0 { + rx-pins { + pinmux = <GPIOMUX(13, GPO_LOW, GPO_DISABLE, +@@ -178,6 +216,15 @@ + clock-frequency = <27000000>; + }; + ++&sdio0 { ++ broken-cd; ++ bus-width = <4>; ++ cap-sd-highspeed; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&sdio0_pins>; ++ status = "okay"; ++}; ++ + &uart3 { + pinctrl-names = "default"; + pinctrl-0 = <&uart3_pins>; diff --git a/target/linux/starfive/patches-6.6/1023-riscv-errata-Make-ERRATA_STARFIVE_JH7100-depend-on-D.patch b/target/linux/starfive/patches-6.6/1023-riscv-errata-Make-ERRATA_STARFIVE_JH7100-depend-on-D.patch new file mode 100644 index 0000000000..2b782e1129 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1023-riscv-errata-Make-ERRATA_STARFIVE_JH7100-depend-on-D.patch @@ -0,0 +1,33 @@ +From 1e70a0772165dd552f82434c9072dabfaaaf4c2a Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Date: Fri, 15 Dec 2023 20:09:09 +0100 +Subject: [PATCH 1023/1024] riscv: errata: Make ERRATA_STARFIVE_JH7100 depend + on !DMA_DIRECT_REMAP + +Similar to the Renesas RZ/Five[1] the JH7100 SoC needs the non-portable +CONFIG_DMA_GLOBAL_POOL enabled which is incompatible with DMA_DIRECT_REMAP +selected by RISCV_ISA_ZICBOM. + +[1]: commit 31b2daea0764 ("soc: renesas: Make RZ/Five depend on !DMA_DIRECT_REMAP") + +Link: https://lore.kernel.org/all/24942b4d-d16a-463f-b39a-f9dfcb89d742@infradead.org/ +Fixes: 64fc984a8a54 ("riscv: errata: Add StarFive JH7100 errata") +Signed-off-by: Emil Renner Berthing <emil.renner.berthing@canonical.com> +Signed-off-by: Conor Dooley <conor.dooley@microchip.com> +--- + arch/riscv/Kconfig.errata | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +--- a/arch/riscv/Kconfig.errata ++++ b/arch/riscv/Kconfig.errata +@@ -55,7 +55,9 @@ config ERRATA_SIFIVE_CIP_1200 + + config ERRATA_STARFIVE_JH7100 + bool "StarFive JH7100 support" +- depends on ARCH_STARFIVE && NONPORTABLE ++ depends on ARCH_STARFIVE ++ depends on !DMA_DIRECT_REMAP ++ depends on NONPORTABLE + select DMA_GLOBAL_POOL + select RISCV_DMA_NONCOHERENT + select RISCV_NONSTANDARD_CACHE_OPS diff --git a/target/linux/starfive/patches-6.6/1024-riscv-dts-Add-full-JH7100-Starlight-and-VisionFive-s.patch b/target/linux/starfive/patches-6.6/1024-riscv-dts-Add-full-JH7100-Starlight-and-VisionFive-s.patch new file mode 100644 index 0000000000..b20c2d8bf5 --- /dev/null +++ b/target/linux/starfive/patches-6.6/1024-riscv-dts-Add-full-JH7100-Starlight-and-VisionFive-s.patch @@ -0,0 +1,1138 @@ +From 782f99cc437d975c9ef5a1f351bb8fb83d50039b Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing <kernel@esmil.dk> +Date: Sun, 1 Sep 2024 12:43:01 +0000 +Subject: [PATCH 1024/1024] riscv: dts: Add full JH7100, Starlight and + VisionFive support + +Based on the device tree in https://github.com/starfive-tech/u-boot/ +with contributions from: +yanhong.wang <yanhong.wang@starfivetech.com> +Huan.Feng <huan.feng@starfivetech.com> +ke.zhu <ke.zhu@starfivetech.com> +yiming.li <yiming.li@starfivetech.com> +jack.zhu <jack.zhu@starfivetech.com> +Samin Guo <samin.guo@starfivetech.com> +Chenjieqin <Jessica.Chen@starfivetech.com> +bo.li <bo.li@starfivetech.com> + +Rearranged, cleanups, fixes, pins and resets added by Emil. +Cleanups, fixes, clocks added by Geert. +Cleanups and GPIO fixes from Drew. +Thermal zone added by Stephen. +PWM pins added by Jianlong. +cpu-map added by Jonas. + +Signed-off-by: Emil Renner Berthing <kernel@esmil.dk> +Signed-off-by: Geert Uytterhoeven <geert@linux-m68k.org> +Signed-off-by: Stephen L Arnold <nerdboy@gentoo.org> +Signed-off-by: Drew Fustini <drew@beagleboard.org> +Signed-off-by: Jianlong Huang <jianlong.huang@starfivetech.com> +Signed-off-by: Jonas Hahnfeld <hahnjo@hahnjo.de> +--- + .../dts/starfive/jh7100-beaglev-starlight.dts | 16 + + .../boot/dts/starfive/jh7100-common.dtsi | 432 +++++++++++++++ + .../jh7100-starfive-visionfive-v1.dts | 19 + + arch/riscv/boot/dts/starfive/jh7100.dtsi | 503 ++++++++++++++++++ + 4 files changed, 970 insertions(+) + +--- a/arch/riscv/boot/dts/starfive/jh7100-beaglev-starlight.dts ++++ b/arch/riscv/boot/dts/starfive/jh7100-beaglev-starlight.dts +@@ -6,8 +6,24 @@ + + /dts-v1/; + #include "jh7100-common.dtsi" ++#include <dt-bindings/gpio/gpio.h> + + / { + model = "BeagleV Starlight Beta"; + compatible = "beagle,beaglev-starlight-jh7100-r0", "starfive,jh7100"; + }; ++ ++&gmac { ++ snps,reset-gpios = <&gpio 63 GPIO_ACTIVE_LOW>; ++}; ++ ++&gpio { ++ /* don't reset gpio mux for serial console on uart3 */ ++ starfive,keep-gpiomux = <13 14>; ++}; ++ ++&mdio { ++ phy: ethernet-phy@7 { ++ reg = <7>; ++ }; ++}; +--- a/arch/riscv/boot/dts/starfive/jh7100-common.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7100-common.dtsi +@@ -14,6 +14,7 @@ + aliases { + mmc0 = &sdio0; + serial0 = &uart3; ++ serial1 = &uart0; + }; + + chosen { +@@ -64,9 +65,174 @@ + <0x00 0xfa000000 0x10 0x7a000000 0x00 0x01000000>, + <0x00 0xfb000000 0x00 0xfb000000 0x07 0x85000000>; + }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ linux,cma { ++ compatible = "shared-dma-pool"; ++ reusable; ++ size = <0x0 0x28000000>; ++ alignment = <0x0 0x1000>; ++ alloc-ranges = <0x0 0xa0000000 0x0 0x28000000>; ++ linux,cma-default; ++ }; ++ ++ jpu_reserved: framebuffer@c9000000 { ++ reg = <0x0 0xc9000000 0x0 0x4000000>; ++ }; ++ ++ nvdla_reserved: framebuffer@d0000000 { ++ no-map; ++ reg = <0x0 0xd0000000 0x0 0x28000000>; ++ }; ++ ++ vin_reserved: framebuffer@f9000000 { ++ compatible = "shared-dma-pool"; ++ no-map; ++ reg = <0x0 0xf9000000 0x0 0x1000000>; ++ }; ++ ++ sffb_reserved: framebuffer@fb000000 { ++ compatible = "shared-dma-pool"; ++ no-map; ++ reg = <0x0 0xfb000000 0x0 0x2000000>; ++ }; ++ }; ++ ++ wifi_pwrseq: wifi-pwrseq { ++ compatible = "mmc-pwrseq-simple"; ++ reset-gpios = <&gpio 37 GPIO_ACTIVE_LOW>; ++ }; ++}; ++ ++&display { ++ memory-region = <&sffb_reserved>; ++ status = "okay"; ++}; ++ ++&crtc { ++ ddr-format = <4>; //<WIN_FMT_RGB565>; ++ status = "okay"; ++ ++ port: port@0 { ++ reg = <0>; ++ ++ crtc_0_out: endpoint { ++ remote-endpoint = <&hdmi_input0>; ++ }; ++ }; ++}; ++ ++&encoder { ++ encoder-type = <2>; // 2-TMDS, 3-LVDS, 6-DSI, 8-DPI ++ status = "okay"; ++ ++ ports { ++ port@0 { ++ hdmi_out: endpoint { ++ remote-endpoint = <&tda998x_0_input>; ++ }; ++ }; ++ ++ port@1 { ++ hdmi_input0: endpoint { ++ remote-endpoint = <&crtc_0_out>; ++ }; ++ }; ++ ++ }; ++}; ++ ++&gmac { ++ starfive,gtxclk-dlychain = <4>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&gmac_pins>; ++ phy-mode = "rgmii-txid"; ++ phy-handle = <&phy>; ++ status = "okay"; ++ ++ mdio: mdio { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ compatible = "snps,dwmac-mdio"; ++ }; + }; + + &gpio { ++ gmac_pins: gmac-0 { ++ gtxclk-pins { ++ pins = <PAD_FUNC_SHARE(115)>; ++ bias-pull-up; ++ drive-strength = <35>; ++ input-enable; ++ input-schmitt-enable; ++ slew-rate = <0>; ++ }; ++ miitxclk-pins { ++ pins = <PAD_FUNC_SHARE(116)>; ++ bias-pull-up; ++ drive-strength = <14>; ++ input-enable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ tx-pins { ++ pins = <PAD_FUNC_SHARE(117)>, ++ <PAD_FUNC_SHARE(119)>, ++ <PAD_FUNC_SHARE(120)>, ++ <PAD_FUNC_SHARE(121)>, ++ <PAD_FUNC_SHARE(122)>, ++ <PAD_FUNC_SHARE(123)>, ++ <PAD_FUNC_SHARE(124)>, ++ <PAD_FUNC_SHARE(125)>, ++ <PAD_FUNC_SHARE(126)>; ++ bias-pull-up; ++ drive-strength = <35>; ++ input-disable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ rxclk-pins { ++ pins = <PAD_FUNC_SHARE(127)>; ++ bias-pull-up; ++ drive-strength = <14>; ++ input-enable; ++ input-schmitt-disable; ++ slew-rate = <6>; ++ }; ++ rxer-pins { ++ pins = <PAD_FUNC_SHARE(129)>; ++ bias-pull-up; ++ drive-strength = <14>; ++ input-enable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ rx-pins { ++ pins = <PAD_FUNC_SHARE(128)>, ++ <PAD_FUNC_SHARE(130)>, ++ <PAD_FUNC_SHARE(131)>, ++ <PAD_FUNC_SHARE(132)>, ++ <PAD_FUNC_SHARE(133)>, ++ <PAD_FUNC_SHARE(134)>, ++ <PAD_FUNC_SHARE(135)>, ++ <PAD_FUNC_SHARE(136)>, ++ <PAD_FUNC_SHARE(137)>, ++ <PAD_FUNC_SHARE(138)>, ++ <PAD_FUNC_SHARE(139)>, ++ <PAD_FUNC_SHARE(140)>, ++ <PAD_FUNC_SHARE(141)>; ++ bias-pull-up; ++ drive-strength = <14>; ++ input-enable; ++ input-schmitt-enable; ++ slew-rate = <0>; ++ }; ++ }; ++ + i2c0_pins: i2c0-0 { + i2c-pins { + pinmux = <GPIOMUX(62, GPO_LOW, +@@ -146,6 +312,166 @@ + }; + }; + ++ pwmdac_pins: pwmdac-0 { ++ pwmdac-pins { ++ pinmux = <GPIOMUX(23, GPO_PWMDAC_LEFT_OUT, ++ GPO_ENABLE, GPI_NONE)>, ++ <GPIOMUX(24, GPO_PWMDAC_RIGHT_OUT, ++ GPO_ENABLE, GPI_NONE)>; ++ bias-disable; ++ drive-strength = <35>; ++ input-disable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ }; ++ ++ pwm_pins: pwm-0 { ++ pwm-pins { ++ pinmux = <GPIOMUX(7, ++ GPO_PWM_PAD_OUT_BIT0, ++ GPO_PWM_PAD_OE_N_BIT0, ++ GPI_NONE)>, ++ <GPIOMUX(5, ++ GPO_PWM_PAD_OUT_BIT1, ++ GPO_PWM_PAD_OE_N_BIT1, ++ GPI_NONE)>; ++ bias-disable; ++ drive-strength = <35>; ++ input-disable; ++ input-schmitt-disable; ++ slew-rate = <0>; ++ }; ++ }; ++ ++ sdio0_pins: sdio0-0 { ++ clk-pins { ++ pinmux = <GPIOMUX(54, GPO_SDIO0_PAD_CCLK_OUT, ++ GPO_ENABLE, GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ sdio-pins { ++ pinmux = <GPIOMUX(55, GPO_LOW, GPO_DISABLE, ++ GPI_SDIO0_PAD_CARD_DETECT_N)>, ++ <GPIOMUX(53, ++ GPO_SDIO0_PAD_CCMD_OUT, ++ GPO_SDIO0_PAD_CCMD_OEN, ++ GPI_SDIO0_PAD_CCMD_IN)>, ++ <GPIOMUX(49, ++ GPO_SDIO0_PAD_CDATA_OUT_BIT0, ++ GPO_SDIO0_PAD_CDATA_OEN_BIT0, ++ GPI_SDIO0_PAD_CDATA_IN_BIT0)>, ++ <GPIOMUX(50, ++ GPO_SDIO0_PAD_CDATA_OUT_BIT1, ++ GPO_SDIO0_PAD_CDATA_OEN_BIT1, ++ GPI_SDIO0_PAD_CDATA_IN_BIT1)>, ++ <GPIOMUX(51, ++ GPO_SDIO0_PAD_CDATA_OUT_BIT2, ++ GPO_SDIO0_PAD_CDATA_OEN_BIT2, ++ GPI_SDIO0_PAD_CDATA_IN_BIT2)>, ++ <GPIOMUX(52, ++ GPO_SDIO0_PAD_CDATA_OUT_BIT3, ++ GPO_SDIO0_PAD_CDATA_OEN_BIT3, ++ GPI_SDIO0_PAD_CDATA_IN_BIT3)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ }; ++ ++ sdio1_pins: sdio1-0 { ++ clk-pins { ++ pinmux = <GPIOMUX(33, GPO_SDIO1_PAD_CCLK_OUT, ++ GPO_ENABLE, GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ sdio-pins { ++ pinmux = <GPIOMUX(29, ++ GPO_SDIO1_PAD_CCMD_OUT, ++ GPO_SDIO1_PAD_CCMD_OEN, ++ GPI_SDIO1_PAD_CCMD_IN)>, ++ <GPIOMUX(36, ++ GPO_SDIO1_PAD_CDATA_OUT_BIT0, ++ GPO_SDIO1_PAD_CDATA_OEN_BIT0, ++ GPI_SDIO1_PAD_CDATA_IN_BIT0)>, ++ <GPIOMUX(30, ++ GPO_SDIO1_PAD_CDATA_OUT_BIT1, ++ GPO_SDIO1_PAD_CDATA_OEN_BIT1, ++ GPI_SDIO1_PAD_CDATA_IN_BIT1)>, ++ <GPIOMUX(34, ++ GPO_SDIO1_PAD_CDATA_OUT_BIT2, ++ GPO_SDIO1_PAD_CDATA_OEN_BIT2, ++ GPI_SDIO1_PAD_CDATA_IN_BIT2)>, ++ <GPIOMUX(31, ++ GPO_SDIO1_PAD_CDATA_OUT_BIT3, ++ GPO_SDIO1_PAD_CDATA_OEN_BIT3, ++ GPI_SDIO1_PAD_CDATA_IN_BIT3)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ }; ++ ++ spi2_pins: spi2-0 { ++ mosi-pins { ++ pinmux = <GPIOMUX(18, GPO_SPI2_PAD_TXD, ++ GPO_ENABLE, GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ miso-pins { ++ pinmux = <GPIOMUX(16, GPO_LOW, GPO_DISABLE, ++ GPI_SPI2_PAD_RXD)>; ++ bias-pull-up; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ sck-pins { ++ pinmux = <GPIOMUX(12, GPO_SPI2_PAD_SCK_OUT, ++ GPO_ENABLE, GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ ss-pins { ++ pinmux = <GPIOMUX(15, GPO_SPI2_PAD_SS_0_N, ++ GPO_ENABLE, GPI_NONE)>, ++ <GPIOMUX(11, GPO_SPI2_PAD_SS_1_N, ++ GPO_ENABLE, GPI_NONE)>; ++ bias-disable; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ }; ++ ++ uart0_pins: uart0-0 { ++ rx-pins { ++ pinmux = <GPIOMUX(40, GPO_LOW, GPO_DISABLE, ++ GPI_UART0_PAD_SIN)>, ++ <GPIOMUX(39, GPO_LOW, GPO_DISABLE, ++ GPI_UART0_PAD_CTSN)>; ++ bias-pull-up; ++ drive-strength = <14>; ++ input-enable; ++ input-schmitt-enable; ++ }; ++ tx-pins { ++ pinmux = <GPIOMUX(41, GPO_UART0_PAD_SOUT, ++ GPO_ENABLE, GPI_NONE)>, ++ <GPIOMUX(42, GPO_UART0_PAD_RTSN, ++ GPO_ENABLE, GPI_NONE)>; ++ bias-disable; ++ drive-strength = <35>; ++ input-disable; ++ input-schmitt-disable; ++ }; ++ }; ++ + uart3_pins: uart3-0 { + rx-pins { + pinmux = <GPIOMUX(13, GPO_LOW, GPO_DISABLE, +@@ -186,6 +512,17 @@ + regulators { + }; + }; ++ ++ tda998x@70 { ++ compatible = "nxp,tda998x"; ++ reg = <0x70>; ++ ++ port { ++ tda998x_0_input: endpoint { ++ remote-endpoint = <&hdmi_out>; ++ }; ++ }; ++ }; + }; + + &i2c1 { +@@ -225,8 +562,104 @@ + status = "okay"; + }; + ++&ptc { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwm_pins>; ++ status = "okay"; ++}; ++ ++&pwmdac { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwmdac_pins>; ++ status = "okay"; ++}; ++ ++&qspi { ++ nor_flash: nor-flash@0 { ++ compatible = "spi-flash"; ++ reg = <0>; ++ spi-max-frequency = <31250000>; ++ page-size = <256>; ++ block-size = <16>; ++ cdns,read-delay = <4>; ++ cdns,tshsl-ns = <1>; ++ cdns,tsd2d-ns = <1>; ++ cdns,tchsh-ns = <1>; ++ cdns,tslch-ns = <1>; ++ spi-tx-bus-width = <1>; ++ spi-rx-bus-width = <1>; ++ }; ++ ++ nand_flash: nand-flash@1 { ++ compatible = "spi-flash-nand"; ++ reg = <1>; ++ spi-max-frequency = <31250000>; ++ page-size = <2048>; ++ block-size = <17>; ++ cdns,read-delay = <4>; ++ cdns,tshsl-ns = <1>; ++ cdns,tsd2d-ns = <1>; ++ cdns,tchsh-ns = <1>; ++ cdns,tslch-ns = <1>; ++ spi-tx-bus-width = <1>; ++ spi-rx-bus-width = <1>; ++ }; ++}; ++ ++&sdio0 { ++ broken-cd; ++ bus-width = <4>; ++ cap-sd-highspeed; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&sdio0_pins>; ++ max-frequency = <10000000>; ++ status = "okay"; ++}; ++ ++&sdio1 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ bus-width = <4>; ++ cap-sd-highspeed; ++ cap-sdio-irq; ++ cap-power-off-card; ++ mmc-pwrseq = <&wifi_pwrseq>; ++ non-removable; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&sdio1_pins>; ++ status = "okay"; ++ ++ wifi@1 { ++ compatible = "brcm,bcm4329-fmac"; ++ reg = <1>; ++ }; ++}; ++ ++&spi2 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&spi2_pins>; ++ status = "okay"; ++ ++ spi_dev0: spi@0 { ++ compatible = "rohm,dh2228fv"; ++ spi-max-frequency = <10000000>; ++ reg = <0>; ++ }; ++}; ++ ++&uart0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&uart0_pins>; ++ status = "okay"; ++}; ++ + &uart3 { + pinctrl-names = "default"; + pinctrl-0 = <&uart3_pins>; + status = "okay"; + }; ++ ++&usb3 { ++ dr_mode = "host"; ++ status = "okay"; ++}; +--- a/arch/riscv/boot/dts/starfive/jh7100-starfive-visionfive-v1.dts ++++ b/arch/riscv/boot/dts/starfive/jh7100-starfive-visionfive-v1.dts +@@ -18,3 +18,22 @@ + priority = <224>; + }; + }; ++ ++&gpio { ++ /* don't reset gpio mux for serial console and reset gpio */ ++ starfive,keep-gpiomux = <13 14 63>; ++}; ++ ++&i2c0 { ++ eeprom@50 { ++ compatible = "atmel,24c04"; ++ reg = <0x50>; ++ pagesize = <16>; ++ }; ++}; ++ ++&mdio { ++ phy: ethernet-phy@0 { ++ reg = <0>; ++ }; ++}; +--- a/arch/riscv/boot/dts/starfive/jh7100.dtsi ++++ b/arch/riscv/boot/dts/starfive/jh7100.dtsi +@@ -6,7 +6,9 @@ + + /dts-v1/; + #include <dt-bindings/clock/starfive-jh7100.h> ++#include <dt-bindings/clock/starfive-jh7100-audio.h> + #include <dt-bindings/reset/starfive-jh7100.h> ++#include <dt-bindings/reset/starfive-jh7100-audio.h> + + / { + compatible = "starfive,jh7100"; +@@ -135,6 +137,13 @@ + clock-frequency = <0>; + }; + ++ /* gmac device configuration */ ++ stmmac_axi_setup: stmmac-axi-config { ++ snps,wr_osr_lmt = <0xf>; ++ snps,rd_osr_lmt = <0xf>; ++ snps,blen = <256 128 64 32 0 0 0>; ++ }; ++ + soc { + compatible = "simple-bus"; + interrupt-parent = <&plic>; +@@ -143,6 +152,24 @@ + dma-noncoherent; + ranges; + ++ dtim: dtim@1000000 { ++ compatible = "starfive,dtim0"; ++ reg = <0x0 0x1000000 0x0 0x2000>; ++ reg-names = "mem"; ++ }; ++ ++ itim0: itim@1808000 { ++ compatible = "starfive,itim0"; ++ reg = <0x0 0x1808000 0x0 0x8000>; ++ reg-names = "mem"; ++ }; ++ ++ itim1: itim@1820000 { ++ compatible = "starfive,itim0"; ++ reg = <0x0 0x1820000 0x0 0x8000>; ++ reg-names = "mem"; ++ }; ++ + clint: clint@2000000 { + compatible = "starfive,jh7100-clint", "sifive,clint0"; + reg = <0x0 0x2000000 0x0 0x10000>; +@@ -172,6 +199,151 @@ + riscv,ndev = <133>; + }; + ++ gmac: ethernet@10020000 { ++ compatible = "starfive,jh7100-dwmac", "snps,dwmac"; ++ reg = <0x0 0x10020000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_GMAC_ROOT_DIV>, ++ <&clkgen JH7100_CLK_GMAC_AHB>, ++ <&clkgen JH7100_CLK_GMAC_PTP_REF>, ++ <&clkgen JH7100_CLK_GMAC_TX_INV>, ++ <&clkgen JH7100_CLK_GMAC_GTX>; ++ clock-names = "stmmaceth", "pclk", "ptp_ref", "tx", "gtx"; ++ resets = <&rstgen JH7100_RSTN_GMAC_AHB>; ++ reset-names = "ahb"; ++ interrupts = <6>, <7>; ++ interrupt-names = "macirq", "eth_wake_irq"; ++ max-frame-size = <9000>; ++ snps,multicast-filter-bins = <0>; ++ snps,perfect-filter-entries = <128>; ++ starfive,syscon = <&sysmain 0x70 0>; ++ rx-fifo-depth = <32768>; ++ tx-fifo-depth = <16384>; ++ snps,axi-config = <&stmmac_axi_setup>; ++ snps,fixed-burst; ++ /*snps,force_sf_dma_mode;*/ ++ snps,force_thresh_dma_mode; ++ snps,no-pbl-x8; ++ status = "disabled"; ++ }; ++ ++ dma2p: dma-controller@100b0000 { ++ compatible = "starfive,jh7100-axi-dma"; ++ reg = <0x0 0x100b0000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_SGDMA2P_AXI>, ++ <&clkgen JH7100_CLK_SGDMA2P_AHB>; ++ clock-names = "core-clk", "cfgr-clk"; ++ resets = <&rstgen JH7100_RSTN_SGDMA2P_AXI>, ++ <&rstgen JH7100_RSTN_SGDMA2P_AHB>; ++ reset-names = "axi", "ahb"; ++ interrupts = <2>; ++ #dma-cells = <1>; ++ dma-channels = <4>; ++ snps,dma-masters = <1>; ++ snps,data-width = <4>; ++ snps,block-size = <4096 4096 4096 4096>; ++ snps,priority = <0 1 2 3>; ++ snps,axi-max-burst-len = <128>; ++ dma-coherent; ++ }; ++ ++ crypto: crypto@100d0000 { ++ compatible = "starfive,vic-sec"; ++ reg = <0x0 0x100d0000 0x0 0x20000>, ++ <0x0 0x11800234 0x0 0xc>; ++ reg-names = "secmem", "secclk"; ++ clocks = <&clkgen JH7100_CLK_SEC_AHB>; ++ interrupts = <31>; ++ }; ++ ++ i2sadc0: i2sadc0@10400000 { ++ compatible = "snps,designware-i2sadc0"; ++ reg = <0x0 0x10400000 0x0 0x1000>; ++ clocks = <&clkgen JH7100_CLK_APB1_BUS>; ++ clock-names = "i2sclk"; ++ interrupt-parent = <&plic>; ++ #sound-dai-cells = <0>; ++ dmas = <&dma2p 28>; ++ dma-names = "rx"; ++ }; ++ ++ i2svad: i2svad@10420000 { ++ compatible = "starfive,sf-i2svad"; ++ reg = <0x0 0x10420000 0x0 0x1000> ; ++ clocks = <&audclk JH7100_AUDCLK_I2SVAD_APB>; ++ clock-names = "i2svad_apb"; ++ resets = <&audrst JH7100_AUDRSTN_I2SVAD_APB>, ++ <&audrst JH7100_AUDRSTN_I2SVAD_SRST>; ++ reset-names = "apb_i2svad", "i2svad_srst"; ++ interrupts = <60>, <61>; ++ interrupt-names = "spintr", "slintr"; ++ #sound-dai-cells = <0>; ++ }; ++ ++ pwmdac: pwmdac@10440000 { ++ compatible = "starfive,pwmdac"; ++ reg = <0x0 0x10440000 0x0 0x1000>; ++ clocks = <&clkgen JH7100_CLK_AUDIO_ROOT>, ++ <&clkgen JH7100_CLK_AUDIO_SRC>, ++ <&clkgen JH7100_CLK_AUDIO_12288>, ++ <&audclk JH7100_AUDCLK_DMA1P_AHB>, ++ <&audclk JH7100_AUDCLK_PWMDAC_APB>, ++ <&audclk JH7100_AUDCLK_DAC_MCLK>; ++ clock-names = "audio_root", ++ "audio_src", ++ "audio_12288", ++ "dma1p_ahb", ++ "pwmdac_apb", ++ "dac_mclk"; ++ resets = <&audrst JH7100_AUDRSTN_APB_BUS>, ++ <&audrst JH7100_AUDRSTN_DMA1P_AHB>, ++ <&audrst JH7100_AUDRSTN_PWMDAC_APB>; ++ reset-names = "apb_bus", "dma1p_ahb", "apb_pwmdac"; ++ dmas = <&dma2p 23>; ++ dma-names = "tx"; ++ #sound-dai-cells = <0>; ++ }; ++ ++ i2sdac0: i2sdac0@10450000 { ++ compatible = "snps,designware-i2sdac0"; ++ reg = <0x0 0x10450000 0x0 0x1000>; ++ clocks = <&audclk JH7100_AUDCLK_DAC_MCLK>, ++ <&audclk JH7100_AUDCLK_I2SDAC_BCLK>, ++ <&audclk JH7100_AUDCLK_I2SDAC_LRCLK>, ++ <&audclk JH7100_AUDCLK_I2SDAC_APB>; ++ clock-names = "dac_mclk", "i2sdac0_bclk", "i2sdac0_lrclk", "i2sdac_apb"; ++ resets = <&audrst JH7100_AUDRSTN_I2SDAC_APB>, ++ <&audrst JH7100_AUDRSTN_I2SDAC_SRST>; ++ reset-names = "apb_i2sdac", "i2sdac_srst"; ++ #sound-dai-cells = <0>; ++ dmas = <&dma2p 30>; ++ dma-names = "tx"; ++ }; ++ ++ i2sdac1: i2sdac1@10460000 { ++ compatible = "snps,designware-i2sdac1"; ++ reg = <0x0 0x10460000 0x0 0x1000>; ++ clocks = <&audclk JH7100_AUDCLK_DAC_MCLK>, ++ <&audclk JH7100_AUDCLK_I2S1_BCLK>, ++ <&audclk JH7100_AUDCLK_I2S1_LRCLK>, ++ <&audclk JH7100_AUDCLK_I2S1_APB>; ++ clock-names = "dac_mclk", "i2sdac1_bclk", "i2sdac1_lrclk", "i2s1_apb"; ++ resets = <&audrst JH7100_AUDRSTN_I2S1_APB>, ++ <&audrst JH7100_AUDRSTN_I2S1_SRST>; ++ #sound-dai-cells = <0>; ++ dmas = <&dma2p 31>; ++ dma-names = "tx"; ++ }; ++ ++ i2sdac16k: i2sdac16k@10470000 { ++ compatible = "snps,designware-i2sdac16k"; ++ reg = <0x0 0x10470000 0x0 0x1000>; ++ clocks = <&clkgen JH7100_CLK_APB1_BUS>; ++ clock-names = "i2sclk"; ++ #sound-dai-cells = <0>; ++ dmas = <&dma2p 29>; ++ dma-names = "tx"; ++ }; ++ + audclk: clock-controller@10480000 { + compatible = "starfive,jh7100-audclk"; + reg = <0x0 0x10480000 0x0 0x10000>; +@@ -214,6 +386,82 @@ + status = "disabled"; + }; + ++ spdif_transmitter: spdif-transmitter { ++ compatible = "linux,spdif-dit"; ++ #sound-dai-cells = <0>; ++ }; ++ ++ spdif_receiver: spdif-receiver { ++ compatible = "linux,spdif-dir"; ++ #sound-dai-cells = <0>; ++ }; ++ ++ pwmdac_codec: pwmdac-transmitter { ++ compatible = "linux,pwmdac-dit"; ++ #sound-dai-cells = <0>; ++ }; ++ ++ dmic_codec: dmic { ++ compatible = "dmic-codec"; ++ #sound-dai-cells = <0>; ++ }; ++ ++ sound: snd-card { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "Starfive-Multi-Sound-Card"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ /* pwmdac */ ++ simple-audio-card,dai-link@0 { ++ reg = <0>; ++ status = "okay"; ++ format = "left_j"; ++ bitclock-master = <&sndcpu0>; ++ frame-master = <&sndcpu0>; ++ ++ sndcpu0: cpu { ++ sound-dai = <&pwmdac>; ++ }; ++ ++ codec { ++ sound-dai = <&pwmdac_codec>; ++ }; ++ }; ++ }; ++ ++ usb3: usb@104c0000 { ++ compatible = "cdns,usb3"; ++ reg = <0x0 0x104c0000 0x0 0x10000>, // memory area for HOST registers ++ <0x0 0x104d0000 0x0 0x10000>, // memory area for DEVICE registers ++ <0x0 0x104e0000 0x0 0x10000>; // memory area for OTG/DRD registers ++ reg-names = "otg", "xhci", "dev"; ++ interrupts = <44>, <52>, <43>; ++ interrupt-names = "host", "peripheral", "otg"; ++ phy-names = "cdns3,usb3-phy", "cdns3,usb2-phy"; ++ maximum-speed = "super-speed"; ++ status = "disabled"; ++ }; ++ ++ dma1p: dma-controller@10500000 { ++ compatible = "starfive,jh7100-axi-dma"; ++ reg = <0x0 0x10500000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_SGDMA1P_AXI>, ++ <&clkgen JH7100_CLK_SGDMA1P_BUS>; ++ clock-names = "core-clk", "cfgr-clk"; ++ resets = <&rstgen JH7100_RSTN_DMA1P_AXI>, ++ <&rstgen JH7100_RSTN_SGDMA1P_AXI>; ++ reset-names = "axi", "ahb"; ++ interrupts = <1>; ++ #dma-cells = <1>; ++ dma-channels = <16>; ++ snps,dma-masters = <1>; ++ snps,data-width = <3>; ++ snps,block-size = <4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096>; ++ snps,priority = <0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15>; ++ snps,axi-max-burst-len = <64>; ++ }; ++ + clkgen: clock-controller@11800000 { + compatible = "starfive,jh7100-clkgen"; + reg = <0x0 0x11800000 0x0 0x10000>; +@@ -222,12 +470,93 @@ + #clock-cells = <1>; + }; + ++ otp: otp@11810000 { ++ compatible = "starfive,fu740-otp"; ++ reg = <0x0 0x11810000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_OTP_APB>; ++ fuse-count = <0x200>; ++ }; ++ + rstgen: reset-controller@11840000 { + compatible = "starfive,jh7100-reset"; + reg = <0x0 0x11840000 0x0 0x10000>; + #reset-cells = <1>; + }; + ++ sysmain: syscon@11850000 { ++ compatible = "starfive,jh7100-sysmain", "syscon"; ++ reg = <0x0 0x11850000 0x0 0x10000>; ++ }; ++ ++ qspi: spi@11860000 { ++ compatible = "cdns,qspi-nor"; ++ reg = <0x0 0x11860000 0x0 0x10000>, ++ <0x0 0x20000000 0x0 0x20000000>; ++ clocks = <&clkgen JH7100_CLK_QSPI_AHB>; ++ interrupts = <3>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ cdns,fifo-depth = <256>; ++ cdns,fifo-width = <4>; ++ cdns,trigger-address = <0x0>; ++ spi-max-frequency = <250000000>; ++ status = "disabled"; ++ }; ++ ++ uart0: serial@11870000 { ++ compatible = "starfive,jh7100-hsuart", "snps,dw-apb-uart"; ++ reg = <0x0 0x11870000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_UART0_CORE>, ++ <&clkgen JH7100_CLK_UART0_APB>; ++ clock-names = "baudclk", "apb_pclk"; ++ resets = <&rstgen JH7100_RSTN_UART0_APB>; ++ interrupts = <92>; ++ reg-io-width = <4>; ++ reg-shift = <2>; ++ status = "disabled"; ++ }; ++ ++ uart1: serial@11880000 { ++ compatible = "starfive,jh7100-hsuart", "snps,dw-apb-uart"; ++ reg = <0x0 0x11880000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_UART1_CORE>, ++ <&clkgen JH7100_CLK_UART1_APB>; ++ clock-names = "baudclk", "apb_pclk"; ++ resets = <&rstgen JH7100_RSTN_UART1_APB>; ++ interrupts = <93>; ++ reg-io-width = <4>; ++ reg-shift = <2>; ++ status = "disabled"; ++ }; ++ ++ spi0: spi@11890000 { ++ compatible = "snps,dw-apb-ssi"; ++ reg = <0x0 0x11890000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_SPI0_CORE>, ++ <&clkgen JH7100_CLK_SPI0_APB>; ++ clock-names = "ssi_clk", "pclk"; ++ resets = <&rstgen JH7100_RSTN_SPI0_APB>; ++ reset-names = "spi"; ++ interrupts = <94>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ status = "disabled"; ++ }; ++ ++ spi1: spi@118a0000 { ++ compatible = "snps,dw-apb-ssi"; ++ reg = <0x0 0x118a0000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_SPI1_CORE>, ++ <&clkgen JH7100_CLK_SPI1_APB>; ++ clock-names = "ssi_clk", "pclk"; ++ resets = <&rstgen JH7100_RSTN_SPI1_APB>; ++ reset-names = "spi"; ++ interrupts = <95>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ status = "disabled"; ++ }; ++ + i2c0: i2c@118b0000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x118b0000 0x0 0x10000>; +@@ -254,6 +583,41 @@ + status = "disabled"; + }; + ++ trng: trng@118d0000 { ++ compatible = "starfive,vic-rng"; ++ reg = <0x0 0x118d0000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_TRNG_APB>; ++ interrupts = <98>; ++ }; ++ ++ vpu_enc: vpu_enc@118e0000 { ++ compatible = "cm,cm521-vpu"; ++ reg = <0x0 0x118e0000 0x0 0x4000>; ++ reg-names = "control"; ++ clocks = <&clkgen JH7100_CLK_VP6_CORE>; ++ clock-names = "vcodec"; ++ interrupts = <26>; ++ }; ++ ++ vpu_dec: vpu_dec@118f0000 { ++ compatible = "c&m,cm511-vpu"; ++ reg = <0 0x118f0000 0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_VP6_CORE>; ++ clock-names = "vcodec"; ++ interrupts = <23>; ++ //memory-region = <&vpu_reserved>; ++ }; ++ ++ jpu: coadj12@11900000 { ++ compatible = "cm,codaj12-jpu-1"; ++ reg = <0x0 0x11900000 0x0 0x300>; ++ reg-names = "control"; ++ clocks = <&clkgen JH7100_CLK_JPEG_APB>; ++ clock-names = "jpege"; ++ interrupts = <24>; ++ memory-region = <&jpu_reserved>; ++ }; ++ + gpio: pinctrl@11910000 { + compatible = "starfive,jh7100-pinctrl"; + reg = <0x0 0x11910000 0x0 0x10000>, +@@ -268,6 +632,86 @@ + #interrupt-cells = <2>; + }; + ++ nvdla@11940000 { ++ compatible = "nvidia,nvdla_os_initial"; ++ interrupts = <22>; ++ memory-region = <&nvdla_reserved>; ++ reg = <0x0 0x11940000 0x0 0x40000>; ++ status = "okay"; ++ }; ++ ++ display: display-subsystem { ++ compatible = "starfive,display-subsystem"; ++ dma-coherent; ++ status = "disabled"; ++ }; ++ ++ encoder: display-encoder { ++ compatible = "starfive,display-encoder"; ++ status = "disabled"; ++ }; ++ ++ crtc: crtc@12000000 { ++ compatible = "starfive,jh7100-crtc"; ++ reg = <0x0 0x12000000 0x0 0x10000>, ++ <0x0 0x12040000 0x0 0x10000>, ++ <0x0 0x12080000 0x0 0x10000>, ++ <0x0 0x120c0000 0x0 0x10000>, ++ <0x0 0x12240000 0x0 0x10000>, ++ <0x0 0x12250000 0x0 0x10000>, ++ <0x0 0x12260000 0x0 0x10000>; ++ reg-names = "lcdc", "vpp0", "vpp1", "vpp2", "clk", "rst", "sys"; ++ clocks = <&clkgen JH7100_CLK_DISP_AXI>, <&clkgen JH7100_CLK_VOUT_SRC>; ++ clock-names = "disp_axi", "vout_src"; ++ resets = <&rstgen JH7100_RSTN_DISP_AXI>, <&rstgen JH7100_RSTN_VOUT_SRC>; ++ reset-names = "disp_axi", "vout_src"; ++ interrupts = <101>, <103>; ++ interrupt-names = "lcdc_irq", "vpp1_irq"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ status = "disabled"; ++ ++ pp1 { ++ pp-id = <1>; ++ fifo-out; ++ //sys-bus-out; ++ src-format = <11>; //<COLOR_RGB565>; ++ src-width = <1920>; ++ src-height = <1080>; ++ dst-format = <7>; //<COLOR_RGB888_ARGB>; ++ dst-width = <1920>; ++ dst-height = <1080>; ++ }; ++ }; ++ ++ spi2: spi@12410000 { ++ compatible = "snps,dw-apb-ssi"; ++ reg = <0x0 0x12410000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_SPI2_CORE>, ++ <&clkgen JH7100_CLK_SPI2_APB>; ++ clock-names = "ssi_clk", "pclk"; ++ resets = <&rstgen JH7100_RSTN_SPI2_APB>; ++ reset-names = "spi"; ++ interrupts = <70>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ status = "disabled"; ++ }; ++ ++ spi3: spi@12420000 { ++ compatible = "snps,dw-apb-ssi"; ++ reg = <0x0 0x12420000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_SPI3_CORE>, ++ <&clkgen JH7100_CLK_SPI3_APB>; ++ clock-names = "ssi_clk", "pclk"; ++ resets = <&rstgen JH7100_RSTN_SPI3_APB>; ++ reset-names = "spi"; ++ interrupts = <71>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ status = "disabled"; ++ }; ++ + uart2: serial@12430000 { + compatible = "starfive,jh7100-uart", "snps,dw-apb-uart"; + reg = <0x0 0x12430000 0x0 0x10000>; +@@ -341,5 +785,64 @@ + reset-names = "sense", "bus"; + #thermal-sensor-cells = <0>; + }; ++ ++ ptc: pwm@12490000 { ++ compatible = "starfive,pwm0"; ++ reg = <0x0 0x12490000 0x0 0x10000>; ++ clocks = <&clkgen JH7100_CLK_PWM_APB>; ++ resets = <&rstgen JH7100_RSTN_PWM_APB>; ++ #pwm-cells = <3>; ++ sifive,npwm = <8>; ++ status = "disabled"; ++ }; ++ ++ thermal-zones { ++ cpu-thermal { ++ polling-delay-passive = <250>; ++ polling-delay = <15000>; ++ ++ thermal-sensors = <&sfctemp>; ++ ++ cooling-maps { ++ }; ++ ++ trips { ++ cpu_alert0: cpu_alert0 { ++ /* milliCelsius */ ++ temperature = <75000>; ++ hysteresis = <2000>; ++ type = "passive"; ++ }; ++ ++ cpu_crit: cpu_crit { ++ /* milliCelsius */ ++ temperature = <90000>; ++ hysteresis = <2000>; ++ type = "critical"; ++ }; ++ }; ++ }; ++ }; ++ ++ xrp@f0000000 { ++ compatible = "cdns,xrp"; ++ reg = <0x0 0xf0000000 0x0 0x01ffffff>, ++ <0x10 0x72000000 0x0 0x00001000>, ++ <0x10 0x72001000 0x0 0x00fff000>, ++ <0x0 0x124b0000 0x0 0x00010000>; ++ clocks = <&clkgen JH7100_CLK_VP6_CORE>; ++ interrupts = <27>, <28>; ++ firmware-name = "vp6_elf"; ++ dsp-irq = <19 20>; ++ dsp-irq-src = <0x20 0x21>; ++ intc-irq-mode = <1>; ++ intc-irq = <0 1>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x40000000 0x0 0x40000000 0x01000000>, ++ <0xb0000000 0x10 0x70000000 0x3000000>; ++ dsp@0 { ++ }; ++ }; + }; + }; |