* [PATCH v4 8/8] MAINTAINERS: Add entry for BM1880 SoC clock driver
From: Manivannan Sadhasivam @ 2019-08-22 17:24 UTC (permalink / raw)
To: sboyd, mturquette, robh+dt
Cc: devicetree, Manivannan Sadhasivam, darren.tsao, linux-kernel,
linux-arm-kernel, fisher.cheng, alec.lin, linux-clk, haitao.suo
In-Reply-To: <20190822172426.25879-1-manivannan.sadhasivam@linaro.org>
Add MAINTAINERS entry for Bitmain BM1880 SoC clock driver.
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
---
MAINTAINERS | 2 ++
1 file changed, 2 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 997a4f8fe88e..280defec35b2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1503,8 +1503,10 @@ M: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Maintained
F: arch/arm64/boot/dts/bitmain/
+F: drivers/clk/clk-bm1880.c
F: drivers/pinctrl/pinctrl-bm1880.c
F: Documentation/devicetree/bindings/arm/bitmain.yaml
+F: Documentation/devicetree/bindings/clock/bitmain,bm1880-clk.yaml
F: Documentation/devicetree/bindings/pinctrl/bitmain,bm1880-pinctrl.txt
ARM/CALXEDA HIGHBANK ARCHITECTURE
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v4 7/8] clk: Add common clock driver for BM1880 SoC
From: Manivannan Sadhasivam @ 2019-08-22 17:24 UTC (permalink / raw)
To: sboyd, mturquette, robh+dt
Cc: devicetree, Manivannan Sadhasivam, darren.tsao, linux-kernel,
linux-arm-kernel, fisher.cheng, alec.lin, linux-clk, haitao.suo
In-Reply-To: <20190822172426.25879-1-manivannan.sadhasivam@linaro.org>
Add common clock driver for Bitmain BM1880 SoC. The clock controller on
BM1880 has supplies clocks to all peripherals in the form of gate clocks
and composite clocks (fixed factor + gate).
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
---
drivers/clk/Kconfig | 7 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-bm1880.c | 966 +++++++++++++++++++++++++++++++++++++++
3 files changed, 974 insertions(+)
create mode 100644 drivers/clk/clk-bm1880.c
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 801fa1cd0321..e70c64e43ff9 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -139,6 +139,13 @@ config COMMON_CLK_SI570
This driver supports Silicon Labs 570/571/598/599 programmable
clock generators.
+config COMMON_CLK_BM1880
+ bool "Clock driver for Bitmain BM1880 SoC"
+ depends on ARCH_BITMAIN || COMPILE_TEST
+ default ARCH_BITMAIN
+ help
+ This driver supports the clocks on Bitmain BM1880 SoC.
+
config COMMON_CLK_CDCE706
tristate "Clock driver for TI CDCE706 clock synthesizer"
depends on I2C
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 0cad76021297..2c1ae6289a78 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_MACH_ASM9260) += clk-asm9260.o
obj-$(CONFIG_COMMON_CLK_AXI_CLKGEN) += clk-axi-clkgen.o
obj-$(CONFIG_ARCH_AXXIA) += clk-axm5516.o
obj-$(CONFIG_COMMON_CLK_BD718XX) += clk-bd718x7.o
+obj-$(CONFIG_COMMON_CLK_BM1880) += clk-bm1880.o
obj-$(CONFIG_COMMON_CLK_CDCE706) += clk-cdce706.o
obj-$(CONFIG_COMMON_CLK_CDCE925) += clk-cdce925.o
obj-$(CONFIG_ARCH_CLPS711X) += clk-clps711x.o
diff --git a/drivers/clk/clk-bm1880.c b/drivers/clk/clk-bm1880.c
new file mode 100644
index 000000000000..3b10de929fd4
--- /dev/null
+++ b/drivers/clk/clk-bm1880.c
@@ -0,0 +1,966 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bitmain BM1880 SoC clock driver
+ *
+ * Copyright (c) 2019 Linaro Ltd.
+ * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/bm1880-clock.h>
+
+#define BM1880_CLK_MPLL_CTL 0x00
+#define BM1880_CLK_SPLL_CTL 0x04
+#define BM1880_CLK_FPLL_CTL 0x08
+#define BM1880_CLK_DDRPLL_CTL 0x0c
+
+#define BM1880_CLK_ENABLE0 0x00
+#define BM1880_CLK_ENABLE1 0x04
+#define BM1880_CLK_SELECT 0x20
+#define BM1880_CLK_DIV0 0x40
+#define BM1880_CLK_DIV1 0x44
+#define BM1880_CLK_DIV2 0x48
+#define BM1880_CLK_DIV3 0x4c
+#define BM1880_CLK_DIV4 0x50
+#define BM1880_CLK_DIV5 0x54
+#define BM1880_CLK_DIV6 0x58
+#define BM1880_CLK_DIV7 0x5c
+#define BM1880_CLK_DIV8 0x60
+#define BM1880_CLK_DIV9 0x64
+#define BM1880_CLK_DIV10 0x68
+#define BM1880_CLK_DIV11 0x6c
+#define BM1880_CLK_DIV12 0x70
+#define BM1880_CLK_DIV13 0x74
+#define BM1880_CLK_DIV14 0x78
+#define BM1880_CLK_DIV15 0x7c
+#define BM1880_CLK_DIV16 0x80
+#define BM1880_CLK_DIV17 0x84
+#define BM1880_CLK_DIV18 0x88
+#define BM1880_CLK_DIV19 0x8c
+#define BM1880_CLK_DIV20 0x90
+#define BM1880_CLK_DIV21 0x94
+#define BM1880_CLK_DIV22 0x98
+#define BM1880_CLK_DIV23 0x9c
+#define BM1880_CLK_DIV24 0xa0
+#define BM1880_CLK_DIV25 0xa4
+#define BM1880_CLK_DIV26 0xa8
+#define BM1880_CLK_DIV27 0xac
+#define BM1880_CLK_DIV28 0xb0
+
+#define to_bm1880_pll_clk(_hw) container_of(_hw, struct bm1880_pll_hw_clock, hw)
+#define to_bm1880_div_clk(_hw) container_of(_hw, struct bm1880_div_hw_clock, hw)
+
+static DEFINE_SPINLOCK(bm1880_clk_lock);
+
+struct bm1880_clock_data {
+ void __iomem *pll_base;
+ void __iomem *sys_base;
+ struct clk_hw_onecell_data *clk_data;
+};
+
+struct bm1880_gate_clock {
+ unsigned int id;
+ const char *name;
+ const char *parent;
+ u32 gate_reg;
+ s8 gate_shift;
+ unsigned long flags;
+};
+
+struct bm1880_mux_clock {
+ unsigned int id;
+ const char *name;
+ const char * const *parents;
+ s8 num_parents;
+ u32 reg;
+ s8 shift;
+ unsigned long flags;
+};
+
+struct bm1880_div_clock {
+ unsigned int id;
+ const char *name;
+ u32 reg;
+ u8 shift;
+ u8 width;
+ u32 initval;
+ const struct clk_div_table *table;
+ unsigned long flags;
+};
+
+struct bm1880_div_hw_clock {
+ struct bm1880_div_clock div;
+ void __iomem *base;
+ spinlock_t *lock;
+ struct clk_hw hw;
+ struct clk_init_data init;
+};
+
+struct bm1880_composite_clock {
+ unsigned int id;
+ const char *name;
+ const char *parent;
+ const char * const *parents;
+ unsigned int num_parents;
+ unsigned long flags;
+
+ u32 gate_reg;
+ u32 mux_reg;
+ u32 div_reg;
+
+ s8 gate_shift;
+ s8 mux_shift;
+ s8 div_shift;
+ s8 div_width;
+ s16 div_initval;
+ const struct clk_div_table *table;
+};
+
+struct bm1880_pll_clock {
+ unsigned int id;
+ const char *name;
+ u32 reg;
+ unsigned long flags;
+};
+
+struct bm1880_pll_hw_clock {
+ struct bm1880_pll_clock pll;
+ void __iomem *base;
+ struct clk_hw hw;
+ struct clk_init_data init;
+};
+
+static const struct clk_ops bm1880_pll_ops;
+static const struct clk_ops bm1880_clk_div_ops;
+
+#define GATE_DIV(_id, _name, _parent, _gate_reg, _gate_shift, _div_reg, \
+ _div_shift, _div_width, _div_initval, _table, \
+ _flags) { \
+ .id = _id, \
+ .parent = _parent, \
+ .name = _name, \
+ .gate_reg = _gate_reg, \
+ .gate_shift = _gate_shift, \
+ .div_reg = _div_reg, \
+ .div_shift = _div_shift, \
+ .div_width = _div_width, \
+ .div_initval = _div_initval, \
+ .table = _table, \
+ .mux_shift = -1, \
+ .flags = _flags, \
+ }
+
+#define GATE_MUX(_id, _name, _parents, _gate_reg, _gate_shift, \
+ _mux_reg, _mux_shift, _flags) { \
+ .id = _id, \
+ .parents = _parents, \
+ .num_parents = ARRAY_SIZE(_parents), \
+ .name = _name, \
+ .gate_reg = _gate_reg, \
+ .gate_shift = _gate_shift, \
+ .div_shift = -1, \
+ .mux_reg = _mux_reg, \
+ .mux_shift = _mux_shift, \
+ .flags = _flags, \
+ }
+
+#define CLK_PLL(_id, _name, _parent, _reg, _flags) { \
+ .pll.id = _id, \
+ .pll.name = _name, \
+ .pll.reg = _reg, \
+ .hw.init = CLK_HW_INIT_PARENTS_DATA(_name, _parent, \
+ &bm1880_pll_ops, \
+ _flags), \
+ }
+
+#define CLK_DIV(_id, _name, _parent, _reg, _shift, _width, _initval, \
+ _table, _flags) { \
+ .div.id = _id, \
+ .div.name = _name, \
+ .div.reg = _reg, \
+ .div.shift = _shift, \
+ .div.width = _width, \
+ .div.initval = _initval, \
+ .div.table = _table, \
+ .hw.init = CLK_HW_INIT_HW(_name, _parent, \
+ &bm1880_clk_div_ops, \
+ _flags), \
+ }
+
+static struct clk_parent_data bm1880_pll_parent[] = {
+ { .fw_name = "osc", .name = "osc" },
+};
+
+/*
+ * All PLL clocks are marked as CRITICAL, hence they are very crucial
+ * for the functioning of the SoC
+ */
+static struct bm1880_pll_hw_clock bm1880_pll_clks[] = {
+ CLK_PLL(BM1880_CLK_MPLL, "clk_mpll", bm1880_pll_parent,
+ BM1880_CLK_MPLL_CTL, CLK_IS_CRITICAL),
+ CLK_PLL(BM1880_CLK_SPLL, "clk_spll", bm1880_pll_parent,
+ BM1880_CLK_SPLL_CTL, CLK_IS_CRITICAL),
+ CLK_PLL(BM1880_CLK_FPLL, "clk_fpll", bm1880_pll_parent,
+ BM1880_CLK_FPLL_CTL, CLK_IS_CRITICAL),
+ CLK_PLL(BM1880_CLK_DDRPLL, "clk_ddrpll", bm1880_pll_parent,
+ BM1880_CLK_DDRPLL_CTL, CLK_IS_CRITICAL),
+};
+
+/*
+ * Clocks marked as CRITICAL are needed for the proper functioning
+ * of the SoC.
+ */
+static const struct bm1880_gate_clock bm1880_gate_clks[] = {
+ { BM1880_CLK_AHB_ROM, "clk_ahb_rom", "clk_mux_axi6",
+ BM1880_CLK_ENABLE0, 2, CLK_IS_CRITICAL },
+ { BM1880_CLK_AXI_SRAM, "clk_axi_sram", "clk_axi1",
+ BM1880_CLK_ENABLE0, 3, CLK_IS_CRITICAL },
+ { BM1880_CLK_DDR_AXI, "clk_ddr_axi", "clk_mux_axi6",
+ BM1880_CLK_ENABLE0, 4, CLK_IS_CRITICAL },
+ { BM1880_CLK_APB_EFUSE, "clk_apb_efuse", "clk_mux_axi6",
+ BM1880_CLK_ENABLE0, 6, CLK_IS_CRITICAL },
+ { BM1880_CLK_AXI5_EMMC, "clk_axi5_emmc", "clk_axi5",
+ BM1880_CLK_ENABLE0, 7, 0 },
+ { BM1880_CLK_AXI5_SD, "clk_axi5_sd", "clk_axi5",
+ BM1880_CLK_ENABLE0, 10, 0 },
+ { BM1880_CLK_AXI4_ETH0, "clk_axi4_eth0", "clk_axi4",
+ BM1880_CLK_ENABLE0, 14, 0 },
+ { BM1880_CLK_AXI4_ETH1, "clk_axi4_eth1", "clk_axi4",
+ BM1880_CLK_ENABLE0, 16, 0 },
+ { BM1880_CLK_AXI1_GDMA, "clk_axi1_gdma", "clk_axi1",
+ BM1880_CLK_ENABLE0, 17, 0 },
+ /* Don't gate GPIO clocks as it is not owned by the GPIO driver */
+ { BM1880_CLK_APB_GPIO, "clk_apb_gpio", "clk_mux_axi6",
+ BM1880_CLK_ENABLE0, 18, CLK_IGNORE_UNUSED },
+ { BM1880_CLK_APB_GPIO_INTR, "clk_apb_gpio_intr", "clk_mux_axi6",
+ BM1880_CLK_ENABLE0, 19, CLK_IGNORE_UNUSED },
+ { BM1880_CLK_AXI1_MINER, "clk_axi1_miner", "clk_axi1",
+ BM1880_CLK_ENABLE0, 21, 0 },
+ { BM1880_CLK_AHB_SF, "clk_ahb_sf", "clk_mux_axi6",
+ BM1880_CLK_ENABLE0, 22, 0 },
+ { BM1880_CLK_SDMA_AXI, "clk_sdma_axi", "clk_axi5",
+ BM1880_CLK_ENABLE0, 23, 0 },
+ { BM1880_CLK_APB_I2C, "clk_apb_i2c", "clk_mux_axi6",
+ BM1880_CLK_ENABLE0, 25, 0 },
+ { BM1880_CLK_APB_WDT, "clk_apb_wdt", "clk_mux_axi6",
+ BM1880_CLK_ENABLE0, 26, 0 },
+ { BM1880_CLK_APB_JPEG, "clk_apb_jpeg", "clk_axi6",
+ BM1880_CLK_ENABLE0, 27, 0 },
+ { BM1880_CLK_AXI5_NF, "clk_axi5_nf", "clk_axi5",
+ BM1880_CLK_ENABLE0, 29, 0 },
+ { BM1880_CLK_APB_NF, "clk_apb_nf", "clk_axi6",
+ BM1880_CLK_ENABLE0, 30, 0 },
+ { BM1880_CLK_APB_PWM, "clk_apb_pwm", "clk_mux_axi6",
+ BM1880_CLK_ENABLE1, 0, 0 },
+ { BM1880_CLK_RV, "clk_rv", "clk_mux_rv",
+ BM1880_CLK_ENABLE1, 1, 0 },
+ { BM1880_CLK_APB_SPI, "clk_apb_spi", "clk_mux_axi6",
+ BM1880_CLK_ENABLE1, 2, 0 },
+ { BM1880_CLK_UART_500M, "clk_uart_500m", "clk_div_uart_500m",
+ BM1880_CLK_ENABLE1, 4, 0 },
+ { BM1880_CLK_APB_UART, "clk_apb_uart", "clk_axi6",
+ BM1880_CLK_ENABLE1, 5, 0 },
+ { BM1880_CLK_APB_I2S, "clk_apb_i2s", "clk_axi6",
+ BM1880_CLK_ENABLE1, 6, 0 },
+ { BM1880_CLK_AXI4_USB, "clk_axi4_usb", "clk_axi4",
+ BM1880_CLK_ENABLE1, 7, 0 },
+ { BM1880_CLK_APB_USB, "clk_apb_usb", "clk_axi6",
+ BM1880_CLK_ENABLE1, 8, 0 },
+ { BM1880_CLK_12M_USB, "clk_12m_usb", "clk_div_12m_usb",
+ BM1880_CLK_ENABLE1, 11, 0 },
+ { BM1880_CLK_APB_VIDEO, "clk_apb_video", "clk_axi6",
+ BM1880_CLK_ENABLE1, 12, 0 },
+ { BM1880_CLK_APB_VPP, "clk_apb_vpp", "clk_axi6",
+ BM1880_CLK_ENABLE1, 15, 0 },
+ { BM1880_CLK_AXI6, "clk_axi6", "clk_mux_axi6",
+ BM1880_CLK_ENABLE1, 21, CLK_IS_CRITICAL },
+};
+
+static const char * const clk_a53_parents[] = { "clk_spll", "clk_mpll" };
+static const char * const clk_rv_parents[] = { "clk_div_1_rv", "clk_div_0_rv" };
+static const char * const clk_axi1_parents[] = { "clk_div_1_axi1", "clk_div_0_axi1" };
+static const char * const clk_axi6_parents[] = { "clk_div_1_axi6", "clk_div_0_axi6" };
+
+static const struct bm1880_mux_clock bm1880_mux_clks[] = {
+ { BM1880_CLK_MUX_RV, "clk_mux_rv", clk_rv_parents, 2,
+ BM1880_CLK_SELECT, 1, 0 },
+ { BM1880_CLK_MUX_AXI6, "clk_mux_axi6", clk_axi6_parents, 2,
+ BM1880_CLK_SELECT, 3, 0 },
+};
+
+static const struct clk_div_table bm1880_div_table_0[] = {
+ { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 4 },
+ { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 8 },
+ { 8, 9 }, { 9, 10 }, { 10, 11 }, { 11, 12 },
+ { 12, 13 }, { 13, 14 }, { 14, 15 }, { 15, 16 },
+ { 16, 17 }, { 17, 18 }, { 18, 19 }, { 19, 20 },
+ { 20, 21 }, { 21, 22 }, { 22, 23 }, { 23, 24 },
+ { 24, 25 }, { 25, 26 }, { 26, 27 }, { 27, 28 },
+ { 28, 29 }, { 29, 30 }, { 30, 31 }, { 31, 32 },
+ { 0, 0 }
+};
+
+static const struct clk_div_table bm1880_div_table_1[] = {
+ { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 4 },
+ { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 8 },
+ { 8, 9 }, { 9, 10 }, { 10, 11 }, { 11, 12 },
+ { 12, 13 }, { 13, 14 }, { 14, 15 }, { 15, 16 },
+ { 16, 17 }, { 17, 18 }, { 18, 19 }, { 19, 20 },
+ { 20, 21 }, { 21, 22 }, { 22, 23 }, { 23, 24 },
+ { 24, 25 }, { 25, 26 }, { 26, 27 }, { 27, 28 },
+ { 28, 29 }, { 29, 30 }, { 30, 31 }, { 31, 32 },
+ { 127, 128 }, { 0, 0 }
+};
+
+static const struct clk_div_table bm1880_div_table_2[] = {
+ { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 4 },
+ { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 8 },
+ { 8, 9 }, { 9, 10 }, { 10, 11 }, { 11, 12 },
+ { 12, 13 }, { 13, 14 }, { 14, 15 }, { 15, 16 },
+ { 16, 17 }, { 17, 18 }, { 18, 19 }, { 19, 20 },
+ { 20, 21 }, { 21, 22 }, { 22, 23 }, { 23, 24 },
+ { 24, 25 }, { 25, 26 }, { 26, 27 }, { 27, 28 },
+ { 28, 29 }, { 29, 30 }, { 30, 31 }, { 31, 32 },
+ { 127, 128 }, { 255, 256 }, { 0, 0 }
+};
+
+static const struct clk_div_table bm1880_div_table_3[] = {
+ { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 4 },
+ { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 8 },
+ { 8, 9 }, { 9, 10 }, { 10, 11 }, { 11, 12 },
+ { 12, 13 }, { 13, 14 }, { 14, 15 }, { 15, 16 },
+ { 16, 17 }, { 17, 18 }, { 18, 19 }, { 19, 20 },
+ { 20, 21 }, { 21, 22 }, { 22, 23 }, { 23, 24 },
+ { 24, 25 }, { 25, 26 }, { 26, 27 }, { 27, 28 },
+ { 28, 29 }, { 29, 30 }, { 30, 31 }, { 31, 32 },
+ { 127, 128 }, { 255, 256 }, { 511, 512 }, { 0, 0 }
+};
+
+static const struct clk_div_table bm1880_div_table_4[] = {
+ { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 4 },
+ { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 8 },
+ { 8, 9 }, { 9, 10 }, { 10, 11 }, { 11, 12 },
+ { 12, 13 }, { 13, 14 }, { 14, 15 }, { 15, 16 },
+ { 16, 17 }, { 17, 18 }, { 18, 19 }, { 19, 20 },
+ { 20, 21 }, { 21, 22 }, { 22, 23 }, { 23, 24 },
+ { 24, 25 }, { 25, 26 }, { 26, 27 }, { 27, 28 },
+ { 28, 29 }, { 29, 30 }, { 30, 31 }, { 31, 32 },
+ { 127, 128 }, { 255, 256 }, { 511, 512 }, { 65535, 65536 },
+ { 0, 0 }
+};
+
+/*
+ * Clocks marked as CRITICAL are needed for the proper functioning
+ * of the SoC.
+ */
+static struct bm1880_div_hw_clock bm1880_div_clks[] = {
+ CLK_DIV(BM1880_CLK_DIV_0_RV, "clk_div_0_rv", &bm1880_pll_clks[1].hw,
+ BM1880_CLK_DIV12, 16, 5, 1, bm1880_div_table_0, 0),
+ CLK_DIV(BM1880_CLK_DIV_1_RV, "clk_div_1_rv", &bm1880_pll_clks[2].hw,
+ BM1880_CLK_DIV13, 16, 5, 1, bm1880_div_table_0, 0),
+ CLK_DIV(BM1880_CLK_DIV_UART_500M, "clk_div_uart_500m", &bm1880_pll_clks[2].hw,
+ BM1880_CLK_DIV15, 16, 7, 3, bm1880_div_table_1, 0),
+ CLK_DIV(BM1880_CLK_DIV_0_AXI1, "clk_div_0_axi1", &bm1880_pll_clks[0].hw,
+ BM1880_CLK_DIV21, 16, 5, 2, bm1880_div_table_0,
+ CLK_IS_CRITICAL),
+ CLK_DIV(BM1880_CLK_DIV_1_AXI1, "clk_div_1_axi1", &bm1880_pll_clks[2].hw,
+ BM1880_CLK_DIV22, 16, 5, 3, bm1880_div_table_0,
+ CLK_IS_CRITICAL),
+ CLK_DIV(BM1880_CLK_DIV_0_AXI6, "clk_div_0_axi6", &bm1880_pll_clks[2].hw,
+ BM1880_CLK_DIV27, 16, 5, 15, bm1880_div_table_0,
+ CLK_IS_CRITICAL),
+ CLK_DIV(BM1880_CLK_DIV_1_AXI6, "clk_div_1_axi6", &bm1880_pll_clks[0].hw,
+ BM1880_CLK_DIV28, 16, 5, 11, bm1880_div_table_0,
+ CLK_IS_CRITICAL),
+ CLK_DIV(BM1880_CLK_DIV_12M_USB, "clk_div_12m_usb", &bm1880_pll_clks[2].hw,
+ BM1880_CLK_DIV18, 16, 7, 125, bm1880_div_table_1, 0),
+};
+
+/*
+ * Clocks marked as CRITICAL are all needed for the proper functioning
+ * of the SoC.
+ */
+static struct bm1880_composite_clock bm1880_composite_clks[] = {
+ GATE_MUX(BM1880_CLK_A53, "clk_a53", clk_a53_parents,
+ BM1880_CLK_ENABLE0, 0, BM1880_CLK_SELECT, 0,
+ CLK_IS_CRITICAL),
+ GATE_DIV(BM1880_CLK_50M_A53, "clk_50m_a53", "clk_fpll",
+ BM1880_CLK_ENABLE0, 1, BM1880_CLK_DIV0, 16, 5, 30,
+ bm1880_div_table_0, CLK_IS_CRITICAL),
+ GATE_DIV(BM1880_CLK_EFUSE, "clk_efuse", "clk_fpll",
+ BM1880_CLK_ENABLE0, 5, BM1880_CLK_DIV1, 16, 7, 60,
+ bm1880_div_table_1, 0),
+ GATE_DIV(BM1880_CLK_EMMC, "clk_emmc", "clk_fpll",
+ BM1880_CLK_ENABLE0, 8, BM1880_CLK_DIV2, 16, 5, 15,
+ bm1880_div_table_0, 0),
+ GATE_DIV(BM1880_CLK_100K_EMMC, "clk_100k_emmc", "clk_div_12m_usb",
+ BM1880_CLK_ENABLE0, 9, BM1880_CLK_DIV3, 16, 8, 120,
+ bm1880_div_table_2, 0),
+ GATE_DIV(BM1880_CLK_SD, "clk_sd", "clk_fpll",
+ BM1880_CLK_ENABLE0, 11, BM1880_CLK_DIV4, 16, 5, 15,
+ bm1880_div_table_0, 0),
+ GATE_DIV(BM1880_CLK_100K_SD, "clk_100k_sd", "clk_div_12m_usb",
+ BM1880_CLK_ENABLE0, 12, BM1880_CLK_DIV5, 16, 8, 120,
+ bm1880_div_table_2, 0),
+ GATE_DIV(BM1880_CLK_500M_ETH0, "clk_500m_eth0", "clk_fpll",
+ BM1880_CLK_ENABLE0, 13, BM1880_CLK_DIV6, 16, 5, 3,
+ bm1880_div_table_0, 0),
+ GATE_DIV(BM1880_CLK_500M_ETH1, "clk_500m_eth1", "clk_fpll",
+ BM1880_CLK_ENABLE0, 15, BM1880_CLK_DIV7, 16, 5, 3,
+ bm1880_div_table_0, 0),
+ /* Don't gate GPIO clocks as it is not owned by the GPIO driver */
+ GATE_DIV(BM1880_CLK_GPIO_DB, "clk_gpio_db", "clk_div_12m_usb",
+ BM1880_CLK_ENABLE0, 20, BM1880_CLK_DIV8, 16, 16, 120,
+ bm1880_div_table_4, CLK_IGNORE_UNUSED),
+ GATE_DIV(BM1880_CLK_SDMA_AUD, "clk_sdma_aud", "clk_fpll",
+ BM1880_CLK_ENABLE0, 24, BM1880_CLK_DIV9, 16, 7, 61,
+ bm1880_div_table_1, 0),
+ GATE_DIV(BM1880_CLK_JPEG_AXI, "clk_jpeg_axi", "clk_fpll",
+ BM1880_CLK_ENABLE0, 28, BM1880_CLK_DIV10, 16, 5, 4,
+ bm1880_div_table_0, 0),
+ GATE_DIV(BM1880_CLK_NF, "clk_nf", "clk_fpll",
+ BM1880_CLK_ENABLE0, 31, BM1880_CLK_DIV11, 16, 5, 30,
+ bm1880_div_table_0, 0),
+ GATE_DIV(BM1880_CLK_TPU_AXI, "clk_tpu_axi", "clk_spll",
+ BM1880_CLK_ENABLE1, 3, BM1880_CLK_DIV14, 16, 5, 1,
+ bm1880_div_table_0, 0),
+ GATE_DIV(BM1880_CLK_125M_USB, "clk_125m_usb", "clk_fpll",
+ BM1880_CLK_ENABLE1, 9, BM1880_CLK_DIV16, 16, 5, 12,
+ bm1880_div_table_0, 0),
+ GATE_DIV(BM1880_CLK_33K_USB, "clk_33k_usb", "clk_div_12m_usb",
+ BM1880_CLK_ENABLE1, 10, BM1880_CLK_DIV17, 16, 9, 363,
+ bm1880_div_table_3, 0),
+ GATE_DIV(BM1880_CLK_VIDEO_AXI, "clk_video_axi", "clk_fpll",
+ BM1880_CLK_ENABLE1, 13, BM1880_CLK_DIV19, 16, 5, 4,
+ bm1880_div_table_0, 0),
+ GATE_DIV(BM1880_CLK_VPP_AXI, "clk_vpp_axi", "clk_fpll",
+ BM1880_CLK_ENABLE1, 14, BM1880_CLK_DIV20, 16, 5, 4,
+ bm1880_div_table_0, 0),
+ GATE_MUX(BM1880_CLK_AXI1, "clk_axi1", clk_axi1_parents,
+ BM1880_CLK_ENABLE1, 15, BM1880_CLK_SELECT, 2,
+ CLK_IS_CRITICAL),
+ GATE_DIV(BM1880_CLK_AXI2, "clk_axi2", "clk_fpll",
+ BM1880_CLK_ENABLE1, 17, BM1880_CLK_DIV23, 16, 5, 3,
+ bm1880_div_table_0, CLK_IS_CRITICAL),
+ GATE_DIV(BM1880_CLK_AXI3, "clk_axi3", "clk_mux_rv",
+ BM1880_CLK_ENABLE1, 18, BM1880_CLK_DIV24, 16, 5, 2,
+ bm1880_div_table_0, CLK_IS_CRITICAL),
+ GATE_DIV(BM1880_CLK_AXI4, "clk_axi4", "clk_fpll",
+ BM1880_CLK_ENABLE1, 19, BM1880_CLK_DIV25, 16, 5, 6,
+ bm1880_div_table_0, CLK_IS_CRITICAL),
+ GATE_DIV(BM1880_CLK_AXI5, "clk_axi5", "clk_fpll",
+ BM1880_CLK_ENABLE1, 20, BM1880_CLK_DIV26, 16, 5, 15,
+ bm1880_div_table_0, CLK_IS_CRITICAL),
+};
+
+static unsigned long bm1880_pll_rate_calc(u32 regval, unsigned long parent_rate)
+{
+ u32 fbdiv, fref, refdiv;
+ u32 postdiv1, postdiv2;
+ unsigned long rate, numerator, denominator;
+
+ fbdiv = (regval >> 16) & 0xfff;
+ fref = parent_rate;
+ refdiv = regval & 0x1f;
+ postdiv1 = (regval >> 8) & 0x7;
+ postdiv2 = (regval >> 12) & 0x7;
+
+ numerator = parent_rate * fbdiv;
+ denominator = refdiv * postdiv1 * postdiv2;
+ do_div(numerator, denominator);
+ rate = numerator;
+
+ return rate;
+}
+
+static unsigned long bm1880_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct bm1880_pll_hw_clock *pll_hw = to_bm1880_pll_clk(hw);
+ unsigned long rate;
+ u32 regval;
+
+ regval = readl(pll_hw->base + pll_hw->pll.reg);
+ rate = bm1880_pll_rate_calc(regval, parent_rate);
+
+ return rate;
+}
+
+static const struct clk_ops bm1880_pll_ops = {
+ .recalc_rate = bm1880_pll_recalc_rate,
+};
+
+static struct clk_hw *bm1880_clk_register_pll(struct bm1880_pll_hw_clock *pll_clk,
+ void __iomem *sys_base)
+{
+ struct clk_hw *hw;
+ int err;
+
+ pll_clk->base = sys_base;
+ hw = &pll_clk->hw;
+
+ err = clk_hw_register(NULL, hw);
+ if (err)
+ return ERR_PTR(err);
+
+ return hw;
+}
+
+static void bm1880_clk_unregister_pll(struct clk_hw *hw)
+{
+ struct bm1880_pll_hw_clock *pll_hw = to_bm1880_pll_clk(hw);
+
+ clk_hw_unregister(hw);
+ kfree(pll_hw);
+}
+
+static int bm1880_clk_register_plls(struct bm1880_pll_hw_clock *clks,
+ int num_clks, struct bm1880_clock_data *data)
+{
+ struct clk_hw *hw;
+ void __iomem *pll_base = data->pll_base;
+ int i;
+
+ for (i = 0; i < num_clks; i++) {
+ struct bm1880_pll_hw_clock *bm1880_clk = &clks[i];
+
+ hw = bm1880_clk_register_pll(bm1880_clk, pll_base);
+ if (IS_ERR(hw)) {
+ pr_err("%s: failed to register clock %s\n",
+ __func__, bm1880_clk->pll.name);
+ goto err_clk;
+ }
+
+ data->clk_data->hws[clks[i].pll.id] = hw;
+ }
+
+ return 0;
+
+err_clk:
+ while (i--)
+ bm1880_clk_unregister_pll(data->clk_data->hws[clks[i].pll.id]);
+
+ return PTR_ERR(hw);
+}
+
+static int bm1880_clk_register_mux(const struct bm1880_mux_clock *clks,
+ int num_clks, struct bm1880_clock_data *data)
+{
+ struct clk_hw *hw;
+ void __iomem *sys_base = data->sys_base;
+ int i;
+
+ for (i = 0; i < num_clks; i++) {
+ hw = clk_hw_register_mux(NULL, clks[i].name,
+ clks[i].parents,
+ clks[i].num_parents,
+ clks[i].flags,
+ sys_base + clks[i].reg,
+ clks[i].shift, 1, 0,
+ &bm1880_clk_lock);
+ if (IS_ERR(hw)) {
+ pr_err("%s: failed to register clock %s\n",
+ __func__, clks[i].name);
+ goto err_clk;
+ }
+
+ data->clk_data->hws[clks[i].id] = hw;
+ }
+
+ return 0;
+
+err_clk:
+ while (i--)
+ clk_hw_unregister_mux(data->clk_data->hws[clks[i].id]);
+
+ return PTR_ERR(hw);
+}
+
+static unsigned long bm1880_clk_div_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct bm1880_div_hw_clock *div_hw = to_bm1880_div_clk(hw);
+ struct bm1880_div_clock *div = &div_hw->div;
+ void __iomem *reg_addr = div_hw->base + div->reg;
+ unsigned int val;
+ unsigned long rate;
+
+ if (!(readl(reg_addr) & BIT(3))) {
+ val = div->initval;
+ } else {
+ val = readl(reg_addr) >> div->shift;
+ val &= clk_div_mask(div->width);
+ }
+
+ rate = divider_recalc_rate(hw, parent_rate, val, div->table,
+ div->flags, div->width);
+
+ return rate;
+}
+
+static long bm1880_clk_div_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct bm1880_div_hw_clock *div_hw = to_bm1880_div_clk(hw);
+ struct bm1880_div_clock *div = &div_hw->div;
+ void __iomem *reg_addr = div_hw->base + div->reg;
+
+ if (div->flags & CLK_DIVIDER_READ_ONLY) {
+ u32 val;
+
+ val = readl(reg_addr) >> div->shift;
+ val &= clk_div_mask(div->width);
+
+ return divider_ro_round_rate(hw, rate, prate, div->table,
+ div->width, div->flags,
+ val);
+ }
+
+ return divider_round_rate(hw, rate, prate, div->table,
+ div->width, div->flags);
+}
+
+static int bm1880_clk_div_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct bm1880_div_hw_clock *div_hw = to_bm1880_div_clk(hw);
+ struct bm1880_div_clock *div = &div_hw->div;
+ void __iomem *reg_addr = div_hw->base + div->reg;
+ unsigned long flags = 0;
+ int value;
+ u32 val;
+
+ value = divider_get_val(rate, parent_rate, div->table,
+ div->width, div_hw->div.flags);
+ if (value < 0)
+ return value;
+
+ if (div_hw->lock)
+ spin_lock_irqsave(div_hw->lock, flags);
+ else
+ __acquire(div_hw->lock);
+
+ if (div->flags & CLK_DIVIDER_HIWORD_MASK) {
+ val = clk_div_mask(div->width) << (div_hw->div.shift + 16);
+ } else {
+ val = readl(reg_addr);
+ val &= ~(clk_div_mask(div->width) << div_hw->div.shift);
+ }
+ val |= (u32)value << div->shift;
+ writel(val, reg_addr);
+
+ if (div_hw->lock)
+ spin_unlock_irqrestore(div_hw->lock, flags);
+ else
+ __release(div_hw->lock);
+
+ return 0;
+}
+
+static const struct clk_ops bm1880_clk_div_ops = {
+ .recalc_rate = bm1880_clk_div_recalc_rate,
+ .round_rate = bm1880_clk_div_round_rate,
+ .set_rate = bm1880_clk_div_set_rate,
+};
+
+static struct clk_hw *bm1880_clk_register_div(struct bm1880_div_hw_clock *div_clk,
+ void __iomem *sys_base)
+{
+ struct clk_hw *hw;
+ int err;
+
+ div_clk->div.flags = CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO;
+ div_clk->base = sys_base;
+ div_clk->lock = &bm1880_clk_lock;
+
+ hw = &div_clk->hw;
+ err = clk_hw_register(NULL, hw);
+ if (err)
+ return ERR_PTR(err);
+
+ return hw;
+}
+
+static void bm1880_clk_unregister_div(struct clk_hw *hw)
+{
+ struct bm1880_div_hw_clock *div_hw = to_bm1880_div_clk(hw);
+
+ clk_hw_unregister(hw);
+ kfree(div_hw);
+}
+
+static int bm1880_clk_register_divs(struct bm1880_div_hw_clock *clks,
+ int num_clks, struct bm1880_clock_data *data)
+{
+ struct clk_hw *hw;
+ void __iomem *sys_base = data->sys_base;
+ int i;
+
+ for (i = 0; i < num_clks; i++) {
+ struct bm1880_div_hw_clock *bm1880_clk = &clks[i];
+
+ hw = bm1880_clk_register_div(bm1880_clk, sys_base);
+ if (IS_ERR(hw)) {
+ pr_err("%s: failed to register clock %s\n",
+ __func__, bm1880_clk->div.name);
+ goto err_clk;
+ }
+
+ data->clk_data->hws[clks[i].div.id] = hw;
+ }
+
+ return 0;
+
+err_clk:
+ while (i--)
+ bm1880_clk_unregister_div(data->clk_data->hws[clks[i].div.id]);
+
+ return PTR_ERR(hw);
+}
+
+static int bm1880_clk_register_gate(const struct bm1880_gate_clock *clks,
+ int num_clks, struct bm1880_clock_data *data)
+{
+ struct clk_hw *hw;
+ void __iomem *sys_base = data->sys_base;
+ int i;
+
+ for (i = 0; i < num_clks; i++) {
+ hw = clk_hw_register_gate(NULL, clks[i].name,
+ clks[i].parent,
+ clks[i].flags,
+ sys_base + clks[i].gate_reg,
+ clks[i].gate_shift,
+ 0,
+ &bm1880_clk_lock);
+ if (IS_ERR(hw)) {
+ pr_err("%s: failed to register clock %s\n",
+ __func__, clks[i].name);
+ goto err_clk;
+ }
+
+ data->clk_data->hws[clks[i].id] = hw;
+ }
+
+ return 0;
+
+err_clk:
+ while (i--)
+ clk_hw_unregister_gate(data->clk_data->hws[clks[i].id]);
+
+ return PTR_ERR(hw);
+}
+
+static struct clk_hw *bm1880_clk_register_composite(struct bm1880_composite_clock *clks,
+ void __iomem *sys_base)
+{
+ struct clk_hw *hw;
+ struct clk_mux *mux = NULL;
+ struct clk_gate *gate = NULL;
+ struct bm1880_div_hw_clock *div_hws = NULL;
+ struct clk_hw *mux_hw = NULL, *gate_hw = NULL, *div_hw = NULL;
+ const struct clk_ops *mux_ops = NULL, *gate_ops = NULL, *div_ops = NULL;
+ const char * const *parent_names;
+ const char *parent;
+ int num_parents;
+ int ret;
+
+ if (clks->mux_shift >= 0) {
+ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return ERR_PTR(-ENOMEM);
+
+ mux->reg = sys_base + clks->mux_reg;
+ mux->mask = 1;
+ mux->shift = clks->mux_shift;
+ mux_hw = &mux->hw;
+ mux_ops = &clk_mux_ops;
+ mux->lock = &bm1880_clk_lock;
+
+ parent_names = clks->parents;
+ num_parents = clks->num_parents;
+ } else {
+ parent = clks->parent;
+ parent_names = &parent;
+ num_parents = 1;
+ }
+
+ if (clks->gate_shift >= 0) {
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ gate->reg = sys_base + clks->gate_reg;
+ gate->bit_idx = clks->gate_shift;
+ gate->lock = &bm1880_clk_lock;
+
+ gate_hw = &gate->hw;
+ gate_ops = &clk_gate_ops;
+ }
+
+ if (clks->div_shift >= 0) {
+ div_hws = kzalloc(sizeof(*div_hws), GFP_KERNEL);
+ if (!div_hws) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ div_hws->base = sys_base;
+ div_hws->div.reg = clks->div_reg;
+ div_hws->div.shift = clks->div_shift;
+ div_hws->div.width = clks->div_width;
+ div_hws->div.table = clks->table;
+ div_hws->div.initval = clks->div_initval;
+ div_hws->lock = &bm1880_clk_lock;
+ div_hws->div.flags = CLK_DIVIDER_ONE_BASED |
+ CLK_DIVIDER_ALLOW_ZERO;
+
+ div_hw = &div_hws->hw;
+ div_ops = &bm1880_clk_div_ops;
+ }
+
+ hw = clk_hw_register_composite(NULL, clks->name, parent_names,
+ num_parents, mux_hw, mux_ops, div_hw,
+ div_ops, gate_hw, gate_ops,
+ clks->flags);
+
+ if (IS_ERR(hw)) {
+ ret = PTR_ERR(hw);
+ goto err_out;
+ }
+
+ return hw;
+
+err_out:
+ kfree(div_hws);
+ kfree(gate);
+ kfree(mux);
+
+ return ERR_PTR(ret);
+}
+
+static int bm1880_clk_register_composites(struct bm1880_composite_clock *clks,
+ int num_clks, struct bm1880_clock_data *data)
+{
+ struct clk_hw *hw;
+ void __iomem *sys_base = data->sys_base;
+ int i;
+
+ for (i = 0; i < num_clks; i++) {
+ struct bm1880_composite_clock *bm1880_clk = &clks[i];
+
+ hw = bm1880_clk_register_composite(bm1880_clk, sys_base);
+ if (IS_ERR(hw)) {
+ pr_err("%s: failed to register clock %s\n",
+ __func__, bm1880_clk->name);
+ goto err_clk;
+ }
+
+ data->clk_data->hws[clks[i].id] = hw;
+ }
+
+ return 0;
+
+err_clk:
+ while (i--)
+ clk_hw_unregister_composite(data->clk_data->hws[clks[i].id]);
+
+ return PTR_ERR(hw);
+}
+
+static int bm1880_clk_probe(struct platform_device *pdev)
+{
+ struct bm1880_clock_data *clk_data;
+ void __iomem *pll_base, *sys_base;
+ struct device_node *np = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct clk_hw_onecell_data *clk_hw_data;
+ int num_clks, i;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ pll_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(pll_base))
+ return PTR_ERR(pll_base);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ sys_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(sys_base))
+ return PTR_ERR(sys_base);
+
+ clk_data = devm_kzalloc(dev, sizeof(*clk_data), GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+ clk_data->pll_base = pll_base;
+ clk_data->sys_base = sys_base;
+
+ num_clks = ARRAY_SIZE(bm1880_pll_clks) +
+ ARRAY_SIZE(bm1880_div_clks) +
+ ARRAY_SIZE(bm1880_mux_clks) +
+ ARRAY_SIZE(bm1880_composite_clks) +
+ ARRAY_SIZE(bm1880_gate_clks);
+
+ clk_hw_data = devm_kzalloc(&pdev->dev, struct_size(clk_hw_data, hws,
+ num_clks), GFP_KERNEL);
+ if (!clk_hw_data)
+ return -ENOMEM;
+
+ clk_data->clk_data = clk_hw_data;
+
+ for (i = 0; i < num_clks; i++)
+ clk_data->clk_data->hws[i] = ERR_PTR(-ENOENT);
+
+ clk_data->clk_data->num = num_clks;
+
+ bm1880_clk_register_plls(bm1880_pll_clks,
+ ARRAY_SIZE(bm1880_pll_clks),
+ clk_data);
+
+ bm1880_clk_register_divs(bm1880_div_clks,
+ ARRAY_SIZE(bm1880_div_clks),
+ clk_data);
+
+ bm1880_clk_register_mux(bm1880_mux_clks,
+ ARRAY_SIZE(bm1880_mux_clks),
+ clk_data);
+
+ bm1880_clk_register_composites(bm1880_composite_clks,
+ ARRAY_SIZE(bm1880_composite_clks),
+ clk_data);
+
+ bm1880_clk_register_gate(bm1880_gate_clks,
+ ARRAY_SIZE(bm1880_gate_clks),
+ clk_data);
+
+ return of_clk_add_hw_provider(np, of_clk_hw_onecell_get,
+ clk_data->clk_data);
+}
+
+static const struct of_device_id bm1880_of_match[] = {
+ { .compatible = "bitmain,bm1880-clk", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, bm1880_of_match);
+
+static struct platform_driver bm1880_clk_driver = {
+ .driver = {
+ .name = "bm1880-clk",
+ .of_match_table = bm1880_of_match,
+ },
+ .probe = bm1880_clk_probe,
+};
+module_platform_driver(bm1880_clk_driver);
+
+MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>");
+MODULE_DESCRIPTION("Clock driver for Bitmain BM1880 SoC");
+MODULE_LICENSE("GPL v2");
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v4 6/8] arm64: dts: bitmain: Source common clock for UART controllers
From: Manivannan Sadhasivam @ 2019-08-22 17:24 UTC (permalink / raw)
To: sboyd, mturquette, robh+dt
Cc: devicetree, Manivannan Sadhasivam, darren.tsao, linux-kernel,
linux-arm-kernel, fisher.cheng, alec.lin, linux-clk, haitao.suo
In-Reply-To: <20190822172426.25879-1-manivannan.sadhasivam@linaro.org>
Remove fixed clock and source common clock for UART controllers.
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
---
arch/arm64/boot/dts/bitmain/bm1880-sophon-edge.dts | 9 ---------
arch/arm64/boot/dts/bitmain/bm1880.dtsi | 12 ++++++++++++
2 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/arch/arm64/boot/dts/bitmain/bm1880-sophon-edge.dts b/arch/arm64/boot/dts/bitmain/bm1880-sophon-edge.dts
index 3e8c70778e24..7a2c7f9c2660 100644
--- a/arch/arm64/boot/dts/bitmain/bm1880-sophon-edge.dts
+++ b/arch/arm64/boot/dts/bitmain/bm1880-sophon-edge.dts
@@ -49,12 +49,6 @@
reg = <0x1 0x00000000 0x0 0x40000000>; // 1GB
};
- uart_clk: uart-clk {
- compatible = "fixed-clock";
- clock-frequency = <500000000>;
- #clock-cells = <0>;
- };
-
soc {
gpio0: gpio@50027000 {
porta: gpio-controller@0 {
@@ -173,21 +167,18 @@
&uart0 {
status = "okay";
- clocks = <&uart_clk>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart0_default>;
};
&uart1 {
status = "okay";
- clocks = <&uart_clk>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1_default>;
};
&uart2 {
status = "okay";
- clocks = <&uart_clk>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart2_default>;
};
diff --git a/arch/arm64/boot/dts/bitmain/bm1880.dtsi b/arch/arm64/boot/dts/bitmain/bm1880.dtsi
index 8471662413da..fa6e6905f588 100644
--- a/arch/arm64/boot/dts/bitmain/bm1880.dtsi
+++ b/arch/arm64/boot/dts/bitmain/bm1880.dtsi
@@ -174,6 +174,9 @@
uart0: serial@58018000 {
compatible = "snps,dw-apb-uart";
reg = <0x0 0x58018000 0x0 0x2000>;
+ clocks = <&clk BM1880_CLK_UART_500M>,
+ <&clk BM1880_CLK_APB_UART>;
+ clock-names = "baudclk", "apb_pclk";
interrupts = <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
reg-io-width = <4>;
@@ -184,6 +187,9 @@
uart1: serial@5801A000 {
compatible = "snps,dw-apb-uart";
reg = <0x0 0x5801a000 0x0 0x2000>;
+ clocks = <&clk BM1880_CLK_UART_500M>,
+ <&clk BM1880_CLK_APB_UART>;
+ clock-names = "baudclk", "apb_pclk";
interrupts = <GIC_SPI 12 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
reg-io-width = <4>;
@@ -194,6 +200,9 @@
uart2: serial@5801C000 {
compatible = "snps,dw-apb-uart";
reg = <0x0 0x5801c000 0x0 0x2000>;
+ clocks = <&clk BM1880_CLK_UART_500M>,
+ <&clk BM1880_CLK_APB_UART>;
+ clock-names = "baudclk", "apb_pclk";
interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
reg-io-width = <4>;
@@ -204,6 +213,9 @@
uart3: serial@5801E000 {
compatible = "snps,dw-apb-uart";
reg = <0x0 0x5801e000 0x0 0x2000>;
+ clocks = <&clk BM1880_CLK_UART_500M>,
+ <&clk BM1880_CLK_APB_UART>;
+ clock-names = "baudclk", "apb_pclk";
interrupts = <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
reg-io-width = <4>;
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v4 5/8] arm64: dts: bitmain: Add clock controller support for BM1880 SoC
From: Manivannan Sadhasivam @ 2019-08-22 17:24 UTC (permalink / raw)
To: sboyd, mturquette, robh+dt
Cc: devicetree, Manivannan Sadhasivam, darren.tsao, linux-kernel,
linux-arm-kernel, fisher.cheng, alec.lin, linux-clk, haitao.suo
In-Reply-To: <20190822172426.25879-1-manivannan.sadhasivam@linaro.org>
Add clock controller support for Bitmain BM1880 SoC.
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
---
arch/arm64/boot/dts/bitmain/bm1880.dtsi | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/arch/arm64/boot/dts/bitmain/bm1880.dtsi b/arch/arm64/boot/dts/bitmain/bm1880.dtsi
index d65453f99a99..8471662413da 100644
--- a/arch/arm64/boot/dts/bitmain/bm1880.dtsi
+++ b/arch/arm64/boot/dts/bitmain/bm1880.dtsi
@@ -4,6 +4,7 @@
* Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
*/
+#include <dt-bindings/clock/bm1880-clock.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/reset/bitmain,bm1880-reset.h>
@@ -66,6 +67,12 @@
<GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
};
+ osc: osc {
+ compatible = "fixed-clock";
+ clock-frequency = <25000000>;
+ #clock-cells = <0>;
+ };
+
soc {
compatible = "simple-bus";
#address-cells = <2>;
@@ -94,6 +101,15 @@
reg = <0x400 0x120>;
};
+ clk: clock-controller@e8 {
+ compatible = "bitmain,bm1880-clk";
+ reg = <0xe8 0x0c>, <0x800 0xb0>;
+ reg-names = "pll", "sys";
+ clocks = <&osc>;
+ clock-names = "osc";
+ #clock-cells = <1>;
+ };
+
rst: reset-controller@c00 {
compatible = "bitmain,bm1880-reset";
reg = <0xc00 0x8>;
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v4 4/8] dt-bindings: clock: Add devicetree binding for BM1880 SoC
From: Manivannan Sadhasivam @ 2019-08-22 17:24 UTC (permalink / raw)
To: sboyd, mturquette, robh+dt
Cc: devicetree, Manivannan Sadhasivam, darren.tsao, linux-kernel,
linux-arm-kernel, fisher.cheng, alec.lin, linux-clk, haitao.suo
In-Reply-To: <20190822172426.25879-1-manivannan.sadhasivam@linaro.org>
Add YAML devicetree binding for Bitmain BM1880 SoC.
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
---
.../bindings/clock/bitmain,bm1880-clk.yaml | 74 +++++++++++++++++
include/dt-bindings/clock/bm1880-clock.h | 82 +++++++++++++++++++
2 files changed, 156 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/bitmain,bm1880-clk.yaml
create mode 100644 include/dt-bindings/clock/bm1880-clock.h
diff --git a/Documentation/devicetree/bindings/clock/bitmain,bm1880-clk.yaml b/Documentation/devicetree/bindings/clock/bitmain,bm1880-clk.yaml
new file mode 100644
index 000000000000..31c48dcf5b8e
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/bitmain,bm1880-clk.yaml
@@ -0,0 +1,74 @@
+# SPDX-License-Identifier: GPL-2.0+
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/bindings/clock/bitmain,bm1880-clk.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Bitmain BM1880 Clock Controller
+
+maintainers:
+ - Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+
+description: |
+ The Bitmain BM1880 clock controller generates and supplies clock to
+ various peripherals within the SoC.
+
+ This binding uses common clock bindings
+ [1] Documentation/devicetree/bindings/clock/clock-bindings.txt
+
+properties:
+ compatible:
+ const: bitmain,bm1880-clk
+
+ reg:
+ items:
+ - description: pll registers
+ - description: system registers
+
+ reg-names:
+ items:
+ - const: pll
+ - const: sys
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ const: osc
+
+ '#clock-cells':
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - clocks
+ - clock-names
+ - '#clock-cells'
+
+examples:
+ # Clock controller node:
+ - |
+ clk: clock-controller@e8 {
+ compatible = "bitmain,bm1880-clk";
+ reg = <0xe8 0x0c>, <0x800 0xb0>;
+ reg-names = "pll", "sys";
+ clocks = <&osc>;
+ clock-names = "osc";
+ #clock-cells = <1>;
+ };
+
+ # Example UART controller node that consumes clock generated by the clock controller:
+ - |
+ uart0: serial@58018000 {
+ compatible = "snps,dw-apb-uart";
+ reg = <0x0 0x58018000 0x0 0x2000>;
+ clocks = <&clk 45>, <&clk 46>;
+ clock-names = "baudclk", "apb_pclk";
+ interrupts = <0 9 4>;
+ reg-shift = <2>;
+ reg-io-width = <4>;
+ };
+
+...
diff --git a/include/dt-bindings/clock/bm1880-clock.h b/include/dt-bindings/clock/bm1880-clock.h
new file mode 100644
index 000000000000..b46732361b25
--- /dev/null
+++ b/include/dt-bindings/clock/bm1880-clock.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Device Tree binding constants for Bitmain BM1880 SoC
+ *
+ * Copyright (c) 2019 Linaro Ltd.
+ */
+
+#ifndef __DT_BINDINGS_CLOCK_BM1880_H
+#define __DT_BINDINGS_CLOCK_BM1880_H
+
+#define BM1880_CLK_OSC 0
+#define BM1880_CLK_MPLL 1
+#define BM1880_CLK_SPLL 2
+#define BM1880_CLK_FPLL 3
+#define BM1880_CLK_DDRPLL 4
+#define BM1880_CLK_A53 5
+#define BM1880_CLK_50M_A53 6
+#define BM1880_CLK_AHB_ROM 7
+#define BM1880_CLK_AXI_SRAM 8
+#define BM1880_CLK_DDR_AXI 9
+#define BM1880_CLK_EFUSE 10
+#define BM1880_CLK_APB_EFUSE 11
+#define BM1880_CLK_AXI5_EMMC 12
+#define BM1880_CLK_EMMC 13
+#define BM1880_CLK_100K_EMMC 14
+#define BM1880_CLK_AXI5_SD 15
+#define BM1880_CLK_SD 16
+#define BM1880_CLK_100K_SD 17
+#define BM1880_CLK_500M_ETH0 18
+#define BM1880_CLK_AXI4_ETH0 19
+#define BM1880_CLK_500M_ETH1 20
+#define BM1880_CLK_AXI4_ETH1 21
+#define BM1880_CLK_AXI1_GDMA 22
+#define BM1880_CLK_APB_GPIO 23
+#define BM1880_CLK_APB_GPIO_INTR 24
+#define BM1880_CLK_GPIO_DB 25
+#define BM1880_CLK_AXI1_MINER 26
+#define BM1880_CLK_AHB_SF 27
+#define BM1880_CLK_SDMA_AXI 28
+#define BM1880_CLK_SDMA_AUD 29
+#define BM1880_CLK_APB_I2C 30
+#define BM1880_CLK_APB_WDT 31
+#define BM1880_CLK_APB_JPEG 32
+#define BM1880_CLK_JPEG_AXI 33
+#define BM1880_CLK_AXI5_NF 34
+#define BM1880_CLK_APB_NF 35
+#define BM1880_CLK_NF 36
+#define BM1880_CLK_APB_PWM 37
+#define BM1880_CLK_DIV_0_RV 38
+#define BM1880_CLK_DIV_1_RV 39
+#define BM1880_CLK_MUX_RV 40
+#define BM1880_CLK_RV 41
+#define BM1880_CLK_APB_SPI 42
+#define BM1880_CLK_TPU_AXI 43
+#define BM1880_CLK_DIV_UART_500M 44
+#define BM1880_CLK_UART_500M 45
+#define BM1880_CLK_APB_UART 46
+#define BM1880_CLK_APB_I2S 47
+#define BM1880_CLK_AXI4_USB 48
+#define BM1880_CLK_APB_USB 49
+#define BM1880_CLK_125M_USB 50
+#define BM1880_CLK_33K_USB 51
+#define BM1880_CLK_DIV_12M_USB 52
+#define BM1880_CLK_12M_USB 53
+#define BM1880_CLK_APB_VIDEO 54
+#define BM1880_CLK_VIDEO_AXI 55
+#define BM1880_CLK_VPP_AXI 56
+#define BM1880_CLK_APB_VPP 57
+#define BM1880_CLK_DIV_0_AXI1 58
+#define BM1880_CLK_DIV_1_AXI1 59
+#define BM1880_CLK_AXI1 60
+#define BM1880_CLK_AXI2 61
+#define BM1880_CLK_AXI3 62
+#define BM1880_CLK_AXI4 63
+#define BM1880_CLK_AXI5 64
+#define BM1880_CLK_DIV_0_AXI6 65
+#define BM1880_CLK_DIV_1_AXI6 66
+#define BM1880_CLK_MUX_AXI6 67
+#define BM1880_CLK_AXI6 68
+#define BM1880_NR_CLKS 69
+
+#endif /* __DT_BINDINGS_CLOCK_BM1880_H */
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v4 3/8] clk: Add clk_hw_unregister_composite helper function definition
From: Manivannan Sadhasivam @ 2019-08-22 17:24 UTC (permalink / raw)
To: sboyd, mturquette, robh+dt
Cc: devicetree, Manivannan Sadhasivam, darren.tsao, linux-kernel,
linux-arm-kernel, fisher.cheng, alec.lin, linux-clk, haitao.suo
In-Reply-To: <20190822172426.25879-1-manivannan.sadhasivam@linaro.org>
This function has been delcared but not defined anywhere. Hence, this
commit adds definition for it.
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
---
drivers/clk/clk-composite.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c
index 4d579f9d20f6..ccca58a6d271 100644
--- a/drivers/clk/clk-composite.c
+++ b/drivers/clk/clk-composite.c
@@ -344,3 +344,14 @@ void clk_unregister_composite(struct clk *clk)
clk_unregister(clk);
kfree(composite);
}
+
+void clk_hw_unregister_composite(struct clk_hw *hw)
+{
+ struct clk_composite *composite;
+
+ composite = to_clk_composite(hw);
+
+ clk_hw_unregister(hw);
+ kfree(composite);
+}
+EXPORT_SYMBOL_GPL(clk_hw_unregister_composite);
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v4 2/8] clk: Warn if clk_init_data is not zero initialized
From: Manivannan Sadhasivam @ 2019-08-22 17:24 UTC (permalink / raw)
To: sboyd, mturquette, robh+dt
Cc: devicetree, Manivannan Sadhasivam, darren.tsao, linux-kernel,
linux-arm-kernel, fisher.cheng, alec.lin, linux-clk, haitao.suo
In-Reply-To: <20190822172426.25879-1-manivannan.sadhasivam@linaro.org>
The new implementation for determining parent map uses multiple ways
to pass parent info. The order in which it gets processed depends on
the first available member. Hence, it is necessary to zero init the
clk_init_data struct so that the expected member gets processed correctly.
So, add a warning if multiple clk_init_data members are available during
clk registration.
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
---
drivers/clk/clk.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index c0990703ce54..7d6d6984c979 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -3497,6 +3497,14 @@ static int clk_core_populate_parent_map(struct clk_core *core)
if (!num_parents)
return 0;
+ /*
+ * Check for non-zero initialized clk_init_data struct. This is
+ * required because, we only require one of the (parent_names/
+ * parent_data/parent_hws) to be set at a time. Otherwise, the
+ * current code would use first available member.
+ */
+ WARN_ON((parent_names && parent_data) || (parent_names && parent_hws));
+
/*
* Avoid unnecessary string look-ups of clk_core's possible parents by
* having a cache of names/clk_hw pointers to clk_core pointers.
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v4 1/8] clk: Zero init clk_init_data in helpers
From: Manivannan Sadhasivam @ 2019-08-22 17:24 UTC (permalink / raw)
To: sboyd, mturquette, robh+dt
Cc: devicetree, Manivannan Sadhasivam, darren.tsao, linux-kernel,
linux-arm-kernel, fisher.cheng, alec.lin, linux-clk, haitao.suo
In-Reply-To: <20190822172426.25879-1-manivannan.sadhasivam@linaro.org>
The clk_init_data struct needs to be initialized to zero for the new
parent_map implementation to work correctly. Otherwise, the member which
is available first will get processed.
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
---
drivers/clk/clk-composite.c | 2 +-
drivers/clk/clk-divider.c | 2 +-
drivers/clk/clk-fixed-rate.c | 2 +-
drivers/clk/clk-gate.c | 2 +-
drivers/clk/clk-mux.c | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c
index b06038b8f658..4d579f9d20f6 100644
--- a/drivers/clk/clk-composite.c
+++ b/drivers/clk/clk-composite.c
@@ -208,7 +208,7 @@ struct clk_hw *clk_hw_register_composite(struct device *dev, const char *name,
unsigned long flags)
{
struct clk_hw *hw;
- struct clk_init_data init;
+ struct clk_init_data init = { NULL };
struct clk_composite *composite;
struct clk_ops *clk_composite_ops;
int ret;
diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
index 3f9ff78c4a2a..65dd8137f9ec 100644
--- a/drivers/clk/clk-divider.c
+++ b/drivers/clk/clk-divider.c
@@ -471,7 +471,7 @@ static struct clk_hw *_register_divider(struct device *dev, const char *name,
{
struct clk_divider *div;
struct clk_hw *hw;
- struct clk_init_data init;
+ struct clk_init_data init = { NULL };
int ret;
if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
diff --git a/drivers/clk/clk-fixed-rate.c b/drivers/clk/clk-fixed-rate.c
index a7e4aef7a376..746c3ecdc5b3 100644
--- a/drivers/clk/clk-fixed-rate.c
+++ b/drivers/clk/clk-fixed-rate.c
@@ -58,7 +58,7 @@ struct clk_hw *clk_hw_register_fixed_rate_with_accuracy(struct device *dev,
{
struct clk_fixed_rate *fixed;
struct clk_hw *hw;
- struct clk_init_data init;
+ struct clk_init_data init = { NULL };
int ret;
/* allocate fixed-rate clock */
diff --git a/drivers/clk/clk-gate.c b/drivers/clk/clk-gate.c
index 1b99fc962745..8ed83ec730cb 100644
--- a/drivers/clk/clk-gate.c
+++ b/drivers/clk/clk-gate.c
@@ -141,7 +141,7 @@ struct clk_hw *clk_hw_register_gate(struct device *dev, const char *name,
{
struct clk_gate *gate;
struct clk_hw *hw;
- struct clk_init_data init;
+ struct clk_init_data init = { NULL };
int ret;
if (clk_gate_flags & CLK_GATE_HIWORD_MASK) {
diff --git a/drivers/clk/clk-mux.c b/drivers/clk/clk-mux.c
index 66e91f740508..2caa6b2a9ee5 100644
--- a/drivers/clk/clk-mux.c
+++ b/drivers/clk/clk-mux.c
@@ -153,7 +153,7 @@ struct clk_hw *clk_hw_register_mux_table(struct device *dev, const char *name,
{
struct clk_mux *mux;
struct clk_hw *hw;
- struct clk_init_data init;
+ struct clk_init_data init = { NULL };
u8 width = 0;
int ret;
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v4 0/8] Add Bitmain BM1880 clock driver
From: Manivannan Sadhasivam @ 2019-08-22 17:24 UTC (permalink / raw)
To: sboyd, mturquette, robh+dt
Cc: devicetree, Manivannan Sadhasivam, darren.tsao, linux-kernel,
linux-arm-kernel, fisher.cheng, alec.lin, linux-clk, haitao.suo
Hello,
This patchset adds common clock driver for Bitmain BM1880 SoC clock
controller. The clock controller consists of gate, divider, mux
and pll clocks with different compositions. Hence, the driver uses
composite clock structure in place where multiple clocking units are
combined together.
This patchset also removes UART fixed clock and sources clocks from clock
controller for Sophon Edge board where the driver has been validated.
Thanks,
Mani
Changes in v4:
* Fixed devicetree binding issue
* Added ARCH_BITMAIN as the default for the clk driver
Changes in v3:
* Switched to clk_hw_{register/unregister} APIs
* Returned clk_hw from the in-driver registration helpers
Changes in v2:
* Converted the dt binding to YAML
* Incorporated review comments from Stephen (majority of change is switching
to new way of specifying clk parents)
Manivannan Sadhasivam (8):
clk: Zero init clk_init_data in helpers
clk: Warn if clk_init_data is not zero initialized
clk: Add clk_hw_unregister_composite helper function definition
dt-bindings: clock: Add devicetree binding for BM1880 SoC
arm64: dts: bitmain: Add clock controller support for BM1880 SoC
arm64: dts: bitmain: Source common clock for UART controllers
clk: Add common clock driver for BM1880 SoC
MAINTAINERS: Add entry for BM1880 SoC clock driver
.../bindings/clock/bitmain,bm1880-clk.yaml | 74 ++
MAINTAINERS | 2 +
.../boot/dts/bitmain/bm1880-sophon-edge.dts | 9 -
arch/arm64/boot/dts/bitmain/bm1880.dtsi | 28 +
drivers/clk/Kconfig | 7 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-bm1880.c | 966 ++++++++++++++++++
drivers/clk/clk-composite.c | 13 +-
drivers/clk/clk-divider.c | 2 +-
drivers/clk/clk-fixed-rate.c | 2 +-
drivers/clk/clk-gate.c | 2 +-
drivers/clk/clk-mux.c | 2 +-
drivers/clk/clk.c | 8 +
include/dt-bindings/clock/bm1880-clock.h | 82 ++
14 files changed, 1184 insertions(+), 14 deletions(-)
create mode 100644 Documentation/devicetree/bindings/clock/bitmain,bm1880-clk.yaml
create mode 100644 drivers/clk/clk-bm1880.c
create mode 100644 include/dt-bindings/clock/bm1880-clock.h
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v10 09/23] iommu/io-pgtable-arm-v7s: Extend to support PA[33:32] for MediaTek
From: Will Deacon @ 2019-08-22 17:14 UTC (permalink / raw)
To: Yong Wu
Cc: youlin.pei, devicetree, Nicolas Boichat, cui.zhang,
srv_heupstream, Tomasz Figa, Joerg Roedel, linux-kernel,
Evan Green, chao.hao, iommu, Rob Herring, linux-mediatek,
Matthias Brugger, ming-fan.chen, anan.sun, Robin Murphy,
Matthias Kaehlcke, linux-arm-kernel
In-Reply-To: <1566475533.11621.18.camel@mhfsdcap03>
On Thu, Aug 22, 2019 at 08:05:33PM +0800, Yong Wu wrote:
> On Thu, 2019-08-22 at 12:28 +0100, Will Deacon wrote:
> > Ok, great. Yong Wu -- are you ok respinning with the above + missing
> > brackets?
>
> Of course I can.
>
> NearlyAll the interface in this file is prefixed with "arm_v7s_", so
> does the new interface also need it?, like arm_v7s_is_mtk_enabled. And
> keep the iopte_to_paddr and paddr_to_iopte symmetrical.
>
>
> Then the final patch would looks like below, is it ok?
Looks good to me:
Acked-by: Will Deacon <will@kernel.org>
Will
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: next take at setting up a dma mask by default for platform devices v2
From: Greg Kroah-Hartman @ 2019-08-22 17:11 UTC (permalink / raw)
To: Christoph Hellwig
Cc: linux-arch, Gavin Li, linuxppc-dev, linux-kernel, Mathias Nyman,
Geoff Levand, Fabio Estevam, Sascha Hauer, linux-usb,
Michal Simek, iommu, Maxime Chevallier, linux-m68k, Alan Stern,
NXP Linux Team, Pengutronix Kernel Team, Minas Harutyunyan,
Shawn Guo, Bin Liu, linux-arm-kernel, Laurentiu Tudor
In-Reply-To: <20190816062435.881-1-hch@lst.de>
On Fri, Aug 16, 2019 at 08:24:29AM +0200, Christoph Hellwig wrote:
> Hi all,
>
> this is another attempt to make sure the dma_mask pointer is always
> initialized for platform devices. Not doing so lead to lots of
> boilerplate code, and makes platform devices different from all our
> major busses like PCI where we always set up a dma_mask. In the long
> run this should also help to eventually make dma_mask a scalar value
> instead of a pointer and remove even more cruft.
>
> The bigger blocker for this last time was the fact that the usb
> subsystem uses the presence or lack of a dma_mask to check if the core
> should do dma mapping for the driver, which is highly unusual. So we
> fix this first. Note that this has some overlap with the pending
> desire to use the proper dma_mmap_coherent helper for mapping usb
> buffers. The first two patches have already been queued up by Greg
> and are only included for completeness.
Note to everyone. The first two patches in this series is already in
5.3-rc5.
I've applied the rest of the series to my usb-next branch (with the 6th
patch landing there later today.) They are scheduled to be merge to
Linus in 5.4-rc1.
Christoph, thanks so much for these cleanups.
greg k-h
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [PATCH v2] KVM: arm: VGIC: properly initialise private IRQ affinity
From: Andre Przywara @ 2019-08-22 17:05 UTC (permalink / raw)
To: Marc Zyngier, Christoffer Dall
Cc: Zenghui Yu, Julien Grall, Dave Martin, linux-arm-kernel, kvmarm
At the moment we initialise the target *mask* of a virtual IRQ to the
VCPU it belongs to, even though this mask is only defined for GICv2 and
quickly runs out of bits for many GICv3 guests.
This behaviour triggers an UBSAN complaint for more than 32 VCPUs:
------
[ 5659.462377] UBSAN: Undefined behaviour in virt/kvm/arm/vgic/vgic-init.c:223:21
[ 5659.471689] shift exponent 32 is too large for 32-bit type 'unsigned int'
------
Also for GICv3 guests the reporting of TARGET in the "vgic-state" debugfs
dump is wrong, due to this very same problem.
Because there is no requirement to create the VGIC device before the
VCPUs (and QEMU actually does it the other way round), we can't safely
initialise mpidr or targets in kvm_vgic_vcpu_init(). But since we touch
every private IRQ for each VCPU anyway later (in vgic_init()), we can
just move the initialisation of those fields into there, where we
definitely know the VGIC type.
On the way make sure we really have either a VGICv2 or a VGICv3 device,
since the former checks was just checking for "VGICv3 or not", silently
ignoring the uninitialised case.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reported-by: Dave Martin <dave.martin@arm.com>
---
Hi,
tested with 4, 8 and 33 VCPUs with kvmtool and QEMU, on a GICv2 and a
GICv3 machine.
Also briefly tested localhost migration on the GICv3 machine w/ 33
VCPUs, although I think all IRQs are group 1.
Cheers,
Andre
virt/kvm/arm/vgic/vgic-init.c | 28 ++++++++++++++++++----------
1 file changed, 18 insertions(+), 10 deletions(-)
diff --git a/virt/kvm/arm/vgic/vgic-init.c b/virt/kvm/arm/vgic/vgic-init.c
index 80127ca9269f..413fb6a5525c 100644
--- a/virt/kvm/arm/vgic/vgic-init.c
+++ b/virt/kvm/arm/vgic/vgic-init.c
@@ -8,6 +8,7 @@
#include <linux/cpu.h>
#include <linux/kvm_host.h>
#include <kvm/arm_vgic.h>
+#include <asm/kvm_emulate.h>
#include <asm/kvm_mmu.h>
#include "vgic.h"
@@ -165,12 +166,17 @@ static int kvm_vgic_dist_init(struct kvm *kvm, unsigned int nr_spis)
irq->vcpu = NULL;
irq->target_vcpu = vcpu0;
kref_init(&irq->refcount);
- if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V2) {
+ switch (dist->vgic_model) {
+ case KVM_DEV_TYPE_ARM_VGIC_V2:
irq->targets = 0;
irq->group = 0;
- } else {
+ break;
+ case KVM_DEV_TYPE_ARM_VGIC_V3:
irq->mpidr = 0;
irq->group = 1;
+ break;
+ default:
+ BUG_ON(1);
}
}
return 0;
@@ -210,7 +216,6 @@ int kvm_vgic_vcpu_init(struct kvm_vcpu *vcpu)
irq->intid = i;
irq->vcpu = NULL;
irq->target_vcpu = vcpu;
- irq->targets = 1U << vcpu->vcpu_id;
kref_init(&irq->refcount);
if (vgic_irq_is_sgi(i)) {
/* SGIs */
@@ -220,11 +225,6 @@ int kvm_vgic_vcpu_init(struct kvm_vcpu *vcpu)
/* PPIs */
irq->config = VGIC_CONFIG_LEVEL;
}
-
- if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
- irq->group = 1;
- else
- irq->group = 0;
}
if (!irqchip_in_kernel(vcpu->kvm))
@@ -287,10 +287,18 @@ int vgic_init(struct kvm *kvm)
for (i = 0; i < VGIC_NR_PRIVATE_IRQS; i++) {
struct vgic_irq *irq = &vgic_cpu->private_irqs[i];
- if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
+ switch (dist->vgic_model) {
+ case KVM_DEV_TYPE_ARM_VGIC_V3:
irq->group = 1;
- else
+ irq->mpidr = kvm_vcpu_get_mpidr_aff(vcpu);
+ break;
+ case KVM_DEV_TYPE_ARM_VGIC_V2:
irq->group = 0;
+ irq->targets = 1U << idx;
+ break;
+ default:
+ BUG_ON(1);
+ }
}
}
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* Re: [EXT] [PATCH v3 2/2] drm/bridge: Add NWL MIPI DSI host controller support
From: Guido Günther @ 2019-08-22 17:03 UTC (permalink / raw)
To: Robert Chiras
Cc: mark.rutland@arm.com, devicetree@vger.kernel.org,
jernej.skrabec@siol.net, kernel@pengutronix.de, sam@ravnborg.org,
narmstrong@baylibre.com, airlied@linux.ie, festevam@gmail.com,
s.hauer@pengutronix.de, jonas@kwiboo.se,
linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org,
a.hajda@samsung.com, robh+dt@kernel.org, arnd@arndb.de,
dl-linux-imx, daniel@ffwll.ch, shawnguo@kernel.org,
lee.jones@linaro.org, linux-arm-kernel@lists.infradead.org,
Laurent.pinchart@ideasonboard.com
In-Reply-To: <1566479899.3209.101.camel@nxp.com>
Hi Robert,
thanks for your comments! Most of this make sense, i have some comments
inline below (mostly since I only have access to the imx8mq reference
manual but not to the any NWL IP docs):
On Thu, Aug 22, 2019 at 01:18:21PM +0000, Robert Chiras wrote:
> Hi Guido,
>
> I added my signed-off, plus some comments inline.
>
> On Jo, 2019-08-22 at 12:44 +0200, Guido Günther wrote:
> > This adds initial support for the NWL MIPI DSI Host controller found
> > on
> > i.MX8 SoCs.
> >
> > It adds support for the i.MX8MQ but the same IP can be found on
> > e.g. the i.MX8QXP.
> >
> > It has been tested on the Librem 5 devkit using mxsfb.
> >
> > Signed-off-by: Guido Günther <agx@sigxcpu.org>
> Signed-off-by: Robert Chiras <robert.chiras@nxp.com>
Thanks!
> > Co-developed-by: Robert Chiras <robert.chiras@nxp.com>
> > ---
> > drivers/gpu/drm/bridge/Kconfig | 2 +
> > drivers/gpu/drm/bridge/Makefile | 1 +
> > drivers/gpu/drm/bridge/nwl-dsi/Kconfig | 16 +
> > drivers/gpu/drm/bridge/nwl-dsi/Makefile | 4 +
> > drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c | 501 ++++++++++++++++
> > drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h | 65 +++
> > drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c | 700
> > +++++++++++++++++++++++
> > drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h | 112 ++++
> > 8 files changed, 1401 insertions(+)
> > create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/Kconfig
> > create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/Makefile
> > create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
> > create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
> > create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
> > create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
> >
> > diff --git a/drivers/gpu/drm/bridge/Kconfig
> > b/drivers/gpu/drm/bridge/Kconfig
> > index 1cc9f502c1f2..7980b5c2156f 100644
> > --- a/drivers/gpu/drm/bridge/Kconfig
> > +++ b/drivers/gpu/drm/bridge/Kconfig
> > @@ -154,6 +154,8 @@ source "drivers/gpu/drm/bridge/analogix/Kconfig"
> >
> > source "drivers/gpu/drm/bridge/adv7511/Kconfig"
> >
> > +source "drivers/gpu/drm/bridge/nwl-dsi/Kconfig"
> > +
> > source "drivers/gpu/drm/bridge/synopsys/Kconfig"
> >
> > endmenu
> > diff --git a/drivers/gpu/drm/bridge/Makefile
> > b/drivers/gpu/drm/bridge/Makefile
> > index 4934fcf5a6f8..d9f6c0f77592 100644
> > --- a/drivers/gpu/drm/bridge/Makefile
> > +++ b/drivers/gpu/drm/bridge/Makefile
> > @@ -16,4 +16,5 @@ obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
> > obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
> > obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o
> > obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
> > +obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-dsi/
> > obj-y += synopsys/
> > diff --git a/drivers/gpu/drm/bridge/nwl-dsi/Kconfig
> > b/drivers/gpu/drm/bridge/nwl-dsi/Kconfig
> > new file mode 100644
> > index 000000000000..3b157a9f2229
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/nwl-dsi/Kconfig
> > @@ -0,0 +1,16 @@
> > +config DRM_NWL_MIPI_DSI
> > + tristate "Support for Northwest Logic MIPI DSI Host
> > controller"
> > + depends on DRM
> > + depends on COMMON_CLK
> > + depends on OF && HAS_IOMEM
> > + select DRM_KMS_HELPER
> > + select DRM_MIPI_DSI
> > + select DRM_PANEL_BRIDGE
> > + select GENERIC_PHY_MIPI_DPHY
> > + select MFD_SYSCON
> > + select MULTIPLEXER
> > + select REGMAP_MMIO
> > + help
> > + This enables the Northwest Logic MIPI DSI Host controller
> > as
> > + for example found on NXP's i.MX8 Processors.
> > +
> > diff --git a/drivers/gpu/drm/bridge/nwl-dsi/Makefile
> > b/drivers/gpu/drm/bridge/nwl-dsi/Makefile
> > new file mode 100644
> > index 000000000000..804baf2f1916
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/nwl-dsi/Makefile
> > @@ -0,0 +1,4 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +nwl-mipi-dsi-y := nwl-drv.o nwl-dsi.o
> > +obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-mipi-dsi.o
> > +header-test-y += nwl-drv.h nwl-dsi.h
> > diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
> > b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
> > new file mode 100644
> > index 000000000000..e457438738c0
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
> > @@ -0,0 +1,501 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * i.MX8 NWL MIPI DSI host driver
> > + *
> > + * Copyright (C) 2017 NXP
> > + * Copyright (C) 2019 Purism SPC
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/irq.h>
> > +#include <linux/mfd/syscon.h>
> > +#include <linux/module.h>
> > +#include <linux/mux/consumer.h>
> > +#include <linux/of.h>
> > +#include <linux/of_platform.h>
> > +#include <linux/phy/phy.h>
> > +#include <linux/reset.h>
> > +#include <linux/regmap.h>
> > +#include <linux/sys_soc.h>
> > +
> > +#include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_of.h>
> > +#include <drm/drm_print.h>
> > +#include <drm/drm_probe_helper.h>
> > +
> > +#include "nwl-drv.h"
> > +#include "nwl-dsi.h"
> > +
> > +#define DRV_NAME "nwl-dsi"
> > +
> > +/* Possible platform specific clocks */
> > +#define NWL_DSI_CLK_CORE "core"
> > +
> > +static const struct regmap_config nwl_dsi_regmap_config = {
> > + .reg_bits = 16,
> > + .val_bits = 32,
> > + .reg_stride = 4,
> > + .max_register = NWL_DSI_IRQ_MASK2,
> > + .name = DRV_NAME,
> > +};
> > +
> > +struct nwl_dsi_platform_data {
> > + int (*poweron)(struct nwl_dsi *dsi);
> > + int (*poweroff)(struct nwl_dsi *dsi);
> > + int (*select_input)(struct nwl_dsi *dsi);
> > + int (*deselect_input)(struct nwl_dsi *dsi);
> > + struct nwl_dsi_plat_clk_config
> > clk_config[NWL_DSI_MAX_PLATFORM_CLOCKS];
> > +};
> > +
> > +static inline struct nwl_dsi *bridge_to_dsi(struct drm_bridge
> > *bridge)
> > +{
> > + return container_of(bridge, struct nwl_dsi, bridge);
> > +}
> > +
> > +static int nwl_dsi_set_platform_clocks(struct nwl_dsi *dsi, bool
> > enable)
> > +{
> > + struct device *dev = dsi->dev;
> > + const char *id;
> > + struct clk *clk;
> > + size_t i;
> > + unsigned long rate;
> > + int ret, result = 0;
> > +
> > + DRM_DEV_DEBUG_DRIVER(dev, "%s platform clocks\n",
> > + enable ? "enabling" : "disabling");
> > + for (i = 0; i < ARRAY_SIZE(dsi->pdata->clk_config); i++) {
> > + if (!dsi->clk_config[i].present)
> > + continue;
> > + id = dsi->clk_config[i].id;
> > + clk = dsi->clk_config[i].clk;
> > +
> > + if (enable) {
> > + ret = clk_prepare_enable(clk);
> > + if (ret < 0) {
> > + DRM_DEV_ERROR(dev,
> > + "Failed to enable %s
> > clk: %d\n",
> > + id, ret);
> > + result = result ?: ret;
> > + }
> > + rate = clk_get_rate(clk);
> > + DRM_DEV_DEBUG_DRIVER(dev, "Enabled %s clk
> > @%lu Hz\n",
> > + id, rate);
> > + } else {
> > + clk_disable_unprepare(clk);
> > + DRM_DEV_DEBUG_DRIVER(dev, "Disabled %s
> > clk\n", id);
> > + }
> > + }
> > +
> > + return result;
> > +}
> > +
> > +static int nwl_dsi_plat_enable(struct nwl_dsi *dsi)
> > +{
> > + struct device *dev = dsi->dev;
> > + int ret;
> > +
> > + if (dsi->pdata->select_input)
> > + dsi->pdata->select_input(dsi);
> > +
> > + ret = nwl_dsi_set_platform_clocks(dsi, true);
> > + if (ret < 0)
> > + return ret;
> > +
> > + ret = dsi->pdata->poweron(dsi);
> > + if (ret < 0)
> > + DRM_DEV_ERROR(dev, "Failed to power on DSI: %d\n",
> > ret);
> > + return ret;
> > +}
> > +
> > +static void nwl_dsi_plat_disable(struct nwl_dsi *dsi)
> > +{
> > + dsi->pdata->poweroff(dsi);
> > + nwl_dsi_set_platform_clocks(dsi, false);
> > + if (dsi->pdata->deselect_input)
> > + dsi->pdata->deselect_input(dsi);
> > +}
> > +
> > +static void nwl_dsi_bridge_disable(struct drm_bridge *bridge)
> > +{
> > + struct nwl_dsi *dsi = bridge_to_dsi(bridge);
> > +
> > + nwl_dsi_disable(dsi);
> > + nwl_dsi_plat_disable(dsi);
> > + pm_runtime_put(dsi->dev);
> > +}
> > +
> > +static int nwl_dsi_get_dphy_params(struct nwl_dsi *dsi,
> > + const struct drm_display_mode
> > *mode,
> > + union phy_configure_opts
> > *phy_opts)
> > +{
> > + unsigned long rate;
> > + int ret;
> > +
> > + if (dsi->lanes < 1 || dsi->lanes > 4)
> > + return -EINVAL;
> > +
> > + /*
> > + * So far the DPHY spec minimal timings work for both mixel
> > + * dphy and nwl dsi host
> > + */
> > + ret = phy_mipi_dphy_get_default_config(
> > + mode->crtc_clock * 1000,
> > + mipi_dsi_pixel_format_to_bpp(dsi->format), dsi-
> > >lanes,
> > + &phy_opts->mipi_dphy);
> > + if (ret < 0)
> > + return ret;
> > +
> > + rate = clk_get_rate(dsi->tx_esc_clk);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "LP clk is @%lu Hz\n", rate);
> > + phy_opts->mipi_dphy.lp_clk_rate = rate;
> > +
> > + return 0;
> > +}
> > +
> > +static bool nwl_dsi_bridge_mode_fixup(struct drm_bridge *bridge,
> > + const struct drm_display_mode
> > *mode,
> > + struct drm_display_mode
> > *adjusted_mode)
> > +{
> > + /* At least LCDIF + NWL needs active high sync */
> > + adjusted_mode->flags |= (DRM_MODE_FLAG_PHSYNC |
> > DRM_MODE_FLAG_PVSYNC);
> > + adjusted_mode->flags &= ~(DRM_MODE_FLAG_NHSYNC |
> > DRM_MODE_FLAG_NVSYNC);
> > +
> > + return true;
> > +}
> > +
> > +static enum drm_mode_status
> > +nwl_dsi_bridge_mode_valid(struct drm_bridge *bridge,
> > + const struct drm_display_mode *mode)
> > +{
> > + struct nwl_dsi *dsi = bridge_to_dsi(bridge);
> > + int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> > +
> > + if (mode->clock * bpp > 15000000)
> > + return MODE_CLOCK_HIGH;
> > +
> > + if (mode->clock * bpp < 80000)
> > + return MODE_CLOCK_LOW;
> These limits (80MBPS min and 1500MBPS max) are per lane, so you should
> involve the numbers of lanes here, too. According to this formula, you
> limit the maximum DSI throughput to 1.5Gbps, while the maximum is 6Gbps
> (1.5 * 4 lanes)
You're right. I thought i had the number of lanes in but seems i
forgot. thanks.
> > +
> > + return MODE_OK;
> > +}
> > +
> > +static void
> > +nwl_dsi_bridge_mode_set(struct drm_bridge *bridge,
> > + const struct drm_display_mode *mode,
> > + const struct drm_display_mode *adjusted_mode)
> > +{
> > + struct nwl_dsi *dsi = bridge_to_dsi(bridge);
> > + struct device *dev = dsi->dev;
> > + union phy_configure_opts new_cfg;
> > + unsigned long phy_ref_rate;
> > + int ret;
> > +
> > + ret = nwl_dsi_get_dphy_params(dsi, adjusted_mode, &new_cfg);
> > + if (ret < 0)
> > + return;
> > +
> > + /*
> > + * If hs clock is unchanged, we're all good - all parameters
> > are
> > + * derived from it atm.
> > + */
> > + if (new_cfg.mipi_dphy.hs_clk_rate == dsi-
> > >phy_cfg.mipi_dphy.hs_clk_rate)
> > + return;
> > +
> > + phy_ref_rate = clk_get_rate(dsi->phy_ref_clk);
> > + DRM_DEV_DEBUG_DRIVER(dev, "PHY at ref rate: %lu\n",
> > phy_ref_rate);
> > + /* Save the new desired phy config */
> > + memcpy(&dsi->phy_cfg, &new_cfg, sizeof(new_cfg));
> > +
> > + memcpy(&dsi->mode, adjusted_mode, sizeof(dsi->mode));
> > + drm_mode_debug_printmodeline(adjusted_mode);
> > +}
> > +
> > +static void nwl_dsi_bridge_pre_enable(struct drm_bridge *bridge)
> > +{
> > + struct nwl_dsi *dsi = bridge_to_dsi(bridge);
> > +
> > + pm_runtime_get_sync(dsi->dev);
> > + nwl_dsi_plat_enable(dsi);
> > + nwl_dsi_enable(dsi);
> > +}
> > +
> > +static int nwl_dsi_bridge_attach(struct drm_bridge *bridge)
> > +{
> > + struct nwl_dsi *dsi = bridge->driver_private;
> > +
> > + return drm_bridge_attach(bridge->encoder, dsi->panel_bridge,
> > bridge);
> > +}
> > +
> > +static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = {
> > + .pre_enable = nwl_dsi_bridge_pre_enable,
> > + .disable = nwl_dsi_bridge_disable,
> > + .mode_fixup = nwl_dsi_bridge_mode_fixup,
> > + .mode_set = nwl_dsi_bridge_mode_set,
> > + .mode_valid = nwl_dsi_bridge_mode_valid,
> > + .attach = nwl_dsi_bridge_attach,
> > +};
> > +
> > +static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
> > +{
> > + struct platform_device *pdev = to_platform_device(dsi->dev);
> > + struct clk *clk;
> > + const char *clk_id;
> > + void __iomem *base;
> > + int i, ret;
> > +
> > + dsi->phy = devm_phy_get(dsi->dev, "dphy");
> > + if (IS_ERR(dsi->phy)) {
> > + ret = PTR_ERR(dsi->phy);
> > + if (ret != -EPROBE_DEFER)
> > + DRM_DEV_ERROR(dsi->dev, "Could not get PHY:
> > %d\n", ret);
> > + return ret;
> > + }
> > +
> > + /* Platform dependent clocks */
> > + memcpy(dsi->clk_config, dsi->pdata->clk_config,
> > + sizeof(dsi->pdata->clk_config));
> > +
> > + for (i = 0; i < ARRAY_SIZE(dsi->pdata->clk_config); i++) {
> > + if (!dsi->clk_config[i].present)
> > + continue;
> > +
> > + clk_id = dsi->clk_config[i].id;
> > + clk = devm_clk_get(dsi->dev, clk_id);
> > + if (IS_ERR(clk)) {
> > + ret = PTR_ERR(clk);
> > + DRM_DEV_ERROR(dsi->dev, "Failed to get %s
> > clock: %d\n",
> > + clk_id, ret);
> > + return ret;
> > + }
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "Setup clk %s (rate:
> > %lu)\n",
> > + clk_id, clk_get_rate(clk));
> > + dsi->clk_config[i].clk = clk;
> > + }
> > +
> > + /* DSI clocks */
> > + clk = devm_clk_get(dsi->dev, "phy_ref");
> > + if (IS_ERR(clk)) {
> > + ret = PTR_ERR(clk);
> > + DRM_DEV_ERROR(dsi->dev, "Failed to get phy_ref clock:
> > %d\n",
> > + ret);
> > + return ret;
> > + }
> > + dsi->phy_ref_clk = clk;
> > +
> > + clk = devm_clk_get(dsi->dev, "rx_esc");
> > + if (IS_ERR(clk)) {
> > + ret = PTR_ERR(clk);
> > + DRM_DEV_ERROR(dsi->dev, "Failed to get rx_esc clock:
> > %d\n",
> > + ret);
> > + return ret;
> > + }
> > + dsi->rx_esc_clk = clk;
> > +
> > + clk = devm_clk_get(dsi->dev, "tx_esc");
> > + if (IS_ERR(clk)) {
> > + ret = PTR_ERR(clk);
> > + DRM_DEV_ERROR(dsi->dev, "Failed to get tx_esc clock:
> > %d\n",
> > + ret);
> > + return ret;
> > + }
> > + dsi->tx_esc_clk = clk;
> > +
> > + dsi->mux = devm_mux_control_get(dsi->dev, NULL);
> > + if (IS_ERR(dsi->mux)) {
> > + ret = PTR_ERR(dsi->mux);
> > + if (ret != -EPROBE_DEFER)
> > + DRM_DEV_ERROR(dsi->dev, "Failed to get mux:
> > %d\n", ret);
> > + return ret;
> > + }
> > +
> > + base = devm_platform_ioremap_resource(pdev, 0);
> > + if (IS_ERR(base))
> > + return PTR_ERR(base);
> > +
> > + dsi->regmap =
> > + devm_regmap_init_mmio(dsi->dev, base,
> > &nwl_dsi_regmap_config);
> > + if (IS_ERR(dsi->regmap)) {
> > + ret = PTR_ERR(dsi->regmap);
> > + DRM_DEV_ERROR(dsi->dev, "Failed to create NWL DSI
> > regmap: %d\n",
> > + ret);
> > + return ret;
> > + }
> > +
> > + dsi->irq = platform_get_irq(pdev, 0);
> > + if (dsi->irq < 0) {
> > + DRM_DEV_ERROR(dsi->dev, "Failed to get device IRQ:
> > %d\n",
> > + dsi->irq);
> > + return dsi->irq;
> > + }
> > +
> > + dsi->rstc = devm_reset_control_array_get(dsi->dev, false,
> > true);
> > + if (IS_ERR(dsi->rstc)) {
> > + DRM_DEV_ERROR(dsi->dev, "Failed to get resets:
> > %ld\n",
> > + PTR_ERR(dsi->rstc));
> > + return PTR_ERR(dsi->rstc);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int imx8mq_dsi_select_input(struct nwl_dsi *dsi)
> > +{
> > + struct device_node *remote;
> > + u32 use_dcss = 1;
> > + int ret;
> > +
> > + remote = of_graph_get_remote_node(dsi->dev->of_node, 0, 0);
> > + if (strcmp(remote->name, "lcdif") == 0)
> > + use_dcss = 0;
> > +
> > + DRM_DEV_INFO(dsi->dev, "Using %s as input source\n",
> > + (use_dcss) ? "DCSS" : "LCDIF");
> > +
> > + ret = mux_control_try_select(dsi->mux, use_dcss);
> > + if (ret < 0)
> > + DRM_DEV_ERROR(dsi->dev, "Failed to select input:
> > %d\n", ret);
> > +
> > + of_node_put(remote);
> > + return ret;
> > +}
> > +
> > +
> > +static int imx8mq_dsi_deselect_input(struct nwl_dsi *dsi)
> > +{
> > + int ret;
> > +
> > + ret = mux_control_deselect(dsi->mux);
> > + if (ret < 0)
> > + DRM_DEV_ERROR(dsi->dev, "Failed to deselect input:
> > %d\n", ret);
> > +
> > + return ret;
> > +}
> > +
> > +
> > +static int imx8mq_dsi_poweron(struct nwl_dsi *dsi)
> > +{
> > + int ret = 0;
> > +
> > + /* otherwise the display stays blank */
> > + usleep_range(200, 300);
> > +
> > + if (dsi->rstc)
> > + ret = reset_control_deassert(dsi->rstc);
> > +
> > + return ret;
> > +}
> > +
> > +static int imx8mq_dsi_poweroff(struct nwl_dsi *dsi)
> > +{
> > + int ret = 0;
> > +
> > + if (dsi->quirks & SRC_RESET_QUIRK)
> > + return 0;
> > +
> > + if (dsi->rstc)
> > + ret = reset_control_assert(dsi->rstc);
> > + return ret;
> > +}
> > +
> > +static const struct drm_bridge_timings nwl_dsi_timings = {
> > + .input_bus_flags = DRM_BUS_FLAG_DE_LOW,
> > +};
> > +
> > +static const struct nwl_dsi_platform_data imx8mq_dev = {
> > + .poweron = &imx8mq_dsi_poweron,
> > + .poweroff = &imx8mq_dsi_poweroff,
> > + .select_input = &imx8mq_dsi_select_input,
> > + .deselect_input = &imx8mq_dsi_deselect_input,
> > + .clk_config = {
> > + { .id = NWL_DSI_CLK_CORE, .present = true },
> > + },
> > +};
> > +
> > +static const struct of_device_id nwl_dsi_dt_ids[] = {
> > + { .compatible = "fsl,imx8mq-nwl-dsi", .data = &imx8mq_dev, },
> > + { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, nwl_dsi_dt_ids);
> > +
> > +static const struct soc_device_attribute nwl_dsi_quirks_match[] = {
> > + { .soc_id = "i.MX8MQ", .revision = "2.0",
> > + .data = (void *)(E11418_HS_MODE_QUIRK | SRC_RESET_QUIRK) },
> > + { /* sentinel. */ },
> > +};
> > +
> > +static int nwl_dsi_probe(struct platform_device *pdev)
> > +{
> > + struct device *dev = &pdev->dev;
> > + const struct of_device_id *of_id =
> > of_match_device(nwl_dsi_dt_ids, dev);
> > + const struct nwl_dsi_platform_data *pdata = of_id->data;
> > + const struct soc_device_attribute *attr;
> > + struct nwl_dsi *dsi;
> > + int ret;
> > +
> > + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
> > + if (!dsi)
> > + return -ENOMEM;
> > +
> > + dsi->dev = dev;
> > + dsi->pdata = pdata;
> > +
> > + ret = nwl_dsi_parse_dt(dsi);
> > + if (ret)
> > + return ret;
> > +
> > + ret = devm_request_irq(dev, dsi->irq, nwl_dsi_irq_handler, 0,
> > + dev_name(dev), dsi);
> > + if (ret < 0) {
> > + DRM_DEV_ERROR(dev, "Failed to request IRQ %d: %d\n",
> > dsi->irq,
> > + ret);
> > + return ret;
> > + }
> > +
> > + dsi->dsi_host.ops = &nwl_dsi_host_ops;
> > + dsi->dsi_host.dev = dev;
> > + ret = mipi_dsi_host_register(&dsi->dsi_host);
> > + if (ret) {
> > + DRM_DEV_ERROR(dev, "Failed to register MIPI host:
> > %d\n", ret);
> > + return ret;
> > + }
> > +
> > + attr = soc_device_match(nwl_dsi_quirks_match);
> > + if (attr)
> > + dsi->quirks = (uintptr_t)attr->data;
> > +
> > + dsi->bridge.driver_private = dsi;
> > + dsi->bridge.funcs = &nwl_dsi_bridge_funcs;
> > + dsi->bridge.of_node = dev->of_node;
> > + dsi->bridge.timings = &nwl_dsi_timings;
> > +
> > + drm_bridge_add(&dsi->bridge);
> > +
> > + dev_set_drvdata(dev, dsi);
> > + pm_runtime_enable(dev);
> > + return 0;
> > +}
> > +
> > +static int nwl_dsi_remove(struct platform_device *pdev)
> > +{
> > + struct nwl_dsi *dsi = platform_get_drvdata(pdev);
> > +
> > + mipi_dsi_host_unregister(&dsi->dsi_host);
> > + pm_runtime_disable(&pdev->dev);
> > + return 0;
> > +}
> > +
> > +static struct platform_driver nwl_dsi_driver = {
> > + .probe = nwl_dsi_probe,
> > + .remove = nwl_dsi_remove,
> > + .driver = {
> > + .of_match_table = nwl_dsi_dt_ids,
> > + .name = DRV_NAME,
> > + },
> > +};
> > +
> > +module_platform_driver(nwl_dsi_driver);
> > +
> > +MODULE_AUTHOR("NXP Semiconductor");
> > +MODULE_AUTHOR("Purism SPC");
> > +MODULE_DESCRIPTION("Northwest Logic MIPI-DSI driver");
> > +MODULE_LICENSE("GPL"); /* GPLv2 or later */
> > diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
> > b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
> > new file mode 100644
> > index 000000000000..1e72a9221401
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
> > @@ -0,0 +1,65 @@
> > +/* SPDX-License-Identifier: GPL-2.0+ */
> > +/*
> > + * NWL MIPI DSI host driver
> > + *
> > + * Copyright (C) 2017 NXP
> > + * Copyright (C) 2019 Purism SPC
> > + */
> > +
> > +#ifndef __NWL_DRV_H__
> > +#define __NWL_DRV_H__
> > +
> > +#include <linux/mux/consumer.h>
> > +#include <linux/phy/phy.h>
> > +
> > +#include <drm/drm_bridge.h>
> > +#include <drm/drm_mipi_dsi.h>
> > +
> > +struct nwl_dsi_platform_data;
> > +
> > +/* i.MX8 NWL quirks */
> > +/* i.MX8MQ errata E11418 */
> > +#define E11418_HS_MODE_QUIRK BIT(0)
> > +/* Skip DSI bits in SRC on disable to avoid blank display on enable
> > */
> > +#define SRC_RESET_QUIRK BIT(1)
> > +
> > +#define NWL_DSI_MAX_PLATFORM_CLOCKS 1
> > +struct nwl_dsi_plat_clk_config {
> > + const char *id;
> > + struct clk *clk;
> > + bool present;
> > +};
> > +
> > +struct nwl_dsi {
> > + struct drm_bridge bridge;
> > + struct mipi_dsi_host dsi_host;
> > + struct drm_bridge *panel_bridge;
> > + struct device *dev;
> > + struct phy *phy;
> > + union phy_configure_opts phy_cfg;
> > + unsigned int quirks;
> > +
> > + struct regmap *regmap;
> > + int irq;
> > + struct reset_control *rstc;
> > + struct mux_control *mux;
> > +
> > + /* DSI clocks */
> > + struct clk *phy_ref_clk;
> > + struct clk *rx_esc_clk;
> > + struct clk *tx_esc_clk;
> > + /* Platform dependent clocks */
> > + struct nwl_dsi_plat_clk_config
> > clk_config[NWL_DSI_MAX_PLATFORM_CLOCKS];
> > +
> > + /* dsi lanes */
> > + u32 lanes;
> > + enum mipi_dsi_pixel_format format;
> > + struct drm_display_mode mode;
> > + unsigned long dsi_mode_flags;
> > +
> > + struct nwl_dsi_transfer *xfer;
> > +
> > + const struct nwl_dsi_platform_data *pdata;
> > +};
> > +
> > +#endif /* __NWL_DRV_H__ */
> > diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
> > b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
> > new file mode 100644
> > index 000000000000..fd030af55bb4
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
> > @@ -0,0 +1,700 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * NWL MIPI DSI host driver
> > + *
> > + * Copyright (C) 2017 NXP
> > + * Copyright (C) 2019 Purism SPC
> > + */
> > +
> > +#include <linux/bitfield.h>
> > +#include <linux/clk.h>
> > +#include <linux/irq.h>
> > +#include <linux/regmap.h>
> > +#include <linux/time64.h>
> > +
> > +#include <video/mipi_display.h>
> > +#include <video/videomode.h>
> > +
> > +#include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_crtc_helper.h>
> > +#include <drm/drm_of.h>
> > +#include <drm/drm_panel.h>
> > +#include <drm/drm_print.h>
> > +
> > +#include "nwl-drv.h"
> > +#include "nwl-dsi.h"
> > +
> > +#define NWL_DSI_MIPI_FIFO_TIMEOUT msecs_to_jiffies(500)
> > +
> > +/*
> > + * PKT_CONTROL format:
> > + * [15: 0] - word count
> > + * [17:16] - virtual channel
> > + * [23:18] - data type
> > + * [24] - LP or HS select (0 - LP, 1 - HS)
> > + * [25] - perform BTA after packet is sent
> > + * [26] - perform BTA only, no packet tx
> > + */
> > +#define NWL_DSI_WC(x) FIELD_PREP(GENMASK(15, 0), (x))
> > +#define NWL_DSI_TX_VC(x) FIELD_PREP(GENMASK(17, 16), (x))
> > +#define NWL_DSI_TX_DT(x) FIELD_PREP(GENMASK(23, 18), (x))
> > +#define NWL_DSI_HS_SEL(x) FIELD_PREP(GENMASK(24, 24), (x))
> > +#define NWL_DSI_BTA_TX(x) FIELD_PREP(GENMASK(25, 25), (x))
> > +#define NWL_DSI_BTA_NO_TX(x) FIELD_PREP(GENMASK(26, 26), (x))
> > +
> > +/*
> > + * RX_PKT_HEADER format:
> > + * [15: 0] - word count
> > + * [21:16] - data type
> > + * [23:22] - virtual channel
> > + */
> > +#define NWL_DSI_RX_DT(x) FIELD_GET(GENMASK(21, 16), (x))
> > +#define NWL_DSI_RX_VC(x) FIELD_GET(GENMASK(23, 22), (x))
> > +
> > +/* DSI Video mode */
> > +#define NWL_DSI_VM_BURST_MODE_WITH_SYNC_PULSES 0
> > +#define NWL_DSI_VM_NON_BURST_MODE_WITH_SYNC_EVENTS BIT(0)
> > +#define NWL_DSI_VM_BURST_MODE BIT(1)
> > +
> > +/* * DPI color coding */
> > +#define NWL_DSI_DPI_16_BIT_565_PACKED 0
> > +#define NWL_DSI_DPI_16_BIT_565_ALIGNED 1
> > +#define NWL_DSI_DPI_16_BIT_565_SHIFTED 2
> > +#define NWL_DSI_DPI_18_BIT_PACKED 3
> > +#define NWL_DSI_DPI_18_BIT_ALIGNED 4
> > +#define NWL_DSI_DPI_24_BIT 5
> > +
> > +/* * DPI Pixel format */
> > +#define NWL_DSI_PIXEL_FORMAT_16 0
> > +#define NWL_DSI_PIXEL_FORMAT_18 BIT(0)
> > +#define NWL_DSI_PIXEL_FORMAT_18L BIT(1)
> > +#define NWL_DSI_PIXEL_FORMAT_24 (BIT(0) | BIT(1))
> > +
> > +enum transfer_direction {
> > + DSI_PACKET_SEND,
> > + DSI_PACKET_RECEIVE,
> > +};
> > +
> > +struct nwl_dsi_transfer {
> > + const struct mipi_dsi_msg *msg;
> > + struct mipi_dsi_packet packet;
> > + struct completion completed;
> > +
> > + int status; /* status of transmission */
> > + enum transfer_direction direction;
> > + bool need_bta;
> > + u8 cmd;
> > + u16 rx_word_count;
> > + size_t tx_len; /* in bytes */
> > + size_t rx_len; /* in bytes */
> > +};
> > +
> > +static int nwl_dsi_write(struct nwl_dsi *dsi, unsigned int reg, u32
> > val)
> > +{
> > + int ret;
> > +
> > + ret = regmap_write(dsi->regmap, reg, val);
> > + if (ret < 0)
> > + DRM_DEV_ERROR(dsi->dev,
> > + "Failed to write NWL DSI reg 0x%x:
> > %d\n", reg,
> > + ret);
> > + return ret;
> > +}
> > +
> > +static u32 nwl_dsi_read(struct nwl_dsi *dsi, u32 reg)
> > +{
> > + unsigned int val;
> > + int ret;
> > +
> > + ret = regmap_read(dsi->regmap, reg, &val);
> > + if (ret < 0)
> > + DRM_DEV_ERROR(dsi->dev, "Failed to read NWL DSI reg
> > 0x%x: %d\n",
> > + reg, ret);
> > +
> > + return val;
> > +}
> > +
> > +static u32 nwl_dsi_get_dpi_pixel_format(enum mipi_dsi_pixel_format
> > format)
> > +{
> > + switch (format) {
> > + case MIPI_DSI_FMT_RGB565:
> > + return NWL_DSI_PIXEL_FORMAT_16;
> > + case MIPI_DSI_FMT_RGB666:
> > + return NWL_DSI_PIXEL_FORMAT_18L;
> > + case MIPI_DSI_FMT_RGB666_PACKED:
> > + return NWL_DSI_PIXEL_FORMAT_18;
> > + case MIPI_DSI_FMT_RGB888:
> > + return NWL_DSI_PIXEL_FORMAT_24;
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +#define PSEC_PER_SEC 1000000000000LL
> > +/*
> > + * ps2bc - Picoseconds to byte clock cycles
> > + */
> > +static u32 ps2bc(struct nwl_dsi *dsi, unsigned long long ps)
> > +{
> > + int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> > +
> > + return DIV_ROUND_UP(ps * dsi->mode.clock * 1000 * bpp,
> > + dsi->lanes * 8 * PSEC_PER_SEC);
> > +}
> > +
> > +/*
> > + * ui2bc - UI time periods to byte clock cycles
> > + */
> > +static u32 ui2bc(struct nwl_dsi *dsi, unsigned long long ui)
> > +{
> > + int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> > +
> > + return DIV_ROUND_UP(ui * dsi->lanes, dsi->mode.clock * 1000 *
> > bpp);
> > +}
> > +
> > +/*
> > + * us2bc - micro seconds to lp clock cycles
> > + */
> > +static u32 us2lp(u32 lp_clk_rate, unsigned long us)
> > +{
> > + return DIV_ROUND_UP(us * lp_clk_rate, USEC_PER_SEC);
> > +}
> > +
> > +static int nwl_dsi_config_host(struct nwl_dsi *dsi)
> > +{
> > + u32 cycles;
> > + struct phy_configure_opts_mipi_dphy *cfg = &dsi-
> > >phy_cfg.mipi_dphy;
> > +
> > + if (dsi->lanes < 1 || dsi->lanes > 4)
> > + return -EINVAL;
> > +
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "DSI Lanes %d\n", dsi->lanes);
> > + nwl_dsi_write(dsi, NWL_DSI_CFG_NUM_LANES, dsi->lanes - 1);
> > +
> > + if (dsi->dsi_mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
> > + nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK,
> > 0x01);
> > + nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP,
> > 0x01);
> > + } else {
> > + nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK,
> > 0x00);
> > + nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP,
> > 0x00);
> > + }
> > +
> > + /* values in byte clock cycles */
> > + cycles = ui2bc(dsi, cfg->clk_pre);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_t_pre: 0x%x\n", cycles);
> > + nwl_dsi_write(dsi, NWL_DSI_CFG_T_PRE, cycles);
> > + cycles = ps2bc(dsi, cfg->lpx + cfg->clk_prepare + cfg-
> > >clk_zero);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap (pre): 0x%x\n",
> > cycles);
> > + cycles += ui2bc(dsi, cfg->clk_pre);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap: 0x%x\n", cycles);
> > + nwl_dsi_write(dsi, NWL_DSI_CFG_T_POST, cycles);
> > + cycles = ps2bc(dsi, cfg->hs_exit);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap: 0x%x\n", cycles);
> > + nwl_dsi_write(dsi, NWL_DSI_CFG_TX_GAP, cycles);
> > +
> > + nwl_dsi_write(dsi, NWL_DSI_CFG_EXTRA_CMDS_AFTER_EOTP, 0x01);
> > + nwl_dsi_write(dsi, NWL_DSI_CFG_HTX_TO_COUNT, 0x00);
> > + nwl_dsi_write(dsi, NWL_DSI_CFG_LRX_H_TO_COUNT, 0x00);
> > + nwl_dsi_write(dsi, NWL_DSI_CFG_BTA_H_TO_COUNT, 0x00);
> > + /* In LP clock cycles */
> > + cycles = us2lp(cfg->lp_clk_rate, cfg->wakeup);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_twakeup: 0x%x\n",
> > cycles);
> > + nwl_dsi_write(dsi, NWL_DSI_CFG_TWAKEUP, cycles);
> > +
> > + return 0;
> > +}
> > +
> > +static int nwl_dsi_config_dpi(struct nwl_dsi *dsi)
> > +{
> > + u32 color_format, mode;
> > + bool burst_mode;
> > + int hfront_porch, hback_porch, vfront_porch, vback_porch;
> > + int hsync_len, vsync_len;
> > +
> > + hfront_porch = dsi->mode.hsync_start - dsi->mode.hdisplay;
> > + hsync_len = dsi->mode.hsync_end - dsi->mode.hsync_start;
> > + hback_porch = dsi->mode.htotal - dsi->mode.hsync_end;
> > +
> > + vfront_porch = dsi->mode.vsync_start - dsi->mode.vdisplay;
> > + vsync_len = dsi->mode.vsync_end - dsi->mode.vsync_start;
> > + vback_porch = dsi->mode.vtotal - dsi->mode.vsync_end;
> > +
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hfront_porch = %d\n",
> > hfront_porch);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hback_porch = %d\n",
> > hback_porch);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hsync_len = %d\n",
> > hsync_len);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hdisplay = %d\n", dsi-
> > >mode.hdisplay);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vfront_porch = %d\n",
> > vfront_porch);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vback_porch = %d\n",
> > vback_porch);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vsync_len = %d\n",
> > vsync_len);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vactive = %d\n", dsi-
> > >mode.vdisplay);
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "clock = %d kHz\n", dsi-
> > >mode.clock);
> > +
> > + color_format = nwl_dsi_get_dpi_pixel_format(dsi->format);
> > + if (color_format < 0) {
> > + DRM_DEV_ERROR(dsi->dev, "Invalid color format
> > 0x%x\n",
> > + dsi->format);
> > + return color_format;
> > + }
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "pixel fmt = %d\n", dsi-
> > >format);
> > +
> > + nwl_dsi_write(dsi, NWL_DSI_INTERFACE_COLOR_CODING,
> > NWL_DSI_DPI_24_BIT);
> > + nwl_dsi_write(dsi, NWL_DSI_PIXEL_FORMAT, color_format);
> > + /*
> > + * Adjusting input polarity based on the video mode results
> > in
> > + * a black screen so always pick active low:
> > + */
> > + nwl_dsi_write(dsi, NWL_DSI_VSYNC_POLARITY,
> > + NWL_DSI_VSYNC_POLARITY_ACTIVE_LOW);
> > + nwl_dsi_write(dsi, NWL_DSI_HSYNC_POLARITY,
> > + NWL_DSI_HSYNC_POLARITY_ACTIVE_LOW);
> > +
> > + burst_mode = (dsi->dsi_mode_flags &
> > MIPI_DSI_MODE_VIDEO_BURST) &&
> > + !(dsi->dsi_mode_flags &
> > MIPI_DSI_MODE_VIDEO_SYNC_PULSE);
> > +
> > + if (burst_mode) {
> > + nwl_dsi_write(dsi, NWL_DSI_VIDEO_MODE,
> > NWL_DSI_VM_BURST_MODE);
> > + nwl_dsi_write(dsi, NWL_DSI_PIXEL_FIFO_SEND_LEVEL,
> > 256);
> > + } else {
> > + mode = ((dsi->dsi_mode_flags &
> > MIPI_DSI_MODE_VIDEO_SYNC_PULSE) ?
> > + NWL_DSI_VM_BURST_MODE_WITH_SYNC_PULSE
> > S :
> > + NWL_DSI_VM_NON_BURST_MODE_WITH_SYNC_E
> > VENTS);
> > + nwl_dsi_write(dsi, NWL_DSI_VIDEO_MODE, mode);
> > + nwl_dsi_write(dsi, NWL_DSI_PIXEL_FIFO_SEND_LEVEL,
> > + dsi->mode.hdisplay);
> > + }
> > +
> > + nwl_dsi_write(dsi, NWL_DSI_HFP, hfront_porch);
> > + nwl_dsi_write(dsi, NWL_DSI_HBP, hback_porch);
> > + nwl_dsi_write(dsi, NWL_DSI_HSA, hsync_len);
> > +
> > + nwl_dsi_write(dsi, NWL_DSI_ENABLE_MULT_PKTS, 0x0);
> > + nwl_dsi_write(dsi, NWL_DSI_BLLP_MODE, 0x1);
> > + nwl_dsi_write(dsi, NWL_DSI_ENABLE_MULT_PKTS, 0x0);
> NWL_DSI_ENABLE_MULT_PKTS is written twice, by mistake, so remove this
> line. I did the same mistake on NXP tree, I think you got it from there
Dropped.
> :)
> > + nwl_dsi_write(dsi, NWL_DSI_USE_NULL_PKT_BLLP, 0x0);
> > + nwl_dsi_write(dsi, NWL_DSI_VC, 0x0);
> > +
> > + nwl_dsi_write(dsi, NWL_DSI_PIXEL_PAYLOAD_SIZE, dsi-
> > >mode.hdisplay);
> > + nwl_dsi_write(dsi, NWL_DSI_VACTIVE, dsi->mode.vdisplay - 1);
> VACTIVE shold contain the number of lines in the vertical area. "-1"
> subtraction was actually wrong.
So is that an error in the imx8MQ reference manual which says in
MIPI_DSI_HOST_DPI_INTFC_DSI_HOST_CFG_DPI_VACTIVE:
Sets the number of lines in the vertical active area. This field is
equivalent to (real vertical size) - 1. For example, for an image of
size 640x480, the bit field should be set as 479.
Is so I'll fix that but it'd be great if you could confirm. I don't see
any visual difference with the panel i have here.
> > + nwl_dsi_write(dsi, NWL_DSI_VBP, vback_porch);
> > + nwl_dsi_write(dsi, NWL_DSI_VFP, vfront_porch);
> > +
> > + return 0;
> > +}
> > +
> > +static void nwl_dsi_init_interrupts(struct nwl_dsi *dsi)
> > +{
> > + u32 irq_enable;
> > +
> > + nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, 0xffffffff);
> > + nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK2, 0x7);
> > +
> > + irq_enable = ~(u32)(NWL_DSI_TX_PKT_DONE_MASK |
> > + NWL_DSI_RX_PKT_HDR_RCVD_MASK |
> > + NWL_DSI_TX_FIFO_OVFLW_MASK |
> > + NWL_DSI_HS_TX_TIMEOUT_MASK);
> > +
> > + nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, irq_enable);
> > +}
> > +
> > +static int nwl_dsi_host_attach(struct mipi_dsi_host *dsi_host,
> > + struct mipi_dsi_device *device)
> > +{
> > + struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi,
> > dsi_host);
> > + struct device *dev = dsi->dev;
> > + struct drm_bridge *bridge;
> > + struct drm_panel *panel;
> > + int ret;
> > +
> > + DRM_DEV_INFO(dev, "lanes=%u, format=0x%x flags=0x%lx\n",
> > device->lanes,
> > + device->format, device->mode_flags);
> > +
> > + if (device->lanes < 1 || device->lanes > 4)
> > + return -EINVAL;
> > +
> > + dsi->lanes = device->lanes;
> > + dsi->format = device->format;
> > + dsi->dsi_mode_flags = device->mode_flags;
> > +
> > + ret = drm_of_find_panel_or_bridge(dsi->dev->of_node, 1, 0,
> > &panel,
> > + &bridge);
> > + if (ret)
> > + return ret;
> > +
> > + if (panel) {
> > + bridge = drm_panel_bridge_add(panel,
> > DRM_MODE_CONNECTOR_DSI);
> > + if (IS_ERR(bridge))
> > + return PTR_ERR(bridge);
> > + }
> > +
> > + dsi->panel_bridge = bridge;
> > + drm_bridge_add(&dsi->bridge);
> > +
> > + return 0;
> > +}
> > +
> > +static int nwl_dsi_host_detach(struct mipi_dsi_host *dsi_host,
> > + struct mipi_dsi_device *device)
> > +{
> > + struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi,
> > dsi_host);
> > +
> > + drm_of_panel_bridge_remove(dsi->dev->of_node, 1, 0);
> > + drm_bridge_remove(&dsi->bridge);
> > +
> > + return 0;
> > +}
> > +
> > +static bool nwl_dsi_read_packet(struct nwl_dsi *dsi, u32 status)
> > +{
> > + struct device *dev = dsi->dev;
> > + struct nwl_dsi_transfer *xfer = dsi->xfer;
> > + u8 *payload = xfer->msg->rx_buf;
> > + u32 val;
> > + u16 word_count;
> > + u8 channel;
> > + u8 data_type;
> > +
> > + xfer->status = 0;
> > +
> > + if (xfer->rx_word_count == 0) {
> > + if (!(status & NWL_DSI_RX_PKT_HDR_RCVD))
> > + return false;
> > + /* Get the RX header and parse it */
> > + val = nwl_dsi_read(dsi, NWL_DSI_RX_PKT_HEADER);
> > + word_count = NWL_DSI_WC(val);
> > + channel = NWL_DSI_RX_VC(val);
> > + data_type = NWL_DSI_RX_DT(val);
> > +
> > + if (channel != xfer->msg->channel) {
> > + DRM_DEV_ERROR(dev,
> > + "[%02X] Channel mismatch (%u !=
> > %u)\n",
> > + xfer->cmd, channel, xfer->msg-
> > >channel);
> > + return true;
> > + }
> > +
> > + switch (data_type) {
> > + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
> > + /* Fall through */
> > + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
> > + if (xfer->msg->rx_len > 1) {
> > + /* read second byte */
> > + payload[1] = word_count >> 8;
> > + ++xfer->rx_len;
> > + }
> > + /* Fall through */
> > + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
> > + /* Fall through */
> > + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
> > + if (xfer->msg->rx_len > 0) {
> > + /* read first byte */
> > + payload[0] = word_count & 0xff;
> > + ++xfer->rx_len;
> > + }
> > + xfer->status = xfer->rx_len;
> > + return true;
> > + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
> > + word_count &= 0xff;
> > + DRM_DEV_ERROR(dev, "[%02X] DSI error report:
> > 0x%02x\n",
> > + xfer->cmd, word_count);
> > + xfer->status = -EPROTO;
> > + return true;
> > + }
> > +
> > + if (word_count > xfer->msg->rx_len) {
> > + DRM_DEV_ERROR(
> > + dev,
> > + "[%02X] Receive buffer too small: %lu
> > (< %u)\n",
> > + xfer->cmd, xfer->msg->rx_len,
> > word_count);
> > + return true;
> > + }
> > +
> > + xfer->rx_word_count = word_count;
> > + } else {
> > + /* Set word_count from previous header read */
> > + word_count = xfer->rx_word_count;
> > + }
> > +
> > + /* If RX payload is not yet received, wait for it */
> > + if (!(status & NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD))
> > + return false;
> > +
> > + /* Read the RX payload */
> > + while (word_count >= 4) {
> > + val = nwl_dsi_read(dsi, NWL_DSI_RX_PAYLOAD);
> > + payload[0] = (val >> 0) & 0xff;
> > + payload[1] = (val >> 8) & 0xff;
> > + payload[2] = (val >> 16) & 0xff;
> > + payload[3] = (val >> 24) & 0xff;
> > + payload += 4;
> > + xfer->rx_len += 4;
> > + word_count -= 4;
> > + }
> > +
> > + if (word_count > 0) {
> > + val = nwl_dsi_read(dsi, NWL_DSI_RX_PAYLOAD);
> > + switch (word_count) {
> > + case 3:
> > + payload[2] = (val >> 16) & 0xff;
> > + ++xfer->rx_len;
> > + /* Fall through */
> > + case 2:
> > + payload[1] = (val >> 8) & 0xff;
> > + ++xfer->rx_len;
> > + /* Fall through */
> > + case 1:
> > + payload[0] = (val >> 0) & 0xff;
> > + ++xfer->rx_len;
> > + break;
> > + }
> > + }
> > +
> > + xfer->status = xfer->rx_len;
> > +
> > + return true;
> > +}
> > +
> > +static void nwl_dsi_finish_transmission(struct nwl_dsi *dsi, u32
> > status)
> > +{
> > + struct nwl_dsi_transfer *xfer = dsi->xfer;
> > + bool end_packet = false;
> > +
> > + if (!xfer)
> > + return;
> > +
> > + if (status & NWL_DSI_TX_FIFO_OVFLW) {
> > + DRM_DEV_ERROR_RATELIMITED(dsi->dev, "tx fifo
> > overflow\n");
> > + return;
> > + }
> > +
> > + if (status & NWL_DSI_HS_TX_TIMEOUT) {
> > + DRM_DEV_ERROR_RATELIMITED(dsi->dev, "HS tx
> > timeout\n");
> > + return;
> > + }
> > +
> > + if (xfer->direction == DSI_PACKET_SEND &&
> > + status & NWL_DSI_TX_PKT_DONE) {
> > + xfer->status = xfer->tx_len;
> > + end_packet = true;
> > + } else if (status & NWL_DSI_DPHY_DIRECTION &&
> > + ((status & (NWL_DSI_RX_PKT_HDR_RCVD |
> > + NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD)))) {
> > + end_packet = nwl_dsi_read_packet(dsi, status);
> > + }
> > +
> > + if (end_packet)
> > + complete(&xfer->completed);
> > +}
> > +
> > +static void nwl_dsi_begin_transmission(struct nwl_dsi *dsi)
> > +{
> > + struct nwl_dsi_transfer *xfer = dsi->xfer;
> > + struct mipi_dsi_packet *pkt = &xfer->packet;
> > + const u8 *payload;
> > + size_t length;
> > + u16 word_count;
> > + u8 hs_mode;
> > + u32 val;
> > + u32 hs_workaround = 0;
> > +
> > + /* Send the payload, if any */
> > + length = pkt->payload_length;
> > + payload = pkt->payload;
> > +
> > + while (length >= 4) {
> > + val = *(u32 *)payload;
> > + hs_workaround |= !(val & 0xFFFF00);
> > + nwl_dsi_write(dsi, NWL_DSI_TX_PAYLOAD, val);
> > + payload += 4;
> > + length -= 4;
> > + }
> > + /* Send the rest of the payload */
> > + val = 0;
> > + switch (length) {
> > + case 3:
> > + val |= payload[2] << 16;
> > + /* Fall through */
> > + case 2:
> > + val |= payload[1] << 8;
> > + hs_workaround |= !(val & 0xFFFF00);
> > + /* Fall through */
> > + case 1:
> > + val |= payload[0];
> > + nwl_dsi_write(dsi, NWL_DSI_TX_PAYLOAD, val);
> > + break;
> > + }
> > + xfer->tx_len = pkt->payload_length;
> > +
> > + /*
> > + * Send the header
> > + * header[0] = Virtual Channel + Data Type
> > + * header[1] = Word Count LSB (LP) or first param (SP)
> > + * header[2] = Word Count MSB (LP) or second param (SP)
> > + */
> > + word_count = pkt->header[1] | (pkt->header[2] << 8);
> > + if (hs_workaround && (dsi->quirks & E11418_HS_MODE_QUIRK)) {
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev,
> > + "Using hs mode workaround for
> > cmd 0x%x\n",
> > + xfer->cmd);
> > + hs_mode = 1;
> > + } else {
> > + hs_mode = (xfer->msg->flags & MIPI_DSI_MSG_USE_LPM) ?
> > 0 : 1;
> > + }
> > + val = NWL_DSI_WC(word_count) | NWL_DSI_TX_VC(xfer->msg-
> > >channel) |
> > + NWL_DSI_TX_DT(xfer->msg->type) |
> > NWL_DSI_HS_SEL(hs_mode) |
> > + NWL_DSI_BTA_TX(xfer->need_bta);
> > + nwl_dsi_write(dsi, NWL_DSI_PKT_CONTROL, val);
> > +
> > + /* Send packet command */
> > + nwl_dsi_write(dsi, NWL_DSI_SEND_PACKET, 0x1);
> > +}
> > +
> > +static ssize_t nwl_dsi_host_transfer(struct mipi_dsi_host *dsi_host,
> > + const struct mipi_dsi_msg *msg)
> > +{
> > + struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi,
> > dsi_host);
> > + struct nwl_dsi_transfer xfer;
> > + ssize_t ret = 0;
> > +
> > + /* Create packet to be sent */
> > + dsi->xfer = &xfer;
> > + ret = mipi_dsi_create_packet(&xfer.packet, msg);
> > + if (ret < 0) {
> > + dsi->xfer = NULL;
> > + return ret;
> > + }
> > +
> > + if ((msg->type & MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM ||
> > + msg->type & MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM ||
> > + msg->type & MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM ||
> > + msg->type & MIPI_DSI_DCS_READ) &&
> > + msg->rx_len > 0 && msg->rx_buf != NULL)
> > + xfer.direction = DSI_PACKET_RECEIVE;
> > + else
> > + xfer.direction = DSI_PACKET_SEND;
> > +
> > + xfer.need_bta = (xfer.direction == DSI_PACKET_RECEIVE);
> > + xfer.need_bta |= (msg->flags & MIPI_DSI_MSG_REQ_ACK) ? 1 : 0;
> > + xfer.msg = msg;
> > + xfer.status = -ETIMEDOUT;
> > + xfer.rx_word_count = 0;
> > + xfer.rx_len = 0;
> > + xfer.cmd = 0x00;
> > + if (msg->tx_len > 0)
> > + xfer.cmd = ((u8 *)(msg->tx_buf))[0];
> > + init_completion(&xfer.completed);
> > +
> > + ret = clk_prepare_enable(dsi->rx_esc_clk);
> > + if (ret < 0) {
> > + DRM_DEV_ERROR(dsi->dev, "Failed to enable rx_esc clk:
> > %zd\n",
> > + ret);
> > + return ret;
> > + }
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "Enabled rx_esc clk @%lu
> > Hz\n",
> > + clk_get_rate(dsi->rx_esc_clk));
> This will be printed every time a DSI command is sent, of course, when
> debugging only, but still: are you sure you need this info?
i'd like to keep it for the moment, it's only on during init and only in
debug but helps to see clocks are toggled correctly.
> > +
> > + /* Initiate the DSI packet transmision */
> > + nwl_dsi_begin_transmission(dsi);
> > +
> > + if (!wait_for_completion_timeout(&xfer.completed,
> > + NWL_DSI_MIPI_FIFO_TIMEOUT))
> > {
> > + DRM_DEV_ERROR(dsi_host->dev, "[%02X] DSI transfer
> > timed out\n",
> > + xfer.cmd);
> > + ret = -ETIMEDOUT;
> > + } else {
> > + ret = xfer.status;
> > + }
> > +
> > + clk_disable_unprepare(dsi->rx_esc_clk);
> > +
> > + return ret;
> > +}
> > +
> > +const struct mipi_dsi_host_ops nwl_dsi_host_ops = {
> > + .attach = nwl_dsi_host_attach,
> > + .detach = nwl_dsi_host_detach,
> > + .transfer = nwl_dsi_host_transfer,
> > +};
> > +
> > +irqreturn_t nwl_dsi_irq_handler(int irq, void *data)
> > +{
> > + u32 irq_status;
> > + struct nwl_dsi *dsi = data;
> > +
> > + irq_status = nwl_dsi_read(dsi, NWL_DSI_IRQ_STATUS);
> > +
> > + if (irq_status & NWL_DSI_TX_PKT_DONE ||
> > + irq_status & NWL_DSI_RX_PKT_HDR_RCVD ||
> > + irq_status & NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD)
> > + nwl_dsi_finish_transmission(dsi, irq_status);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +int nwl_dsi_enable(struct nwl_dsi *dsi)
> > +{
> > + struct device *dev = dsi->dev;
> > + union phy_configure_opts *phy_cfg = &dsi->phy_cfg;
> > + int ret;
> > +
> > + if (!dsi->lanes) {
> > + DRM_DEV_ERROR(dev, "Need DSI lanes: %d\n", dsi-
> > >lanes);
> > + return -EINVAL;
> > + }
> > +
> > + ret = phy_init(dsi->phy);
> > + if (ret < 0) {
> > + DRM_DEV_ERROR(dev, "Failed to init DSI phy: %d\n",
> > ret);
> > + return ret;
> > + }
> > +
> > + ret = phy_configure(dsi->phy, phy_cfg);
> > + if (ret < 0) {
> > + DRM_DEV_ERROR(dev, "Failed to configure DSI phy:
> > %d\n", ret);
> > + return ret;
> > + }
> > +
> > + ret = clk_prepare_enable(dsi->tx_esc_clk);
> > + if (ret < 0) {
> > + DRM_DEV_ERROR(dsi->dev, "Failed to enable tx_esc clk:
> > %d\n",
> > + ret);
> > + return ret;
> > + }
> > + DRM_DEV_DEBUG_DRIVER(dsi->dev, "Enabled tx_esc clk @%lu
> > Hz\n",
> > + clk_get_rate(dsi->tx_esc_clk));
> > +
> > + ret = nwl_dsi_config_host(dsi);
> > + if (ret < 0) {
> > + DRM_DEV_ERROR(dev, "Failed to set up DSI: %d", ret);
> > + return ret;
> > + }
> > +
> > + ret = nwl_dsi_config_dpi(dsi);
> > + if (ret < 0) {
> > + DRM_DEV_ERROR(dev, "Failed to set up DPI: %d", ret);
> > + return ret;
> > + }
> > +
> > + ret = phy_power_on(dsi->phy);
> > + if (ret < 0) {
> > + DRM_DEV_ERROR(dev, "Failed to power on DPHY (%d)\n",
> > ret);
> > + return ret;
> > + }
> > +
> > + nwl_dsi_init_interrupts(dsi);
> > +
> > + return 0;
> > +}
> I see that you do all the initialization from the pre_enable stage. If
> you power on the DPHY, the DSI host block will start transmitting pixel
> data on the data lanes. We had some customers that complained about
> this, so I think is better to hold on the pixel flow until the whole
> pixel pipe is completely initialized.
I had the dphy power on past the dsi host setup to make sure the host
sends data matching the dphy setup. I'm happy to change that but...
> What I recommend here, is to also use the enable stage in the bridge
> and move nwl_dsi_config_host in there. Thus, the call flow will be:
> 1. bridge->pre_enable (DSI is configured, but not streaming since the
> host is not yet configured)
...what parameter exactly is gating the pixel stream in
nwl_dsi_config_host() ? because that is what would need to go into
enable at least, right? The function is mostly concerned with HS mode
setup and from the docs nothing stands out that would enable/disable the
pixel stream per se. Or do you mean nwl_dsi_config_dpi() ?
Thanks a lot for your comments!
-- Guido
> 2. panel->prepare (panel will use the DSI APB block to send DSI
> commands)
> 3. bridge->enable (DSI host block is configured, DSI starts streamming
> pixels)
> 3. panel->enable (panel is ready to display the pixel flow)
> > +
> > +int nwl_dsi_disable(struct nwl_dsi *dsi)
> > +{
> > + struct device *dev = dsi->dev;
> > +
> > + DRM_DEV_DEBUG_DRIVER(dev, "Disabling clocks and phy\n");
> > +
> > + phy_power_off(dsi->phy);
> > + phy_exit(dsi->phy);
> > +
> > + /* Disabling the clock before the phy breaks enabling dsi
> > again */
> > + clk_disable_unprepare(dsi->tx_esc_clk);
> > +
> > + return 0;
> > +}
> > diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
> > b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
> > new file mode 100644
> > index 000000000000..579b366de652
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
> > @@ -0,0 +1,112 @@
> > +/* SPDX-License-Identifier: GPL-2.0+ */
> > +/*
> > + * NWL MIPI DSI host driver
> > + *
> > + * Copyright (C) 2017 NXP
> > + * Copyright (C) 2019 Purism SPC
> > + */
> > +#ifndef __NWL_DSI_H__
> > +#define __NWL_DSI_H__
> > +
> > +#include <linux/irqreturn.h>
> > +
> > +#include <drm/drm_mipi_dsi.h>
> > +
> > +#include "nwl-drv.h"
> > +
> > +/* DSI HOST registers */
> > +#define NWL_DSI_CFG_NUM_LANES 0x0
> > +#define NWL_DSI_CFG_NONCONTINUOUS_CLK 0x4
> > +#define NWL_DSI_CFG_T_PRE 0x8
> > +#define NWL_DSI_CFG_T_POST 0xc
> > +#define NWL_DSI_CFG_TX_GAP 0x10
> > +#define NWL_DSI_CFG_AUTOINSERT_EOTP 0x14
> > +#define NWL_DSI_CFG_EXTRA_CMDS_AFTER_EOTP 0x18
> > +#define NWL_DSI_CFG_HTX_TO_COUNT 0x1c
> > +#define NWL_DSI_CFG_LRX_H_TO_COUNT 0x20
> > +#define NWL_DSI_CFG_BTA_H_TO_COUNT 0x24
> > +#define NWL_DSI_CFG_TWAKEUP 0x28
> > +#define NWL_DSI_CFG_STATUS_OUT 0x2c
> > +#define NWL_DSI_RX_ERROR_STATUS 0x30
> > +
> > +/* DSI DPI registers */
> > +#define NWL_DSI_PIXEL_PAYLOAD_SIZE 0x200
> > +#define NWL_DSI_PIXEL_FIFO_SEND_LEVEL 0x204
> > +#define NWL_DSI_INTERFACE_COLOR_CODING 0x208
> > +#define NWL_DSI_PIXEL_FORMAT 0x20c
> > +#define NWL_DSI_VSYNC_POLARITY 0x210
> > +#define NWL_DSI_VSYNC_POLARITY_ACTIVE_LOW 0
> > +#define NWL_DSI_VSYNC_POLARITY_ACTIVE_HIGH BIT(1)
> > +
> > +#define NWL_DSI_HSYNC_POLARITY 0x214
> > +#define NWL_DSI_HSYNC_POLARITY_ACTIVE_LOW 0
> > +#define NWL_DSI_HSYNC_POLARITY_ACTIVE_HIGH BIT(1)
> > +
> > +#define NWL_DSI_VIDEO_MODE 0x218
> > +#define NWL_DSI_HFP 0x21c
> > +#define NWL_DSI_HBP 0x220
> > +#define NWL_DSI_HSA 0x224
> > +#define NWL_DSI_ENABLE_MULT_PKTS 0x228
> > +#define NWL_DSI_VBP 0x22c
> > +#define NWL_DSI_VFP 0x230
> > +#define NWL_DSI_BLLP_MODE 0x234
> > +#define NWL_DSI_USE_NULL_PKT_BLLP 0x238
> > +#define NWL_DSI_VACTIVE 0x23c
> > +#define NWL_DSI_VC 0x240
> > +
> > +/* DSI APB PKT control */
> > +#define NWL_DSI_TX_PAYLOAD 0x280
> > +#define NWL_DSI_PKT_CONTROL 0x284
> > +#define NWL_DSI_SEND_PACKET 0x288
> > +#define NWL_DSI_PKT_STATUS 0x28c
> > +#define NWL_DSI_PKT_FIFO_WR_LEVEL 0x290
> > +#define NWL_DSI_PKT_FIFO_RD_LEVEL 0x294
> > +#define NWL_DSI_RX_PAYLOAD 0x298
> > +#define NWL_DSI_RX_PKT_HEADER 0x29c
> > +
> > +/* DSI IRQ handling */
> > +#define NWL_DSI_IRQ_STATUS 0x2a0
> > +#define NWL_DSI_SM_NOT_IDLE BIT(0)
> > +#define NWL_DSI_TX_PKT_DONE BIT(1)
> > +#define NWL_DSI_DPHY_DIRECTION BIT(2)
> > +#define NWL_DSI_TX_FIFO_OVFLW BIT(3)
> > +#define NWL_DSI_TX_FIFO_UDFLW BIT(4)
> > +#define NWL_DSI_RX_FIFO_OVFLW BIT(5)
> > +#define NWL_DSI_RX_FIFO_UDFLW BIT(6)
> > +#define NWL_DSI_RX_PKT_HDR_RCVD BIT(7)
> > +#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD BIT(8)
> > +#define NWL_DSI_BTA_TIMEOUT BIT(29)
> > +#define NWL_DSI_LP_RX_TIMEOUT BIT(30)
> > +#define NWL_DSI_HS_TX_TIMEOUT BIT(31)
> > +
> > +#define NWL_DSI_IRQ_STATUS2 0x2a4
> > +#define NWL_DSI_SINGLE_BIT_ECC_ERR BIT(0)
> > +#define NWL_DSI_MULTI_BIT_ECC_ERR BIT(1)
> > +#define NWL_DSI_CRC_ERR BIT(2)
> > +
> > +#define NWL_DSI_IRQ_MASK 0x2a8
> > +#define NWL_DSI_SM_NOT_IDLE_MASK BIT(0)
> > +#define NWL_DSI_TX_PKT_DONE_MASK BIT(1)
> > +#define NWL_DSI_DPHY_DIRECTION_MASK BIT(2)
> > +#define NWL_DSI_TX_FIFO_OVFLW_MASK BIT(3)
> > +#define NWL_DSI_TX_FIFO_UDFLW_MASK BIT(4)
> > +#define NWL_DSI_RX_FIFO_OVFLW_MASK BIT(5)
> > +#define NWL_DSI_RX_FIFO_UDFLW_MASK BIT(6)
> > +#define NWL_DSI_RX_PKT_HDR_RCVD_MASK BIT(7)
> > +#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD_MASK BIT(8)
> > +#define NWL_DSI_BTA_TIMEOUT_MASK BIT(29)
> > +#define NWL_DSI_LP_RX_TIMEOUT_MASK BIT(30)
> > +#define NWL_DSI_HS_TX_TIMEOUT_MASK BIT(31)
> > +
> > +#define NWL_DSI_IRQ_MASK2 0x2ac
> > +#define NWL_DSI_SINGLE_BIT_ECC_ERR_MASK BIT(0)
> > +#define NWL_DSI_MULTI_BIT_ECC_ERR_MASK BIT(1)
> > +#define NWL_DSI_CRC_ERR_MASK BIT(2)
> > +
> > +extern const struct mipi_dsi_host_ops nwl_dsi_host_ops;
> > +
> > +irqreturn_t nwl_dsi_irq_handler(int irq, void *data);
> > +int nwl_dsi_enable(struct nwl_dsi *dsi);
> > +int nwl_dsi_disable(struct nwl_dsi *dsi);
> > +
> > +#endif /* __NWL_DSI_H__ */
> > --
> > 2.20.1
> >
> >
> Best regards,
> Robert
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v3 3/5] arm64: atomics: avoid out-of-line ll/sc atomics
From: Mark Rutland @ 2019-08-22 17:01 UTC (permalink / raw)
To: Andrew Murray
Cc: Peter Zijlstra, Catalin Marinas, Boqun Feng, Will Deacon,
Ard.Biesheuvel, linux-arm-kernel
In-Reply-To: <20190812143625.42745-4-andrew.murray@arm.com>
On Mon, Aug 12, 2019 at 03:36:23PM +0100, Andrew Murray wrote:
> When building for LSE atomics (CONFIG_ARM64_LSE_ATOMICS), if the hardware
> or toolchain doesn't support it the existing code will fallback to ll/sc
> atomics. It achieves this by branching from inline assembly to a function
> that is built with specical compile flags. Further this results in the
> clobbering of registers even when the fallback isn't used increasing
> register pressure.
>
> Let's improve this by providing inline implementations of both LSE and
> ll/sc and use a static key to select between them. This allows for the
> compiler to generate better atomics code.
>
> To improve icache performance for the LL/SC fallback atomics, we put them
> in their own subsection.
>
> Please note that as atomic_arch.h is included indirectly by kernel.h
> (via bitops.h), we cannot depend on features provided later in the kernel.h
> file. This prevents us from placing the system_uses_lse_atomics function
> in cpu_feature.h due to its dependencies.
>
> Signed-off-by: Andrew Murray <andrew.murray@arm.com>
[...]
> diff --git a/arch/arm64/include/asm/atomic_arch.h b/arch/arm64/include/asm/atomic_arch.h
> new file mode 100644
> index 000000000000..255a284321c6
> --- /dev/null
> +++ b/arch/arm64/include/asm/atomic_arch.h
> @@ -0,0 +1,154 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Selection between LSE and LL/SC atomics.
> + *
> + * Copyright (C) 2018 ARM Ltd.
> + * Author: Andrew Murray <andrew.murray@arm.com>
> + */
> +
> +#ifndef __ASM_ATOMIC_ARCH_H
> +#define __ASM_ATOMIC_ARCH_H
> +
> +#include <asm/atomic_lse.h>
> +#include <asm/atomic_ll_sc.h>
> +
> +#include <linux/jump_label.h>
> +#include <asm/cpucaps.h>
I'm guessing that we have to include the <asm/atomic_*> headers first
due to the include dependencies. If that's the case, could we please
have a comment here to that effect?
Minor nit, but could we also order those two alphabetically, please?
The general style is to have headers alphabetically, with (for reasons
unknown) the <linux/*> headers before the <asm/*> headers.
[...]
> +#if IS_ENABLED(CONFIG_ARM64_LSE_ATOMICS) && IS_ENABLED(CONFIG_AS_LSE)
> +#define __LL_SC_FALLBACK(asm_ops) \
> +" b 3f\n" \
> +" .subsection 1\n" \
> +"3:\n" \
> +asm_ops "\n" \
> +" b 4f\n" \
> +" .previous\n" \
> +"4:\n"
> +#else
> +#define __LL_SC_FALLBACK(asm_ops) asm_ops
> #endif
Can we instead make the ll/sc functions with the cold attribute (wrapped
by <linux/compiler.h> as __cold)?
IIUC that should have a similar effect, and might allow GCC to do better
(e.g. merging compatible instances of the ll/sc code in the same cold
subsection).
https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/Common-Function-Attributes.html#index-cold-function-attribute
Otherwise, this is looking much nicer!
Thanks,
Mark.
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH 3/3] watchdog/aspeed: add support for dual boot
From: Alexander Amelkin @ 2019-08-22 16:45 UTC (permalink / raw)
To: Guenter Roeck
Cc: linux-watchdog, linux-aspeed, Andrew Jeffery, linux-kernel,
Joel Stanley, Ivan Mikhaylov, Wim Van Sebroeck, linux-arm-kernel
In-Reply-To: <20190822160127.GA6992@roeck-us.net>
[-- Attachment #1.1.1: Type: text/plain, Size: 4196 bytes --]
22.08.2019 19:01, Guenter Roeck wrote:
> On Thu, Aug 22, 2019 at 05:36:21PM +0300, Alexander Amelkin wrote:
>> 21.08.2019 21:10, Guenter Roeck wrote:
>>> On Wed, Aug 21, 2019 at 08:42:24PM +0300, Alexander Amelkin wrote:
>>>> 21.08.2019 19:32, Guenter Roeck wrote:
>>>>> On Wed, Aug 21, 2019 at 06:57:43PM +0300, Ivan Mikhaylov wrote:
>>>>>> Set WDT_CLEAR_TIMEOUT_AND_BOOT_CODE_SELECTION into WDT_CLEAR_TIMEOUT_STATUS
>>>>>> to clear out boot code source and re-enable access to the primary SPI flash
>>>>>> chip while booted via wdt2 from the alternate chip.
>>>>>>
>>>>>> AST2400 datasheet says:
>>>>>> "In the 2nd flash booting mode, all the address mapping to CS0# would be
>>>>>> re-directed to CS1#. And CS0# is not accessable under this mode. To access
>>>>>> CS0#, firmware should clear the 2nd boot mode register in the WDT2 status
>>>>>> register WDT30.bit[1]."
>>>>> Is there reason to not do this automatically when loading the module
>>>>> in alt-boot mode ? What means does userspace have to determine if CS0
>>>>> or CS1 is active at any given time ? If there is reason to ever have CS1
>>>>> active instead of CS0, what means would userspace have to enable it ?
>>>> Yes, there is. The driver is loaded long before the filesystems are mounted.
>>>> The filesystems, in the event of alternate/recovery boot, need to be mounted
>>>> from the same chip that the kernel was booted. For one reason because the main
>>>> chip at CS0 is most probably corrupt. If you clear that bit when driver is
>>>> loaded, your software will not know that and will try to mount the wrong
>>>> filesystems. The whole idea of ASPEED's switching chipselects is to have
>>>> identical firmware in both chips, without the need to process the alternate
>>>> boot state in any way except for indicating a successful boot and restoring
>>>> access to CS0 when needed.
>>>>
>>>> The userspace can read bootstatus sysfs node to determine if an alternate
>>>> boot has occured.
>>>>
>>>> With ASPEED, CS1 is activated automatically by wdt2 when system fails to boot
>>>> from the primary flash chip (at CS0) and disable the watchdog to indicate a
>>>> successful boot. When that happens, both CS0 and CS1 controls get routed in
>>>> hardware to CS1 line, making the primary flash chip inaccessible. Depending
>>>> on the architecture of the user-space software, it may choose to re-enable
>>>> access to the primary chip via CS0 at different times. There must be a way to do so.
>>>>
>>> So by activating cs0, userspace would essentially pull its own root file system
>>> from underneath itself ?
>> Exactly. That's why for alternate boot the firmware would usually copy
>> all filesystems to memory and mount from there. Some embedded systems
>> do that always, regardless of which chip they boot from.
>>
> That is different, though, to what you said earlier. Linux would then start
> with a clean file system, and not need access to the file system in cs1 at all.
> Clearing the flag when starting the driver would then be ok.
I don't see how that is different. Copying to memory may be done by startup
scripts that run after the driver is loaded, so they need to read the data from
the chip they are booted from. That is how it is done in OpenBMC, for instance.
Other flavors of firmware may choose a different approach.
Having the control available via sysfs gives more flexibility.
>> However, to be able to recover the main flash chip, the system needs CS0
>> to function as such (not as CS1). That's why this control is needed.
>>
> If what you said is correct, not really. It should be fine and create more
> predictive behavior if the probe function selects cs0 automatically.
Well, this is not a function for home users. This is for servers. You won't
even find an ASPEED BMC chip in a home PC. Aspeed's dual-boot is quite
an advanced feature and people willing to use it are expected to be able
to predict the behavior. To me, as an embedded systems developer,
automatic selection of cs0 by probe is a limitation. I prefer flexibility.
With best regards,
Alexander Amelkin,
BIOS/BMC Team Lead, YADRO
https://yadro.com
[-- Attachment #1.2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
[-- Attachment #2: Type: text/plain, Size: 176 bytes --]
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v4 6/7] arm64: perf: Enable pmu counter direct access for perf event on armv8
From: Jonathan Cameron @ 2019-08-22 16:39 UTC (permalink / raw)
To: Raphael Gault
Cc: mark.rutland, raph.gault+kdev, peterz, catalin.marinas,
will.deacon, linux-kernel, acme, mingo, linux-arm-kernel
In-Reply-To: <20190822144220.27860-7-raphael.gault@arm.com>
On Thu, 22 Aug 2019 15:42:19 +0100
Raphael Gault <raphael.gault@arm.com> wrote:
> Keep track of event opened with direct access to the hardware counters
> and modify permissions while they are open.
>
> The strategy used here is the same which x86 uses: everytime an event
> is mapped, the permissions are set if required. The atomic field added
> in the mm_context helps keep track of the different event opened and
> de-activate the permissions when all are unmapped.
> We also need to update the permissions in the context switch code so
> that tasks keep the right permissions.
>
> Signed-off-by: Raphael Gault <raphael.gault@arm.com>
Hi Raphael,
One trivial comment inline.
Thanks,
Jonathan
> ---
> arch/arm64/include/asm/mmu.h | 6 ++++
> arch/arm64/include/asm/mmu_context.h | 2 ++
> arch/arm64/include/asm/perf_event.h | 14 ++++++++
> arch/arm64/kernel/perf_event.c | 1 +
> drivers/perf/arm_pmu.c | 54 ++++++++++++++++++++++++++++
> 5 files changed, 77 insertions(+)
>
> diff --git a/arch/arm64/include/asm/mmu.h b/arch/arm64/include/asm/mmu.h
> index fd6161336653..88ed4466bd06 100644
> --- a/arch/arm64/include/asm/mmu.h
> +++ b/arch/arm64/include/asm/mmu.h
> @@ -18,6 +18,12 @@
>
> typedef struct {
> atomic64_t id;
> +
> + /*
> + * non-zero if userspace have access to hardware
> + * counters directly.
> + */
> + atomic_t pmu_direct_access;
> void *vdso;
> unsigned long flags;
> } mm_context_t;
> diff --git a/arch/arm64/include/asm/mmu_context.h b/arch/arm64/include/asm/mmu_context.h
> index 7ed0adb187a8..6e66ff940494 100644
> --- a/arch/arm64/include/asm/mmu_context.h
> +++ b/arch/arm64/include/asm/mmu_context.h
> @@ -21,6 +21,7 @@
> #include <asm-generic/mm_hooks.h>
> #include <asm/cputype.h>
> #include <asm/pgtable.h>
> +#include <asm/perf_event.h>
> #include <asm/sysreg.h>
> #include <asm/tlbflush.h>
>
> @@ -224,6 +225,7 @@ static inline void __switch_mm(struct mm_struct *next)
> }
>
> check_and_switch_context(next, cpu);
> + perf_switch_user_access(next);
> }
>
> static inline void
> diff --git a/arch/arm64/include/asm/perf_event.h b/arch/arm64/include/asm/perf_event.h
> index 2bdbc79bbd01..ba58fa726631 100644
> --- a/arch/arm64/include/asm/perf_event.h
> +++ b/arch/arm64/include/asm/perf_event.h
> @@ -8,6 +8,7 @@
>
> #include <asm/stack_pointer.h>
> #include <asm/ptrace.h>
> +#include <linux/mm_types.h>
>
> #define ARMV8_PMU_MAX_COUNTERS 32
> #define ARMV8_PMU_COUNTER_MASK (ARMV8_PMU_MAX_COUNTERS - 1)
> @@ -223,4 +224,17 @@ extern unsigned long perf_misc_flags(struct pt_regs *regs);
> (regs)->pstate = PSR_MODE_EL1h; \
> }
>
> +static inline void perf_switch_user_access(struct mm_struct *mm)
> +{
> + if (!IS_ENABLED(CONFIG_PERF_EVENTS))
> + return;
> +
> + if (atomic_read(&mm->context.pmu_direct_access)) {
> + write_sysreg(ARMV8_PMU_USERENR_ER|ARMV8_PMU_USERENR_CR,
> + pmuserenr_el0);
> + } else {
> + write_sysreg(0, pmuserenr_el0);
> + }
> +}
> +
> #endif
> diff --git a/arch/arm64/kernel/perf_event.c b/arch/arm64/kernel/perf_event.c
> index de9b001e8b7c..7de56f22d038 100644
> --- a/arch/arm64/kernel/perf_event.c
> +++ b/arch/arm64/kernel/perf_event.c
> @@ -1285,6 +1285,7 @@ void arch_perf_update_userpage(struct perf_event *event,
> */
> freq = arch_timer_get_rate();
> userpg->cap_user_time = 1;
> + userpg->cap_user_rdpmc = !!(event->hw.flags & ARMPMU_EL0_RD_CNTR);
>
> clocks_calc_mult_shift(&userpg->time_mult, &shift, freq,
> NSEC_PER_SEC, 0);
> diff --git a/drivers/perf/arm_pmu.c b/drivers/perf/arm_pmu.c
> index 2d06b8095a19..d0d3e523a4c4 100644
> --- a/drivers/perf/arm_pmu.c
> +++ b/drivers/perf/arm_pmu.c
> @@ -25,6 +25,7 @@
> #include <linux/irqdesc.h>
>
> #include <asm/irq_regs.h>
> +#include <asm/mmu_context.h>
>
> static DEFINE_PER_CPU(struct arm_pmu *, cpu_armpmu);
> static DEFINE_PER_CPU(int, cpu_irq);
> @@ -778,6 +779,57 @@ static void cpu_pmu_destroy(struct arm_pmu *cpu_pmu)
> &cpu_pmu->node);
> }
>
> +static void refresh_pmuserenr(void *mm)
> +{
> + perf_switch_user_access(mm);
> +}
> +
> +static int check_homogeneous_cap(struct perf_event *event, struct mm_struct *mm)
> +{
> + pr_info("checking HAS_HOMOGENEOUS_PMU");
Can we drop this spam from the good path. Makes a bit of a mess of my
terminal when running the test ;)
> + if (!cpus_have_cap(ARM64_HAS_HOMOGENEOUS_PMU)) {
> + pr_info("Disable direct access (!HAS_HOMOGENEOUS_PMU)");
> + atomic_set(&mm->context.pmu_direct_access, 0);
> + on_each_cpu(refresh_pmuserenr, mm, 1);
> + event->hw.flags &= ~ARMPMU_EL0_RD_CNTR;
> + return 0;
> + }
> +
> + return 1;
> +}
> +
> +static void armpmu_event_mapped(struct perf_event *event, struct mm_struct *mm)
> +{
> + if (!(event->hw.flags & ARMPMU_EL0_RD_CNTR))
> + return;
> +
> + /*
> + * This function relies on not being called concurrently in two
> + * tasks in the same mm. Otherwise one task could observe
> + * pmu_direct_access > 1 and return all the way back to
> + * userspace with user access disabled while another task is still
> + * doing on_each_cpu_mask() to enable user access.
> + *
> + * For now, this can't happen because all callers hold mmap_sem
> + * for write. If this changes, we'll need a different solution.
> + */
> + lockdep_assert_held_write(&mm->mmap_sem);
> +
> + if (check_homogeneous_cap(event, mm) &&
> + atomic_inc_return(&mm->context.pmu_direct_access) == 1)
> + on_each_cpu(refresh_pmuserenr, mm, 1);
> +}
> +
> +static void armpmu_event_unmapped(struct perf_event *event, struct mm_struct *mm)
> +{
> + if (!(event->hw.flags & ARMPMU_EL0_RD_CNTR))
> + return;
> +
> + if (check_homogeneous_cap(event, mm) &&
> + atomic_dec_and_test(&mm->context.pmu_direct_access))
> + on_each_cpu_mask(mm_cpumask(mm), refresh_pmuserenr, NULL, 1);
> +}
> +
> static struct arm_pmu *__armpmu_alloc(gfp_t flags)
> {
> struct arm_pmu *pmu;
> @@ -799,6 +851,8 @@ static struct arm_pmu *__armpmu_alloc(gfp_t flags)
> .pmu_enable = armpmu_enable,
> .pmu_disable = armpmu_disable,
> .event_init = armpmu_event_init,
> + .event_mapped = armpmu_event_mapped,
> + .event_unmapped = armpmu_event_unmapped,
> .add = armpmu_add,
> .del = armpmu_del,
> .start = armpmu_start,
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v9 3/3] arm64: Relax Documentation/arm64/tagged-pointers.rst
From: Dave Martin @ 2019-08-22 16:37 UTC (permalink / raw)
To: Catalin Marinas
Cc: linux-arch, linux-doc, Szabolcs Nagy, Andrey Konovalov,
Kevin Brodsky, Will Deacon, linux-mm, Andrew Morton,
Vincenzo Frascino, Will Deacon, Dave Hansen, linux-arm-kernel
In-Reply-To: <20190822155531.GB55798@arrakis.emea.arm.com>
On Thu, Aug 22, 2019 at 04:55:32PM +0100, Catalin Marinas wrote:
> On Wed, Aug 21, 2019 at 07:46:51PM +0100, Dave P Martin wrote:
> > On Wed, Aug 21, 2019 at 06:33:53PM +0100, Will Deacon wrote:
> > > On Wed, Aug 21, 2019 at 05:47:30PM +0100, Catalin Marinas wrote:
> > > > @@ -59,6 +63,11 @@ be preserved.
> > > > The architecture prevents the use of a tagged PC, so the upper byte will
> > > > be set to a sign-extension of bit 55 on exception return.
> > > >
> > > > +This behaviour is maintained when the AArch64 Tagged Address ABI is
> > > > +enabled. In addition, with the exceptions above, the kernel will
> > > > +preserve any non-zero tags passed by the user via syscalls and stored in
> > > > +kernel data structures (e.g. ``set_robust_list()``, ``sigaltstack()``).
> >
> > sigaltstack() is interesting, since we don't support tagged stacks.
>
> We should support tagged SP with the new ABI as they'll be required for
> MTE. sigaltstack() and clone() are the two syscalls that come to mind
> here.
>
> > Do we keep the ss_sp tag in the kernel, but squash it when delivering
> > a signal to the alternate stack?
>
> We don't seem to be doing any untagging, so we just just use whatever
> the caller asked for. We may need a small test to confirm.
If we want to support tagged SP, then I guess we shouldn't be squashing
the tag anywhere. A test for that would be sensible to have.
> That said, on_sig_stack() probably needs some untagging as it does user
> pointer arithmetics with potentially different tags.
Good point.
> > > Hmm. I can see the need to provide this guarantee for things like
> > > set_robust_list(), but the problem is that the statement above is too broad
> > > and isn't strictly true: for example, mmap() doesn't propagate the tag of
> > > its address parameter into the VMA.
> > >
> > > So I think we need to nail this down a bit more, but I'm having a really
> > > hard time coming up with some wording :(
> >
> > Time for some creative vagueness?
> >
> > We can write a statement of our overall intent, along with examples of
> > a few cases where the tag should and should not be expected to emerge
> > intact.
> >
> > There is no foolproof rule, unless we can rewrite history...
>
> I would expect the norm to be the preservation of tags with a few
> exceptions. The only ones I think where we won't preserve the tags are
> mmap, mremap, brk (apart from the signal stuff already mentioned in the
> current tagged-pointers.rst doc).
>
> So I can remove this paragraph altogether and add a note in part 3 of
> the tagged-address-abi.rst document that mmap/mremap/brk do not preserve
> the tag information.
Deleting text is always a good idea ;)
There are other cases like (non-)propagation of the tag to si_addr
when a fault is reported via a signal, but I think we already have
appropriate wording to cover that.
Cheers
---Dave
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v2 16/20] ARM: mmp: add SMP support
From: Florian Fainelli @ 2019-08-22 16:36 UTC (permalink / raw)
To: Lubomir Rintel, Olof Johansson
Cc: Mark Rutland, devicetree, linux-kernel, Jason Cooper,
Stephen Boyd, Marc Zyngier, Michael Turquette, Russell King,
Kishon Vijay Abraham I, Rob Herring, Thomas Gleixner, linux-clk,
linux-arm-kernel
In-Reply-To: <20190822092643.593488-17-lkundrak@v3.sk>
On 8/22/19 2:26 AM, Lubomir Rintel wrote:
> Used to bring up the second core on MMP3.
>
> Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
>
> ---
> Changes since v1:
> - Wrap SW_BRANCH_VIRT_ADDR with __pa_symbol()
>
> arch/arm/mach-mmp/Makefile | 3 +++
> arch/arm/mach-mmp/platsmp.c | 33 +++++++++++++++++++++++++++++++++
> 2 files changed, 36 insertions(+)
> create mode 100644 arch/arm/mach-mmp/platsmp.c
>
> diff --git a/arch/arm/mach-mmp/Makefile b/arch/arm/mach-mmp/Makefile
> index 322c1c97dc900..7b3a7f979eece 100644
> --- a/arch/arm/mach-mmp/Makefile
> +++ b/arch/arm/mach-mmp/Makefile
> @@ -22,6 +22,9 @@ ifeq ($(CONFIG_PM),y)
> obj-$(CONFIG_CPU_PXA910) += pm-pxa910.o
> obj-$(CONFIG_CPU_MMP2) += pm-mmp2.o
> endif
> +ifeq ($(CONFIG_SMP),y)
> +obj-$(CONFIG_MACH_MMP3_DT) += platsmp.o
> +endif
>
> # board support
> obj-$(CONFIG_MACH_ASPENITE) += aspenite.o
> diff --git a/arch/arm/mach-mmp/platsmp.c b/arch/arm/mach-mmp/platsmp.c
> new file mode 100644
> index 0000000000000..98d5ef23623cb
> --- /dev/null
> +++ b/arch/arm/mach-mmp/platsmp.c
> @@ -0,0 +1,33 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2019 Lubomir Rintel <lkundrak@v3.sk>
> + */
> +#include <linux/io.h>
> +#include <asm/smp_scu.h>
> +#include <asm/smp.h>
> +#include "addr-map.h"
> +
> +#define SW_BRANCH_VIRT_ADDR CIU_REG(0x24)
> +
> +static int mmp3_boot_secondary(unsigned int cpu, struct task_struct *idle)
> +{
> + /*
> + * Apparently, the boot ROM on the second core spins on this
> + * register becoming non-zero and then jumps to the address written
> + * there. No IPIs involved.
> + */
> + __raw_writel(virt_to_phys(secondary_startup),
> + __pa_symbol(SW_BRANCH_VIRT_ADDR));
That looks wrong, the __pa_symbol() is applicable to secondary_startup,
while SW_BRANCH_VIRT_ADDR does not need that.
> + return 0;
> +}
> +
> +static void mmp3_smp_prepare_cpus(unsigned int max_cpus)
> +{
> + scu_enable(SCU_VIRT_BASE);
> +}
> +
> +static const struct smp_operations mmp3_smp_ops __initconst = {
> + .smp_prepare_cpus = mmp3_smp_prepare_cpus,
> + .smp_boot_secondary = mmp3_boot_secondary,
> +};
> +CPU_METHOD_OF_DECLARE(mmp3_smp, "marvell,mmp3-smp", &mmp3_smp_ops);
>
--
Florian
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v2 05/12] irqchip/gic: Prepare for more than 16 PPIs
From: Marc Zyngier @ 2019-08-22 16:32 UTC (permalink / raw)
To: Julien, Thomas Gleixner, Jason Cooper, Rob Herring
Cc: Lokesh Vutla, John Garry, linux-kernel, Shameerali Kolothum Thodi,
linux-arm-kernel
In-Reply-To: <1b2675f6-f839-80f8-b7d8-a7d402085745@gmail.com>
Hi Julien,
On 22/08/2019 17:11, Julien wrote:
> Hi Marc,
>
> On 06/08/19 11:01, Marc Zyngier wrote:
>> GICv3.1 allows up to 80 PPIs (16 legaci PPIs and 64 Extended PPIs),
>> meaning we can't just leave the old 16 hardcoded everywhere.
>>
>> We also need to add the infrastructure to discover the number of PPIs
>> on a per redistributor basis, although we still pretend there is only
>> 16 of them for now.
>>
>> No functional change.
>>
>> Signed-off-by: Marc Zyngier <maz@kernel.org>
>> ---
>> drivers/irqchip/irq-gic-common.c | 19 ++++++++++++-------
>> drivers/irqchip/irq-gic-common.h | 2 +-
>> drivers/irqchip/irq-gic-v3.c | 22 +++++++++++++++-------
>> drivers/irqchip/irq-gic.c | 2 +-
>> drivers/irqchip/irq-hip04.c | 2 +-
>> 5 files changed, 30 insertions(+), 17 deletions(-)
>>
>> diff --git a/drivers/irqchip/irq-gic-common.c b/drivers/irqchip/irq-gic-common.c
>> index 6900b6f0921c..14110db01c05 100644
>> --- a/drivers/irqchip/irq-gic-common.c
>> +++ b/drivers/irqchip/irq-gic-common.c
>> @@ -128,26 +128,31 @@ void gic_dist_config(void __iomem *base, int gic_irqs,
>> sync_access();
>> }
>>
>> -void gic_cpu_config(void __iomem *base, void (*sync_access)(void))
>> +void gic_cpu_config(void __iomem *base, int nr, void (*sync_access)(void))
>> {
>> int i;
>>
>> /*
>> * Deal with the banked PPI and SGI interrupts - disable all
>> - * PPI interrupts, ensure all SGI interrupts are enabled.
>> - * Make sure everything is deactivated.
>> + * private interrupts. Make sure everything is deactivated.
>> */
>> - writel_relaxed(GICD_INT_EN_CLR_X32, base + GIC_DIST_ACTIVE_CLEAR);
>> - writel_relaxed(GICD_INT_EN_CLR_PPI, base + GIC_DIST_ENABLE_CLEAR);
>> - writel_relaxed(GICD_INT_EN_SET_SGI, base + GIC_DIST_ENABLE_SET);
>> + for (i = 0; i < nr; i += 32) {
>
> You added "nr" as argument but if "nr" isn't a multiple of 32 weird
> things might happen, no?
>
> It would be worth specifying that somewhere, and checking it with a WARN().
TBH, I'm unsure whether that's worth it. The architecture is completely
built around having the private interrupts in blocks of 32, and you can
only get something wrong if you misdecode the number of interrupts from
the registers.
> Maybe it might be worth reducing the granularity to manipulating 16 irqs
> since there are 16 SGI + 16 PPI + 64 EPPI, but that might not be very
> useful right now.
I don't see what this brings us at this point. The architecture doesn't
seem to go in the direction of adding more SGIs, so we're pretty safe on
that front...
Thanks,
M.
--
Jazz is not dead, it just smells funny...
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [RFC v4 01/18] objtool: Add abstraction for computation of symbols offsets
From: Julien @ 2019-08-22 16:30 UTC (permalink / raw)
To: Raphael Gault, linux-arm-kernel, linux-kernel, jpoimboe
Cc: peterz, catalin.marinas, will.deacon, raph.gault+kdev
In-Reply-To: <20190816122403.14994-2-raphael.gault@arm.com>
Hi Raphaël,
On 16/08/19 13:23, Raphael Gault wrote:
> The jump destination and relocation offset used previously are only
> reliable on x86_64 architecture. We abstract these computations by calling
> arch-dependent implementations.
>
> Signed-off-by: Raphael Gault <raphael.gault@arm.com>
> ---
> tools/objtool/arch.h | 6 ++++++
> tools/objtool/arch/x86/decode.c | 11 +++++++++++
> tools/objtool/check.c | 15 ++++++++++-----
> 3 files changed, 27 insertions(+), 5 deletions(-)
>
> diff --git a/tools/objtool/arch.h b/tools/objtool/arch.h
> index ced3765c4f44..a9a50a25ca66 100644
> --- a/tools/objtool/arch.h
> +++ b/tools/objtool/arch.h
> @@ -66,6 +66,8 @@ struct stack_op {
> struct op_src src;
> };
>
> +struct instruction;
> +
> void arch_initial_func_cfi_state(struct cfi_state *state);
>
> int arch_decode_instruction(struct elf *elf, struct section *sec,
> @@ -75,4 +77,8 @@ int arch_decode_instruction(struct elf *elf, struct section *sec,
>
> bool arch_callee_saved_reg(unsigned char reg);
>
> +unsigned long arch_jump_destination(struct instruction *insn);
> +
> +unsigned long arch_dest_rela_offset(int addend);
> +
> #endif /* _ARCH_H */
> diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c
> index 0567c47a91b1..fa33b3465722 100644
> --- a/tools/objtool/arch/x86/decode.c
> +++ b/tools/objtool/arch/x86/decode.c
> @@ -11,6 +11,7 @@
> #include "lib/inat.c"
> #include "lib/insn.c"
>
> +#include "../../check.h"
> #include "../../elf.h"
> #include "../../arch.h"
> #include "../../warn.h"
> @@ -66,6 +67,11 @@ bool arch_callee_saved_reg(unsigned char reg)
> }
> }
>
> +unsigned long arch_dest_rela_offset(int addend)
> +{
> + return addend + 4;
> +}
> +
> int arch_decode_instruction(struct elf *elf, struct section *sec,
> unsigned long offset, unsigned int maxlen,
> unsigned int *len, enum insn_type *type,
> @@ -497,3 +503,8 @@ void arch_initial_func_cfi_state(struct cfi_state *state)
> state->regs[16].base = CFI_CFA;
> state->regs[16].offset = -8;
> }
> +
> +unsigned long arch_jump_destination(struct instruction *insn)
> +{
> + return insn->offset + insn->len + insn->immediate;
> +}
> diff --git a/tools/objtool/check.c b/tools/objtool/check.c
> index 176f2f084060..479fab46b656 100644
> --- a/tools/objtool/check.c
> +++ b/tools/objtool/check.c
> @@ -563,7 +563,7 @@ static int add_jump_destinations(struct objtool_file *file)
> insn->len);
> if (!rela) {
> dest_sec = insn->sec;
> - dest_off = insn->offset + insn->len + insn->immediate;
> + dest_off = arch_jump_destination(insn);
> } else if (rela->sym->type == STT_SECTION) {
> dest_sec = rela->sym->sec;
> dest_off = rela->addend + 4;
> @@ -659,7 +659,7 @@ static int add_call_destinations(struct objtool_file *file)
> rela = find_rela_by_dest_range(insn->sec, insn->offset,
> insn->len);
> if (!rela) {
> - dest_off = insn->offset + insn->len + insn->immediate;
> + dest_off = arch_jump_destination(insn);
> insn->call_dest = find_symbol_by_offset(insn->sec,
> dest_off);
>
> @@ -672,14 +672,19 @@ static int add_call_destinations(struct objtool_file *file)
> }
>
> } else if (rela->sym->type == STT_SECTION) {
> + /*
> + * the original x86_64 code adds 4 to the rela->addend
> + * which is not needed on arm64 architecture.
> + */
I'm not sure this is worth mentioning in generic code. You might include
it in the commit message to justify the change.
> + dest_off = arch_dest_rela_offset(rela->addend);
> insn->call_dest = find_symbol_by_offset(rela->sym->sec,
> - rela->addend+4);
> + dest_off);
> if (!insn->call_dest ||
> insn->call_dest->type != STT_FUNC) {
> - WARN_FUNC("can't find call dest symbol at %s+0x%x",
> + WARN_FUNC("can't find call dest symbol at %s+0x%lx",
> insn->sec, insn->offset,
> rela->sym->sec->name,
> - rela->addend + 4);
> + dest_off);
> return -1;
> }
> } else
>
Otherwise, the change looks good to me.
Thanks,
--
Julien Thierry
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Aw: [PATCH net-next v2 0/3] net: dsa: mt7530: Convert to PHYLINK and add support for port 5
From: Frank Wunderlich @ 2019-08-22 16:26 UTC (permalink / raw)
To: Frank Wunderlich
Cc: Andrew Lunn, Florian Fainelli, netdev, Sean Wang, linux-mips,
Vivien Didelot, "René van Dorst", linux-mediatek,
John Crispin, Matthias Brugger, David S . Miller,
linux-arm-kernel
In-Reply-To: <trinity-b1f48e51-af73-466d-9ecf-d560a7d7c1ee-1566488653737@3c-app-gmx-bap07>
tested now also on bpi-r64 (mt7622) v0.1 (rtl8367 switch), without linux-next to avoid power-regulator-problems like on bpi-r2
dmesg without warnings/errors caused by this patches
link came up as desired
iperf3 looks good: 943 Mbits/sec in both directions and no other issues
so it is currently only the rx-throughput-problem on mt7623/bpi-r2
regards Frank
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v3 04/10] KVM: Implement kvm_put_guest()
From: Sean Christopherson @ 2019-08-22 16:24 UTC (permalink / raw)
To: Steven Price
Cc: Mark Rutland, Radim Krčmář, kvm, Suzuki K Pouloze,
Marc Zyngier, linux-doc, Russell King, linux-kernel, James Morse,
linux-arm-kernel, Catalin Marinas, Paolo Bonzini, Will Deacon,
kvmarm, Julien Thierry
In-Reply-To: <e2abc69b-74c2-64ef-e270-43d93513eaae@arm.com>
On Thu, Aug 22, 2019 at 04:46:10PM +0100, Steven Price wrote:
> On 22/08/2019 16:28, Sean Christopherson wrote:
> > On Wed, Aug 21, 2019 at 04:36:50PM +0100, Steven Price wrote:
> >> kvm_put_guest() is analogous to put_user() - it writes a single value to
> >> the guest physical address. The implementation is built upon put_user()
> >> and so it has the same single copy atomic properties.
> >
> > What you mean by "single copy atomic"? I.e. what guarantees does
> > put_user() provide that __copy_to_user() does not?
>
> Single-copy atomicity is defined by the Arm architecture[1] and I'm not
> going to try to go into the full details here, so this is a summary.
>
> For the sake of this feature what we care about is that the value
> written/read cannot be "torn". In other words if there is a read (in
> this case from another VCPU) that is racing with the write then the read
> will either get the old value or the new value. It cannot return a
> mixture. (This is of course assuming that the read is using a
> single-copy atomic safe method).
Thanks for the explanation. I assumed that's what you were referring to,
but wanted to double check.
> __copy_to_user() is implemented as a memcpy() and as such cannot provide
> single-copy atomicity in the general case (the buffer could easily be
> bigger than the architecture can guarantee).
>
> put_user() on the other hand is implemented (on arm64) as an explicit
> store instruction and therefore is guaranteed by the architecture to be
> single-copy atomic (i.e. another CPU cannot see a half-written value).
I don't think kvm_put_guest() belongs in generic code, at least not with
the current changelog explanation about it providing single-copy atomic
semantics. AFAICT, the single-copy thing is very much an arm64
implementation detail, e.g. the vast majority of 32-bit architectures,
including x86, do not provide any guarantees, and x86-64 generates more
or less the same code for put_user() and __copy_to_user() for 8-byte and
smaller accesses.
As an alternative to kvm_put_guest() entirely, is it an option to change
arm64's raw_copy_to_user() to redirect to __put_user() for sizes that are
constant at compile time and can be handled by __put_user()? That would
allow using kvm_write_guest() to update stolen time, albeit with
arguably an even bigger dependency on the uaccess implementation details.
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v2 00/20] Initial support for Marvell MMP3 SoC
From: Olof Johansson @ 2019-08-22 16:23 UTC (permalink / raw)
To: Marc Zyngier
Cc: Mark Rutland, DTML, Jason Cooper, Stephen Boyd,
Linux Kernel Mailing List, Michael Turquette, Russell King,
Kishon Vijay Abraham I, Lubomir Rintel, Rob Herring,
Thomas Gleixner, linux-clk, Linux ARM Mailing List
In-Reply-To: <244fdc87-0fe5-be79-d9cd-2395d0ac3f57@kernel.org>
On Thu, Aug 22, 2019 at 3:32 AM Marc Zyngier <maz@kernel.org> wrote:
>
> On 22/08/2019 10:26, Lubomir Rintel wrote:
> > Hi,
> >
> > this is a second spin of a patch set that adds support for the Marvell
> > MMP3 processor. MMP3 is used in OLPC XO-4 laptops, Panasonic Toughpad
> > FZ-A1 tablet and Dell Wyse 3020 Tx0D thin clients.
> >
> > Compared to v1, there's a handful of fixes in response to reviews. Patch
> > 02/20 is new. Details in individual patches.
> >
> > Apart from the adjustments in mach-mmp/, the patch makes necessary
> > changes to the irqchip driver and adds an USB2 PHY driver. The latter
> > has a dependency on the mach-mmp/ changes, so it can't be submitted
> > separately.
> >
> > The patch set has been tested to work on Wyse Tx0D and not ruin MMP2
> > support on XO-1.75.
>
> How do you want this series to be merged? I'm happy to take the irqchip
> related patches as well as the corresponding DT change (once reviewed)
> through my tree.
DT changes, unless there's lack of backwards compatibility, are best
merged through the platform trees. Especially for new platforms like
these where there's likely going to be nearby changes (and thus
conflicts).
I.e. driver changes I'm all for bringing through driver trees
(including binding patches), but please leave dts/dtsi changes to the
platform.
-Olof
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v2 05/12] irqchip/gic: Prepare for more than 16 PPIs
From: Julien @ 2019-08-22 16:11 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Jason Cooper, Rob Herring
Cc: Lokesh Vutla, John Garry, linux-kernel, Shameerali Kolothum Thodi,
linux-arm-kernel
In-Reply-To: <20190806100121.240767-6-maz@kernel.org>
Hi Marc,
On 06/08/19 11:01, Marc Zyngier wrote:
> GICv3.1 allows up to 80 PPIs (16 legaci PPIs and 64 Extended PPIs),
> meaning we can't just leave the old 16 hardcoded everywhere.
>
> We also need to add the infrastructure to discover the number of PPIs
> on a per redistributor basis, although we still pretend there is only
> 16 of them for now.
>
> No functional change.
>
> Signed-off-by: Marc Zyngier <maz@kernel.org>
> ---
> drivers/irqchip/irq-gic-common.c | 19 ++++++++++++-------
> drivers/irqchip/irq-gic-common.h | 2 +-
> drivers/irqchip/irq-gic-v3.c | 22 +++++++++++++++-------
> drivers/irqchip/irq-gic.c | 2 +-
> drivers/irqchip/irq-hip04.c | 2 +-
> 5 files changed, 30 insertions(+), 17 deletions(-)
>
> diff --git a/drivers/irqchip/irq-gic-common.c b/drivers/irqchip/irq-gic-common.c
> index 6900b6f0921c..14110db01c05 100644
> --- a/drivers/irqchip/irq-gic-common.c
> +++ b/drivers/irqchip/irq-gic-common.c
> @@ -128,26 +128,31 @@ void gic_dist_config(void __iomem *base, int gic_irqs,
> sync_access();
> }
>
> -void gic_cpu_config(void __iomem *base, void (*sync_access)(void))
> +void gic_cpu_config(void __iomem *base, int nr, void (*sync_access)(void))
> {
> int i;
>
> /*
> * Deal with the banked PPI and SGI interrupts - disable all
> - * PPI interrupts, ensure all SGI interrupts are enabled.
> - * Make sure everything is deactivated.
> + * private interrupts. Make sure everything is deactivated.
> */
> - writel_relaxed(GICD_INT_EN_CLR_X32, base + GIC_DIST_ACTIVE_CLEAR);
> - writel_relaxed(GICD_INT_EN_CLR_PPI, base + GIC_DIST_ENABLE_CLEAR);
> - writel_relaxed(GICD_INT_EN_SET_SGI, base + GIC_DIST_ENABLE_SET);
> + for (i = 0; i < nr; i += 32) {
You added "nr" as argument but if "nr" isn't a multiple of 32 weird
things might happen, no?
It would be worth specifying that somewhere, and checking it with a WARN().
Maybe it might be worth reducing the granularity to manipulating 16 irqs
since there are 16 SGI + 16 PPI + 64 EPPI, but that might not be very
useful right now.
Cheers,
Julien
> + writel_relaxed(GICD_INT_EN_CLR_X32,
> + base + GIC_DIST_ACTIVE_CLEAR + i / 8);
> + writel_relaxed(GICD_INT_EN_CLR_X32,
> + base + GIC_DIST_ENABLE_CLEAR + i / 8);
> + }
>
> /*
> * Set priority on PPI and SGI interrupts
> */
> - for (i = 0; i < 32; i += 4)
> + for (i = 0; i < nr; i += 4)
> writel_relaxed(GICD_INT_DEF_PRI_X4,
> base + GIC_DIST_PRI + i * 4 / 4);
>
> + /* Ensure all SGI interrupts are now enabled */
> + writel_relaxed(GICD_INT_EN_SET_SGI, base + GIC_DIST_ENABLE_SET);
> +
> if (sync_access)
> sync_access();
> }
> diff --git a/drivers/irqchip/irq-gic-common.h b/drivers/irqchip/irq-gic-common.h
> index 5a46b6b57750..ccba8b0fe0f5 100644
> --- a/drivers/irqchip/irq-gic-common.h
> +++ b/drivers/irqchip/irq-gic-common.h
> @@ -22,7 +22,7 @@ int gic_configure_irq(unsigned int irq, unsigned int type,
> void __iomem *base, void (*sync_access)(void));
> void gic_dist_config(void __iomem *base, int gic_irqs,
> void (*sync_access)(void));
> -void gic_cpu_config(void __iomem *base, void (*sync_access)(void));
> +void gic_cpu_config(void __iomem *base, int nr, void (*sync_access)(void));
> void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
> void *data);
> void gic_enable_of_quirks(const struct device_node *np,
> diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c
> index 1ca4dde32034..e03fb6d7c2ce 100644
> --- a/drivers/irqchip/irq-gic-v3.c
> +++ b/drivers/irqchip/irq-gic-v3.c
> @@ -51,6 +51,7 @@ struct gic_chip_data {
> u32 nr_redist_regions;
> u64 flags;
> bool has_rss;
> + unsigned int ppi_nr;
> struct partition_desc *ppi_descs[16];
> };
>
> @@ -812,19 +813,24 @@ static int gic_populate_rdist(void)
> return -ENODEV;
> }
>
> -static int __gic_update_vlpi_properties(struct redist_region *region,
> - void __iomem *ptr)
> +static int __gic_update_rdist_properties(struct redist_region *region,
> + void __iomem *ptr)
> {
> u64 typer = gic_read_typer(ptr + GICR_TYPER);
> gic_data.rdists.has_vlpis &= !!(typer & GICR_TYPER_VLPIS);
> gic_data.rdists.has_direct_lpi &= !!(typer & GICR_TYPER_DirectLPIS);
> + gic_data.ppi_nr = 16;
>
> return 1;
> }
>
> -static void gic_update_vlpi_properties(void)
> +static void gic_update_rdist_properties(void)
> {
> - gic_iterate_rdists(__gic_update_vlpi_properties);
> + gic_data.ppi_nr = UINT_MAX;
> + gic_iterate_rdists(__gic_update_rdist_properties);
> + if (WARN_ON(gic_data.ppi_nr == UINT_MAX))
> + gic_data.ppi_nr = 0;
> + pr_info("%d PPIs implemented\n", gic_data.ppi_nr);
> pr_info("%sVLPI support, %sdirect LPI support\n",
> !gic_data.rdists.has_vlpis ? "no " : "",
> !gic_data.rdists.has_direct_lpi ? "no " : "");
> @@ -968,6 +974,7 @@ static int gic_dist_supports_lpis(void)
> static void gic_cpu_init(void)
> {
> void __iomem *rbase;
> + int i;
>
> /* Register ourselves with the rest of the world */
> if (gic_populate_rdist())
> @@ -978,9 +985,10 @@ static void gic_cpu_init(void)
> rbase = gic_data_rdist_sgi_base();
>
> /* Configure SGIs/PPIs as non-secure Group-1 */
> - writel_relaxed(~0, rbase + GICR_IGROUPR0);
> + for (i = 0; i < gic_data.ppi_nr + 16; i += 32)
> + writel_relaxed(~0, rbase + GICR_IGROUPR0 + i / 8);
>
> - gic_cpu_config(rbase, gic_redist_wait_for_rwp);
> + gic_cpu_config(rbase, gic_data.ppi_nr + 16, gic_redist_wait_for_rwp);
>
> /* initialise system registers */
> gic_cpu_sys_reg_init();
> @@ -1449,7 +1457,7 @@ static int __init gic_init_bases(void __iomem *dist_base,
>
> set_handle_irq(gic_handle_irq);
>
> - gic_update_vlpi_properties();
> + gic_update_rdist_properties();
>
> gic_smp_init();
> gic_dist_init();
> diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c
> index ab48760acabb..25c1ae69db30 100644
> --- a/drivers/irqchip/irq-gic.c
> +++ b/drivers/irqchip/irq-gic.c
> @@ -543,7 +543,7 @@ static int gic_cpu_init(struct gic_chip_data *gic)
> gic_cpu_map[i] &= ~cpu_mask;
> }
>
> - gic_cpu_config(dist_base, NULL);
> + gic_cpu_config(dist_base, 32, NULL);
>
> writel_relaxed(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK);
> gic_cpu_if_up(gic);
> diff --git a/drivers/irqchip/irq-hip04.c b/drivers/irqchip/irq-hip04.c
> index 1626131834a6..130caa1c9d93 100644
> --- a/drivers/irqchip/irq-hip04.c
> +++ b/drivers/irqchip/irq-hip04.c
> @@ -273,7 +273,7 @@ static void hip04_irq_cpu_init(struct hip04_irq_data *intc)
> if (i != cpu)
> hip04_cpu_map[i] &= ~cpu_mask;
>
> - gic_cpu_config(dist_base, NULL);
> + gic_cpu_config(dist_base, 32, NULL);
>
> writel_relaxed(0xf0, base + GIC_CPU_PRIMASK);
> writel_relaxed(1, base + GIC_CPU_CTRL);
>
--
Julien Thierry
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v9 2/3] fdt: add support for rng-seed
From: Theodore Y. Ts'o @ 2019-08-22 16:03 UTC (permalink / raw)
To: Hsin-Yi Wang
Cc: Kate Stewart, Peter Zijlstra, Catalin Marinas, Mukesh Ojha,
Grzegorz Halat, H . Peter Anvin, Guenter Roeck, Will Deacon,
Marek Szyprowski, Rob Herring, Daniel Thompson, Anders Roxell,
Yury Norov, Marc Zyngier, Russell King, Aaro Koskinen,
Ingo Molnar, Viresh Kumar, Waiman Long, Paul E . McKenney, Wei Li,
Alexey Dobriyan, Julien Thierry, Len Brown, Kees Cook,
Arnd Bergmann, Rik van Riel, Stephen Boyd, Shaokun Zhang,
Mike Rapoport, Borislav Petkov, Josh Poimboeuf, Thomas Gleixner,
linux-arm-kernel, Greg Kroah-Hartman, Marcelo Tosatti,
linux-kernel, Armijn Hemel, Jiri Kosina, Mathieu Desnoyers,
Andrew Morton, Tim Chen, David S . Miller
In-Reply-To: <20190822071522.143986-3-hsinyi@chromium.org>
On Thu, Aug 22, 2019 at 03:15:22PM +0800, Hsin-Yi Wang wrote:
> Introducing a chosen node, rng-seed, which is an entropy that can be
> passed to kernel called very early to increase initial device
> randomness. Bootloader should provide this entropy and the value is
> read from /chosen/rng-seed in DT.
>
> Obtain of_fdt_crc32 for CRC check after early_init_dt_scan_nodes(),
> since early_init_dt_scan_chosen() would modify fdt to erase rng-seed.
>
> Add a new interface add_bootloader_randomness() for rng-seed use case.
> Depends on whether the seed is trustworthy, rng seed would be passed to
> add_hwgenerator_randomness(). Otherwise it would be passed to
> add_device_randomness(). Decision is controlled by kernel config
> RANDOM_TRUST_BOOTLOADER.
>
> Signed-off-by: Hsin-Yi Wang <hsinyi@chromium.org>
> Reviewed-by: Stephen Boyd <swboyd@chromium.org>
> Reviewed-by: Rob Herring <robh@kernel.org>
For the changes to drivers/char/random.c:
Reviewed-by: Theodore Ts'o <tytso@mit.edu>
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox