summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/ata/ahci-pci.c1
-rw-r--r--drivers/ata/ahci.c11
-rw-r--r--drivers/clk/Kconfig16
-rw-r--r--drivers/clk/Makefile2
-rw-r--r--drivers/clk/clk-cdce9xx.c254
-rw-r--r--drivers/clk/clk_versal.c746
-rw-r--r--drivers/core/device.c11
-rw-r--r--drivers/firmware/Kconfig10
-rw-r--r--drivers/firmware/Makefile1
-rw-r--r--drivers/firmware/firmware-zynqmp.c161
-rw-r--r--drivers/fpga/Kconfig9
-rw-r--r--drivers/fpga/Makefile1
-rw-r--r--drivers/fpga/versalpl.c51
-rw-r--r--drivers/fpga/xilinx.c8
-rw-r--r--drivers/fpga/zynqmppl.c7
-rw-r--r--drivers/gpio/zynq_gpio.c2
-rw-r--r--drivers/i2c/imx_lpi2c.c11
-rw-r--r--drivers/mailbox/Kconfig6
-rw-r--r--drivers/mailbox/Makefile1
-rw-r--r--drivers/mailbox/mailbox-uclass.c19
-rw-r--r--drivers/mailbox/zynqmp-ipi.c134
-rw-r--r--drivers/mmc/Kconfig17
-rw-r--r--drivers/mmc/Makefile1
-rw-r--r--drivers/mmc/am654_sdhci.c13
-rw-r--r--drivers/mmc/iproc_sdhci.c247
-rw-r--r--drivers/mmc/mmc-uclass.c14
-rw-r--r--drivers/mmc/mmc.c39
-rw-r--r--drivers/mmc/stm32_sdmmc2.c13
-rw-r--r--drivers/mmc/zynq_sdhci.c4
-rw-r--r--drivers/net/phy/Kconfig7
-rw-r--r--drivers/net/phy/Makefile1
-rw-r--r--drivers/net/phy/phy.c49
-rw-r--r--drivers/net/phy/xilinx_gmii2rgmii.c144
-rw-r--r--drivers/net/xilinx_axi_emac.c7
-rw-r--r--drivers/net/zynq_gem.c7
-rw-r--r--drivers/pci/Kconfig8
-rw-r--r--drivers/pci/Makefile1
-rw-r--r--drivers/pci/pci-emul-uclass.c30
-rw-r--r--drivers/pci/pcie_mediatek.c279
-rw-r--r--drivers/phy/Kconfig11
-rw-r--r--drivers/phy/Makefile1
-rw-r--r--drivers/phy/phy-mtk-tphy.c362
-rw-r--r--drivers/power/regulator/regulator_common.c7
-rw-r--r--drivers/remoteproc/Kconfig20
-rw-r--r--drivers/remoteproc/Makefile2
-rw-r--r--drivers/remoteproc/rproc-elf-loader.c179
-rw-r--r--drivers/remoteproc/sandbox_testproc.c4
-rw-r--r--drivers/remoteproc/stm32_copro.c21
-rw-r--r--drivers/remoteproc/ti_k3_dsp_rproc.c354
-rw-r--r--drivers/remoteproc/ti_k3_r5f_rproc.c816
-rw-r--r--drivers/remoteproc/ti_sci_proc.h27
-rw-r--r--drivers/spi/Kconfig2
-rw-r--r--drivers/spi/bcm63xx_hsspi.c47
-rw-r--r--drivers/usb/dwc3/core.c4
-rw-r--r--drivers/video/Kconfig47
-rw-r--r--drivers/video/Makefile6
-rw-r--r--drivers/video/dsi-host-uclass.c39
-rw-r--r--drivers/video/dw_mipi_dsi.c838
-rw-r--r--drivers/video/mipi_dsi.c828
-rw-r--r--drivers/video/orisetech_otm8009a.c379
-rw-r--r--drivers/video/raydium-rm68200.c351
-rw-r--r--drivers/video/sandbox_dsi_host.c90
-rw-r--r--drivers/video/stm32/Kconfig9
-rw-r--r--drivers/video/stm32/Makefile1
-rw-r--r--drivers/video/stm32/stm32_dsi.c490
-rw-r--r--drivers/video/stm32/stm32_ltdc.c143
66 files changed, 7276 insertions, 145 deletions
diff --git a/drivers/ata/ahci-pci.c b/drivers/ata/ahci-pci.c
index 1ca439d3fa..11ec98b56f 100644
--- a/drivers/ata/ahci-pci.c
+++ b/drivers/ata/ahci-pci.c
@@ -35,6 +35,7 @@ U_BOOT_DRIVER(ahci_pci) = {
static struct pci_device_id ahci_pci_supported[] = {
{ PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_SATA_AHCI, ~0) },
+ { PCI_DEVICE(0x1b21, 0x0611) },
{},
};
diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
index 9a08575053..21a89eba5a 100644
--- a/drivers/ata/ahci.c
+++ b/drivers/ata/ahci.c
@@ -548,6 +548,7 @@ static int ahci_port_start(struct ahci_uc_priv *uc_priv, u8 port)
{
struct ahci_ioports *pp = &(uc_priv->port[port]);
void __iomem *port_mmio = pp->port_mmio;
+ u64 dma_addr;
u32 port_status;
void __iomem *mem;
@@ -593,10 +594,12 @@ static int ahci_port_start(struct ahci_uc_priv *uc_priv, u8 port)
pp->cmd_tbl_sg =
(struct ahci_sg *)(uintptr_t)virt_to_phys((void *)mem);
- writel_with_flush((unsigned long)pp->cmd_slot,
- port_mmio + PORT_LST_ADDR);
-
- writel_with_flush(pp->rx_fis, port_mmio + PORT_FIS_ADDR);
+ dma_addr = (ulong)pp->cmd_slot;
+ writel_with_flush(dma_addr, port_mmio + PORT_LST_ADDR);
+ writel_with_flush(dma_addr >> 32, port_mmio + PORT_LST_ADDR_HI);
+ dma_addr = (ulong)pp->rx_fis;
+ writel_with_flush(dma_addr, port_mmio + PORT_FIS_ADDR);
+ writel_with_flush(dma_addr >> 32, port_mmio + PORT_FIS_ADDR_HI);
#ifdef CONFIG_SUNXI_AHCI
sunxi_dma_init(port_mmio);
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 95fe0aea2c..16d4237f89 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -95,6 +95,14 @@ config CLK_HSDK
help
Enable this to support the cgu clocks on Synopsys ARC HSDK
+config CLK_VERSAL
+ bool "Enable clock driver support for Versal"
+ depends on ARCH_VERSAL
+ select ZYNQMP_FIRMWARE
+ help
+ This clock driver adds support for clock realted settings for
+ Versal platform.
+
config CLK_VEXPRESS_OSC
bool "Enable driver for Arm Versatile Express OSC clock generators"
depends on CLK && VEXPRESS_CONFIG
@@ -113,6 +121,7 @@ config CLK_ZYNQ
config CLK_ZYNQMP
bool "Enable clock driver support for ZynqMP"
depends on ARCH_ZYNQMP
+ select ZYNQMP_FIRMWARE
help
This clock driver adds support for clock realted settings for
ZynqMP platform.
@@ -125,6 +134,13 @@ config CLK_STM32MP1
Enable the STM32 clock (RCC) driver. Enable support for
manipulating STM32MP1's on-SoC clocks.
+config CLK_CDCE9XX
+ bool "Enable CDCD9XX clock driver"
+ depends on CLK
+ help
+ Enable the clock synthesizer driver for CDCE913/925/937/949
+ series of chips.
+
source "drivers/clk/analogbits/Kconfig"
source "drivers/clk/at91/Kconfig"
source "drivers/clk/exynos/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 68aabe1ca9..8de6777468 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -43,3 +43,5 @@ obj-$(CONFIG_SANDBOX) += clk_sandbox_test.o
obj-$(CONFIG_SANDBOX_CLK_CCF) += clk_sandbox_ccf.o
obj-$(CONFIG_STM32H7) += clk_stm32h7.o
obj-$(CONFIG_CLK_TI_SCI) += clk-ti-sci.o
+obj-$(CONFIG_CLK_VERSAL) += clk_versal.o
+obj-$(CONFIG_CLK_CDCE9XX) += clk-cdce9xx.o
diff --git a/drivers/clk/clk-cdce9xx.c b/drivers/clk/clk-cdce9xx.c
new file mode 100644
index 0000000000..5d1489ab0e
--- /dev/null
+++ b/drivers/clk/clk-cdce9xx.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Texas Instruments CDCE913/925/937/949 clock synthesizer driver
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+ * Tero Kristo <t-kristo@ti.com>
+ *
+ * Based on Linux kernel clk-cdce925.c.
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <clk-uclass.h>
+#include <i2c.h>
+
+#define MAX_NUMBER_OF_PLLS 4
+#define MAX_NUMER_OF_OUTPUTS 9
+
+#define CDCE9XX_REG_GLOBAL1 0x01
+#define CDCE9XX_REG_Y1SPIPDIVH 0x02
+#define CDCE9XX_REG_PDIV1L 0x03
+#define CDCE9XX_REG_XCSEL 0x05
+
+#define CDCE9XX_PDIV1_H_MASK 0x3
+
+#define CDCE9XX_REG_PDIV(clk) (0x16 + (((clk) - 1) & 1) + \
+ ((clk) - 1) / 2 * 0x10)
+
+#define CDCE9XX_PDIV_MASK 0x7f
+
+#define CDCE9XX_BYTE_TRANSFER BIT(7)
+
+struct cdce9xx_chip_info {
+ int num_plls;
+ int num_outputs;
+};
+
+struct cdce9xx_clk_data {
+ struct udevice *i2c;
+ struct cdce9xx_chip_info *chip;
+ u32 xtal_rate;
+};
+
+static const struct cdce9xx_chip_info cdce913_chip_info = {
+ .num_plls = 1, .num_outputs = 3,
+};
+
+static const struct cdce9xx_chip_info cdce925_chip_info = {
+ .num_plls = 2, .num_outputs = 5,
+};
+
+static const struct cdce9xx_chip_info cdce937_chip_info = {
+ .num_plls = 3, .num_outputs = 7,
+};
+
+static const struct cdce9xx_chip_info cdce949_chip_info = {
+ .num_plls = 4, .num_outputs = 9,
+};
+
+static int cdce9xx_reg_read(struct udevice *dev, u8 addr, u8 *buf)
+{
+ struct cdce9xx_clk_data *data = dev_get_priv(dev);
+ int ret;
+
+ ret = dm_i2c_read(data->i2c, addr | CDCE9XX_BYTE_TRANSFER, buf, 1);
+ if (ret)
+ dev_err(dev, "%s: failed for addr:%x, ret:%d\n", __func__,
+ addr, ret);
+
+ return ret;
+}
+
+static int cdce9xx_reg_write(struct udevice *dev, u8 addr, u8 val)
+{
+ struct cdce9xx_clk_data *data = dev_get_priv(dev);
+ int ret;
+
+ ret = dm_i2c_write(data->i2c, addr | CDCE9XX_BYTE_TRANSFER, &val, 1);
+ if (ret)
+ dev_err(dev, "%s: failed for addr:%x, ret:%d\n", __func__,
+ addr, ret);
+
+ return ret;
+}
+
+static int cdce9xx_clk_of_xlate(struct clk *clk,
+ struct ofnode_phandle_args *args)
+{
+ struct cdce9xx_clk_data *data = dev_get_priv(clk->dev);
+
+ if (args->args_count != 1)
+ return -EINVAL;
+
+ if (args->args[0] > data->chip->num_outputs)
+ return -EINVAL;
+
+ clk->id = args->args[0];
+
+ return 0;
+}
+
+static int cdce9xx_clk_probe(struct udevice *dev)
+{
+ struct cdce9xx_clk_data *data = dev_get_priv(dev);
+ struct cdce9xx_chip_info *chip = (void *)dev_get_driver_data(dev);
+ int ret;
+ u32 val;
+ struct clk clk;
+
+ val = (u32)dev_read_addr_ptr(dev);
+
+ ret = i2c_get_chip(dev->parent, val, 1, &data->i2c);
+ if (ret) {
+ dev_err(dev, "I2C probe failed.\n");
+ return ret;
+ }
+
+ data->chip = chip;
+
+ ret = clk_get_by_index(dev, 0, &clk);
+ data->xtal_rate = clk_get_rate(&clk);
+
+ val = dev_read_u32_default(dev, "xtal-load-pf", -1);
+ if (val >= 0)
+ cdce9xx_reg_write(dev, CDCE9XX_REG_XCSEL, val << 3);
+
+ return 0;
+}
+
+static u16 cdce9xx_clk_get_pdiv(struct clk *clk)
+{
+ u8 val;
+ u16 pdiv;
+ int ret;
+
+ if (clk->id == 0) {
+ ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_Y1SPIPDIVH, &val);
+ if (ret)
+ return 0;
+
+ pdiv = (val & CDCE9XX_PDIV1_H_MASK) << 8;
+
+ ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_PDIV1L, &val);
+ if (ret)
+ return 0;
+
+ pdiv |= val;
+ } else {
+ ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_PDIV(clk->id),
+ &val);
+ if (ret)
+ return 0;
+
+ pdiv = val & CDCE9XX_PDIV_MASK;
+ }
+
+ return pdiv;
+}
+
+static u32 cdce9xx_clk_get_parent_rate(struct clk *clk)
+{
+ struct cdce9xx_clk_data *data = dev_get_priv(clk->dev);
+
+ return data->xtal_rate;
+}
+
+static ulong cdce9xx_clk_get_rate(struct clk *clk)
+{
+ u32 parent_rate;
+ u16 pdiv;
+
+ parent_rate = cdce9xx_clk_get_parent_rate(clk);
+
+ pdiv = cdce9xx_clk_get_pdiv(clk);
+
+ return parent_rate / pdiv;
+}
+
+static ulong cdce9xx_clk_set_rate(struct clk *clk, ulong rate)
+{
+ u32 parent_rate;
+ int pdiv;
+ u32 diff;
+ u8 val;
+ int ret;
+
+ parent_rate = cdce9xx_clk_get_parent_rate(clk);
+
+ pdiv = parent_rate / rate;
+
+ diff = rate - parent_rate / pdiv;
+
+ if (rate - parent_rate / (pdiv + 1) < diff)
+ pdiv++;
+
+ if (clk->id == 0) {
+ ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_Y1SPIPDIVH, &val);
+ if (ret)
+ return ret;
+
+ val &= ~CDCE9XX_PDIV1_H_MASK;
+
+ val |= (pdiv >> 8);
+
+ ret = cdce9xx_reg_write(clk->dev, CDCE9XX_REG_Y1SPIPDIVH, val);
+ if (ret)
+ return ret;
+
+ ret = cdce9xx_reg_write(clk->dev, CDCE9XX_REG_PDIV1L,
+ (pdiv & 0xff));
+ if (ret)
+ return ret;
+ } else {
+ ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_PDIV(clk->id),
+ &val);
+ if (ret)
+ return ret;
+
+ val &= ~CDCE9XX_PDIV_MASK;
+
+ val |= pdiv;
+
+ ret = cdce9xx_reg_write(clk->dev, CDCE9XX_REG_PDIV(clk->id),
+ val);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct udevice_id cdce9xx_clk_of_match[] = {
+ { .compatible = "ti,cdce913", .data = (u32)&cdce913_chip_info },
+ { .compatible = "ti,cdce925", .data = (u32)&cdce925_chip_info },
+ { .compatible = "ti,cdce937", .data = (u32)&cdce937_chip_info },
+ { .compatible = "ti,cdce949", .data = (u32)&cdce949_chip_info },
+ { /* sentinel */ },
+};
+
+static const struct clk_ops cdce9xx_clk_ops = {
+ .of_xlate = cdce9xx_clk_of_xlate,
+ .get_rate = cdce9xx_clk_get_rate,
+ .set_rate = cdce9xx_clk_set_rate,
+};
+
+U_BOOT_DRIVER(cdce9xx_clk) = {
+ .name = "cdce9xx-clk",
+ .id = UCLASS_CLK,
+ .of_match = cdce9xx_clk_of_match,
+ .probe = cdce9xx_clk_probe,
+ .priv_auto_alloc_size = sizeof(struct cdce9xx_clk_data),
+ .ops = &cdce9xx_clk_ops,
+};
diff --git a/drivers/clk/clk_versal.c b/drivers/clk/clk_versal.c
new file mode 100644
index 0000000000..df87645774
--- /dev/null
+++ b/drivers/clk/clk_versal.c
@@ -0,0 +1,746 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2019 Xilinx, Inc.
+ * Siva Durga Prasad Paladugu <siva.durga.paladugu@xilinx.com>
+ */
+
+#include <common.h>
+#include <linux/bitops.h>
+#include <linux/bitfield.h>
+#include <malloc.h>
+#include <clk-uclass.h>
+#include <clk.h>
+#include <dm.h>
+#include <asm/arch/sys_proto.h>
+
+#define MAX_PARENT 100
+#define MAX_NODES 6
+#define MAX_NAME_LEN 50
+
+#define CLK_TYPE_SHIFT 2
+
+#define PM_API_PAYLOAD_LEN 3
+
+#define NA_PARENT 0xFFFFFFFF
+#define DUMMY_PARENT 0xFFFFFFFE
+
+#define CLK_TYPE_FIELD_LEN 4
+#define CLK_TOPOLOGY_NODE_OFFSET 16
+#define NODES_PER_RESP 3
+
+#define CLK_TYPE_FIELD_MASK 0xF
+#define CLK_FLAG_FIELD_MASK GENMASK(21, 8)
+#define CLK_TYPE_FLAG_FIELD_MASK GENMASK(31, 24)
+#define CLK_TYPE_FLAG2_FIELD_MASK GENMASK(7, 4)
+#define CLK_TYPE_FLAG_BITS 8
+
+#define CLK_PARENTS_ID_LEN 16
+#define CLK_PARENTS_ID_MASK 0xFFFF
+
+#define END_OF_TOPOLOGY_NODE 1
+#define END_OF_PARENTS 1
+
+#define CLK_VALID_MASK 0x1
+#define NODE_CLASS_SHIFT 26U
+#define NODE_SUBCLASS_SHIFT 20U
+#define NODE_TYPE_SHIFT 14U
+#define NODE_INDEX_SHIFT 0U
+
+#define CLK_GET_NAME_RESP_LEN 16
+#define CLK_GET_TOPOLOGY_RESP_WORDS 3
+#define CLK_GET_PARENTS_RESP_WORDS 3
+#define CLK_GET_ATTR_RESP_WORDS 1
+
+#define NODE_SUBCLASS_CLOCK_PLL 1
+#define NODE_SUBCLASS_CLOCK_OUT 2
+#define NODE_SUBCLASS_CLOCK_REF 3
+
+#define NODE_CLASS_CLOCK 2
+#define NODE_CLASS_MASK 0x3F
+
+#define CLOCK_NODE_TYPE_MUX 1
+#define CLOCK_NODE_TYPE_DIV 4
+#define CLOCK_NODE_TYPE_GATE 6
+
+enum pm_query_id {
+ PM_QID_INVALID,
+ PM_QID_CLOCK_GET_NAME,
+ PM_QID_CLOCK_GET_TOPOLOGY,
+ PM_QID_CLOCK_GET_FIXEDFACTOR_PARAMS,
+ PM_QID_CLOCK_GET_PARENTS,
+ PM_QID_CLOCK_GET_ATTRIBUTES,
+ PM_QID_PINCTRL_GET_NUM_PINS,
+ PM_QID_PINCTRL_GET_NUM_FUNCTIONS,
+ PM_QID_PINCTRL_GET_NUM_FUNCTION_GROUPS,
+ PM_QID_PINCTRL_GET_FUNCTION_NAME,
+ PM_QID_PINCTRL_GET_FUNCTION_GROUPS,
+ PM_QID_PINCTRL_GET_PIN_GROUPS,
+ PM_QID_CLOCK_GET_NUM_CLOCKS,
+ PM_QID_CLOCK_GET_MAX_DIVISOR,
+};
+
+enum clk_type {
+ CLK_TYPE_OUTPUT,
+ CLK_TYPE_EXTERNAL,
+};
+
+struct clock_parent {
+ char name[MAX_NAME_LEN];
+ int id;
+ u32 flag;
+};
+
+struct clock_topology {
+ u32 type;
+ u32 flag;
+ u32 type_flag;
+};
+
+struct versal_clock {
+ char clk_name[MAX_NAME_LEN];
+ u32 valid;
+ enum clk_type type;
+ struct clock_topology node[MAX_NODES];
+ u32 num_nodes;
+ struct clock_parent parent[MAX_PARENT];
+ u32 num_parents;
+ u32 clk_id;
+};
+
+struct versal_clk_priv {
+ struct versal_clock *clk;
+};
+
+static ulong alt_ref_clk;
+static ulong pl_alt_ref_clk;
+static ulong ref_clk;
+
+struct versal_pm_query_data {
+ u32 qid;
+ u32 arg1;
+ u32 arg2;
+ u32 arg3;
+};
+
+static struct versal_clock *clock;
+static unsigned int clock_max_idx;
+
+#define PM_QUERY_DATA 35
+
+static int versal_pm_query(struct versal_pm_query_data qdata, u32 *ret_payload)
+{
+ struct pt_regs regs;
+
+ regs.regs[0] = PM_SIP_SVC | PM_QUERY_DATA;
+ regs.regs[1] = ((u64)qdata.arg1 << 32) | qdata.qid;
+ regs.regs[2] = ((u64)qdata.arg3 << 32) | qdata.arg2;
+
+ smc_call(&regs);
+
+ if (ret_payload) {
+ ret_payload[0] = (u32)regs.regs[0];
+ ret_payload[1] = upper_32_bits(regs.regs[0]);
+ ret_payload[2] = (u32)regs.regs[1];
+ ret_payload[3] = upper_32_bits(regs.regs[1]);
+ ret_payload[4] = (u32)regs.regs[2];
+ }
+
+ return qdata.qid == PM_QID_CLOCK_GET_NAME ? 0 : regs.regs[0];
+}
+
+static inline int versal_is_valid_clock(u32 clk_id)
+{
+ if (clk_id >= clock_max_idx)
+ return -ENODEV;
+
+ return clock[clk_id].valid;
+}
+
+static int versal_get_clock_name(u32 clk_id, char *clk_name)
+{
+ int ret;
+
+ ret = versal_is_valid_clock(clk_id);
+ if (ret == 1) {
+ strncpy(clk_name, clock[clk_id].clk_name, MAX_NAME_LEN);
+ return 0;
+ }
+
+ return ret == 0 ? -EINVAL : ret;
+}
+
+static int versal_get_clock_type(u32 clk_id, u32 *type)
+{
+ int ret;
+
+ ret = versal_is_valid_clock(clk_id);
+ if (ret == 1) {
+ *type = clock[clk_id].type;
+ return 0;
+ }
+
+ return ret == 0 ? -EINVAL : ret;
+}
+
+static int versal_pm_clock_get_num_clocks(u32 *nclocks)
+{
+ struct versal_pm_query_data qdata = {0};
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ qdata.qid = PM_QID_CLOCK_GET_NUM_CLOCKS;
+
+ ret = versal_pm_query(qdata, ret_payload);
+ *nclocks = ret_payload[1];
+
+ return ret;
+}
+
+static int versal_pm_clock_get_name(u32 clock_id, char *name)
+{
+ struct versal_pm_query_data qdata = {0};
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ qdata.qid = PM_QID_CLOCK_GET_NAME;
+ qdata.arg1 = clock_id;
+
+ ret = versal_pm_query(qdata, ret_payload);
+ if (ret)
+ return ret;
+ memcpy(name, ret_payload, CLK_GET_NAME_RESP_LEN);
+
+ return 0;
+}
+
+static int versal_pm_clock_get_topology(u32 clock_id, u32 index, u32 *topology)
+{
+ struct versal_pm_query_data qdata = {0};
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ qdata.qid = PM_QID_CLOCK_GET_TOPOLOGY;
+ qdata.arg1 = clock_id;
+ qdata.arg2 = index;
+
+ ret = versal_pm_query(qdata, ret_payload);
+ memcpy(topology, &ret_payload[1], CLK_GET_TOPOLOGY_RESP_WORDS * 4);
+
+ return ret;
+}
+
+static int versal_pm_clock_get_parents(u32 clock_id, u32 index, u32 *parents)
+{
+ struct versal_pm_query_data qdata = {0};
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ qdata.qid = PM_QID_CLOCK_GET_PARENTS;
+ qdata.arg1 = clock_id;
+ qdata.arg2 = index;
+
+ ret = versal_pm_query(qdata, ret_payload);
+ memcpy(parents, &ret_payload[1], CLK_GET_PARENTS_RESP_WORDS * 4);
+
+ return ret;
+}
+
+static int versal_pm_clock_get_attributes(u32 clock_id, u32 *attr)
+{
+ struct versal_pm_query_data qdata = {0};
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ qdata.qid = PM_QID_CLOCK_GET_ATTRIBUTES;
+ qdata.arg1 = clock_id;
+
+ ret = versal_pm_query(qdata, ret_payload);
+ memcpy(attr, &ret_payload[1], CLK_GET_ATTR_RESP_WORDS * 4);
+
+ return ret;
+}
+
+static int __versal_clock_get_topology(struct clock_topology *topology,
+ u32 *data, u32 *nnodes)
+{
+ int i;
+
+ for (i = 0; i < PM_API_PAYLOAD_LEN; i++) {
+ if (!(data[i] & CLK_TYPE_FIELD_MASK))
+ return END_OF_TOPOLOGY_NODE;
+ topology[*nnodes].type = data[i] & CLK_TYPE_FIELD_MASK;
+ topology[*nnodes].flag = FIELD_GET(CLK_FLAG_FIELD_MASK,
+ data[i]);
+ topology[*nnodes].type_flag =
+ FIELD_GET(CLK_TYPE_FLAG_FIELD_MASK, data[i]);
+ topology[*nnodes].type_flag |=
+ FIELD_GET(CLK_TYPE_FLAG2_FIELD_MASK, data[i]) <<
+ CLK_TYPE_FLAG_BITS;
+ debug("topology type:0x%x, flag:0x%x, type_flag:0x%x\n",
+ topology[*nnodes].type, topology[*nnodes].flag,
+ topology[*nnodes].type_flag);
+ (*nnodes)++;
+ }
+
+ return 0;
+}
+
+static int versal_clock_get_topology(u32 clk_id,
+ struct clock_topology *topology,
+ u32 *num_nodes)
+{
+ int j, ret;
+ u32 pm_resp[PM_API_PAYLOAD_LEN] = {0};
+
+ *num_nodes = 0;
+ for (j = 0; j <= MAX_NODES; j += 3) {
+ ret = versal_pm_clock_get_topology(clock[clk_id].clk_id, j,
+ pm_resp);
+ if (ret)
+ return ret;
+ ret = __versal_clock_get_topology(topology, pm_resp, num_nodes);
+ if (ret == END_OF_TOPOLOGY_NODE)
+ return 0;
+ }
+
+ return 0;
+}
+
+static int __versal_clock_get_parents(struct clock_parent *parents, u32 *data,
+ u32 *nparent)
+{
+ int i;
+ struct clock_parent *parent;
+
+ for (i = 0; i < PM_API_PAYLOAD_LEN; i++) {
+ if (data[i] == NA_PARENT)
+ return END_OF_PARENTS;
+
+ parent = &parents[i];
+ parent->id = data[i] & CLK_PARENTS_ID_MASK;
+ if (data[i] == DUMMY_PARENT) {
+ strcpy(parent->name, "dummy_name");
+ parent->flag = 0;
+ } else {
+ parent->flag = data[i] >> CLK_PARENTS_ID_LEN;
+ if (versal_get_clock_name(parent->id, parent->name))
+ continue;
+ }
+ debug("parent name:%s\n", parent->name);
+ *nparent += 1;
+ }
+
+ return 0;
+}
+
+static int versal_clock_get_parents(u32 clk_id, struct clock_parent *parents,
+ u32 *num_parents)
+{
+ int j = 0, ret;
+ u32 pm_resp[PM_API_PAYLOAD_LEN] = {0};
+
+ *num_parents = 0;
+ do {
+ /* Get parents from firmware */
+ ret = versal_pm_clock_get_parents(clock[clk_id].clk_id, j,
+ pm_resp);
+ if (ret)
+ return ret;
+
+ ret = __versal_clock_get_parents(&parents[j], pm_resp,
+ num_parents);
+ if (ret == END_OF_PARENTS)
+ return 0;
+ j += PM_API_PAYLOAD_LEN;
+ } while (*num_parents <= MAX_PARENT);
+
+ return 0;
+}
+
+static u32 versal_clock_get_div(u32 clk_id)
+{
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ u32 div;
+
+ versal_pm_request(PM_CLOCK_GETDIVIDER, clk_id, 0, 0, 0, ret_payload);
+ div = ret_payload[1];
+
+ return div;
+}
+
+static u32 versal_clock_set_div(u32 clk_id, u32 div)
+{
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+
+ versal_pm_request(PM_CLOCK_SETDIVIDER, clk_id, div, 0, 0, ret_payload);
+
+ return div;
+}
+
+static u64 versal_clock_ref(u32 clk_id)
+{
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ref;
+
+ versal_pm_request(PM_CLOCK_GETPARENT, clk_id, 0, 0, 0, ret_payload);
+ ref = ret_payload[0];
+ if (!(ref & 1))
+ return ref_clk;
+ if (ref & 2)
+ return pl_alt_ref_clk;
+ return 0;
+}
+
+static u64 versal_clock_get_pll_rate(u32 clk_id)
+{
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ u32 fbdiv;
+ u32 res;
+ u32 frac;
+ u64 freq;
+ u32 parent_rate, parent_id;
+ u32 id = clk_id & 0xFFF;
+
+ versal_pm_request(PM_CLOCK_GETSTATE, clk_id, 0, 0, 0, ret_payload);
+ res = ret_payload[1];
+ if (!res) {
+ printf("0%x PLL not enabled\n", clk_id);
+ return 0;
+ }
+
+ parent_id = clock[clock[id].parent[0].id].clk_id;
+ parent_rate = versal_clock_ref(parent_id);
+
+ versal_pm_request(PM_CLOCK_GETDIVIDER, clk_id, 0, 0, 0, ret_payload);
+ fbdiv = ret_payload[1];
+ versal_pm_request(PM_CLOCK_PLL_GETPARAM, clk_id, 2, 0, 0, ret_payload);
+ frac = ret_payload[1];
+
+ freq = (fbdiv * parent_rate) >> (1 << frac);
+
+ return freq;
+}
+
+static u32 versal_clock_mux(u32 clk_id)
+{
+ int i;
+ u32 id = clk_id & 0xFFF;
+
+ for (i = 0; i < clock[id].num_nodes; i++)
+ if (clock[id].node[i].type == CLOCK_NODE_TYPE_MUX)
+ return 1;
+
+ return 0;
+}
+
+static u32 versal_clock_get_parentid(u32 clk_id)
+{
+ u32 parent_id = 0;
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ u32 id = clk_id & 0xFFF;
+
+ if (versal_clock_mux(clk_id)) {
+ versal_pm_request(PM_CLOCK_GETPARENT, clk_id, 0, 0, 0,
+ ret_payload);
+ parent_id = ret_payload[1];
+ }
+
+ debug("parent_id:0x%x\n", clock[clock[id].parent[parent_id].id].clk_id);
+ return clock[clock[id].parent[parent_id].id].clk_id;
+}
+
+static u32 versal_clock_gate(u32 clk_id)
+{
+ u32 id = clk_id & 0xFFF;
+ int i;
+
+ for (i = 0; i < clock[id].num_nodes; i++)
+ if (clock[id].node[i].type == CLOCK_NODE_TYPE_GATE)
+ return 1;
+
+ return 0;
+}
+
+static u32 versal_clock_div(u32 clk_id)
+{
+ int i;
+ u32 id = clk_id & 0xFFF;
+
+ for (i = 0; i < clock[id].num_nodes; i++)
+ if (clock[id].node[i].type == CLOCK_NODE_TYPE_DIV)
+ return 1;
+
+ return 0;
+}
+
+static u32 versal_clock_pll(u32 clk_id, u64 *clk_rate)
+{
+ if (((clk_id >> NODE_SUBCLASS_SHIFT) & NODE_CLASS_MASK) ==
+ NODE_SUBCLASS_CLOCK_PLL &&
+ ((clk_id >> NODE_CLASS_SHIFT) & NODE_CLASS_MASK) ==
+ NODE_CLASS_CLOCK) {
+ *clk_rate = versal_clock_get_pll_rate(clk_id);
+ return 1;
+ }
+
+ return 0;
+}
+
+static u64 versal_clock_calc(u32 clk_id)
+{
+ u32 parent_id;
+ u64 clk_rate;
+ u32 div;
+
+ if (versal_clock_pll(clk_id, &clk_rate))
+ return clk_rate;
+
+ parent_id = versal_clock_get_parentid(clk_id);
+ if (((parent_id >> NODE_SUBCLASS_SHIFT) &
+ NODE_CLASS_MASK) == NODE_SUBCLASS_CLOCK_REF)
+ return versal_clock_ref(clk_id);
+
+ clk_rate = versal_clock_calc(parent_id);
+
+ if (versal_clock_div(clk_id)) {
+ div = versal_clock_get_div(clk_id);
+ clk_rate = DIV_ROUND_CLOSEST(clk_rate, div);
+ }
+
+ return clk_rate;
+}
+
+static int versal_clock_get_rate(u32 clk_id, u64 *clk_rate)
+{
+ if (((clk_id >> NODE_SUBCLASS_SHIFT) &
+ NODE_CLASS_MASK) == NODE_SUBCLASS_CLOCK_REF)
+ *clk_rate = versal_clock_ref(clk_id);
+
+ if (versal_clock_pll(clk_id, clk_rate))
+ return 0;
+
+ if (((clk_id >> NODE_SUBCLASS_SHIFT) &
+ NODE_CLASS_MASK) == NODE_SUBCLASS_CLOCK_OUT &&
+ ((clk_id >> NODE_CLASS_SHIFT) &
+ NODE_CLASS_MASK) == NODE_CLASS_CLOCK) {
+ if (!versal_clock_gate(clk_id))
+ return -EINVAL;
+ *clk_rate = versal_clock_calc(clk_id);
+ return 0;
+ }
+
+ return 0;
+}
+
+int soc_clk_dump(void)
+{
+ u64 clk_rate = 0;
+ u32 type, ret, i = 0;
+
+ printf("\n ****** VERSAL CLOCKS *****\n");
+
+ printf("alt_ref_clk:%ld pl_alt_ref_clk:%ld ref_clk:%ld\n",
+ alt_ref_clk, pl_alt_ref_clk, ref_clk);
+ for (i = 0; i < clock_max_idx; i++) {
+ debug("%s\n", clock[i].clk_name);
+ ret = versal_get_clock_type(i, &type);
+ if (ret || type != CLK_TYPE_OUTPUT)
+ continue;
+
+ ret = versal_clock_get_rate(clock[i].clk_id, &clk_rate);
+
+ if (ret != -EINVAL)
+ printf("clk: %s freq:%lld\n",
+ clock[i].clk_name, clk_rate);
+ }
+
+ return 0;
+}
+
+static void versal_get_clock_info(void)
+{
+ int i, ret;
+ u32 attr, type = 0, nodetype, subclass, class;
+
+ for (i = 0; i < clock_max_idx; i++) {
+ ret = versal_pm_clock_get_attributes(i, &attr);
+ if (ret)
+ continue;
+
+ clock[i].valid = attr & CLK_VALID_MASK;
+ clock[i].type = ((attr >> CLK_TYPE_SHIFT) & 0x1) ?
+ CLK_TYPE_EXTERNAL : CLK_TYPE_OUTPUT;
+ nodetype = (attr >> NODE_TYPE_SHIFT) & NODE_CLASS_MASK;
+ subclass = (attr >> NODE_SUBCLASS_SHIFT) & NODE_CLASS_MASK;
+ class = (attr >> NODE_CLASS_SHIFT) & NODE_CLASS_MASK;
+
+ clock[i].clk_id = (class << NODE_CLASS_SHIFT) |
+ (subclass << NODE_SUBCLASS_SHIFT) |
+ (nodetype << NODE_TYPE_SHIFT) |
+ (i << NODE_INDEX_SHIFT);
+
+ ret = versal_pm_clock_get_name(clock[i].clk_id,
+ clock[i].clk_name);
+ if (ret)
+ continue;
+ debug("clk name:%s, Valid:%d, type:%d, clk_id:0x%x\n",
+ clock[i].clk_name, clock[i].valid,
+ clock[i].type, clock[i].clk_id);
+ }
+
+ /* Get topology of all clock */
+ for (i = 0; i < clock_max_idx; i++) {
+ ret = versal_get_clock_type(i, &type);
+ if (ret || type != CLK_TYPE_OUTPUT)
+ continue;
+ debug("clk name:%s\n", clock[i].clk_name);
+ ret = versal_clock_get_topology(i, clock[i].node,
+ &clock[i].num_nodes);
+ if (ret)
+ continue;
+
+ ret = versal_clock_get_parents(i, clock[i].parent,
+ &clock[i].num_parents);
+ if (ret)
+ continue;
+ }
+}
+
+int versal_clock_setup(void)
+{
+ int ret;
+
+ ret = versal_pm_clock_get_num_clocks(&clock_max_idx);
+ if (ret)
+ return ret;
+
+ debug("%s, clock_max_idx:0x%x\n", __func__, clock_max_idx);
+ clock = calloc(clock_max_idx, sizeof(*clock));
+ if (!clock)
+ return -ENOMEM;
+
+ versal_get_clock_info();
+
+ return 0;
+}
+
+static int versal_clock_get_freq_by_name(char *name, struct udevice *dev,
+ ulong *freq)
+{
+ struct clk clk;
+ int ret;
+
+ ret = clk_get_by_name(dev, name, &clk);
+ if (ret < 0) {
+ dev_err(dev, "failed to get %s\n", name);
+ return ret;
+ }
+
+ *freq = clk_get_rate(&clk);
+ if (IS_ERR_VALUE(*freq)) {
+ dev_err(dev, "failed to get rate %s\n", name);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int versal_clk_probe(struct udevice *dev)
+{
+ int ret;
+ struct versal_clk_priv *priv = dev_get_priv(dev);
+
+ debug("%s\n", __func__);
+
+ ret = versal_clock_get_freq_by_name("alt_ref_clk", dev, &alt_ref_clk);
+ if (ret < 0)
+ return -EINVAL;
+
+ ret = versal_clock_get_freq_by_name("pl_alt_ref_clk",
+ dev, &pl_alt_ref_clk);
+ if (ret < 0)
+ return -EINVAL;
+
+ ret = versal_clock_get_freq_by_name("ref_clk", dev, &ref_clk);
+ if (ret < 0)
+ return -EINVAL;
+
+ versal_clock_setup();
+
+ priv->clk = clock;
+
+ return ret;
+}
+
+static ulong versal_clk_get_rate(struct clk *clk)
+{
+ struct versal_clk_priv *priv = dev_get_priv(clk->dev);
+ u32 id = clk->id;
+ u32 clk_id;
+ u64 clk_rate = 0;
+
+ debug("%s\n", __func__);
+
+ clk_id = priv->clk[id].clk_id;
+
+ versal_clock_get_rate(clk_id, &clk_rate);
+
+ return clk_rate;
+}
+
+static ulong versal_clk_set_rate(struct clk *clk, ulong rate)
+{
+ struct versal_clk_priv *priv = dev_get_priv(clk->dev);
+ u32 id = clk->id;
+ u32 clk_id;
+ u64 clk_rate = 0;
+ u32 div;
+ int ret;
+
+ debug("%s\n", __func__);
+
+ clk_id = priv->clk[id].clk_id;
+
+ ret = versal_clock_get_rate(clk_id, &clk_rate);
+ if (ret) {
+ printf("Clock is not a Gate:0x%x\n", clk_id);
+ return 0;
+ }
+
+ do {
+ if (versal_clock_div(clk_id)) {
+ div = versal_clock_get_div(clk_id);
+ clk_rate *= div;
+ div = DIV_ROUND_CLOSEST(clk_rate, rate);
+ versal_clock_set_div(clk_id, div);
+ debug("%s, div:%d, newrate:%lld\n", __func__,
+ div, DIV_ROUND_CLOSEST(clk_rate, div));
+ return DIV_ROUND_CLOSEST(clk_rate, div);
+ }
+ clk_id = versal_clock_get_parentid(clk_id);
+ } while (((clk_id >> NODE_SUBCLASS_SHIFT) &
+ NODE_CLASS_MASK) != NODE_SUBCLASS_CLOCK_REF);
+
+ printf("Clock didn't has Divisors:0x%x\n", priv->clk[id].clk_id);
+
+ return clk_rate;
+}
+
+static struct clk_ops versal_clk_ops = {
+ .set_rate = versal_clk_set_rate,
+ .get_rate = versal_clk_get_rate,
+};
+
+static const struct udevice_id versal_clk_ids[] = {
+ { .compatible = "xlnx,versal-clk" },
+ { }
+};
+
+U_BOOT_DRIVER(versal_clk) = {
+ .name = "versal-clk",
+ .id = UCLASS_CLK,
+ .of_match = versal_clk_ids,
+ .probe = versal_clk_probe,
+ .ops = &versal_clk_ops,
+ .priv_auto_alloc_size = sizeof(struct versal_clk_priv),
+};
diff --git a/drivers/core/device.c b/drivers/core/device.c
index 84f0f0fbf0..ce66c72e5e 100644
--- a/drivers/core/device.c
+++ b/drivers/core/device.c
@@ -568,6 +568,17 @@ int device_get_child(struct udevice *parent, int index, struct udevice **devp)
return -ENODEV;
}
+int device_get_child_count(struct udevice *parent)
+{
+ struct udevice *dev;
+ int count = 0;
+
+ list_for_each_entry(dev, &parent->child_head, sibling_node)
+ count++;
+
+ return count;
+}
+
int device_find_child_by_seq(struct udevice *parent, int seq_or_req_seq,
bool find_req_seq, struct udevice **devp)
{
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 873bc8c796..b70a206355 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -26,3 +26,13 @@ config TI_SCI_PROTOCOL
This protocol library is used by client drivers to use the features
provided by the system controller.
+
+config ZYNQMP_FIRMWARE
+ bool "ZynqMP Firmware interface"
+ select FIRMWARE
+ help
+ Firmware interface driver is used by different
+ drivers to communicate with the firmware for
+ various platform management services.
+ Say yes to enable ZynqMP firmware interface driver.
+ If in doubt, say N.
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index 6c3e129685..a0c250a473 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_FIRMWARE) += firmware-uclass.o
obj-$(CONFIG_$(SPL_)ARM_PSCI_FW) += psci.o
obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o
obj-$(CONFIG_SANDBOX) += firmware-sandbox.o
+obj-$(CONFIG_ZYNQMP_FIRMWARE) += firmware-zynqmp.o
diff --git a/drivers/firmware/firmware-zynqmp.c b/drivers/firmware/firmware-zynqmp.c
new file mode 100644
index 0000000000..15e82ac3b3
--- /dev/null
+++ b/drivers/firmware/firmware-zynqmp.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx Zynq MPSoC Firmware driver
+ *
+ * Copyright (C) 2018-2019 Xilinx, Inc.
+ */
+
+#include <common.h>
+#include <dm.h>
+
+#if defined(CONFIG_ZYNQMP_IPI)
+#include <mailbox.h>
+#include <zynqmp_firmware.h>
+#include <asm/arch/sys_proto.h>
+
+#define PMUFW_PAYLOAD_ARG_CNT 8
+
+struct zynqmp_power {
+ struct mbox_chan tx_chan;
+ struct mbox_chan rx_chan;
+} zynqmp_power;
+
+static int ipi_req(const u32 *req, size_t req_len, u32 *res, size_t res_maxlen)
+{
+ struct zynqmp_ipi_msg msg;
+ int ret;
+
+ if (req_len > PMUFW_PAYLOAD_ARG_CNT ||
+ res_maxlen > PMUFW_PAYLOAD_ARG_CNT)
+ return -EINVAL;
+
+ if (!(zynqmp_power.tx_chan.dev) || !(&zynqmp_power.rx_chan.dev))
+ return -EINVAL;
+
+ msg.buf = (u32 *)req;
+ msg.len = req_len;
+ ret = mbox_send(&zynqmp_power.tx_chan, &msg);
+ if (ret) {
+ debug("%s: Sending message failed\n", __func__);
+ return ret;
+ }
+
+ msg.buf = res;
+ msg.len = res_maxlen;
+ ret = mbox_recv(&zynqmp_power.rx_chan, &msg, 100);
+ if (ret)
+ debug("%s: Receiving message failed\n", __func__);
+
+ return ret;
+}
+
+static int send_req(const u32 *req, size_t req_len, u32 *res, size_t res_maxlen)
+{
+ if (IS_ENABLED(CONFIG_SPL_BUILD))
+ return ipi_req(req, req_len, res, res_maxlen);
+
+ return invoke_smc(req[0] + PM_SIP_SVC, 0, 0, 0, 0, res);
+}
+
+unsigned int zynqmp_firmware_version(void)
+{
+ int ret;
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ static u32 pm_api_version = ZYNQMP_PM_VERSION_INVALID;
+
+ /*
+ * Get PMU version only once and later
+ * just return stored values instead of
+ * asking PMUFW again.
+ **/
+ if (pm_api_version == ZYNQMP_PM_VERSION_INVALID) {
+ const u32 request[] = { PM_GET_API_VERSION };
+
+ ret = send_req(request, ARRAY_SIZE(request), ret_payload, 2);
+ if (ret)
+ panic("PMUFW is not found - Please load it!\n");
+
+ pm_api_version = ret_payload[1];
+ if (pm_api_version < ZYNQMP_PM_VERSION)
+ panic("PMUFW version error. Expected: v%d.%d\n",
+ ZYNQMP_PM_VERSION_MAJOR, ZYNQMP_PM_VERSION_MINOR);
+ }
+
+ return pm_api_version;
+};
+
+/**
+ * Send a configuration object to the PMU firmware.
+ *
+ * @cfg_obj: Pointer to the configuration object
+ * @size: Size of @cfg_obj in bytes
+ */
+void zynqmp_pmufw_load_config_object(const void *cfg_obj, size_t size)
+{
+ const u32 request[] = {
+ PM_SET_CONFIGURATION,
+ (u32)((u64)cfg_obj)
+ };
+ u32 response;
+ int err;
+
+ printf("Loading new PMUFW cfg obj (%ld bytes)\n", size);
+
+ err = send_req(request, ARRAY_SIZE(request), &response, 1);
+ if (err)
+ panic("Cannot load PMUFW configuration object (%d)\n", err);
+ if (response != 0)
+ panic("PMUFW returned 0x%08x status!\n", response);
+}
+
+static int zynqmp_power_probe(struct udevice *dev)
+{
+ int ret = 0;
+
+ debug("%s, (dev=%p)\n", __func__, dev);
+
+ ret = mbox_get_by_name(dev, "tx", &zynqmp_power.tx_chan);
+ if (ret) {
+ debug("%s, cannot tx mailbox\n", __func__);
+ return ret;
+ }
+
+ ret = mbox_get_by_name(dev, "rx", &zynqmp_power.rx_chan);
+ if (ret) {
+ debug("%s, cannot rx mailbox\n", __func__);
+ return ret;
+ }
+
+ ret = zynqmp_firmware_version();
+ printf("PMUFW:\tv%d.%d\n",
+ ret >> ZYNQMP_PM_VERSION_MAJOR_SHIFT,
+ ret & ZYNQMP_PM_VERSION_MINOR_MASK);
+
+ return 0;
+};
+
+static const struct udevice_id zynqmp_power_ids[] = {
+ { .compatible = "xlnx,zynqmp-power" },
+ { }
+};
+
+U_BOOT_DRIVER(zynqmp_power) = {
+ .name = "zynqmp_power",
+ .id = UCLASS_FIRMWARE,
+ .of_match = zynqmp_power_ids,
+ .probe = zynqmp_power_probe,
+};
+#endif
+
+static const struct udevice_id zynqmp_firmware_ids[] = {
+ { .compatible = "xlnx,zynqmp-firmware" },
+ { .compatible = "xlnx,versal-firmware"},
+ { }
+};
+
+U_BOOT_DRIVER(zynqmp_firmware) = {
+ .id = UCLASS_FIRMWARE,
+ .name = "zynqmp-firmware",
+ .probe = dm_scan_fdt_dev,
+ .of_match = zynqmp_firmware_ids,
+};
diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
index 105a299812..fe398a1d49 100644
--- a/drivers/fpga/Kconfig
+++ b/drivers/fpga/Kconfig
@@ -56,6 +56,15 @@ config FPGA_ZYNQMPPL
Enable FPGA driver for loading bitstream in BIT and BIN format
on Xilinx Zynq UltraScale+ (ZynqMP) device.
+config FPGA_VERSALPL
+ bool "Enable Xilinx FPGA driver for Versal"
+ depends on FPGA_XILINX
+ help
+ Enable FPGA driver for loading bitstream in PDI format on Xilinx
+ Versal device. PDI is a new programmable device image format for
+ Versal. The bitstream will only be generated as PDI for Versal
+ platform.
+
config FPGA_SPARTAN3
bool "Enable Spartan3 FPGA driver"
depends on FPGA_XILINX
diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
index 5a778c10e8..04e6480f20 100644
--- a/drivers/fpga/Makefile
+++ b/drivers/fpga/Makefile
@@ -6,6 +6,7 @@
obj-y += fpga.o
obj-$(CONFIG_FPGA_SPARTAN2) += spartan2.o
obj-$(CONFIG_FPGA_SPARTAN3) += spartan3.o
+obj-$(CONFIG_FPGA_VERSALPL) += versalpl.o
obj-$(CONFIG_FPGA_VIRTEX2) += virtex2.o
obj-$(CONFIG_FPGA_ZYNQPL) += zynqpl.o
obj-$(CONFIG_FPGA_ZYNQMPPL) += zynqmppl.o
diff --git a/drivers/fpga/versalpl.c b/drivers/fpga/versalpl.c
new file mode 100644
index 0000000000..69617a9b1d
--- /dev/null
+++ b/drivers/fpga/versalpl.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2019, Xilinx, Inc,
+ * Siva Durga Prasad Paladugu <siva.durga.paladugu@xilinx.com>
+ */
+
+#include <common.h>
+#include <asm/arch/sys_proto.h>
+#include <memalign.h>
+#include <versalpl.h>
+
+static ulong versal_align_dma_buffer(ulong *buf, u32 len)
+{
+ ulong *new_buf;
+
+ if ((ulong)buf != ALIGN((ulong)buf, ARCH_DMA_MINALIGN)) {
+ new_buf = (ulong *)ALIGN((ulong)buf, ARCH_DMA_MINALIGN);
+ memcpy(new_buf, buf, len);
+ buf = new_buf;
+ }
+
+ return (ulong)buf;
+}
+
+static int versal_load(xilinx_desc *desc, const void *buf, size_t bsize,
+ bitstream_type bstype)
+{
+ ulong bin_buf;
+ int ret;
+ u32 buf_lo, buf_hi;
+ u32 ret_payload[5];
+
+ bin_buf = versal_align_dma_buffer((ulong *)buf, bsize);
+
+ debug("%s called!\n", __func__);
+ flush_dcache_range(bin_buf, bin_buf + bsize);
+
+ buf_lo = lower_32_bits(bin_buf);
+ buf_hi = upper_32_bits(bin_buf);
+
+ ret = versal_pm_request(VERSAL_PM_LOAD_PDI, VERSAL_PM_PDI_TYPE, buf_lo,
+ buf_hi, 0, ret_payload);
+ if (ret)
+ puts("PL FPGA LOAD fail\n");
+
+ return ret;
+}
+
+struct xilinx_fpga_op versal_op = {
+ .load = versal_load,
+};
diff --git a/drivers/fpga/xilinx.c b/drivers/fpga/xilinx.c
index f5135504ee..4b0334b6be 100644
--- a/drivers/fpga/xilinx.c
+++ b/drivers/fpga/xilinx.c
@@ -226,7 +226,10 @@ int xilinx_info(xilinx_desc *desc)
case xilinx_zynqmp:
printf("ZynqMP PL\n");
break;
- /* Add new family types here */
+ case xilinx_versal:
+ printf("Versal PL\n");
+ break;
+ /* Add new family types here */
default:
printf ("Unknown family type, %d\n", desc->family);
}
@@ -257,6 +260,9 @@ int xilinx_info(xilinx_desc *desc)
case csu_dma:
printf("csu_dma configuration interface (ZynqMP)\n");
break;
+ case cfi:
+ printf("CFI configuration interface (Versal)\n");
+ break;
/* Add new interface types here */
default:
printf ("Unsupported interface type, %d\n", desc->iface);
diff --git a/drivers/fpga/zynqmppl.c b/drivers/fpga/zynqmppl.c
index 22bfdd8dce..c2670271c8 100644
--- a/drivers/fpga/zynqmppl.c
+++ b/drivers/fpga/zynqmppl.c
@@ -8,6 +8,7 @@
#include <console.h>
#include <common.h>
#include <zynqmppl.h>
+#include <zynqmp_firmware.h>
#include <linux/sizes.h>
#include <asm/arch/sys_proto.h>
#include <memalign.h>
@@ -151,9 +152,9 @@ static ulong zynqmp_align_dma_buffer(u32 *buf, u32 len, u32 swap)
buf = new_buf;
} else if ((swap != SWAP_DONE) &&
- (zynqmp_pmufw_version() <= PMUFW_V1_0)) {
+ (zynqmp_firmware_version() <= PMUFW_V1_0)) {
/* For bitstream which are aligned */
- u32 *new_buf = (u32 *)buf;
+ new_buf = buf;
printf("%s: Bitstream is not swapped(%d) - swap it\n", __func__,
swap);
@@ -204,7 +205,7 @@ static int zynqmp_load(xilinx_desc *desc, const void *buf, size_t bsize,
u32 ret_payload[PAYLOAD_ARG_CNT];
bool xilfpga_old = false;
- if (zynqmp_pmufw_version() <= PMUFW_V1_0) {
+ if (zynqmp_firmware_version() <= PMUFW_V1_0) {
puts("WARN: PMUFW v1.0 or less is detected\n");
puts("WARN: Not all bitstream formats are supported\n");
puts("WARN: Please upgrade PMUFW\n");
diff --git a/drivers/gpio/zynq_gpio.c b/drivers/gpio/zynq_gpio.c
index 55a5cba068..a760c5bdda 100644
--- a/drivers/gpio/zynq_gpio.c
+++ b/drivers/gpio/zynq_gpio.c
@@ -292,7 +292,7 @@ static int zynq_gpio_direction_output(struct udevice *dev, unsigned gpio,
writel(reg, platdata->base + ZYNQ_GPIO_OUTEN_OFFSET(bank_num));
/* set the state of the pin */
- gpio_set_value(gpio, value);
+ zynq_gpio_set_value(dev, gpio, value);
return 0;
}
diff --git a/drivers/i2c/imx_lpi2c.c b/drivers/i2c/imx_lpi2c.c
index 4586d4331f..2de99d019e 100644
--- a/drivers/i2c/imx_lpi2c.c
+++ b/drivers/i2c/imx_lpi2c.c
@@ -471,6 +471,17 @@ static int imx_lpi2c_probe(struct udevice *bus)
dev_err(bus, "Failed to enable per clk\n");
return ret;
}
+
+ ret = clk_get_by_name(bus, "ipg", &i2c_bus->ipg_clk);
+ if (ret) {
+ dev_err(bus, "Failed to get ipg clk\n");
+ return ret;
+ }
+ ret = clk_enable(&i2c_bus->ipg_clk);
+ if (ret) {
+ dev_err(bus, "Failed to enable ipg clk\n");
+ return ret;
+ }
} else {
/* To i.MX7ULP, only i2c4-7 can be handled by A7 core */
ret = enable_i2c_clk(1, bus->seq);
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index 11bf5522db..85c2a829ae 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -41,4 +41,10 @@ config K3_SEC_PROXY
Select this driver if your platform has support for this hardware
block.
+config ZYNQMP_IPI
+ bool "Xilinx ZynqMP IPI controller support"
+ depends on DM_MAILBOX && ARCH_ZYNQMP
+ help
+ This enables support for the Xilinx ZynqMP Inter Processor Interrupt
+ communication controller.
endmenu
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index a753cc4e68..d2ace8cd21 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -9,3 +9,4 @@ obj-$(CONFIG_SANDBOX_MBOX) += sandbox-mbox-test.o
obj-$(CONFIG_STM32_IPCC) += stm32-ipcc.o
obj-$(CONFIG_TEGRA_HSP) += tegra-hsp.o
obj-$(CONFIG_K3_SEC_PROXY) += k3-sec-proxy.o
+obj-$(CONFIG_ZYNQMP_IPI) += zynqmp-ipi.o
diff --git a/drivers/mailbox/mailbox-uclass.c b/drivers/mailbox/mailbox-uclass.c
index 1b4a5863c9..9fdb6279e4 100644
--- a/drivers/mailbox/mailbox-uclass.c
+++ b/drivers/mailbox/mailbox-uclass.c
@@ -49,7 +49,16 @@ int mbox_get_by_index(struct udevice *dev, int index, struct mbox_chan *chan)
if (ret) {
debug("%s: uclass_get_device_by_of_offset failed: %d\n",
__func__, ret);
- return ret;
+
+ /* Test with parent node */
+ ret = uclass_get_device_by_ofnode(UCLASS_MAILBOX,
+ ofnode_get_parent(args.node),
+ &dev_mbox);
+ if (ret) {
+ debug("%s: mbox node from parent failed: %d\n",
+ __func__, ret);
+ return ret;
+ };
}
ops = mbox_dev_ops(dev_mbox);
@@ -63,7 +72,8 @@ int mbox_get_by_index(struct udevice *dev, int index, struct mbox_chan *chan)
return ret;
}
- ret = ops->request(chan);
+ if (ops->request)
+ ret = ops->request(chan);
if (ret) {
debug("ops->request() failed: %d\n", ret);
return ret;
@@ -94,7 +104,10 @@ int mbox_free(struct mbox_chan *chan)
debug("%s(chan=%p)\n", __func__, chan);
- return ops->free(chan);
+ if (ops->free)
+ return ops->free(chan);
+
+ return 0;
}
int mbox_send(struct mbox_chan *chan, const void *data)
diff --git a/drivers/mailbox/zynqmp-ipi.c b/drivers/mailbox/zynqmp-ipi.c
new file mode 100644
index 0000000000..c181a7b817
--- /dev/null
+++ b/drivers/mailbox/zynqmp-ipi.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Xilinx Zynq MPSoC Mailbox driver
+ *
+ * Copyright (C) 2018-2019 Xilinx, Inc.
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <dm.h>
+#include <mailbox-uclass.h>
+#include <mach/sys_proto.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <wait_bit.h>
+
+/* IPI bitmasks, register base */
+/* TODO: move reg base to DT */
+#define IPI_BIT_MASK_PMU0 0x10000
+#define IPI_INT_REG_BASE_APU 0xFF300000
+
+struct ipi_int_regs {
+ u32 trig; /* 0x0 */
+ u32 obs; /* 0x4 */
+ u32 ist; /* 0x8 */
+ u32 imr; /* 0xC */
+ u32 ier; /* 0x10 */
+ u32 idr; /* 0x14 */
+};
+
+#define ipi_int_apu ((struct ipi_int_regs *)IPI_INT_REG_BASE_APU)
+
+struct zynqmp_ipi {
+ void __iomem *local_req_regs;
+ void __iomem *local_res_regs;
+ void __iomem *remote_req_regs;
+ void __iomem *remote_res_regs;
+};
+
+static int zynqmp_ipi_send(struct mbox_chan *chan, const void *data)
+{
+ const struct zynqmp_ipi_msg *msg = (struct zynqmp_ipi_msg *)data;
+ struct zynqmp_ipi *zynqmp = dev_get_priv(chan->dev);
+ u32 ret;
+ u32 *mbx = (u32 *)zynqmp->local_req_regs;
+
+ for (size_t i = 0; i < msg->len; i++)
+ writel(msg->buf[i], &mbx[i]);
+
+ /* Write trigger interrupt */
+ writel(IPI_BIT_MASK_PMU0, &ipi_int_apu->trig);
+
+ /* Wait until observation bit is cleared */
+ ret = wait_for_bit_le32(&ipi_int_apu->obs, IPI_BIT_MASK_PMU0, false,
+ 100, false);
+
+ debug("%s, send %ld bytes\n", __func__, msg->len);
+ return ret;
+};
+
+static int zynqmp_ipi_recv(struct mbox_chan *chan, void *data)
+{
+ struct zynqmp_ipi_msg *msg = (struct zynqmp_ipi_msg *)data;
+ struct zynqmp_ipi *zynqmp = dev_get_priv(chan->dev);
+ u32 *mbx = (u32 *)zynqmp->local_res_regs;
+
+ for (size_t i = 0; i < msg->len; i++)
+ msg->buf[i] = readl(&mbx[i]);
+
+ debug("%s, recv %ld bytes\n", __func__, msg->len);
+ return 0;
+};
+
+static int zynqmp_ipi_probe(struct udevice *dev)
+{
+ struct zynqmp_ipi *zynqmp = dev_get_priv(dev);
+ struct resource res;
+ ofnode node;
+
+ debug("%s(dev=%p)\n", __func__, dev);
+
+ /* Get subnode where the regs are defined */
+ /* Note IPI mailbox node needs to be the first one in DT */
+ node = ofnode_first_subnode(dev_ofnode(dev));
+
+ if (ofnode_read_resource_byname(node, "local_request_region", &res)) {
+ dev_err(dev, "No reg property for local_request_region\n");
+ return -EINVAL;
+ };
+ zynqmp->local_req_regs = devm_ioremap(dev, res.start,
+ (res.start - res.end));
+
+ if (ofnode_read_resource_byname(node, "local_response_region", &res)) {
+ dev_err(dev, "No reg property for local_response_region\n");
+ return -EINVAL;
+ };
+ zynqmp->local_res_regs = devm_ioremap(dev, res.start,
+ (res.start - res.end));
+
+ if (ofnode_read_resource_byname(node, "remote_request_region", &res)) {
+ dev_err(dev, "No reg property for remote_request_region\n");
+ return -EINVAL;
+ };
+ zynqmp->remote_req_regs = devm_ioremap(dev, res.start,
+ (res.start - res.end));
+
+ if (ofnode_read_resource_byname(node, "remote_response_region", &res)) {
+ dev_err(dev, "No reg property for remote_response_region\n");
+ return -EINVAL;
+ };
+ zynqmp->remote_res_regs = devm_ioremap(dev, res.start,
+ (res.start - res.end));
+
+ return 0;
+};
+
+static const struct udevice_id zynqmp_ipi_ids[] = {
+ { .compatible = "xlnx,zynqmp-ipi-mailbox" },
+ { }
+};
+
+struct mbox_ops zynqmp_ipi_mbox_ops = {
+ .send = zynqmp_ipi_send,
+ .recv = zynqmp_ipi_recv,
+};
+
+U_BOOT_DRIVER(zynqmp_ipi) = {
+ .name = "zynqmp-ipi",
+ .id = UCLASS_MAILBOX,
+ .of_match = zynqmp_ipi_ids,
+ .probe = zynqmp_ipi_probe,
+ .priv_auto_alloc_size = sizeof(struct zynqmp_ipi),
+ .ops = &zynqmp_ipi_mbox_ops,
+};
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig
index 7361bcaf8e..18af0762a8 100644
--- a/drivers/mmc/Kconfig
+++ b/drivers/mmc/Kconfig
@@ -489,6 +489,17 @@ config MMC_SDHCI_AM654
Support for Secure Digital Host Controller Interface (SDHCI)
controllers present on TI's AM654 SOCs.
+config MMC_SDHCI_IPROC
+ bool "SDHCI support for the iProc SD/MMC Controller"
+ depends on MMC_SDHCI
+ help
+ This selects the iProc SD/MMC controller.
+
+ If you have a Broadcom IPROC platform with SD or MMC devices,
+ say Y or M here.
+
+ If unsure, say N.
+
config MMC_SDHCI_KONA
bool "SDHCI support on Broadcom KONA platform"
depends on MMC_SDHCI
@@ -634,6 +645,12 @@ config ZYNQ_SDHCI_MIN_FREQ
help
Set the minimum frequency of the controller.
+config ZYNQ_HISPD_BROKEN
+ bool "High speed broken for Zynq SDHCI controller"
+ depends on MMC_SDHCI_ZYNQ
+ help
+ Set if high speed mode is broken.
+
config MMC_SUNXI
bool "Allwinner sunxi SD/MMC Host Controller support"
depends on ARCH_SUNXI && !UART0_PORT_F
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
index 5594195528..9c1f8e56e2 100644
--- a/drivers/mmc/Makefile
+++ b/drivers/mmc/Makefile
@@ -52,6 +52,7 @@ obj-$(CONFIG_MMC_SDHCI_BCM2835) += bcm2835_sdhci.o
obj-$(CONFIG_MMC_SDHCI_BCMSTB) += bcmstb_sdhci.o
obj-$(CONFIG_MMC_SDHCI_CADENCE) += sdhci-cadence.o
obj-$(CONFIG_MMC_SDHCI_AM654) += am654_sdhci.o
+obj-$(CONFIG_MMC_SDHCI_IPROC) += iproc_sdhci.o
obj-$(CONFIG_MMC_SDHCI_KONA) += kona_sdhci.o
obj-$(CONFIG_MMC_SDHCI_MSM) += msm_sdhci.o
obj-$(CONFIG_MMC_SDHCI_MV) += mv_sdhci.o
diff --git a/drivers/mmc/am654_sdhci.c b/drivers/mmc/am654_sdhci.c
index 1793a3f99a..7cd5516197 100644
--- a/drivers/mmc/am654_sdhci.c
+++ b/drivers/mmc/am654_sdhci.c
@@ -219,23 +219,10 @@ static int am654_sdhci_probe(struct udevice *dev)
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
struct sdhci_host *host = dev_get_priv(dev);
struct mmc_config *cfg = &plat->cfg;
- struct power_domain sdhci_pwrdmn;
struct clk clk;
unsigned long clock;
int ret;
- ret = power_domain_get_by_index(dev, &sdhci_pwrdmn, 0);
- if (!ret) {
- ret = power_domain_on(&sdhci_pwrdmn);
- if (ret) {
- dev_err(dev, "Power domain on failed (%d)\n", ret);
- return ret;
- }
- } else if (ret != -ENOENT && ret != -ENODEV && ret != -ENOSYS) {
- dev_err(dev, "failed to get power domain (%d)\n", ret);
- return ret;
- }
-
ret = clk_get_by_index(dev, 0, &clk);
if (ret) {
dev_err(dev, "failed to get clock\n");
diff --git a/drivers/mmc/iproc_sdhci.c b/drivers/mmc/iproc_sdhci.c
new file mode 100644
index 0000000000..831dd32eb7
--- /dev/null
+++ b/drivers/mmc/iproc_sdhci.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2019 Broadcom.
+ *
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <malloc.h>
+#include <sdhci.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct sdhci_iproc_host {
+ struct sdhci_host host;
+ u32 shadow_cmd;
+ u32 shadow_blk;
+};
+
+#define REG_OFFSET_IN_BITS(reg) ((reg) << 3 & 0x18)
+
+static inline struct sdhci_iproc_host *to_iproc(struct sdhci_host *host)
+{
+ return (struct sdhci_iproc_host *)host;
+}
+
+#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
+static u32 sdhci_iproc_readl(struct sdhci_host *host, int reg)
+{
+ u32 val = readl(host->ioaddr + reg);
+#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS_TRACE
+ printf("%s %d: readl [0x%02x] 0x%08x\n",
+ host->name, host->index, reg, val);
+#endif
+ return val;
+}
+
+static u16 sdhci_iproc_readw(struct sdhci_host *host, int reg)
+{
+ u32 val = sdhci_iproc_readl(host, (reg & ~3));
+ u16 word = val >> REG_OFFSET_IN_BITS(reg) & 0xffff;
+ return word;
+}
+
+static u8 sdhci_iproc_readb(struct sdhci_host *host, int reg)
+{
+ u32 val = sdhci_iproc_readl(host, (reg & ~3));
+ u8 byte = val >> REG_OFFSET_IN_BITS(reg) & 0xff;
+ return byte;
+}
+
+static void sdhci_iproc_writel(struct sdhci_host *host, u32 val, int reg)
+{
+ u32 clock = 0;
+#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS_TRACE
+ printf("%s %d: writel [0x%02x] 0x%08x\n",
+ host->name, host->index, reg, val);
+#endif
+ writel(val, host->ioaddr + reg);
+
+ if (host->mmc)
+ clock = host->mmc->clock;
+ if (clock <= 400000) {
+ /* Round up to micro-second four SD clock delay */
+ if (clock)
+ udelay((4 * 1000000 + clock - 1) / clock);
+ else
+ udelay(10);
+ }
+}
+
+/*
+ * The Arasan has a bugette whereby it may lose the content of successive
+ * writes to the same register that are within two SD-card clock cycles of
+ * each other (a clock domain crossing problem). The data
+ * register does not have this problem, which is just as well - otherwise we'd
+ * have to nobble the DMA engine too.
+ *
+ * This wouldn't be a problem with the code except that we can only write the
+ * controller with 32-bit writes. So two different 16-bit registers are
+ * written back to back creates the problem.
+ *
+ * In reality, this only happens when SDHCI_BLOCK_SIZE and SDHCI_BLOCK_COUNT
+ * are written followed by SDHCI_TRANSFER_MODE and SDHCI_COMMAND.
+ * The BLOCK_SIZE and BLOCK_COUNT are meaningless until a command issued so
+ * the work around can be further optimized. We can keep shadow values of
+ * BLOCK_SIZE, BLOCK_COUNT, and TRANSFER_MODE until a COMMAND is issued.
+ * Then, write the BLOCK_SIZE+BLOCK_COUNT in a single 32-bit write followed
+ * by the TRANSFER+COMMAND in another 32-bit write.
+ */
+static void sdhci_iproc_writew(struct sdhci_host *host, u16 val, int reg)
+{
+ struct sdhci_iproc_host *iproc_host = to_iproc(host);
+ u32 word_shift = REG_OFFSET_IN_BITS(reg);
+ u32 mask = 0xffff << word_shift;
+ u32 oldval, newval;
+
+ if (reg == SDHCI_COMMAND) {
+ /* Write the block now as we are issuing a command */
+ if (iproc_host->shadow_blk != 0) {
+ sdhci_iproc_writel(host, iproc_host->shadow_blk,
+ SDHCI_BLOCK_SIZE);
+ iproc_host->shadow_blk = 0;
+ }
+ oldval = iproc_host->shadow_cmd;
+ } else if (reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) {
+ /* Block size and count are stored in shadow reg */
+ oldval = iproc_host->shadow_blk;
+ } else {
+ /* Read reg, all other registers are not shadowed */
+ oldval = sdhci_iproc_readl(host, (reg & ~3));
+ }
+ newval = (oldval & ~mask) | (val << word_shift);
+
+ if (reg == SDHCI_TRANSFER_MODE) {
+ /* Save the transfer mode until the command is issued */
+ iproc_host->shadow_cmd = newval;
+ } else if (reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) {
+ /* Save the block info until the command is issued */
+ iproc_host->shadow_blk = newval;
+ } else {
+ /* Command or other regular 32-bit write */
+ sdhci_iproc_writel(host, newval, reg & ~3);
+ }
+}
+
+static void sdhci_iproc_writeb(struct sdhci_host *host, u8 val, int reg)
+{
+ u32 oldval = sdhci_iproc_readl(host, (reg & ~3));
+ u32 byte_shift = REG_OFFSET_IN_BITS(reg);
+ u32 mask = 0xff << byte_shift;
+ u32 newval = (oldval & ~mask) | (val << byte_shift);
+
+ sdhci_iproc_writel(host, newval, reg & ~3);
+}
+#endif
+
+static void sdhci_iproc_set_ios_post(struct sdhci_host *host)
+{
+ u32 ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+
+ /* Reset UHS mode bits */
+ ctrl &= ~SDHCI_CTRL_UHS_MASK;
+
+ if (host->mmc->ddr_mode)
+ ctrl |= UHS_DDR50_BUS_SPEED;
+
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+}
+
+static struct sdhci_ops sdhci_platform_ops = {
+#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
+ .read_l = sdhci_iproc_readl,
+ .read_w = sdhci_iproc_readw,
+ .read_b = sdhci_iproc_readb,
+ .write_l = sdhci_iproc_writel,
+ .write_w = sdhci_iproc_writew,
+ .write_b = sdhci_iproc_writeb,
+#endif
+ .set_ios_post = sdhci_iproc_set_ios_post,
+};
+
+struct iproc_sdhci_plat {
+ struct mmc_config cfg;
+ struct mmc mmc;
+};
+
+static int iproc_sdhci_probe(struct udevice *dev)
+{
+ struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
+ struct iproc_sdhci_plat *plat = dev_get_platdata(dev);
+ struct sdhci_host *host = dev_get_priv(dev);
+ struct sdhci_iproc_host *iproc_host;
+ int node = dev_of_offset(dev);
+ u32 f_min_max[2];
+ int ret;
+
+ iproc_host = (struct sdhci_iproc_host *)
+ malloc(sizeof(struct sdhci_iproc_host));
+ if (!iproc_host) {
+ printf("%s: sdhci host malloc fail!\n", __func__);
+ return -ENOMEM;
+ }
+ iproc_host->shadow_cmd = 0;
+ iproc_host->shadow_blk = 0;
+
+ host->name = dev->name;
+ host->ioaddr = (void *)devfdt_get_addr(dev);
+ host->voltages = MMC_VDD_165_195 |
+ MMC_VDD_32_33 | MMC_VDD_33_34;
+ host->quirks = SDHCI_QUIRK_BROKEN_VOLTAGE;
+ host->host_caps = MMC_MODE_DDR_52MHz;
+ host->index = fdtdec_get_uint(gd->fdt_blob, node, "index", 0);
+ host->ops = &sdhci_platform_ops;
+ host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
+ ret = fdtdec_get_int_array(gd->fdt_blob, dev_of_offset(dev),
+ "clock-freq-min-max", f_min_max, 2);
+ if (ret) {
+ printf("sdhci: clock-freq-min-max not found\n");
+ return ret;
+ }
+ host->max_clk = f_min_max[1];
+ host->bus_width = fdtdec_get_int(gd->fdt_blob,
+ dev_of_offset(dev), "bus-width", 4);
+
+ /* Update host_caps for 8 bit bus width */
+ if (host->bus_width == 8)
+ host->host_caps |= MMC_MODE_8BIT;
+
+ memcpy(&iproc_host->host, host, sizeof(struct sdhci_host));
+
+ ret = sdhci_setup_cfg(&plat->cfg, &iproc_host->host,
+ f_min_max[1], f_min_max[0]);
+ if (ret)
+ return ret;
+
+ iproc_host->host.mmc = &plat->mmc;
+ iproc_host->host.mmc->dev = dev;
+ iproc_host->host.mmc->priv = &iproc_host->host;
+ upriv->mmc = iproc_host->host.mmc;
+
+ return sdhci_probe(dev);
+}
+
+static int iproc_sdhci_bind(struct udevice *dev)
+{
+ struct iproc_sdhci_plat *plat = dev_get_platdata(dev);
+
+ return sdhci_bind(dev, &plat->mmc, &plat->cfg);
+}
+
+static const struct udevice_id iproc_sdhci_ids[] = {
+ { .compatible = "brcm,iproc-sdhci" },
+ { }
+};
+
+U_BOOT_DRIVER(iproc_sdhci_drv) = {
+ .name = "iproc_sdhci",
+ .id = UCLASS_MMC,
+ .of_match = iproc_sdhci_ids,
+ .ops = &sdhci_ops,
+ .bind = iproc_sdhci_bind,
+ .probe = iproc_sdhci_probe,
+ .priv_auto_alloc_size = sizeof(struct sdhci_host),
+ .platdata_auto_alloc_size = sizeof(struct iproc_sdhci_plat),
+};
diff --git a/drivers/mmc/mmc-uclass.c b/drivers/mmc/mmc-uclass.c
index 37c3843902..c7a832ca90 100644
--- a/drivers/mmc/mmc-uclass.c
+++ b/drivers/mmc/mmc-uclass.c
@@ -122,6 +122,20 @@ int mmc_set_enhanced_strobe(struct mmc *mmc)
}
#endif
+int dm_mmc_host_power_cycle(struct udevice *dev)
+{
+ struct dm_mmc_ops *ops = mmc_get_ops(dev);
+
+ if (ops->host_power_cycle)
+ return ops->host_power_cycle(dev);
+ return 0;
+}
+
+int mmc_host_power_cycle(struct mmc *mmc)
+{
+ return dm_mmc_host_power_cycle(mmc->dev);
+}
+
int mmc_of_parse(struct udevice *dev, struct mmc_config *cfg)
{
int val;
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c
index 6bece7f307..f683b52ead 100644
--- a/drivers/mmc/mmc.c
+++ b/drivers/mmc/mmc.c
@@ -1546,6 +1546,16 @@ static int mmc_set_ios(struct mmc *mmc)
return ret;
}
+
+static int mmc_host_power_cycle(struct mmc *mmc)
+{
+ int ret = 0;
+
+ if (mmc->cfg->ops->host_power_cycle)
+ ret = mmc->cfg->ops->host_power_cycle(mmc);
+
+ return ret;
+}
#endif
int mmc_set_clock(struct mmc *mmc, uint clock, bool disable)
@@ -2715,6 +2725,11 @@ static int mmc_power_cycle(struct mmc *mmc)
ret = mmc_power_off(mmc);
if (ret)
return ret;
+
+ ret = mmc_host_power_cycle(mmc);
+ if (ret)
+ return ret;
+
/*
* SD spec recommends at least 1ms of delay. Let's wait for 2ms
* to be on the safer side.
@@ -2998,6 +3013,30 @@ int mmc_initialize(bd_t *bis)
return 0;
}
+#if CONFIG_IS_ENABLED(DM_MMC)
+int mmc_init_device(int num)
+{
+ struct udevice *dev;
+ struct mmc *m;
+ int ret;
+
+ ret = uclass_get_device(UCLASS_MMC, num, &dev);
+ if (ret)
+ return ret;
+
+ m = mmc_get_mmc_dev(dev);
+ if (!m)
+ return 0;
+#ifdef CONFIG_FSL_ESDHC_ADAPTER_IDENT
+ mmc_set_preinit(m, 1);
+#endif
+ if (m->preinit)
+ mmc_start_init(m);
+
+ return 0;
+}
+#endif
+
#ifdef CONFIG_CMD_BKOPS_ENABLE
int mmc_set_bkops_enable(struct mmc *mmc)
{
diff --git a/drivers/mmc/stm32_sdmmc2.c b/drivers/mmc/stm32_sdmmc2.c
index 32434a4762..1726ed72ef 100644
--- a/drivers/mmc/stm32_sdmmc2.c
+++ b/drivers/mmc/stm32_sdmmc2.c
@@ -524,8 +524,6 @@ static void stm32_sdmmc2_pwrcycle(struct stm32_sdmmc2_priv *priv)
return;
stm32_sdmmc2_reset(priv);
- writel(SDMMC_POWER_PWRCTRL_CYCLE | priv->pwr_reg_msk,
- priv->base + SDMMC_POWER);
}
/*
@@ -619,10 +617,21 @@ static int stm32_sdmmc2_getcd(struct udevice *dev)
return 1;
}
+static int stm32_sdmmc2_host_power_cycle(struct udevice *dev)
+{
+ struct stm32_sdmmc2_priv *priv = dev_get_priv(dev);
+
+ writel(SDMMC_POWER_PWRCTRL_CYCLE | priv->pwr_reg_msk,
+ priv->base + SDMMC_POWER);
+
+ return 0;
+}
+
static const struct dm_mmc_ops stm32_sdmmc2_ops = {
.send_cmd = stm32_sdmmc2_send_cmd,
.set_ios = stm32_sdmmc2_set_ios,
.get_cd = stm32_sdmmc2_getcd,
+ .host_power_cycle = stm32_sdmmc2_host_power_cycle,
};
static int stm32_sdmmc2_probe(struct udevice *dev)
diff --git a/drivers/mmc/zynq_sdhci.c b/drivers/mmc/zynq_sdhci.c
index 3225a7ac93..529eec9c45 100644
--- a/drivers/mmc/zynq_sdhci.c
+++ b/drivers/mmc/zynq_sdhci.c
@@ -190,7 +190,7 @@ static void arasan_sdhci_set_control_reg(struct sdhci_host *host)
}
#endif
-#if defined(CONFIG_DM_MMC) && defined(CONFIG_ARCH_ZYNQMP)
+#if defined(CONFIG_ARCH_ZYNQMP)
const struct sdhci_ops arasan_ops = {
.platform_execute_tuning = &arasan_sdhci_execute_tuning,
.set_delay = &arasan_sdhci_set_tapdelay,
@@ -266,7 +266,7 @@ static int arasan_sdhci_ofdata_to_platdata(struct udevice *dev)
priv->host->name = dev->name;
-#if defined(CONFIG_DM_MMC) && defined(CONFIG_ARCH_ZYNQMP)
+#if defined(CONFIG_ARCH_ZYNQMP)
priv->host->ops = &arasan_ops;
#endif
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 2a3da068c9..30bd8e7653 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -228,6 +228,13 @@ config PHY_VITESSE
config PHY_XILINX
bool "Xilinx Ethernet PHYs support"
+config PHY_XILINX_GMII2RGMII
+ bool "Xilinx GMII to RGMII Ethernet PHYs support"
+ help
+ This adds support for Xilinx GMII to RGMII IP core. This IP acts
+ as bridge between MAC connected over GMII and external phy that
+ is connected over RGMII interface.
+
config PHY_FIXED
bool "Fixed-Link PHY"
depends on DM_ETH
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 555da83630..76b6197009 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_PHY_SMSC) += smsc.o
obj-$(CONFIG_PHY_TERANETICS) += teranetics.o
obj-$(CONFIG_PHY_TI) += ti.o
obj-$(CONFIG_PHY_XILINX) += xilinx_phy.o
+obj-$(CONFIG_PHY_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
obj-$(CONFIG_PHY_VITESSE) += vitesse.o
obj-$(CONFIG_PHY_MSCC) += mscc.o
obj-$(CONFIG_PHY_FIXED) += fixed.o
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index ae37dd6c1e..f2d17aa91a 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -458,6 +458,11 @@ static struct phy_driver genphy_driver = {
.shutdown = genphy_shutdown,
};
+int genphy_init(void)
+{
+ return phy_register(&genphy_driver);
+}
+
static LIST_HEAD(phy_drivers);
int phy_init(void)
@@ -540,6 +545,11 @@ int phy_init(void)
#ifdef CONFIG_PHY_FIXED
phy_fixed_init();
#endif
+#ifdef CONFIG_PHY_XILINX_GMII2RGMII
+ phy_xilinx_gmii2rgmii_init();
+#endif
+ genphy_init();
+
return 0;
}
@@ -911,6 +921,41 @@ void phy_connect_dev(struct phy_device *phydev, struct eth_device *dev)
debug("%s connected to %s\n", dev->name, phydev->drv->name);
}
+#ifdef CONFIG_PHY_XILINX_GMII2RGMII
+#ifdef CONFIG_DM_ETH
+static struct phy_device *phy_connect_gmii2rgmii(struct mii_dev *bus,
+ struct udevice *dev,
+ phy_interface_t interface)
+#else
+static struct phy_device *phy_connect_gmii2rgmii(struct mii_dev *bus,
+ struct eth_device *dev,
+ phy_interface_t interface)
+#endif
+{
+ struct phy_device *phydev = NULL;
+ int sn = dev_of_offset(dev);
+ int off;
+
+ while (sn > 0) {
+ off = fdt_node_offset_by_compatible(gd->fdt_blob, sn,
+ "xlnx,gmii-to-rgmii-1.0");
+ if (off > 0) {
+ phydev = phy_device_create(bus, off,
+ PHY_GMII2RGMII_ID, false,
+ interface);
+ break;
+ }
+ if (off == -FDT_ERR_NOTFOUND)
+ sn = fdt_first_subnode(gd->fdt_blob, sn);
+ else
+ printf("%s: Error finding compat string:%d\n",
+ __func__, off);
+ }
+
+ return phydev;
+}
+#endif
+
#ifdef CONFIG_PHY_FIXED
#ifdef CONFIG_DM_ETH
static struct phy_device *phy_connect_fixed(struct mii_dev *bus,
@@ -957,6 +1002,10 @@ struct phy_device *phy_connect(struct mii_dev *bus, int addr,
#ifdef CONFIG_PHY_FIXED
phydev = phy_connect_fixed(bus, dev, interface);
#endif
+#ifdef CONFIG_PHY_XILINX_GMII2RGMII
+ if (!phydev)
+ phydev = phy_connect_gmii2rgmii(bus, dev, interface);
+#endif
if (!phydev)
phydev = phy_find_by_mask(bus, mask, interface);
diff --git a/drivers/net/phy/xilinx_gmii2rgmii.c b/drivers/net/phy/xilinx_gmii2rgmii.c
new file mode 100644
index 0000000000..8c20da2682
--- /dev/null
+++ b/drivers/net/phy/xilinx_gmii2rgmii.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx GMII2RGMII phy driver
+ *
+ * Copyright (C) 2018 Xilinx, Inc.
+ */
+
+#include <dm.h>
+#include <phy.h>
+#include <config.h>
+#include <common.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define ZYNQ_GMII2RGMII_REG 0x10
+#define ZYNQ_GMII2RGMII_SPEED_MASK (BMCR_SPEED1000 | BMCR_SPEED100)
+
+static int xilinxgmiitorgmii_config(struct phy_device *phydev)
+{
+ struct phy_device *ext_phydev = phydev->priv;
+
+ debug("%s\n", __func__);
+ if (ext_phydev->drv->config)
+ ext_phydev->drv->config(ext_phydev);
+
+ return 0;
+}
+
+static int xilinxgmiitorgmii_extread(struct phy_device *phydev, int addr,
+ int devaddr, int regnum)
+{
+ struct phy_device *ext_phydev = phydev->priv;
+
+ debug("%s\n", __func__);
+ if (ext_phydev->drv->readext)
+ ext_phydev->drv->readext(ext_phydev, addr, devaddr, regnum);
+
+ return 0;
+}
+
+static int xilinxgmiitorgmii_extwrite(struct phy_device *phydev, int addr,
+ int devaddr, int regnum, u16 val)
+
+{
+ struct phy_device *ext_phydev = phydev->priv;
+
+ debug("%s\n", __func__);
+ if (ext_phydev->drv->writeext)
+ ext_phydev->drv->writeext(ext_phydev, addr, devaddr, regnum,
+ val);
+
+ return 0;
+}
+
+static int xilinxgmiitorgmii_startup(struct phy_device *phydev)
+{
+ u16 val = 0;
+ struct phy_device *ext_phydev = phydev->priv;
+
+ debug("%s\n", __func__);
+ ext_phydev->dev = phydev->dev;
+ if (ext_phydev->drv->startup)
+ ext_phydev->drv->startup(ext_phydev);
+
+ val = phy_read(phydev, phydev->addr, ZYNQ_GMII2RGMII_REG);
+ val &= ~ZYNQ_GMII2RGMII_SPEED_MASK;
+
+ if (ext_phydev->speed == SPEED_1000)
+ val |= BMCR_SPEED1000;
+ else if (ext_phydev->speed == SPEED_100)
+ val |= BMCR_SPEED100;
+
+ phy_write(phydev, phydev->addr, ZYNQ_GMII2RGMII_REG, val |
+ BMCR_FULLDPLX);
+
+ phydev->duplex = ext_phydev->duplex;
+ phydev->speed = ext_phydev->speed;
+ phydev->link = ext_phydev->link;
+
+ return 0;
+}
+
+static int xilinxgmiitorgmii_probe(struct phy_device *phydev)
+{
+ int ofnode = phydev->addr;
+ u32 phy_of_handle;
+ int ext_phyaddr = -1;
+ struct phy_device *ext_phydev;
+
+ debug("%s\n", __func__);
+
+ if (phydev->interface != PHY_INTERFACE_MODE_GMII) {
+ printf("Incorrect interface type\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Read the phy address again as the one we read in ethernet driver
+ * was overwritten for the purpose of storing the ofnode
+ */
+ phydev->addr = fdtdec_get_int(gd->fdt_blob, ofnode, "reg", -1);
+ phy_of_handle = fdtdec_lookup_phandle(gd->fdt_blob, ofnode,
+ "phy-handle");
+ if (phy_of_handle > 0)
+ ext_phyaddr = fdtdec_get_int(gd->fdt_blob,
+ phy_of_handle,
+ "reg", -1);
+ ext_phydev = phy_find_by_mask(phydev->bus,
+ 1 << ext_phyaddr,
+ PHY_INTERFACE_MODE_RGMII);
+ if (!ext_phydev) {
+ printf("%s, No external phy device found\n", __func__);
+ return -EINVAL;
+ }
+
+ ext_phydev->node = offset_to_ofnode(phy_of_handle);
+ phydev->priv = ext_phydev;
+
+ debug("%s, gmii2rgmmi:0x%x, extphy:0x%x\n", __func__, phydev->addr,
+ ext_phyaddr);
+
+ phydev->flags |= PHY_FLAG_BROKEN_RESET;
+
+ return 0;
+}
+
+static struct phy_driver gmii2rgmii_driver = {
+ .name = "XILINX GMII2RGMII",
+ .uid = PHY_GMII2RGMII_ID,
+ .mask = 0xffffffff,
+ .features = PHY_GBIT_FEATURES,
+ .probe = xilinxgmiitorgmii_probe,
+ .config = xilinxgmiitorgmii_config,
+ .startup = xilinxgmiitorgmii_startup,
+ .writeext = xilinxgmiitorgmii_extwrite,
+ .readext = xilinxgmiitorgmii_extread,
+};
+
+int phy_xilinx_gmii2rgmii_init(void)
+{
+ phy_register(&gmii2rgmii_driver);
+
+ return 0;
+}
diff --git a/drivers/net/xilinx_axi_emac.c b/drivers/net/xilinx_axi_emac.c
index 26c21c6d70..36d651109c 100644
--- a/drivers/net/xilinx_axi_emac.c
+++ b/drivers/net/xilinx_axi_emac.c
@@ -93,6 +93,7 @@ struct axidma_priv {
struct phy_device *phydev;
struct mii_dev *bus;
u8 eth_hasnobuf;
+ int phy_of_handle;
};
/* BD descriptors */
@@ -276,6 +277,8 @@ static int axiemac_phy_init(struct udevice *dev)
phydev->supported &= supported;
phydev->advertising = phydev->supported;
priv->phydev = phydev;
+ if (priv->phy_of_handle)
+ priv->phydev->node = offset_to_ofnode(priv->phy_of_handle);
phy_config(phydev);
return 0;
@@ -736,8 +739,10 @@ static int axi_emac_ofdata_to_platdata(struct udevice *dev)
priv->phyaddr = -1;
offset = fdtdec_lookup_phandle(gd->fdt_blob, node, "phy-handle");
- if (offset > 0)
+ if (offset > 0) {
priv->phyaddr = fdtdec_get_int(gd->fdt_blob, offset, "reg", -1);
+ priv->phy_of_handle = offset;
+ }
phy_mode = fdt_getprop(gd->fdt_blob, node, "phy-mode", NULL);
if (phy_mode)
diff --git a/drivers/net/zynq_gem.c b/drivers/net/zynq_gem.c
index 033efb8195..a7a6ce987f 100644
--- a/drivers/net/zynq_gem.c
+++ b/drivers/net/zynq_gem.c
@@ -26,8 +26,6 @@
#include <asm/arch/sys_proto.h>
#include <linux/errno.h>
-DECLARE_GLOBAL_DATA_PTR;
-
/* Bit/mask specification */
#define ZYNQ_GEM_PHYMNTNC_OP_MASK 0x40020000 /* operation mask bits */
#define ZYNQ_GEM_PHYMNTNC_OP_R_MASK 0x20000000 /* read operation */
@@ -465,7 +463,6 @@ static int zynq_gem_init(struct udevice *dev)
break;
}
-#if !defined(CONFIG_ARCH_VERSAL)
ret = clk_set_rate(&priv->clk, clk_rate);
if (IS_ERR_VALUE(ret) && ret != (unsigned long)-ENOSYS) {
dev_err(dev, "failed to set tx clock rate\n");
@@ -477,9 +474,6 @@ static int zynq_gem_init(struct udevice *dev)
dev_err(dev, "failed to enable tx clock\n");
return ret;
}
-#else
- debug("requested clk_rate %ld\n", clk_rate);
-#endif
setbits_le32(&regs->nwctrl, ZYNQ_GEM_NWCTRL_RXEN_MASK |
ZYNQ_GEM_NWCTRL_TXEN_MASK);
@@ -753,6 +747,7 @@ static int zynq_gem_ofdata_to_platdata(struct udevice *dev)
}
static const struct udevice_id zynq_gem_ids[] = {
+ { .compatible = "cdns,versal-gem" },
{ .compatible = "cdns,zynqmp-gem" },
{ .compatible = "cdns,zynq-gem" },
{ .compatible = "cdns,gem" },
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index bdfc0c1796..19e7b50046 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -151,4 +151,12 @@ config PCI_KEYSTONE
help
Say Y here if you want to enable PCI controller support on AM654 SoC.
+config PCIE_MEDIATEK
+ bool "MediaTek PCIe Gen2 controller"
+ depends on DM_PCI
+ depends on ARCH_MEDIATEK
+ help
+ Say Y here if you want to enable Gen2 PCIe controller,
+ which could be found on MT7623 SoC family.
+
endif
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index e54a98b8c9..b1d3dc8610 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -39,3 +39,4 @@ obj-$(CONFIG_PCIE_LAYERSCAPE_GEN4) += pcie_layerscape_gen4.o \
obj-$(CONFIG_PCI_XILINX) += pcie_xilinx.o
obj-$(CONFIG_PCIE_INTEL_FPGA) += pcie_intel_fpga.o
obj-$(CONFIG_PCI_KEYSTONE) += pcie_dw_ti.o
+obj-$(CONFIG_PCIE_MEDIATEK) += pcie_mediatek.o
diff --git a/drivers/pci/pci-emul-uclass.c b/drivers/pci/pci-emul-uclass.c
index 0dcf937d9a..0f63e491a7 100644
--- a/drivers/pci/pci-emul-uclass.c
+++ b/drivers/pci/pci-emul-uclass.c
@@ -18,6 +18,7 @@ struct sandbox_pci_emul_priv {
int sandbox_pci_get_emul(struct udevice *bus, pci_dev_t find_devfn,
struct udevice **containerp, struct udevice **emulp)
{
+ struct pci_emul_uc_priv *upriv;
struct udevice *dev;
int ret;
@@ -30,16 +31,32 @@ int sandbox_pci_get_emul(struct udevice *bus, pci_dev_t find_devfn,
}
*containerp = dev;
- /*
- * See commit 4345998ae9df,
- * "pci: sandbox: Support dynamically binding device driver"
- */
ret = uclass_get_device_by_phandle(UCLASS_PCI_EMUL, dev, "sandbox,emul",
emulp);
- if (ret && device_get_uclass_id(dev) != UCLASS_PCI_GENERIC)
+ if (!ret) {
+ upriv = dev_get_uclass_priv(*emulp);
+
+ upriv->client = dev;
+ } else if (device_get_uclass_id(dev) != UCLASS_PCI_GENERIC) {
+ /*
+ * See commit 4345998ae9df,
+ * "pci: sandbox: Support dynamically binding device driver"
+ */
*emulp = dev;
+ }
+
+ return 0;
+}
- return *emulp ? 0 : -ENODEV;
+int sandbox_pci_get_client(struct udevice *emul, struct udevice **devp)
+{
+ struct pci_emul_uc_priv *upriv = dev_get_uclass_priv(emul);
+
+ if (!upriv->client)
+ return -ENOENT;
+ *devp = upriv->client;
+
+ return 0;
}
uint sandbox_pci_read_bar(u32 barval, int type, uint size)
@@ -88,6 +105,7 @@ UCLASS_DRIVER(pci_emul) = {
.post_probe = sandbox_pci_emul_post_probe,
.pre_remove = sandbox_pci_emul_pre_remove,
.priv_auto_alloc_size = sizeof(struct sandbox_pci_emul_priv),
+ .per_device_auto_alloc_size = sizeof(struct pci_emul_uc_priv),
};
/*
diff --git a/drivers/pci/pcie_mediatek.c b/drivers/pci/pcie_mediatek.c
new file mode 100644
index 0000000000..a0dcb258b0
--- /dev/null
+++ b/drivers/pci/pcie_mediatek.c
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MediaTek PCIe host controller driver.
+ *
+ * Copyright (c) 2017-2019 MediaTek Inc.
+ * Author: Ryder Lee <ryder.lee@mediatek.com>
+ * Honghui Zhang <honghui.zhang@mediatek.com>
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <generic-phy.h>
+#include <pci.h>
+#include <reset.h>
+#include <asm/io.h>
+#include <linux/iopoll.h>
+#include <linux/list.h>
+
+/* PCIe shared registers */
+#define PCIE_SYS_CFG 0x00
+#define PCIE_INT_ENABLE 0x0c
+#define PCIE_CFG_ADDR 0x20
+#define PCIE_CFG_DATA 0x24
+
+/* PCIe per port registers */
+#define PCIE_BAR0_SETUP 0x10
+#define PCIE_CLASS 0x34
+#define PCIE_LINK_STATUS 0x50
+
+#define PCIE_PORT_INT_EN(x) BIT(20 + (x))
+#define PCIE_PORT_PERST(x) BIT(1 + (x))
+#define PCIE_PORT_LINKUP BIT(0)
+#define PCIE_BAR_MAP_MAX GENMASK(31, 16)
+
+#define PCIE_BAR_ENABLE BIT(0)
+#define PCIE_REVISION_ID BIT(0)
+#define PCIE_CLASS_CODE (0x60400 << 8)
+#define PCIE_CONF_REG(regn) (((regn) & GENMASK(7, 2)) | \
+ ((((regn) >> 8) & GENMASK(3, 0)) << 24))
+#define PCIE_CONF_ADDR(regn, bdf) \
+ (PCIE_CONF_REG(regn) | (bdf))
+
+/* MediaTek specific configuration registers */
+#define PCIE_FTS_NUM 0x70c
+#define PCIE_FTS_NUM_MASK GENMASK(15, 8)
+#define PCIE_FTS_NUM_L0(x) ((x) & 0xff << 8)
+
+#define PCIE_FC_CREDIT 0x73c
+#define PCIE_FC_CREDIT_MASK (GENMASK(31, 31) | GENMASK(28, 16))
+#define PCIE_FC_CREDIT_VAL(x) ((x) << 16)
+
+struct mtk_pcie_port {
+ void __iomem *base;
+ struct list_head list;
+ struct mtk_pcie *pcie;
+ struct reset_ctl reset;
+ struct clk sys_ck;
+ struct phy phy;
+ u32 slot;
+};
+
+struct mtk_pcie {
+ void __iomem *base;
+ struct clk free_ck;
+ struct list_head ports;
+};
+
+static int mtk_pcie_config_address(struct udevice *udev, pci_dev_t bdf,
+ uint offset, void **paddress)
+{
+ struct mtk_pcie *pcie = dev_get_priv(udev);
+
+ writel(PCIE_CONF_ADDR(offset, bdf), pcie->base + PCIE_CFG_ADDR);
+ *paddress = pcie->base + PCIE_CFG_DATA + (offset & 3);
+
+ return 0;
+}
+
+static int mtk_pcie_read_config(struct udevice *bus, pci_dev_t bdf,
+ uint offset, ulong *valuep,
+ enum pci_size_t size)
+{
+ return pci_generic_mmap_read_config(bus, mtk_pcie_config_address,
+ bdf, offset, valuep, size);
+}
+
+static int mtk_pcie_write_config(struct udevice *bus, pci_dev_t bdf,
+ uint offset, ulong value,
+ enum pci_size_t size)
+{
+ return pci_generic_mmap_write_config(bus, mtk_pcie_config_address,
+ bdf, offset, value, size);
+}
+
+static const struct dm_pci_ops mtk_pcie_ops = {
+ .read_config = mtk_pcie_read_config,
+ .write_config = mtk_pcie_write_config,
+};
+
+static void mtk_pcie_port_free(struct mtk_pcie_port *port)
+{
+ list_del(&port->list);
+ free(port);
+}
+
+static int mtk_pcie_startup_port(struct mtk_pcie_port *port)
+{
+ struct mtk_pcie *pcie = port->pcie;
+ u32 slot = PCI_DEV(port->slot << 11);
+ u32 val;
+ int err;
+
+ /* assert port PERST_N */
+ setbits_le32(pcie->base + PCIE_SYS_CFG, PCIE_PORT_PERST(port->slot));
+ /* de-assert port PERST_N */
+ clrbits_le32(pcie->base + PCIE_SYS_CFG, PCIE_PORT_PERST(port->slot));
+
+ /* 100ms timeout value should be enough for Gen1/2 training */
+ err = readl_poll_timeout(port->base + PCIE_LINK_STATUS, val,
+ !!(val & PCIE_PORT_LINKUP), 100000);
+ if (err)
+ return -ETIMEDOUT;
+
+ /* disable interrupt */
+ clrbits_le32(pcie->base + PCIE_INT_ENABLE,
+ PCIE_PORT_INT_EN(port->slot));
+
+ /* map to all DDR region. We need to set it before cfg operation. */
+ writel(PCIE_BAR_MAP_MAX | PCIE_BAR_ENABLE,
+ port->base + PCIE_BAR0_SETUP);
+
+ /* configure class code and revision ID */
+ writel(PCIE_CLASS_CODE | PCIE_REVISION_ID, port->base + PCIE_CLASS);
+
+ /* configure FC credit */
+ writel(PCIE_CONF_ADDR(PCIE_FC_CREDIT, slot),
+ pcie->base + PCIE_CFG_ADDR);
+ clrsetbits_le32(pcie->base + PCIE_CFG_DATA, PCIE_FC_CREDIT_MASK,
+ PCIE_FC_CREDIT_VAL(0x806c));
+
+ /* configure RC FTS number to 250 when it leaves L0s */
+ writel(PCIE_CONF_ADDR(PCIE_FTS_NUM, slot), pcie->base + PCIE_CFG_ADDR);
+ clrsetbits_le32(pcie->base + PCIE_CFG_DATA, PCIE_FTS_NUM_MASK,
+ PCIE_FTS_NUM_L0(0x50));
+
+ return 0;
+}
+
+static void mtk_pcie_enable_port(struct mtk_pcie_port *port)
+{
+ int err;
+
+ err = clk_enable(&port->sys_ck);
+ if (err)
+ goto exit;
+
+ err = reset_assert(&port->reset);
+ if (err)
+ goto exit;
+
+ err = reset_deassert(&port->reset);
+ if (err)
+ goto exit;
+
+ err = generic_phy_init(&port->phy);
+ if (err)
+ goto exit;
+
+ err = generic_phy_power_on(&port->phy);
+ if (err)
+ goto exit;
+
+ if (!mtk_pcie_startup_port(port))
+ return;
+
+ pr_err("Port%d link down\n", port->slot);
+exit:
+ mtk_pcie_port_free(port);
+}
+
+static int mtk_pcie_parse_port(struct udevice *dev, u32 slot)
+{
+ struct mtk_pcie *pcie = dev_get_priv(dev);
+ struct mtk_pcie_port *port;
+ char name[10];
+ int err;
+
+ port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ snprintf(name, sizeof(name), "port%d", slot);
+ port->base = dev_remap_addr_name(dev, name);
+ if (!port->base)
+ return -ENOENT;
+
+ snprintf(name, sizeof(name), "sys_ck%d", slot);
+ err = clk_get_by_name(dev, name, &port->sys_ck);
+ if (err)
+ return err;
+
+ err = reset_get_by_index(dev, slot, &port->reset);
+ if (err)
+ return err;
+
+ err = generic_phy_get_by_index(dev, slot, &port->phy);
+ if (err)
+ return err;
+
+ port->slot = slot;
+ port->pcie = pcie;
+
+ INIT_LIST_HEAD(&port->list);
+ list_add_tail(&port->list, &pcie->ports);
+
+ return 0;
+}
+
+static int mtk_pcie_probe(struct udevice *dev)
+{
+ struct mtk_pcie *pcie = dev_get_priv(dev);
+ struct mtk_pcie_port *port, *tmp;
+ ofnode subnode;
+ int err;
+
+ INIT_LIST_HEAD(&pcie->ports);
+
+ pcie->base = dev_remap_addr_name(dev, "subsys");
+ if (!pcie->base)
+ return -ENOENT;
+
+ err = clk_get_by_name(dev, "free_ck", &pcie->free_ck);
+ if (err)
+ return err;
+
+ /* enable top level clock */
+ err = clk_enable(&pcie->free_ck);
+ if (err)
+ return err;
+
+ dev_for_each_subnode(subnode, dev) {
+ struct fdt_pci_addr addr;
+ u32 slot = 0;
+
+ if (!ofnode_is_available(subnode))
+ continue;
+
+ err = ofnode_read_pci_addr(subnode, 0, "reg", &addr);
+ if (err)
+ return err;
+
+ slot = PCI_DEV(addr.phys_hi);
+
+ err = mtk_pcie_parse_port(dev, slot);
+ if (err)
+ return err;
+ }
+
+ /* enable each port, and then check link status */
+ list_for_each_entry_safe(port, tmp, &pcie->ports, list)
+ mtk_pcie_enable_port(port);
+
+ return 0;
+}
+
+static const struct udevice_id mtk_pcie_ids[] = {
+ { .compatible = "mediatek,mt7623-pcie", },
+ { }
+};
+
+U_BOOT_DRIVER(pcie_mediatek) = {
+ .name = "pcie_mediatek",
+ .id = UCLASS_PCI,
+ .of_match = mtk_pcie_ids,
+ .ops = &mtk_pcie_ops,
+ .probe = mtk_pcie_probe,
+ .priv_auto_alloc_size = sizeof(struct mtk_pcie),
+};
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 02312273e2..e317373a5c 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -205,4 +205,15 @@ config MT76X8_USB_PHY
This PHY is found on MT76x8 devices supporting USB.
+config PHY_MTK_TPHY
+ bool "MediaTek T-PHY Driver"
+ depends on PHY
+ depends on ARCH_MEDIATEK
+ help
+ MediaTek T-PHY driver supports usb2.0, usb3.0 ports, PCIe and
+ SATA, and meanwhile supports two version T-PHY which have
+ different banks layout, the T-PHY with shared banks between
+ multi-ports is first version, otherwise is second veriosn,
+ so you can easily distinguish them by banks layout.
+
endmenu
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 3157f1b7ee..43ce62e08c 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -23,3 +23,4 @@ obj-$(CONFIG_OMAP_USB2_PHY) += omap-usb2-phy.o
obj-$(CONFIG_KEYSTONE_USB_PHY) += keystone-usb-phy.o
obj-$(CONFIG_MT76X8_USB_PHY) += mt76x8-usb-phy.o
obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o
+obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
diff --git a/drivers/phy/phy-mtk-tphy.c b/drivers/phy/phy-mtk-tphy.c
new file mode 100644
index 0000000000..3701481256
--- /dev/null
+++ b/drivers/phy/phy-mtk-tphy.c
@@ -0,0 +1,362 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015 - 2019 MediaTek Inc.
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ * Ryder Lee <ryder.lee@mediatek.com>
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <generic-phy.h>
+#include <mapmem.h>
+#include <asm/io.h>
+
+#include <dt-bindings/phy/phy.h>
+
+/* version V1 sub-banks offset base address */
+/* banks shared by multiple phys */
+#define SSUSB_SIFSLV_V1_SPLLC 0x000 /* shared by u3 phys */
+#define SSUSB_SIFSLV_V1_CHIP 0x300 /* shared by u3 phys */
+/* u3/pcie/sata phy banks */
+#define SSUSB_SIFSLV_V1_U3PHYD 0x000
+#define SSUSB_SIFSLV_V1_U3PHYA 0x200
+
+#define U3P_U3_CHIP_GPIO_CTLD 0x0c
+#define P3C_REG_IP_SW_RST BIT(31)
+#define P3C_MCU_BUS_CK_GATE_EN BIT(30)
+#define P3C_FORCE_IP_SW_RST BIT(29)
+
+#define U3P_U3_CHIP_GPIO_CTLE 0x10
+#define P3C_RG_SWRST_U3_PHYD BIT(25)
+#define P3C_RG_SWRST_U3_PHYD_FORCE_EN BIT(24)
+
+#define U3P_U3_PHYA_REG0 0x000
+#define P3A_RG_CLKDRV_OFF GENMASK(3, 2)
+#define P3A_RG_CLKDRV_OFF_VAL(x) ((0x3 & (x)) << 2)
+
+#define U3P_U3_PHYA_REG1 0x004
+#define P3A_RG_CLKDRV_AMP GENMASK(31, 29)
+#define P3A_RG_CLKDRV_AMP_VAL(x) ((0x7 & (x)) << 29)
+
+#define U3P_U3_PHYA_DA_REG0 0x100
+#define P3A_RG_XTAL_EXT_PE2H GENMASK(17, 16)
+#define P3A_RG_XTAL_EXT_PE2H_VAL(x) ((0x3 & (x)) << 16)
+#define P3A_RG_XTAL_EXT_PE1H GENMASK(13, 12)
+#define P3A_RG_XTAL_EXT_PE1H_VAL(x) ((0x3 & (x)) << 12)
+#define P3A_RG_XTAL_EXT_EN_U3 GENMASK(11, 10)
+#define P3A_RG_XTAL_EXT_EN_U3_VAL(x) ((0x3 & (x)) << 10)
+
+#define U3P_U3_PHYA_DA_REG4 0x108
+#define P3A_RG_PLL_DIVEN_PE2H GENMASK(21, 19)
+#define P3A_RG_PLL_BC_PE2H GENMASK(7, 6)
+#define P3A_RG_PLL_BC_PE2H_VAL(x) ((0x3 & (x)) << 6)
+
+#define U3P_U3_PHYA_DA_REG5 0x10c
+#define P3A_RG_PLL_BR_PE2H GENMASK(29, 28)
+#define P3A_RG_PLL_BR_PE2H_VAL(x) ((0x3 & (x)) << 28)
+#define P3A_RG_PLL_IC_PE2H GENMASK(15, 12)
+#define P3A_RG_PLL_IC_PE2H_VAL(x) ((0xf & (x)) << 12)
+
+#define U3P_U3_PHYA_DA_REG6 0x110
+#define P3A_RG_PLL_IR_PE2H GENMASK(19, 16)
+#define P3A_RG_PLL_IR_PE2H_VAL(x) ((0xf & (x)) << 16)
+
+#define U3P_U3_PHYA_DA_REG7 0x114
+#define P3A_RG_PLL_BP_PE2H GENMASK(19, 16)
+#define P3A_RG_PLL_BP_PE2H_VAL(x) ((0xf & (x)) << 16)
+
+#define U3P_U3_PHYA_DA_REG20 0x13c
+#define P3A_RG_PLL_DELTA1_PE2H GENMASK(31, 16)
+#define P3A_RG_PLL_DELTA1_PE2H_VAL(x) ((0xffff & (x)) << 16)
+
+#define U3P_U3_PHYA_DA_REG25 0x148
+#define P3A_RG_PLL_DELTA_PE2H GENMASK(15, 0)
+#define P3A_RG_PLL_DELTA_PE2H_VAL(x) (0xffff & (x))
+
+#define U3P_U3_PHYD_RXDET1 0x128
+#define P3D_RG_RXDET_STB2_SET GENMASK(17, 9)
+#define P3D_RG_RXDET_STB2_SET_VAL(x) ((0x1ff & (x)) << 9)
+
+#define U3P_U3_PHYD_RXDET2 0x12c
+#define P3D_RG_RXDET_STB2_SET_P3 GENMASK(8, 0)
+#define P3D_RG_RXDET_STB2_SET_P3_VAL(x) (0x1ff & (x))
+
+struct u3phy_banks {
+ void __iomem *spllc;
+ void __iomem *chip;
+ void __iomem *phyd; /* include u3phyd_bank2 */
+ void __iomem *phya; /* include u3phya_da */
+};
+
+struct mtk_phy_instance {
+ void __iomem *port_base;
+ const struct device_node *np;
+
+ struct u3phy_banks u3_banks;
+
+ /* reference clock of anolog phy */
+ struct clk ref_clk;
+ u32 index;
+ u8 type;
+};
+
+struct mtk_tphy {
+ void __iomem *sif_base;
+ struct mtk_phy_instance **phys;
+ int nphys;
+};
+
+static void pcie_phy_instance_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+
+ clrsetbits_le32(u3_banks->phya + U3P_U3_PHYA_DA_REG0,
+ P3A_RG_XTAL_EXT_PE1H | P3A_RG_XTAL_EXT_PE2H,
+ P3A_RG_XTAL_EXT_PE1H_VAL(0x2) |
+ P3A_RG_XTAL_EXT_PE2H_VAL(0x2));
+
+ /* ref clk drive */
+ clrsetbits_le32(u3_banks->phya + U3P_U3_PHYA_REG1, P3A_RG_CLKDRV_AMP,
+ P3A_RG_CLKDRV_AMP_VAL(0x4));
+ clrsetbits_le32(u3_banks->phya + U3P_U3_PHYA_REG0, P3A_RG_CLKDRV_OFF,
+ P3A_RG_CLKDRV_OFF_VAL(0x1));
+
+ /* SSC delta -5000ppm */
+ clrsetbits_le32(u3_banks->phya + U3P_U3_PHYA_DA_REG20,
+ P3A_RG_PLL_DELTA1_PE2H,
+ P3A_RG_PLL_DELTA1_PE2H_VAL(0x3c));
+
+ clrsetbits_le32(u3_banks->phya + U3P_U3_PHYA_DA_REG25,
+ P3A_RG_PLL_DELTA_PE2H,
+ P3A_RG_PLL_DELTA_PE2H_VAL(0x36));
+
+ /* change pll BW 0.6M */
+ clrsetbits_le32(u3_banks->phya + U3P_U3_PHYA_DA_REG5,
+ P3A_RG_PLL_BR_PE2H | P3A_RG_PLL_IC_PE2H,
+ P3A_RG_PLL_BR_PE2H_VAL(0x1) |
+ P3A_RG_PLL_IC_PE2H_VAL(0x1));
+ clrsetbits_le32(u3_banks->phya + U3P_U3_PHYA_DA_REG4,
+ P3A_RG_PLL_DIVEN_PE2H | P3A_RG_PLL_BC_PE2H,
+ P3A_RG_PLL_BC_PE2H_VAL(0x3));
+
+ clrsetbits_le32(u3_banks->phya + U3P_U3_PHYA_DA_REG6,
+ P3A_RG_PLL_IR_PE2H, P3A_RG_PLL_IR_PE2H_VAL(0x2));
+ clrsetbits_le32(u3_banks->phya + U3P_U3_PHYA_DA_REG7,
+ P3A_RG_PLL_BP_PE2H, P3A_RG_PLL_BP_PE2H_VAL(0xa));
+
+ /* Tx Detect Rx Timing: 10us -> 5us */
+ clrsetbits_le32(u3_banks->phyd + U3P_U3_PHYD_RXDET1,
+ P3D_RG_RXDET_STB2_SET,
+ P3D_RG_RXDET_STB2_SET_VAL(0x10));
+ clrsetbits_le32(u3_banks->phyd + U3P_U3_PHYD_RXDET2,
+ P3D_RG_RXDET_STB2_SET_P3,
+ P3D_RG_RXDET_STB2_SET_P3_VAL(0x10));
+
+ /* wait for PCIe subsys register to active */
+ udelay(3000);
+}
+
+static void pcie_phy_instance_power_on(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u3phy_banks *bank = &instance->u3_banks;
+
+ clrbits_le32(bank->chip + U3P_U3_CHIP_GPIO_CTLD,
+ P3C_FORCE_IP_SW_RST | P3C_REG_IP_SW_RST);
+ clrbits_le32(bank->chip + U3P_U3_CHIP_GPIO_CTLE,
+ P3C_RG_SWRST_U3_PHYD_FORCE_EN | P3C_RG_SWRST_U3_PHYD);
+}
+
+static void pcie_phy_instance_power_off(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+
+{
+ struct u3phy_banks *bank = &instance->u3_banks;
+
+ setbits_le32(bank->chip + U3P_U3_CHIP_GPIO_CTLD,
+ P3C_FORCE_IP_SW_RST | P3C_REG_IP_SW_RST);
+ setbits_le32(bank->chip + U3P_U3_CHIP_GPIO_CTLE,
+ P3C_RG_SWRST_U3_PHYD_FORCE_EN | P3C_RG_SWRST_U3_PHYD);
+}
+
+static void phy_v1_banks_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+
+ switch (instance->type) {
+ case PHY_TYPE_PCIE:
+ u3_banks->spllc = tphy->sif_base + SSUSB_SIFSLV_V1_SPLLC;
+ u3_banks->chip = tphy->sif_base + SSUSB_SIFSLV_V1_CHIP;
+ u3_banks->phyd = instance->port_base + SSUSB_SIFSLV_V1_U3PHYD;
+ u3_banks->phya = instance->port_base + SSUSB_SIFSLV_V1_U3PHYA;
+ break;
+ default:
+ return;
+ }
+}
+
+static int mtk_phy_init(struct phy *phy)
+{
+ struct mtk_tphy *tphy = dev_get_priv(phy->dev);
+ struct mtk_phy_instance *instance = tphy->phys[phy->id];
+ int ret;
+
+ /* we may use a fixed-clock here */
+ ret = clk_enable(&instance->ref_clk);
+ if (ret && ret != -ENOSYS)
+ return ret;
+
+ switch (instance->type) {
+ case PHY_TYPE_PCIE:
+ pcie_phy_instance_init(tphy, instance);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mtk_phy_power_on(struct phy *phy)
+{
+ struct mtk_tphy *tphy = dev_get_priv(phy->dev);
+ struct mtk_phy_instance *instance = tphy->phys[phy->id];
+
+ pcie_phy_instance_power_on(tphy, instance);
+
+ return 0;
+}
+
+static int mtk_phy_power_off(struct phy *phy)
+{
+ struct mtk_tphy *tphy = dev_get_priv(phy->dev);
+ struct mtk_phy_instance *instance = tphy->phys[phy->id];
+
+ pcie_phy_instance_power_off(tphy, instance);
+
+ return 0;
+}
+
+static int mtk_phy_exit(struct phy *phy)
+{
+ struct mtk_tphy *tphy = dev_get_priv(phy->dev);
+ struct mtk_phy_instance *instance = tphy->phys[phy->id];
+
+ clk_disable(&instance->ref_clk);
+
+ return 0;
+}
+
+static int mtk_phy_xlate(struct phy *phy,
+ struct ofnode_phandle_args *args)
+{
+ struct mtk_tphy *tphy = dev_get_priv(phy->dev);
+ struct mtk_phy_instance *instance = NULL;
+ const struct device_node *phy_np = ofnode_to_np(args->node);
+ u32 index;
+
+ if (!phy_np) {
+ dev_err(phy->dev, "null pointer phy node\n");
+ return -EINVAL;
+ }
+
+ if (args->args_count < 1) {
+ dev_err(phy->dev, "invalid number of cells in 'phy' property\n");
+ return -EINVAL;
+ }
+
+ for (index = 0; index < tphy->nphys; index++)
+ if (phy_np == tphy->phys[index]->np) {
+ instance = tphy->phys[index];
+ break;
+ }
+
+ if (!instance) {
+ dev_err(phy->dev, "failed to find appropriate phy\n");
+ return -EINVAL;
+ }
+
+ phy->id = index;
+ instance->type = args->args[1];
+ if (!(instance->type == PHY_TYPE_USB2 ||
+ instance->type == PHY_TYPE_USB3 ||
+ instance->type == PHY_TYPE_PCIE ||
+ instance->type == PHY_TYPE_SATA)) {
+ dev_err(phy->dev, "unsupported device type\n");
+ return -EINVAL;
+ }
+
+ phy_v1_banks_init(tphy, instance);
+
+ return 0;
+}
+
+static const struct phy_ops mtk_tphy_ops = {
+ .init = mtk_phy_init,
+ .exit = mtk_phy_exit,
+ .power_on = mtk_phy_power_on,
+ .power_off = mtk_phy_power_off,
+ .of_xlate = mtk_phy_xlate,
+};
+
+static int mtk_tphy_probe(struct udevice *dev)
+{
+ struct mtk_tphy *tphy = dev_get_priv(dev);
+ ofnode subnode;
+ int index = 0;
+
+ dev_for_each_subnode(subnode, dev)
+ tphy->nphys++;
+
+ tphy->phys = devm_kcalloc(dev, tphy->nphys, sizeof(*tphy->phys),
+ GFP_KERNEL);
+ if (!tphy->phys)
+ return -ENOMEM;
+
+ tphy->sif_base = dev_read_addr_ptr(dev);
+ if (!tphy->sif_base)
+ return -ENOENT;
+
+ dev_for_each_subnode(subnode, dev) {
+ struct mtk_phy_instance *instance;
+ fdt_addr_t addr;
+ int err;
+
+ instance = devm_kzalloc(dev, sizeof(*instance), GFP_KERNEL);
+ if (!instance)
+ return -ENOMEM;
+
+ addr = ofnode_get_addr(subnode);
+ if (addr == FDT_ADDR_T_NONE)
+ return -ENOMEM;
+
+ instance->port_base = map_sysmem(addr, 0);
+ instance->index = index;
+ instance->np = ofnode_to_np(subnode);
+ tphy->phys[index] = instance;
+ index++;
+
+ err = clk_get_by_index_nodev(subnode, 0, &instance->ref_clk);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct udevice_id mtk_tphy_id_table[] = {
+ { .compatible = "mediatek,generic-tphy-v1", },
+ { }
+};
+
+U_BOOT_DRIVER(mtk_tphy) = {
+ .name = "mtk-tphy",
+ .id = UCLASS_PHY,
+ .of_match = mtk_tphy_id_table,
+ .ops = &mtk_tphy_ops,
+ .probe = mtk_tphy_probe,
+ .priv_auto_alloc_size = sizeof(struct mtk_tphy),
+};
diff --git a/drivers/power/regulator/regulator_common.c b/drivers/power/regulator/regulator_common.c
index 3dabbe2a85..2041086567 100644
--- a/drivers/power/regulator/regulator_common.c
+++ b/drivers/power/regulator/regulator_common.c
@@ -12,10 +12,15 @@ int regulator_common_ofdata_to_platdata(struct udevice *dev,
struct regulator_common_platdata *dev_pdata, const char *enable_gpio_name)
{
struct gpio_desc *gpio;
+ struct dm_regulator_uclass_platdata *uc_pdata;
int flags = GPIOD_IS_OUT;
int ret;
- if (dev_read_bool(dev, "enable-active-high"))
+ uc_pdata = dev_get_uclass_platdata(dev);
+
+ if (!dev_read_bool(dev, "enable-active-high"))
+ flags |= GPIOD_ACTIVE_LOW;
+ if (uc_pdata->boot_on)
flags |= GPIOD_IS_OUT_ACTIVE;
/* Get optional enable GPIO desc */
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index f54a245424..7c2e4804b5 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -52,6 +52,26 @@ config REMOTEPROC_TI_K3_ARM64
on various TI K3 family of SoCs through the remote processor
framework.
+config REMOTEPROC_TI_K3_DSP
+ bool "TI K3 C66 and C71 remoteproc support"
+ select REMOTEPROC
+ depends on ARCH_K3
+ depends on TI_SCI_PROTOCOL
+ help
+ Say y here to support TI's C66/C71 remote processor subsystems
+ on various TI K3 family of SoCs through the remote processor
+ framework.
+
+config REMOTEPROC_TI_K3_R5F
+ bool "TI K3 R5F remoteproc support"
+ select REMOTEPROC
+ depends on ARCH_K3
+ depends on TI_SCI_PROTOCOL
+ help
+ Say y here to support TI's R5F remote processor subsystems
+ on various TI K3 family of SoCs through the remote processor
+ framework.
+
config REMOTEPROC_TI_POWER
bool "Support for TI Power processor"
select REMOTEPROC
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 271ba55b09..69ae7bd1e8 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -11,4 +11,6 @@ obj-$(CONFIG_K3_SYSTEM_CONTROLLER) += k3_system_controller.o
obj-$(CONFIG_REMOTEPROC_SANDBOX) += sandbox_testproc.o
obj-$(CONFIG_REMOTEPROC_STM32_COPRO) += stm32_copro.o
obj-$(CONFIG_REMOTEPROC_TI_K3_ARM64) += ti_k3_arm64_rproc.o
+obj-$(CONFIG_REMOTEPROC_TI_K3_DSP) += ti_k3_dsp_rproc.o
+obj-$(CONFIG_REMOTEPROC_TI_K3_R5F) += ti_k3_r5f_rproc.o
obj-$(CONFIG_REMOTEPROC_TI_POWER) += ti_power_proc.o
diff --git a/drivers/remoteproc/rproc-elf-loader.c b/drivers/remoteproc/rproc-elf-loader.c
index 67937a7595..b38a226065 100644
--- a/drivers/remoteproc/rproc-elf-loader.c
+++ b/drivers/remoteproc/rproc-elf-loader.c
@@ -64,13 +64,90 @@ int rproc_elf32_sanity_check(ulong addr, ulong size)
return 0;
}
-/* A very simple elf loader, assumes the image is valid */
-int rproc_elf32_load_image(struct udevice *dev, unsigned long addr)
+/* Basic function to verify ELF64 image format */
+int rproc_elf64_sanity_check(ulong addr, ulong size)
+{
+ Elf64_Ehdr *ehdr = (Elf64_Ehdr *)addr;
+ char class;
+
+ if (!addr) {
+ pr_debug("Invalid fw address?\n");
+ return -EFAULT;
+ }
+
+ if (size < sizeof(Elf64_Ehdr)) {
+ pr_debug("Image is too small\n");
+ return -ENOSPC;
+ }
+
+ class = ehdr->e_ident[EI_CLASS];
+
+ if (!IS_ELF(*ehdr) || ehdr->e_type != ET_EXEC || class != ELFCLASS64) {
+ pr_debug("Not an executable ELF64 image\n");
+ return -EPROTONOSUPPORT;
+ }
+
+ /* We assume the firmware has the same endianness as the host */
+# ifdef __LITTLE_ENDIAN
+ if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) {
+# else /* BIG ENDIAN */
+ if (ehdr->e_ident[EI_DATA] != ELFDATA2MSB) {
+# endif
+ pr_debug("Unsupported firmware endianness\n");
+ return -EILSEQ;
+ }
+
+ if (size < ehdr->e_shoff + sizeof(Elf64_Shdr)) {
+ pr_debug("Image is too small\n");
+ return -ENOSPC;
+ }
+
+ if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
+ pr_debug("Image is corrupted (bad magic)\n");
+ return -EBADF;
+ }
+
+ if (ehdr->e_phnum == 0) {
+ pr_debug("No loadable segments\n");
+ return -ENOEXEC;
+ }
+
+ if (ehdr->e_phoff > size) {
+ pr_debug("Firmware size is too small\n");
+ return -ENOSPC;
+ }
+
+ return 0;
+}
+
+/* Basic function to verify ELF image format */
+int rproc_elf_sanity_check(ulong addr, ulong size)
+{
+ Elf32_Ehdr *ehdr = (Elf32_Ehdr *)addr;
+
+ if (!addr) {
+ dev_err(dev, "Invalid firmware address\n");
+ return -EFAULT;
+ }
+
+ if (ehdr->e_ident[EI_CLASS] == ELFCLASS64)
+ return rproc_elf64_sanity_check(addr, size);
+ else
+ return rproc_elf32_sanity_check(addr, size);
+}
+
+int rproc_elf32_load_image(struct udevice *dev, unsigned long addr, ulong size)
{
Elf32_Ehdr *ehdr; /* Elf header structure pointer */
Elf32_Phdr *phdr; /* Program header structure pointer */
const struct dm_rproc_ops *ops;
- unsigned int i;
+ unsigned int i, ret;
+
+ ret = rproc_elf32_sanity_check(addr, size);
+ if (ret) {
+ dev_err(dev, "Invalid ELF32 Image %d\n", ret);
+ return ret;
+ }
ehdr = (Elf32_Ehdr *)addr;
phdr = (Elf32_Phdr *)(addr + ehdr->e_phoff);
@@ -86,7 +163,8 @@ int rproc_elf32_load_image(struct udevice *dev, unsigned long addr)
continue;
if (ops->device_to_virt)
- dst = ops->device_to_virt(dev, (ulong)dst);
+ dst = ops->device_to_virt(dev, (ulong)dst,
+ phdr->p_memsz);
dev_dbg(dev, "Loading phdr %i to 0x%p (%i bytes)\n",
i, dst, phdr->p_filesz);
@@ -104,3 +182,96 @@ int rproc_elf32_load_image(struct udevice *dev, unsigned long addr)
return 0;
}
+
+int rproc_elf64_load_image(struct udevice *dev, ulong addr, ulong size)
+{
+ const struct dm_rproc_ops *ops = rproc_get_ops(dev);
+ u64 da, memsz, filesz, offset;
+ Elf64_Ehdr *ehdr;
+ Elf64_Phdr *phdr;
+ int i, ret = 0;
+ void *ptr;
+
+ dev_dbg(dev, "%s: addr = 0x%lx size = 0x%lx\n", __func__, addr, size);
+
+ if (rproc_elf64_sanity_check(addr, size))
+ return -EINVAL;
+
+ ehdr = (Elf64_Ehdr *)addr;
+ phdr = (Elf64_Phdr *)(addr + (ulong)ehdr->e_phoff);
+
+ /* go through the available ELF segments */
+ for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
+ da = phdr->p_paddr;
+ memsz = phdr->p_memsz;
+ filesz = phdr->p_filesz;
+ offset = phdr->p_offset;
+
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ dev_dbg(dev, "%s:phdr: type %d da 0x%llx memsz 0x%llx filesz 0x%llx\n",
+ __func__, phdr->p_type, da, memsz, filesz);
+
+ ptr = (void *)(uintptr_t)da;
+ if (ops->device_to_virt) {
+ ptr = ops->device_to_virt(dev, da, phdr->p_memsz);
+ if (!ptr) {
+ dev_err(dev, "bad da 0x%llx mem 0x%llx\n", da,
+ memsz);
+ ret = -EINVAL;
+ break;
+ }
+ }
+
+ if (filesz)
+ memcpy(ptr, (void *)addr + offset, filesz);
+ if (filesz != memsz)
+ memset(ptr + filesz, 0x00, memsz - filesz);
+
+ flush_cache(rounddown((ulong)ptr, ARCH_DMA_MINALIGN),
+ roundup((ulong)ptr + filesz, ARCH_DMA_MINALIGN) -
+ rounddown((ulong)ptr, ARCH_DMA_MINALIGN));
+ }
+
+ return ret;
+}
+
+int rproc_elf_load_image(struct udevice *dev, ulong addr, ulong size)
+{
+ Elf32_Ehdr *ehdr = (Elf32_Ehdr *)addr;
+
+ if (!addr) {
+ dev_err(dev, "Invalid firmware address\n");
+ return -EFAULT;
+ }
+
+ if (ehdr->e_ident[EI_CLASS] == ELFCLASS64)
+ return rproc_elf64_load_image(dev, addr, size);
+ else
+ return rproc_elf32_load_image(dev, addr, size);
+}
+
+static ulong rproc_elf32_get_boot_addr(ulong addr)
+{
+ Elf32_Ehdr *ehdr = (Elf32_Ehdr *)addr;
+
+ return ehdr->e_entry;
+}
+
+static ulong rproc_elf64_get_boot_addr(ulong addr)
+{
+ Elf64_Ehdr *ehdr = (Elf64_Ehdr *)addr;
+
+ return ehdr->e_entry;
+}
+
+ulong rproc_elf_get_boot_addr(struct udevice *dev, ulong addr)
+{
+ Elf32_Ehdr *ehdr = (Elf32_Ehdr *)addr;
+
+ if (ehdr->e_ident[EI_CLASS] == ELFCLASS64)
+ return rproc_elf64_get_boot_addr(addr);
+ else
+ return rproc_elf32_get_boot_addr(addr);
+}
diff --git a/drivers/remoteproc/sandbox_testproc.c b/drivers/remoteproc/sandbox_testproc.c
index 5f35119ab7..eeee49c4dd 100644
--- a/drivers/remoteproc/sandbox_testproc.c
+++ b/drivers/remoteproc/sandbox_testproc.c
@@ -306,9 +306,11 @@ static int sandbox_testproc_ping(struct udevice *dev)
* sandbox_testproc_device_to_virt() - Convert device address to virtual address
* @dev: device to operate upon
* @da: device address
+ * @size: Size of the memory region @da is pointing to
* @return converted virtual address
*/
-static void *sandbox_testproc_device_to_virt(struct udevice *dev, ulong da)
+static void *sandbox_testproc_device_to_virt(struct udevice *dev, ulong da,
+ ulong size)
{
u64 paddr;
diff --git a/drivers/remoteproc/stm32_copro.c b/drivers/remoteproc/stm32_copro.c
index ad941f67e8..40bba37211 100644
--- a/drivers/remoteproc/stm32_copro.c
+++ b/drivers/remoteproc/stm32_copro.c
@@ -107,11 +107,13 @@ static int stm32_copro_set_hold_boot(struct udevice *dev, bool hold)
* stm32_copro_device_to_virt() - Convert device address to virtual address
* @dev: corresponding STM32 remote processor device
* @da: device address
+ * @size: Size of the memory region @da is pointing to
* @return converted virtual address
*/
-static void *stm32_copro_device_to_virt(struct udevice *dev, ulong da)
+static void *stm32_copro_device_to_virt(struct udevice *dev, ulong da,
+ ulong size)
{
- fdt32_t in_addr = cpu_to_be32(da);
+ fdt32_t in_addr = cpu_to_be32(da), end_addr;
u64 paddr;
paddr = dev_translate_dma_address(dev, &in_addr);
@@ -120,6 +122,12 @@ static void *stm32_copro_device_to_virt(struct udevice *dev, ulong da)
return NULL;
}
+ end_addr = cpu_to_be32(da + size - 1);
+ if (dev_translate_dma_address(dev, &end_addr) == OF_BAD_ADDR) {
+ dev_err(dev, "Unable to convert address %ld\n", da + size - 1);
+ return NULL;
+ }
+
return phys_to_virt(paddr);
}
@@ -147,14 +155,7 @@ static int stm32_copro_load(struct udevice *dev, ulong addr, ulong size)
return ret;
}
- /* Support only ELF32 image */
- ret = rproc_elf32_sanity_check(addr, size);
- if (ret) {
- dev_err(dev, "Invalid ELF32 image (%d)\n", ret);
- return ret;
- }
-
- return rproc_elf32_load_image(dev, addr);
+ return rproc_elf32_load_image(dev, addr, size);
}
/**
diff --git a/drivers/remoteproc/ti_k3_dsp_rproc.c b/drivers/remoteproc/ti_k3_dsp_rproc.c
new file mode 100644
index 0000000000..c5dc6b25da
--- /dev/null
+++ b/drivers/remoteproc/ti_k3_dsp_rproc.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Texas Instruments' K3 DSP Remoteproc driver
+ *
+ * Copyright (C) 2018-2019 Texas Instruments Incorporated - http://www.ti.com/
+ * Lokesh Vutla <lokeshvutla@ti.com>
+ *
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <remoteproc.h>
+#include <errno.h>
+#include <clk.h>
+#include <reset.h>
+#include <asm/io.h>
+#include <power-domain.h>
+#include <linux/soc/ti/ti_sci_protocol.h>
+#include "ti_sci_proc.h"
+
+#define KEYSTONE_RPROC_LOCAL_ADDRESS_MASK (SZ_16M - 1)
+
+/**
+ * struct k3_dsp_mem - internal memory structure
+ * @cpu_addr: MPU virtual address of the memory region
+ * @bus_addr: Bus address used to access the memory region
+ * @dev_addr: Device address from remoteproc view
+ * @size: Size of the memory region
+ */
+struct k3_dsp_mem {
+ void __iomem *cpu_addr;
+ phys_addr_t bus_addr;
+ phys_addr_t dev_addr;
+ size_t size;
+};
+
+/**
+ * struct k3_dsp_privdata - Structure representing Remote processor data.
+ * @rproc_rst: rproc reset control data
+ * @tsp: Pointer to TISCI proc contrl handle
+ * @mem: Array of available memories
+ * @num_mem: Number of available memories
+ */
+struct k3_dsp_privdata {
+ struct reset_ctl dsp_rst;
+ struct ti_sci_proc tsp;
+ struct k3_dsp_mem *mem;
+ int num_mems;
+};
+
+/**
+ * k3_dsp_load() - Load up the Remote processor image
+ * @dev: rproc device pointer
+ * @addr: Address at which image is available
+ * @size: size of the image
+ *
+ * Return: 0 if all goes good, else appropriate error message.
+ */
+static int k3_dsp_load(struct udevice *dev, ulong addr, ulong size)
+{
+ struct k3_dsp_privdata *dsp = dev_get_priv(dev);
+ u32 boot_vector;
+ int ret;
+
+ dev_dbg(dev, "%s addr = 0x%lx, size = 0x%lx\n", __func__, addr, size);
+ ret = ti_sci_proc_request(&dsp->tsp);
+ if (ret)
+ return ret;
+
+ ret = rproc_elf_load_image(dev, addr, size);
+ if (ret < 0) {
+ dev_err(dev, "Loading elf failed %d\n", ret);
+ goto proc_release;
+ }
+
+ boot_vector = rproc_elf_get_boot_addr(dev, addr);
+
+ dev_dbg(dev, "%s: Boot vector = 0x%x\n", __func__, boot_vector);
+
+ ret = ti_sci_proc_set_config(&dsp->tsp, boot_vector, 0, 0);
+proc_release:
+ ti_sci_proc_release(&dsp->tsp);
+ return ret;
+}
+
+/**
+ * k3_dsp_start() - Start the remote processor
+ * @dev: rproc device pointer
+ *
+ * Return: 0 if all went ok, else return appropriate error
+ */
+static int k3_dsp_start(struct udevice *dev)
+{
+ struct k3_dsp_privdata *dsp = dev_get_priv(dev);
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ ret = ti_sci_proc_request(&dsp->tsp);
+ if (ret)
+ return ret;
+ /*
+ * Setting the right clock frequency would have taken care by
+ * assigned-clock-rates during the device probe. So no need to
+ * set the frequency again here.
+ */
+ ret = ti_sci_proc_power_domain_on(&dsp->tsp);
+ if (ret)
+ goto proc_release;
+
+ ret = reset_deassert(&dsp->dsp_rst);
+
+proc_release:
+ ti_sci_proc_release(&dsp->tsp);
+
+ return ret;
+}
+
+static int k3_dsp_stop(struct udevice *dev)
+{
+ struct k3_dsp_privdata *dsp = dev_get_priv(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ ti_sci_proc_request(&dsp->tsp);
+ reset_assert(&dsp->dsp_rst);
+ ti_sci_proc_power_domain_off(&dsp->tsp);
+ ti_sci_proc_release(&dsp->tsp);
+
+ return 0;
+}
+
+/**
+ * k3_dsp_init() - Initialize the remote processor
+ * @dev: rproc device pointer
+ *
+ * Return: 0 if all went ok, else return appropriate error
+ */
+static int k3_dsp_init(struct udevice *dev)
+{
+ dev_dbg(dev, "%s\n", __func__);
+
+ return 0;
+}
+
+static int k3_dsp_reset(struct udevice *dev)
+{
+ dev_dbg(dev, "%s\n", __func__);
+
+ return 0;
+}
+
+static void *k3_dsp_da_to_va(struct udevice *dev, ulong da, ulong len)
+{
+ struct k3_dsp_privdata *dsp = dev_get_priv(dev);
+ phys_addr_t bus_addr, dev_addr;
+ void __iomem *va = NULL;
+ size_t size;
+ u32 offset;
+ int i;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (len <= 0)
+ return NULL;
+
+ for (i = 0; i < dsp->num_mems; i++) {
+ bus_addr = dsp->mem[i].bus_addr;
+ dev_addr = dsp->mem[i].dev_addr;
+ size = dsp->mem[i].size;
+
+ if (da >= dev_addr && ((da + len) <= (dev_addr + size))) {
+ offset = da - dev_addr;
+ va = dsp->mem[i].cpu_addr + offset;
+ return (__force void *)va;
+ }
+
+ if (da >= bus_addr && (da + len) <= (bus_addr + size)) {
+ offset = da - bus_addr;
+ va = dsp->mem[i].cpu_addr + offset;
+ return (__force void *)va;
+ }
+ }
+
+ /* Assume it is DDR region and return da */
+ return map_physmem(da, len, MAP_NOCACHE);
+}
+
+static const struct dm_rproc_ops k3_dsp_ops = {
+ .init = k3_dsp_init,
+ .load = k3_dsp_load,
+ .start = k3_dsp_start,
+ .stop = k3_dsp_stop,
+ .reset = k3_dsp_reset,
+ .device_to_virt = k3_dsp_da_to_va,
+};
+
+static int ti_sci_proc_of_to_priv(struct udevice *dev, struct ti_sci_proc *tsp)
+{
+ u32 ids[2];
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ tsp->sci = ti_sci_get_by_phandle(dev, "ti,sci");
+ if (IS_ERR(tsp->sci)) {
+ dev_err(dev, "ti_sci get failed: %ld\n", PTR_ERR(tsp->sci));
+ return PTR_ERR(tsp->sci);
+ }
+
+ ret = dev_read_u32_array(dev, "ti,sci-proc-ids", ids, 2);
+ if (ret) {
+ dev_err(dev, "Proc IDs not populated %d\n", ret);
+ return ret;
+ }
+
+ tsp->ops = &tsp->sci->ops.proc_ops;
+ tsp->proc_id = ids[0];
+ tsp->host_id = ids[1];
+ tsp->dev_id = dev_read_u32_default(dev, "ti,sci-dev-id",
+ TI_SCI_RESOURCE_NULL);
+ if (tsp->dev_id == TI_SCI_RESOURCE_NULL) {
+ dev_err(dev, "Device ID not populated %d\n", ret);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int k3_dsp_of_get_memories(struct udevice *dev)
+{
+ static const char * const mem_names[] = {"l2sram", "l1pram", "l1dram"};
+ struct k3_dsp_privdata *dsp = dev_get_priv(dev);
+ int i;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ dsp->num_mems = ARRAY_SIZE(mem_names);
+ dsp->mem = calloc(dsp->num_mems, sizeof(*dsp->mem));
+ if (!dsp->mem)
+ return -ENOMEM;
+
+ for (i = 0; i < dsp->num_mems; i++) {
+ /* C71 cores only have a L1P Cache, there are no L1P SRAMs */
+ if (device_is_compatible(dev, "ti,j721e-c71-dsp") &&
+ !strcmp(mem_names[i], "l1pram")) {
+ dsp->mem[i].bus_addr = FDT_ADDR_T_NONE;
+ dsp->mem[i].dev_addr = FDT_ADDR_T_NONE;
+ dsp->mem[i].cpu_addr = NULL;
+ dsp->mem[i].size = 0;
+ continue;
+ }
+
+ dsp->mem[i].bus_addr = dev_read_addr_size_name(dev, mem_names[i],
+ (fdt_addr_t *)&dsp->mem[i].size);
+ if (dsp->mem[i].bus_addr == FDT_ADDR_T_NONE) {
+ dev_err(dev, "%s bus address not found\n", mem_names[i]);
+ return -EINVAL;
+ }
+ dsp->mem[i].cpu_addr = map_physmem(dsp->mem[i].bus_addr,
+ dsp->mem[i].size,
+ MAP_NOCACHE);
+ dsp->mem[i].dev_addr = dsp->mem[i].bus_addr &
+ KEYSTONE_RPROC_LOCAL_ADDRESS_MASK;
+
+ dev_dbg(dev, "memory %8s: bus addr %pa size 0x%zx va %p da %pa\n",
+ mem_names[i], &dsp->mem[i].bus_addr,
+ dsp->mem[i].size, dsp->mem[i].cpu_addr,
+ &dsp->mem[i].dev_addr);
+ }
+
+ return 0;
+}
+
+/**
+ * k3_of_to_priv() - generate private data from device tree
+ * @dev: corresponding k3 dsp processor device
+ * @dsp: pointer to driver specific private data
+ *
+ * Return: 0 if all goes good, else appropriate error message.
+ */
+static int k3_dsp_of_to_priv(struct udevice *dev, struct k3_dsp_privdata *dsp)
+{
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ ret = reset_get_by_index(dev, 0, &dsp->dsp_rst);
+ if (ret) {
+ dev_err(dev, "reset_get() failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = ti_sci_proc_of_to_priv(dev, &dsp->tsp);
+ if (ret)
+ return ret;
+
+ ret = k3_dsp_of_get_memories(dev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * k3_dsp_probe() - Basic probe
+ * @dev: corresponding k3 remote processor device
+ *
+ * Return: 0 if all goes good, else appropriate error message.
+ */
+static int k3_dsp_probe(struct udevice *dev)
+{
+ struct k3_dsp_privdata *dsp;
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ dsp = dev_get_priv(dev);
+
+ ret = k3_dsp_of_to_priv(dev, dsp);
+ if (ret) {
+ dev_dbg(dev, "%s: Probe failed with error %d\n", __func__, ret);
+ return ret;
+ }
+
+ dev_dbg(dev, "Remoteproc successfully probed\n");
+
+ return 0;
+}
+
+static int k3_dsp_remove(struct udevice *dev)
+{
+ struct k3_dsp_privdata *dsp = dev_get_priv(dev);
+
+ free(dsp->mem);
+
+ return 0;
+}
+
+static const struct udevice_id k3_dsp_ids[] = {
+ { .compatible = "ti,j721e-c66-dsp"},
+ { .compatible = "ti,j721e-c71-dsp"},
+ {}
+};
+
+U_BOOT_DRIVER(k3_dsp) = {
+ .name = "k3_dsp",
+ .of_match = k3_dsp_ids,
+ .id = UCLASS_REMOTEPROC,
+ .ops = &k3_dsp_ops,
+ .probe = k3_dsp_probe,
+ .remove = k3_dsp_remove,
+ .priv_auto_alloc_size = sizeof(struct k3_dsp_privdata),
+};
diff --git a/drivers/remoteproc/ti_k3_r5f_rproc.c b/drivers/remoteproc/ti_k3_r5f_rproc.c
new file mode 100644
index 0000000000..ae1e4b9e04
--- /dev/null
+++ b/drivers/remoteproc/ti_k3_r5f_rproc.c
@@ -0,0 +1,816 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Texas Instruments' K3 R5 Remoteproc driver
+ *
+ * Copyright (C) 2018-2019 Texas Instruments Incorporated - http://www.ti.com/
+ * Lokesh Vutla <lokeshvutla@ti.com>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <remoteproc.h>
+#include <errno.h>
+#include <clk.h>
+#include <reset.h>
+#include <asm/io.h>
+#include <linux/kernel.h>
+#include <linux/soc/ti/ti_sci_protocol.h>
+#include "ti_sci_proc.h"
+
+/*
+ * R5F's view of this address can either be for ATCM or BTCM with the other
+ * at address 0x0 based on loczrama signal.
+ */
+#define K3_R5_TCM_DEV_ADDR 0x41010000
+
+/* R5 TI-SCI Processor Configuration Flags */
+#define PROC_BOOT_CFG_FLAG_R5_DBG_EN 0x00000001
+#define PROC_BOOT_CFG_FLAG_R5_DBG_NIDEN 0x00000002
+#define PROC_BOOT_CFG_FLAG_R5_LOCKSTEP 0x00000100
+#define PROC_BOOT_CFG_FLAG_R5_TEINIT 0x00000200
+#define PROC_BOOT_CFG_FLAG_R5_NMFI_EN 0x00000400
+#define PROC_BOOT_CFG_FLAG_R5_TCM_RSTBASE 0x00000800
+#define PROC_BOOT_CFG_FLAG_R5_BTCM_EN 0x00001000
+#define PROC_BOOT_CFG_FLAG_R5_ATCM_EN 0x00002000
+#define PROC_BOOT_CFG_FLAG_GEN_IGN_BOOTVECTOR 0x10000000
+
+/* R5 TI-SCI Processor Control Flags */
+#define PROC_BOOT_CTRL_FLAG_R5_CORE_HALT 0x00000001
+
+/* R5 TI-SCI Processor Status Flags */
+#define PROC_BOOT_STATUS_FLAG_R5_WFE 0x00000001
+#define PROC_BOOT_STATUS_FLAG_R5_WFI 0x00000002
+#define PROC_BOOT_STATUS_FLAG_R5_CLK_GATED 0x00000004
+#define PROC_BOOT_STATUS_FLAG_R5_LOCKSTEP_PERMITTED 0x00000100
+
+#define NR_CORES 2
+
+enum cluster_mode {
+ CLUSTER_MODE_SPLIT = 0,
+ CLUSTER_MODE_LOCKSTEP,
+};
+
+/**
+ * struct k3_r5_mem - internal memory structure
+ * @cpu_addr: MPU virtual address of the memory region
+ * @bus_addr: Bus address used to access the memory region
+ * @dev_addr: Device address from remoteproc view
+ * @size: Size of the memory region
+ */
+struct k3_r5f_mem {
+ void __iomem *cpu_addr;
+ phys_addr_t bus_addr;
+ u32 dev_addr;
+ size_t size;
+};
+
+/**
+ * struct k3_r5f_core - K3 R5 core structure
+ * @dev: cached device pointer
+ * @cluster: pointer to the parent cluster.
+ * @reset: reset control handle
+ * @tsp: TI-SCI processor control handle
+ * @mem: Array of available internal memories
+ * @num_mem: Number of available memories
+ * @atcm_enable: flag to control ATCM enablement
+ * @btcm_enable: flag to control BTCM enablement
+ * @loczrama: flag to dictate which TCM is at device address 0x0
+ * @in_use: flag to tell if the core is already in use.
+ */
+struct k3_r5f_core {
+ struct udevice *dev;
+ struct k3_r5f_cluster *cluster;
+ struct reset_ctl reset;
+ struct ti_sci_proc tsp;
+ struct k3_r5f_mem *mem;
+ int num_mems;
+ u32 atcm_enable;
+ u32 btcm_enable;
+ u32 loczrama;
+ bool in_use;
+};
+
+/**
+ * struct k3_r5f_cluster - K3 R5F Cluster structure
+ * @mode: Mode to configure the Cluster - Split or LockStep
+ * @cores: Array of pointers to R5 cores within the cluster
+ */
+struct k3_r5f_cluster {
+ enum cluster_mode mode;
+ struct k3_r5f_core *cores[NR_CORES];
+};
+
+static bool is_primary_core(struct k3_r5f_core *core)
+{
+ return core == core->cluster->cores[0];
+}
+
+static int k3_r5f_proc_request(struct k3_r5f_core *core)
+{
+ struct k3_r5f_cluster *cluster = core->cluster;
+ int i, ret;
+
+ if (cluster->mode == CLUSTER_MODE_LOCKSTEP) {
+ for (i = 0; i < NR_CORES; i++) {
+ ret = ti_sci_proc_request(&cluster->cores[i]->tsp);
+ if (ret)
+ goto proc_release;
+ }
+ } else {
+ ret = ti_sci_proc_request(&core->tsp);
+ }
+
+ return 0;
+
+proc_release:
+ while (i >= 0) {
+ ti_sci_proc_release(&cluster->cores[i]->tsp);
+ i--;
+ }
+ return ret;
+}
+
+static void k3_r5f_proc_release(struct k3_r5f_core *core)
+{
+ struct k3_r5f_cluster *cluster = core->cluster;
+ int i;
+
+ if (cluster->mode == CLUSTER_MODE_LOCKSTEP)
+ for (i = 0; i < NR_CORES; i++)
+ ti_sci_proc_release(&cluster->cores[i]->tsp);
+ else
+ ti_sci_proc_release(&core->tsp);
+}
+
+static int k3_r5f_lockstep_release(struct k3_r5f_cluster *cluster)
+{
+ int ret, c;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ for (c = NR_CORES - 1; c >= 0; c--) {
+ ret = ti_sci_proc_power_domain_on(&cluster->cores[c]->tsp);
+ if (ret)
+ goto unroll_module_reset;
+ }
+
+ /* deassert local reset on all applicable cores */
+ for (c = NR_CORES - 1; c >= 0; c--) {
+ ret = reset_deassert(&cluster->cores[c]->reset);
+ if (ret)
+ goto unroll_local_reset;
+ }
+
+ return 0;
+
+unroll_local_reset:
+ while (c < NR_CORES) {
+ reset_assert(&cluster->cores[c]->reset);
+ c++;
+ }
+ c = 0;
+unroll_module_reset:
+ while (c < NR_CORES) {
+ ti_sci_proc_power_domain_off(&cluster->cores[c]->tsp);
+ c++;
+ }
+
+ return ret;
+}
+
+static int k3_r5f_split_release(struct k3_r5f_core *core)
+{
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ ret = ti_sci_proc_power_domain_on(&core->tsp);
+ if (ret) {
+ dev_err(core->dev, "module-reset deassert failed, ret = %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = reset_deassert(&core->reset);
+ if (ret) {
+ dev_err(core->dev, "local-reset deassert failed, ret = %d\n",
+ ret);
+ if (ti_sci_proc_power_domain_off(&core->tsp))
+ dev_warn(core->dev, "module-reset assert back failed\n");
+ }
+
+ return ret;
+}
+
+static int k3_r5f_prepare(struct udevice *dev)
+{
+ struct k3_r5f_core *core = dev_get_priv(dev);
+ struct k3_r5f_cluster *cluster = core->cluster;
+ int ret = 0;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (cluster->mode == CLUSTER_MODE_LOCKSTEP)
+ ret = k3_r5f_lockstep_release(cluster);
+ else
+ ret = k3_r5f_split_release(core);
+
+ if (ret)
+ dev_err(dev, "Unable to enable cores for TCM loading %d\n",
+ ret);
+
+ return ret;
+}
+
+static int k3_r5f_core_sanity_check(struct k3_r5f_core *core)
+{
+ struct k3_r5f_cluster *cluster = core->cluster;
+
+ if (core->in_use) {
+ dev_err(dev, "Invalid op: Trying to load/start on already running core %d\n",
+ core->tsp.proc_id);
+ return -EINVAL;
+ }
+
+ if (cluster->mode == CLUSTER_MODE_LOCKSTEP && !cluster->cores[1]) {
+ printf("Secondary core is not probed in this cluster\n");
+ return -EAGAIN;
+ }
+
+ if (cluster->mode == CLUSTER_MODE_LOCKSTEP && !is_primary_core(core)) {
+ dev_err(dev, "Invalid op: Trying to start secondary core %d in lockstep mode\n",
+ core->tsp.proc_id);
+ return -EINVAL;
+ }
+
+ if (cluster->mode == CLUSTER_MODE_SPLIT && !is_primary_core(core)) {
+ if (!core->cluster->cores[0]->in_use) {
+ dev_err(dev, "Invalid seq: Enable primary core before loading secondary core\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * k3_r5f_load() - Load up the Remote processor image
+ * @dev: rproc device pointer
+ * @addr: Address at which image is available
+ * @size: size of the image
+ *
+ * Return: 0 if all goes good, else appropriate error message.
+ */
+static int k3_r5f_load(struct udevice *dev, ulong addr, ulong size)
+{
+ struct k3_r5f_core *core = dev_get_priv(dev);
+ u32 boot_vector;
+ int ret;
+
+ dev_dbg(dev, "%s addr = 0x%lx, size = 0x%lx\n", __func__, addr, size);
+
+ ret = k3_r5f_core_sanity_check(core);
+ if (ret)
+ return ret;
+
+ ret = k3_r5f_proc_request(core);
+ if (ret)
+ return ret;
+
+ ret = k3_r5f_prepare(dev);
+ if (ret) {
+ dev_err(dev, "R5f prepare failed for core %d\n",
+ core->tsp.proc_id);
+ goto proc_release;
+ }
+
+ /* Zero out TCMs so that ECC can be effective on all TCM addresses */
+ if (core->atcm_enable)
+ memset(core->mem[0].cpu_addr, 0x00, core->mem[0].size);
+ if (core->btcm_enable)
+ memset(core->mem[1].cpu_addr, 0x00, core->mem[1].size);
+
+ ret = rproc_elf_load_image(dev, addr, size);
+ if (ret < 0) {
+ dev_err(dev, "Loading elf failedi %d\n", ret);
+ goto proc_release;
+ }
+
+ boot_vector = rproc_elf_get_boot_addr(dev, addr);
+
+ dev_dbg(dev, "%s: Boot vector = 0x%x\n", __func__, boot_vector);
+
+ ret = ti_sci_proc_set_config(&core->tsp, boot_vector, 0, 0);
+
+proc_release:
+ k3_r5f_proc_release(core);
+
+ return ret;
+}
+
+static int k3_r5f_core_halt(struct k3_r5f_core *core)
+{
+ int ret;
+
+ ret = ti_sci_proc_set_control(&core->tsp,
+ PROC_BOOT_CTRL_FLAG_R5_CORE_HALT, 0);
+ if (ret)
+ dev_err(core->dev, "Core %d failed to stop\n",
+ core->tsp.proc_id);
+
+ return ret;
+}
+
+static int k3_r5f_core_run(struct k3_r5f_core *core)
+{
+ int ret;
+
+ ret = ti_sci_proc_set_control(&core->tsp,
+ 0, PROC_BOOT_CTRL_FLAG_R5_CORE_HALT);
+ if (ret) {
+ dev_err(core->dev, "Core %d failed to start\n",
+ core->tsp.proc_id);
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * k3_r5f_start() - Start the remote processor
+ * @dev: rproc device pointer
+ *
+ * Return: 0 if all went ok, else return appropriate error
+ */
+static int k3_r5f_start(struct udevice *dev)
+{
+ struct k3_r5f_core *core = dev_get_priv(dev);
+ struct k3_r5f_cluster *cluster = core->cluster;
+ int ret, c;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ ret = k3_r5f_core_sanity_check(core);
+ if (ret)
+ return ret;
+
+ ret = k3_r5f_proc_request(core);
+ if (ret)
+ return ret;
+
+ if (cluster->mode == CLUSTER_MODE_LOCKSTEP) {
+ if (is_primary_core(core)) {
+ for (c = NR_CORES - 1; c >= 0; c--) {
+ ret = k3_r5f_core_run(cluster->cores[c]);
+ if (ret)
+ goto unroll_core_run;
+ }
+ } else {
+ dev_err(dev, "Invalid op: Trying to start secondary core %d in lockstep mode\n",
+ core->tsp.proc_id);
+ ret = -EINVAL;
+ goto proc_release;
+ }
+ } else {
+ ret = k3_r5f_core_run(core);
+ if (ret)
+ goto proc_release;
+ }
+
+ core->in_use = true;
+
+ k3_r5f_proc_release(core);
+ return 0;
+
+unroll_core_run:
+ while (c < NR_CORES) {
+ k3_r5f_core_halt(cluster->cores[c]);
+ c++;
+ }
+proc_release:
+ k3_r5f_proc_release(core);
+
+ return ret;
+}
+
+static int k3_r5f_split_reset(struct k3_r5f_core *core)
+{
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (reset_assert(&core->reset))
+ ret = -EINVAL;
+
+ if (ti_sci_proc_power_domain_off(&core->tsp))
+ ret = -EINVAL;
+
+ return ret;
+}
+
+static int k3_r5f_lockstep_reset(struct k3_r5f_cluster *cluster)
+{
+ int ret = 0, c;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ for (c = 0; c < NR_CORES; c++)
+ if (reset_assert(&cluster->cores[c]->reset))
+ ret = -EINVAL;
+
+ /* disable PSC modules on all applicable cores */
+ for (c = 0; c < NR_CORES; c++)
+ if (ti_sci_proc_power_domain_off(&cluster->cores[c]->tsp))
+ ret = -EINVAL;
+
+ return ret;
+}
+
+static int k3_r5f_unprepare(struct udevice *dev)
+{
+ struct k3_r5f_core *core = dev_get_priv(dev);
+ struct k3_r5f_cluster *cluster = core->cluster;
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (cluster->mode == CLUSTER_MODE_LOCKSTEP) {
+ if (is_primary_core(core))
+ ret = k3_r5f_lockstep_reset(cluster);
+ } else {
+ ret = k3_r5f_split_reset(core);
+ }
+
+ if (ret)
+ dev_warn(dev, "Unable to enable cores for TCM loading %d\n",
+ ret);
+
+ return 0;
+}
+
+static int k3_r5f_stop(struct udevice *dev)
+{
+ struct k3_r5f_core *core = dev_get_priv(dev);
+ struct k3_r5f_cluster *cluster = core->cluster;
+ int c, ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ ret = k3_r5f_proc_request(core);
+ if (ret)
+ return ret;
+
+ core->in_use = false;
+
+ if (cluster->mode == CLUSTER_MODE_LOCKSTEP) {
+ if (is_primary_core(core)) {
+ for (c = 0; c < NR_CORES; c++)
+ k3_r5f_core_halt(cluster->cores[c]);
+ } else {
+ dev_err(dev, "Invalid op: Trying to stop secondary core in lockstep mode\n");
+ ret = -EINVAL;
+ goto proc_release;
+ }
+ } else {
+ k3_r5f_core_halt(core);
+ }
+
+ ret = k3_r5f_unprepare(dev);
+proc_release:
+ k3_r5f_proc_release(core);
+ return ret;
+}
+
+static void *k3_r5f_da_to_va(struct udevice *dev, ulong da, ulong size)
+{
+ struct k3_r5f_core *core = dev_get_priv(dev);
+ void __iomem *va = NULL;
+ phys_addr_t bus_addr;
+ u32 dev_addr, offset;
+ ulong mem_size;
+ int i;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (size <= 0)
+ return NULL;
+
+ for (i = 0; i < core->num_mems; i++) {
+ bus_addr = core->mem[i].bus_addr;
+ dev_addr = core->mem[i].dev_addr;
+ mem_size = core->mem[i].size;
+
+ if (da >= bus_addr && (da + size) <= (bus_addr + mem_size)) {
+ offset = da - bus_addr;
+ va = core->mem[i].cpu_addr + offset;
+ return (__force void *)va;
+ }
+
+ if (da >= dev_addr && (da + size) <= (dev_addr + mem_size)) {
+ offset = da - dev_addr;
+ va = core->mem[i].cpu_addr + offset;
+ return (__force void *)va;
+ }
+ }
+
+ /* Assume it is DDR region and return da */
+ return map_physmem(da, size, MAP_NOCACHE);
+}
+
+static int k3_r5f_init(struct udevice *dev)
+{
+ return 0;
+}
+
+static int k3_r5f_reset(struct udevice *dev)
+{
+ return 0;
+}
+
+static const struct dm_rproc_ops k3_r5f_rproc_ops = {
+ .init = k3_r5f_init,
+ .reset = k3_r5f_reset,
+ .start = k3_r5f_start,
+ .stop = k3_r5f_stop,
+ .load = k3_r5f_load,
+ .device_to_virt = k3_r5f_da_to_va,
+};
+
+static int k3_r5f_rproc_configure(struct k3_r5f_core *core)
+{
+ struct k3_r5f_cluster *cluster = core->cluster;
+ u32 set_cfg = 0, clr_cfg = 0, cfg, ctrl, sts;
+ u64 boot_vec = 0;
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ ret = ti_sci_proc_request(&core->tsp);
+ if (ret < 0)
+ return ret;
+
+ /* Do not touch boot vector now. Load will take care of it. */
+ clr_cfg |= PROC_BOOT_CFG_FLAG_GEN_IGN_BOOTVECTOR;
+
+ ret = ti_sci_proc_get_status(&core->tsp, &boot_vec, &cfg, &ctrl, &sts);
+ if (ret)
+ goto out;
+
+ /* Sanity check for Lockstep mode */
+ if (cluster->mode && is_primary_core(core) &&
+ !(sts & PROC_BOOT_STATUS_FLAG_R5_LOCKSTEP_PERMITTED)) {
+ dev_err(core->dev, "LockStep mode not permitted on this device\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Primary core only configuration */
+ if (is_primary_core(core)) {
+ /* always enable ARM mode */
+ clr_cfg |= PROC_BOOT_CFG_FLAG_R5_TEINIT;
+ if (cluster->mode == CLUSTER_MODE_LOCKSTEP)
+ set_cfg |= PROC_BOOT_CFG_FLAG_R5_LOCKSTEP;
+ else
+ clr_cfg |= PROC_BOOT_CFG_FLAG_R5_LOCKSTEP;
+ }
+
+ if (core->atcm_enable)
+ set_cfg |= PROC_BOOT_CFG_FLAG_R5_ATCM_EN;
+ else
+ clr_cfg |= PROC_BOOT_CFG_FLAG_R5_ATCM_EN;
+
+ if (core->btcm_enable)
+ set_cfg |= PROC_BOOT_CFG_FLAG_R5_BTCM_EN;
+ else
+ clr_cfg |= PROC_BOOT_CFG_FLAG_R5_BTCM_EN;
+
+ if (core->loczrama)
+ set_cfg |= PROC_BOOT_CFG_FLAG_R5_TCM_RSTBASE;
+ else
+ clr_cfg |= PROC_BOOT_CFG_FLAG_R5_TCM_RSTBASE;
+
+ ret = k3_r5f_core_halt(core);
+ if (ret)
+ goto out;
+
+ ret = ti_sci_proc_set_config(&core->tsp, boot_vec, set_cfg, clr_cfg);
+out:
+ ti_sci_proc_release(&core->tsp);
+ return ret;
+}
+
+static int ti_sci_proc_of_to_priv(struct udevice *dev, struct ti_sci_proc *tsp)
+{
+ u32 ids[2];
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ tsp->sci = ti_sci_get_by_phandle(dev, "ti,sci");
+ if (IS_ERR(tsp->sci)) {
+ dev_err(dev, "ti_sci get failed: %ld\n", PTR_ERR(tsp->sci));
+ return PTR_ERR(tsp->sci);
+ }
+
+ ret = dev_read_u32_array(dev, "ti,sci-proc-ids", ids, 2);
+ if (ret) {
+ dev_err(dev, "Proc IDs not populated %d\n", ret);
+ return ret;
+ }
+
+ tsp->ops = &tsp->sci->ops.proc_ops;
+ tsp->proc_id = ids[0];
+ tsp->host_id = ids[1];
+ tsp->dev_id = dev_read_u32_default(dev, "ti,sci-dev-id",
+ TI_SCI_RESOURCE_NULL);
+ if (tsp->dev_id == TI_SCI_RESOURCE_NULL) {
+ dev_err(dev, "Device ID not populated %d\n", ret);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int k3_r5f_of_to_priv(struct k3_r5f_core *core)
+{
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ core->atcm_enable = dev_read_u32_default(core->dev, "atcm-enable", 0);
+ core->btcm_enable = dev_read_u32_default(core->dev, "btcm-enable", 1);
+ core->loczrama = dev_read_u32_default(core->dev, "loczrama", 1);
+
+ ret = ti_sci_proc_of_to_priv(core->dev, &core->tsp);
+ if (ret)
+ return ret;
+
+ ret = reset_get_by_index(core->dev, 0, &core->reset);
+ if (ret) {
+ dev_err(core->dev, "Reset lines not available: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int k3_r5f_core_of_get_memories(struct k3_r5f_core *core)
+{
+ static const char * const mem_names[] = {"atcm", "btcm"};
+ struct udevice *dev = core->dev;
+ int i;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ core->num_mems = ARRAY_SIZE(mem_names);
+ core->mem = calloc(core->num_mems, sizeof(*core->mem));
+ if (!core->mem)
+ return -ENOMEM;
+
+ for (i = 0; i < core->num_mems; i++) {
+ core->mem[i].bus_addr = dev_read_addr_size_name(dev,
+ mem_names[i],
+ (fdt_addr_t *)&core->mem[i].size);
+ if (core->mem[i].bus_addr == FDT_ADDR_T_NONE) {
+ dev_err(dev, "%s bus address not found\n",
+ mem_names[i]);
+ return -EINVAL;
+ }
+ core->mem[i].cpu_addr = map_physmem(core->mem[i].bus_addr,
+ core->mem[i].size,
+ MAP_NOCACHE);
+ if (!strcmp(mem_names[i], "atcm")) {
+ core->mem[i].dev_addr = core->loczrama ?
+ 0 : K3_R5_TCM_DEV_ADDR;
+ } else {
+ core->mem[i].dev_addr = core->loczrama ?
+ K3_R5_TCM_DEV_ADDR : 0;
+ }
+
+ dev_dbg(dev, "memory %8s: bus addr %pa size 0x%zx va %p da 0x%x\n",
+ mem_names[i], &core->mem[i].bus_addr,
+ core->mem[i].size, core->mem[i].cpu_addr,
+ core->mem[i].dev_addr);
+ }
+
+ return 0;
+}
+
+/**
+ * k3_r5f_probe() - Basic probe
+ * @dev: corresponding k3 remote processor device
+ *
+ * Return: 0 if all goes good, else appropriate error message.
+ */
+static int k3_r5f_probe(struct udevice *dev)
+{
+ struct k3_r5f_cluster *cluster = dev_get_priv(dev->parent);
+ struct k3_r5f_core *core = dev_get_priv(dev);
+ bool r_state;
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ core->dev = dev;
+ ret = k3_r5f_of_to_priv(core);
+ if (ret)
+ return ret;
+
+ core->cluster = cluster;
+ /* Assume Primary core gets probed first */
+ if (!cluster->cores[0])
+ cluster->cores[0] = core;
+ else
+ cluster->cores[1] = core;
+
+ ret = k3_r5f_core_of_get_memories(core);
+ if (ret) {
+ dev_err(dev, "Rproc getting internal memories failed\n");
+ return ret;
+ }
+
+ ret = core->tsp.sci->ops.dev_ops.is_on(core->tsp.sci, core->tsp.dev_id,
+ &r_state, &core->in_use);
+ if (ret)
+ return ret;
+
+ if (core->in_use) {
+ dev_info(dev, "Core %d is already in use. No rproc commands work\n",
+ core->tsp.proc_id);
+ return 0;
+ }
+
+ /* Make sure Local reset is asserted. Redundant? */
+ reset_assert(&core->reset);
+
+ ret = k3_r5f_rproc_configure(core);
+ if (ret) {
+ dev_err(dev, "rproc configure failed %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(dev, "Remoteproc successfully probed\n");
+
+ return 0;
+}
+
+static int k3_r5f_remove(struct udevice *dev)
+{
+ struct k3_r5f_core *core = dev_get_priv(dev);
+
+ free(core->mem);
+
+ ti_sci_proc_release(&core->tsp);
+
+ return 0;
+}
+
+static const struct udevice_id k3_r5f_rproc_ids[] = {
+ { .compatible = "ti,am654-r5f"},
+ { .compatible = "ti,j721e-r5f"},
+ {}
+};
+
+U_BOOT_DRIVER(k3_r5f_rproc) = {
+ .name = "k3_r5f_rproc",
+ .of_match = k3_r5f_rproc_ids,
+ .id = UCLASS_REMOTEPROC,
+ .ops = &k3_r5f_rproc_ops,
+ .probe = k3_r5f_probe,
+ .remove = k3_r5f_remove,
+ .priv_auto_alloc_size = sizeof(struct k3_r5f_core),
+};
+
+static int k3_r5f_cluster_probe(struct udevice *dev)
+{
+ struct k3_r5f_cluster *cluster = dev_get_priv(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ cluster->mode = dev_read_u32_default(dev, "lockstep-mode",
+ CLUSTER_MODE_LOCKSTEP);
+
+ if (device_get_child_count(dev) != 2) {
+ dev_err(dev, "Invalid number of R5 cores");
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "%s: Cluster successfully probed in %s mode\n",
+ __func__, cluster->mode ? "lockstep" : "split");
+
+ return 0;
+}
+
+static const struct udevice_id k3_r5fss_ids[] = {
+ { .compatible = "ti,am654-r5fss"},
+ { .compatible = "ti,j721e-r5fss"},
+ {}
+};
+
+U_BOOT_DRIVER(k3_r5fss) = {
+ .name = "k3_r5fss",
+ .of_match = k3_r5fss_ids,
+ .id = UCLASS_MISC,
+ .probe = k3_r5f_cluster_probe,
+ .priv_auto_alloc_size = sizeof(struct k3_r5f_cluster),
+};
diff --git a/drivers/remoteproc/ti_sci_proc.h b/drivers/remoteproc/ti_sci_proc.h
index ccfc39ec88..f8299d1aff 100644
--- a/drivers/remoteproc/ti_sci_proc.h
+++ b/drivers/remoteproc/ti_sci_proc.h
@@ -19,12 +19,14 @@
* @proc_id: processor id for the consumer remoteproc device
* @host_id: host id to pass the control over for this consumer remoteproc
* device
+ * @dev_id: Device ID as identified by system controller.
*/
struct ti_sci_proc {
const struct ti_sci_handle *sci;
const struct ti_sci_proc_ops *ops;
u8 proc_id;
u8 host_id;
+ u16 dev_id;
};
static inline int ti_sci_proc_request(struct ti_sci_proc *tsp)
@@ -118,4 +120,29 @@ static inline int ti_sci_proc_set_control(struct ti_sci_proc *tsp,
return ret;
}
+static inline int ti_sci_proc_power_domain_on(struct ti_sci_proc *tsp)
+{
+ int ret;
+
+ debug("%s: dev_id = %d\n", __func__, tsp->dev_id);
+
+ ret = tsp->sci->ops.dev_ops.get_device_exclusive(tsp->sci, tsp->dev_id);
+ if (ret)
+ pr_err("Power-domain on failed for dev = %d\n", tsp->dev_id);
+
+ return ret;
+}
+
+static inline int ti_sci_proc_power_domain_off(struct ti_sci_proc *tsp)
+{
+ int ret;
+
+ debug("%s: dev_id = %d\n", __func__, tsp->dev_id);
+
+ ret = tsp->sci->ops.dev_ops.put_device(tsp->sci, tsp->dev_id);
+ if (ret)
+ pr_err("Power-domain off failed for dev = %d\n", tsp->dev_id);
+
+ return ret;
+}
#endif /* REMOTEPROC_TI_SCI_PROC_H */
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 8dd3213d48..b8ca2bdedd 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -67,7 +67,7 @@ config ATMEL_SPI
config BCM63XX_HSSPI
bool "BCM63XX HSSPI driver"
- depends on ARCH_BMIPS
+ depends on (ARCH_BMIPS || ARCH_BCM6858 || ARCH_BCM63158)
help
Enable the BCM6328 HSSPI driver. This driver can be used to
access the SPI NOR flash on platforms embedding this Broadcom
diff --git a/drivers/spi/bcm63xx_hsspi.c b/drivers/spi/bcm63xx_hsspi.c
index 4f527fa74a..e82b80c107 100644
--- a/drivers/spi/bcm63xx_hsspi.c
+++ b/drivers/spi/bcm63xx_hsspi.c
@@ -120,9 +120,9 @@ static int bcm63xx_hsspi_set_mode(struct udevice *bus, uint mode)
/* clock polarity */
if (mode & SPI_CPOL)
- setbits_be32(priv->regs + SPI_CTL_REG, SPI_CTL_CLK_POL_MASK);
+ setbits_32(priv->regs + SPI_CTL_REG, SPI_CTL_CLK_POL_MASK);
else
- clrbits_be32(priv->regs + SPI_CTL_REG, SPI_CTL_CLK_POL_MASK);
+ clrbits_32(priv->regs + SPI_CTL_REG, SPI_CTL_CLK_POL_MASK);
return 0;
}
@@ -146,7 +146,7 @@ static void bcm63xx_hsspi_activate_cs(struct bcm63xx_hsspi_priv *priv,
set = DIV_ROUND_UP(2048, set);
set &= SPI_PFL_CLK_FREQ_MASK;
set |= SPI_PFL_CLK_RSTLOOP_MASK;
- writel_be(set, priv->regs + SPI_PFL_CLK_REG(plat->cs));
+ writel(set, priv->regs + SPI_PFL_CLK_REG(plat->cs));
/* profile signal */
set = 0;
@@ -164,7 +164,7 @@ static void bcm63xx_hsspi_activate_cs(struct bcm63xx_hsspi_priv *priv,
if (priv->speed > SPI_MAX_SYNC_CLOCK)
set |= SPI_PFL_SIG_ASYNCIN_MASK;
- clrsetbits_be32(priv->regs + SPI_PFL_SIG_REG(plat->cs), clr, set);
+ clrsetbits_32(priv->regs + SPI_PFL_SIG_REG(plat->cs), clr, set);
/* global control */
set = 0;
@@ -182,13 +182,13 @@ static void bcm63xx_hsspi_activate_cs(struct bcm63xx_hsspi_priv *priv,
else
set |= BIT(!plat->cs);
- clrsetbits_be32(priv->regs + SPI_CTL_REG, clr, set);
+ clrsetbits_32(priv->regs + SPI_CTL_REG, clr, set);
}
static void bcm63xx_hsspi_deactivate_cs(struct bcm63xx_hsspi_priv *priv)
{
/* restore cs polarities */
- clrsetbits_be32(priv->regs + SPI_CTL_REG, SPI_CTL_CS_POL_MASK,
+ clrsetbits_32(priv->regs + SPI_CTL_REG, SPI_CTL_CS_POL_MASK,
priv->cs_pols);
}
@@ -247,7 +247,7 @@ static int bcm63xx_hsspi_xfer(struct udevice *dev, unsigned int bitlen,
SPI_PFL_MODE_MDWRSZ_MASK;
if (plat->mode & SPI_3WIRE)
val |= SPI_PFL_MODE_3WIRE_MASK;
- writel_be(val, priv->regs + SPI_PFL_MODE_REG(plat->cs));
+ writel(val, priv->regs + SPI_PFL_MODE_REG(plat->cs));
/* transfer loop */
while (data_bytes > 0) {
@@ -262,7 +262,7 @@ static int bcm63xx_hsspi_xfer(struct udevice *dev, unsigned int bitlen,
}
/* set fifo operation */
- writew_be(opcode | (curr_step & HSSPI_FIFO_OP_BYTES_MASK),
+ writew(cpu_to_be16(opcode | (curr_step & HSSPI_FIFO_OP_BYTES_MASK)),
priv->regs + HSSPI_FIFO_OP_REG);
/* issue the transfer */
@@ -271,10 +271,10 @@ static int bcm63xx_hsspi_xfer(struct udevice *dev, unsigned int bitlen,
SPI_CMD_PFL_MASK;
val |= (!plat->cs << SPI_CMD_SLAVE_SHIFT) &
SPI_CMD_SLAVE_MASK;
- writel_be(val, priv->regs + SPI_CMD_REG);
+ writel(val, priv->regs + SPI_CMD_REG);
/* wait for completion */
- ret = wait_for_bit_be32(priv->regs + SPI_STAT_REG,
+ ret = wait_for_bit_32(priv->regs + SPI_STAT_REG,
SPI_STAT_SRCBUSY_MASK, false,
1000, false);
if (ret) {
@@ -349,48 +349,47 @@ static int bcm63xx_hsspi_probe(struct udevice *dev)
return ret;
ret = clk_enable(&clk);
- if (ret < 0)
+ if (ret < 0 && ret != -ENOSYS)
return ret;
ret = clk_free(&clk);
- if (ret < 0)
+ if (ret < 0 && ret != -ENOSYS)
return ret;
/* get clock rate */
ret = clk_get_by_name(dev, "pll", &clk);
- if (ret < 0)
+ if (ret < 0 && ret != -ENOSYS)
return ret;
priv->clk_rate = clk_get_rate(&clk);
ret = clk_free(&clk);
- if (ret < 0)
+ if (ret < 0 && ret != -ENOSYS)
return ret;
/* perform reset */
ret = reset_get_by_index(dev, 0, &rst_ctl);
- if (ret < 0)
- return ret;
-
- ret = reset_deassert(&rst_ctl);
- if (ret < 0)
- return ret;
+ if (ret >= 0) {
+ ret = reset_deassert(&rst_ctl);
+ if (ret < 0)
+ return ret;
+ }
ret = reset_free(&rst_ctl);
if (ret < 0)
return ret;
/* initialize hardware */
- writel_be(0, priv->regs + SPI_IR_MASK_REG);
+ writel(0, priv->regs + SPI_IR_MASK_REG);
/* clear pending interrupts */
- writel_be(SPI_IR_CLEAR_ALL, priv->regs + SPI_IR_STAT_REG);
+ writel(SPI_IR_CLEAR_ALL, priv->regs + SPI_IR_STAT_REG);
/* enable clk gate */
- setbits_be32(priv->regs + SPI_CTL_REG, SPI_CTL_CLK_GATE_MASK);
+ setbits_32(priv->regs + SPI_CTL_REG, SPI_CTL_CLK_GATE_MASK);
/* read default cs polarities */
- priv->cs_pols = readl_be(priv->regs + SPI_CTL_REG) &
+ priv->cs_pols = readl(priv->regs + SPI_CTL_REG) &
SPI_CTL_CS_POL_MASK;
return 0;
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 56e2a046bf..2498f0efb1 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -440,6 +440,8 @@ static int dwc3_core_init(struct dwc3 *dwc)
goto err0;
}
+ dwc3_phy_setup(dwc);
+
ret = dwc3_core_soft_reset(dwc);
if (ret)
goto err0;
@@ -514,8 +516,6 @@ static int dwc3_core_init(struct dwc3 *dwc)
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
- dwc3_phy_setup(dwc);
-
ret = dwc3_alloc_scratch_buffers(dwc);
if (ret)
goto err0;
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 261fa98517..805713c08b 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -73,6 +73,14 @@ config VIDEO_ANSI
Enable ANSI escape sequence decoding for a more fully functional
console.
+config VIDEO_MIPI_DSI
+ bool "Support MIPI DSI interface"
+ depends on DM_VIDEO
+ help
+ Support MIPI DSI interface for driving a MIPI compatible device.
+ The MIPI Display Serial Interface (MIPI DSI) defines a high-speed
+ serial interface between a host processor and a display module.
+
config CONSOLE_NORMAL
bool "Support a simple text console"
depends on DM_VIDEO
@@ -320,6 +328,24 @@ config VIDEO_LCD_ANX9804
from a parallel LCD interface and translate it on the fy into a DP
interface for driving eDP TFT displays. It uses I2C for configuration.
+config VIDEO_LCD_ORISETECH_OTM8009A
+ bool "OTM8009A DSI LCD panel support"
+ depends on DM_VIDEO
+ select VIDEO_MIPI_DSI
+ default n
+ help
+ Say Y here if you want to enable support for Orise Technology
+ otm8009a 480x800 dsi 2dl panel.
+
+config VIDEO_LCD_RAYDIUM_RM68200
+ bool "RM68200 DSI LCD panel support"
+ depends on DM_VIDEO
+ select VIDEO_MIPI_DSI
+ default n
+ help
+ Say Y here if you want to enable support for Raydium RM68200
+ 720x1280 DSI video mode panel.
+
config VIDEO_LCD_SSD2828
bool "SSD2828 bridge chip"
default n
@@ -678,6 +704,27 @@ config VIDEO_DW_HDMI
rather requires a SoC-specific glue driver to call it), it
can not be enabled from the configuration menu.
+config VIDEO_DSI_HOST_SANDBOX
+ bool "Enable sandbox for dsi host"
+ depends on SANDBOX
+ select VIDEO_MIPI_DSI
+ help
+ Enable support for sandbox dsi host device used for testing
+ purposes.
+ Display Serial Interface (DSI) defines a serial bus and
+ a communication protocol between the host and the device
+ (panel, bridge).
+
+config VIDEO_DW_MIPI_DSI
+ bool
+ select VIDEO_MIPI_DSI
+ help
+ Enables the common driver code for the Synopsis Designware
+ MIPI DSI block found in SoCs from various vendors.
+ As this does not provide any functionality by itself (but
+ rather requires a SoC-specific glue driver to call it), it
+ can not be enabled from the configuration menu.
+
config VIDEO_SIMPLE
bool "Simple display driver for preconfigured display"
help
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 349a207035..df7119d62a 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_CONSOLE_ROTATION) += console_rotate.o
obj-$(CONFIG_CONSOLE_TRUETYPE) += console_truetype.o fonts/
obj-$(CONFIG_DISPLAY) += display-uclass.o
obj-$(CONFIG_DM_VIDEO) += backlight-uclass.o
+obj-$(CONFIG_VIDEO_MIPI_DSI) += dsi-host-uclass.o
obj-$(CONFIG_DM_VIDEO) += panel-uclass.o simple_panel.o
obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o
obj-$(CONFIG_DM_VIDEO) += video_bmp.o
@@ -44,19 +45,24 @@ obj-$(CONFIG_VIDEO_BROADWELL_IGD) += broadwell_igd.o
obj-$(CONFIG_VIDEO_COREBOOT) += coreboot.o
obj-$(CONFIG_VIDEO_DA8XX) += da8xx-fb.o videomodes.o
obj-$(CONFIG_VIDEO_DW_HDMI) += dw_hdmi.o
+obj-$(CONFIG_VIDEO_DW_MIPI_DSI) += dw_mipi_dsi.o
obj-$(CONFIG_VIDEO_EFI) += efi.o
obj-$(CONFIG_VIDEO_FSL_DCU_FB) += fsl_dcu_fb.o videomodes.o
obj-$(CONFIG_VIDEO_IPUV3) += imx/
obj-$(CONFIG_VIDEO_IVYBRIDGE_IGD) += ivybridge_igd.o
obj-$(CONFIG_VIDEO_LCD_ANX9804) += anx9804.o
obj-$(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM) += hitachi_tx18d42vm_lcd.o
+obj-$(CONFIG_VIDEO_LCD_ORISETECH_OTM8009A) += orisetech_otm8009a.o
+obj-$(CONFIG_VIDEO_LCD_RAYDIUM_RM68200) += raydium-rm68200.o
obj-$(CONFIG_VIDEO_LCD_SSD2828) += ssd2828.o
obj-$(CONFIG_VIDEO_MB862xx) += mb862xx.o videomodes.o
obj-${CONFIG_VIDEO_MESON} += meson/
+obj-${CONFIG_VIDEO_MIPI_DSI} += mipi_dsi.o
obj-$(CONFIG_VIDEO_MVEBU) += mvebu_lcd.o
obj-$(CONFIG_VIDEO_MX3) += mx3fb.o videomodes.o
obj-$(CONFIG_VIDEO_MXS) += mxsfb.o videomodes.o
obj-$(CONFIG_VIDEO_OMAP3) += omap3_dss.o
+obj-$(CONFIG_VIDEO_DSI_HOST_SANDBOX) += sandbox_dsi_host.o
obj-$(CONFIG_VIDEO_SANDBOX_SDL) += sandbox_sdl.o
obj-$(CONFIG_VIDEO_SIMPLE) += simplefb.o
obj-$(CONFIG_VIDEO_TEGRA20) += tegra.o
diff --git a/drivers/video/dsi-host-uclass.c b/drivers/video/dsi-host-uclass.c
new file mode 100644
index 0000000000..1db1f88a17
--- /dev/null
+++ b/drivers/video/dsi-host-uclass.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 STMicroelectronics - All Rights Reserved
+ * Author(s): Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
+ *
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <dsi_host.h>
+
+int dsi_host_init(struct udevice *dev,
+ struct mipi_dsi_device *device,
+ struct display_timing *timings,
+ unsigned int max_data_lanes,
+ const struct mipi_dsi_phy_ops *phy_ops)
+{
+ struct dsi_host_ops *ops = dsi_host_get_ops(dev);
+
+ if (!ops->init)
+ return -ENOSYS;
+
+ return ops->init(dev, device, timings, max_data_lanes, phy_ops);
+}
+
+int dsi_host_enable(struct udevice *dev)
+{
+ struct dsi_host_ops *ops = dsi_host_get_ops(dev);
+
+ if (!ops->enable)
+ return -ENOSYS;
+
+ return ops->enable(dev);
+}
+
+UCLASS_DRIVER(dsi_host) = {
+ .id = UCLASS_DSI_HOST,
+ .name = "dsi_host",
+};
diff --git a/drivers/video/dw_mipi_dsi.c b/drivers/video/dw_mipi_dsi.c
new file mode 100644
index 0000000000..04b07e3a2c
--- /dev/null
+++ b/drivers/video/dw_mipi_dsi.c
@@ -0,0 +1,838 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ * Copyright (C) 2019, STMicroelectronics - All Rights Reserved
+ * Author(s): Philippe Cornu <philippe.cornu@st.com> for STMicroelectronics.
+ * Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
+ *
+ * This generic Synopsys DesignWare MIPI DSI host driver is inspired from
+ * the Linux Kernel driver drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c.
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dsi_host.h>
+#include <dm.h>
+#include <errno.h>
+#include <panel.h>
+#include <video.h>
+#include <asm/io.h>
+#include <asm/arch/gpio.h>
+#include <dm/device-internal.h>
+#include <linux/iopoll.h>
+#include <video_bridge.h>
+
+#define HWVER_131 0x31333100 /* IP version 1.31 */
+
+#define DSI_VERSION 0x00
+#define VERSION GENMASK(31, 8)
+
+#define DSI_PWR_UP 0x04
+#define RESET 0
+#define POWERUP BIT(0)
+
+#define DSI_CLKMGR_CFG 0x08
+#define TO_CLK_DIVISION(div) (((div) & 0xff) << 8)
+#define TX_ESC_CLK_DIVISION(div) ((div) & 0xff)
+
+#define DSI_DPI_VCID 0x0c
+#define DPI_VCID(vcid) ((vcid) & 0x3)
+
+#define DSI_DPI_COLOR_CODING 0x10
+#define LOOSELY18_EN BIT(8)
+#define DPI_COLOR_CODING_16BIT_1 0x0
+#define DPI_COLOR_CODING_16BIT_2 0x1
+#define DPI_COLOR_CODING_16BIT_3 0x2
+#define DPI_COLOR_CODING_18BIT_1 0x3
+#define DPI_COLOR_CODING_18BIT_2 0x4
+#define DPI_COLOR_CODING_24BIT 0x5
+
+#define DSI_DPI_CFG_POL 0x14
+#define COLORM_ACTIVE_LOW BIT(4)
+#define SHUTD_ACTIVE_LOW BIT(3)
+#define HSYNC_ACTIVE_LOW BIT(2)
+#define VSYNC_ACTIVE_LOW BIT(1)
+#define DATAEN_ACTIVE_LOW BIT(0)
+
+#define DSI_DPI_LP_CMD_TIM 0x18
+#define OUTVACT_LPCMD_TIME(p) (((p) & 0xff) << 16)
+#define INVACT_LPCMD_TIME(p) ((p) & 0xff)
+
+#define DSI_DBI_VCID 0x1c
+#define DSI_DBI_CFG 0x20
+#define DSI_DBI_PARTITIONING_EN 0x24
+#define DSI_DBI_CMDSIZE 0x28
+
+#define DSI_PCKHDL_CFG 0x2c
+#define CRC_RX_EN BIT(4)
+#define ECC_RX_EN BIT(3)
+#define BTA_EN BIT(2)
+#define EOTP_RX_EN BIT(1)
+#define EOTP_TX_EN BIT(0)
+
+#define DSI_GEN_VCID 0x30
+
+#define DSI_MODE_CFG 0x34
+#define ENABLE_VIDEO_MODE 0
+#define ENABLE_CMD_MODE BIT(0)
+
+#define DSI_VID_MODE_CFG 0x38
+#define ENABLE_LOW_POWER (0x3f << 8)
+#define ENABLE_LOW_POWER_MASK (0x3f << 8)
+#define VID_MODE_TYPE_NON_BURST_SYNC_PULSES 0x0
+#define VID_MODE_TYPE_NON_BURST_SYNC_EVENTS 0x1
+#define VID_MODE_TYPE_BURST 0x2
+#define VID_MODE_TYPE_MASK 0x3
+
+#define DSI_VID_PKT_SIZE 0x3c
+#define VID_PKT_SIZE(p) ((p) & 0x3fff)
+
+#define DSI_VID_NUM_CHUNKS 0x40
+#define VID_NUM_CHUNKS(c) ((c) & 0x1fff)
+
+#define DSI_VID_NULL_SIZE 0x44
+#define VID_NULL_SIZE(b) ((b) & 0x1fff)
+
+#define DSI_VID_HSA_TIME 0x48
+#define DSI_VID_HBP_TIME 0x4c
+#define DSI_VID_HLINE_TIME 0x50
+#define DSI_VID_VSA_LINES 0x54
+#define DSI_VID_VBP_LINES 0x58
+#define DSI_VID_VFP_LINES 0x5c
+#define DSI_VID_VACTIVE_LINES 0x60
+#define DSI_EDPI_CMD_SIZE 0x64
+
+#define DSI_CMD_MODE_CFG 0x68
+#define MAX_RD_PKT_SIZE_LP BIT(24)
+#define DCS_LW_TX_LP BIT(19)
+#define DCS_SR_0P_TX_LP BIT(18)
+#define DCS_SW_1P_TX_LP BIT(17)
+#define DCS_SW_0P_TX_LP BIT(16)
+#define GEN_LW_TX_LP BIT(14)
+#define GEN_SR_2P_TX_LP BIT(13)
+#define GEN_SR_1P_TX_LP BIT(12)
+#define GEN_SR_0P_TX_LP BIT(11)
+#define GEN_SW_2P_TX_LP BIT(10)
+#define GEN_SW_1P_TX_LP BIT(9)
+#define GEN_SW_0P_TX_LP BIT(8)
+#define ACK_RQST_EN BIT(1)
+#define TEAR_FX_EN BIT(0)
+
+#define CMD_MODE_ALL_LP (MAX_RD_PKT_SIZE_LP | \
+ DCS_LW_TX_LP | \
+ DCS_SR_0P_TX_LP | \
+ DCS_SW_1P_TX_LP | \
+ DCS_SW_0P_TX_LP | \
+ GEN_LW_TX_LP | \
+ GEN_SR_2P_TX_LP | \
+ GEN_SR_1P_TX_LP | \
+ GEN_SR_0P_TX_LP | \
+ GEN_SW_2P_TX_LP | \
+ GEN_SW_1P_TX_LP | \
+ GEN_SW_0P_TX_LP)
+
+#define DSI_GEN_HDR 0x6c
+#define DSI_GEN_PLD_DATA 0x70
+
+#define DSI_CMD_PKT_STATUS 0x74
+#define GEN_RD_CMD_BUSY BIT(6)
+#define GEN_PLD_R_FULL BIT(5)
+#define GEN_PLD_R_EMPTY BIT(4)
+#define GEN_PLD_W_FULL BIT(3)
+#define GEN_PLD_W_EMPTY BIT(2)
+#define GEN_CMD_FULL BIT(1)
+#define GEN_CMD_EMPTY BIT(0)
+
+#define DSI_TO_CNT_CFG 0x78
+#define HSTX_TO_CNT(p) (((p) & 0xffff) << 16)
+#define LPRX_TO_CNT(p) ((p) & 0xffff)
+
+#define DSI_HS_RD_TO_CNT 0x7c
+#define DSI_LP_RD_TO_CNT 0x80
+#define DSI_HS_WR_TO_CNT 0x84
+#define DSI_LP_WR_TO_CNT 0x88
+#define DSI_BTA_TO_CNT 0x8c
+
+#define DSI_LPCLK_CTRL 0x94
+#define AUTO_CLKLANE_CTRL BIT(1)
+#define PHY_TXREQUESTCLKHS BIT(0)
+
+#define DSI_PHY_TMR_LPCLK_CFG 0x98
+#define PHY_CLKHS2LP_TIME(lbcc) (((lbcc) & 0x3ff) << 16)
+#define PHY_CLKLP2HS_TIME(lbcc) ((lbcc) & 0x3ff)
+
+#define DSI_PHY_TMR_CFG 0x9c
+#define PHY_HS2LP_TIME(lbcc) (((lbcc) & 0xff) << 24)
+#define PHY_LP2HS_TIME(lbcc) (((lbcc) & 0xff) << 16)
+#define MAX_RD_TIME(lbcc) ((lbcc) & 0x7fff)
+#define PHY_HS2LP_TIME_V131(lbcc) (((lbcc) & 0x3ff) << 16)
+#define PHY_LP2HS_TIME_V131(lbcc) ((lbcc) & 0x3ff)
+
+#define DSI_PHY_RSTZ 0xa0
+#define PHY_DISFORCEPLL 0
+#define PHY_ENFORCEPLL BIT(3)
+#define PHY_DISABLECLK 0
+#define PHY_ENABLECLK BIT(2)
+#define PHY_RSTZ 0
+#define PHY_UNRSTZ BIT(1)
+#define PHY_SHUTDOWNZ 0
+#define PHY_UNSHUTDOWNZ BIT(0)
+
+#define DSI_PHY_IF_CFG 0xa4
+#define PHY_STOP_WAIT_TIME(cycle) (((cycle) & 0xff) << 8)
+#define N_LANES(n) (((n) - 1) & 0x3)
+
+#define DSI_PHY_ULPS_CTRL 0xa8
+#define DSI_PHY_TX_TRIGGERS 0xac
+
+#define DSI_PHY_STATUS 0xb0
+#define PHY_STOP_STATE_CLK_LANE BIT(2)
+#define PHY_LOCK BIT(0)
+
+#define DSI_PHY_TST_CTRL0 0xb4
+#define PHY_TESTCLK BIT(1)
+#define PHY_UNTESTCLK 0
+#define PHY_TESTCLR BIT(0)
+#define PHY_UNTESTCLR 0
+
+#define DSI_PHY_TST_CTRL1 0xb8
+#define PHY_TESTEN BIT(16)
+#define PHY_UNTESTEN 0
+#define PHY_TESTDOUT(n) (((n) & 0xff) << 8)
+#define PHY_TESTDIN(n) ((n) & 0xff)
+
+#define DSI_INT_ST0 0xbc
+#define DSI_INT_ST1 0xc0
+#define DSI_INT_MSK0 0xc4
+#define DSI_INT_MSK1 0xc8
+
+#define DSI_PHY_TMR_RD_CFG 0xf4
+#define MAX_RD_TIME_V131(lbcc) ((lbcc) & 0x7fff)
+
+#define PHY_STATUS_TIMEOUT_US 10000
+#define CMD_PKT_STATUS_TIMEOUT_US 20000
+
+#define MSEC_PER_SEC 1000
+
+struct dw_mipi_dsi {
+ struct mipi_dsi_host dsi_host;
+ struct mipi_dsi_device *device;
+ void __iomem *base;
+ unsigned int lane_mbps; /* per lane */
+ u32 channel;
+ unsigned int max_data_lanes;
+ const struct mipi_dsi_phy_ops *phy_ops;
+};
+
+static int dsi_mode_vrefresh(struct display_timing *timings)
+{
+ int refresh = 0;
+ unsigned int calc_val;
+ u32 htotal = timings->hactive.typ + timings->hfront_porch.typ +
+ timings->hback_porch.typ + timings->hsync_len.typ;
+ u32 vtotal = timings->vactive.typ + timings->vfront_porch.typ +
+ timings->vback_porch.typ + timings->vsync_len.typ;
+
+ if (htotal > 0 && vtotal > 0) {
+ calc_val = timings->pixelclock.typ;
+ calc_val /= htotal;
+ refresh = (calc_val + vtotal / 2) / vtotal;
+ }
+
+ return refresh;
+}
+
+/*
+ * The controller should generate 2 frames before
+ * preparing the peripheral.
+ */
+static void dw_mipi_dsi_wait_for_two_frames(struct display_timing *timings)
+{
+ int refresh, two_frames;
+
+ refresh = dsi_mode_vrefresh(timings);
+ two_frames = DIV_ROUND_UP(MSEC_PER_SEC, refresh) * 2;
+ mdelay(two_frames);
+}
+
+static inline struct dw_mipi_dsi *host_to_dsi(struct mipi_dsi_host *host)
+{
+ return container_of(host, struct dw_mipi_dsi, dsi_host);
+}
+
+static inline void dsi_write(struct dw_mipi_dsi *dsi, u32 reg, u32 val)
+{
+ writel(val, dsi->base + reg);
+}
+
+static inline u32 dsi_read(struct dw_mipi_dsi *dsi, u32 reg)
+{
+ return readl(dsi->base + reg);
+}
+
+static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *device)
+{
+ struct dw_mipi_dsi *dsi = host_to_dsi(host);
+
+ if (device->lanes > dsi->max_data_lanes) {
+ dev_err(device->dev,
+ "the number of data lanes(%u) is too many\n",
+ device->lanes);
+ return -EINVAL;
+ }
+
+ dsi->channel = device->channel;
+
+ return 0;
+}
+
+static void dw_mipi_message_config(struct dw_mipi_dsi *dsi,
+ const struct mipi_dsi_msg *msg)
+{
+ bool lpm = msg->flags & MIPI_DSI_MSG_USE_LPM;
+ u32 val = 0;
+
+ if (msg->flags & MIPI_DSI_MSG_REQ_ACK)
+ val |= ACK_RQST_EN;
+ if (lpm)
+ val |= CMD_MODE_ALL_LP;
+
+ dsi_write(dsi, DSI_LPCLK_CTRL, lpm ? 0 : PHY_TXREQUESTCLKHS);
+ dsi_write(dsi, DSI_CMD_MODE_CFG, val);
+}
+
+static int dw_mipi_dsi_gen_pkt_hdr_write(struct dw_mipi_dsi *dsi, u32 hdr_val)
+{
+ int ret;
+ u32 val, mask;
+
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, !(val & GEN_CMD_FULL),
+ CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ dev_err(dsi->dev, "failed to get available command FIFO\n");
+ return ret;
+ }
+
+ dsi_write(dsi, DSI_GEN_HDR, hdr_val);
+
+ mask = GEN_CMD_EMPTY | GEN_PLD_W_EMPTY;
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, (val & mask) == mask,
+ CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ dev_err(dsi->dev, "failed to write command FIFO\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dw_mipi_dsi_write(struct dw_mipi_dsi *dsi,
+ const struct mipi_dsi_packet *packet)
+{
+ const u8 *tx_buf = packet->payload;
+ int len = packet->payload_length, pld_data_bytes = sizeof(u32), ret;
+ __le32 word;
+ u32 val;
+
+ while (len) {
+ if (len < pld_data_bytes) {
+ word = 0;
+ memcpy(&word, tx_buf, len);
+ dsi_write(dsi, DSI_GEN_PLD_DATA, le32_to_cpu(word));
+ len = 0;
+ } else {
+ memcpy(&word, tx_buf, pld_data_bytes);
+ dsi_write(dsi, DSI_GEN_PLD_DATA, le32_to_cpu(word));
+ tx_buf += pld_data_bytes;
+ len -= pld_data_bytes;
+ }
+
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, !(val & GEN_PLD_W_FULL),
+ CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ dev_err(dsi->dev,
+ "failed to get available write payload FIFO\n");
+ return ret;
+ }
+ }
+
+ word = 0;
+ memcpy(&word, packet->header, sizeof(packet->header));
+ return dw_mipi_dsi_gen_pkt_hdr_write(dsi, le32_to_cpu(word));
+}
+
+static int dw_mipi_dsi_read(struct dw_mipi_dsi *dsi,
+ const struct mipi_dsi_msg *msg)
+{
+ int i, j, ret, len = msg->rx_len;
+ u8 *buf = msg->rx_buf;
+ u32 val;
+
+ /* Wait end of the read operation */
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, !(val & GEN_RD_CMD_BUSY),
+ CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ dev_err(dsi->dev, "Timeout during read operation\n");
+ return ret;
+ }
+
+ for (i = 0; i < len; i += 4) {
+ /* Read fifo must not be empty before all bytes are read */
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, !(val & GEN_PLD_R_EMPTY),
+ CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ dev_err(dsi->dev, "Read payload FIFO is empty\n");
+ return ret;
+ }
+
+ val = dsi_read(dsi, DSI_GEN_PLD_DATA);
+ for (j = 0; j < 4 && j + i < len; j++)
+ buf[i + j] = val >> (8 * j);
+ }
+
+ return ret;
+}
+
+static ssize_t dw_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg)
+{
+ struct dw_mipi_dsi *dsi = host_to_dsi(host);
+ struct mipi_dsi_packet packet;
+ int ret, nb_bytes;
+
+ ret = mipi_dsi_create_packet(&packet, msg);
+ if (ret) {
+ dev_err(dsi->dev, "failed to create packet: %d\n", ret);
+ return ret;
+ }
+
+ dw_mipi_message_config(dsi, msg);
+
+ ret = dw_mipi_dsi_write(dsi, &packet);
+ if (ret)
+ return ret;
+
+ if (msg->rx_buf && msg->rx_len) {
+ ret = dw_mipi_dsi_read(dsi, msg);
+ if (ret)
+ return ret;
+ nb_bytes = msg->rx_len;
+ } else {
+ nb_bytes = packet.size;
+ }
+
+ return nb_bytes;
+}
+
+static const struct mipi_dsi_host_ops dw_mipi_dsi_host_ops = {
+ .attach = dw_mipi_dsi_host_attach,
+ .transfer = dw_mipi_dsi_host_transfer,
+};
+
+static void dw_mipi_dsi_video_mode_config(struct dw_mipi_dsi *dsi)
+{
+ struct mipi_dsi_device *device = dsi->device;
+ u32 val;
+
+ /*
+ * TODO dw drv improvements
+ * enabling low power is panel-dependent, we should use the
+ * panel configuration here...
+ */
+ val = ENABLE_LOW_POWER;
+
+ if (device->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
+ val |= VID_MODE_TYPE_BURST;
+ else if (device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
+ val |= VID_MODE_TYPE_NON_BURST_SYNC_PULSES;
+ else
+ val |= VID_MODE_TYPE_NON_BURST_SYNC_EVENTS;
+
+ dsi_write(dsi, DSI_VID_MODE_CFG, val);
+}
+
+static void dw_mipi_dsi_set_mode(struct dw_mipi_dsi *dsi,
+ unsigned long mode_flags)
+{
+ const struct mipi_dsi_phy_ops *phy_ops = dsi->phy_ops;
+
+ dsi_write(dsi, DSI_PWR_UP, RESET);
+
+ if (mode_flags & MIPI_DSI_MODE_VIDEO) {
+ dsi_write(dsi, DSI_MODE_CFG, ENABLE_VIDEO_MODE);
+ dw_mipi_dsi_video_mode_config(dsi);
+ dsi_write(dsi, DSI_LPCLK_CTRL, PHY_TXREQUESTCLKHS);
+ } else {
+ dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE);
+ }
+
+ if (phy_ops->post_set_mode)
+ phy_ops->post_set_mode(dsi->device, mode_flags);
+
+ dsi_write(dsi, DSI_PWR_UP, POWERUP);
+}
+
+static void dw_mipi_dsi_init_pll(struct dw_mipi_dsi *dsi)
+{
+ /*
+ * The maximum permitted escape clock is 20MHz and it is derived from
+ * lanebyteclk, which is running at "lane_mbps / 8". Thus we want:
+ *
+ * (lane_mbps >> 3) / esc_clk_division < 20
+ * which is:
+ * (lane_mbps >> 3) / 20 > esc_clk_division
+ */
+ u32 esc_clk_division = (dsi->lane_mbps >> 3) / 20 + 1;
+
+ dsi_write(dsi, DSI_PWR_UP, RESET);
+
+ /*
+ * TODO dw drv improvements
+ * timeout clock division should be computed with the
+ * high speed transmission counter timeout and byte lane...
+ */
+ dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVISION(10) |
+ TX_ESC_CLK_DIVISION(esc_clk_division));
+}
+
+static void dw_mipi_dsi_dpi_config(struct dw_mipi_dsi *dsi,
+ struct display_timing *timings)
+{
+ struct mipi_dsi_device *device = dsi->device;
+ u32 val = 0, color = 0;
+
+ switch (device->format) {
+ case MIPI_DSI_FMT_RGB888:
+ color = DPI_COLOR_CODING_24BIT;
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ color = DPI_COLOR_CODING_18BIT_2 | LOOSELY18_EN;
+ break;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ color = DPI_COLOR_CODING_18BIT_1;
+ break;
+ case MIPI_DSI_FMT_RGB565:
+ color = DPI_COLOR_CODING_16BIT_1;
+ break;
+ }
+
+ if (device->mode_flags & DISPLAY_FLAGS_VSYNC_HIGH)
+ val |= VSYNC_ACTIVE_LOW;
+ if (device->mode_flags & DISPLAY_FLAGS_HSYNC_HIGH)
+ val |= HSYNC_ACTIVE_LOW;
+
+ dsi_write(dsi, DSI_DPI_VCID, DPI_VCID(dsi->channel));
+ dsi_write(dsi, DSI_DPI_COLOR_CODING, color);
+ dsi_write(dsi, DSI_DPI_CFG_POL, val);
+ /*
+ * TODO dw drv improvements
+ * largest packet sizes during hfp or during vsa/vpb/vfp
+ * should be computed according to byte lane, lane number and only
+ * if sending lp cmds in high speed is enable (PHY_TXREQUESTCLKHS)
+ */
+ dsi_write(dsi, DSI_DPI_LP_CMD_TIM, OUTVACT_LPCMD_TIME(4)
+ | INVACT_LPCMD_TIME(4));
+}
+
+static void dw_mipi_dsi_packet_handler_config(struct dw_mipi_dsi *dsi)
+{
+ dsi_write(dsi, DSI_PCKHDL_CFG, CRC_RX_EN | ECC_RX_EN | BTA_EN);
+}
+
+static void dw_mipi_dsi_video_packet_config(struct dw_mipi_dsi *dsi,
+ struct display_timing *timings)
+{
+ /*
+ * TODO dw drv improvements
+ * only burst mode is supported here. For non-burst video modes,
+ * we should compute DSI_VID_PKT_SIZE, DSI_VCCR.NUMC &
+ * DSI_VNPCR.NPSIZE... especially because this driver supports
+ * non-burst video modes, see dw_mipi_dsi_video_mode_config()...
+ */
+ dsi_write(dsi, DSI_VID_PKT_SIZE, VID_PKT_SIZE(timings->hactive.typ));
+}
+
+static void dw_mipi_dsi_command_mode_config(struct dw_mipi_dsi *dsi)
+{
+ const struct mipi_dsi_phy_ops *phy_ops = dsi->phy_ops;
+
+ /*
+ * TODO dw drv improvements
+ * compute high speed transmission counter timeout according
+ * to the timeout clock division (TO_CLK_DIVISION) and byte lane...
+ */
+ dsi_write(dsi, DSI_TO_CNT_CFG, HSTX_TO_CNT(1000) | LPRX_TO_CNT(1000));
+ /*
+ * TODO dw drv improvements
+ * the Bus-Turn-Around Timeout Counter should be computed
+ * according to byte lane...
+ */
+ dsi_write(dsi, DSI_BTA_TO_CNT, 0xd00);
+ dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE);
+
+ if (phy_ops->post_set_mode)
+ phy_ops->post_set_mode(dsi->device, 0);
+}
+
+/* Get lane byte clock cycles. */
+static u32 dw_mipi_dsi_get_hcomponent_lbcc(struct dw_mipi_dsi *dsi,
+ struct display_timing *timings,
+ u32 hcomponent)
+{
+ u32 frac, lbcc;
+
+ lbcc = hcomponent * dsi->lane_mbps * MSEC_PER_SEC / 8;
+
+ frac = lbcc % (timings->pixelclock.typ / 1000);
+ lbcc = lbcc / (timings->pixelclock.typ / 1000);
+ if (frac)
+ lbcc++;
+
+ return lbcc;
+}
+
+static void dw_mipi_dsi_line_timer_config(struct dw_mipi_dsi *dsi,
+ struct display_timing *timings)
+{
+ u32 htotal, hsa, hbp, lbcc;
+
+ htotal = timings->hactive.typ + timings->hfront_porch.typ +
+ timings->hback_porch.typ + timings->hsync_len.typ;
+
+ hsa = timings->hback_porch.typ;
+ hbp = timings->hsync_len.typ;
+
+ /*
+ * TODO dw drv improvements
+ * computations below may be improved...
+ */
+ lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, timings, htotal);
+ dsi_write(dsi, DSI_VID_HLINE_TIME, lbcc);
+
+ lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, timings, hsa);
+ dsi_write(dsi, DSI_VID_HSA_TIME, lbcc);
+
+ lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, timings, hbp);
+ dsi_write(dsi, DSI_VID_HBP_TIME, lbcc);
+}
+
+static void dw_mipi_dsi_vertical_timing_config(struct dw_mipi_dsi *dsi,
+ struct display_timing *timings)
+{
+ u32 vactive, vsa, vfp, vbp;
+
+ vactive = timings->vactive.typ;
+ vsa = timings->vback_porch.typ;
+ vfp = timings->vfront_porch.typ;
+ vbp = timings->vsync_len.typ;
+
+ dsi_write(dsi, DSI_VID_VACTIVE_LINES, vactive);
+ dsi_write(dsi, DSI_VID_VSA_LINES, vsa);
+ dsi_write(dsi, DSI_VID_VFP_LINES, vfp);
+ dsi_write(dsi, DSI_VID_VBP_LINES, vbp);
+}
+
+static void dw_mipi_dsi_dphy_timing_config(struct dw_mipi_dsi *dsi)
+{
+ u32 hw_version;
+
+ /*
+ * TODO dw drv improvements
+ * data & clock lane timers should be computed according to panel
+ * blankings and to the automatic clock lane control mode...
+ * note: DSI_PHY_TMR_CFG.MAX_RD_TIME should be in line with
+ * DSI_CMD_MODE_CFG.MAX_RD_PKT_SIZE_LP (see CMD_MODE_ALL_LP)
+ */
+
+ hw_version = dsi_read(dsi, DSI_VERSION) & VERSION;
+
+ if (hw_version >= HWVER_131) {
+ dsi_write(dsi, DSI_PHY_TMR_CFG, PHY_HS2LP_TIME_V131(0x40) |
+ PHY_LP2HS_TIME_V131(0x40));
+ dsi_write(dsi, DSI_PHY_TMR_RD_CFG, MAX_RD_TIME_V131(10000));
+ } else {
+ dsi_write(dsi, DSI_PHY_TMR_CFG, PHY_HS2LP_TIME(0x40) |
+ PHY_LP2HS_TIME(0x40) | MAX_RD_TIME(10000));
+ }
+
+ dsi_write(dsi, DSI_PHY_TMR_LPCLK_CFG, PHY_CLKHS2LP_TIME(0x40)
+ | PHY_CLKLP2HS_TIME(0x40));
+}
+
+static void dw_mipi_dsi_dphy_interface_config(struct dw_mipi_dsi *dsi)
+{
+ struct mipi_dsi_device *device = dsi->device;
+
+ /*
+ * TODO dw drv improvements
+ * stop wait time should be the maximum between host dsi
+ * and panel stop wait times
+ */
+ dsi_write(dsi, DSI_PHY_IF_CFG, PHY_STOP_WAIT_TIME(0x20) |
+ N_LANES(device->lanes));
+}
+
+static void dw_mipi_dsi_dphy_init(struct dw_mipi_dsi *dsi)
+{
+ /* Clear PHY state */
+ dsi_write(dsi, DSI_PHY_RSTZ, PHY_DISFORCEPLL | PHY_DISABLECLK
+ | PHY_RSTZ | PHY_SHUTDOWNZ);
+ dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLR);
+ dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLR);
+ dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLR);
+}
+
+static void dw_mipi_dsi_dphy_enable(struct dw_mipi_dsi *dsi)
+{
+ u32 val;
+ int ret;
+
+ dsi_write(dsi, DSI_PHY_RSTZ, PHY_ENFORCEPLL | PHY_ENABLECLK |
+ PHY_UNRSTZ | PHY_UNSHUTDOWNZ);
+
+ ret = readl_poll_timeout(dsi->base + DSI_PHY_STATUS, val,
+ val & PHY_LOCK, PHY_STATUS_TIMEOUT_US);
+ if (ret)
+ dev_warn(dsi->dev, "failed to wait phy lock state\n");
+
+ ret = readl_poll_timeout(dsi->base + DSI_PHY_STATUS,
+ val, val & PHY_STOP_STATE_CLK_LANE,
+ PHY_STATUS_TIMEOUT_US);
+ if (ret)
+ dev_warn(dsi->dev, "failed to wait phy clk lane stop state\n");
+}
+
+static void dw_mipi_dsi_clear_err(struct dw_mipi_dsi *dsi)
+{
+ dsi_read(dsi, DSI_INT_ST0);
+ dsi_read(dsi, DSI_INT_ST1);
+ dsi_write(dsi, DSI_INT_MSK0, 0);
+ dsi_write(dsi, DSI_INT_MSK1, 0);
+}
+
+static void dw_mipi_dsi_bridge_set(struct dw_mipi_dsi *dsi,
+ struct display_timing *timings)
+{
+ const struct mipi_dsi_phy_ops *phy_ops = dsi->phy_ops;
+ struct mipi_dsi_device *device = dsi->device;
+ int ret;
+
+ ret = phy_ops->get_lane_mbps(dsi->device, timings, device->lanes,
+ device->format, &dsi->lane_mbps);
+ if (ret)
+ dev_warn(dsi->dev, "Phy get_lane_mbps() failed\n");
+
+ dw_mipi_dsi_init_pll(dsi);
+ dw_mipi_dsi_dpi_config(dsi, timings);
+ dw_mipi_dsi_packet_handler_config(dsi);
+ dw_mipi_dsi_video_mode_config(dsi);
+ dw_mipi_dsi_video_packet_config(dsi, timings);
+ dw_mipi_dsi_command_mode_config(dsi);
+ dw_mipi_dsi_line_timer_config(dsi, timings);
+ dw_mipi_dsi_vertical_timing_config(dsi, timings);
+
+ dw_mipi_dsi_dphy_init(dsi);
+ dw_mipi_dsi_dphy_timing_config(dsi);
+ dw_mipi_dsi_dphy_interface_config(dsi);
+
+ dw_mipi_dsi_clear_err(dsi);
+
+ ret = phy_ops->init(dsi->device);
+ if (ret)
+ dev_warn(dsi->dev, "Phy init() failed\n");
+
+ dw_mipi_dsi_dphy_enable(dsi);
+
+ dw_mipi_dsi_wait_for_two_frames(timings);
+
+ /* Switch to cmd mode for panel-bridge pre_enable & panel prepare */
+ dw_mipi_dsi_set_mode(dsi, 0);
+}
+
+static int dw_mipi_dsi_init(struct udevice *dev,
+ struct mipi_dsi_device *device,
+ struct display_timing *timings,
+ unsigned int max_data_lanes,
+ const struct mipi_dsi_phy_ops *phy_ops)
+{
+ struct dw_mipi_dsi *dsi = dev_get_priv(dev);
+ struct clk clk;
+ int ret;
+
+ if (!phy_ops->init || !phy_ops->get_lane_mbps) {
+ dev_err(device->dev, "Phy not properly configured\n");
+ return -ENODEV;
+ }
+
+ dsi->phy_ops = phy_ops;
+ dsi->max_data_lanes = max_data_lanes;
+ dsi->device = device;
+ dsi->dsi_host.ops = &dw_mipi_dsi_host_ops;
+ device->host = &dsi->dsi_host;
+
+ dsi->base = (void *)dev_read_addr(device->dev);
+ if ((fdt_addr_t)dsi->base == FDT_ADDR_T_NONE) {
+ dev_err(device->dev, "dsi dt register address error\n");
+ return -EINVAL;
+ }
+
+ ret = clk_get_by_name(device->dev, "px_clk", &clk);
+ if (ret) {
+ dev_err(device->dev, "peripheral clock get error %d\n", ret);
+ return ret;
+ }
+
+ /* get the pixel clock set by the clock framework */
+ timings->pixelclock.typ = clk_get_rate(&clk);
+
+ dw_mipi_dsi_bridge_set(dsi, timings);
+
+ return 0;
+}
+
+static int dw_mipi_dsi_enable(struct udevice *dev)
+{
+ struct dw_mipi_dsi *dsi = dev_get_priv(dev);
+
+ /* Switch to video mode for panel-bridge enable & panel enable */
+ dw_mipi_dsi_set_mode(dsi, MIPI_DSI_MODE_VIDEO);
+
+ return 0;
+}
+
+struct dsi_host_ops dw_mipi_dsi_ops = {
+ .init = dw_mipi_dsi_init,
+ .enable = dw_mipi_dsi_enable,
+};
+
+static int dw_mipi_dsi_probe(struct udevice *dev)
+{
+ return 0;
+}
+
+static const struct udevice_id dw_mipi_dsi_ids[] = {
+ { .compatible = "synopsys,dw-mipi-dsi" },
+ { }
+};
+
+U_BOOT_DRIVER(dw_mipi_dsi) = {
+ .name = "dw_mipi_dsi",
+ .id = UCLASS_DSI_HOST,
+ .of_match = dw_mipi_dsi_ids,
+ .probe = dw_mipi_dsi_probe,
+ .ops = &dw_mipi_dsi_ops,
+ .priv_auto_alloc_size = sizeof(struct dw_mipi_dsi),
+};
+
+MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
+MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>");
+MODULE_AUTHOR("Yannick Fertré <yannick.fertre@st.com>");
+MODULE_DESCRIPTION("DW MIPI DSI host controller driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dw-mipi-dsi");
diff --git a/drivers/video/mipi_dsi.c b/drivers/video/mipi_dsi.c
new file mode 100644
index 0000000000..cdc3ef58ab
--- /dev/null
+++ b/drivers/video/mipi_dsi.c
@@ -0,0 +1,828 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MIPI DSI Bus
+ *
+ * Copyright (C) 2012-2013, Samsung Electronics, Co., Ltd.
+ * Copyright (C) 2019 STMicroelectronics - All Rights Reserved
+ * Andrzej Hajda <a.hajda@samsung.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Mipi_dsi.c contains a set of dsi helpers.
+ * This file is inspired from the drm helper file drivers/gpu/drm/drm_mipi_dsi.c
+ * (kernel linux).
+ *
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <display.h>
+#include <dm.h>
+#include <mipi_display.h>
+#include <mipi_dsi.h>
+
+/**
+ * DOC: dsi helpers
+ *
+ * These functions contain some common logic and helpers to deal with MIPI DSI
+ * peripherals.
+ *
+ * Helpers are provided for a number of standard MIPI DSI command as well as a
+ * subset of the MIPI DCS command set.
+ */
+
+/**
+ * mipi_dsi_attach - attach a DSI device to its DSI host
+ * @dsi: DSI peripheral
+ */
+int mipi_dsi_attach(struct mipi_dsi_device *dsi)
+{
+ const struct mipi_dsi_host_ops *ops = dsi->host->ops;
+
+ if (!ops || !ops->attach)
+ return -ENOSYS;
+
+ return ops->attach(dsi->host, dsi);
+}
+EXPORT_SYMBOL(mipi_dsi_attach);
+
+/**
+ * mipi_dsi_detach - detach a DSI device from its DSI host
+ * @dsi: DSI peripheral
+ */
+int mipi_dsi_detach(struct mipi_dsi_device *dsi)
+{
+ const struct mipi_dsi_host_ops *ops = dsi->host->ops;
+
+ if (!ops || !ops->detach)
+ return -ENOSYS;
+
+ return ops->detach(dsi->host, dsi);
+}
+EXPORT_SYMBOL(mipi_dsi_detach);
+
+/**
+ * mipi_dsi_device_transfer - transfer message to a DSI device
+ * @dsi: DSI peripheral
+ * @msg: message
+ */
+static ssize_t mipi_dsi_device_transfer(struct mipi_dsi_device *dsi,
+ struct mipi_dsi_msg *msg)
+{
+ const struct mipi_dsi_host_ops *ops = dsi->host->ops;
+
+ if (!ops || !ops->transfer)
+ return -ENOSYS;
+
+ if (dsi->mode_flags & MIPI_DSI_MODE_LPM)
+ msg->flags |= MIPI_DSI_MSG_USE_LPM;
+
+ return ops->transfer(dsi->host, msg);
+}
+
+/**
+ * mipi_dsi_packet_format_is_short - check if a packet is of the short format
+ * @type: MIPI DSI data type of the packet
+ *
+ * Return: true if the packet for the given data type is a short packet, false
+ * otherwise.
+ */
+bool mipi_dsi_packet_format_is_short(u8 type)
+{
+ switch (type) {
+ case MIPI_DSI_V_SYNC_START:
+ case MIPI_DSI_V_SYNC_END:
+ case MIPI_DSI_H_SYNC_START:
+ case MIPI_DSI_H_SYNC_END:
+ case MIPI_DSI_END_OF_TRANSMISSION:
+ case MIPI_DSI_COLOR_MODE_OFF:
+ case MIPI_DSI_COLOR_MODE_ON:
+ case MIPI_DSI_SHUTDOWN_PERIPHERAL:
+ case MIPI_DSI_TURN_ON_PERIPHERAL:
+ case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM:
+ case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM:
+ case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM:
+ case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM:
+ case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM:
+ case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM:
+ case MIPI_DSI_DCS_SHORT_WRITE:
+ case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
+ case MIPI_DSI_DCS_READ:
+ case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE:
+ return true;
+ }
+
+ return false;
+}
+EXPORT_SYMBOL(mipi_dsi_packet_format_is_short);
+
+/**
+ * mipi_dsi_packet_format_is_long - check if a packet is of the long format
+ * @type: MIPI DSI data type of the packet
+ *
+ * Return: true if the packet for the given data type is a long packet, false
+ * otherwise.
+ */
+bool mipi_dsi_packet_format_is_long(u8 type)
+{
+ switch (type) {
+ case MIPI_DSI_NULL_PACKET:
+ case MIPI_DSI_BLANKING_PACKET:
+ case MIPI_DSI_GENERIC_LONG_WRITE:
+ case MIPI_DSI_DCS_LONG_WRITE:
+ case MIPI_DSI_LOOSELY_PACKED_PIXEL_STREAM_YCBCR20:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR24:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR16:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_30:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_36:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR12:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_16:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_18:
+ case MIPI_DSI_PIXEL_STREAM_3BYTE_18:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_24:
+ return true;
+ }
+
+ return false;
+}
+EXPORT_SYMBOL(mipi_dsi_packet_format_is_long);
+
+/**
+ * mipi_dsi_create_packet - create a packet from a message according to the
+ * DSI protocol
+ * @packet: pointer to a DSI packet structure
+ * @msg: message to translate into a packet
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_create_packet(struct mipi_dsi_packet *packet,
+ const struct mipi_dsi_msg *msg)
+{
+ if (!packet || !msg)
+ return -EINVAL;
+
+ /* do some minimum sanity checking */
+ if (!mipi_dsi_packet_format_is_short(msg->type) &&
+ !mipi_dsi_packet_format_is_long(msg->type))
+ return -EINVAL;
+
+ if (msg->channel > 3)
+ return -EINVAL;
+
+ memset(packet, 0, sizeof(*packet));
+ packet->header[0] = ((msg->channel & 0x3) << 6) | (msg->type & 0x3f);
+
+ /* TODO: compute ECC if hardware support is not available */
+
+ /*
+ * Long write packets contain the word count in header bytes 1 and 2.
+ * The payload follows the header and is word count bytes long.
+ *
+ * Short write packets encode up to two parameters in header bytes 1
+ * and 2.
+ */
+ if (mipi_dsi_packet_format_is_long(msg->type)) {
+ packet->header[1] = (msg->tx_len >> 0) & 0xff;
+ packet->header[2] = (msg->tx_len >> 8) & 0xff;
+
+ packet->payload_length = msg->tx_len;
+ packet->payload = msg->tx_buf;
+ } else {
+ const u8 *tx = msg->tx_buf;
+
+ packet->header[1] = (msg->tx_len > 0) ? tx[0] : 0;
+ packet->header[2] = (msg->tx_len > 1) ? tx[1] : 0;
+ }
+
+ packet->size = sizeof(packet->header) + packet->payload_length;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_create_packet);
+
+/**
+ * mipi_dsi_shutdown_peripheral() - sends a Shutdown Peripheral command
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_shutdown_peripheral(struct mipi_dsi_device *dsi)
+{
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .type = MIPI_DSI_SHUTDOWN_PERIPHERAL,
+ .tx_buf = (u8 [2]) { 0, 0 },
+ .tx_len = 2,
+ };
+ int ret = mipi_dsi_device_transfer(dsi, &msg);
+
+ return (ret < 0) ? ret : 0;
+}
+EXPORT_SYMBOL(mipi_dsi_shutdown_peripheral);
+
+/**
+ * mipi_dsi_turn_on_peripheral() - sends a Turn On Peripheral command
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_turn_on_peripheral(struct mipi_dsi_device *dsi)
+{
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .type = MIPI_DSI_TURN_ON_PERIPHERAL,
+ .tx_buf = (u8 [2]) { 0, 0 },
+ .tx_len = 2,
+ };
+ int ret = mipi_dsi_device_transfer(dsi, &msg);
+
+ return (ret < 0) ? ret : 0;
+}
+EXPORT_SYMBOL(mipi_dsi_turn_on_peripheral);
+
+/*
+ * mipi_dsi_set_maximum_return_packet_size() - specify the maximum size of the
+ * the payload in a long packet transmitted from the peripheral back to the
+ * host processor
+ * @dsi: DSI peripheral device
+ * @value: the maximum size of the payload
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_set_maximum_return_packet_size(struct mipi_dsi_device *dsi,
+ u16 value)
+{
+ u8 tx[2] = { value & 0xff, value >> 8 };
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .type = MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE,
+ .tx_len = sizeof(tx),
+ .tx_buf = tx,
+ };
+ int ret = mipi_dsi_device_transfer(dsi, &msg);
+
+ return (ret < 0) ? ret : 0;
+}
+EXPORT_SYMBOL(mipi_dsi_set_maximum_return_packet_size);
+
+/**
+ * mipi_dsi_generic_write() - transmit data using a generic write packet
+ * @dsi: DSI peripheral device
+ * @payload: buffer containing the payload
+ * @size: size of payload buffer
+ *
+ * This function will automatically choose the right data type depending on
+ * the payload length.
+ *
+ * Return: The number of bytes transmitted on success or a negative error code
+ * on failure.
+ */
+ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload,
+ size_t size)
+{
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .tx_buf = payload,
+ .tx_len = size
+ };
+
+ switch (size) {
+ case 0:
+ msg.type = MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM;
+ break;
+
+ case 1:
+ msg.type = MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM;
+ break;
+
+ case 2:
+ msg.type = MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM;
+ break;
+
+ default:
+ msg.type = MIPI_DSI_GENERIC_LONG_WRITE;
+ break;
+ }
+
+ return mipi_dsi_device_transfer(dsi, &msg);
+}
+EXPORT_SYMBOL(mipi_dsi_generic_write);
+
+/**
+ * mipi_dsi_generic_read() - receive data using a generic read packet
+ * @dsi: DSI peripheral device
+ * @params: buffer containing the request parameters
+ * @num_params: number of request parameters
+ * @data: buffer in which to return the received data
+ * @size: size of receive buffer
+ *
+ * This function will automatically choose the right data type depending on
+ * the number of parameters passed in.
+ *
+ * Return: The number of bytes successfully read or a negative error code on
+ * failure.
+ */
+ssize_t mipi_dsi_generic_read(struct mipi_dsi_device *dsi, const void *params,
+ size_t num_params, void *data, size_t size)
+{
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .tx_len = num_params,
+ .tx_buf = params,
+ .rx_len = size,
+ .rx_buf = data
+ };
+
+ switch (num_params) {
+ case 0:
+ msg.type = MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM;
+ break;
+
+ case 1:
+ msg.type = MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM;
+ break;
+
+ case 2:
+ msg.type = MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return mipi_dsi_device_transfer(dsi, &msg);
+}
+EXPORT_SYMBOL(mipi_dsi_generic_read);
+
+/**
+ * mipi_dsi_dcs_write_buffer() - transmit a DCS command with payload
+ * @dsi: DSI peripheral device
+ * @data: buffer containing data to be transmitted
+ * @len: size of transmission buffer
+ *
+ * This function will automatically choose the right data type depending on
+ * the command payload length.
+ *
+ * Return: The number of bytes successfully transmitted or a negative error
+ * code on failure.
+ */
+ssize_t mipi_dsi_dcs_write_buffer(struct mipi_dsi_device *dsi,
+ const void *data, size_t len)
+{
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .tx_buf = data,
+ .tx_len = len
+ };
+
+ switch (len) {
+ case 0:
+ return -EINVAL;
+
+ case 1:
+ msg.type = MIPI_DSI_DCS_SHORT_WRITE;
+ break;
+
+ case 2:
+ msg.type = MIPI_DSI_DCS_SHORT_WRITE_PARAM;
+ break;
+
+ default:
+ msg.type = MIPI_DSI_DCS_LONG_WRITE;
+ break;
+ }
+
+ return mipi_dsi_device_transfer(dsi, &msg);
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer);
+
+/**
+ * mipi_dsi_dcs_write() - send DCS write command
+ * @dsi: DSI peripheral device
+ * @cmd: DCS command
+ * @data: buffer containing the command payload
+ * @len: command payload length
+ *
+ * This function will automatically choose the right data type depending on
+ * the command payload length.
+ *
+ * Return: The number of bytes successfully transmitted or a negative error
+ * code on failure.
+ */
+ssize_t mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, u8 cmd,
+ const void *data, size_t len)
+{
+ ssize_t err;
+ size_t size;
+ u8 *tx;
+
+ if (len > 0) {
+ size = 1 + len;
+
+ tx = kmalloc(size, GFP_KERNEL);
+ if (!tx)
+ return -ENOMEM;
+
+ /* concatenate the DCS command byte and the payload */
+ tx[0] = cmd;
+ memcpy(&tx[1], data, len);
+ } else {
+ tx = &cmd;
+ size = 1;
+ }
+
+ err = mipi_dsi_dcs_write_buffer(dsi, tx, size);
+
+ if (len > 0)
+ kfree(tx);
+
+ return err;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_write);
+
+/**
+ * mipi_dsi_dcs_read() - send DCS read request command
+ * @dsi: DSI peripheral device
+ * @cmd: DCS command
+ * @data: buffer in which to receive data
+ * @len: size of receive buffer
+ *
+ * Return: The number of bytes read or a negative error code on failure.
+ */
+ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, u8 cmd, void *data,
+ size_t len)
+{
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .type = MIPI_DSI_DCS_READ,
+ .tx_buf = &cmd,
+ .tx_len = 1,
+ .rx_buf = data,
+ .rx_len = len
+ };
+
+ return mipi_dsi_device_transfer(dsi, &msg);
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_read);
+
+/**
+ * mipi_dsi_dcs_nop() - send DCS nop packet
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_nop(struct mipi_dsi_device *dsi)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_NOP, NULL, 0);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_nop);
+
+/**
+ * mipi_dsi_dcs_soft_reset() - perform a software reset of the display module
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_soft_reset(struct mipi_dsi_device *dsi)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SOFT_RESET, NULL, 0);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_soft_reset);
+
+/**
+ * mipi_dsi_dcs_get_power_mode() - query the display module's current power
+ * mode
+ * @dsi: DSI peripheral device
+ * @mode: return location for the current power mode
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_get_power_mode(struct mipi_dsi_device *dsi, u8 *mode)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_read(dsi, MIPI_DCS_GET_POWER_MODE, mode,
+ sizeof(*mode));
+ if (err <= 0) {
+ if (err == 0)
+ err = -ENODATA;
+
+ return err;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_get_power_mode);
+
+/**
+ * mipi_dsi_dcs_get_pixel_format() - gets the pixel format for the RGB image
+ * data used by the interface
+ * @dsi: DSI peripheral device
+ * @format: return location for the pixel format
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_get_pixel_format(struct mipi_dsi_device *dsi, u8 *format)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_read(dsi, MIPI_DCS_GET_PIXEL_FORMAT, format,
+ sizeof(*format));
+ if (err <= 0) {
+ if (err == 0)
+ err = -ENODATA;
+
+ return err;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_get_pixel_format);
+
+/**
+ * mipi_dsi_dcs_enter_sleep_mode() - disable all unnecessary blocks inside the
+ * display module except interface communication
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_enter_sleep_mode(struct mipi_dsi_device *dsi)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_ENTER_SLEEP_MODE, NULL, 0);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_enter_sleep_mode);
+
+/**
+ * mipi_dsi_dcs_exit_sleep_mode() - enable all blocks inside the display
+ * module
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_exit_sleep_mode(struct mipi_dsi_device *dsi)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_EXIT_SLEEP_MODE, NULL, 0);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_exit_sleep_mode);
+
+/**
+ * mipi_dsi_dcs_set_display_off() - stop displaying the image data on the
+ * display device
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_display_off(struct mipi_dsi_device *dsi)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_OFF, NULL, 0);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_display_off);
+
+/**
+ * mipi_dsi_dcs_set_display_on() - start displaying the image data on the
+ * display device
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure
+ */
+int mipi_dsi_dcs_set_display_on(struct mipi_dsi_device *dsi)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_ON, NULL, 0);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_display_on);
+
+/**
+ * mipi_dsi_dcs_set_column_address() - define the column extent of the frame
+ * memory accessed by the host processor
+ * @dsi: DSI peripheral device
+ * @start: first column of frame memory
+ * @end: last column of frame memory
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_column_address(struct mipi_dsi_device *dsi, u16 start,
+ u16 end)
+{
+ u8 payload[4] = { start >> 8, start & 0xff, end >> 8, end & 0xff };
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, payload,
+ sizeof(payload));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_column_address);
+
+/**
+ * mipi_dsi_dcs_set_page_address() - define the page extent of the frame
+ * memory accessed by the host processor
+ * @dsi: DSI peripheral device
+ * @start: first page of frame memory
+ * @end: last page of frame memory
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_page_address(struct mipi_dsi_device *dsi, u16 start,
+ u16 end)
+{
+ u8 payload[4] = { start >> 8, start & 0xff, end >> 8, end & 0xff };
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_PAGE_ADDRESS, payload,
+ sizeof(payload));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_page_address);
+
+/**
+ * mipi_dsi_dcs_set_tear_off() - turn off the display module's Tearing Effect
+ * output signal on the TE signal line
+ * @dsi: DSI peripheral device
+ *
+ * Return: 0 on success or a negative error code on failure
+ */
+int mipi_dsi_dcs_set_tear_off(struct mipi_dsi_device *dsi)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_TEAR_OFF, NULL, 0);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_tear_off);
+
+/**
+ * mipi_dsi_dcs_set_tear_on() - turn on the display module's Tearing Effect
+ * output signal on the TE signal line.
+ * @dsi: DSI peripheral device
+ * @mode: the Tearing Effect Output Line mode
+ *
+ * Return: 0 on success or a negative error code on failure
+ */
+int mipi_dsi_dcs_set_tear_on(struct mipi_dsi_device *dsi,
+ enum mipi_dsi_dcs_tear_mode mode)
+{
+ u8 value = mode;
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_TEAR_ON, &value,
+ sizeof(value));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_tear_on);
+
+/**
+ * mipi_dsi_dcs_set_pixel_format() - sets the pixel format for the RGB image
+ * data used by the interface
+ * @dsi: DSI peripheral device
+ * @format: pixel format
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_pixel_format(struct mipi_dsi_device *dsi, u8 format)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_PIXEL_FORMAT, &format,
+ sizeof(format));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_pixel_format);
+
+/**
+ * mipi_dsi_dcs_set_tear_scanline() - set the scanline to use as trigger for
+ * the Tearing Effect output signal of the display module
+ * @dsi: DSI peripheral device
+ * @scanline: scanline to use as trigger
+ *
+ * Return: 0 on success or a negative error code on failure
+ */
+int mipi_dsi_dcs_set_tear_scanline(struct mipi_dsi_device *dsi, u16 scanline)
+{
+ u8 payload[3] = { MIPI_DCS_SET_TEAR_SCANLINE, scanline >> 8,
+ scanline & 0xff };
+ ssize_t err;
+
+ err = mipi_dsi_generic_write(dsi, payload, sizeof(payload));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_tear_scanline);
+
+/**
+ * mipi_dsi_dcs_set_display_brightness() - sets the brightness value of the
+ * display
+ * @dsi: DSI peripheral device
+ * @brightness: brightness value
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_set_display_brightness(struct mipi_dsi_device *dsi,
+ u16 brightness)
+{
+ u8 payload[2] = { brightness & 0xff, brightness >> 8 };
+ ssize_t err;
+
+ err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+ payload, sizeof(payload));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_set_display_brightness);
+
+/**
+ * mipi_dsi_dcs_get_display_brightness() - gets the current brightness value
+ * of the display
+ * @dsi: DSI peripheral device
+ * @brightness: brightness value
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_dcs_get_display_brightness(struct mipi_dsi_device *dsi,
+ u16 *brightness)
+{
+ ssize_t err;
+
+ err = mipi_dsi_dcs_read(dsi, MIPI_DCS_GET_DISPLAY_BRIGHTNESS,
+ brightness, sizeof(*brightness));
+ if (err <= 0) {
+ if (err == 0)
+ err = -ENODATA;
+
+ return err;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_get_display_brightness);
diff --git a/drivers/video/orisetech_otm8009a.c b/drivers/video/orisetech_otm8009a.c
new file mode 100644
index 0000000000..89d9cfdbb3
--- /dev/null
+++ b/drivers/video/orisetech_otm8009a.c
@@ -0,0 +1,379 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 STMicroelectronics - All Rights Reserved
+ * Author(s): Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
+ * Philippe Cornu <philippe.cornu@st.com> for STMicroelectronics.
+ *
+ * This otm8009a panel driver is inspired from the Linux Kernel driver
+ * drivers/gpu/drm/panel/panel-orisetech-otm8009a.c.
+ */
+#include <common.h>
+#include <backlight.h>
+#include <dm.h>
+#include <mipi_dsi.h>
+#include <panel.h>
+#include <asm/gpio.h>
+#include <power/regulator.h>
+
+#define OTM8009A_BACKLIGHT_DEFAULT 240
+#define OTM8009A_BACKLIGHT_MAX 255
+
+/* Manufacturer Command Set */
+#define MCS_ADRSFT 0x0000 /* Address Shift Function */
+#define MCS_PANSET 0xB3A6 /* Panel Type Setting */
+#define MCS_SD_CTRL 0xC0A2 /* Source Driver Timing Setting */
+#define MCS_P_DRV_M 0xC0B4 /* Panel Driving Mode */
+#define MCS_OSC_ADJ 0xC181 /* Oscillator Adjustment for Idle/Normal mode */
+#define MCS_RGB_VID_SET 0xC1A1 /* RGB Video Mode Setting */
+#define MCS_SD_PCH_CTRL 0xC480 /* Source Driver Precharge Control */
+#define MCS_NO_DOC1 0xC48A /* Command not documented */
+#define MCS_PWR_CTRL1 0xC580 /* Power Control Setting 1 */
+#define MCS_PWR_CTRL2 0xC590 /* Power Control Setting 2 for Normal Mode */
+#define MCS_PWR_CTRL4 0xC5B0 /* Power Control Setting 4 for DC Voltage */
+#define MCS_PANCTRLSET1 0xCB80 /* Panel Control Setting 1 */
+#define MCS_PANCTRLSET2 0xCB90 /* Panel Control Setting 2 */
+#define MCS_PANCTRLSET3 0xCBA0 /* Panel Control Setting 3 */
+#define MCS_PANCTRLSET4 0xCBB0 /* Panel Control Setting 4 */
+#define MCS_PANCTRLSET5 0xCBC0 /* Panel Control Setting 5 */
+#define MCS_PANCTRLSET6 0xCBD0 /* Panel Control Setting 6 */
+#define MCS_PANCTRLSET7 0xCBE0 /* Panel Control Setting 7 */
+#define MCS_PANCTRLSET8 0xCBF0 /* Panel Control Setting 8 */
+#define MCS_PANU2D1 0xCC80 /* Panel U2D Setting 1 */
+#define MCS_PANU2D2 0xCC90 /* Panel U2D Setting 2 */
+#define MCS_PANU2D3 0xCCA0 /* Panel U2D Setting 3 */
+#define MCS_PAND2U1 0xCCB0 /* Panel D2U Setting 1 */
+#define MCS_PAND2U2 0xCCC0 /* Panel D2U Setting 2 */
+#define MCS_PAND2U3 0xCCD0 /* Panel D2U Setting 3 */
+#define MCS_GOAVST 0xCE80 /* GOA VST Setting */
+#define MCS_GOACLKA1 0xCEA0 /* GOA CLKA1 Setting */
+#define MCS_GOACLKA3 0xCEB0 /* GOA CLKA3 Setting */
+#define MCS_GOAECLK 0xCFC0 /* GOA ECLK Setting */
+#define MCS_NO_DOC2 0xCFD0 /* Command not documented */
+#define MCS_GVDDSET 0xD800 /* GVDD/NGVDD */
+#define MCS_VCOMDC 0xD900 /* VCOM Voltage Setting */
+#define MCS_GMCT2_2P 0xE100 /* Gamma Correction 2.2+ Setting */
+#define MCS_GMCT2_2N 0xE200 /* Gamma Correction 2.2- Setting */
+#define MCS_NO_DOC3 0xF5B6 /* Command not documented */
+#define MCS_CMD2_ENA1 0xFF00 /* Enable Access Command2 "CMD2" */
+#define MCS_CMD2_ENA2 0xFF80 /* Enable Access Orise Command2 */
+
+struct otm8009a_panel_priv {
+ struct udevice *reg;
+ struct gpio_desc reset;
+ unsigned int lanes;
+ enum mipi_dsi_pixel_format format;
+ unsigned long mode_flags;
+};
+
+static const struct display_timing default_timing = {
+ .pixelclock.typ = 29700000,
+ .hactive.typ = 480,
+ .hfront_porch.typ = 98,
+ .hback_porch.typ = 98,
+ .hsync_len.typ = 32,
+ .vactive.typ = 800,
+ .vfront_porch.typ = 15,
+ .vback_porch.typ = 14,
+ .vsync_len.typ = 10,
+};
+
+static void otm8009a_dcs_write_buf(struct udevice *dev, const void *data,
+ size_t len)
+{
+ struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
+ struct mipi_dsi_device *device = plat->device;
+
+ if (mipi_dsi_dcs_write_buffer(device, data, len) < 0)
+ dev_err(dev, "mipi dsi dcs write buffer failed\n");
+}
+
+static void otm8009a_dcs_write_buf_hs(struct udevice *dev, const void *data,
+ size_t len)
+{
+ struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
+ struct mipi_dsi_device *device = plat->device;
+
+ /* data will be sent in dsi hs mode (ie. no lpm) */
+ device->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+ if (mipi_dsi_dcs_write_buffer(device, data, len) < 0)
+ dev_err(dev, "mipi dsi dcs write buffer failed\n");
+
+ /* restore back the dsi lpm mode */
+ device->mode_flags |= MIPI_DSI_MODE_LPM;
+}
+
+#define dcs_write_seq(dev, seq...) \
+({ \
+ static const u8 d[] = { seq }; \
+ otm8009a_dcs_write_buf(dev, d, ARRAY_SIZE(d)); \
+})
+
+#define dcs_write_seq_hs(dev, seq...) \
+({ \
+ static const u8 d[] = { seq }; \
+ otm8009a_dcs_write_buf_hs(dev, d, ARRAY_SIZE(d)); \
+})
+
+#define dcs_write_cmd_at(dev, cmd, seq...) \
+({ \
+ static const u16 c = cmd; \
+ struct udevice *device = dev; \
+ dcs_write_seq(device, MCS_ADRSFT, (c) & 0xFF); \
+ dcs_write_seq(device, (c) >> 8, seq); \
+})
+
+static int otm8009a_init_sequence(struct udevice *dev)
+{
+ struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
+ struct mipi_dsi_device *device = plat->device;
+ int ret;
+
+ /* Enter CMD2 */
+ dcs_write_cmd_at(dev, MCS_CMD2_ENA1, 0x80, 0x09, 0x01);
+
+ /* Enter Orise Command2 */
+ dcs_write_cmd_at(dev, MCS_CMD2_ENA2, 0x80, 0x09);
+
+ dcs_write_cmd_at(dev, MCS_SD_PCH_CTRL, 0x30);
+ mdelay(10);
+
+ dcs_write_cmd_at(dev, MCS_NO_DOC1, 0x40);
+ mdelay(10);
+
+ dcs_write_cmd_at(dev, MCS_PWR_CTRL4 + 1, 0xA9);
+ dcs_write_cmd_at(dev, MCS_PWR_CTRL2 + 1, 0x34);
+ dcs_write_cmd_at(dev, MCS_P_DRV_M, 0x50);
+ dcs_write_cmd_at(dev, MCS_VCOMDC, 0x4E);
+ dcs_write_cmd_at(dev, MCS_OSC_ADJ, 0x66); /* 65Hz */
+ dcs_write_cmd_at(dev, MCS_PWR_CTRL2 + 2, 0x01);
+ dcs_write_cmd_at(dev, MCS_PWR_CTRL2 + 5, 0x34);
+ dcs_write_cmd_at(dev, MCS_PWR_CTRL2 + 4, 0x33);
+ dcs_write_cmd_at(dev, MCS_GVDDSET, 0x79, 0x79);
+ dcs_write_cmd_at(dev, MCS_SD_CTRL + 1, 0x1B);
+ dcs_write_cmd_at(dev, MCS_PWR_CTRL1 + 2, 0x83);
+ dcs_write_cmd_at(dev, MCS_SD_PCH_CTRL + 1, 0x83);
+ dcs_write_cmd_at(dev, MCS_RGB_VID_SET, 0x0E);
+ dcs_write_cmd_at(dev, MCS_PANSET, 0x00, 0x01);
+
+ dcs_write_cmd_at(dev, MCS_GOAVST, 0x85, 0x01, 0x00, 0x84, 0x01, 0x00);
+ dcs_write_cmd_at(dev, MCS_GOACLKA1, 0x18, 0x04, 0x03, 0x39, 0x00, 0x00,
+ 0x00, 0x18, 0x03, 0x03, 0x3A, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(dev, MCS_GOACLKA3, 0x18, 0x02, 0x03, 0x3B, 0x00, 0x00,
+ 0x00, 0x18, 0x01, 0x03, 0x3C, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(dev, MCS_GOAECLK, 0x01, 0x01, 0x20, 0x20, 0x00, 0x00,
+ 0x01, 0x02, 0x00, 0x00);
+
+ dcs_write_cmd_at(dev, MCS_NO_DOC2, 0x00);
+
+ dcs_write_cmd_at(dev, MCS_PANCTRLSET1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(dev, MCS_PANCTRLSET2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(dev, MCS_PANCTRLSET3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(dev, MCS_PANCTRLSET4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(dev, MCS_PANCTRLSET5, 0, 4, 4, 4, 4, 4, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(dev, MCS_PANCTRLSET6, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4,
+ 4, 0, 0, 0, 0);
+ dcs_write_cmd_at(dev, MCS_PANCTRLSET7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ dcs_write_cmd_at(dev, MCS_PANCTRLSET8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
+
+ dcs_write_cmd_at(dev, MCS_PANU2D1, 0x00, 0x26, 0x09, 0x0B, 0x01, 0x25,
+ 0x00, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(dev, MCS_PANU2D2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x0A, 0x0C, 0x02);
+ dcs_write_cmd_at(dev, MCS_PANU2D3, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(dev, MCS_PAND2U1, 0x00, 0x25, 0x0C, 0x0A, 0x02, 0x26,
+ 0x00, 0x00, 0x00, 0x00);
+ dcs_write_cmd_at(dev, MCS_PAND2U2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x0B, 0x09, 0x01);
+ dcs_write_cmd_at(dev, MCS_PAND2U3, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+
+ dcs_write_cmd_at(dev, MCS_PWR_CTRL1 + 1, 0x66);
+
+ dcs_write_cmd_at(dev, MCS_NO_DOC3, 0x06);
+
+ dcs_write_cmd_at(dev, MCS_GMCT2_2P, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10,
+ 0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10, 0x0A,
+ 0x01);
+ dcs_write_cmd_at(dev, MCS_GMCT2_2N, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10,
+ 0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10, 0x0A,
+ 0x01);
+
+ /* Exit CMD2 */
+ dcs_write_cmd_at(dev, MCS_CMD2_ENA1, 0xFF, 0xFF, 0xFF);
+
+ ret = mipi_dsi_dcs_nop(device);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(device);
+ if (ret)
+ return ret;
+
+ /* Wait for sleep out exit */
+ mdelay(120);
+
+ /* Default portrait 480x800 rgb24 */
+ dcs_write_seq(dev, MIPI_DCS_SET_ADDRESS_MODE, 0x00);
+
+ ret = mipi_dsi_dcs_set_column_address(device, 0,
+ default_timing.hactive.typ - 1);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_dcs_set_page_address(device, 0,
+ default_timing.vactive.typ - 1);
+ if (ret)
+ return ret;
+
+ /* See otm8009a driver documentation for pixel format descriptions */
+ ret = mipi_dsi_dcs_set_pixel_format(device, MIPI_DCS_PIXEL_FMT_24BIT |
+ MIPI_DCS_PIXEL_FMT_24BIT << 4);
+ if (ret)
+ return ret;
+
+ /* Disable CABC feature */
+ dcs_write_seq(dev, MIPI_DCS_WRITE_POWER_SAVE, 0x00);
+
+ ret = mipi_dsi_dcs_set_display_on(device);
+ if (ret)
+ return ret;
+
+ ret = mipi_dsi_dcs_nop(device);
+ if (ret)
+ return ret;
+
+ /* Send Command GRAM memory write (no parameters) */
+ dcs_write_seq(dev, MIPI_DCS_WRITE_MEMORY_START);
+
+ return 0;
+}
+
+static int otm8009a_panel_enable_backlight(struct udevice *dev)
+{
+ struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
+ struct mipi_dsi_device *device = plat->device;
+ int ret;
+
+ ret = mipi_dsi_attach(device);
+ if (ret < 0)
+ return ret;
+
+ ret = otm8009a_init_sequence(dev);
+ if (ret)
+ return ret;
+
+ /*
+ * Power on the backlight with the requested brightness
+ * Note We can not use mipi_dsi_dcs_set_display_brightness()
+ * as otm8009a driver support only 8-bit brightness (1 param).
+ */
+ dcs_write_seq(dev, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+ OTM8009A_BACKLIGHT_DEFAULT);
+
+ /* Update Brightness Control & Backlight */
+ dcs_write_seq(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24);
+
+ /* Update Brightness Control & Backlight */
+ dcs_write_seq_hs(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY);
+
+ /* Need to wait a few time before sending the first image */
+ mdelay(10);
+
+ return 0;
+}
+
+static int otm8009a_panel_get_display_timing(struct udevice *dev,
+ struct display_timing *timings)
+{
+ struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
+ struct mipi_dsi_device *device = plat->device;
+ struct otm8009a_panel_priv *priv = dev_get_priv(dev);
+
+ memcpy(timings, &default_timing, sizeof(*timings));
+
+ /* fill characteristics of DSI data link */
+ device->lanes = priv->lanes;
+ device->format = priv->format;
+ device->mode_flags = priv->mode_flags;
+
+ return 0;
+}
+
+static int otm8009a_panel_ofdata_to_platdata(struct udevice *dev)
+{
+ struct otm8009a_panel_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ if (IS_ENABLED(CONFIG_DM_REGULATOR)) {
+ ret = device_get_supply_regulator(dev, "power-supply",
+ &priv->reg);
+ if (ret && ret != -ENOENT) {
+ dev_err(dev, "Warning: cannot get power supply\n");
+ return ret;
+ }
+ }
+
+ ret = gpio_request_by_name(dev, "reset-gpios", 0, &priv->reset,
+ GPIOD_IS_OUT);
+ if (ret) {
+ dev_err(dev, "warning: cannot get reset GPIO\n");
+ if (ret != -ENOENT)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int otm8009a_panel_probe(struct udevice *dev)
+{
+ struct otm8009a_panel_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ if (IS_ENABLED(CONFIG_DM_REGULATOR) && priv->reg) {
+ dev_dbg(dev, "enable regulator '%s'\n", priv->reg->name);
+ ret = regulator_set_enable(priv->reg, true);
+ if (ret)
+ return ret;
+ }
+
+ /* reset panel */
+ dm_gpio_set_value(&priv->reset, true);
+ mdelay(1); /* >50us */
+ dm_gpio_set_value(&priv->reset, false);
+ mdelay(10); /* >5ms */
+
+ priv->lanes = 2;
+ priv->format = MIPI_DSI_FMT_RGB888;
+ priv->mode_flags = MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_BURST |
+ MIPI_DSI_MODE_LPM;
+
+ return 0;
+}
+
+static const struct panel_ops otm8009a_panel_ops = {
+ .enable_backlight = otm8009a_panel_enable_backlight,
+ .get_display_timing = otm8009a_panel_get_display_timing,
+};
+
+static const struct udevice_id otm8009a_panel_ids[] = {
+ { .compatible = "orisetech,otm8009a" },
+ { }
+};
+
+U_BOOT_DRIVER(otm8009a_panel) = {
+ .name = "otm8009a_panel",
+ .id = UCLASS_PANEL,
+ .of_match = otm8009a_panel_ids,
+ .ops = &otm8009a_panel_ops,
+ .ofdata_to_platdata = otm8009a_panel_ofdata_to_platdata,
+ .probe = otm8009a_panel_probe,
+ .platdata_auto_alloc_size = sizeof(struct mipi_dsi_panel_plat),
+ .priv_auto_alloc_size = sizeof(struct otm8009a_panel_priv),
+};
diff --git a/drivers/video/raydium-rm68200.c b/drivers/video/raydium-rm68200.c
new file mode 100644
index 0000000000..91555e26ed
--- /dev/null
+++ b/drivers/video/raydium-rm68200.c
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 STMicroelectronics - All Rights Reserved
+ * Author(s): Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
+ * Philippe Cornu <philippe.cornu@st.com> for STMicroelectronics.
+ *
+ * This rm68200 panel driver is inspired from the Linux Kernel driver
+ * drivers/gpu/drm/panel/panel-raydium-rm68200.c.
+ */
+#include <common.h>
+#include <backlight.h>
+#include <dm.h>
+#include <mipi_dsi.h>
+#include <panel.h>
+#include <asm/gpio.h>
+#include <power/regulator.h>
+
+/*** Manufacturer Command Set ***/
+#define MCS_CMD_MODE_SW 0xFE /* CMD Mode Switch */
+#define MCS_CMD1_UCS 0x00 /* User Command Set (UCS = CMD1) */
+#define MCS_CMD2_P0 0x01 /* Manufacture Command Set Page0 (CMD2 P0) */
+#define MCS_CMD2_P1 0x02 /* Manufacture Command Set Page1 (CMD2 P1) */
+#define MCS_CMD2_P2 0x03 /* Manufacture Command Set Page2 (CMD2 P2) */
+#define MCS_CMD2_P3 0x04 /* Manufacture Command Set Page3 (CMD2 P3) */
+
+/* CMD2 P0 commands (Display Options and Power) */
+#define MCS_STBCTR 0x12 /* TE1 Output Setting Zig-Zag Connection */
+#define MCS_SGOPCTR 0x16 /* Source Bias Current */
+#define MCS_SDCTR 0x1A /* Source Output Delay Time */
+#define MCS_INVCTR 0x1B /* Inversion Type */
+#define MCS_EXT_PWR_IC 0x24 /* External PWR IC Control */
+#define MCS_SETAVDD 0x27 /* PFM Control for AVDD Output */
+#define MCS_SETAVEE 0x29 /* PFM Control for AVEE Output */
+#define MCS_BT2CTR 0x2B /* DDVDL Charge Pump Control */
+#define MCS_BT3CTR 0x2F /* VGH Charge Pump Control */
+#define MCS_BT4CTR 0x34 /* VGL Charge Pump Control */
+#define MCS_VCMCTR 0x46 /* VCOM Output Level Control */
+#define MCS_SETVGN 0x52 /* VG M/S N Control */
+#define MCS_SETVGP 0x54 /* VG M/S P Control */
+#define MCS_SW_CTRL 0x5F /* Interface Control for PFM and MIPI */
+
+/* CMD2 P2 commands (GOA Timing Control) - no description in datasheet */
+#define GOA_VSTV1 0x00
+#define GOA_VSTV2 0x07
+#define GOA_VCLK1 0x0E
+#define GOA_VCLK2 0x17
+#define GOA_VCLK_OPT1 0x20
+#define GOA_BICLK1 0x2A
+#define GOA_BICLK2 0x37
+#define GOA_BICLK3 0x44
+#define GOA_BICLK4 0x4F
+#define GOA_BICLK_OPT1 0x5B
+#define GOA_BICLK_OPT2 0x60
+#define MCS_GOA_GPO1 0x6D
+#define MCS_GOA_GPO2 0x71
+#define MCS_GOA_EQ 0x74
+#define MCS_GOA_CLK_GALLON 0x7C
+#define MCS_GOA_FS_SEL0 0x7E
+#define MCS_GOA_FS_SEL1 0x87
+#define MCS_GOA_FS_SEL2 0x91
+#define MCS_GOA_FS_SEL3 0x9B
+#define MCS_GOA_BS_SEL0 0xAC
+#define MCS_GOA_BS_SEL1 0xB5
+#define MCS_GOA_BS_SEL2 0xBF
+#define MCS_GOA_BS_SEL3 0xC9
+#define MCS_GOA_BS_SEL4 0xD3
+
+/* CMD2 P3 commands (Gamma) */
+#define MCS_GAMMA_VP 0x60 /* Gamma VP1~VP16 */
+#define MCS_GAMMA_VN 0x70 /* Gamma VN1~VN16 */
+
+struct rm68200_panel_priv {
+ struct udevice *reg;
+ struct udevice *backlight;
+ struct gpio_desc reset;
+ unsigned int lanes;
+ enum mipi_dsi_pixel_format format;
+ unsigned long mode_flags;
+};
+
+static const struct display_timing default_timing = {
+ .pixelclock.typ = 54000000,
+ .hactive.typ = 720,
+ .hfront_porch.typ = 48,
+ .hback_porch.typ = 48,
+ .hsync_len.typ = 9,
+ .vactive.typ = 1280,
+ .vfront_porch.typ = 12,
+ .vback_porch.typ = 12,
+ .vsync_len.typ = 5,
+};
+
+static void rm68200_dcs_write_buf(struct udevice *dev, const void *data,
+ size_t len)
+{
+ struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
+ struct mipi_dsi_device *device = plat->device;
+ int err;
+
+ err = mipi_dsi_dcs_write_buffer(device, data, len);
+ if (err < 0)
+ dev_err(dev, "MIPI DSI DCS write buffer failed: %d\n", err);
+}
+
+static void rm68200_dcs_write_cmd(struct udevice *dev, u8 cmd, u8 value)
+{
+ struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
+ struct mipi_dsi_device *device = plat->device;
+ int err;
+
+ err = mipi_dsi_dcs_write(device, cmd, &value, 1);
+ if (err < 0)
+ dev_err(dev, "MIPI DSI DCS write failed: %d\n", err);
+}
+
+#define dcs_write_seq(ctx, seq...) \
+({ \
+ static const u8 d[] = { seq }; \
+ \
+ rm68200_dcs_write_buf(ctx, d, ARRAY_SIZE(d)); \
+})
+
+/*
+ * This panel is not able to auto-increment all cmd addresses so for some of
+ * them, we need to send them one by one...
+ */
+#define dcs_write_cmd_seq(ctx, cmd, seq...) \
+({ \
+ static const u8 d[] = { seq }; \
+ unsigned int i; \
+ \
+ for (i = 0; i < ARRAY_SIZE(d) ; i++) \
+ rm68200_dcs_write_cmd(ctx, cmd + i, d[i]); \
+})
+
+static void rm68200_init_sequence(struct udevice *dev)
+{
+ /* Enter CMD2 with page 0 */
+ dcs_write_seq(dev, MCS_CMD_MODE_SW, MCS_CMD2_P0);
+ dcs_write_cmd_seq(dev, MCS_EXT_PWR_IC, 0xC0, 0x53, 0x00);
+ dcs_write_seq(dev, MCS_BT2CTR, 0xE5);
+ dcs_write_seq(dev, MCS_SETAVDD, 0x0A);
+ dcs_write_seq(dev, MCS_SETAVEE, 0x0A);
+ dcs_write_seq(dev, MCS_SGOPCTR, 0x52);
+ dcs_write_seq(dev, MCS_BT3CTR, 0x53);
+ dcs_write_seq(dev, MCS_BT4CTR, 0x5A);
+ dcs_write_seq(dev, MCS_INVCTR, 0x00);
+ dcs_write_seq(dev, MCS_STBCTR, 0x0A);
+ dcs_write_seq(dev, MCS_SDCTR, 0x06);
+ dcs_write_seq(dev, MCS_VCMCTR, 0x56);
+ dcs_write_seq(dev, MCS_SETVGN, 0xA0, 0x00);
+ dcs_write_seq(dev, MCS_SETVGP, 0xA0, 0x00);
+ dcs_write_seq(dev, MCS_SW_CTRL, 0x11); /* 2 data lanes, see doc */
+
+ dcs_write_seq(dev, MCS_CMD_MODE_SW, MCS_CMD2_P2);
+ dcs_write_seq(dev, GOA_VSTV1, 0x05);
+ dcs_write_seq(dev, 0x02, 0x0B);
+ dcs_write_seq(dev, 0x03, 0x0F);
+ dcs_write_seq(dev, 0x04, 0x7D, 0x00, 0x50);
+ dcs_write_cmd_seq(dev, GOA_VSTV2, 0x05, 0x16, 0x0D, 0x11, 0x7D, 0x00,
+ 0x50);
+ dcs_write_cmd_seq(dev, GOA_VCLK1, 0x07, 0x08, 0x01, 0x02, 0x00, 0x7D,
+ 0x00, 0x85, 0x08);
+ dcs_write_cmd_seq(dev, GOA_VCLK2, 0x03, 0x04, 0x05, 0x06, 0x00, 0x7D,
+ 0x00, 0x85, 0x08);
+ dcs_write_seq(dev, GOA_VCLK_OPT1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00);
+ dcs_write_cmd_seq(dev, GOA_BICLK1, 0x07, 0x08);
+ dcs_write_seq(dev, 0x2D, 0x01);
+ dcs_write_seq(dev, 0x2F, 0x02, 0x00, 0x40, 0x05, 0x08, 0x54, 0x7D,
+ 0x00);
+ dcs_write_cmd_seq(dev, GOA_BICLK2, 0x03, 0x04, 0x05, 0x06, 0x00);
+ dcs_write_seq(dev, 0x3D, 0x40);
+ dcs_write_seq(dev, 0x3F, 0x05, 0x08, 0x54, 0x7D, 0x00);
+ dcs_write_seq(dev, GOA_BICLK3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00);
+ dcs_write_seq(dev, GOA_BICLK4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00);
+ dcs_write_seq(dev, 0x58, 0x00, 0x00, 0x00);
+ dcs_write_seq(dev, GOA_BICLK_OPT1, 0x00, 0x00, 0x00, 0x00, 0x00);
+ dcs_write_seq(dev, GOA_BICLK_OPT2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+ dcs_write_seq(dev, MCS_GOA_GPO1, 0x00, 0x00, 0x00, 0x00);
+ dcs_write_seq(dev, MCS_GOA_GPO2, 0x00, 0x20, 0x00);
+ dcs_write_seq(dev, MCS_GOA_EQ, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x00, 0x00);
+ dcs_write_seq(dev, MCS_GOA_CLK_GALLON, 0x00, 0x00);
+ dcs_write_cmd_seq(dev, MCS_GOA_FS_SEL0, 0xBF, 0x02, 0x06, 0x14, 0x10,
+ 0x16, 0x12, 0x08, 0x3F);
+ dcs_write_cmd_seq(dev, MCS_GOA_FS_SEL1, 0x3F, 0x3F, 0x3F, 0x3F, 0x0C,
+ 0x0A, 0x0E, 0x3F, 0x3F, 0x00);
+ dcs_write_cmd_seq(dev, MCS_GOA_FS_SEL2, 0x04, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x05, 0x01, 0x3F, 0x3F, 0x0F);
+ dcs_write_cmd_seq(dev, MCS_GOA_FS_SEL3, 0x0B, 0x0D, 0x3F, 0x3F, 0x3F,
+ 0x3F);
+ dcs_write_cmd_seq(dev, 0xA2, 0x3F, 0x09, 0x13, 0x17, 0x11, 0x15);
+ dcs_write_cmd_seq(dev, 0xA9, 0x07, 0x03, 0x3F);
+ dcs_write_cmd_seq(dev, MCS_GOA_BS_SEL0, 0x3F, 0x05, 0x01, 0x17, 0x13,
+ 0x15, 0x11, 0x0F, 0x3F);
+ dcs_write_cmd_seq(dev, MCS_GOA_BS_SEL1, 0x3F, 0x3F, 0x3F, 0x3F, 0x0B,
+ 0x0D, 0x09, 0x3F, 0x3F, 0x07);
+ dcs_write_cmd_seq(dev, MCS_GOA_BS_SEL2, 0x03, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x02, 0x06, 0x3F, 0x3F, 0x08);
+ dcs_write_cmd_seq(dev, MCS_GOA_BS_SEL3, 0x0C, 0x0A, 0x3F, 0x3F, 0x3F,
+ 0x3F, 0x3F, 0x0E, 0x10, 0x14);
+ dcs_write_cmd_seq(dev, MCS_GOA_BS_SEL4, 0x12, 0x16, 0x00, 0x04, 0x3F);
+ dcs_write_seq(dev, 0xDC, 0x02);
+ dcs_write_seq(dev, 0xDE, 0x12);
+
+ dcs_write_seq(dev, MCS_CMD_MODE_SW, 0x0E); /* No documentation */
+ dcs_write_seq(dev, 0x01, 0x75);
+
+ dcs_write_seq(dev, MCS_CMD_MODE_SW, MCS_CMD2_P3);
+ dcs_write_cmd_seq(dev, MCS_GAMMA_VP, 0x00, 0x0C, 0x12, 0x0E, 0x06,
+ 0x12, 0x0E, 0x0B, 0x15, 0x0B, 0x10, 0x07, 0x0F,
+ 0x12, 0x0C, 0x00);
+ dcs_write_cmd_seq(dev, MCS_GAMMA_VN, 0x00, 0x0C, 0x12, 0x0E, 0x06,
+ 0x12, 0x0E, 0x0B, 0x15, 0x0B, 0x10, 0x07, 0x0F,
+ 0x12, 0x0C, 0x00);
+
+ /* Exit CMD2 */
+ dcs_write_seq(dev, MCS_CMD_MODE_SW, MCS_CMD1_UCS);
+}
+
+static int rm68200_panel_enable_backlight(struct udevice *dev)
+{
+ struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
+ struct mipi_dsi_device *device = plat->device;
+ struct rm68200_panel_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = mipi_dsi_attach(device);
+ if (ret < 0)
+ return ret;
+
+ rm68200_init_sequence(dev);
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(device);
+ if (ret)
+ return ret;
+
+ mdelay(125);
+
+ ret = mipi_dsi_dcs_set_display_on(device);
+ if (ret)
+ return ret;
+
+ mdelay(20);
+
+ ret = backlight_enable(priv->backlight);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int rm68200_panel_get_display_timing(struct udevice *dev,
+ struct display_timing *timings)
+{
+ struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
+ struct mipi_dsi_device *device = plat->device;
+ struct rm68200_panel_priv *priv = dev_get_priv(dev);
+
+ memcpy(timings, &default_timing, sizeof(*timings));
+
+ /* fill characteristics of DSI data link */
+ device->lanes = priv->lanes;
+ device->format = priv->format;
+ device->mode_flags = priv->mode_flags;
+
+ return 0;
+}
+
+static int rm68200_panel_ofdata_to_platdata(struct udevice *dev)
+{
+ struct rm68200_panel_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ if (IS_ENABLED(CONFIG_DM_REGULATOR)) {
+ ret = device_get_supply_regulator(dev, "power-supply",
+ &priv->reg);
+ if (ret && ret != -ENOENT) {
+ dev_err(dev, "Warning: cannot get power supply\n");
+ return ret;
+ }
+ }
+
+ ret = gpio_request_by_name(dev, "reset-gpios", 0, &priv->reset,
+ GPIOD_IS_OUT);
+ if (ret) {
+ dev_err(dev, "Warning: cannot get reset GPIO\n");
+ if (ret != -ENOENT)
+ return ret;
+ }
+
+ ret = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev,
+ "backlight", &priv->backlight);
+ if (ret) {
+ dev_err(dev, "Cannot get backlight: ret=%d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rm68200_panel_probe(struct udevice *dev)
+{
+ struct rm68200_panel_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ if (IS_ENABLED(CONFIG_DM_REGULATOR) && priv->reg) {
+ ret = regulator_set_enable(priv->reg, true);
+ if (ret)
+ return ret;
+ }
+
+ /* reset panel */
+ dm_gpio_set_value(&priv->reset, true);
+ mdelay(1);
+ dm_gpio_set_value(&priv->reset, false);
+ mdelay(10);
+
+ priv->lanes = 2;
+ priv->format = MIPI_DSI_FMT_RGB888;
+ priv->mode_flags = MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_BURST |
+ MIPI_DSI_MODE_LPM;
+
+ return 0;
+}
+
+static const struct panel_ops rm68200_panel_ops = {
+ .enable_backlight = rm68200_panel_enable_backlight,
+ .get_display_timing = rm68200_panel_get_display_timing,
+};
+
+static const struct udevice_id rm68200_panel_ids[] = {
+ { .compatible = "raydium,rm68200" },
+ { }
+};
+
+U_BOOT_DRIVER(rm68200_panel) = {
+ .name = "rm68200_panel",
+ .id = UCLASS_PANEL,
+ .of_match = rm68200_panel_ids,
+ .ops = &rm68200_panel_ops,
+ .ofdata_to_platdata = rm68200_panel_ofdata_to_platdata,
+ .probe = rm68200_panel_probe,
+ .platdata_auto_alloc_size = sizeof(struct mipi_dsi_panel_plat),
+ .priv_auto_alloc_size = sizeof(struct rm68200_panel_priv),
+};
diff --git a/drivers/video/sandbox_dsi_host.c b/drivers/video/sandbox_dsi_host.c
new file mode 100644
index 0000000000..cd644ec0b4
--- /dev/null
+++ b/drivers/video/sandbox_dsi_host.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause
+/*
+ * Copyright (C) 2019, STMicroelectronics - All Rights Reserved
+ */
+
+#include <common.h>
+#include <display.h>
+#include <dm.h>
+#include <dsi_host.h>
+
+/**
+ * struct sandbox_dsi_host_priv - private data for driver
+ * @device: DSI peripheral device
+ * @timing: Display timings
+ * @max_data_lanes: maximum number of data lines
+ * @phy_ops: set of function pointers for performing physical operations
+ */
+struct sandbox_dsi_host_priv {
+ struct mipi_dsi_device *device;
+ struct display_timing *timings;
+ unsigned int max_data_lanes;
+ const struct mipi_dsi_phy_ops *phy_ops;
+};
+
+static int sandbox_dsi_host_init(struct udevice *dev,
+ struct mipi_dsi_device *device,
+ struct display_timing *timings,
+ unsigned int max_data_lanes,
+ const struct mipi_dsi_phy_ops *phy_ops)
+{
+ struct sandbox_dsi_host_priv *priv = dev_get_priv(dev);
+
+ if (!device)
+ return -1;
+
+ if (!timings)
+ return -2;
+
+ if (max_data_lanes == 0)
+ return -3;
+
+ if (!phy_ops)
+ return -4;
+
+ if (!phy_ops->init || !phy_ops->get_lane_mbps ||
+ !phy_ops->post_set_mode)
+ return -5;
+
+ priv->max_data_lanes = max_data_lanes;
+ priv->phy_ops = phy_ops;
+ priv->timings = timings;
+ priv->device = device;
+
+ return 0;
+}
+
+static int sandbox_dsi_host_enable(struct udevice *dev)
+{
+ struct sandbox_dsi_host_priv *priv = dev_get_priv(dev);
+ unsigned int lane_mbps;
+ int ret;
+
+ priv->phy_ops->init(priv->device);
+ ret = priv->phy_ops->get_lane_mbps(priv->device, priv->timings, 2,
+ MIPI_DSI_FMT_RGB888, &lane_mbps);
+ if (ret)
+ return -1;
+
+ priv->phy_ops->post_set_mode(priv->device, MIPI_DSI_MODE_VIDEO);
+
+ return 0;
+}
+
+struct dsi_host_ops sandbox_dsi_host_ops = {
+ .init = sandbox_dsi_host_init,
+ .enable = sandbox_dsi_host_enable,
+};
+
+static const struct udevice_id sandbox_dsi_host_ids[] = {
+ { .compatible = "sandbox,dsi-host"},
+ { }
+};
+
+U_BOOT_DRIVER(sandbox_dsi_host) = {
+ .name = "sandbox-dsi-host",
+ .id = UCLASS_DSI_HOST,
+ .of_match = sandbox_dsi_host_ids,
+ .ops = &sandbox_dsi_host_ops,
+ .priv_auto_alloc_size = sizeof(struct sandbox_dsi_host_priv),
+};
diff --git a/drivers/video/stm32/Kconfig b/drivers/video/stm32/Kconfig
index 78b1facad4..95d51bb4e9 100644
--- a/drivers/video/stm32/Kconfig
+++ b/drivers/video/stm32/Kconfig
@@ -13,6 +13,15 @@ menuconfig VIDEO_STM32
DSI. This option enables these supports which can be used on
devices which have RGB TFT or DSI display connected.
+config VIDEO_STM32_DSI
+ bool "Enable STM32 DSI video support"
+ depends on VIDEO_STM32
+ select VIDEO_BRIDGE
+ select VIDEO_DW_MIPI_DSI
+ help
+ This option enables support DSI internal bridge which can be used on
+ devices which have DSI devices connected.
+
config VIDEO_STM32_MAX_XRES
int "Maximum horizontal resolution (for memory allocation purposes)"
depends on VIDEO_STM32
diff --git a/drivers/video/stm32/Makefile b/drivers/video/stm32/Makefile
index 7297e5f57a..f8b42d1a4d 100644
--- a/drivers/video/stm32/Makefile
+++ b/drivers/video/stm32/Makefile
@@ -6,3 +6,4 @@
# Yannick Fertre <yannick.fertre@st.com>
obj-${CONFIG_VIDEO_STM32} = stm32_ltdc.o
+obj-${CONFIG_VIDEO_STM32_DSI} += stm32_dsi.o
diff --git a/drivers/video/stm32/stm32_dsi.c b/drivers/video/stm32/stm32_dsi.c
new file mode 100644
index 0000000000..cb89576e1d
--- /dev/null
+++ b/drivers/video/stm32/stm32_dsi.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 STMicroelectronics - All Rights Reserved
+ * Author(s): Philippe Cornu <philippe.cornu@st.com> for STMicroelectronics.
+ * Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
+ *
+ * This MIPI DSI controller driver is based on the Linux Kernel driver from
+ * drivers/gpu/drm/stm/dw_mipi_dsi-stm.c.
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <dsi_host.h>
+#include <mipi_dsi.h>
+#include <panel.h>
+#include <reset.h>
+#include <video.h>
+#include <video_bridge.h>
+#include <asm/io.h>
+#include <asm/arch/gpio.h>
+#include <dm/device-internal.h>
+#include <linux/iopoll.h>
+#include <power/regulator.h>
+
+#define HWVER_130 0x31333000 /* IP version 1.30 */
+#define HWVER_131 0x31333100 /* IP version 1.31 */
+
+/* DSI digital registers & bit definitions */
+#define DSI_VERSION 0x00
+#define VERSION GENMASK(31, 8)
+
+/*
+ * DSI wrapper registers & bit definitions
+ * Note: registers are named as in the Reference Manual
+ */
+#define DSI_WCFGR 0x0400 /* Wrapper ConFiGuration Reg */
+#define WCFGR_DSIM BIT(0) /* DSI Mode */
+#define WCFGR_COLMUX GENMASK(3, 1) /* COLor MUltipleXing */
+
+#define DSI_WCR 0x0404 /* Wrapper Control Reg */
+#define WCR_DSIEN BIT(3) /* DSI ENable */
+
+#define DSI_WISR 0x040C /* Wrapper Interrupt and Status Reg */
+#define WISR_PLLLS BIT(8) /* PLL Lock Status */
+#define WISR_RRS BIT(12) /* Regulator Ready Status */
+
+#define DSI_WPCR0 0x0418 /* Wrapper Phy Conf Reg 0 */
+#define WPCR0_UIX4 GENMASK(5, 0) /* Unit Interval X 4 */
+#define WPCR0_TDDL BIT(16) /* Turn Disable Data Lanes */
+
+#define DSI_WRPCR 0x0430 /* Wrapper Regulator & Pll Ctrl Reg */
+#define WRPCR_PLLEN BIT(0) /* PLL ENable */
+#define WRPCR_NDIV GENMASK(8, 2) /* pll loop DIVision Factor */
+#define WRPCR_IDF GENMASK(14, 11) /* pll Input Division Factor */
+#define WRPCR_ODF GENMASK(17, 16) /* pll Output Division Factor */
+#define WRPCR_REGEN BIT(24) /* REGulator ENable */
+#define WRPCR_BGREN BIT(28) /* BandGap Reference ENable */
+#define IDF_MIN 1
+#define IDF_MAX 7
+#define NDIV_MIN 10
+#define NDIV_MAX 125
+#define ODF_MIN 1
+#define ODF_MAX 8
+
+/* dsi color format coding according to the datasheet */
+enum dsi_color {
+ DSI_RGB565_CONF1,
+ DSI_RGB565_CONF2,
+ DSI_RGB565_CONF3,
+ DSI_RGB666_CONF1,
+ DSI_RGB666_CONF2,
+ DSI_RGB888,
+};
+
+#define LANE_MIN_KBPS 31250
+#define LANE_MAX_KBPS 500000
+
+/* Timeout for regulator on/off, pll lock/unlock & fifo empty */
+#define TIMEOUT_US 200000
+
+struct stm32_dsi_priv {
+ struct mipi_dsi_device device;
+ void __iomem *base;
+ struct udevice *panel;
+ u32 pllref_clk;
+ u32 hw_version;
+ int lane_min_kbps;
+ int lane_max_kbps;
+ struct udevice *vdd_reg;
+ struct udevice *dsi_host;
+};
+
+static inline void dsi_write(struct stm32_dsi_priv *dsi, u32 reg, u32 val)
+{
+ writel(val, dsi->base + reg);
+}
+
+static inline u32 dsi_read(struct stm32_dsi_priv *dsi, u32 reg)
+{
+ return readl(dsi->base + reg);
+}
+
+static inline void dsi_set(struct stm32_dsi_priv *dsi, u32 reg, u32 mask)
+{
+ dsi_write(dsi, reg, dsi_read(dsi, reg) | mask);
+}
+
+static inline void dsi_clear(struct stm32_dsi_priv *dsi, u32 reg, u32 mask)
+{
+ dsi_write(dsi, reg, dsi_read(dsi, reg) & ~mask);
+}
+
+static inline void dsi_update_bits(struct stm32_dsi_priv *dsi, u32 reg,
+ u32 mask, u32 val)
+{
+ dsi_write(dsi, reg, (dsi_read(dsi, reg) & ~mask) | val);
+}
+
+static enum dsi_color dsi_color_from_mipi(u32 fmt)
+{
+ switch (fmt) {
+ case MIPI_DSI_FMT_RGB888:
+ return DSI_RGB888;
+ case MIPI_DSI_FMT_RGB666:
+ return DSI_RGB666_CONF2;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ return DSI_RGB666_CONF1;
+ case MIPI_DSI_FMT_RGB565:
+ return DSI_RGB565_CONF1;
+ default:
+ pr_err("MIPI color invalid, so we use rgb888\n");
+ }
+ return DSI_RGB888;
+}
+
+static int dsi_pll_get_clkout_khz(int clkin_khz, int idf, int ndiv, int odf)
+{
+ int divisor = idf * odf;
+
+ /* prevent from division by 0 */
+ if (!divisor)
+ return 0;
+
+ return DIV_ROUND_CLOSEST(clkin_khz * ndiv, divisor);
+}
+
+static int dsi_pll_get_params(struct stm32_dsi_priv *dsi,
+ int clkin_khz, int clkout_khz,
+ int *idf, int *ndiv, int *odf)
+{
+ int i, o, n, n_min, n_max;
+ int fvco_min, fvco_max, delta, best_delta; /* all in khz */
+
+ /* Early checks preventing division by 0 & odd results */
+ if (clkin_khz <= 0 || clkout_khz <= 0)
+ return -EINVAL;
+
+ fvco_min = dsi->lane_min_kbps * 2 * ODF_MAX;
+ fvco_max = dsi->lane_max_kbps * 2 * ODF_MIN;
+
+ best_delta = 1000000; /* big started value (1000000khz) */
+
+ for (i = IDF_MIN; i <= IDF_MAX; i++) {
+ /* Compute ndiv range according to Fvco */
+ n_min = ((fvco_min * i) / (2 * clkin_khz)) + 1;
+ n_max = (fvco_max * i) / (2 * clkin_khz);
+
+ /* No need to continue idf loop if we reach ndiv max */
+ if (n_min >= NDIV_MAX)
+ break;
+
+ /* Clamp ndiv to valid values */
+ if (n_min < NDIV_MIN)
+ n_min = NDIV_MIN;
+ if (n_max > NDIV_MAX)
+ n_max = NDIV_MAX;
+
+ for (o = ODF_MIN; o <= ODF_MAX; o *= 2) {
+ n = DIV_ROUND_CLOSEST(i * o * clkout_khz, clkin_khz);
+ /* Check ndiv according to vco range */
+ if (n < n_min || n > n_max)
+ continue;
+ /* Check if new delta is better & saves parameters */
+ delta = dsi_pll_get_clkout_khz(clkin_khz, i, n, o) -
+ clkout_khz;
+ if (delta < 0)
+ delta = -delta;
+ if (delta < best_delta) {
+ *idf = i;
+ *ndiv = n;
+ *odf = o;
+ best_delta = delta;
+ }
+ /* fast return in case of "perfect result" */
+ if (!delta)
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static int dsi_phy_init(void *priv_data)
+{
+ struct mipi_dsi_device *device = priv_data;
+ struct udevice *dev = device->dev;
+ struct stm32_dsi_priv *dsi = dev_get_priv(dev);
+ u32 val;
+ int ret;
+
+ debug("Initialize DSI physical layer\n");
+
+ /* Enable the regulator */
+ dsi_set(dsi, DSI_WRPCR, WRPCR_REGEN | WRPCR_BGREN);
+ ret = readl_poll_timeout(dsi->base + DSI_WISR, val, val & WISR_RRS,
+ TIMEOUT_US);
+ if (ret) {
+ debug("!TIMEOUT! waiting REGU\n");
+ return ret;
+ }
+
+ /* Enable the DSI PLL & wait for its lock */
+ dsi_set(dsi, DSI_WRPCR, WRPCR_PLLEN);
+ ret = readl_poll_timeout(dsi->base + DSI_WISR, val, val & WISR_PLLLS,
+ TIMEOUT_US);
+ if (ret) {
+ debug("!TIMEOUT! waiting PLL\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void dsi_phy_post_set_mode(void *priv_data, unsigned long mode_flags)
+{
+ struct mipi_dsi_device *device = priv_data;
+ struct udevice *dev = device->dev;
+ struct stm32_dsi_priv *dsi = dev_get_priv(dev);
+
+ debug("Set mode %p enable %ld\n", dsi,
+ mode_flags & MIPI_DSI_MODE_VIDEO);
+
+ if (!dsi)
+ return;
+
+ /*
+ * DSI wrapper must be enabled in video mode & disabled in command mode.
+ * If wrapper is enabled in command mode, the display controller
+ * register access will hang.
+ */
+
+ if (mode_flags & MIPI_DSI_MODE_VIDEO)
+ dsi_set(dsi, DSI_WCR, WCR_DSIEN);
+ else
+ dsi_clear(dsi, DSI_WCR, WCR_DSIEN);
+}
+
+static int dsi_get_lane_mbps(void *priv_data, struct display_timing *timings,
+ u32 lanes, u32 format, unsigned int *lane_mbps)
+{
+ struct mipi_dsi_device *device = priv_data;
+ struct udevice *dev = device->dev;
+ struct stm32_dsi_priv *dsi = dev_get_priv(dev);
+ int idf, ndiv, odf, pll_in_khz, pll_out_khz;
+ int ret, bpp;
+ u32 val;
+
+ /* Update lane capabilities according to hw version */
+ dsi->hw_version = dsi_read(dsi, DSI_VERSION) & VERSION;
+ dsi->lane_min_kbps = LANE_MIN_KBPS;
+ dsi->lane_max_kbps = LANE_MAX_KBPS;
+ if (dsi->hw_version == HWVER_131) {
+ dsi->lane_min_kbps *= 2;
+ dsi->lane_max_kbps *= 2;
+ }
+
+ pll_in_khz = dsi->pllref_clk / 1000;
+
+ /* Compute requested pll out */
+ bpp = mipi_dsi_pixel_format_to_bpp(format);
+ pll_out_khz = (timings->pixelclock.typ / 1000) * bpp / lanes;
+ /* Add 20% to pll out to be higher than pixel bw (burst mode only) */
+ pll_out_khz = (pll_out_khz * 12) / 10;
+ if (pll_out_khz > dsi->lane_max_kbps) {
+ pll_out_khz = dsi->lane_max_kbps;
+ dev_warn(dev, "Warning max phy mbps is used\n");
+ }
+ if (pll_out_khz < dsi->lane_min_kbps) {
+ pll_out_khz = dsi->lane_min_kbps;
+ dev_warn(dev, "Warning min phy mbps is used\n");
+ }
+
+ /* Compute best pll parameters */
+ idf = 0;
+ ndiv = 0;
+ odf = 0;
+ ret = dsi_pll_get_params(dsi, pll_in_khz, pll_out_khz,
+ &idf, &ndiv, &odf);
+ if (ret) {
+ dev_err(dev, "Warning dsi_pll_get_params(): bad params\n");
+ return ret;
+ }
+
+ /* Get the adjusted pll out value */
+ pll_out_khz = dsi_pll_get_clkout_khz(pll_in_khz, idf, ndiv, odf);
+
+ /* Set the PLL division factors */
+ dsi_update_bits(dsi, DSI_WRPCR, WRPCR_NDIV | WRPCR_IDF | WRPCR_ODF,
+ (ndiv << 2) | (idf << 11) | ((ffs(odf) - 1) << 16));
+
+ /* Compute uix4 & set the bit period in high-speed mode */
+ val = 4000000 / pll_out_khz;
+ dsi_update_bits(dsi, DSI_WPCR0, WPCR0_UIX4, val);
+
+ /* Select video mode by resetting DSIM bit */
+ dsi_clear(dsi, DSI_WCFGR, WCFGR_DSIM);
+
+ /* Select the color coding */
+ dsi_update_bits(dsi, DSI_WCFGR, WCFGR_COLMUX,
+ dsi_color_from_mipi(format) << 1);
+
+ *lane_mbps = pll_out_khz / 1000;
+
+ debug("pll_in %ukHz pll_out %ukHz lane_mbps %uMHz\n",
+ pll_in_khz, pll_out_khz, *lane_mbps);
+
+ return 0;
+}
+
+static const struct mipi_dsi_phy_ops dsi_stm_phy_ops = {
+ .init = dsi_phy_init,
+ .get_lane_mbps = dsi_get_lane_mbps,
+ .post_set_mode = dsi_phy_post_set_mode,
+};
+
+static int stm32_dsi_attach(struct udevice *dev)
+{
+ struct stm32_dsi_priv *priv = dev_get_priv(dev);
+ struct mipi_dsi_device *device = &priv->device;
+ struct mipi_dsi_panel_plat *mplat;
+ struct display_timing timings;
+ int ret;
+
+ ret = uclass_first_device(UCLASS_PANEL, &priv->panel);
+ if (ret) {
+ dev_err(dev, "panel device error %d\n", ret);
+ return ret;
+ }
+
+ mplat = dev_get_platdata(priv->panel);
+ mplat->device = &priv->device;
+
+ ret = panel_get_display_timing(priv->panel, &timings);
+ if (ret) {
+ ret = fdtdec_decode_display_timing(gd->fdt_blob,
+ dev_of_offset(priv->panel),
+ 0, &timings);
+ if (ret) {
+ dev_err(dev, "decode display timing error %d\n", ret);
+ return ret;
+ }
+ }
+
+ ret = uclass_get_device(UCLASS_DSI_HOST, 0, &priv->dsi_host);
+ if (ret) {
+ dev_err(dev, "No video dsi host detected %d\n", ret);
+ return ret;
+ }
+
+ ret = dsi_host_init(priv->dsi_host, device, &timings, 2,
+ &dsi_stm_phy_ops);
+ if (ret) {
+ dev_err(dev, "failed to initialize mipi dsi host\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int stm32_dsi_set_backlight(struct udevice *dev, int percent)
+{
+ struct stm32_dsi_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = panel_enable_backlight(priv->panel);
+ if (ret) {
+ dev_err(dev, "panel %s enable backlight error %d\n",
+ priv->panel->name, ret);
+ return ret;
+ }
+
+ ret = dsi_host_enable(priv->dsi_host);
+ if (ret) {
+ dev_err(dev, "failed to enable mipi dsi host\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int stm32_dsi_probe(struct udevice *dev)
+{
+ struct stm32_dsi_priv *priv = dev_get_priv(dev);
+ struct mipi_dsi_device *device = &priv->device;
+ struct reset_ctl rst;
+ struct clk clk;
+ int ret;
+
+ device->dev = dev;
+
+ priv->base = (void *)dev_read_addr(dev);
+ if ((fdt_addr_t)priv->base == FDT_ADDR_T_NONE) {
+ dev_err(dev, "dsi dt register address error\n");
+ return -EINVAL;
+ }
+
+ if (IS_ENABLED(CONFIG_DM_REGULATOR)) {
+ ret = device_get_supply_regulator(dev, "phy-dsi-supply",
+ &priv->vdd_reg);
+ if (ret && ret != -ENOENT) {
+ dev_err(dev, "Warning: cannot get phy dsi supply\n");
+ return -ENODEV;
+ }
+
+ if (ret != -ENOENT) {
+ ret = regulator_set_enable(priv->vdd_reg, true);
+ if (ret)
+ return ret;
+ }
+ }
+
+ ret = clk_get_by_name(device->dev, "pclk", &clk);
+ if (ret) {
+ dev_err(dev, "peripheral clock get error %d\n", ret);
+ goto err_reg;
+ }
+
+ ret = clk_enable(&clk);
+ if (ret) {
+ dev_err(dev, "peripheral clock enable error %d\n", ret);
+ goto err_reg;
+ }
+
+ ret = clk_get_by_name(dev, "ref", &clk);
+ if (ret) {
+ dev_err(dev, "pll reference clock get error %d\n", ret);
+ goto err_clk;
+ }
+
+ priv->pllref_clk = (unsigned int)clk_get_rate(&clk);
+
+ ret = reset_get_by_index(device->dev, 0, &rst);
+ if (ret) {
+ dev_err(dev, "missing dsi hardware reset\n");
+ goto err_clk;
+ }
+
+ /* Reset */
+ reset_deassert(&rst);
+
+ return 0;
+err_clk:
+ clk_disable(&clk);
+err_reg:
+ if (IS_ENABLED(CONFIG_DM_REGULATOR))
+ regulator_set_enable(priv->vdd_reg, false);
+
+ return ret;
+}
+
+struct video_bridge_ops stm32_dsi_ops = {
+ .attach = stm32_dsi_attach,
+ .set_backlight = stm32_dsi_set_backlight,
+};
+
+static const struct udevice_id stm32_dsi_ids[] = {
+ { .compatible = "st,stm32-dsi"},
+ { }
+};
+
+U_BOOT_DRIVER(stm32_dsi) = {
+ .name = "stm32-display-dsi",
+ .id = UCLASS_VIDEO_BRIDGE,
+ .of_match = stm32_dsi_ids,
+ .bind = dm_scan_fdt_dev,
+ .probe = stm32_dsi_probe,
+ .ops = &stm32_dsi_ops,
+ .priv_auto_alloc_size = sizeof(struct stm32_dsi_priv),
+};
diff --git a/drivers/video/stm32/stm32_ltdc.c b/drivers/video/stm32/stm32_ltdc.c
index dc6c88902f..59ff692b0b 100644
--- a/drivers/video/stm32/stm32_ltdc.c
+++ b/drivers/video/stm32/stm32_ltdc.c
@@ -7,19 +7,18 @@
#include <common.h>
#include <clk.h>
+#include <display.h>
#include <dm.h>
#include <panel.h>
#include <reset.h>
#include <video.h>
+#include <video_bridge.h>
#include <asm/io.h>
#include <asm/arch/gpio.h>
#include <dm/device-internal.h>
-DECLARE_GLOBAL_DATA_PTR;
-
struct stm32_ltdc_priv {
void __iomem *regs;
- struct display_timing timing;
enum video_log2_bpp l2bpp;
u32 bg_col_argb;
u32 crop_x, crop_y, crop_w, crop_h;
@@ -174,8 +173,8 @@ static u32 stm32_ltdc_get_pixel_format(enum video_log2_bpp l2bpp)
case VIDEO_BPP2:
case VIDEO_BPP4:
default:
- debug("%s: warning %dbpp not supported yet, %dbpp instead\n",
- __func__, VNBITS(l2bpp), VNBITS(VIDEO_BPP16));
+ pr_warn("%s: warning %dbpp not supported yet, %dbpp instead\n",
+ __func__, VNBITS(l2bpp), VNBITS(VIDEO_BPP16));
pf = PF_RGB565;
break;
}
@@ -209,23 +208,23 @@ static void stm32_ltdc_enable(struct stm32_ltdc_priv *priv)
setbits_le32(priv->regs + LTDC_GCR, GCR_LTDCEN);
}
-static void stm32_ltdc_set_mode(struct stm32_ltdc_priv *priv)
+static void stm32_ltdc_set_mode(struct stm32_ltdc_priv *priv,
+ struct display_timing *timings)
{
void __iomem *regs = priv->regs;
- struct display_timing *timing = &priv->timing;
u32 hsync, vsync, acc_hbp, acc_vbp, acc_act_w, acc_act_h;
u32 total_w, total_h;
u32 val;
/* Convert video timings to ltdc timings */
- hsync = timing->hsync_len.typ - 1;
- vsync = timing->vsync_len.typ - 1;
- acc_hbp = hsync + timing->hback_porch.typ;
- acc_vbp = vsync + timing->vback_porch.typ;
- acc_act_w = acc_hbp + timing->hactive.typ;
- acc_act_h = acc_vbp + timing->vactive.typ;
- total_w = acc_act_w + timing->hfront_porch.typ;
- total_h = acc_act_h + timing->vfront_porch.typ;
+ hsync = timings->hsync_len.typ - 1;
+ vsync = timings->vsync_len.typ - 1;
+ acc_hbp = hsync + timings->hback_porch.typ;
+ acc_vbp = vsync + timings->vback_porch.typ;
+ acc_act_w = acc_hbp + timings->hactive.typ;
+ acc_act_h = acc_vbp + timings->vactive.typ;
+ total_w = acc_act_w + timings->hfront_porch.typ;
+ total_h = acc_act_h + timings->vfront_porch.typ;
/* Synchronization sizes */
val = (hsync << 16) | vsync;
@@ -247,14 +246,14 @@ static void stm32_ltdc_set_mode(struct stm32_ltdc_priv *priv)
/* Signal polarities */
val = 0;
- debug("%s: timing->flags 0x%08x\n", __func__, timing->flags);
- if (timing->flags & DISPLAY_FLAGS_HSYNC_HIGH)
+ debug("%s: timing->flags 0x%08x\n", __func__, timings->flags);
+ if (timings->flags & DISPLAY_FLAGS_HSYNC_HIGH)
val |= GCR_HSPOL;
- if (timing->flags & DISPLAY_FLAGS_VSYNC_HIGH)
+ if (timings->flags & DISPLAY_FLAGS_VSYNC_HIGH)
val |= GCR_VSPOL;
- if (timing->flags & DISPLAY_FLAGS_DE_HIGH)
+ if (timings->flags & DISPLAY_FLAGS_DE_HIGH)
val |= GCR_DEPOL;
- if (timing->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
+ if (timings->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
val |= GCR_PCPOL;
clrsetbits_le32(regs + LTDC_GCR,
GCR_HSPOL | GCR_VSPOL | GCR_DEPOL | GCR_PCPOL, val);
@@ -330,96 +329,120 @@ static int stm32_ltdc_probe(struct udevice *dev)
struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev);
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
struct stm32_ltdc_priv *priv = dev_get_priv(dev);
- struct udevice *panel;
+ struct udevice *bridge = NULL;
+ struct udevice *panel = NULL;
+ struct display_timing timings;
struct clk pclk;
struct reset_ctl rst;
- int rate, ret;
+ int ret;
priv->regs = (void *)dev_read_addr(dev);
if ((fdt_addr_t)priv->regs == FDT_ADDR_T_NONE) {
- debug("%s: ltdc dt register address error\n", __func__);
+ dev_err(dev, "ltdc dt register address error\n");
return -EINVAL;
}
ret = clk_get_by_index(dev, 0, &pclk);
if (ret) {
- debug("%s: peripheral clock get error %d\n", __func__, ret);
+ dev_err(dev, "peripheral clock get error %d\n", ret);
return ret;
}
ret = clk_enable(&pclk);
if (ret) {
- debug("%s: peripheral clock enable error %d\n",
- __func__, ret);
+ dev_err(dev, "peripheral clock enable error %d\n", ret);
return ret;
}
- ret = reset_get_by_index(dev, 0, &rst);
+ ret = uclass_first_device_err(UCLASS_PANEL, &panel);
if (ret) {
- debug("%s: missing ltdc hardware reset\n", __func__);
- return -ENODEV;
+ if (ret != -ENODEV)
+ dev_err(dev, "panel device error %d\n", ret);
+ return ret;
}
- /* Reset */
- reset_deassert(&rst);
-
- ret = uclass_first_device(UCLASS_PANEL, &panel);
+ ret = panel_get_display_timing(panel, &timings);
if (ret) {
- debug("%s: panel device error %d\n", __func__, ret);
- return ret;
+ ret = fdtdec_decode_display_timing(gd->fdt_blob,
+ dev_of_offset(panel),
+ 0, &timings);
+ if (ret) {
+ dev_err(dev, "decode display timing error %d\n", ret);
+ return ret;
+ }
}
- ret = panel_enable_backlight(panel);
+ ret = clk_set_rate(&pclk, timings.pixelclock.typ);
+ if (ret)
+ dev_warn(dev, "fail to set pixel clock %d hz\n",
+ timings.pixelclock.typ);
+
+ debug("%s: Set pixel clock req %d hz get %ld hz\n", __func__,
+ timings.pixelclock.typ, clk_get_rate(&pclk));
+
+ ret = reset_get_by_index(dev, 0, &rst);
if (ret) {
- debug("%s: panel %s enable backlight error %d\n",
- __func__, panel->name, ret);
+ dev_err(dev, "missing ltdc hardware reset\n");
return ret;
}
- ret = fdtdec_decode_display_timing(gd->fdt_blob,
- dev_of_offset(dev), 0,
- &priv->timing);
- if (ret) {
- debug("%s: decode display timing error %d\n",
- __func__, ret);
- return -EINVAL;
- }
+ /* Reset */
+ reset_deassert(&rst);
- rate = clk_set_rate(&pclk, priv->timing.pixelclock.typ);
- if (rate < 0) {
- debug("%s: fail to set pixel clock %d hz %d hz\n",
- __func__, priv->timing.pixelclock.typ, rate);
- return rate;
+ if (IS_ENABLED(CONFIG_VIDEO_BRIDGE)) {
+ ret = uclass_get_device(UCLASS_VIDEO_BRIDGE, 0, &bridge);
+ if (ret)
+ debug("No video bridge, or no backlight on bridge\n");
+
+ if (bridge) {
+ ret = video_bridge_attach(bridge);
+ if (ret) {
+ dev_err(dev, "fail to attach bridge\n");
+ return ret;
+ }
+ }
}
- debug("%s: Set pixel clock req %d hz get %d hz\n", __func__,
- priv->timing.pixelclock.typ, rate);
-
/* TODO Below parameters are hard-coded for the moment... */
priv->l2bpp = VIDEO_BPP16;
priv->bg_col_argb = 0xFFFFFFFF; /* white no transparency */
priv->crop_x = 0;
priv->crop_y = 0;
- priv->crop_w = priv->timing.hactive.typ;
- priv->crop_h = priv->timing.vactive.typ;
+ priv->crop_w = timings.hactive.typ;
+ priv->crop_h = timings.vactive.typ;
priv->alpha = 0xFF;
debug("%s: %dx%d %dbpp frame buffer at 0x%lx\n", __func__,
- priv->timing.hactive.typ, priv->timing.vactive.typ,
+ timings.hactive.typ, timings.vactive.typ,
VNBITS(priv->l2bpp), uc_plat->base);
debug("%s: crop %d,%d %dx%d bg 0x%08x alpha %d\n", __func__,
priv->crop_x, priv->crop_y, priv->crop_w, priv->crop_h,
priv->bg_col_argb, priv->alpha);
/* Configure & start LTDC */
- stm32_ltdc_set_mode(priv);
+ stm32_ltdc_set_mode(priv, &timings);
stm32_ltdc_set_layer1(priv, uc_plat->base);
stm32_ltdc_enable(priv);
- uc_priv->xsize = priv->timing.hactive.typ;
- uc_priv->ysize = priv->timing.vactive.typ;
+ uc_priv->xsize = timings.hactive.typ;
+ uc_priv->ysize = timings.vactive.typ;
uc_priv->bpix = priv->l2bpp;
+ if (!bridge) {
+ ret = panel_enable_backlight(panel);
+ if (ret) {
+ dev_err(dev, "panel %s enable backlight error %d\n",
+ panel->name, ret);
+ return ret;
+ }
+ } else if (IS_ENABLED(CONFIG_VIDEO_BRIDGE)) {
+ ret = video_bridge_set_backlight(bridge, 80);
+ if (ret) {
+ dev_err(dev, "fail to set backlight\n");
+ return ret;
+ }
+ }
+
video_set_flush_dcache(dev, true);
return 0;