diff -Naur a/drivers/clk/meson/clk-mux.c b/drivers/clk/meson/clk-mux.c --- a/drivers/clk/meson/clk-mux.c 1970-01-01 08:00:00.000000000 +0800 +++ b/drivers/clk/meson/clk-mux.c 2023-02-23 17:02:04.947750903 +0800 @@ -0,0 +1,214 @@ +/* + * drivers/amlogic/clk/clk-mux.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clk-mux.h" + + +#define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw) + +static inline u32 clk_mux_readl(struct clk_mux *mux) +{ + if (mux->flags & CLK_MUX_BIG_ENDIAN) + return ioread32be(mux->reg); + + return readl(mux->reg); +} + +static inline void clk_mux_writel(struct clk_mux *mux, u32 val) +{ + if (mux->flags & CLK_MUX_BIG_ENDIAN) + iowrite32be(val, mux->reg); + else + writel(val, mux->reg); +} + + +static u8 meson_clk_mux_get_parent(struct clk_hw *hw) +{ + struct clk_mux *mux = to_clk_mux(hw); + int num_parents = clk_hw_get_num_parents(hw); + u32 val; + + /* + * FIXME need a mux-specific flag to determine if val is bitwise or + * numeric. e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges + * from 0x1 to 0x7 (index starts at one) + * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so + * val = 0x4 really means "bit 2, index starts at bit 0" + */ + val = clk_mux_readl(mux) >> mux->shift; + val &= mux->mask; + + if (mux->table) { + int i; + + for (i = 0; i < num_parents; i++) + if (mux->table[i] == val) + return i; + return -EINVAL; + } + + if (val && (mux->flags & CLK_MUX_INDEX_BIT)) + val = ffs(val) - 1; + + if (val && (mux->flags & CLK_MUX_INDEX_ONE)) + val--; + + if (val >= num_parents) + return -EINVAL; + + return val; +} + +static int meson_clk_mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_mux *mux = to_clk_mux(hw); + u32 val; + unsigned long flags = 0; + + if (mux->table) { + index = mux->table[index]; + } else { + if (mux->flags & CLK_MUX_INDEX_BIT) + index = (1 << ffs(index)); + + if (mux->flags & CLK_MUX_INDEX_ONE) + index++; + } + + if (mux->lock) + spin_lock_irqsave(mux->lock, flags); + else + __acquire(mux->lock); + + if (mux->flags & CLK_MUX_HIWORD_MASK) { + val = mux->mask << (mux->shift + 16); + } else { + val = clk_mux_readl(mux); + val &= ~(mux->mask << mux->shift); + } + + val |= index << mux->shift; + clk_mux_writel(mux, val); + + if (mux->lock) + spin_unlock_irqrestore(mux->lock, flags); + else + __release(mux->lock); + + return 0; +} + +static unsigned long meson_clk_mux_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_hw *parent_hw; + u32 index = 0; + unsigned long new_parent_rate; + + index = meson_clk_mux_get_parent(hw); + + parent_hw = clk_hw_get_parent_by_index(hw, index); + new_parent_rate = clk_hw_get_rate(parent_hw); + if (new_parent_rate != parent_rate) + clk_set_parent(hw->clk, parent_hw->clk); + + return new_parent_rate; +} + +int meson_clk_mux_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_hw *parent, *best_parent = NULL; + int i, num_parents, ret; + unsigned long best = 0; + struct clk_rate_request parent_req = *req; + struct clk_mux *mux = to_clk_mux(hw); + + num_parents = clk_hw_get_num_parents(hw); + + if ((num_parents == 2) && (mux->flags == CLK_PARENT_ALTERNATE)) { + i = meson_clk_mux_get_parent(hw); + i = (i + 1) % 2; + + best_parent = clk_hw_get_parent_by_index(hw, i); + best = clk_hw_get_rate(best_parent); + if (best != parent_req.rate) { + ret = clk_set_rate(best_parent->clk, parent_req.rate); + if (ret) + pr_err("Fail! Can not set to %lu, cur rate: %lu\n", + parent_req.rate, best); + else { + best = clk_hw_get_rate(best_parent); + pr_debug("success set parent %s rate to %lu\n", + clk_hw_get_name(best_parent), best); + if (!(clk_hw_get_flags(hw) & + CLK_SET_RATE_UNGATE)) { + clk_prepare(best_parent->clk); + clk_enable(best_parent->clk); + } + } + } + } else { + for (i = 0; i < num_parents; i++) { + parent = clk_hw_get_parent_by_index(hw, i); + if (!parent) + continue; + + if (mux->flags & CLK_SET_RATE_PARENT) { + parent_req = *req; + ret = __clk_determine_rate(parent, &parent_req); + if (ret) + continue; + } else { + parent_req.rate = clk_hw_get_rate(parent); + } + } + } + + if (!best_parent) + return -EINVAL; + + if (best_parent) + req->best_parent_hw = best_parent; + + req->best_parent_rate = best; + req->rate = best; + + return 0; +} + +const struct clk_ops meson_clk_mux_ops = { + .get_parent = meson_clk_mux_get_parent, + .set_parent = meson_clk_mux_set_parent, + .determine_rate = meson_clk_mux_determine_rate, + .recalc_rate = meson_clk_mux_recalc_rate, +}; + +const struct clk_ops meson_clk_mux_ro_ops = { + .get_parent = meson_clk_mux_get_parent, +}; diff -Naur a/drivers/clk/meson/clk-mux.h b/drivers/clk/meson/clk-mux.h --- a/drivers/clk/meson/clk-mux.h 1970-01-01 08:00:00.000000000 +0800 +++ b/drivers/clk/meson/clk-mux.h 2023-02-23 17:02:04.947750903 +0800 @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019 BayLibre, SAS. + * Author: Jerome Brunet + */ + +#ifndef __MESON_CLK_MUX_H +#define __MESON_CLK_MUX_H + +#include +#include +#include "parm.h" + +#define CLK_PARENT_ALTERNATE BIT(5) + +extern const struct clk_ops meson_clk_mux_ops; + + +#endif /* __MESON_CLK_MUX_H */ diff -Naur a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c --- a/drivers/clk/meson/clk-pll.c 2022-12-19 17:13:11.809508587 +0800 +++ b/drivers/clk/meson/clk-pll.c 2023-02-23 17:02:04.947750903 +0800 @@ -283,6 +283,8 @@ delay--; } while (delay > 0); + pr_warn("%s: pll %s did not lock\n", __func__, clk_hw_get_name(hw)); + return -ETIMEDOUT; } @@ -339,6 +341,9 @@ /* Enable the pll */ meson_parm_write(clk->map, &pll->en, 1); + /* Reset delay */ + udelay(pll->rst_delay_us); + /* Take the pll out reset */ meson_parm_write(clk->map, &pll->rst, 0); diff -Naur a/drivers/clk/meson/clk-pll.h b/drivers/clk/meson/clk-pll.h --- a/drivers/clk/meson/clk-pll.h 2022-12-19 17:13:11.809508587 +0800 +++ b/drivers/clk/meson/clk-pll.h 2023-02-23 17:02:04.947750903 +0800 @@ -41,6 +41,7 @@ const struct pll_params_table *table; const struct pll_mult_range *range; u8 flags; + u32 rst_delay_us; }; extern const struct clk_ops meson_clk_pll_ro_ops; diff -Naur a/drivers/clk/meson/g12a.c b/drivers/clk/meson/g12a.c --- a/drivers/clk/meson/g12a.c 2022-12-19 17:13:11.813508635 +0800 +++ b/drivers/clk/meson/g12a.c 2023-02-23 17:02:04.951750958 +0800 @@ -15,6 +15,7 @@ #include #include #include +#include #include "clk-mpll.h" #include "clk-pll.h" @@ -23,8 +24,9 @@ #include "vid-pll-div.h" #include "meson-eeclk.h" #include "g12a.h" +#include "g12a-vcodec-clk.h" -static DEFINE_SPINLOCK(meson_clk_lock); +/*static*/ DEFINE_SPINLOCK(meson_clk_lock); static struct clk_regmap g12a_fixed_pll_dco = { .data = &(struct meson_clk_pll_data){ @@ -1602,20 +1604,21 @@ }; static const struct pll_mult_range g12a_gp0_pll_mult_range = { - .min = 125, - .max = 255, + .min = 132, /* Force M to 132 */ + .max = 132, }; /* * Internal gp0 pll emulation configuration parameters */ static const struct reg_sequence g12a_gp0_init_regs[] = { + { .reg = HHI_GP0_PLL_CNTL0, .def = 0x08000000 }, { .reg = HHI_GP0_PLL_CNTL1, .def = 0x00000000 }, { .reg = HHI_GP0_PLL_CNTL2, .def = 0x00000000 }, - { .reg = HHI_GP0_PLL_CNTL3, .def = 0x48681c00 }, - { .reg = HHI_GP0_PLL_CNTL4, .def = 0x33771290 }, + { .reg = HHI_GP0_PLL_CNTL3, .def = 0x6a285c00}, + { .reg = HHI_GP0_PLL_CNTL4, .def = 0x65771290}, { .reg = HHI_GP0_PLL_CNTL5, .def = 0x39272000 }, - { .reg = HHI_GP0_PLL_CNTL6, .def = 0x56540000 }, + { .reg = HHI_GP0_PLL_CNTL6, .def = 0x56540000, .delay_us = 10 }, }; static struct clk_regmap g12a_gp0_pll_dco = { @@ -1653,6 +1656,7 @@ .range = &g12a_gp0_pll_mult_range, .init_regs = g12a_gp0_init_regs, .init_count = ARRAY_SIZE(g12a_gp0_init_regs), + .rst_delay_us = 100, }, .hw.init = &(struct clk_init_data){ .name = "gp0_pll_dco", @@ -3657,6 +3661,86 @@ }, }; +/* MIPI DSI Host Clocks */ + +static const struct clk_hw *g12a_mipi_dsi_pxclk_parent_hws[] = { + &g12a_vid_pll.hw, + &g12a_gp0_pll.hw, + &g12a_hifi_pll.hw, + &g12a_mpll1.hw, + &g12a_fclk_div2.hw, + &g12a_fclk_div2p5.hw, + &g12a_fclk_div3.hw, + &g12a_fclk_div7.hw, +}; + +static struct clk_regmap g12a_mipi_dsi_pxclk_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = HHI_MIPIDSI_PHY_CLK_CNTL, + .mask = 0x7, + .shift = 12, + .flags = CLK_MUX_ROUND_CLOSEST, + }, + .hw.init = &(struct clk_init_data){ + .name = "mipi_dsi_pxclk_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = g12a_mipi_dsi_pxclk_parent_hws, + .num_parents = ARRAY_SIZE(g12a_mipi_dsi_pxclk_parent_hws), + .flags = CLK_SET_RATE_NO_REPARENT, + }, +}; + +static struct clk_regmap g12a_mipi_dsi_pxclk_div = { + .data = &(struct clk_regmap_div_data){ + .offset = HHI_MIPIDSI_PHY_CLK_CNTL, + .shift = 0, + .width = 7, + }, + .hw.init = &(struct clk_init_data){ + .name = "mipi_dsi_pxclk_div", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &g12a_mipi_dsi_pxclk_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap g12a_mipi_dsi_pxclk = { + .data = &(struct clk_regmap_gate_data){ + .offset = HHI_MIPIDSI_PHY_CLK_CNTL, + .bit_idx = 8, + }, + .hw.init = &(struct clk_init_data) { + .name = "mipi_dsi_pxclk", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &g12a_mipi_dsi_pxclk_div.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +/* GPIO 24M */ +static struct clk_regmap g12a_24m = { + .data = &(struct clk_regmap_gate_data){ + .offset = HHI_XTAL_DIVN_CNTL, + .bit_idx = 6, + }, + .hw.init = &(struct clk_init_data){ + .name = "g12a_24m", + .ops = &clk_regmap_gate_ops, + .parent_names = (const char *[]){ "xtal" }, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "xtal", + }, + .num_parents = 1, + .flags = (CLK_SET_RATE_PARENT | CLK_IS_CRITICAL), + }, +}; + /* HDMI Clocks */ static const struct clk_parent_data g12a_hdmi_parent_data[] = { @@ -4099,6 +4183,96 @@ }, }; +static const char * const media_parent_names[] = { "xtal", + "gp0_pll", "hifi_pll", "fclk_div2p5", "fclk_div3", "fclk_div4", + "fclk_div5", "fclk_div7"}; + +static const char * const media_parent_names_mipi[] = { "xtal", + "gp0_pll", "mpll1", "mpll2", "fclk_div3", "fclk_div4", + "fclk_div5", "fclk_div7"}; + +static struct clk_mux cts_mipi_isp_clk_mux = { + .reg = (void *)HHI_MIPI_ISP_CLK_CNTL, + .mask = 0x7, + .shift = 9, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "cts_mipi_isp_clk_mux", + .ops = &clk_mux_ops, + .parent_names = media_parent_names, + .num_parents = 8, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_divider cts_mipi_isp_clk_div = { + .reg = (void *)HHI_MIPI_ISP_CLK_CNTL, + .shift = 0, + .width = 7, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "cts_mipi_isp_clk_div", + .ops = &clk_divider_ops, + .parent_names = (const char *[]){ "cts_mipi_isp_clk_mux" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_gate cts_mipi_isp_clk_gate = { + .reg = (void *)HHI_MIPI_ISP_CLK_CNTL, + .bit_idx = 8, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data) { + .name = "cts_mipi_isp_clk_gate", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "cts_mipi_isp_clk_div" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_mux cts_mipi_csi_phy_clk0_mux = { + .reg = (void *)HHI_MIPI_CSI_PHY_CLK_CNTL, + .mask = 0x7, + .shift = 9, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "cts_mipi_csi_phy_clk0_mux", + .ops = &clk_mux_ops, + .parent_names = media_parent_names_mipi, + .num_parents = 8, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_divider cts_mipi_csi_phy_clk0_div = { + .reg = (void *)HHI_MIPI_CSI_PHY_CLK_CNTL, + .shift = 0, + .width = 7, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "cts_mipi_csi_phy_clk0_div", + .ops = &clk_divider_ops, + .parent_names = (const char *[]){ "cts_mipi_csi_phy_clk0_mux" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_gate cts_mipi_csi_phy_clk0_gate = { + .reg = (void *)HHI_MIPI_CSI_PHY_CLK_CNTL, + .bit_idx = 8, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data) { + .name = "cts_mipi_csi_phy_clk0_gate", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "cts_mipi_csi_phy_clk0_div" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + #define MESON_GATE(_name, _reg, _bit) \ MESON_PCLK(_name, _reg, _bit, &g12a_clk81.hw) @@ -4402,6 +4576,9 @@ [CLKID_SPICC1_SCLK_SEL] = &g12a_spicc1_sclk_sel.hw, [CLKID_SPICC1_SCLK_DIV] = &g12a_spicc1_sclk_div.hw, [CLKID_SPICC1_SCLK] = &g12a_spicc1_sclk.hw, + [CLKID_MIPI_DSI_PXCLK_SEL] = &g12a_mipi_dsi_pxclk_sel.hw, + [CLKID_MIPI_DSI_PXCLK_DIV] = &g12a_mipi_dsi_pxclk_div.hw, + [CLKID_MIPI_DSI_PXCLK] = &g12a_mipi_dsi_pxclk.hw, [NR_CLKS] = NULL, }, .num = NR_CLKS, @@ -4657,6 +4834,16 @@ [CLKID_SPICC1_SCLK_SEL] = &g12a_spicc1_sclk_sel.hw, [CLKID_SPICC1_SCLK_DIV] = &g12a_spicc1_sclk_div.hw, [CLKID_SPICC1_SCLK] = &g12a_spicc1_sclk.hw, + [CLKID_NNA_AXI_CLK_SEL] = &sm1_nna_axi_clk_sel.hw, + [CLKID_NNA_AXI_CLK_DIV] = &sm1_nna_axi_clk_div.hw, + [CLKID_NNA_AXI_CLK] = &sm1_nna_axi_clk.hw, + [CLKID_NNA_CORE_CLK_SEL] = &sm1_nna_core_clk_sel.hw, + [CLKID_NNA_CORE_CLK_DIV] = &sm1_nna_core_clk_div.hw, + [CLKID_NNA_CORE_CLK] = &sm1_nna_core_clk.hw, + [CLKID_MIPI_DSI_PXCLK_SEL] = &g12a_mipi_dsi_pxclk_sel.hw, + [CLKID_MIPI_DSI_PXCLK_DIV] = &g12a_mipi_dsi_pxclk_div.hw, + [CLKID_MIPI_DSI_PXCLK] = &g12a_mipi_dsi_pxclk.hw, + [CLKID_24M] = &g12a_24m.hw, [NR_CLKS] = NULL, }, .num = NR_CLKS, @@ -4903,6 +5090,9 @@ [CLKID_NNA_CORE_CLK_SEL] = &sm1_nna_core_clk_sel.hw, [CLKID_NNA_CORE_CLK_DIV] = &sm1_nna_core_clk_div.hw, [CLKID_NNA_CORE_CLK] = &sm1_nna_core_clk.hw, + [CLKID_MIPI_DSI_PXCLK_SEL] = &g12a_mipi_dsi_pxclk_sel.hw, + [CLKID_MIPI_DSI_PXCLK_DIV] = &g12a_mipi_dsi_pxclk_div.hw, + [CLKID_MIPI_DSI_PXCLK] = &g12a_mipi_dsi_pxclk.hw, [NR_CLKS] = NULL, }, .num = NR_CLKS, @@ -5150,6 +5340,10 @@ &sm1_nna_core_clk_sel, &sm1_nna_core_clk_div, &sm1_nna_core_clk, + &g12a_mipi_dsi_pxclk_sel, + &g12a_mipi_dsi_pxclk_div, + &g12a_mipi_dsi_pxclk, + &g12a_24m, }; static const struct reg_sequence g12a_init_regs[] = { @@ -5166,6 +5360,57 @@ xtal = clk_hw_get_parent_by_index(hws[CLKID_CPU_CLK_DYN1_SEL], 0); + void __iomem *clk_base; + + clk_base = of_iomap(of_get_parent(pdev->dev.of_node), 0); + if (!clk_base) { + pr_err("%s: Unable to map clk base\n", __func__); + return -1; + } + + cts_mipi_isp_clk_mux.reg = clk_base + + (unsigned long)(cts_mipi_isp_clk_mux.reg); + cts_mipi_isp_clk_gate.reg = clk_base + + (unsigned long)(cts_mipi_isp_clk_gate.reg); + cts_mipi_isp_clk_div.reg = clk_base + + (unsigned long)(cts_mipi_isp_clk_div.reg); + + cts_mipi_csi_phy_clk0_mux.reg = clk_base + + (unsigned long)(cts_mipi_csi_phy_clk0_mux.reg); + cts_mipi_csi_phy_clk0_div.reg = clk_base + + (unsigned long)(cts_mipi_csi_phy_clk0_div.reg); + cts_mipi_csi_phy_clk0_gate.reg = clk_base + + (unsigned long)(cts_mipi_csi_phy_clk0_gate.reg); + + // register composite clks + hws[CLKID_MIPI_ISP_CLK_COMP] = clk_hw_register_composite(NULL, + "cts_mipi_isp_clk_composite", + media_parent_names, 8, + &cts_mipi_isp_clk_mux.hw, + &clk_mux_ops, + &cts_mipi_isp_clk_div.hw, + &clk_divider_ops, + &cts_mipi_isp_clk_gate.hw, + &clk_gate_ops, 0); + if (IS_ERR(hws[CLKID_MIPI_ISP_CLK_COMP])) + panic("%s: %d register cts_mipi_isp_clk_composite error\n", + __func__, __LINE__); + + hws[CLKID_MIPI_CSI_PHY_CLK0_COMP] = clk_hw_register_composite(NULL, + "cts_mipi_csi_phy_clk0_composite", + media_parent_names_mipi, 8, + &cts_mipi_csi_phy_clk0_mux.hw, + &clk_mux_ops, + &cts_mipi_csi_phy_clk0_div.hw, + &clk_divider_ops, + &cts_mipi_csi_phy_clk0_gate.hw, + &clk_gate_ops, 0); + if (IS_ERR(hws[CLKID_MIPI_CSI_PHY_CLK0_COMP])) + panic("%s: %d register cts_mipi_csi_phy_clk0_composite error\n", + __func__, __LINE__); + + meson_g12a_vcodec_init(hws, clk_base); + /* Setup clock notifier for cpu_clk_postmux0 */ g12a_cpu_clk_postmux0_nb_data.xtal = xtal; notifier_clk_name = clk_hw_get_name(&g12a_cpu_clk_postmux0.hw); diff -Naur a/drivers/clk/meson/g12a.h b/drivers/clk/meson/g12a.h --- a/drivers/clk/meson/g12a.h 2022-12-19 17:13:11.813508635 +0800 +++ b/drivers/clk/meson/g12a.h 2023-02-23 17:02:04.951750958 +0800 @@ -44,6 +44,7 @@ #define HHI_PCIE_PLL_CNTL4 0x0A8 #define HHI_PCIE_PLL_CNTL5 0x0AC #define HHI_PCIE_PLL_STS 0x0B8 +#define HHI_XTAL_DIVN_CNTL 0x0BC /* 0x2f offset in data sheet */ #define HHI_HIFI_PLL_CNTL0 0x0D8 #define HHI_HIFI_PLL_CNTL1 0x0DC #define HHI_HIFI_PLL_CNTL2 0x0E0 @@ -70,6 +71,7 @@ #define HHI_MALI_CLK_CNTL 0x1b0 #define HHI_VPU_CLKC_CNTL 0x1b4 #define HHI_VPU_CLK_CNTL 0x1bC +#define HHI_MIPI_ISP_CLK_CNTL 0x1C0 /* 0x70 offset in data sheet */ #define HHI_NNA_CLK_CNTL 0x1C8 #define HHI_HDMI_CLK_CNTL 0x1CC #define HHI_VDEC_CLK_CNTL 0x1E0 @@ -117,6 +119,7 @@ #define HHI_HDMI_PLL_CNTL4 0x330 #define HHI_HDMI_PLL_CNTL5 0x334 #define HHI_HDMI_PLL_CNTL6 0x338 +#define HHI_MIPI_CSI_PHY_CLK_CNTL 0x340 /* 0xd0 offset in data sheet */ #define HHI_SPICC_CLK_CNTL 0x3dc #define HHI_SYS1_PLL_CNTL0 0x380 #define HHI_SYS1_PLL_CNTL1 0x384 @@ -264,8 +267,9 @@ #define CLKID_NNA_AXI_CLK_DIV 263 #define CLKID_NNA_CORE_CLK_SEL 265 #define CLKID_NNA_CORE_CLK_DIV 266 +#define CLKID_MIPI_DSI_PXCLK_DIV 268 -#define NR_CLKS 268 +#define NR_CLKS 288 /* include the CLKIDs that have been made part of the DT binding */ #include diff -Naur a/drivers/clk/meson/g12a-vcodec-clk.c b/drivers/clk/meson/g12a-vcodec-clk.c --- a/drivers/clk/meson/g12a-vcodec-clk.c 1970-01-01 08:00:00.000000000 +0800 +++ b/drivers/clk/meson/g12a-vcodec-clk.c 2023-02-23 17:02:04.951750958 +0800 @@ -0,0 +1,731 @@ +/* + * drivers/amlogic/clk/g12a/g12a_clk_media.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "g12a.h" +#include "clk-mux.h" +#include "g12a-vcodec-clk.h" + + +extern spinlock_t meson_clk_lock; + + +const char *g12a_dec_parent_names[] = { "fclk_div2p5", "fclk_div3", + "fclk_div4", "fclk_div5", "fclk_div7", "hifi_pll", "gp0_pll", "xtal"}; + +/* cts_vdec_clk */ +static struct clk_mux vdec_p0_mux = { + .reg = (void *)HHI_VDEC_CLK_CNTL, + .mask = 0x7, + .shift = 9, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "vdec_p0_mux", + .ops = &clk_mux_ops, + .parent_names = g12a_dec_parent_names, + .num_parents = 8, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_divider vdec_p0_div = { + .reg = (void *)HHI_VDEC_CLK_CNTL, + .shift = 0, + .width = 7, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "vdec_p0_div", + .ops = &clk_divider_ops, + .parent_names = (const char *[]){ "vdec_p0_mux" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_gate vdec_p0_gate = { + .reg = (void *)HHI_VDEC_CLK_CNTL, + .bit_idx = 8, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data) { + .name = "vdec_p0_gate", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "vdec_p0_div" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_mux vdec_p1_mux = { + .reg = (void *)HHI_VDEC3_CLK_CNTL, + .mask = 0x7, + .shift = 9, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "vdec_p1_mux", + .ops = &clk_mux_ops, + .parent_names = g12a_dec_parent_names, + .num_parents = 8, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_divider vdec_p1_div = { + .reg = (void *)HHI_VDEC3_CLK_CNTL, + .shift = 0, + .width = 7, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "vdec_p1_div", + .ops = &clk_divider_ops, + .parent_names = (const char *[]){ "vdec_p1_mux" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_gate vdec_p1_gate = { + .reg = (void *)HHI_VDEC3_CLK_CNTL, + .bit_idx = 8, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data) { + .name = "vdec_p1_gate", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "vdec_p1_div" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_mux vdec_mux = { + .reg = (void *)HHI_VDEC3_CLK_CNTL, + .mask = 0x1, + .shift = 15, + .lock = &meson_clk_lock, + .flags = CLK_PARENT_ALTERNATE, + .hw.init = &(struct clk_init_data){ + .name = "vdec_mux", + .ops = &meson_clk_mux_ops, + .parent_names = (const char *[]){ "vdec_p0_composite", + "vdec_p1_composite"}, + .num_parents = 2, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +/* cts_hcodec_clk */ +static struct clk_mux hcodec_p0_mux = { + .reg = (void *)HHI_VDEC_CLK_CNTL, + .mask = 0x7, + .shift = 25, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "hcodec_p0_mux", + .ops = &clk_mux_ops, + .parent_names = g12a_dec_parent_names, + .num_parents = 8, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_divider hcodec_p0_div = { + .reg = (void *)HHI_VDEC_CLK_CNTL, + .shift = 16, + .width = 7, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "hcodec_p0_div", + .ops = &clk_divider_ops, + .parent_names = (const char *[]){ "hcodec_p0_mux" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_gate hcodec_p0_gate = { + .reg = (void *)HHI_VDEC_CLK_CNTL, + .bit_idx = 24, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data) { + .name = "hcodec_p0_gate", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "hcodec_p0_div" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_mux hcodec_p1_mux = { + .reg = (void *)HHI_VDEC3_CLK_CNTL, + .mask = 0x7, + .shift = 25, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "hcodec_p1_mux", + .ops = &clk_mux_ops, + .parent_names = g12a_dec_parent_names, + .num_parents = 8, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_divider hcodec_p1_div = { + .reg = (void *)HHI_VDEC3_CLK_CNTL, + .shift = 16, + .width = 7, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "hcodec_p1_div", + .ops = &clk_divider_ops, + .parent_names = (const char *[]){ "hcodec_p1_mux" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_gate hcodec_p1_gate = { + .reg = (void *)HHI_VDEC3_CLK_CNTL, + .bit_idx = 24, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data) { + .name = "hcodec_p1_gate", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "hcodec_p1_div" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_mux hcodec_mux = { + .reg = (void *)HHI_VDEC3_CLK_CNTL, + .mask = 0x1, + .shift = 31, + .lock = &meson_clk_lock, + .flags = CLK_PARENT_ALTERNATE, + .hw.init = &(struct clk_init_data){ + .name = "hcodec_mux", + .ops = &meson_clk_mux_ops, + .parent_names = (const char *[]){ "hcodec_p0_composite", + "hcodec_p1_composite"}, + .num_parents = 2, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +/* cts_hevcb_clk */ +static struct clk_mux hevc_p0_mux = { + .reg = (void *)HHI_VDEC2_CLK_CNTL, + .mask = 0x7, + .shift = 25, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "hevc_p0_mux", + .ops = &clk_mux_ops, + .parent_names = g12a_dec_parent_names, + .num_parents = 8, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_divider hevc_p0_div = { + .reg = (void *)HHI_VDEC2_CLK_CNTL, + .shift = 16, + .width = 7, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "hevc_p0_div", + .ops = &clk_divider_ops, + .parent_names = (const char *[]){ "hevc_p0_mux" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_gate hevc_p0_gate = { + .reg = (void *)HHI_VDEC2_CLK_CNTL, + .bit_idx = 24, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data) { + .name = "hevc_p0_gate", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "hevc_p0_div" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_mux hevc_p1_mux = { + .reg = (void *)HHI_VDEC4_CLK_CNTL, + .mask = 0x7, + .shift = 25, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "hevc_p1_mux", + .ops = &clk_mux_ops, + .parent_names = g12a_dec_parent_names, + .num_parents = 8, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_divider hevc_p1_div = { + .reg = (void *)HHI_VDEC4_CLK_CNTL, + .shift = 16, + .width = 7, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "hevc_p1_div", + .ops = &clk_divider_ops, + .parent_names = (const char *[]){ "hevc_p1_mux" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_gate hevc_p1_gate = { + .reg = (void *)HHI_VDEC4_CLK_CNTL, + .bit_idx = 24, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data) { + .name = "hevc_p1_gate", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "hevc_p1_div" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_mux hevc_mux = { + .reg = (void *)HHI_VDEC4_CLK_CNTL, + .mask = 0x1, + .shift = 31, + .lock = &meson_clk_lock, + .flags = CLK_PARENT_ALTERNATE, + .hw.init = &(struct clk_init_data){ + .name = "hevc_mux", + .ops = &meson_clk_mux_ops, + .parent_names = (const char *[]){ "hevc_p0_composite", + "hevc_p1_composite"}, + .num_parents = 2, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +/* cts_hevcf_clk */ +static struct clk_mux hevcf_p0_mux = { + .reg = (void *)HHI_VDEC2_CLK_CNTL, + .mask = 0x7, + .shift = 9, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "hevcf_p0_mux", + .ops = &clk_mux_ops, + .parent_names = g12a_dec_parent_names, + .num_parents = 8, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_divider hevcf_p0_div = { + .reg = (void *)HHI_VDEC2_CLK_CNTL, + .shift = 0, + .width = 7, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "hevcf_p0_div", + .ops = &clk_divider_ops, + .parent_names = (const char *[]){ "hevcf_p0_mux" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_gate hevcf_p0_gate = { + .reg = (void *)HHI_VDEC2_CLK_CNTL, + .bit_idx = 8, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data) { + .name = "hevcf_p0_gate", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "hevcf_p0_div" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_mux hevcf_p1_mux = { + .reg = (void *)HHI_VDEC4_CLK_CNTL, + .mask = 0x7, + .shift = 9, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "hevcf_p1_mux", + .ops = &clk_mux_ops, + .parent_names = g12a_dec_parent_names, + .num_parents = 8, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_divider hevcf_p1_div = { + .reg = (void *)HHI_VDEC4_CLK_CNTL, + .shift = 0, + .width = 7, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "hevcf_p1_div", + .ops = &clk_divider_ops, + .parent_names = (const char *[]){ "hevcf_p1_mux" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_gate hevcf_p1_gate = { + .reg = (void *)HHI_VDEC4_CLK_CNTL, + .bit_idx = 8, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data) { + .name = "hevcf_p1_gate", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "hevcf_p1_div" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_mux hevcf_mux = { + .reg = (void *)HHI_VDEC4_CLK_CNTL, + .mask = 0x1, + .shift = 15, + .lock = &meson_clk_lock, + .flags = CLK_PARENT_ALTERNATE, + .hw.init = &(struct clk_init_data){ + .name = "hevcf_mux", + .ops = &meson_clk_mux_ops, + .parent_names = (const char *[]){ "hevcf_p0_composite", + "hevcf_p1_composite"}, + .num_parents = 2, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static const char * const vpu_clkb_tmp_parent_names[] = { "vpu", + "fclk_div4", "fclk_div5", "fclk_div7"}; + +static struct clk_mux vpu_clkb_tmp_mux = { + .reg = (void *)HHI_VPU_CLKB_CNTL, + .mask = 0x3, + .shift = 20, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "vpu_clkb_tmp_mux", + .ops = &clk_mux_ops, + .parent_names = vpu_clkb_tmp_parent_names, + .num_parents = ARRAY_SIZE(vpu_clkb_tmp_parent_names), + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_divider vpu_clkb_tmp_div = { + .reg = (void *)HHI_VPU_CLKB_CNTL, + .shift = 16, + .width = 4, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "vpu_clkb_tmp_div", + .ops = &clk_divider_ops, + .parent_names = (const char *[]){ "vpu_clkb_tmp_mux" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_gate vpu_clkb_tmp_gate = { + .reg = (void *)HHI_VPU_CLKB_CNTL, + .bit_idx = 24, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data) { + .name = "vpu_clkb_tmp_gate", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "vpu_clkb_tmp_div" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static const char * const vpu_clkb_parent_names[] + = { "vpu_clkb_tmp_composite" }; + +static struct clk_divider vpu_clkb_div = { + .reg = (void *)HHI_VPU_CLKB_CNTL, + .shift = 0, + .width = 8, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "vpu_clkb_div", + .ops = &clk_divider_ops, + .parent_names = (const char *[]){ "vpu_clkb_tmp_composite" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_gate vpu_clkb_gate = { + .reg = (void *)HHI_VPU_CLKB_CNTL, + .bit_idx = 8, + .lock = &meson_clk_lock, + .hw.init = &(struct clk_init_data) { + .name = "vpu_clkb_gate", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "vpu_clkb_div" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +void meson_g12a_vcodec_init(struct clk_hw **clk_hws, void __iomem *clk_base) +{ + /* cts_vdec_clk */ + vdec_p0_mux.reg = clk_base + (unsigned long)(vdec_p0_mux.reg); + vdec_p0_div.reg = clk_base + (unsigned long)(vdec_p0_div.reg); + vdec_p0_gate.reg = clk_base + (unsigned long)(vdec_p0_gate.reg); + vdec_p1_mux.reg = clk_base + (unsigned long)(vdec_p1_mux.reg); + vdec_p1_div.reg = clk_base + (unsigned long)(vdec_p1_div.reg); + vdec_p1_gate.reg = clk_base + (unsigned long)(vdec_p1_gate.reg); + vdec_mux.reg = clk_base + (unsigned long)(vdec_mux.reg); + + /* cts_hcodec_clk */ + hcodec_p0_mux.reg = clk_base + (unsigned long)(hcodec_p0_mux.reg); + hcodec_p0_div.reg = clk_base + (unsigned long)(hcodec_p0_div.reg); + hcodec_p0_gate.reg = clk_base + (unsigned long)(hcodec_p0_gate.reg); + hcodec_p1_mux.reg = clk_base + (unsigned long)(hcodec_p1_mux.reg); + hcodec_p1_div.reg = clk_base + (unsigned long)(hcodec_p1_div.reg); + hcodec_p1_gate.reg = clk_base + (unsigned long)(hcodec_p1_gate.reg); + hcodec_mux.reg = clk_base + (unsigned long)(hcodec_mux.reg); + + /* cts_hevc_clk */ + hevc_p0_mux.reg = clk_base + (unsigned long)(hevc_p0_mux.reg); + hevc_p0_div.reg = clk_base + (unsigned long)(hevc_p0_div.reg); + hevc_p0_gate.reg = clk_base + (unsigned long)(hevc_p0_gate.reg); + hevc_p1_mux.reg = clk_base + (unsigned long)(hevc_p1_mux.reg); + hevc_p1_div.reg = clk_base + (unsigned long)(hevc_p1_div.reg); + hevc_p1_gate.reg = clk_base + (unsigned long)(hevc_p1_gate.reg); + hevc_mux.reg = clk_base + (unsigned long)(hevc_mux.reg); + + /* cts_hevcf_clk */ + hevcf_p0_mux.reg = clk_base + (unsigned long)(hevcf_p0_mux.reg); + hevcf_p0_div.reg = clk_base + (unsigned long)(hevcf_p0_div.reg); + hevcf_p0_gate.reg = clk_base + (unsigned long)(hevcf_p0_gate.reg); + hevcf_p1_mux.reg = clk_base + (unsigned long)(hevcf_p1_mux.reg); + hevcf_p1_div.reg = clk_base + (unsigned long)(hevcf_p1_div.reg); + hevcf_p1_gate.reg = clk_base + (unsigned long)(hevcf_p1_gate.reg); + hevcf_mux.reg = clk_base + (unsigned long)(hevcf_mux.reg); + + vpu_clkb_tmp_mux.reg = clk_base + (unsigned long)(vpu_clkb_tmp_mux.reg); + vpu_clkb_tmp_div.reg = clk_base + (unsigned long)(vpu_clkb_tmp_div.reg); + vpu_clkb_tmp_gate.reg = clk_base + (unsigned long)(vpu_clkb_tmp_gate.reg); + + vpu_clkb_div.reg = clk_base + (unsigned long)(vpu_clkb_div.reg); + vpu_clkb_gate.reg = clk_base + (unsigned long)(vpu_clkb_gate.reg); + + /* cts_vdec_clk */ + clk_hws[CLKID_VDEC_P0_COMP] = clk_hw_register_composite(NULL, + "vdec_p0_composite", + g12a_dec_parent_names, 8, + &vdec_p0_mux.hw, + &clk_mux_ops, + &vdec_p0_div.hw, + &clk_divider_ops, + &vdec_p0_gate.hw, + &clk_gate_ops, 0); + if (IS_ERR(clk_hws[CLKID_VDEC_P0_COMP])) + panic("%s: %d clk_hw_register_composite vdec_p0_composite error\n", + __func__, __LINE__); + + clk_hws[CLKID_VDEC_P1_COMP] = clk_hw_register_composite(NULL, + "vdec_p1_composite", + g12a_dec_parent_names, 8, + &vdec_p1_mux.hw, + &clk_mux_ops, + &vdec_p1_div.hw, + &clk_divider_ops, + &vdec_p1_gate.hw, + &clk_gate_ops, 0); + if (IS_ERR(clk_hws[CLKID_VDEC_P1_COMP])) + panic("%s: %d clk_hw_register_composite vdec_p1_composite error\n", + __func__, __LINE__); + + if (clk_hw_register(NULL, &vdec_mux.hw)) { + panic("%s: %d clk_hw_register vdec_mux error\n", + __func__, __LINE__); + } else { + clk_hws[CLKID_VDEC_MUX] = &vdec_mux.hw; + } + + if (IS_ERR(clk_hws[CLKID_VDEC_MUX])) + panic("%s: %d clk_hw_register vdec_mux error\n", + __func__, __LINE__); + + /* cts_hcodec_clk */ + clk_hws[CLKID_HCODEC_P0_COMP] = clk_hw_register_composite(NULL, + "hcodec_p0_composite", + g12a_dec_parent_names, 8, + &hcodec_p0_mux.hw, + &clk_mux_ops, + &hcodec_p0_div.hw, + &clk_divider_ops, + &hcodec_p0_gate.hw, + &clk_gate_ops, 0); + if (IS_ERR(clk_hws[CLKID_HCODEC_P0_COMP])) + panic("%s: %d clk_hw_register_composite hcodec_p0_composite error\n", + __func__, __LINE__); + + clk_hws[CLKID_HCODEC_P1_COMP] = clk_hw_register_composite(NULL, + "hcodec_p1_composite", + g12a_dec_parent_names, 8, + &hcodec_p1_mux.hw, + &clk_mux_ops, + &hcodec_p1_div.hw, + &clk_divider_ops, + &hcodec_p1_gate.hw, + &clk_gate_ops, 0); + if (IS_ERR(clk_hws[CLKID_HCODEC_P1_COMP])) + panic("%s: %d clk_hw_register_composite hcodec_p1_composite error\n", + __func__, __LINE__); + + if (clk_hw_register(NULL, &hcodec_mux.hw)) { + panic("%s: %d clk_hw_register hcodec_mux error\n", + __func__, __LINE__); + } else { + clk_hws[CLKID_HCODEC_MUX] = &hcodec_mux.hw; + } + + if (IS_ERR(clk_hws[CLKID_HCODEC_MUX])) + panic("%s: %d clk_hw_register hcodec_mux error\n", + __func__, __LINE__); + + /* cts_hevc_clk */ + clk_hws[CLKID_HEVC_P0_COMP] = clk_hw_register_composite(NULL, + "hevc_p0_composite", + g12a_dec_parent_names, 8, + &hevc_p0_mux.hw, + &clk_mux_ops, + &hevc_p0_div.hw, + &clk_divider_ops, + &hevc_p0_gate.hw, + &clk_gate_ops, 0); + if (IS_ERR(clk_hws[CLKID_HEVC_P0_COMP])) + panic("%s: %d clk_hw_register_composite hevc_p0_composite error\n", + __func__, __LINE__); + + clk_hws[CLKID_HEVC_P1_COMP] = clk_hw_register_composite(NULL, + "hevc_p1_composite", + g12a_dec_parent_names, 8, + &hevc_p1_mux.hw, + &clk_mux_ops, + &hevc_p1_div.hw, + &clk_divider_ops, + &hevc_p1_gate.hw, + &clk_gate_ops, 0); + if (IS_ERR(clk_hws[CLKID_HEVC_P1_COMP])) + panic("%s: %d clk_hw_register_composite hevc_p1_composite error\n", + __func__, __LINE__); + + if (clk_hw_register(NULL, &hevc_mux.hw)) { + panic("%s: %d clk_hw_register hcodec_mux error\n", + __func__, __LINE__); + } else { + clk_hws[CLKID_HEVC_MUX] = &hevc_mux.hw; + } + + if (IS_ERR(clk_hws[CLKID_HEVC_MUX])) + panic("%s: %d clk_hw_register hevc_mux error\n", + __func__, __LINE__); + + /* cts_hevcf_clk */ + clk_hws[CLKID_HEVCF_P0_COMP] = clk_hw_register_composite(NULL, + "hevcf_p0_composite", + g12a_dec_parent_names, 8, + &hevcf_p0_mux.hw, + &clk_mux_ops, + &hevcf_p0_div.hw, + &clk_divider_ops, + &hevcf_p0_gate.hw, + &clk_gate_ops, 0); + if (IS_ERR(clk_hws[CLKID_HEVCF_P0_COMP])) + panic("%s: %d clk_hw_register_composite hevcf_p0_composite error\n", + __func__, __LINE__); + + clk_hws[CLKID_HEVCF_P1_COMP] = clk_hw_register_composite(NULL, + "hevcf_p1_composite", + g12a_dec_parent_names, 8, + &hevcf_p1_mux.hw, + &clk_mux_ops, + &hevcf_p1_div.hw, + &clk_divider_ops, + &hevcf_p1_gate.hw, + &clk_gate_ops, 0); + if (IS_ERR(clk_hws[CLKID_HEVCF_P1_COMP])) + panic("%s: %d clk_hw_register_composite hevcf_p1_composite error\n", + __func__, __LINE__); + + if (clk_hw_register(NULL, &hevcf_mux.hw)) { + panic("%s: %d clk_hw_register hevcf_mux error\n", + __func__, __LINE__); + } else { + clk_hws[CLKID_HEVCF_MUX] = &hevcf_mux.hw; + } + + if (IS_ERR(clk_hws[CLKID_HEVCF_MUX])) + panic("%s: %d clk_hw_register hevcf_mux error\n", + __func__, __LINE__); + + clk_hws[CLKID_VPU_CLKB_TMP_COMP] = clk_hw_register_composite(NULL, + "vpu_clkb_tmp_composite", + vpu_clkb_tmp_parent_names, 4, + &vpu_clkb_tmp_mux.hw, + &clk_mux_ops, + &vpu_clkb_tmp_div.hw, + &clk_divider_ops, + &vpu_clkb_tmp_gate.hw, + &clk_gate_ops, 0); + if (IS_ERR(clk_hws[CLKID_VPU_CLKB_TMP_COMP])) + panic("%s: %d clk_register_composite vpu_clkb_tmp_composite error\n", + __func__, __LINE__); + + clk_hws[CLKID_VPU_CLKB_COMP] = clk_hw_register_composite(NULL, + "vpu_clkb_composite", + vpu_clkb_parent_names, 1, + NULL, + NULL, + &vpu_clkb_div.hw, + &clk_divider_ops, + &vpu_clkb_gate.hw, + &clk_gate_ops, 0); + if (IS_ERR(clk_hws[CLKID_VPU_CLKB_COMP])) + panic("%s: %d clk_register_composite vpu_clkb_composite error\n", + __func__, __LINE__); + + pr_info("%s: register meson media clk\n", __func__); +} + diff -Naur a/drivers/clk/meson/g12a-vcodec-clk.h b/drivers/clk/meson/g12a-vcodec-clk.h --- a/drivers/clk/meson/g12a-vcodec-clk.h 1970-01-01 08:00:00.000000000 +0800 +++ b/drivers/clk/meson/g12a-vcodec-clk.h 2023-02-23 17:02:04.951750958 +0800 @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ +/* + * Copyright (c) 2019 BayLibre, SAS + * Author: Neil Armstrong + */ + +#ifndef __G12A_VCODEC_CLK_H +#define __G12A_VCODEC_CLK_H + +extern void meson_g12a_vcodec_init(struct clk_hw **clks, void __iomem *clk_base); + +#endif /* __G12A_VCODEC_CLK_H */ diff -Naur a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile --- a/drivers/clk/meson/Makefile 2022-12-19 17:13:11.809508587 +0800 +++ b/drivers/clk/meson/Makefile 2023-02-23 17:02:04.951750958 +0800 @@ -7,7 +7,7 @@ obj-$(CONFIG_COMMON_CLK_MESON_EE_CLKC) += meson-eeclk.o obj-$(CONFIG_COMMON_CLK_MESON_MPLL) += clk-mpll.o obj-$(CONFIG_COMMON_CLK_MESON_PHASE) += clk-phase.o -obj-$(CONFIG_COMMON_CLK_MESON_PLL) += clk-pll.o +obj-$(CONFIG_COMMON_CLK_MESON_PLL) += clk-pll.o clk-mux.o obj-$(CONFIG_COMMON_CLK_MESON_REGMAP) += clk-regmap.o obj-$(CONFIG_COMMON_CLK_MESON_SCLK_DIV) += sclk-div.o obj-$(CONFIG_COMMON_CLK_MESON_VID_PLL_DIV) += vid-pll-div.o @@ -17,5 +17,5 @@ obj-$(CONFIG_COMMON_CLK_AXG) += axg.o axg-aoclk.o obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o -obj-$(CONFIG_COMMON_CLK_G12A) += g12a.o g12a-aoclk.o +obj-$(CONFIG_COMMON_CLK_G12A) += g12a.o g12a-aoclk.o g12a-vcodec-clk.o obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o meson8-ddr.o diff -Naur a/drivers/clk/meson/vid-pll-div.c b/drivers/clk/meson/vid-pll-div.c --- a/drivers/clk/meson/vid-pll-div.c 2022-12-19 17:13:11.813508635 +0800 +++ b/drivers/clk/meson/vid-pll-div.c 2023-02-23 17:02:04.951750958 +0800 @@ -39,12 +39,14 @@ } static const struct vid_pll_div vid_pll_div_table[] = { + VID_PLL_DIV(0xffff, 0, 1, 1), /* 1/1 => /1 */ VID_PLL_DIV(0x0aaa, 0, 2, 1), /* 2/1 => /2 */ VID_PLL_DIV(0x5294, 2, 5, 2), /* 5/2 => /2.5 */ VID_PLL_DIV(0x0db6, 0, 3, 1), /* 3/1 => /3 */ VID_PLL_DIV(0x36cc, 1, 7, 2), /* 7/2 => /3.5 */ VID_PLL_DIV(0x6666, 2, 15, 4), /* 15/4 => /3.75 */ VID_PLL_DIV(0x0ccc, 0, 4, 1), /* 4/1 => /4 */ + VID_PLL_DIV(0x0ccc, 1, 467, 100), /* 467/100 => /4.67 */ VID_PLL_DIV(0x739c, 2, 5, 1), /* 5/1 => /5 */ VID_PLL_DIV(0x0e38, 0, 6, 1), /* 6/1 => /6 */ VID_PLL_DIV(0x0000, 3, 25, 4), /* 25/4 => /6.25 */ diff -Naur a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c 2022-12-19 17:13:12.585517887 +0800 +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c 2023-02-23 17:02:04.951750958 +0800 @@ -143,6 +143,7 @@ struct dw_hdmi { struct drm_connector connector; struct drm_bridge bridge; + struct drm_bridge *next_bridge; unsigned int version; @@ -754,6 +755,11 @@ else hdmi->mc_clkdis |= HDMI_MC_CLKDIS_AUDCLK_DISABLE; hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + + if (enable) { + hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0); + hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); + } } static u8 *hdmi_audio_get_eld(struct dw_hdmi *hdmi) @@ -2403,21 +2409,6 @@ return ret; } -static bool hdr_metadata_equal(const struct drm_connector_state *old_state, - const struct drm_connector_state *new_state) -{ - struct drm_property_blob *old_blob = old_state->hdr_output_metadata; - struct drm_property_blob *new_blob = new_state->hdr_output_metadata; - - if (!old_blob || !new_blob) - return old_blob == new_blob; - - if (old_blob->length != new_blob->length) - return false; - - return !memcmp(old_blob->data, new_blob->data, old_blob->length); -} - static int dw_hdmi_connector_atomic_check(struct drm_connector *connector, struct drm_atomic_state *state) { @@ -2431,7 +2422,7 @@ if (!crtc) return 0; - if (!hdr_metadata_equal(old_state, new_state)) { + if (!drm_connector_atomic_hdr_metadata_equal(old_state, new_state)) { crtc_state = drm_atomic_get_crtc_state(state, crtc); if (IS_ERR(crtc_state)) return PTR_ERR(crtc_state); @@ -2500,8 +2491,7 @@ drm_connector_attach_max_bpc_property(connector, 8, 16); if (hdmi->version >= 0x200a && hdmi->plat_data->use_drm_infoframe) - drm_object_attach_property(&connector->base, - connector->dev->mode_config.hdr_output_metadata_property, 0); + drm_connector_attach_hdr_output_metadata_property(connector); drm_connector_attach_encoder(connector, hdmi->bridge.encoder); @@ -2800,7 +2790,8 @@ struct dw_hdmi *hdmi = bridge->driver_private; if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) - return 0; + return drm_bridge_attach(bridge->encoder, hdmi->next_bridge, + bridge, flags); return dw_hdmi_connector_create(hdmi); } @@ -3185,6 +3176,52 @@ /* ----------------------------------------------------------------------------- * Probe/remove API, used from platforms based on the DRM bridge API. */ + +static int dw_hdmi_parse_dt(struct dw_hdmi *hdmi) +{ + struct device_node *endpoint; + struct device_node *remote; + + if (!hdmi->plat_data->output_port) + return 0; + + endpoint = of_graph_get_endpoint_by_regs(hdmi->dev->of_node, + hdmi->plat_data->output_port, + -1); + if (!endpoint) { + /* + * On platforms whose bindings don't make the output port + * mandatory (such as Rockchip) the plat_data->output_port + * field isn't set, so it's safe to make this a fatal error. + */ + dev_err(hdmi->dev, "Missing endpoint in port@%u\n", + hdmi->plat_data->output_port); + return -ENODEV; + } + + remote = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + if (!remote) { + dev_err(hdmi->dev, "Endpoint in port@%u unconnected\n", + hdmi->plat_data->output_port); + return -ENODEV; + } + + if (!of_device_is_available(remote)) { + dev_err(hdmi->dev, "port@%u remote device is disabled\n", + hdmi->plat_data->output_port); + of_node_put(remote); + return -ENODEV; + } + + hdmi->next_bridge = of_drm_find_bridge(remote); + of_node_put(remote); + if (!hdmi->next_bridge) + return -EPROBE_DEFER; + + return 0; +} + struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, const struct dw_hdmi_plat_data *plat_data) { @@ -3221,6 +3258,10 @@ mutex_init(&hdmi->cec_notifier_mutex); spin_lock_init(&hdmi->audio_lock); + ret = dw_hdmi_parse_dt(hdmi); + if (ret < 0) + return ERR_PTR(ret); + ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0); if (ddc_node) { hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node); @@ -3386,6 +3427,7 @@ hdmi->bridge.funcs = &dw_hdmi_bridge_funcs; hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; + hdmi->bridge.interlace_allowed = true; #ifdef CONFIG_OF hdmi->bridge.of_node = pdev->dev.of_node; #endif @@ -3430,7 +3472,7 @@ hdmi->audio = platform_device_register_full(&pdevinfo); } - if (config0 & HDMI_CONFIG0_CEC) { + if (!plat_data->disable_cec && (config0 & HDMI_CONFIG0_CEC)) { cec.hdmi = hdmi; cec.ops = &dw_hdmi_cec_ops; cec.irq = irq; @@ -3449,8 +3491,7 @@ err_iahb: clk_disable_unprepare(hdmi->iahb_clk); - if (hdmi->cec_clk) - clk_disable_unprepare(hdmi->cec_clk); + clk_disable_unprepare(hdmi->cec_clk); err_isfr: clk_disable_unprepare(hdmi->isfr_clk); err_res: @@ -3474,8 +3515,7 @@ clk_disable_unprepare(hdmi->iahb_clk); clk_disable_unprepare(hdmi->isfr_clk); - if (hdmi->cec_clk) - clk_disable_unprepare(hdmi->cec_clk); + clk_disable_unprepare(hdmi->cec_clk); if (hdmi->i2c) i2c_del_adapter(&hdmi->i2c->adap); diff -Naur a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c 2022-12-19 17:13:12.585517887 +0800 +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c 2023-02-23 17:02:04.951750958 +0800 @@ -265,11 +265,9 @@ /* override the module pointer */ cec->adap->owner = THIS_MODULE; - ret = devm_add_action(&pdev->dev, dw_hdmi_cec_del, cec); - if (ret) { - cec_delete_adapter(cec->adap); + ret = devm_add_action_or_reset(&pdev->dev, dw_hdmi_cec_del, cec); + if (ret) return ret; - } ret = devm_request_threaded_irq(&pdev->dev, cec->irq, dw_hdmi_cec_hardirq, diff -Naur a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c --- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c 2022-12-19 17:13:12.589517935 +0800 +++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c 2023-02-23 17:02:04.951750958 +0800 @@ -246,6 +246,7 @@ struct clk *pclk; + bool device_found; unsigned int lane_mbps; /* per lane */ u32 channel; u32 lanes; @@ -309,13 +310,37 @@ return readl(dsi->base + reg); } +static int dw_mipi_dsi_panel_or_bridge(struct dw_mipi_dsi *dsi, + struct device_node *node) +{ + struct drm_bridge *bridge; + struct drm_panel *panel; + int ret; + + ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge); + if (ret) + return ret; + + if (panel) { + bridge = drm_panel_bridge_add_typed(panel, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + } + + dsi->panel_bridge = bridge; + + if (!dsi->panel_bridge) + return -EPROBE_DEFER; + + return 0; +} + static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { struct dw_mipi_dsi *dsi = host_to_dsi(host); const struct dw_mipi_dsi_plat_data *pdata = dsi->plat_data; - struct drm_bridge *bridge; - struct drm_panel *panel; int ret; if (device->lanes > dsi->plat_data->max_data_lanes) { @@ -329,22 +354,14 @@ dsi->format = device->format; dsi->mode_flags = device->mode_flags; - ret = drm_of_find_panel_or_bridge(host->dev->of_node, 1, 0, - &panel, &bridge); - if (ret) - return ret; + if (!dsi->device_found) { + ret = dw_mipi_dsi_panel_or_bridge(dsi, host->dev->of_node); + if (ret) + return ret; - if (panel) { - bridge = drm_panel_bridge_add_typed(panel, - DRM_MODE_CONNECTOR_DSI); - if (IS_ERR(bridge)) - return PTR_ERR(bridge); + dsi->device_found = true; } - dsi->panel_bridge = bridge; - - drm_bridge_add(&dsi->bridge); - if (pdata->host_ops && pdata->host_ops->attach) { ret = pdata->host_ops->attach(pdata->priv_data, device); if (ret < 0) @@ -854,7 +871,8 @@ dsi_write(dsi, DSI_INT_MSK1, 0); } -static void dw_mipi_dsi_bridge_post_disable(struct drm_bridge *bridge) +static void dw_mipi_dsi_bridge_post_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops; @@ -961,7 +979,8 @@ dw_mipi_dsi_mode_set(dsi->slave, adjusted_mode); } -static void dw_mipi_dsi_bridge_enable(struct drm_bridge *bridge) +static void dw_mipi_dsi_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); @@ -981,7 +1000,10 @@ enum drm_mode_status mode_status = MODE_OK; if (pdata->mode_valid) - mode_status = pdata->mode_valid(pdata->priv_data, mode); + mode_status = pdata->mode_valid(pdata->priv_data, mode, + dsi->mode_flags, + dw_mipi_dsi_get_lanes(dsi), + dsi->format); return mode_status; } @@ -999,17 +1021,30 @@ /* Set the encoder type as caller does not know it */ bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI; + if (!dsi->device_found) { + int ret; + + ret = dw_mipi_dsi_panel_or_bridge(dsi, dsi->dev->of_node); + if (ret) + return ret; + + dsi->device_found = true; + } + /* Attach the panel-bridge to the dsi bridge */ return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge, flags); } static const struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = { - .mode_set = dw_mipi_dsi_bridge_mode_set, - .enable = dw_mipi_dsi_bridge_enable, - .post_disable = dw_mipi_dsi_bridge_post_disable, - .mode_valid = dw_mipi_dsi_bridge_mode_valid, - .attach = dw_mipi_dsi_bridge_attach, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_enable = dw_mipi_dsi_bridge_atomic_enable, + .atomic_post_disable = dw_mipi_dsi_bridge_post_atomic_disable, + .mode_set = dw_mipi_dsi_bridge_mode_set, + .mode_valid = dw_mipi_dsi_bridge_mode_valid, + .attach = dw_mipi_dsi_bridge_attach, }; #ifdef CONFIG_DEBUG_FS @@ -1182,6 +1217,7 @@ #ifdef CONFIG_OF dsi->bridge.of_node = pdev->dev.of_node; #endif + drm_bridge_add(&dsi->bridge); return dsi; } diff -Naur a/drivers/gpu/drm/drm_aperture.c b/drivers/gpu/drm/drm_aperture.c --- a/drivers/gpu/drm/drm_aperture.c 1970-01-01 08:00:00.000000000 +0800 +++ b/drivers/gpu/drm/drm_aperture.c 2023-02-23 17:02:04.951750958 +0800 @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include /* for firmware helpers */ +#include +#include +#include + +#include +#include +#include + +/** + * DOC: overview + * + * A graphics device might be supported by different drivers, but only one + * driver can be active at any given time. Many systems load a generic + * graphics drivers, such as EFI-GOP or VESA, early during the boot process. + * During later boot stages, they replace the generic driver with a dedicated, + * hardware-specific driver. To take over the device the dedicated driver + * first has to remove the generic driver. DRM aperture functions manage + * ownership of DRM framebuffer memory and hand-over between drivers. + * + * DRM drivers should call drm_aperture_remove_conflicting_framebuffers() + * at the top of their probe function. The function removes any generic + * driver that is currently associated with the given framebuffer memory. + * If the framebuffer is located at PCI BAR 0, the rsp code looks as in the + * example given below. + * + * .. code-block:: c + * + * static const struct drm_driver example_driver = { + * ... + * }; + * + * static int remove_conflicting_framebuffers(struct pci_dev *pdev) + * { + * bool primary = false; + * resource_size_t base, size; + * int ret; + * + * base = pci_resource_start(pdev, 0); + * size = pci_resource_len(pdev, 0); + * #ifdef CONFIG_X86 + * primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW; + * #endif + * + * return drm_aperture_remove_conflicting_framebuffers(base, size, primary, + * &example_driver); + * } + * + * static int probe(struct pci_dev *pdev) + * { + * int ret; + * + * // Remove any generic drivers... + * ret = remove_conflicting_framebuffers(pdev); + * if (ret) + * return ret; + * + * // ... and initialize the hardware. + * ... + * + * drm_dev_register(); + * + * return 0; + * } + * + * PCI device drivers should call + * drm_aperture_remove_conflicting_pci_framebuffers() and let it detect the + * framebuffer apertures automatically. Device drivers without knowledge of + * the framebuffer's location shall call drm_aperture_remove_framebuffers(), + * which removes all drivers for known framebuffer. + * + * Drivers that are susceptible to being removed by other drivers, such as + * generic EFI or VESA drivers, have to register themselves as owners of their + * given framebuffer memory. Ownership of the framebuffer memory is achieved + * by calling devm_aperture_acquire_from_firmware(). On success, the driver + * is the owner of the framebuffer range. The function fails if the + * framebuffer is already by another driver. See below for an example. + * + * .. code-block:: c + * + * static int acquire_framebuffers(struct drm_device *dev, struct platform_device *pdev) + * { + * resource_size_t base, size; + * + * mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + * if (!mem) + * return -EINVAL; + * base = mem->start; + * size = resource_size(mem); + * + * return devm_acquire_aperture_from_firmware(dev, base, size); + * } + * + * static int probe(struct platform_device *pdev) + * { + * struct drm_device *dev; + * int ret; + * + * // ... Initialize the device... + * dev = devm_drm_dev_alloc(); + * ... + * + * // ... and acquire ownership of the framebuffer. + * ret = acquire_framebuffers(dev, pdev); + * if (ret) + * return ret; + * + * drm_dev_register(dev, 0); + * + * return 0; + * } + * + * The generic driver is now subject to forced removal by other drivers. This + * only works for platform drivers that support hot unplug. + * When a driver calls drm_aperture_remove_conflicting_framebuffers() et al + * for the registered framebuffer range, the aperture helpers call + * platform_device_unregister() and the generic driver unloads itself. It + * may not access the device's registers, framebuffer memory, ROM, etc + * afterwards. + */ + +struct drm_aperture { + struct drm_device *dev; + resource_size_t base; + resource_size_t size; + struct list_head lh; + void (*detach)(struct drm_device *dev); +}; + +static LIST_HEAD(drm_apertures); +static DEFINE_MUTEX(drm_apertures_lock); + +static bool overlap(resource_size_t base1, resource_size_t end1, + resource_size_t base2, resource_size_t end2) +{ + return (base1 < end2) && (end1 > base2); +} + +static void devm_aperture_acquire_release(void *data) +{ + struct drm_aperture *ap = data; + bool detached = !ap->dev; + + if (detached) + return; + + mutex_lock(&drm_apertures_lock); + list_del(&ap->lh); + mutex_unlock(&drm_apertures_lock); +} + +static int devm_aperture_acquire(struct drm_device *dev, + resource_size_t base, resource_size_t size, + void (*detach)(struct drm_device *)) +{ + size_t end = base + size; + struct list_head *pos; + struct drm_aperture *ap; + + mutex_lock(&drm_apertures_lock); + + list_for_each(pos, &drm_apertures) { + ap = container_of(pos, struct drm_aperture, lh); + if (overlap(base, end, ap->base, ap->base + ap->size)) { + mutex_unlock(&drm_apertures_lock); + return -EBUSY; + } + } + + ap = devm_kzalloc(dev->dev, sizeof(*ap), GFP_KERNEL); + if (!ap) { + mutex_unlock(&drm_apertures_lock); + return -ENOMEM; + } + + ap->dev = dev; + ap->base = base; + ap->size = size; + ap->detach = detach; + INIT_LIST_HEAD(&ap->lh); + + list_add(&ap->lh, &drm_apertures); + + mutex_unlock(&drm_apertures_lock); + + return devm_add_action_or_reset(dev->dev, devm_aperture_acquire_release, ap); +} + +static void drm_aperture_detach_firmware(struct drm_device *dev) +{ + struct platform_device *pdev = to_platform_device(dev->dev); + + /* + * Remove the device from the device hierarchy. This is the right thing + * to do for firmware-based DRM drivers, such as EFI, VESA or VGA. After + * the new driver takes over the hardware, the firmware device's state + * will be lost. + * + * For non-platform devices, a new callback would be required. + * + * If the aperture helpers ever need to handle native drivers, this call + * would only have to unplug the DRM device, so that the hardware device + * stays around after detachment. + */ + platform_device_unregister(pdev); +} + +/** + * devm_aperture_acquire_from_firmware - Acquires ownership of a firmware framebuffer + * on behalf of a DRM driver. + * @dev: the DRM device to own the framebuffer memory + * @base: the framebuffer's byte offset in physical memory + * @size: the framebuffer size in bytes + * + * Installs the given device as the new owner of the framebuffer. The function + * expects the framebuffer to be provided by a platform device that has been + * set up by firmware. Firmware can be any generic interface, such as EFI, + * VESA, VGA, etc. If the native hardware driver takes over ownership of the + * framebuffer range, the firmware state gets lost. Aperture helpers will then + * unregister the platform device automatically. Acquired apertures are + * released automatically if the underlying device goes away. + * + * The function fails if the framebuffer range, or parts of it, is currently + * owned by another driver. To evict current owners, callers should use + * drm_aperture_remove_conflicting_framebuffers() et al. before calling this + * function. The function also fails if the given device is not a platform + * device. + * + * Returns: + * 0 on success, or a negative errno value otherwise. + */ +int devm_aperture_acquire_from_firmware(struct drm_device *dev, resource_size_t base, + resource_size_t size) +{ + if (drm_WARN_ON(dev, !dev_is_platform(dev->dev))) + return -EINVAL; + + return devm_aperture_acquire(dev, base, size, drm_aperture_detach_firmware); +} +EXPORT_SYMBOL(devm_aperture_acquire_from_firmware); + +static void drm_aperture_detach_drivers(resource_size_t base, resource_size_t size) +{ + resource_size_t end = base + size; + struct list_head *pos, *n; + + mutex_lock(&drm_apertures_lock); + + list_for_each_safe(pos, n, &drm_apertures) { + struct drm_aperture *ap = + container_of(pos, struct drm_aperture, lh); + struct drm_device *dev = ap->dev; + + if (WARN_ON_ONCE(!dev)) + continue; + + if (!overlap(base, end, ap->base, ap->base + ap->size)) + continue; + + ap->dev = NULL; /* detach from device */ + list_del(&ap->lh); + + ap->detach(dev); + } + + mutex_unlock(&drm_apertures_lock); +} + +/** + * drm_aperture_remove_conflicting_framebuffers - remove existing framebuffers in the given range + * @base: the aperture's base address in physical memory + * @size: aperture size in bytes + * @primary: also kick vga16fb if present + * @req_driver: requesting DRM driver + * + * This function removes graphics device drivers which use memory range described by + * @base and @size. + * + * Returns: + * 0 on success, or a negative errno code otherwise + */ +int drm_aperture_remove_conflicting_framebuffers(resource_size_t base, resource_size_t size, + bool primary, const struct drm_driver *req_driver) +{ +#if IS_REACHABLE(CONFIG_FB) + struct apertures_struct *a; + int ret; + + a = alloc_apertures(1); + if (!a) + return -ENOMEM; + + a->ranges[0].base = base; + a->ranges[0].size = size; + + ret = remove_conflicting_framebuffers(a, req_driver->name, primary); + kfree(a); + + if (ret) + return ret; +#endif + + drm_aperture_detach_drivers(base, size); + + return 0; +} +EXPORT_SYMBOL(drm_aperture_remove_conflicting_framebuffers); + +/** + * drm_aperture_remove_conflicting_pci_framebuffers - remove existing framebuffers for PCI devices + * @pdev: PCI device + * @req_driver: requesting DRM driver + * + * This function removes graphics device drivers using memory range configured + * for any of @pdev's memory bars. The function assumes that PCI device with + * shadowed ROM drives a primary display and so kicks out vga16fb. + * + * Returns: + * 0 on success, or a negative errno code otherwise + */ +int drm_aperture_remove_conflicting_pci_framebuffers(struct pci_dev *pdev, + const struct drm_driver *req_driver) +{ + resource_size_t base, size; + int bar, ret = 0; + + for (bar = 0; bar < PCI_STD_NUM_BARS; ++bar) { + if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) + continue; + base = pci_resource_start(pdev, bar); + size = pci_resource_len(pdev, bar); + drm_aperture_detach_drivers(base, size); + } + + /* + * WARNING: Apparently we must kick fbdev drivers before vgacon, + * otherwise the vga fbdev driver falls over. + */ +#if IS_REACHABLE(CONFIG_FB) + ret = remove_conflicting_pci_framebuffers(pdev, req_driver->name); +#endif + if (ret == 0) + ret = vga_remove_vgacon(pdev); + return ret; +} +EXPORT_SYMBOL(drm_aperture_remove_conflicting_pci_framebuffers); diff -Naur a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c --- a/drivers/gpu/drm/drm_connector.c 2022-12-19 17:13:12.593517982 +0800 +++ b/drivers/gpu/drm/drm_connector.c 2023-02-23 17:02:04.951750958 +0800 @@ -2144,6 +2144,55 @@ EXPORT_SYMBOL(drm_connector_attach_max_bpc_property); /** + * drm_connector_attach_hdr_output_metadata_property - attach "HDR_OUTPUT_METADA" property + * @connector: connector to attach the property on. + * + * This is used to allow the userspace to send HDR Metadata to the + * driver. + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_connector_attach_hdr_output_metadata_property(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_property *prop = dev->mode_config.hdr_output_metadata_property; + + drm_object_attach_property(&connector->base, prop, 0); + + return 0; +} +EXPORT_SYMBOL(drm_connector_attach_hdr_output_metadata_property); + +/** + * drm_connector_atomic_hdr_metadata_equal - checks if the hdr metadata changed + * @old_state: old connector state to compare + * @new_state: new connector state to compare + * + * This is used by HDR-enabled drivers to test whether the HDR metadata + * have changed between two different connector state (and thus probably + * requires a full blown mode change). + * + * Returns: + * True if the metadata are equal, False otherwise + */ +bool drm_connector_atomic_hdr_metadata_equal(struct drm_connector_state *old_state, + struct drm_connector_state *new_state) +{ + struct drm_property_blob *old_blob = old_state->hdr_output_metadata; + struct drm_property_blob *new_blob = new_state->hdr_output_metadata; + + if (!old_blob || !new_blob) + return old_blob == new_blob; + + if (old_blob->length != new_blob->length) + return false; + + return !memcmp(old_blob->data, new_blob->data, old_blob->length); +} +EXPORT_SYMBOL(drm_connector_atomic_hdr_metadata_equal); + +/** * drm_connector_set_vrr_capable_property - sets the variable refresh rate * capable property for a connector * @connector: drm connector diff -Naur a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c --- a/drivers/gpu/drm/drm_ioctl.c 2022-12-19 17:13:12.597518030 +0800 +++ b/drivers/gpu/drm/drm_ioctl.c 2023-02-23 17:02:04.955751014 +0800 @@ -678,9 +678,9 @@ DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb_ioctl, 0), DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER), DRM_IOCTL_DEF(DRM_IOCTL_MODE_DIRTYFB, drm_mode_dirtyfb_ioctl, DRM_MASTER), - DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_DUMB, drm_mode_create_dumb_ioctl, 0), - DRM_IOCTL_DEF(DRM_IOCTL_MODE_MAP_DUMB, drm_mode_mmap_dumb_ioctl, 0), - DRM_IOCTL_DEF(DRM_IOCTL_MODE_DESTROY_DUMB, drm_mode_destroy_dumb_ioctl, 0), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_DUMB, drm_mode_create_dumb_ioctl, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_MAP_DUMB, drm_mode_mmap_dumb_ioctl, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_DESTROY_DUMB, drm_mode_destroy_dumb_ioctl, DRM_RENDER_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_OBJ_GETPROPERTIES, drm_mode_obj_get_properties_ioctl, 0), DRM_IOCTL_DEF(DRM_IOCTL_MODE_OBJ_SETPROPERTY, drm_mode_obj_set_property_ioctl, DRM_MASTER), DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR2, drm_mode_cursor2_ioctl, DRM_MASTER), diff -Naur a/drivers/gpu/drm/drm_plane_helper.c b/drivers/gpu/drm/drm_plane_helper.c --- a/drivers/gpu/drm/drm_plane_helper.c 2022-12-19 17:13:12.601518079 +0800 +++ b/drivers/gpu/drm/drm_plane_helper.c 2023-02-23 17:02:04.955751014 +0800 @@ -123,6 +123,7 @@ .crtc_w = drm_rect_width(dst), .crtc_h = drm_rect_height(dst), .rotation = rotation, + .visible = *visible, }; struct drm_crtc_state crtc_state = { .crtc = crtc, diff -Naur a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c --- a/drivers/gpu/drm/drm_vblank.c 2022-12-19 17:13:12.601518079 +0800 +++ b/drivers/gpu/drm/drm_vblank.c 2023-02-23 17:02:04.955751014 +0800 @@ -1725,6 +1725,15 @@ reply->tval_usec = ts.tv_nsec / 1000; } +static bool drm_wait_vblank_supported(struct drm_device *dev) +{ +#if IS_ENABLED(CONFIG_DRM_LEGACY) + if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY))) + return dev->irq_enabled; +#endif + return drm_dev_has_vblank(dev); +} + int drm_wait_vblank_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { @@ -1736,7 +1745,7 @@ unsigned int pipe_index; unsigned int flags, pipe, high_pipe; - if (!dev->irq_enabled) + if (!drm_wait_vblank_supported(dev)) return -EOPNOTSUPP; if (vblwait->request.type & _DRM_VBLANK_SIGNAL) @@ -2011,7 +2020,7 @@ if (!drm_core_check_feature(dev, DRIVER_MODESET)) return -EOPNOTSUPP; - if (!dev->irq_enabled) + if (!drm_dev_has_vblank(dev)) return -EOPNOTSUPP; crtc = drm_crtc_find(dev, file_priv, get_seq->crtc_id); @@ -2070,7 +2079,7 @@ if (!drm_core_check_feature(dev, DRIVER_MODESET)) return -EOPNOTSUPP; - if (!dev->irq_enabled) + if (!drm_dev_has_vblank(dev)) return -EOPNOTSUPP; crtc = drm_crtc_find(dev, file_priv, queue_seq->crtc_id); diff -Naur a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile --- a/drivers/gpu/drm/Makefile 2022-12-19 17:13:11.981510649 +0800 +++ b/drivers/gpu/drm/Makefile 2023-02-23 17:02:04.955751014 +0800 @@ -18,7 +18,7 @@ drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \ drm_syncobj.o drm_lease.o drm_writeback.o drm_client.o \ drm_client_modeset.o drm_atomic_uapi.o drm_hdcp.o \ - drm_managed.o drm_vblank_work.o + drm_managed.o drm_vblank_work.o drm_aperture.o drm-$(CONFIG_DRM_LEGACY) += drm_legacy_misc.o drm_bufs.o drm_context.o drm_dma.o drm_scatter.o drm_lock.o drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o diff -Naur a/drivers/gpu/drm/meson/Kconfig b/drivers/gpu/drm/meson/Kconfig --- a/drivers/gpu/drm/meson/Kconfig 2022-12-19 17:13:12.677518989 +0800 +++ b/drivers/gpu/drm/meson/Kconfig 2023-02-23 17:02:04.955751014 +0800 @@ -6,9 +6,11 @@ select DRM_KMS_HELPER select DRM_KMS_CMA_HELPER select DRM_GEM_CMA_HELPER + select DRM_DISPLAY_CONNECTOR select VIDEOMODE_HELPERS select REGMAP_MMIO select MESON_CANVAS + select CEC_CORE if CEC_NOTIFIER config DRM_MESON_DW_HDMI tristate "HDMI Synopsys Controller support for Amlogic Meson Display" @@ -16,3 +18,10 @@ default y if DRM_MESON select DRM_DW_HDMI imply DRM_DW_HDMI_I2S_AUDIO + +config DRM_MESON_DW_MIPI_DSI + tristate "MIPI DSI Synopsys Controller support for Amlogic Meson Display" + depends on DRM_MESON + default y if DRM_MESON + select DRM_DW_MIPI_DSI + select GENERIC_PHY_MIPI_DPHY diff -Naur a/drivers/gpu/drm/meson/Makefile b/drivers/gpu/drm/meson/Makefile --- a/drivers/gpu/drm/meson/Makefile 2022-12-19 17:13:12.677518989 +0800 +++ b/drivers/gpu/drm/meson/Makefile 2023-02-23 17:02:04.955751014 +0800 @@ -1,7 +1,9 @@ # SPDX-License-Identifier: GPL-2.0-only -meson-drm-y := meson_drv.o meson_plane.o meson_crtc.o meson_venc_cvbs.o +meson-drm-y := meson_drv.o meson_plane.o meson_cursor.o meson_crtc.o meson_encoder_cvbs.o meson-drm-y += meson_viu.o meson_vpp.o meson_venc.o meson_vclk.o meson_overlay.o meson-drm-y += meson_rdma.o meson_osd_afbcd.o +meson-drm-y += meson_encoder_hdmi.o meson_encoder_dsi.o obj-$(CONFIG_DRM_MESON) += meson-drm.o obj-$(CONFIG_DRM_MESON_DW_HDMI) += meson_dw_hdmi.o +obj-$(CONFIG_DRM_MESON_DW_MIPI_DSI) += meson_dw_mipi_dsi.o diff -Naur a/drivers/gpu/drm/meson/meson_crtc.c b/drivers/gpu/drm/meson/meson_crtc.c --- a/drivers/gpu/drm/meson/meson_crtc.c 2022-12-19 17:13:12.677518989 +0800 +++ b/drivers/gpu/drm/meson/meson_crtc.c 2023-02-23 17:02:04.955751014 +0800 @@ -36,6 +36,7 @@ struct drm_pending_vblank_event *event; struct meson_drm *priv; void (*enable_osd1)(struct meson_drm *priv); + void (*enable_osd2)(struct meson_drm *priv); void (*enable_vd1)(struct meson_drm *priv); void (*enable_osd1_afbc)(struct meson_drm *priv); void (*disable_osd1_afbc)(struct meson_drm *priv); @@ -45,6 +46,10 @@ }; #define to_meson_crtc(x) container_of(x, struct meson_crtc, base) +static bool s_force_commit = false; +static bool s_force_osd1_disable = false; +static bool s_force_video_zorder_up = false; + /* CRTC */ static int meson_crtc_enable_vblank(struct drm_crtc *crtc) @@ -81,8 +86,11 @@ }; +struct drm_display_mode meson_display_mode = { 0 }; +EXPORT_SYMBOL(meson_display_mode); + static void meson_g12a_crtc_atomic_enable(struct drm_crtc *crtc, - struct drm_crtc_state *old_state) + struct drm_crtc_state *old_crtc_state) { struct meson_crtc *meson_crtc = to_meson_crtc(crtc); struct drm_crtc_state *crtc_state = crtc->state; @@ -110,15 +118,31 @@ writel_relaxed(0 << 16 | (crtc_state->mode.vdisplay - 1), priv->io_base + _REG(VPP_OSD1_BLD_V_SCOPE)); + writel_relaxed(0 << 16 | + (crtc_state->mode.hdisplay - 1), + priv->io_base + _REG(VPP_OSD2_BLD_H_SCOPE)); + writel_relaxed(0 << 16 | + (crtc_state->mode.vdisplay - 1), + priv->io_base + _REG(VPP_OSD2_BLD_V_SCOPE)); + writel_relaxed(crtc_state->mode.hdisplay | + crtc_state->mode.vdisplay << 16, + priv->io_base + + _REG(VIU_OSD_BLEND_BLEND0_SIZE)); + writel_relaxed(crtc_state->mode.hdisplay | + crtc_state->mode.vdisplay << 16, + priv->io_base + + _REG(VIU_OSD_BLEND_BLEND1_SIZE)); writel_relaxed(crtc_state->mode.hdisplay << 16 | crtc_state->mode.vdisplay, priv->io_base + _REG(VPP_OUT_H_V_SIZE)); drm_crtc_vblank_on(crtc); + + memcpy(&meson_display_mode, &crtc_state->mode, sizeof(meson_display_mode)); } static void meson_crtc_atomic_enable(struct drm_crtc *crtc, - struct drm_crtc_state *old_state) + struct drm_crtc_state *old_crtc_state) { struct meson_crtc *meson_crtc = to_meson_crtc(crtc); struct drm_crtc_state *crtc_state = crtc->state; @@ -146,7 +170,7 @@ } static void meson_g12a_crtc_atomic_disable(struct drm_crtc *crtc, - struct drm_crtc_state *old_state) + struct drm_crtc_state *old_crtc_state) { struct meson_crtc *meson_crtc = to_meson_crtc(crtc); struct meson_drm *priv = meson_crtc->priv; @@ -158,6 +182,9 @@ priv->viu.osd1_enabled = false; priv->viu.osd1_commit = false; + priv->viu.osd2_enabled = false; + priv->viu.osd2_commit = false; + priv->viu.vd1_enabled = false; priv->viu.vd1_commit = false; @@ -171,7 +198,7 @@ } static void meson_crtc_atomic_disable(struct drm_crtc *crtc, - struct drm_crtc_state *old_state) + struct drm_crtc_state *old_crtc_state) { struct meson_crtc *meson_crtc = to_meson_crtc(crtc); struct meson_drm *priv = meson_crtc->priv; @@ -183,11 +210,14 @@ priv->viu.osd1_enabled = false; priv->viu.osd1_commit = false; + priv->viu.osd2_enabled = false; + priv->viu.osd2_commit = false; + priv->viu.vd1_enabled = false; priv->viu.vd1_commit = false; /* Disable VPP Postblend */ - writel_bits_relaxed(VPP_OSD1_POSTBLEND | VPP_VD1_POSTBLEND | + writel_bits_relaxed(VPP_OSD1_POSTBLEND | VPP_OSD2_POSTBLEND | VPP_VD1_POSTBLEND | VPP_VD1_PREBLEND | VPP_POSTBLEND_ENABLE, 0, priv->io_base + _REG(VPP_MISC)); @@ -201,7 +231,7 @@ } static void meson_crtc_atomic_begin(struct drm_crtc *crtc, - struct drm_crtc_state *state) + struct drm_crtc_state *old_crtc_state) { struct meson_crtc *meson_crtc = to_meson_crtc(crtc); unsigned long flags; @@ -223,6 +253,7 @@ struct meson_drm *priv = meson_crtc->priv; priv->viu.osd1_commit = true; + priv->viu.osd2_commit = true; priv->viu.vd1_commit = true; } @@ -246,6 +277,12 @@ priv->io_base + _REG(VPP_MISC)); } +static void meson_crtc_enable_osd2(struct meson_drm *priv) +{ + writel_bits_relaxed(VPP_OSD2_POSTBLEND, VPP_OSD2_POSTBLEND, + priv->io_base + _REG(VPP_MISC)); +} + static void meson_crtc_g12a_enable_osd1_afbc(struct meson_drm *priv) { writel_relaxed(priv->viu.osd1_blk2_cfg4, @@ -274,14 +311,25 @@ writel_relaxed(priv->viu.osd_blend_din0_scope_v, priv->io_base + _REG(VIU_OSD_BLEND_DIN0_SCOPE_V)); - writel_relaxed(priv->viu.osb_blend0_size, + if (s_force_video_zorder_up) { + writel_bits_relaxed(0xF << 8, OSD_BLEND_POSTBLD_SRC_VD1, + priv->io_base + _REG(OSD1_BLEND_SRC_CTRL)); + } else { + writel_bits_relaxed(OSD_BLEND_POSTBLD_SRC_OSD1, OSD_BLEND_POSTBLD_SRC_OSD1, + priv->io_base + _REG(OSD1_BLEND_SRC_CTRL)); + } +} + +static void meson_g12a_crtc_enable_osd2(struct meson_drm *priv) +{ + writel_relaxed(priv->viu.osd_blend_din3_scope_h, priv->io_base + - _REG(VIU_OSD_BLEND_BLEND0_SIZE)); - writel_relaxed(priv->viu.osb_blend1_size, + _REG(VIU_OSD_BLEND_DIN1_SCOPE_H)); + writel_relaxed(priv->viu.osd_blend_din3_scope_v, priv->io_base + - _REG(VIU_OSD_BLEND_BLEND1_SIZE)); - writel_bits_relaxed(3 << 8, 3 << 8, - priv->io_base + _REG(OSD1_BLEND_SRC_CTRL)); + _REG(VIU_OSD_BLEND_DIN1_SCOPE_V)); + writel_bits_relaxed(OSD_BLEND_POSTBLD_SRC_OSD2, OSD_BLEND_POSTBLD_SRC_OSD2, + priv->io_base + _REG(OSD2_BLEND_SRC_CTRL)); } static void meson_crtc_enable_vd1(struct meson_drm *priv) @@ -315,8 +363,22 @@ struct meson_crtc *meson_crtc = to_meson_crtc(priv->crtc); unsigned long flags; + if (s_force_commit) { + s_force_commit = false; + priv->viu.osd1_commit = true; + if (s_force_osd1_disable) { + writel_bits_relaxed(OSD_BLEND_POSTBLD_SRC_OSD1, 0, + priv->io_base + _REG(OSD1_BLEND_SRC_CTRL)); + } else if (s_force_video_zorder_up) { + writel_bits_relaxed(0xF << 8, OSD_BLEND_POSTBLD_SRC_VD1, + priv->io_base + _REG(OSD1_BLEND_SRC_CTRL)); // OSD1 postblend src -> vd1 + writel_bits_relaxed(0xF << 8, OSD_BLEND_POSTBLD_SRC_OSD1, + priv->io_base + _REG(VD1_BLEND_SRC_CTRL)); // VD1 postblend src -> osd1 + } + } + /* Update the OSD registers */ - if (priv->viu.osd1_enabled && priv->viu.osd1_commit) { + if (!s_force_osd1_disable && priv->viu.osd1_enabled && priv->viu.osd1_commit) { writel_relaxed(priv->viu.osd1_ctrl_stat, priv->io_base + _REG(VIU_OSD1_CTRL_STAT)); writel_relaxed(priv->viu.osd1_ctrl_stat2, @@ -388,6 +450,43 @@ priv->viu.osd1_commit = false; } + if (priv->viu.osd2_enabled && priv->viu.osd2_commit) { + writel_relaxed(priv->viu.osd2_ctrl_stat, + priv->io_base + _REG(VIU_OSD2_CTRL_STAT)); + writel_relaxed(priv->viu.osd2_ctrl_stat2, + priv->io_base + _REG(VIU_OSD2_CTRL_STAT2)); + writel_relaxed(priv->viu.osd2_blk0_cfg[0], + priv->io_base + _REG(VIU_OSD2_BLK0_CFG_W0)); + writel_relaxed(priv->viu.osd2_blk0_cfg[1], + priv->io_base + _REG(VIU_OSD2_BLK0_CFG_W1)); + writel_relaxed(priv->viu.osd2_blk0_cfg[2], + priv->io_base + _REG(VIU_OSD2_BLK0_CFG_W2)); + writel_relaxed(priv->viu.osd2_blk0_cfg[3], + priv->io_base + _REG(VIU_OSD2_BLK0_CFG_W3)); + writel_relaxed(priv->viu.osd2_blk0_cfg[4], + priv->io_base + _REG(VIU_OSD2_BLK0_CFG_W4)); + + /* vsync forced to update INTERLACE_SEL_ODD in interlace mode */ + meson_crtc->vsync_forced = priv->viu.osd2_interlace; + + meson_canvas_config(priv->canvas, priv->canvas_id_osd2, + priv->viu.osd2_addr, + priv->viu.osd2_stride, + priv->viu.osd2_height, + MESON_CANVAS_WRAP_NONE, + MESON_CANVAS_BLKMODE_LINEAR, 0); + + /* Enable OSD2 */ + if (meson_crtc->enable_osd2) + meson_crtc->enable_osd2(priv); + + priv->viu.osd2_commit = false; + } else if (priv->viu.osd2_enabled && priv->viu.osd2_interlace) { + u32 reg = readl_relaxed(priv->io_base + _REG(VIU_OSD2_BLK0_CFG_W0)) & ~BIT(0); + writel_relaxed(reg | meson_venci_get_field(priv) ? 1 : 0, + priv->io_base + _REG(VIU_OSD2_BLK0_CFG_W0)); + } + /* Update the VD1 registers */ if (priv->viu.vd1_enabled && priv->viu.vd1_commit) { @@ -671,12 +770,77 @@ spin_unlock_irqrestore(&priv->drm->event_lock, flags); } +static ssize_t enable_osd_store(struct class *class, + struct class_attribute *attr, + const char *buf, size_t count) +{ + pr_info("enable_osd_store: %s\n", buf); + int osd1_enabled = 1; + if (1 != sscanf(buf, "%d", &osd1_enabled)) { + return 0; + } + s_force_osd1_disable = !osd1_enabled; + s_force_commit = true; + pr_info("s_force_osd1_disable=%d\n", s_force_osd1_disable); + return count; +} + +static ssize_t enable_osd_show(struct class *class, + struct class_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", s_force_osd1_disable ? 0 : 1); +} + +static ssize_t video_zorder_up_store(struct class *class, + struct class_attribute *attr, + const char *buf, size_t count) +{ + pr_info("enable_osd_store: %s\n", buf); + int video_zorder_up = 0; + if (1 != sscanf(buf, "%d", &video_zorder_up)) { + return 0; + } + s_force_video_zorder_up = video_zorder_up; + s_force_commit = true; + pr_info("s_force_video_zorder_up=%d\n", s_force_video_zorder_up); + return count; +} + +static ssize_t video_zorder_up_show(struct class *class, + struct class_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", s_force_video_zorder_up ? 1 : 0); +} + +static CLASS_ATTR_RW(enable_osd); +static CLASS_ATTR_RW(video_zorder_up); + + +static struct attribute *meson_crtc_class_attrs[] = { + &class_attr_enable_osd.attr, + &class_attr_video_zorder_up.attr, + NULL +}; + +ATTRIBUTE_GROUPS(meson_crtc_class); +static struct class meson_crtc_class = { + .name = "meson_crtc", + .class_groups = meson_crtc_class_groups, +}; + + int meson_crtc_create(struct meson_drm *priv) { struct meson_crtc *meson_crtc; struct drm_crtc *crtc; int ret; + ret = class_register(&meson_crtc_class); + if (ret < 0) + return ret; + meson_crtc = devm_kzalloc(priv->drm->dev, sizeof(*meson_crtc), GFP_KERNEL); if (!meson_crtc) @@ -685,7 +849,7 @@ meson_crtc->priv = priv; crtc = &meson_crtc->base; ret = drm_crtc_init_with_planes(priv->drm, crtc, - priv->primary_plane, NULL, + priv->primary_plane, priv->cursor_plane, &meson_crtc_funcs, "meson_crtc"); if (ret) { dev_err(priv->drm->dev, "Failed to init CRTC\n"); @@ -694,6 +858,7 @@ if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { meson_crtc->enable_osd1 = meson_g12a_crtc_enable_osd1; + meson_crtc->enable_osd2 = meson_g12a_crtc_enable_osd2; meson_crtc->enable_vd1 = meson_g12a_crtc_enable_vd1; meson_crtc->viu_offset = MESON_G12A_VIU_OFFSET; meson_crtc->enable_osd1_afbc = @@ -703,6 +868,7 @@ drm_crtc_helper_add(crtc, &meson_g12a_crtc_helper_funcs); } else { meson_crtc->enable_osd1 = meson_crtc_enable_osd1; + meson_crtc->enable_osd2 = meson_crtc_enable_osd2; meson_crtc->enable_vd1 = meson_crtc_enable_vd1; if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM)) { meson_crtc->enable_osd1_afbc = diff -Naur a/drivers/gpu/drm/meson/meson_cursor.c b/drivers/gpu/drm/meson/meson_cursor.c --- a/drivers/gpu/drm/meson/meson_cursor.c 1970-01-01 08:00:00.000000000 +0800 +++ b/drivers/gpu/drm/meson/meson_cursor.c 2023-02-23 17:02:04.955751014 +0800 @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 BayLibre, SAS + * Author: Neil Armstrong + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "meson_cursor.h" +#include "meson_registers.h" +#include "meson_viu.h" + +struct meson_cursor { + struct drm_plane base; + struct meson_drm *priv; +}; +#define to_meson_cursor(x) container_of(x, struct meson_cursor, base) + +static int meson_cursor_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state->state, + plane); + struct drm_crtc_state *crtc_state; + + if (!new_plane_state->crtc) + return 0; + + crtc_state = drm_atomic_get_crtc_state(state->state, + new_plane_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + return drm_atomic_helper_check_plane_state(new_plane_state, + crtc_state, + DRM_PLANE_HELPER_NO_SCALING, + DRM_PLANE_HELPER_NO_SCALING, + true, true); +} + +/* Takes a fixed 16.16 number and converts it to integer. */ +static inline int64_t fixed16_to_int(int64_t value) +{ + return value >> 16; +} + +static void meson_cursor_atomic_update(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct meson_cursor *meson_cursor = to_meson_cursor(plane); + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state->state, + plane); + struct drm_rect dest = drm_plane_state_dest(new_state); + struct meson_drm *priv = meson_cursor->priv; + struct drm_framebuffer *fb = new_state->fb; + struct drm_gem_cma_object *gem; + unsigned long flags; + int dst_w, dst_h; + + /* + * Update Coordinates + * Update Formats + * Update Buffer + * Enable Plane + */ + spin_lock_irqsave(&priv->drm->event_lock, flags); + + /* Enable OSD and BLK0, set max global alpha */ + priv->viu.osd2_ctrl_stat = OSD_ENABLE | + (0xFF << OSD_GLOBAL_ALPHA_SHIFT) | + OSD_BLK0_ENABLE; + + priv->viu.osd2_ctrl_stat2 = readl(priv->io_base + + _REG(VIU_OSD2_CTRL_STAT2)); + + /* Set up BLK0 to point to the right canvas */ + priv->viu.osd2_blk0_cfg[0] = priv->canvas_id_osd2 << OSD_CANVAS_SEL; + priv->viu.osd2_blk0_cfg[0] |= OSD_ENDIANNESS_LE; + + /* On GXBB, Use the old non-HDR RGB2YUV converter */ + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB)) + priv->viu.osd2_blk0_cfg[0] |= OSD_OUTPUT_COLOR_RGB; + + switch (fb->format->format) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + priv->viu.osd2_blk0_cfg[0] |= OSD_BLK_MODE_32 | + OSD_COLOR_MATRIX_32_ARGB; + break; + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + priv->viu.osd2_blk0_cfg[0] |= OSD_BLK_MODE_32 | + OSD_COLOR_MATRIX_32_ABGR; + break; + case DRM_FORMAT_RGB888: + priv->viu.osd2_blk0_cfg[0] |= OSD_BLK_MODE_24 | + OSD_COLOR_MATRIX_24_RGB; + break; + case DRM_FORMAT_RGB565: + priv->viu.osd2_blk0_cfg[0] |= OSD_BLK_MODE_16 | + OSD_COLOR_MATRIX_16_RGB565; + break; + } + + switch (fb->format->format) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + /* For XRGB, replace the pixel's alpha by 0xFF */ + priv->viu.osd2_ctrl_stat2 |= OSD_REPLACE_EN; + break; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + /* For ARGB, use the pixel's alpha */ + priv->viu.osd2_ctrl_stat2 &= ~OSD_REPLACE_EN; + break; + } + + dst_w = new_state->crtc_w; + dst_h = new_state->crtc_h; + + if (new_state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE) + priv->viu.osd2_interlace = true; + else + priv->viu.osd2_interlace = false; + + /* + * The format of these registers is (x2 << 16 | x1), + * where x2 is exclusive. + * e.g. +30x1920 would be (1919 << 16) | 30 + */ + priv->viu.osd2_blk0_cfg[1] = + ((fixed16_to_int(new_state->src.x2) - 1) << 16) | + fixed16_to_int(new_state->src.x1); + priv->viu.osd2_blk0_cfg[2] = + ((fixed16_to_int(new_state->src.y2) - 1) << 16) | + fixed16_to_int(new_state->src.y1); + priv->viu.osd2_blk0_cfg[3] = ((dest.x2 - 1) << 16) | dest.x1; + priv->viu.osd2_blk0_cfg[4] = ((dest.y2 - 1) << 16) | dest.y1; + + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { + priv->viu.osd_blend_din3_scope_h = ((dest.x2 - 1) << 16) | dest.x1; + priv->viu.osd_blend_din3_scope_v = ((dest.y2 - 1) << 16) | dest.y1; + priv->viu.osb_blend1_size = dst_h << 16 | dst_w; + } + + /* Update Canvas with buffer address */ + gem = drm_fb_cma_get_gem_obj(fb, 0); + + priv->viu.osd2_addr = gem->paddr; + priv->viu.osd2_stride = fb->pitches[0]; + priv->viu.osd2_height = fb->height; + priv->viu.osd2_width = fb->width; + + /* TOFIX: Reset OSD2 before enabling it on GXL+ SoCs ? */ + + priv->viu.osd2_enabled = true; + + spin_unlock_irqrestore(&priv->drm->event_lock, flags); +} + +static void meson_cursor_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct meson_cursor *meson_cursor = to_meson_cursor(plane); + struct meson_drm *priv = meson_cursor->priv; + + /* Disable OSD2 */ + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) + writel_bits_relaxed(OSD_BLEND_POSTBLD_SRC_OSD2, 0, + priv->io_base + _REG(OSD2_BLEND_SRC_CTRL)); + else + writel_bits_relaxed(VPP_OSD2_POSTBLEND, 0, + priv->io_base + _REG(VPP_MISC)); + + priv->viu.osd2_enabled = false; +} + +static const struct drm_plane_helper_funcs meson_cursor_helper_funcs = { + .atomic_check = meson_cursor_atomic_check, + .atomic_disable = meson_cursor_atomic_disable, + .atomic_update = meson_cursor_atomic_update, +}; + +static const struct drm_plane_funcs meson_cursor_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static const uint32_t supported_drm_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGB565, +}; + +static const uint64_t format_modifiers_default[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID, +}; + +int meson_cursor_create(struct meson_drm *priv) +{ + struct meson_cursor *meson_cursor; + struct drm_plane *cursor; + + meson_cursor = devm_kzalloc(priv->drm->dev, sizeof(*meson_cursor), + GFP_KERNEL); + if (!meson_cursor) + return -ENOMEM; + + meson_cursor->priv = priv; + cursor = &meson_cursor->base; + + drm_universal_plane_init(priv->drm, cursor, 0xFF, + &meson_cursor_funcs, + supported_drm_formats, + ARRAY_SIZE(supported_drm_formats), + format_modifiers_default, + DRM_PLANE_TYPE_CURSOR, "meson_cursor_plane"); + + drm_plane_helper_add(cursor, &meson_cursor_helper_funcs); + + /* For now, OSD Cursor is always on top of the primary plane */ + drm_plane_create_zpos_immutable_property(cursor, 2); + + priv->cursor_plane = cursor; + + return 0; +} diff -Naur a/drivers/gpu/drm/meson/meson_cursor.h b/drivers/gpu/drm/meson/meson_cursor.h --- a/drivers/gpu/drm/meson/meson_cursor.h 1970-01-01 08:00:00.000000000 +0800 +++ b/drivers/gpu/drm/meson/meson_cursor.h 2023-02-23 17:02:04.955751014 +0800 @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021 BayLibre, SAS + * Author: Neil Armstrong + */ + +#ifndef __MESON_CURSOR_H +#define __MESON_CURSOR_H + +#include "meson_drv.h" + +int meson_cursor_create(struct meson_drm *priv); + +#endif /* __MESON_CURSOR_H */ diff -Naur a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c --- a/drivers/gpu/drm/meson/meson_drv.c 2022-12-19 17:13:12.677518989 +0800 +++ b/drivers/gpu/drm/meson/meson_drv.c 2023-02-23 17:02:04.955751014 +0800 @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -29,9 +30,12 @@ #include "meson_drv.h" #include "meson_overlay.h" #include "meson_plane.h" +#include "meson_cursor.h" #include "meson_osd_afbcd.h" #include "meson_registers.h" -#include "meson_venc_cvbs.h" +#include "meson_encoder_cvbs.h" +#include "meson_encoder_hdmi.h" +#include "meson_encoder_dsi.h" #include "meson_viu.h" #include "meson_vpp.h" #include "meson_rdma.h" @@ -93,9 +97,6 @@ static struct drm_driver meson_driver = { .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, - /* IRQ */ - .irq_handler = meson_irq, - /* CMA Ops */ DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(meson_dumb_create), @@ -159,23 +160,6 @@ writel_relaxed(value, priv->io_base + _REG(VPU_WRARB_MODE_L2C1)); } -static void meson_remove_framebuffers(void) -{ - struct apertures_struct *ap; - - ap = alloc_apertures(1); - if (!ap) - return; - - /* The framebuffer can be located anywhere in RAM */ - ap->ranges[0].base = 0; - ap->ranges[0].size = ~0; - - drm_fb_helper_remove_conflicting_framebuffers(ap, "meson-drm-fb", - false); - kfree(ap); -} - struct meson_drm_soc_attr { struct meson_drm_soc_limits limits; const struct soc_device_attribute *attrs; @@ -229,8 +213,7 @@ priv->compat = match->compat; priv->afbcd.ops = match->afbcd_ops; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vpu"); - regs = devm_ioremap_resource(dev, res); + regs = devm_platform_ioremap_resource_byname(pdev, "vpu"); if (IS_ERR(regs)) { ret = PTR_ERR(regs); goto free_drm; @@ -267,6 +250,9 @@ ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_osd1); if (ret) goto free_drm; + ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_osd2); + if (ret) + goto free_drm; ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_0); if (ret) { meson_canvas_free(priv->canvas, priv->canvas_id_osd1); @@ -300,8 +286,13 @@ } } - /* Remove early framebuffers (ie. simplefb) */ - meson_remove_framebuffers(); + /* + * Remove early framebuffers (ie. simplefb). The framebuffer can be + * located anywhere in RAM + */ + ret = drm_aperture_remove_framebuffers(false, &meson_driver); + if (ret) + goto free_drm; ret = drmm_mode_config_init(drm); if (ret) @@ -325,33 +316,47 @@ /* Encoder Initialization */ - ret = meson_venc_cvbs_create(priv); + ret = meson_encoder_cvbs_init(priv); if (ret) - goto free_drm; + goto exit_afbcd; if (has_components) { ret = component_bind_all(drm->dev, drm); if (ret) { dev_err(drm->dev, "Couldn't bind all components\n"); - goto free_drm; + goto exit_afbcd; } } + ret = meson_encoder_hdmi_init(priv); + if (ret) + goto exit_afbcd; + + ret = meson_cursor_create(priv); + if (ret) + goto exit_afbcd; + + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { + ret = meson_encoder_dsi_init(priv); + if (ret) + goto free_drm; + } + ret = meson_plane_create(priv); if (ret) - goto free_drm; + goto exit_afbcd; ret = meson_overlay_create(priv); if (ret) - goto free_drm; + goto exit_afbcd; ret = meson_crtc_create(priv); if (ret) - goto free_drm; + goto exit_afbcd; - ret = drm_irq_install(drm, priv->vsync_irq); + ret = request_irq(priv->vsync_irq, meson_irq, IRQF_SHARED, drm->driver->name, drm); if (ret) - goto free_drm; + goto exit_afbcd; drm_mode_config_reset(drm); @@ -368,7 +373,10 @@ return 0; uninstall_irq: - drm_irq_uninstall(drm); + free_irq(priv->vsync_irq, drm); +exit_afbcd: + if (priv->afbcd.ops) + priv->afbcd.ops->exit(priv); free_drm: drm_dev_put(drm); @@ -396,7 +404,7 @@ drm_kms_helper_poll_fini(drm); drm_atomic_helper_shutdown(drm); component_unbind_all(dev, drm); - drm_irq_uninstall(drm); + free_irq(priv->vsync_irq, drm); drm_dev_put(drm); if (priv->afbcd.ops) @@ -443,46 +451,6 @@ return dev->of_node == data; } -/* Possible connectors nodes to ignore */ -static const struct of_device_id connectors_match[] = { - { .compatible = "composite-video-connector" }, - { .compatible = "svideo-connector" }, - { .compatible = "hdmi-connector" }, - { .compatible = "dvi-connector" }, - {} -}; - -static int meson_probe_remote(struct platform_device *pdev, - struct component_match **match, - struct device_node *parent, - struct device_node *remote) -{ - struct device_node *ep, *remote_node; - int count = 1; - - /* If node is a connector, return and do not add to match table */ - if (of_match_node(connectors_match, remote)) - return 1; - - component_match_add(&pdev->dev, match, compare_of, remote); - - for_each_endpoint_of_node(remote, ep) { - remote_node = of_graph_get_remote_port_parent(ep); - if (!remote_node || - remote_node == parent || /* Ignore parent endpoint */ - !of_device_is_available(remote_node)) { - of_node_put(remote_node); - continue; - } - - count += meson_probe_remote(pdev, match, remote, remote_node); - - of_node_put(remote_node); - } - - return count; -} - static void meson_drv_shutdown(struct platform_device *pdev) { struct meson_drm *priv = dev_get_drvdata(&pdev->dev); @@ -494,6 +462,13 @@ drm_atomic_helper_shutdown(priv->drm); } +/* Possible connectors nodes to ignore */ +static const struct of_device_id connectors_match[] = { + { .compatible = "composite-video-connector" }, + { .compatible = "svideo-connector" }, + {} +}; + static int meson_drv_probe(struct platform_device *pdev) { struct component_match *match = NULL; @@ -508,8 +483,21 @@ continue; } - count += meson_probe_remote(pdev, &match, np, remote); + /* If an analog connector is detected, count it as an output */ + if (of_match_node(connectors_match, remote)) { + ++count; + of_node_put(remote); + continue; + } + + dev_dbg(&pdev->dev, "parent %pOF remote match add %pOF parent %s\n", + np, remote, dev_name(&pdev->dev)); + + component_match_add(&pdev->dev, &match, compare_of, remote); + of_node_put(remote); + + ++count; } if (count && !match) diff -Naur a/drivers/gpu/drm/meson/meson_drv.h b/drivers/gpu/drm/meson/meson_drv.h --- a/drivers/gpu/drm/meson/meson_drv.h 2022-12-19 17:13:12.677518989 +0800 +++ b/drivers/gpu/drm/meson/meson_drv.h 2023-02-23 17:02:04.955751014 +0800 @@ -43,12 +43,14 @@ struct meson_canvas *canvas; u8 canvas_id_osd1; + u8 canvas_id_osd2; u8 canvas_id_vd1_0; u8 canvas_id_vd1_1; u8 canvas_id_vd1_2; struct drm_device *drm; struct drm_crtc *crtc; + struct drm_plane *cursor_plane; struct drm_plane *primary_plane; struct drm_plane *overlay_plane; @@ -82,6 +84,21 @@ uint32_t osd_blend_din0_scope_h; uint32_t osd_blend_din0_scope_v; uint32_t osb_blend0_size; + + bool osd2_enabled; + bool osd2_interlace; + bool osd2_commit; + uint32_t osd2_ctrl_stat; + uint32_t osd2_ctrl_stat2; + uint32_t osd2_blk0_cfg[5]; + uint32_t osd2_blk1_cfg4; + uint32_t osd2_blk2_cfg4; + uint32_t osd2_addr; + uint32_t osd2_stride; + uint32_t osd2_height; + uint32_t osd2_width; + uint32_t osd_blend_din3_scope_h; + uint32_t osd_blend_din3_scope_v; uint32_t osb_blend1_size; bool vd1_enabled; diff -Naur a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c 2022-12-19 17:13:12.677518989 +0800 +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c 2023-02-23 17:02:04.955751014 +0800 @@ -22,14 +22,11 @@ #include #include -#include #include #include "meson_drv.h" #include "meson_dw_hdmi.h" #include "meson_registers.h" -#include "meson_vclk.h" -#include "meson_venc.h" #define DRIVER_NAME "meson-dw-hdmi" #define DRIVER_DESC "Amlogic Meson HDMI-TX DRM driver" @@ -135,8 +132,6 @@ }; struct meson_dw_hdmi { - struct drm_encoder encoder; - struct drm_bridge bridge; struct dw_hdmi_plat_data dw_plat_data; struct meson_drm *priv; struct device *dev; @@ -148,12 +143,8 @@ struct regulator *hdmi_supply; u32 irq_stat; struct dw_hdmi *hdmi; - unsigned long output_bus_fmt; + struct drm_bridge *bridge; }; -#define encoder_to_meson_dw_hdmi(x) \ - container_of(x, struct meson_dw_hdmi, encoder) -#define bridge_to_meson_dw_hdmi(x) \ - container_of(x, struct meson_dw_hdmi, bridge) static inline int dw_hdmi_is_compatible(struct meson_dw_hdmi *dw_hdmi, const char *compat) @@ -295,14 +286,14 @@ /* Setup PHY bandwidth modes */ static void meson_hdmi_phy_setup_mode(struct meson_dw_hdmi *dw_hdmi, - const struct drm_display_mode *mode) + const struct drm_display_mode *mode, + bool mode_is_420) { struct meson_drm *priv = dw_hdmi->priv; unsigned int pixel_clock = mode->clock; /* For 420, pixel clock is half unlike venc clock */ - if (dw_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) - pixel_clock /= 2; + if (mode_is_420) pixel_clock /= 2; if (dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxl-dw-hdmi") || dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxm-dw-hdmi")) { @@ -374,68 +365,25 @@ mdelay(2); } -static void dw_hdmi_set_vclk(struct meson_dw_hdmi *dw_hdmi, - const struct drm_display_mode *mode) -{ - struct meson_drm *priv = dw_hdmi->priv; - int vic = drm_match_cea_mode(mode); - unsigned int phy_freq; - unsigned int vclk_freq; - unsigned int venc_freq; - unsigned int hdmi_freq; - - vclk_freq = mode->clock; - - /* For 420, pixel clock is half unlike venc clock */ - if (dw_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) - vclk_freq /= 2; - - /* TMDS clock is pixel_clock * 10 */ - phy_freq = vclk_freq * 10; - - if (!vic) { - meson_vclk_setup(priv, MESON_VCLK_TARGET_DMT, phy_freq, - vclk_freq, vclk_freq, vclk_freq, false); - return; - } - - /* 480i/576i needs global pixel doubling */ - if (mode->flags & DRM_MODE_FLAG_DBLCLK) - vclk_freq *= 2; - - venc_freq = vclk_freq; - hdmi_freq = vclk_freq; - - /* VENC double pixels for 1080i, 720p and YUV420 modes */ - if (meson_venc_hdmi_venc_repeat(vic) || - dw_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) - venc_freq *= 2; - - vclk_freq = max(venc_freq, hdmi_freq); - - if (mode->flags & DRM_MODE_FLAG_DBLCLK) - venc_freq /= 2; - - DRM_DEBUG_DRIVER("vclk:%d phy=%d venc=%d hdmi=%d enci=%d\n", - phy_freq, vclk_freq, venc_freq, hdmi_freq, - priv->venc.hdmi_use_enci); - - meson_vclk_setup(priv, MESON_VCLK_TARGET_HDMI, phy_freq, vclk_freq, - venc_freq, hdmi_freq, priv->venc.hdmi_use_enci); -} - static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, const struct drm_display_info *display, const struct drm_display_mode *mode) { struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; + bool is_hdmi2_sink = display->hdmi.scdc.supported; struct meson_drm *priv = dw_hdmi->priv; unsigned int wr_clk = readl_relaxed(priv->io_base + _REG(VPU_HDMI_SETTING)); + bool mode_is_420 = false; DRM_DEBUG_DRIVER("\"%s\" div%d\n", mode->name, mode->clock > 340000 ? 40 : 10); + if (drm_mode_is_420_only(display, mode) || + (!is_hdmi2_sink && + drm_mode_is_420_also(display, mode))) + mode_is_420 = true; + /* Enable clocks */ regmap_update_bits(priv->hhi, HHI_HDMI_CLK_CNTL, 0xffff, 0x100); @@ -457,8 +405,7 @@ dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_BIST_CNTL, BIT(12)); /* TMDS pattern setup */ - if (mode->clock > 340000 && - dw_hdmi->output_bus_fmt == MEDIA_BUS_FMT_YUV8_1X24) { + if (mode->clock > 340000 && !mode_is_420) { dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, 0); dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, @@ -476,7 +423,7 @@ dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x2); /* Setup PHY parameters */ - meson_hdmi_phy_setup_mode(dw_hdmi, mode); + meson_hdmi_phy_setup_mode(dw_hdmi, mode, mode_is_420); /* Setup PHY */ regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, @@ -622,214 +569,15 @@ dw_hdmi_setup_rx_sense(dw_hdmi->hdmi, hpd_connected, hpd_connected); - drm_helper_hpd_irq_event(dw_hdmi->encoder.dev); + drm_helper_hpd_irq_event(dw_hdmi->bridge->dev); + drm_bridge_hpd_notify(dw_hdmi->bridge, + hpd_connected ? connector_status_connected + : connector_status_disconnected); } return IRQ_HANDLED; } -static enum drm_mode_status -dw_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data, - const struct drm_display_info *display_info, - const struct drm_display_mode *mode) -{ - struct meson_dw_hdmi *dw_hdmi = data; - struct meson_drm *priv = dw_hdmi->priv; - bool is_hdmi2_sink = display_info->hdmi.scdc.supported; - unsigned int phy_freq; - unsigned int vclk_freq; - unsigned int venc_freq; - unsigned int hdmi_freq; - int vic = drm_match_cea_mode(mode); - enum drm_mode_status status; - - DRM_DEBUG_DRIVER("Modeline " DRM_MODE_FMT "\n", DRM_MODE_ARG(mode)); - - /* If sink does not support 540MHz, reject the non-420 HDMI2 modes */ - if (display_info->max_tmds_clock && - mode->clock > display_info->max_tmds_clock && - !drm_mode_is_420_only(display_info, mode) && - !drm_mode_is_420_also(display_info, mode)) - return MODE_BAD; - - /* Check against non-VIC supported modes */ - if (!vic) { - status = meson_venc_hdmi_supported_mode(mode); - if (status != MODE_OK) - return status; - - return meson_vclk_dmt_supported_freq(priv, mode->clock); - /* Check against supported VIC modes */ - } else if (!meson_venc_hdmi_supported_vic(vic)) - return MODE_BAD; - - vclk_freq = mode->clock; - - /* For 420, pixel clock is half unlike venc clock */ - if (drm_mode_is_420_only(display_info, mode) || - (!is_hdmi2_sink && - drm_mode_is_420_also(display_info, mode))) - vclk_freq /= 2; - - /* TMDS clock is pixel_clock * 10 */ - phy_freq = vclk_freq * 10; - - /* 480i/576i needs global pixel doubling */ - if (mode->flags & DRM_MODE_FLAG_DBLCLK) - vclk_freq *= 2; - - venc_freq = vclk_freq; - hdmi_freq = vclk_freq; - - /* VENC double pixels for 1080i, 720p and YUV420 modes */ - if (meson_venc_hdmi_venc_repeat(vic) || - drm_mode_is_420_only(display_info, mode) || - (!is_hdmi2_sink && - drm_mode_is_420_also(display_info, mode))) - venc_freq *= 2; - - vclk_freq = max(venc_freq, hdmi_freq); - - if (mode->flags & DRM_MODE_FLAG_DBLCLK) - venc_freq /= 2; - - dev_dbg(dw_hdmi->dev, "%s: vclk:%d phy=%d venc=%d hdmi=%d\n", - __func__, phy_freq, vclk_freq, venc_freq, hdmi_freq); - - return meson_vclk_vic_supported_freq(priv, phy_freq, vclk_freq); -} - -/* Encoder */ - -static const u32 meson_dw_hdmi_out_bus_fmts[] = { - MEDIA_BUS_FMT_YUV8_1X24, - MEDIA_BUS_FMT_UYYVYY8_0_5X24, -}; - -static void meson_venc_hdmi_encoder_destroy(struct drm_encoder *encoder) -{ - drm_encoder_cleanup(encoder); -} - -static const struct drm_encoder_funcs meson_venc_hdmi_encoder_funcs = { - .destroy = meson_venc_hdmi_encoder_destroy, -}; - -static u32 * -meson_venc_hdmi_encoder_get_inp_bus_fmts(struct drm_bridge *bridge, - struct drm_bridge_state *bridge_state, - struct drm_crtc_state *crtc_state, - struct drm_connector_state *conn_state, - u32 output_fmt, - unsigned int *num_input_fmts) -{ - u32 *input_fmts = NULL; - int i; - - *num_input_fmts = 0; - - for (i = 0 ; i < ARRAY_SIZE(meson_dw_hdmi_out_bus_fmts) ; ++i) { - if (output_fmt == meson_dw_hdmi_out_bus_fmts[i]) { - *num_input_fmts = 1; - input_fmts = kcalloc(*num_input_fmts, - sizeof(*input_fmts), - GFP_KERNEL); - if (!input_fmts) - return NULL; - - input_fmts[0] = output_fmt; - - break; - } - } - - return input_fmts; -} - -static int meson_venc_hdmi_encoder_atomic_check(struct drm_bridge *bridge, - struct drm_bridge_state *bridge_state, - struct drm_crtc_state *crtc_state, - struct drm_connector_state *conn_state) -{ - struct meson_dw_hdmi *dw_hdmi = bridge_to_meson_dw_hdmi(bridge); - - dw_hdmi->output_bus_fmt = bridge_state->output_bus_cfg.format; - - DRM_DEBUG_DRIVER("output_bus_fmt %lx\n", dw_hdmi->output_bus_fmt); - - return 0; -} - -static void meson_venc_hdmi_encoder_disable(struct drm_bridge *bridge) -{ - struct meson_dw_hdmi *dw_hdmi = bridge_to_meson_dw_hdmi(bridge); - struct meson_drm *priv = dw_hdmi->priv; - - DRM_DEBUG_DRIVER("\n"); - - writel_bits_relaxed(0x3, 0, - priv->io_base + _REG(VPU_HDMI_SETTING)); - - writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN)); - writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN)); -} - -static void meson_venc_hdmi_encoder_enable(struct drm_bridge *bridge) -{ - struct meson_dw_hdmi *dw_hdmi = bridge_to_meson_dw_hdmi(bridge); - struct meson_drm *priv = dw_hdmi->priv; - - DRM_DEBUG_DRIVER("%s\n", priv->venc.hdmi_use_enci ? "VENCI" : "VENCP"); - - if (priv->venc.hdmi_use_enci) - writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN)); - else - writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN)); -} - -static void meson_venc_hdmi_encoder_mode_set(struct drm_bridge *bridge, - const struct drm_display_mode *mode, - const struct drm_display_mode *adjusted_mode) -{ - struct meson_dw_hdmi *dw_hdmi = bridge_to_meson_dw_hdmi(bridge); - struct meson_drm *priv = dw_hdmi->priv; - int vic = drm_match_cea_mode(mode); - unsigned int ycrcb_map = VPU_HDMI_OUTPUT_CBYCR; - bool yuv420_mode = false; - - DRM_DEBUG_DRIVER("\"%s\" vic %d\n", mode->name, vic); - - if (dw_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) { - ycrcb_map = VPU_HDMI_OUTPUT_CRYCB; - yuv420_mode = true; - } - - /* VENC + VENC-DVI Mode setup */ - meson_venc_hdmi_mode_set(priv, vic, ycrcb_map, yuv420_mode, mode); - - /* VCLK Set clock */ - dw_hdmi_set_vclk(dw_hdmi, mode); - - if (dw_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) - /* Setup YUV420 to HDMI-TX, no 10bit diphering */ - writel_relaxed(2 | (2 << 2), - priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); - else - /* Setup YUV444 to HDMI-TX, no 10bit diphering */ - writel_relaxed(0, priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); -} - -static const struct drm_bridge_funcs meson_venc_hdmi_encoder_bridge_funcs = { - .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, - .atomic_get_input_bus_fmts = meson_venc_hdmi_encoder_get_inp_bus_fmts, - .atomic_reset = drm_atomic_helper_bridge_reset, - .atomic_check = meson_venc_hdmi_encoder_atomic_check, - .enable = meson_venc_hdmi_encoder_enable, - .disable = meson_venc_hdmi_encoder_disable, - .mode_set = meson_venc_hdmi_encoder_mode_set, -}; - /* DW HDMI Regmap */ static int meson_dw_hdmi_reg_read(void *context, unsigned int reg, @@ -876,28 +624,6 @@ .dwc_write = dw_hdmi_g12a_dwc_write, }; -static bool meson_hdmi_connector_is_available(struct device *dev) -{ - struct device_node *ep, *remote; - - /* HDMI Connector is on the second port, first endpoint */ - ep = of_graph_get_endpoint_by_regs(dev->of_node, 1, 0); - if (!ep) - return false; - - /* If the endpoint node exists, consider it enabled */ - remote = of_graph_get_remote_port(ep); - if (remote) { - of_node_put(ep); - return true; - } - - of_node_put(ep); - of_node_put(remote); - - return false; -} - static void meson_dw_hdmi_init(struct meson_dw_hdmi *meson_dw_hdmi) { struct meson_drm *priv = meson_dw_hdmi->priv; @@ -976,19 +702,11 @@ struct drm_device *drm = data; struct meson_drm *priv = drm->dev_private; struct dw_hdmi_plat_data *dw_plat_data; - struct drm_bridge *next_bridge; - struct drm_encoder *encoder; - struct resource *res; int irq; int ret; DRM_DEBUG_DRIVER("\n"); - if (!meson_hdmi_connector_is_available(dev)) { - dev_info(drm->dev, "HDMI Output connector not available\n"); - return -ENODEV; - } - match = of_device_get_match_data(&pdev->dev); if (!match) { dev_err(&pdev->dev, "failed to get match data\n"); @@ -1004,7 +722,6 @@ meson_dw_hdmi->dev = dev; meson_dw_hdmi->data = match; dw_plat_data = &meson_dw_hdmi->dw_plat_data; - encoder = &meson_dw_hdmi->encoder; meson_dw_hdmi->hdmi_supply = devm_regulator_get_optional(dev, "hdmi"); if (IS_ERR(meson_dw_hdmi->hdmi_supply)) { @@ -1042,8 +759,7 @@ return PTR_ERR(meson_dw_hdmi->hdmitx_phy); } - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - meson_dw_hdmi->hdmitx = devm_ioremap_resource(dev, res); + meson_dw_hdmi->hdmitx = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(meson_dw_hdmi->hdmitx)) return PTR_ERR(meson_dw_hdmi->hdmitx); @@ -1076,33 +792,18 @@ return ret; } - /* Encoder */ - - ret = drm_encoder_init(drm, encoder, &meson_venc_hdmi_encoder_funcs, - DRM_MODE_ENCODER_TMDS, "meson_hdmi"); - if (ret) { - dev_err(priv->dev, "Failed to init HDMI encoder\n"); - return ret; - } - - meson_dw_hdmi->bridge.funcs = &meson_venc_hdmi_encoder_bridge_funcs; - drm_bridge_attach(encoder, &meson_dw_hdmi->bridge, NULL, 0); - - encoder->possible_crtcs = BIT(0); - meson_dw_hdmi_init(meson_dw_hdmi); - DRM_DEBUG_DRIVER("encoder initialized\n"); - /* Bridge / Connector */ dw_plat_data->priv_data = meson_dw_hdmi; - dw_plat_data->mode_valid = dw_hdmi_mode_valid; dw_plat_data->phy_ops = &meson_dw_hdmi_phy_ops; dw_plat_data->phy_name = "meson_dw_hdmi_phy"; dw_plat_data->phy_data = meson_dw_hdmi; dw_plat_data->input_bus_encoding = V4L2_YCBCR_ENC_709; dw_plat_data->ycbcr_420_allowed = true; + dw_plat_data->disable_cec = true; + dw_plat_data->output_port = 1; if (dw_hdmi_is_compatible(meson_dw_hdmi, "amlogic,meson-gxl-dw-hdmi") || dw_hdmi_is_compatible(meson_dw_hdmi, "amlogic,meson-gxm-dw-hdmi") || @@ -1111,15 +812,15 @@ platform_set_drvdata(pdev, meson_dw_hdmi); - meson_dw_hdmi->hdmi = dw_hdmi_probe(pdev, - &meson_dw_hdmi->dw_plat_data); + meson_dw_hdmi->hdmi = dw_hdmi_probe(pdev, &meson_dw_hdmi->dw_plat_data); if (IS_ERR(meson_dw_hdmi->hdmi)) return PTR_ERR(meson_dw_hdmi->hdmi); - next_bridge = of_drm_find_bridge(pdev->dev.of_node); - if (next_bridge) - drm_bridge_attach(encoder, next_bridge, - &meson_dw_hdmi->bridge, 0); + meson_dw_hdmi->bridge = of_drm_find_bridge(pdev->dev.of_node); + +#ifdef CONFIG_DRIVERS_HDF_AUDIO + dw_hdmi_audio_enable(meson_dw_hdmi->hdmi); +#endif DRM_DEBUG_DRIVER("HDMI controller initialized\n"); diff -Naur a/drivers/gpu/drm/meson/meson_dw_mipi_dsi.c b/drivers/gpu/drm/meson/meson_dw_mipi_dsi.c --- a/drivers/gpu/drm/meson/meson_dw_mipi_dsi.c 1970-01-01 08:00:00.000000000 +0800 +++ b/drivers/gpu/drm/meson/meson_dw_mipi_dsi.c 2023-02-23 17:02:04.955751014 +0800 @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 BayLibre, SAS + * Author: Neil Armstrong + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include