diff options
Diffstat (limited to 'drivers')
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(®s); + + 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(®s->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; |