From 49b4c54bc969aae9a79e598266feec3d74275635 Mon Sep 17 00:00:00 2001 From: Tomas Novotny Date: Wed, 25 Nov 2020 18:42:16 +0100 Subject: gpio: tca642x: fix input subcommand for gpio banks > 0 The value of input pin for bank > 0 is always 0 for input subcommand. The reason is that gpio_bank variable is computed only for invert and output subcommands (it depends on number of arguments). The default value of zero causes to shift the mask away for banks > 0. Please note that info subcommand works as expected, because the input pin values are accessed differently. Fixes: 61c1775f16ed ("gpio: tca642x: Add the tca642x gpio expander driver") Cc: Dan Murphy Signed-off-by: Tomas Novotny --- drivers/gpio/tca642x.c | 49 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/gpio/tca642x.c b/drivers/gpio/tca642x.c index 463cfe879a8..7007c7a0027 100644 --- a/drivers/gpio/tca642x.c +++ b/drivers/gpio/tca642x.c @@ -213,6 +213,24 @@ static int tca642x_info(uchar chip) return 0; } +static int tca642x_get_bank(int pin) +{ + int gpio_bank; + + if (pin <= 7) { + gpio_bank = 0; + } else if ((pin >= 10) && (pin <= 17)) { + gpio_bank = 1; + } else if ((pin >= 20) && (pin <= 27)) { + gpio_bank = 2; + } else { + printf("Requested pin is not available\n"); + gpio_bank = -1; + } + + return gpio_bank; +} + static struct cmd_tbl cmd_tca642x[] = { U_BOOT_CMD_MKENT(device, 3, 0, (void *)TCA642X_CMD_DEVICE, "", ""), U_BOOT_CMD_MKENT(output, 4, 0, (void *)TCA642X_CMD_OUTPUT, "", ""), @@ -226,7 +244,7 @@ static int do_tca642x(struct cmd_tbl *cmdtp, int flag, int argc, { static uchar chip = CONFIG_SYS_I2C_TCA642X_ADDR; int ret = CMD_RET_USAGE, val; - uint8_t gpio_bank = 0; + int gpio_bank = 0; uint8_t bank_shift; ulong ul_arg2 = 0; ulong ul_arg3 = 0; @@ -247,20 +265,8 @@ static int do_tca642x(struct cmd_tbl *cmdtp, int flag, int argc, ul_arg2 = simple_strtoul(argv[2], NULL, 10); /* arg3 used as pin or invert value */ - if (argc > 3) { + if (argc > 3) ul_arg3 = simple_strtoul(argv[3], NULL, 10) & 0x1; - if (ul_arg2 <= 7) { - gpio_bank = 0; - } else if ((ul_arg2 >= 10) && (ul_arg2 <= 17)) { - gpio_bank = 1; - } else if ((ul_arg2 >= 20) && (ul_arg2 <= 27)) { - gpio_bank = 2; - } else { - printf("Requested pin is not available\n"); - ret = CMD_RET_FAILURE; - goto error; - } - } switch ((int)c->cmd) { case TCA642X_CMD_INFO: @@ -277,6 +283,11 @@ static int do_tca642x(struct cmd_tbl *cmdtp, int flag, int argc, break; case TCA642X_CMD_INPUT: + gpio_bank = tca642x_get_bank(ul_arg2); + if (gpio_bank < 0) { + ret = CMD_RET_FAILURE; + goto error; + } bank_shift = ul_arg2 - (gpio_bank * 10); ret = tca642x_set_dir(chip, gpio_bank, (1 << bank_shift), TCA642X_DIR_IN << bank_shift); @@ -291,6 +302,11 @@ static int do_tca642x(struct cmd_tbl *cmdtp, int flag, int argc, break; case TCA642X_CMD_OUTPUT: + gpio_bank = tca642x_get_bank(ul_arg2); + if (gpio_bank < 0) { + ret = CMD_RET_FAILURE; + goto error; + } bank_shift = ul_arg2 - (gpio_bank * 10); ret = tca642x_set_dir(chip, gpio_bank, (1 << bank_shift), (TCA642X_DIR_OUT << bank_shift)); @@ -303,6 +319,11 @@ static int do_tca642x(struct cmd_tbl *cmdtp, int flag, int argc, break; case TCA642X_CMD_INVERT: + gpio_bank = tca642x_get_bank(ul_arg2); + if (gpio_bank < 0) { + ret = CMD_RET_FAILURE; + goto error; + } bank_shift = ul_arg2 - (gpio_bank * 10); ret = tca642x_set_pol(chip, gpio_bank, (1 << bank_shift), (ul_arg3 << bank_shift)); -- cgit v1.3.1 From 705082d4b1a2040c9f1ff2a7d5c4f2fccafde4bc Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Sat, 28 Nov 2020 10:11:28 +0200 Subject: spi: ti_qspi: Fix "spi-max-frequency" error path in ti_qspi_ofdata_to_platdata struct ti_qspi_priv->max_hz is declared as unsigned int, so the following error path check will always be false, even when "spi-max-frequency" property is invalid/missing: priv->max_hz = fdtdec_get_int(blob, node, "spi-max-frequency", -1); if (priv->max_hz < 0) { ... } Replace the fdtdec call with dev_read_u32_default() and use 0 as the default value. Error out if max_hz is zero. Signed-off-by: Ovidiu Panait --- drivers/spi/ti_qspi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/spi/ti_qspi.c b/drivers/spi/ti_qspi.c index 7c3b1f7b88e..76bc480f433 100644 --- a/drivers/spi/ti_qspi.c +++ b/drivers/spi/ti_qspi.c @@ -467,8 +467,8 @@ static int ti_qspi_of_to_plat(struct udevice *bus) priv->memory_map = map_physmem(mmap_addr, mmap_size, MAP_NOCACHE); priv->mmap_size = mmap_size; - priv->max_hz = fdtdec_get_int(blob, node, "spi-max-frequency", -1); - if (priv->max_hz < 0) { + priv->max_hz = dev_read_u32_default(bus, "spi-max-frequency", 0); + if (!priv->max_hz) { debug("Error: Max frequency missing\n"); return -ENODEV; } -- cgit v1.3.1 From f3f83ad4caf4f4fb2394d70585a20ce56b552c5e Mon Sep 17 00:00:00 2001 From: Vignesh Raghavendra Date: Sun, 29 Nov 2020 12:53:05 +0530 Subject: spi: omap3_spi: Fix speed and mode selection McSPI IP provides per CS specific speed and mode selection. Therefore it is possible to apply these settings only after CS is known. But set_speed and set_mode can be called without bus being claimed, this would lead driver to set up wrong CS (or previously used CS). Fix this by apply set_speed and set_mode only if bus is already claimed. Signed-off-by: Vignesh Raghavendra Tested-by: Miquel Raynal --- drivers/spi/omap3_spi.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/spi/omap3_spi.c b/drivers/spi/omap3_spi.c index 78e2a25cdb4..74931768c0f 100644 --- a/drivers/spi/omap3_spi.c +++ b/drivers/spi/omap3_spi.c @@ -37,6 +37,8 @@ struct omap3_spi_priv { unsigned int mode; unsigned int wordlen; unsigned int pin_dir:1; + + bool bus_claimed; }; static void omap3_spi_write_chconf(struct omap3_spi_priv *priv, int val) @@ -372,6 +374,8 @@ static void _omap3_spi_claim_bus(struct omap3_spi_priv *priv) conf |= OMAP3_MCSPI_MODULCTRL_SINGLE; writel(conf, &priv->regs->modulctrl); + + priv->bus_claimed = true; } static int omap3_spi_claim_bus(struct udevice *dev) @@ -381,9 +385,12 @@ static int omap3_spi_claim_bus(struct udevice *dev) struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev); priv->cs = slave_plat->cs; - priv->freq = slave_plat->max_hz; + if (!priv->freq) + priv->freq = slave_plat->max_hz; _omap3_spi_claim_bus(priv); + _omap3_spi_set_speed(priv); + _omap3_spi_set_mode(priv); return 0; } @@ -395,6 +402,8 @@ static int omap3_spi_release_bus(struct udevice *dev) writel(OMAP3_MCSPI_MODULCTRL_MS, &priv->regs->modulctrl); + priv->bus_claimed = false; + return 0; } @@ -440,7 +449,8 @@ static int omap3_spi_set_speed(struct udevice *dev, unsigned int speed) struct omap3_spi_priv *priv = dev_get_priv(dev); priv->freq = speed; - _omap3_spi_set_speed(priv); + if (priv->bus_claimed) + _omap3_spi_set_speed(priv); return 0; } @@ -451,7 +461,8 @@ static int omap3_spi_set_mode(struct udevice *dev, uint mode) priv->mode = mode; - _omap3_spi_set_mode(priv); + if (priv->bus_claimed) + _omap3_spi_set_mode(priv); return 0; } -- cgit v1.3.1 From 5688f3bf0b741886452bb9c065d4c64ab25faae0 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:06:27 +0100 Subject: clk: export generic routines Export routines that can be used by other drivers avoiding duplicating code. Signed-off-by: Dario Binacchi Reviewed-by: Simon Glass --- drivers/clk/clk-divider.c | 24 +++++++++---------- include/linux/clk-provider.h | 57 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index 8f59d7fb72c..9df50a5e723 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -28,8 +28,8 @@ #define UBOOT_DM_CLK_CCF_DIVIDER "ccf_clk_divider" -static unsigned int _get_table_div(const struct clk_div_table *table, - unsigned int val) +unsigned int clk_divider_get_table_div(const struct clk_div_table *table, + unsigned int val) { const struct clk_div_table *clkt; @@ -49,7 +49,7 @@ static unsigned int _get_div(const struct clk_div_table *table, if (flags & CLK_DIVIDER_MAX_AT_ZERO) return val ? val : clk_div_mask(width) + 1; if (table) - return _get_table_div(table, val); + return clk_divider_get_table_div(table, val); return val + 1; } @@ -89,8 +89,8 @@ static ulong clk_divider_recalc_rate(struct clk *clk) divider->flags, divider->width); } -static bool _is_valid_table_div(const struct clk_div_table *table, - unsigned int div) +bool clk_divider_is_valid_table_div(const struct clk_div_table *table, + unsigned int div) { const struct clk_div_table *clkt; @@ -100,18 +100,18 @@ static bool _is_valid_table_div(const struct clk_div_table *table, return false; } -static bool _is_valid_div(const struct clk_div_table *table, unsigned int div, - unsigned long flags) +bool clk_divider_is_valid_div(const struct clk_div_table *table, + unsigned int div, unsigned long flags) { if (flags & CLK_DIVIDER_POWER_OF_TWO) return is_power_of_2(div); if (table) - return _is_valid_table_div(table, div); + return clk_divider_is_valid_table_div(table, div); return true; } -static unsigned int _get_table_val(const struct clk_div_table *table, - unsigned int div) +unsigned int clk_divider_get_table_val(const struct clk_div_table *table, + unsigned int div) { const struct clk_div_table *clkt; @@ -131,7 +131,7 @@ static unsigned int _get_val(const struct clk_div_table *table, if (flags & CLK_DIVIDER_MAX_AT_ZERO) return (div == clk_div_mask(width) + 1) ? 0 : div; if (table) - return _get_table_val(table, div); + return clk_divider_get_table_val(table, div); return div - 1; } int divider_get_val(unsigned long rate, unsigned long parent_rate, @@ -142,7 +142,7 @@ int divider_get_val(unsigned long rate, unsigned long parent_rate, div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); - if (!_is_valid_div(table, div, flags)) + if (!clk_divider_is_valid_div(table, div, flags)) return -EINVAL; value = _get_val(table, div, flags, width); diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 75b16353dad..c871ea646d3 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -76,6 +76,19 @@ struct clk_mux { extern const struct clk_ops clk_mux_ops; u8 clk_mux_get_parent(struct clk *clk); +/** + * clk_mux_index_to_val() - Convert the parent index to the register value + * + * It returns the value to write in the hardware register to output the selected + * input clock parent. + * + * @table: array of register values corresponding to the parent index (optional) + * @flags: hardware-specific flags + * @index: parent clock index + * @return the register value + */ +unsigned int clk_mux_index_to_val(u32 *table, unsigned int flags, u8 index); + struct clk_gate { struct clk clk; void __iomem *reg; @@ -125,6 +138,50 @@ struct clk_divider { #define CLK_DIVIDER_READ_ONLY BIT(5) #define CLK_DIVIDER_MAX_AT_ZERO BIT(6) extern const struct clk_ops clk_divider_ops; + +/** + * clk_divider_get_table_div() - convert the register value to the divider + * + * @table: array of register values corresponding to valid dividers + * @val: value to convert + * @return the divider + */ +unsigned int clk_divider_get_table_div(const struct clk_div_table *table, + unsigned int val); + +/** + * clk_divider_get_table_val() - convert the divider to the register value + * + * It returns the value to write in the hardware register to divide the input + * clock rate by @div. + * + * @table: array of register values corresponding to valid dividers + * @div: requested divider + * @return the register value + */ +unsigned int clk_divider_get_table_val(const struct clk_div_table *table, + unsigned int div); + +/** + * clk_divider_is_valid_div() - check if the divider is valid + * + * @table: array of valid dividers (optional) + * @div: divider to check + * @flags: hardware-specific flags + * @return true if the divider is valid, false otherwise + */ +bool clk_divider_is_valid_div(const struct clk_div_table *table, + unsigned int div, unsigned long flags); + +/** + * clk_divider_is_valid_table_div - check if the divider is in the @table array + * + * @table: array of valid dividers + * @div: divider to check + * @return true if the divider is found in the @table array, false otherwise + */ +bool clk_divider_is_valid_table_div(const struct clk_div_table *table, + unsigned int div); unsigned long divider_recalc_rate(struct clk *hw, unsigned long parent_rate, unsigned int val, const struct clk_div_table *table, -- cgit v1.3.1 From 92cc4e1c21e27b604665f080973837c3618297a3 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:06:29 +0100 Subject: bus: ti: add minimal sysc interconnect target driver We can handle the sysc interconnect target module in a generic way for many TI SoCs. Initially let's just enable domain clocks before the children are probed. The code is loosely based on the drivers/bus/ti-sysc.c of the Linux kernel version 5.9-rc7. For DT binding details see: - Documentation/devicetree/bindings/bus/ti-sysc.txt Signed-off-by: Dario Binacchi --- arch/arm/Kconfig | 1 + configs/nokia_rx51_defconfig | 1 + drivers/bus/Kconfig | 7 ++ drivers/bus/Makefile | 1 + drivers/bus/ti-sysc.c | 166 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 176 insertions(+) create mode 100644 drivers/bus/ti-sysc.c (limited to 'drivers') diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index fbe90875ae4..6b5235e740a 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -799,6 +799,7 @@ config ARCH_OMAP2PLUS select SPL_BOARD_INIT if SPL select SPL_STACK_R if SPL select SUPPORT_SPL + imply TI_SYSC if DM && OF_CONTROL imply FIT config ARCH_MESON diff --git a/configs/nokia_rx51_defconfig b/configs/nokia_rx51_defconfig index d0c89295251..30a02e2bc31 100644 --- a/configs/nokia_rx51_defconfig +++ b/configs/nokia_rx51_defconfig @@ -4,6 +4,7 @@ CONFIG_ARCH_OMAP2PLUS=y CONFIG_SYS_TEXT_BASE=0x80008000 CONFIG_NR_DRAM_BANKS=2 CONFIG_TARGET_NOKIA_RX51=y +# CONFIG_TI_SYSC is not set # CONFIG_FIT is not set CONFIG_BOOTDELAY=30 CONFIG_AUTOBOOT_KEYED=y diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 07a33c6287f..733bec5a56c 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -5,6 +5,13 @@ menu "Bus devices" +config TI_SYSC + bool "TI sysc interconnect target module driver" + depends on ARCH_OMAP2PLUS + help + Generic driver for Texas Instruments interconnect target module + found on many TI SoCs. + config UNIPHIER_SYSTEM_BUS bool "UniPhier System Bus driver" depends on ARCH_UNIPHIER diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index 0b97fc1f8bb..875bb4ed424 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -3,4 +3,5 @@ # Makefile for the bus drivers. # +obj-$(CONFIG_TI_SYSC) += ti-sysc.o obj-$(CONFIG_UNIPHIER_SYSTEM_BUS) += uniphier-system-bus.o diff --git a/drivers/bus/ti-sysc.c b/drivers/bus/ti-sysc.c new file mode 100644 index 00000000000..4e3d6103005 --- /dev/null +++ b/drivers/bus/ti-sysc.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Texas Instruments sysc interconnect target driver + * + * Copyright (C) 2020 Dario Binacchi + */ + +#include +#include +#include +#include + +enum ti_sysc_clocks { + TI_SYSC_FCK, + TI_SYSC_ICK, + TI_SYSC_MAX_CLOCKS, +}; + +static const char *const clock_names[] = {"fck", "ick"}; + +struct ti_sysc_priv { + int clocks_count; + struct clk clocks[TI_SYSC_MAX_CLOCKS]; +}; + +static const struct udevice_id ti_sysc_ids[] = { + {.compatible = "ti,sysc-omap2"}, + {.compatible = "ti,sysc-omap4"}, + {.compatible = "ti,sysc-omap4-simple"}, + {.compatible = "ti,sysc-omap3430-sr"}, + {.compatible = "ti,sysc-omap3630-sr"}, + {.compatible = "ti,sysc-omap4-sr"}, + {.compatible = "ti,sysc-omap3-sham"}, + {.compatible = "ti,sysc-omap-aes"}, + {.compatible = "ti,sysc-mcasp"}, + {.compatible = "ti,sysc-usb-host-fs"}, + {} +}; + +static int ti_sysc_get_one_clock(struct udevice *dev, enum ti_sysc_clocks index) +{ + struct ti_sysc_priv *priv = dev_get_priv(dev); + const char *name; + int err; + + switch (index) { + case TI_SYSC_FCK: + break; + case TI_SYSC_ICK: + break; + default: + return -EINVAL; + } + + name = clock_names[index]; + + err = clk_get_by_name(dev, name, &priv->clocks[index]); + if (err) { + if (err == -ENODATA) + return 0; + + dev_err(dev, "failed to get %s clock\n", name); + return err; + } + + return 0; +} + +static int ti_sysc_put_clocks(struct udevice *dev) +{ + struct ti_sysc_priv *priv = dev_get_priv(dev); + int err; + + err = clk_release_all(priv->clocks, priv->clocks_count); + if (err) + dev_err(dev, "failed to release all clocks\n"); + + return err; +} + +static int ti_sysc_get_clocks(struct udevice *dev) +{ + struct ti_sysc_priv *priv = dev_get_priv(dev); + int i, err; + + for (i = 0; i < TI_SYSC_MAX_CLOCKS; i++) { + err = ti_sysc_get_one_clock(dev, i); + if (!err) + priv->clocks_count++; + else if (err != -ENOENT) + return err; + } + + return 0; +} + +static int ti_sysc_child_post_remove(struct udevice *dev) +{ + struct ti_sysc_priv *priv = dev_get_priv(dev->parent); + int i, err; + + for (i = 0; i < priv->clocks_count; i++) { + err = clk_disable(&priv->clocks[i]); + if (err) { + dev_err(dev->parent, "failed to disable %s clock\n", + clock_names[i]); + return err; + } + } + + return 0; +} + +static int ti_sysc_child_pre_probe(struct udevice *dev) +{ + struct ti_sysc_priv *priv = dev_get_priv(dev->parent); + int i, err; + + for (i = 0; i < priv->clocks_count; i++) { + err = clk_enable(&priv->clocks[i]); + if (err) { + dev_err(dev->parent, "failed to enable %s clock\n", + clock_names[i]); + return err; + } + } + + return 0; +} + +static int ti_sysc_remove(struct udevice *dev) +{ + return ti_sysc_put_clocks(dev); +} + +static int ti_sysc_probe(struct udevice *dev) +{ + int err; + + err = ti_sysc_get_clocks(dev); + if (err) + goto clocks_err; + + return 0; + +clocks_err: + ti_sysc_put_clocks(dev); + return err; +} + +UCLASS_DRIVER(ti_sysc) = { + .id = UCLASS_SIMPLE_BUS, + .name = "ti_sysc", + .post_bind = dm_scan_fdt_dev +}; + +U_BOOT_DRIVER(ti_sysc) = { + .name = "ti_sysc", + .id = UCLASS_SIMPLE_BUS, + .of_match = ti_sysc_ids, + .probe = ti_sysc_probe, + .remove = ti_sysc_remove, + .child_pre_probe = ti_sysc_child_pre_probe, + .child_post_remove = ti_sysc_child_post_remove, + .priv_auto = sizeof(struct ti_sysc_priv) +}; -- cgit v1.3.1 From 2983ad55a13e9afcdf1a3d8f55eea038c0a0e8a3 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:06:31 +0100 Subject: clk: add clk_round_rate() It returns the rate which will be set if you ask clk_set_rate() to set that rate. It provides a way to query exactly what rate you'll get if you call clk_set_rate() with that same argument. So essentially, clk_round_rate() and clk_set_rate() are equivalent except the former does not modify the clock hardware in any way. Signed-off-by: Dario Binacchi Reviewed-by: Simon Glass Reviewed-by: Sean Anderson --- arch/sandbox/include/asm/clk.h | 9 +++++++++ drivers/clk/clk-uclass.c | 15 +++++++++++++++ drivers/clk/clk_sandbox.c | 17 +++++++++++++++++ drivers/clk/clk_sandbox_test.c | 10 ++++++++++ include/clk-uclass.h | 8 ++++++++ include/clk.h | 28 ++++++++++++++++++++++++++++ test/dm/clk.c | 22 ++++++++++++++++++++++ 7 files changed, 109 insertions(+) (limited to 'drivers') diff --git a/arch/sandbox/include/asm/clk.h b/arch/sandbox/include/asm/clk.h index c184c4bffcf..0294baee278 100644 --- a/arch/sandbox/include/asm/clk.h +++ b/arch/sandbox/include/asm/clk.h @@ -105,6 +105,15 @@ int sandbox_clk_test_get_bulk(struct udevice *dev); * @return: The rate of the clock. */ ulong sandbox_clk_test_get_rate(struct udevice *dev, int id); +/** + * sandbox_clk_test_round_rate - Ask the sandbox clock test device to round a + * clock's rate. + * + * @dev: The sandbox clock test (client) device. + * @id: The test device's clock ID to configure. + * @return: The rounded rate of the clock. + */ +ulong sandbox_clk_test_round_rate(struct udevice *dev, int id, ulong rate); /** * sandbox_clk_test_set_rate - Ask the sandbox clock test device to set a * clock's rate. diff --git a/drivers/clk/clk-uclass.c b/drivers/clk/clk-uclass.c index 5cfd00ce771..b75056718bf 100644 --- a/drivers/clk/clk-uclass.c +++ b/drivers/clk/clk-uclass.c @@ -523,6 +523,21 @@ long long clk_get_parent_rate(struct clk *clk) return pclk->rate; } +ulong clk_round_rate(struct clk *clk, ulong rate) +{ + const struct clk_ops *ops; + + debug("%s(clk=%p, rate=%lu)\n", __func__, clk, rate); + if (!clk_valid(clk)) + return 0; + + ops = clk_dev_ops(clk->dev); + if (!ops->round_rate) + return -ENOSYS; + + return ops->round_rate(clk, rate); +} + ulong clk_set_rate(struct clk *clk, ulong rate) { const struct clk_ops *ops; diff --git a/drivers/clk/clk_sandbox.c b/drivers/clk/clk_sandbox.c index 2c6c0e239f7..b28b67b4486 100644 --- a/drivers/clk/clk_sandbox.c +++ b/drivers/clk/clk_sandbox.c @@ -30,6 +30,22 @@ static ulong sandbox_clk_get_rate(struct clk *clk) return priv->rate[clk->id]; } +static ulong sandbox_clk_round_rate(struct clk *clk, ulong rate) +{ + struct sandbox_clk_priv *priv = dev_get_priv(clk->dev); + + if (!priv->probed) + return -ENODEV; + + if (clk->id >= SANDBOX_CLK_ID_COUNT) + return -EINVAL; + + if (!rate) + return -EINVAL; + + return rate; +} + static ulong sandbox_clk_set_rate(struct clk *clk, ulong rate) { struct sandbox_clk_priv *priv = dev_get_priv(clk->dev); @@ -103,6 +119,7 @@ static int sandbox_clk_free(struct clk *clk) } static struct clk_ops sandbox_clk_ops = { + .round_rate = sandbox_clk_round_rate, .get_rate = sandbox_clk_get_rate, .set_rate = sandbox_clk_set_rate, .enable = sandbox_clk_enable, diff --git a/drivers/clk/clk_sandbox_test.c b/drivers/clk/clk_sandbox_test.c index e9eb738684b..c4e44815084 100644 --- a/drivers/clk/clk_sandbox_test.c +++ b/drivers/clk/clk_sandbox_test.c @@ -86,6 +86,16 @@ ulong sandbox_clk_test_get_rate(struct udevice *dev, int id) return clk_get_rate(sbct->clkps[id]); } +ulong sandbox_clk_test_round_rate(struct udevice *dev, int id, ulong rate) +{ + struct sandbox_clk_test *sbct = dev_get_priv(dev); + + if (id < 0 || id >= SANDBOX_CLK_TEST_ID_COUNT) + return -EINVAL; + + return clk_round_rate(sbct->clkps[id], rate); +} + ulong sandbox_clk_test_set_rate(struct udevice *dev, int id, ulong rate) { struct sandbox_clk_test *sbct = dev_get_priv(dev); diff --git a/include/clk-uclass.h b/include/clk-uclass.h index dac42dab368..50e8681b553 100644 --- a/include/clk-uclass.h +++ b/include/clk-uclass.h @@ -61,6 +61,14 @@ struct clk_ops { * @return 0 if OK, or a negative error code. */ int (*rfree)(struct clk *clock); + /** + * round_rate() - Adjust a rate to the exact rate a clock can provide. + * + * @clk: The clock to manipulate. + * @rate: Desidered clock rate in Hz. + * @return rounded rate in Hz, or -ve error code. + */ + ulong (*round_rate)(struct clk *clk, ulong rate); /** * get_rate() - Get current clock rate. * diff --git a/include/clk.h b/include/clk.h index a62e2efa2ca..ca6b85fa6fe 100644 --- a/include/clk.h +++ b/include/clk.h @@ -366,6 +366,29 @@ struct clk *clk_get_parent(struct clk *clk); */ long long clk_get_parent_rate(struct clk *clk); +/** + * clk_round_rate() - Adjust a rate to the exact rate a clock can provide + * + * This answers the question "if I were to pass @rate to clk_set_rate(), + * what clock rate would I end up with?" without changing the hardware + * in any way. In other words: + * + * rate = clk_round_rate(clk, r); + * + * and: + * + * rate = clk_set_rate(clk, r); + * + * are equivalent except the former does not modify the clock hardware + * in any way. + * + * @clk: A clock struct that was previously successfully requested by + * clk_request/get_by_*(). + * @rate: desired clock rate in Hz. + * @return rounded rate in Hz, or -ve error code. + */ +ulong clk_round_rate(struct clk *clk, ulong rate); + /** * clk_set_rate() - Set current clock rate. * @@ -482,6 +505,11 @@ static inline long long clk_get_parent_rate(struct clk *clk) return -ENOSYS; } +static inline ulong clk_round_rate(struct clk *clk, ulong rate) +{ + return -ENOSYS; +} + static inline ulong clk_set_rate(struct clk *clk, ulong rate) { return -ENOSYS; diff --git a/test/dm/clk.c b/test/dm/clk.c index edca3b49f60..21997ed8922 100644 --- a/test/dm/clk.c +++ b/test/dm/clk.c @@ -112,6 +112,28 @@ static int dm_test_clk(struct unit_test_state *uts) rate = sandbox_clk_test_set_rate(dev_test, SANDBOX_CLK_TEST_ID_I2C, 0); ut_assert(IS_ERR_VALUE(rate)); + ut_asserteq(10000, sandbox_clk_test_get_rate(dev_test, + SANDBOX_CLK_TEST_ID_SPI)); + ut_asserteq(20000, sandbox_clk_test_get_rate(dev_test, + SANDBOX_CLK_TEST_ID_I2C)); + + ut_asserteq(5000, sandbox_clk_test_round_rate(dev_test, + SANDBOX_CLK_TEST_ID_SPI, + 5000)); + ut_asserteq(7000, sandbox_clk_test_round_rate(dev_test, + SANDBOX_CLK_TEST_ID_I2C, + 7000)); + + ut_asserteq(10000, sandbox_clk_test_get_rate(dev_test, + SANDBOX_CLK_TEST_ID_SPI)); + ut_asserteq(20000, sandbox_clk_test_get_rate(dev_test, + SANDBOX_CLK_TEST_ID_I2C)); + + rate = sandbox_clk_test_round_rate(dev_test, SANDBOX_CLK_TEST_ID_SPI, 0); + ut_assert(IS_ERR_VALUE(rate)); + rate = sandbox_clk_test_round_rate(dev_test, SANDBOX_CLK_TEST_ID_I2C, 0); + ut_assert(IS_ERR_VALUE(rate)); + ut_asserteq(10000, sandbox_clk_test_get_rate(dev_test, SANDBOX_CLK_TEST_ID_SPI)); ut_asserteq(20000, sandbox_clk_test_get_rate(dev_test, -- cgit v1.3.1 From d09f063a04bea982ef554105f73bfbaf56dc5ab4 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:06:32 +0100 Subject: clk: ti: add mux clock driver The driver manages a register-mapped multiplexer with multiple input clock signals or parents, one of which can be selected as output. It uses routines provided by the common clock framework (ccf). The code is based on the drivers/clk/ti/mux.c driver of the Linux kernel version 5.9-rc7. For DT binding details see: - Documentation/devicetree/bindings/clock/ti/mux.txt Signed-off-by: Dario Binacchi --- drivers/clk/Kconfig | 1 + drivers/clk/Makefile | 1 + drivers/clk/ti/Kconfig | 10 ++ drivers/clk/ti/Makefile | 6 ++ drivers/clk/ti/clk-mux.c | 276 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 294 insertions(+) create mode 100644 drivers/clk/ti/Kconfig create mode 100644 drivers/clk/ti/Makefile create mode 100644 drivers/clk/ti/clk-mux.c (limited to 'drivers') diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 4dfbad7986b..9e549290398 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -179,6 +179,7 @@ source "drivers/clk/renesas/Kconfig" source "drivers/clk/sunxi/Kconfig" source "drivers/clk/sifive/Kconfig" source "drivers/clk/tegra/Kconfig" +source "drivers/clk/ti/Kconfig" source "drivers/clk/uniphier/Kconfig" config ICS8N3QV01 diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index d1e295ac7c1..2581fe0a198 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_$(SPL_TPL_)CLK_COMPOSITE_CCF) += clk-composite.o obj-y += analogbits/ obj-y += imx/ obj-y += tegra/ +obj-y += ti/ obj-$(CONFIG_ARCH_ASPEED) += aspeed/ obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/ obj-$(CONFIG_ARCH_MTMIPS) += mtmips/ diff --git a/drivers/clk/ti/Kconfig b/drivers/clk/ti/Kconfig new file mode 100644 index 00000000000..be4f26817f6 --- /dev/null +++ b/drivers/clk/ti/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (C) 2020 Dario Binacchi +# + +config CLK_TI_MUX + bool "TI mux clock driver" + depends on CLK && OF_CONTROL && CLK_CCF + help + This enables the mux clock driver support on TI's SoCs. diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile new file mode 100644 index 00000000000..5faf68d30ea --- /dev/null +++ b/drivers/clk/ti/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (C) 2020 Dario Binacchi +# + +obj-$(CONFIG_CLK_TI_MUX) += clk-mux.o diff --git a/drivers/clk/ti/clk-mux.c b/drivers/clk/ti/clk-mux.c new file mode 100644 index 00000000000..1e22a50910c --- /dev/null +++ b/drivers/clk/ti/clk-mux.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * TI multiplexer clock support + * + * Copyright (C) 2020 Dario Binacchi + * + * Based on Linux kernel drivers/clk/ti/mux.c + */ + +#include +#include +#include +#include +#include +#include + +struct clk_ti_mux_priv { + struct clk_bulk parents; + fdt_addr_t reg; + u32 flags; + u32 mux_flags; + u32 mask; + u32 shift; + s32 latch; +}; + +static void clk_ti_mux_rmw(u32 val, u32 mask, fdt_addr_t reg) +{ + u32 v; + + v = readl(reg); + v &= ~mask; + v |= val; + writel(v, reg); +} + +static void clk_ti_mux_latch(fdt_addr_t reg, s8 shift) +{ + u32 latch; + + if (shift < 0) + return; + + latch = 1 << shift; + + clk_ti_mux_rmw(latch, latch, reg); + clk_ti_mux_rmw(0, latch, reg); + readl(reg); /* OCP barrier */ +} + +static struct clk *clk_ti_mux_get_parent_by_index(struct clk_bulk *parents, + int index) +{ + if (index < 0 || !parents) + return ERR_PTR(-EINVAL); + + if (index >= parents->count) + return ERR_PTR(-ENODEV); + + return &parents->clks[index]; +} + +static int clk_ti_mux_get_parent_index(struct clk_bulk *parents, + struct clk *parent) +{ + int i; + + if (!parents || !parent) + return -EINVAL; + + for (i = 0; i < parents->count; i++) { + if (parents->clks[i].dev == parent->dev) + return i; + } + + return -ENODEV; +} + +static int clk_ti_mux_get_index(struct clk *clk) +{ + struct clk_ti_mux_priv *priv = dev_get_priv(clk->dev); + u32 val; + + val = readl(priv->reg); + val >>= priv->shift; + val &= priv->mask; + + if (val && (priv->flags & CLK_MUX_INDEX_BIT)) + val = ffs(val) - 1; + + if (val && (priv->flags & CLK_MUX_INDEX_ONE)) + val--; + + if (val >= priv->parents.count) + return -EINVAL; + + return val; +} + +static int clk_ti_mux_set_parent(struct clk *clk, struct clk *parent) +{ + struct clk_ti_mux_priv *priv = dev_get_priv(clk->dev); + int index; + u32 val; + + index = clk_ti_mux_get_parent_index(&priv->parents, parent); + if (index < 0) { + dev_err(clk->dev, "failed to get parent clock\n"); + return index; + } + + index = clk_mux_index_to_val(NULL, priv->flags, index); + + if (priv->flags & CLK_MUX_HIWORD_MASK) { + val = priv->mask << (priv->shift + 16); + } else { + val = readl(priv->reg); + val &= ~(priv->mask << priv->shift); + } + + val |= index << priv->shift; + writel(val, priv->reg); + clk_ti_mux_latch(priv->reg, priv->latch); + return 0; +} + +static ulong clk_ti_mux_set_rate(struct clk *clk, ulong rate) +{ + struct clk_ti_mux_priv *priv = dev_get_priv(clk->dev); + struct clk *parent; + int index; + + if ((clk->flags & CLK_SET_RATE_PARENT) == 0) + return -ENOSYS; + + index = clk_ti_mux_get_index(clk); + parent = clk_ti_mux_get_parent_by_index(&priv->parents, index); + if (IS_ERR(parent)) + return PTR_ERR(parent); + + rate = clk_set_rate(parent, rate); + dev_dbg(clk->dev, "rate=%ld\n", rate); + return rate; +} + +static ulong clk_ti_mux_get_rate(struct clk *clk) +{ + struct clk_ti_mux_priv *priv = dev_get_priv(clk->dev); + int index; + struct clk *parent; + ulong rate; + + index = clk_ti_mux_get_index(clk); + parent = clk_ti_mux_get_parent_by_index(&priv->parents, index); + if (IS_ERR(parent)) + return PTR_ERR(parent); + + rate = clk_get_rate(parent); + dev_dbg(clk->dev, "rate=%ld\n", rate); + return rate; +} + +static ulong clk_ti_mux_round_rate(struct clk *clk, ulong rate) +{ + struct clk_ti_mux_priv *priv = dev_get_priv(clk->dev); + struct clk *parent; + int index; + + if ((clk->flags & CLK_SET_RATE_PARENT) == 0) + return -ENOSYS; + + index = clk_ti_mux_get_index(clk); + parent = clk_ti_mux_get_parent_by_index(&priv->parents, index); + if (IS_ERR(parent)) + return PTR_ERR(parent); + + rate = clk_round_rate(parent, rate); + dev_dbg(clk->dev, "rate=%ld\n", rate); + return rate; +} + +static int clk_ti_mux_request(struct clk *clk) +{ + struct clk_ti_mux_priv *priv = dev_get_priv(clk->dev); + struct clk *parent; + int index; + + clk->flags = priv->flags; + + index = clk_ti_mux_get_index(clk); + parent = clk_ti_mux_get_parent_by_index(&priv->parents, index); + if (IS_ERR(parent)) + return PTR_ERR(parent); + + return clk_ti_mux_set_parent(clk, parent); +} + +static struct clk_ops clk_ti_mux_ops = { + .request = clk_ti_mux_request, + .round_rate = clk_ti_mux_round_rate, + .get_rate = clk_ti_mux_get_rate, + .set_rate = clk_ti_mux_set_rate, + .set_parent = clk_ti_mux_set_parent, +}; + +static int clk_ti_mux_remove(struct udevice *dev) +{ + struct clk_ti_mux_priv *priv = dev_get_priv(dev); + int err; + + err = clk_release_all(priv->parents.clks, priv->parents.count); + if (err) + dev_dbg(dev, "could not release all parents' clocks\n"); + + return err; +} + +static int clk_ti_mux_probe(struct udevice *dev) +{ + struct clk_ti_mux_priv *priv = dev_get_priv(dev); + int err; + + err = clk_get_bulk(dev, &priv->parents); + if (err || priv->parents.count < 2) { + dev_err(dev, "mux-clock must have parents\n"); + return err ? err : -EFAULT; + } + + /* Generate bit-mask based on parents info */ + priv->mask = priv->parents.count; + if (!(priv->mux_flags & CLK_MUX_INDEX_ONE)) + priv->mask--; + + priv->mask = (1 << fls(priv->mask)) - 1; + return 0; +} + +static int clk_ti_mux_of_to_plat(struct udevice *dev) +{ + struct clk_ti_mux_priv *priv = dev_get_priv(dev); + + priv->reg = dev_read_addr(dev); + if (priv->reg == FDT_ADDR_T_NONE) { + dev_err(dev, "failed to get register\n"); + return -EINVAL; + } + + dev_dbg(dev, "reg=0x%08lx\n", priv->reg); + priv->shift = dev_read_u32_default(dev, "ti,bit-shift", 0); + priv->latch = dev_read_s32_default(dev, "ti,latch-bit", -EINVAL); + + priv->flags = CLK_SET_RATE_NO_REPARENT; + if (dev_read_bool(dev, "ti,set-rate-parent")) + priv->flags |= CLK_SET_RATE_PARENT; + + if (dev_read_bool(dev, "ti,index-starts-at-one")) + priv->mux_flags |= CLK_MUX_INDEX_ONE; + + return 0; +} + +static const struct udevice_id clk_ti_mux_of_match[] = { + {.compatible = "ti,mux-clock"}, + {}, +}; + +U_BOOT_DRIVER(clk_ti_mux) = { + .name = "ti_mux_clock", + .id = UCLASS_CLK, + .of_match = clk_ti_mux_of_match, + .ofdata_to_platdata = clk_ti_mux_of_to_plat, + .probe = clk_ti_mux_probe, + .remove = clk_ti_mux_remove, + .priv_auto = sizeof(struct clk_ti_mux_priv), + .ops = &clk_ti_mux_ops, +}; -- cgit v1.3.1 From 756d64e43e20be558e6e49f2ccee2f747feb4a4d Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:06:34 +0100 Subject: clk: ti: am33xx: add DPLL clock drivers The digital phase-locked loop (DPLL) provides all interface clocks and functional clocks to the processor of the AM33xx device. The AM33xx device integrates five different DPLLs: * Core DPLL * Per DPLL * LCD DPLL * DDR DPLL * MPU DPLL The patch adds support for the compatible strings: * "ti,am3-dpll-core-clock" * "ti,am3-dpll-no-gate-clock" * "ti,am3-dpll-no-gate-j-type-clock" * "ti,am3-dpll-x2-clock" The code is loosely based on the drivers/clk/ti/dpll.c drivers of the Linux kernel version 5.9-rc7. For DT binding details see: - Documentation/devicetree/bindings/clock/ti/dpll.txt Signed-off-by: Dario Binacchi --- drivers/clk/ti/Kconfig | 7 + drivers/clk/ti/Makefile | 1 + drivers/clk/ti/clk-am3-dpll-x2.c | 79 ++++++++++++ drivers/clk/ti/clk-am3-dpll.c | 268 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 355 insertions(+) create mode 100644 drivers/clk/ti/clk-am3-dpll-x2.c create mode 100644 drivers/clk/ti/clk-am3-dpll.c (limited to 'drivers') diff --git a/drivers/clk/ti/Kconfig b/drivers/clk/ti/Kconfig index be4f26817f6..c430dd9b8ae 100644 --- a/drivers/clk/ti/Kconfig +++ b/drivers/clk/ti/Kconfig @@ -3,6 +3,13 @@ # Copyright (C) 2020 Dario Binacchi # +config CLK_TI_AM3_DPLL + bool "TI AM33XX Digital Phase-Locked Loop (DPLL) clock drivers" + depends on CLK && OF_CONTROL + help + This enables the DPLL clock drivers support on AM33XX SoCs. The DPLL + provides all interface clocks and functional clocks to the processor. + config CLK_TI_MUX bool "TI mux clock driver" depends on CLK && OF_CONTROL && CLK_CCF diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile index 5faf68d30ea..9e14b83cfe9 100644 --- a/drivers/clk/ti/Makefile +++ b/drivers/clk/ti/Makefile @@ -3,4 +3,5 @@ # Copyright (C) 2020 Dario Binacchi # +obj-$(CONFIG_CLK_TI_AM3_DPLL) += clk-am3-dpll.o clk-am3-dpll-x2.o obj-$(CONFIG_CLK_TI_MUX) += clk-mux.o diff --git a/drivers/clk/ti/clk-am3-dpll-x2.c b/drivers/clk/ti/clk-am3-dpll-x2.c new file mode 100644 index 00000000000..3cf279d6a3a --- /dev/null +++ b/drivers/clk/ti/clk-am3-dpll-x2.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * TI DPLL x2 clock support + * + * Copyright (C) 2020 Dario Binacchi + * + * Loosely based on Linux kernel drivers/clk/ti/dpll.c + */ + +#include +#include +#include +#include +#include + +struct clk_ti_am3_dpll_x2_priv { + struct clk parent; +}; + +static ulong clk_ti_am3_dpll_x2_get_rate(struct clk *clk) +{ + struct clk_ti_am3_dpll_x2_priv *priv = dev_get_priv(clk->dev); + unsigned long rate; + + rate = clk_get_rate(&priv->parent); + if (IS_ERR_VALUE(rate)) + return rate; + + rate *= 2; + dev_dbg(clk->dev, "rate=%ld\n", rate); + return rate; +} + +const struct clk_ops clk_ti_am3_dpll_x2_ops = { + .get_rate = clk_ti_am3_dpll_x2_get_rate, +}; + +static int clk_ti_am3_dpll_x2_remove(struct udevice *dev) +{ + struct clk_ti_am3_dpll_x2_priv *priv = dev_get_priv(dev); + int err; + + err = clk_release_all(&priv->parent, 1); + if (err) { + dev_err(dev, "failed to release parent clock\n"); + return err; + } + + return 0; +} + +static int clk_ti_am3_dpll_x2_probe(struct udevice *dev) +{ + struct clk_ti_am3_dpll_x2_priv *priv = dev_get_priv(dev); + int err; + + err = clk_get_by_index(dev, 0, &priv->parent); + if (err) { + dev_err(dev, "%s: failed to get parent clock\n", __func__); + return err; + } + + return 0; +} + +static const struct udevice_id clk_ti_am3_dpll_x2_of_match[] = { + {.compatible = "ti,am3-dpll-x2-clock"}, + {} +}; + +U_BOOT_DRIVER(clk_ti_am3_dpll_x2) = { + .name = "ti_am3_dpll_x2_clock", + .id = UCLASS_CLK, + .of_match = clk_ti_am3_dpll_x2_of_match, + .probe = clk_ti_am3_dpll_x2_probe, + .remove = clk_ti_am3_dpll_x2_remove, + .priv_auto = sizeof(struct clk_ti_am3_dpll_x2_priv), + .ops = &clk_ti_am3_dpll_x2_ops, +}; diff --git a/drivers/clk/ti/clk-am3-dpll.c b/drivers/clk/ti/clk-am3-dpll.c new file mode 100644 index 00000000000..7916a24538b --- /dev/null +++ b/drivers/clk/ti/clk-am3-dpll.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * TI DPLL clock support + * + * Copyright (C) 2020 Dario Binacchi + * + * Loosely based on Linux kernel drivers/clk/ti/dpll.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct clk_ti_am3_dpll_drv_data { + ulong max_rate; +}; + +struct clk_ti_am3_dpll_priv { + fdt_addr_t clkmode_reg; + fdt_addr_t idlest_reg; + fdt_addr_t clksel_reg; + struct clk clk_bypass; + struct clk clk_ref; + u16 last_rounded_mult; + u8 last_rounded_div; + ulong max_rate; +}; + +static ulong clk_ti_am3_dpll_round_rate(struct clk *clk, ulong rate) +{ + struct clk_ti_am3_dpll_priv *priv = dev_get_priv(clk->dev); + ulong ret, ref_rate, r; + int m, d, err_min, err; + int mult = INT_MAX, div = INT_MAX; + + if (priv->max_rate && rate > priv->max_rate) { + dev_warn(clk->dev, "%ld is to high a rate, lowered to %ld\n", + rate, priv->max_rate); + rate = priv->max_rate; + } + + ret = -EFAULT; + err = rate; + err_min = rate; + ref_rate = clk_get_rate(&priv->clk_ref); + for (d = 1; err_min && d <= 128; d++) { + for (m = 2; m <= 2047; m++) { + r = (ref_rate * m) / d; + err = abs(r - rate); + if (err < err_min) { + err_min = err; + ret = r; + mult = m; + div = d; + + if (err == 0) + break; + } else if (r > rate) { + break; + } + } + } + + priv->last_rounded_mult = mult; + priv->last_rounded_div = div; + dev_dbg(clk->dev, "rate=%ld, best_rate=%ld, mult=%d, div=%d\n", rate, + ret, mult, div); + return ret; +} + +static ulong clk_ti_am3_dpll_set_rate(struct clk *clk, ulong rate) +{ + struct clk_ti_am3_dpll_priv *priv = dev_get_priv(clk->dev); + u32 v; + ulong round_rate; + + round_rate = clk_ti_am3_dpll_round_rate(clk, rate); + if (IS_ERR_VALUE(round_rate)) + return round_rate; + + v = readl(priv->clksel_reg); + + /* enter bypass mode */ + clrsetbits_le32(priv->clkmode_reg, CM_CLKMODE_DPLL_DPLL_EN_MASK, + DPLL_EN_MN_BYPASS << CM_CLKMODE_DPLL_EN_SHIFT); + + /* wait for bypass mode */ + if (!wait_on_value(ST_DPLL_CLK_MASK, 0, + (void *)priv->idlest_reg, LDELAY)) + dev_err(clk->dev, "failed bypassing dpll\n"); + + /* set M & N */ + v &= ~CM_CLKSEL_DPLL_M_MASK; + v |= (priv->last_rounded_mult << CM_CLKSEL_DPLL_M_SHIFT) & + CM_CLKSEL_DPLL_M_MASK; + + v &= ~CM_CLKSEL_DPLL_N_MASK; + v |= ((priv->last_rounded_div - 1) << CM_CLKSEL_DPLL_N_SHIFT) & + CM_CLKSEL_DPLL_N_MASK; + + writel(v, priv->clksel_reg); + + /* lock dpll */ + clrsetbits_le32(priv->clkmode_reg, CM_CLKMODE_DPLL_DPLL_EN_MASK, + DPLL_EN_LOCK << CM_CLKMODE_DPLL_EN_SHIFT); + + /* wait till the dpll locks */ + if (!wait_on_value(ST_DPLL_CLK_MASK, ST_DPLL_CLK_MASK, + (void *)priv->idlest_reg, LDELAY)) { + dev_err(clk->dev, "failed locking dpll\n"); + hang(); + } + + return round_rate; +} + +static ulong clk_ti_am3_dpll_get_rate(struct clk *clk) +{ + struct clk_ti_am3_dpll_priv *priv = dev_get_priv(clk->dev); + u64 rate; + u32 m, n, v; + + /* Return bypass rate if DPLL is bypassed */ + v = readl(priv->clkmode_reg); + v &= CM_CLKMODE_DPLL_EN_MASK; + v >>= CM_CLKMODE_DPLL_EN_SHIFT; + + switch (v) { + case DPLL_EN_MN_BYPASS: + case DPLL_EN_LOW_POWER_BYPASS: + case DPLL_EN_FAST_RELOCK_BYPASS: + rate = clk_get_rate(&priv->clk_bypass); + dev_dbg(clk->dev, "rate=%lld\n", rate); + return rate; + } + + v = readl(priv->clksel_reg); + m = v & CM_CLKSEL_DPLL_M_MASK; + m >>= CM_CLKSEL_DPLL_M_SHIFT; + n = v & CM_CLKSEL_DPLL_N_MASK; + n >>= CM_CLKSEL_DPLL_N_SHIFT; + + rate = clk_get_rate(&priv->clk_ref) * m; + do_div(rate, n + 1); + dev_dbg(clk->dev, "rate=%lld\n", rate); + return rate; +} + +const struct clk_ops clk_ti_am3_dpll_ops = { + .round_rate = clk_ti_am3_dpll_round_rate, + .get_rate = clk_ti_am3_dpll_get_rate, + .set_rate = clk_ti_am3_dpll_set_rate, +}; + +static int clk_ti_am3_dpll_remove(struct udevice *dev) +{ + struct clk_ti_am3_dpll_priv *priv = dev_get_priv(dev); + int err; + + err = clk_release_all(&priv->clk_bypass, 1); + if (err) { + dev_err(dev, "failed to release bypass clock\n"); + return err; + } + + err = clk_release_all(&priv->clk_ref, 1); + if (err) { + dev_err(dev, "failed to release reference clock\n"); + return err; + } + + return 0; +} + +static int clk_ti_am3_dpll_probe(struct udevice *dev) +{ + struct clk_ti_am3_dpll_priv *priv = dev_get_priv(dev); + int err; + + err = clk_get_by_index(dev, 0, &priv->clk_ref); + if (err) { + dev_err(dev, "failed to get reference clock\n"); + return err; + } + + err = clk_get_by_index(dev, 1, &priv->clk_bypass); + if (err) { + dev_err(dev, "failed to get bypass clock\n"); + return err; + } + + return 0; +} + +static int clk_ti_am3_dpll_of_to_plat(struct udevice *dev) +{ + struct clk_ti_am3_dpll_priv *priv = dev_get_priv(dev); + struct clk_ti_am3_dpll_drv_data *data = + (struct clk_ti_am3_dpll_drv_data *)dev_get_driver_data(dev); + + priv->max_rate = data->max_rate; + + priv->clkmode_reg = dev_read_addr_index(dev, 0); + if (priv->clkmode_reg == FDT_ADDR_T_NONE) { + dev_err(dev, "failed to get clkmode register\n"); + return -EINVAL; + } + + dev_dbg(dev, "clkmode_reg=0x%08lx\n", priv->clkmode_reg); + + priv->idlest_reg = dev_read_addr_index(dev, 1); + if (priv->idlest_reg == FDT_ADDR_T_NONE) { + dev_err(dev, "failed to get idlest register\n"); + return -EINVAL; + } + + dev_dbg(dev, "idlest_reg=0x%08lx\n", priv->idlest_reg); + + priv->clksel_reg = dev_read_addr_index(dev, 2); + if (priv->clksel_reg == FDT_ADDR_T_NONE) { + dev_err(dev, "failed to get clksel register\n"); + return -EINVAL; + } + + dev_dbg(dev, "clksel_reg=0x%08lx\n", priv->clksel_reg); + + return 0; +} + +static const struct clk_ti_am3_dpll_drv_data dpll_no_gate_data = { + .max_rate = 1000000000 +}; + +static const struct clk_ti_am3_dpll_drv_data dpll_no_gate_j_type_data = { + .max_rate = 2000000000 +}; + +static const struct clk_ti_am3_dpll_drv_data dpll_core_data = { + .max_rate = 1000000000 +}; + +static const struct udevice_id clk_ti_am3_dpll_of_match[] = { + {.compatible = "ti,am3-dpll-core-clock", + .data = (ulong)&dpll_core_data}, + {.compatible = "ti,am3-dpll-no-gate-clock", + .data = (ulong)&dpll_no_gate_data}, + {.compatible = "ti,am3-dpll-no-gate-j-type-clock", + .data = (ulong)&dpll_no_gate_j_type_data}, + {} +}; + +U_BOOT_DRIVER(clk_ti_am3_dpll) = { + .name = "ti_am3_dpll_clock", + .id = UCLASS_CLK, + .of_match = clk_ti_am3_dpll_of_match, + .ofdata_to_platdata = clk_ti_am3_dpll_of_to_plat, + .probe = clk_ti_am3_dpll_probe, + .remove = clk_ti_am3_dpll_remove, + .priv_auto = sizeof(struct clk_ti_am3_dpll_priv), + .ops = &clk_ti_am3_dpll_ops, +}; -- cgit v1.3.1 From ea45b8f28d0638e49ff361814ebe7e8bdd536a48 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:06:35 +0100 Subject: clk: ti: add divider clock driver The patch adds support for TI divider clock binding. The driver uses routines provided by the common clock framework (ccf). The code is based on the drivers/clk/ti/divider.c driver of the Linux kernel version 5.9-rc7. For DT binding details see: - Documentation/devicetree/bindings/clock/ti/divider.txt Signed-off-by: Dario Binacchi --- drivers/clk/ti/Kconfig | 6 + drivers/clk/ti/Makefile | 3 + drivers/clk/ti/clk-divider.c | 381 +++++++++++++++++++++++++++++++++++++++++++ drivers/clk/ti/clk-mux.c | 27 +-- drivers/clk/ti/clk.c | 34 ++++ drivers/clk/ti/clk.h | 13 ++ 6 files changed, 439 insertions(+), 25 deletions(-) create mode 100644 drivers/clk/ti/clk-divider.c create mode 100644 drivers/clk/ti/clk.c create mode 100644 drivers/clk/ti/clk.h (limited to 'drivers') diff --git a/drivers/clk/ti/Kconfig b/drivers/clk/ti/Kconfig index c430dd9b8ae..87eea86c6fe 100644 --- a/drivers/clk/ti/Kconfig +++ b/drivers/clk/ti/Kconfig @@ -10,6 +10,12 @@ config CLK_TI_AM3_DPLL This enables the DPLL clock drivers support on AM33XX SoCs. The DPLL provides all interface clocks and functional clocks to the processor. +config CLK_TI_DIVIDER + bool "TI divider clock driver" + depends on CLK && OF_CONTROL && CLK_CCF + help + This enables the divider clock driver support on TI's SoCs. + config CLK_TI_MUX bool "TI mux clock driver" depends on CLK && OF_CONTROL && CLK_CCF diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile index 9e14b83cfe9..fd7094cff0c 100644 --- a/drivers/clk/ti/Makefile +++ b/drivers/clk/ti/Makefile @@ -3,5 +3,8 @@ # Copyright (C) 2020 Dario Binacchi # +obj-$(CONFIG_ARCH_OMAP2PLUS) += clk.o + obj-$(CONFIG_CLK_TI_AM3_DPLL) += clk-am3-dpll.o clk-am3-dpll-x2.o +obj-$(CONFIG_CLK_TI_DIVIDER) += clk-divider.o obj-$(CONFIG_CLK_TI_MUX) += clk-mux.o diff --git a/drivers/clk/ti/clk-divider.c b/drivers/clk/ti/clk-divider.c new file mode 100644 index 00000000000..a8626377851 --- /dev/null +++ b/drivers/clk/ti/clk-divider.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * TI divider clock support + * + * Copyright (C) 2020 Dario Binacchi + * + * Loosely based on Linux kernel drivers/clk/ti/divider.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "clk.h" + +/* + * The reverse of DIV_ROUND_UP: The maximum number which + * divided by m is r + */ +#define MULT_ROUND_UP(r, m) ((r) * (m) + (m) - 1) + +struct clk_ti_divider_priv { + struct clk parent; + fdt_addr_t reg; + const struct clk_div_table *table; + u8 shift; + u8 flags; + u8 div_flags; + s8 latch; + u16 min; + u16 max; + u16 mask; +}; + +static unsigned int _get_div(const struct clk_div_table *table, ulong flags, + unsigned int val) +{ + if (flags & CLK_DIVIDER_ONE_BASED) + return val; + + if (flags & CLK_DIVIDER_POWER_OF_TWO) + return 1 << val; + + if (table) + return clk_divider_get_table_div(table, val); + + return val + 1; +} + +static unsigned int _get_val(const struct clk_div_table *table, ulong flags, + unsigned int div) +{ + if (flags & CLK_DIVIDER_ONE_BASED) + return div; + + if (flags & CLK_DIVIDER_POWER_OF_TWO) + return __ffs(div); + + if (table) + return clk_divider_get_table_val(table, div); + + return div - 1; +} + +static int _div_round_up(const struct clk_div_table *table, ulong parent_rate, + ulong rate) +{ + const struct clk_div_table *clkt; + int up = INT_MAX; + int div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); + + for (clkt = table; clkt->div; clkt++) { + if (clkt->div == div) + return clkt->div; + else if (clkt->div < div) + continue; + + if ((clkt->div - div) < (up - div)) + up = clkt->div; + } + + return up; +} + +static int _div_round(const struct clk_div_table *table, ulong parent_rate, + ulong rate) +{ + if (table) + return _div_round_up(table, parent_rate, rate); + + return DIV_ROUND_UP(parent_rate, rate); +} + +static int clk_ti_divider_best_div(struct clk *clk, ulong rate, + ulong *best_parent_rate) +{ + struct clk_ti_divider_priv *priv = dev_get_priv(clk->dev); + ulong parent_rate, parent_round_rate, max_div; + ulong best_rate, r; + int i, best_div = 0; + + parent_rate = clk_get_rate(&priv->parent); + if (IS_ERR_VALUE(parent_rate)) + return parent_rate; + + if (!rate) + rate = 1; + + if (!(clk->flags & CLK_SET_RATE_PARENT)) { + best_div = _div_round(priv->table, parent_rate, rate); + if (best_div == 0) + best_div = 1; + + if (best_div > priv->max) + best_div = priv->max; + + *best_parent_rate = parent_rate; + return best_div; + } + + max_div = min(ULONG_MAX / rate, (ulong)priv->max); + for (best_rate = 0, i = 1; i <= max_div; i++) { + if (!clk_divider_is_valid_div(priv->table, priv->div_flags, i)) + continue; + + /* + * It's the most ideal case if the requested rate can be + * divided from parent clock without needing to change + * parent rate, so return the divider immediately. + */ + if ((rate * i) == parent_rate) { + *best_parent_rate = parent_rate; + dev_dbg(clk->dev, "rate=%ld, best_rate=%ld, div=%d\n", + rate, rate, i); + return i; + } + + parent_round_rate = clk_round_rate(&priv->parent, + MULT_ROUND_UP(rate, i)); + if (IS_ERR_VALUE(parent_round_rate)) + continue; + + r = DIV_ROUND_UP(parent_round_rate, i); + if (r <= rate && r > best_rate) { + best_div = i; + best_rate = r; + *best_parent_rate = parent_round_rate; + if (best_rate == rate) + break; + } + } + + if (best_div == 0) { + best_div = priv->max; + parent_round_rate = clk_round_rate(&priv->parent, 1); + if (IS_ERR_VALUE(parent_round_rate)) + return parent_round_rate; + } + + dev_dbg(clk->dev, "rate=%ld, best_rate=%ld, div=%d\n", rate, best_rate, + best_div); + + return best_div; +} + +static ulong clk_ti_divider_round_rate(struct clk *clk, ulong rate) +{ + ulong parent_rate; + int div; + + div = clk_ti_divider_best_div(clk, rate, &parent_rate); + if (div < 0) + return div; + + return DIV_ROUND_UP(parent_rate, div); +} + +static ulong clk_ti_divider_set_rate(struct clk *clk, ulong rate) +{ + struct clk_ti_divider_priv *priv = dev_get_priv(clk->dev); + ulong parent_rate; + int div; + u32 val, v; + + div = clk_ti_divider_best_div(clk, rate, &parent_rate); + if (div < 0) + return div; + + if (clk->flags & CLK_SET_RATE_PARENT) { + parent_rate = clk_set_rate(&priv->parent, parent_rate); + if (IS_ERR_VALUE(parent_rate)) + return parent_rate; + } + + val = _get_val(priv->table, priv->div_flags, div); + + v = readl(priv->reg); + v &= ~(priv->mask << priv->shift); + v |= val << priv->shift; + writel(v, priv->reg); + clk_ti_latch(priv->reg, priv->latch); + + return clk_get_rate(clk); +} + +static ulong clk_ti_divider_get_rate(struct clk *clk) +{ + struct clk_ti_divider_priv *priv = dev_get_priv(clk->dev); + ulong rate, parent_rate; + unsigned int div; + u32 v; + + parent_rate = clk_get_rate(&priv->parent); + if (IS_ERR_VALUE(parent_rate)) + return parent_rate; + + v = readl(priv->reg) >> priv->shift; + v &= priv->mask; + + div = _get_div(priv->table, priv->div_flags, v); + if (!div) { + if (!(priv->div_flags & CLK_DIVIDER_ALLOW_ZERO)) + dev_warn(clk->dev, + "zero divisor and CLK_DIVIDER_ALLOW_ZERO not set\n"); + return parent_rate; + } + + rate = DIV_ROUND_UP(parent_rate, div); + dev_dbg(clk->dev, "rate=%ld\n", rate); + return rate; +} + +static int clk_ti_divider_request(struct clk *clk) +{ + struct clk_ti_divider_priv *priv = dev_get_priv(clk->dev); + + clk->flags = priv->flags; + return 0; +} + +const struct clk_ops clk_ti_divider_ops = { + .request = clk_ti_divider_request, + .round_rate = clk_ti_divider_round_rate, + .get_rate = clk_ti_divider_get_rate, + .set_rate = clk_ti_divider_set_rate +}; + +static int clk_ti_divider_remove(struct udevice *dev) +{ + struct clk_ti_divider_priv *priv = dev_get_priv(dev); + int err; + + err = clk_release_all(&priv->parent, 1); + if (err) { + dev_err(dev, "failed to release parent clock\n"); + return err; + } + + return 0; +} + +static int clk_ti_divider_probe(struct udevice *dev) +{ + struct clk_ti_divider_priv *priv = dev_get_priv(dev); + int err; + + err = clk_get_by_index(dev, 0, &priv->parent); + if (err) { + dev_err(dev, "failed to get parent clock\n"); + return err; + } + + return 0; +} + +static int clk_ti_divider_of_to_plat(struct udevice *dev) +{ + struct clk_ti_divider_priv *priv = dev_get_priv(dev); + struct clk_div_table *table = NULL; + u32 val, valid_div; + u32 min_div = 0; + u32 max_val, max_div = 0; + u16 mask; + int i, div_num; + + priv->reg = dev_read_addr(dev); + dev_dbg(dev, "reg=0x%08lx\n", priv->reg); + priv->shift = dev_read_u32_default(dev, "ti,bit-shift", 0); + priv->latch = dev_read_s32_default(dev, "ti,latch-bit", -EINVAL); + if (dev_read_bool(dev, "ti,index-starts-at-one")) + priv->div_flags |= CLK_DIVIDER_ONE_BASED; + + if (dev_read_bool(dev, "ti,index-power-of-two")) + priv->div_flags |= CLK_DIVIDER_POWER_OF_TWO; + + if (dev_read_bool(dev, "ti,set-rate-parent")) + priv->flags |= CLK_SET_RATE_PARENT; + + if (dev_read_prop(dev, "ti,dividers", &div_num)) { + div_num /= sizeof(u32); + + /* Determine required size for divider table */ + for (i = 0, valid_div = 0; i < div_num; i++) { + dev_read_u32_index(dev, "ti,dividers", i, &val); + if (val) + valid_div++; + } + + if (!valid_div) { + dev_err(dev, "no valid dividers\n"); + return -EINVAL; + } + + table = calloc(valid_div + 1, sizeof(*table)); + if (!table) + return -ENOMEM; + + for (i = 0, valid_div = 0; i < div_num; i++) { + dev_read_u32_index(dev, "ti,dividers", i, &val); + if (!val) + continue; + + table[valid_div].div = val; + table[valid_div].val = i; + valid_div++; + if (val > max_div) + max_div = val; + + if (!min_div || val < min_div) + min_div = val; + } + + max_val = max_div; + } else { + /* Divider table not provided, determine min/max divs */ + min_div = dev_read_u32_default(dev, "ti,min-div", 1); + if (dev_read_u32(dev, "ti,max-div", &max_div)) { + dev_err(dev, "missing 'max-div' property\n"); + return -EFAULT; + } + + max_val = max_div; + if (!(priv->div_flags & CLK_DIVIDER_ONE_BASED) && + !(priv->div_flags & CLK_DIVIDER_POWER_OF_TWO)) + max_val--; + } + + priv->table = table; + priv->min = min_div; + priv->max = max_div; + + if (priv->div_flags & CLK_DIVIDER_POWER_OF_TWO) + mask = fls(max_val) - 1; + else + mask = max_val; + + priv->mask = (1 << fls(mask)) - 1; + return 0; +} + +static const struct udevice_id clk_ti_divider_of_match[] = { + {.compatible = "ti,divider-clock"}, + {} +}; + +U_BOOT_DRIVER(clk_ti_divider) = { + .name = "ti_divider_clock", + .id = UCLASS_CLK, + .of_match = clk_ti_divider_of_match, + .ofdata_to_platdata = clk_ti_divider_of_to_plat, + .probe = clk_ti_divider_probe, + .remove = clk_ti_divider_remove, + .priv_auto = sizeof(struct clk_ti_divider_priv), + .ops = &clk_ti_divider_ops, +}; diff --git a/drivers/clk/ti/clk-mux.c b/drivers/clk/ti/clk-mux.c index 1e22a50910c..419502c3899 100644 --- a/drivers/clk/ti/clk-mux.c +++ b/drivers/clk/ti/clk-mux.c @@ -13,6 +13,7 @@ #include #include #include +#include "clk.h" struct clk_ti_mux_priv { struct clk_bulk parents; @@ -24,30 +25,6 @@ struct clk_ti_mux_priv { s32 latch; }; -static void clk_ti_mux_rmw(u32 val, u32 mask, fdt_addr_t reg) -{ - u32 v; - - v = readl(reg); - v &= ~mask; - v |= val; - writel(v, reg); -} - -static void clk_ti_mux_latch(fdt_addr_t reg, s8 shift) -{ - u32 latch; - - if (shift < 0) - return; - - latch = 1 << shift; - - clk_ti_mux_rmw(latch, latch, reg); - clk_ti_mux_rmw(0, latch, reg); - readl(reg); /* OCP barrier */ -} - static struct clk *clk_ti_mux_get_parent_by_index(struct clk_bulk *parents, int index) { @@ -120,7 +97,7 @@ static int clk_ti_mux_set_parent(struct clk *clk, struct clk *parent) val |= index << priv->shift; writel(val, priv->reg); - clk_ti_mux_latch(priv->reg, priv->latch); + clk_ti_latch(priv->reg, priv->latch); return 0; } diff --git a/drivers/clk/ti/clk.c b/drivers/clk/ti/clk.c new file mode 100644 index 00000000000..e44b90ad6ab --- /dev/null +++ b/drivers/clk/ti/clk.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * TI clock utilities + * + * Copyright (C) 2020 Dario Binacchi + */ + +#include +#include +#include "clk.h" + +static void clk_ti_rmw(u32 val, u32 mask, fdt_addr_t reg) +{ + u32 v; + + v = readl(reg); + v &= ~mask; + v |= val; + writel(v, reg); +} + +void clk_ti_latch(fdt_addr_t reg, s8 shift) +{ + u32 latch; + + if (shift < 0) + return; + + latch = 1 << shift; + + clk_ti_rmw(latch, latch, reg); + clk_ti_rmw(0, latch, reg); + readl(reg); /* OCP barrier */ +} diff --git a/drivers/clk/ti/clk.h b/drivers/clk/ti/clk.h new file mode 100644 index 00000000000..601c3823f70 --- /dev/null +++ b/drivers/clk/ti/clk.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * TI clock utilities header + * + * Copyright (C) 2020 Dario Binacchi + */ + +#ifndef _CLK_TI_H +#define _CLK_TI_H + +void clk_ti_latch(fdt_addr_t reg, s8 shift); + +#endif /* #ifndef _CLK_TI_H */ -- cgit v1.3.1 From 58e1af972f4f8855561ec018e7a9c565d53c4639 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:06:36 +0100 Subject: clk: ti: add gate clock driver The patch adds support for TI gate clock binding. The code is based on the drivers/clk/ti/gate.c driver of the Linux kernel version 5.9-rc7. For DT binding details see: - Documentation/devicetree/bindings/clock/ti/gate.txt Signed-off-by: Dario Binacchi --- drivers/clk/ti/Kconfig | 6 +++ drivers/clk/ti/Makefile | 1 + drivers/clk/ti/clk-gate.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 drivers/clk/ti/clk-gate.c (limited to 'drivers') diff --git a/drivers/clk/ti/Kconfig b/drivers/clk/ti/Kconfig index 87eea86c6fe..30959a316af 100644 --- a/drivers/clk/ti/Kconfig +++ b/drivers/clk/ti/Kconfig @@ -16,6 +16,12 @@ config CLK_TI_DIVIDER help This enables the divider clock driver support on TI's SoCs. +config CLK_TI_GATE + bool "TI gate clock driver" + depends on CLK && OF_CONTROL + help + This enables the gate clock driver support on TI's SoCs. + config CLK_TI_MUX bool "TI mux clock driver" depends on CLK && OF_CONTROL && CLK_CCF diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile index fd7094cff0c..f8aa735c833 100644 --- a/drivers/clk/ti/Makefile +++ b/drivers/clk/ti/Makefile @@ -7,4 +7,5 @@ obj-$(CONFIG_ARCH_OMAP2PLUS) += clk.o obj-$(CONFIG_CLK_TI_AM3_DPLL) += clk-am3-dpll.o clk-am3-dpll-x2.o obj-$(CONFIG_CLK_TI_DIVIDER) += clk-divider.o +obj-$(CONFIG_CLK_TI_GATE) += clk-gate.o obj-$(CONFIG_CLK_TI_MUX) += clk-mux.o diff --git a/drivers/clk/ti/clk-gate.c b/drivers/clk/ti/clk-gate.c new file mode 100644 index 00000000000..236eaed6df1 --- /dev/null +++ b/drivers/clk/ti/clk-gate.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * TI gate clock support + * + * Copyright (C) 2020 Dario Binacchi + * + * Loosely based on Linux kernel drivers/clk/ti/gate.c + */ + +#include +#include +#include +#include +#include +#include + +struct clk_ti_gate_priv { + fdt_addr_t reg; + u8 enable_bit; + u32 flags; + bool invert_enable; +}; + +static int clk_ti_gate_disable(struct clk *clk) +{ + struct clk_ti_gate_priv *priv = dev_get_priv(clk->dev); + u32 v; + + v = readl(priv->reg); + if (priv->invert_enable) + v |= (1 << priv->enable_bit); + else + v &= ~(1 << priv->enable_bit); + + writel(v, priv->reg); + /* No OCP barrier needed here since it is a disable operation */ + return 0; +} + +static int clk_ti_gate_enable(struct clk *clk) +{ + struct clk_ti_gate_priv *priv = dev_get_priv(clk->dev); + u32 v; + + v = readl(priv->reg); + if (priv->invert_enable) + v &= ~(1 << priv->enable_bit); + else + v |= (1 << priv->enable_bit); + + writel(v, priv->reg); + /* OCP barrier */ + v = readl(priv->reg); + return 0; +} + +static int clk_ti_gate_of_to_plat(struct udevice *dev) +{ + struct clk_ti_gate_priv *priv = dev_get_priv(dev); + + priv->reg = dev_read_addr(dev); + if (priv->reg == FDT_ADDR_T_NONE) { + dev_err(dev, "failed to get control register\n"); + return -EINVAL; + } + + dev_dbg(dev, "reg=0x%08lx\n", priv->reg); + priv->enable_bit = dev_read_u32_default(dev, "ti,bit-shift", 0); + if (dev_read_bool(dev, "ti,set-rate-parent")) + priv->flags |= CLK_SET_RATE_PARENT; + + priv->invert_enable = dev_read_bool(dev, "ti,set-bit-to-disable"); + return 0; +} + +static struct clk_ops clk_ti_gate_ops = { + .enable = clk_ti_gate_enable, + .disable = clk_ti_gate_disable, +}; + +static const struct udevice_id clk_ti_gate_of_match[] = { + { .compatible = "ti,gate-clock" }, + { }, +}; + +U_BOOT_DRIVER(clk_ti_gate) = { + .name = "ti_gate_clock", + .id = UCLASS_CLK, + .of_match = clk_ti_gate_of_match, + .ofdata_to_platdata = clk_ti_gate_of_to_plat, + .priv_auto = sizeof(struct clk_ti_gate_priv), + .ops = &clk_ti_gate_ops, +}; -- cgit v1.3.1 From 215bd541b8bb71fa97b4dc158f7c533be23043ef Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:06:39 +0100 Subject: clk: ti: add support for clkctrl clocks Until now the clkctrl clocks have been enabled/disabled through platform routines. Thanks to this patch they can be enabled and configured directly by the probed devices that need to use them. For DT binding details see Linux doc: - Documentation/devicetree/bindings/clock/ti-clkctrl.txt Signed-off-by: Dario Binacchi --- drivers/clk/ti/Kconfig | 6 ++ drivers/clk/ti/Makefile | 1 + drivers/clk/ti/clk-ctrl.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 drivers/clk/ti/clk-ctrl.c (limited to 'drivers') diff --git a/drivers/clk/ti/Kconfig b/drivers/clk/ti/Kconfig index 30959a316af..9e257a2eb7c 100644 --- a/drivers/clk/ti/Kconfig +++ b/drivers/clk/ti/Kconfig @@ -10,6 +10,12 @@ config CLK_TI_AM3_DPLL This enables the DPLL clock drivers support on AM33XX SoCs. The DPLL provides all interface clocks and functional clocks to the processor. +config CLK_TI_CTRL + bool "TI OMAP4 clock controller" + depends on CLK && OF_CONTROL + help + This enables the clock controller driver support on TI's SoCs. + config CLK_TI_DIVIDER bool "TI divider clock driver" depends on CLK && OF_CONTROL && CLK_CCF diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile index f8aa735c833..ed45f18311b 100644 --- a/drivers/clk/ti/Makefile +++ b/drivers/clk/ti/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_ARCH_OMAP2PLUS) += clk.o obj-$(CONFIG_CLK_TI_AM3_DPLL) += clk-am3-dpll.o clk-am3-dpll-x2.o +obj-$(CONFIG_CLK_TI_CTRL) += clk-ctrl.o obj-$(CONFIG_CLK_TI_DIVIDER) += clk-divider.o obj-$(CONFIG_CLK_TI_GATE) += clk-gate.o obj-$(CONFIG_CLK_TI_MUX) += clk-mux.o diff --git a/drivers/clk/ti/clk-ctrl.c b/drivers/clk/ti/clk-ctrl.c new file mode 100644 index 00000000000..940e8d6cafb --- /dev/null +++ b/drivers/clk/ti/clk-ctrl.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * OMAP clock controller support + * + * Copyright (C) 2020 Dario Binacchi + */ + +#include +#include +#include +#include +#include + +struct clk_ti_ctrl_offs { + fdt_addr_t start; + fdt_size_t end; +}; + +struct clk_ti_ctrl_priv { + int offs_num; + struct clk_ti_ctrl_offs *offs; +}; + +static int clk_ti_ctrl_check_offs(struct clk *clk, fdt_addr_t offs) +{ + struct clk_ti_ctrl_priv *priv = dev_get_priv(clk->dev); + int i; + + for (i = 0; i < priv->offs_num; i++) { + if (offs >= priv->offs[i].start && offs <= priv->offs[i].end) + return 0; + } + + return -EFAULT; +} + +static int clk_ti_ctrl_disable(struct clk *clk) +{ + struct clk_ti_ctrl_priv *priv = dev_get_priv(clk->dev); + u32 *clk_modules[2] = { }; + fdt_addr_t offs; + int err; + + offs = priv->offs[0].start + clk->id; + err = clk_ti_ctrl_check_offs(clk, offs); + if (err) { + dev_err(clk->dev, "invalid offset: 0x%lx\n", offs); + return err; + } + + clk_modules[0] = (u32 *)(offs); + dev_dbg(clk->dev, "module address=%p\n", clk_modules[0]); + do_disable_clocks(NULL, clk_modules, 1); + return 0; +} + +static int clk_ti_ctrl_enable(struct clk *clk) +{ + struct clk_ti_ctrl_priv *priv = dev_get_priv(clk->dev); + u32 *clk_modules[2] = { }; + fdt_addr_t offs; + int err; + + offs = priv->offs[0].start + clk->id; + err = clk_ti_ctrl_check_offs(clk, offs); + if (err) { + dev_err(clk->dev, "invalid offset: 0x%lx\n", offs); + return err; + } + + clk_modules[0] = (u32 *)(offs); + dev_dbg(clk->dev, "module address=%p\n", clk_modules[0]); + do_enable_clocks(NULL, clk_modules, 1); + return 0; +} + +static ulong clk_ti_ctrl_get_rate(struct clk *clk) +{ + return 0; +} + +static int clk_ti_ctrl_of_xlate(struct clk *clk, + struct ofnode_phandle_args *args) +{ + if (args->args_count != 2) { + dev_err(clk->dev, "invaild args_count: %d\n", args->args_count); + return -EINVAL; + } + + if (args->args_count) + clk->id = args->args[0]; + else + clk->id = 0; + + dev_dbg(clk->dev, "name=%s, id=%ld\n", clk->dev->name, clk->id); + return 0; +} + +static int clk_ti_ctrl_of_to_plat(struct udevice *dev) +{ + struct clk_ti_ctrl_priv *priv = dev_get_priv(dev); + fdt_size_t fdt_size; + int i, size; + + size = dev_read_size(dev, "reg"); + if (size < 0) { + dev_err(dev, "failed to get 'reg' size\n"); + return size; + } + + priv->offs_num = size / 2 / sizeof(u32); + dev_dbg(dev, "size=%d, regs_num=%d\n", size, priv->offs_num); + + priv->offs = kmalloc_array(priv->offs_num, sizeof(*priv->offs), + GFP_KERNEL); + if (!priv->offs) + return -ENOMEM; + + for (i = 0; i < priv->offs_num; i++) { + priv->offs[i].start = + dev_read_addr_size_index(dev, i, &fdt_size); + if (priv->offs[i].start == FDT_ADDR_T_NONE) { + dev_err(dev, "failed to get offset %d\n", i); + return -EINVAL; + } + + priv->offs[i].end = priv->offs[i].start + fdt_size; + dev_dbg(dev, "start=0x%08lx, end=0x%08lx\n", + priv->offs[i].start, priv->offs[i].end); + } + + return 0; +} + +static struct clk_ops clk_ti_ctrl_ops = { + .of_xlate = clk_ti_ctrl_of_xlate, + .enable = clk_ti_ctrl_enable, + .disable = clk_ti_ctrl_disable, + .get_rate = clk_ti_ctrl_get_rate, +}; + +static const struct udevice_id clk_ti_ctrl_ids[] = { + {.compatible = "ti,clkctrl"}, + {}, +}; + +U_BOOT_DRIVER(clk_ti_ctrl) = { + .name = "ti_ctrl_clk", + .id = UCLASS_CLK, + .of_match = clk_ti_ctrl_ids, + .ofdata_to_platdata = clk_ti_ctrl_of_to_plat, + .ops = &clk_ti_ctrl_ops, + .priv_auto = sizeof(struct clk_ti_ctrl_priv), +}; -- cgit v1.3.1 From 06c94c2463fa4ce85f162851cac2112e9b5bdf9f Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:16:18 +0100 Subject: clk: ti: omap4: add clock manager driver This minimal driver is only used to bind child devices. For DT binding details see Linux doc: - Documentation/devicetree/bindings/arm/omap/prcm.txt Signed-off-by: Dario Binacchi --- drivers/clk/ti/Makefile | 2 +- drivers/clk/ti/omap4-cm.c | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 drivers/clk/ti/omap4-cm.c (limited to 'drivers') diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile index ed45f18311b..dbd343069c6 100644 --- a/drivers/clk/ti/Makefile +++ b/drivers/clk/ti/Makefile @@ -3,7 +3,7 @@ # Copyright (C) 2020 Dario Binacchi # -obj-$(CONFIG_ARCH_OMAP2PLUS) += clk.o +obj-$(CONFIG_ARCH_OMAP2PLUS) += clk.o omap4-cm.o obj-$(CONFIG_CLK_TI_AM3_DPLL) += clk-am3-dpll.o clk-am3-dpll-x2.o obj-$(CONFIG_CLK_TI_CTRL) += clk-ctrl.o diff --git a/drivers/clk/ti/omap4-cm.c b/drivers/clk/ti/omap4-cm.c new file mode 100644 index 00000000000..3cdc9b28887 --- /dev/null +++ b/drivers/clk/ti/omap4-cm.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * OMAP4 clock manager (cm) + * + * Copyright (C) 2020 Dario Binacchi + */ + +#include +#include +#include + +static const struct udevice_id ti_omap4_cm_ids[] = { + {.compatible = "ti,omap4-cm"}, + {} +}; + +U_BOOT_DRIVER(ti_omap4_cm) = { + .name = "ti_omap4_cm", + .id = UCLASS_SIMPLE_BUS, + .of_match = ti_omap4_cm_ids, + .bind = dm_scan_fdt_dev, +}; -- cgit v1.3.1 From 52b61c944ce536f975d095d4fa32d8bb656661fd Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:16:20 +0100 Subject: clk: move clk-ti-sci driver to 'ti' directory The patch moves the clk-ti-sci.c file to the 'ti' directory along with all the other TI's drivers, and renames it clk-sci.c. Signed-off-by: Dario Binacchi --- drivers/clk/Kconfig | 8 -- drivers/clk/Makefile | 1 - drivers/clk/clk-ti-sci.c | 225 ----------------------------------------------- drivers/clk/ti/Kconfig | 8 ++ drivers/clk/ti/Makefile | 1 + drivers/clk/ti/clk-sci.c | 225 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 234 insertions(+), 234 deletions(-) delete mode 100644 drivers/clk/clk-ti-sci.c create mode 100644 drivers/clk/ti/clk-sci.c (limited to 'drivers') diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 9e549290398..db06f276ec2 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -98,14 +98,6 @@ config CLK_STM32F This clock driver adds support for RCC clock management for STM32F4 and STM32F7 SoCs. -config CLK_TI_SCI - bool "TI System Control Interface (TI SCI) clock driver" - depends on CLK && TI_SCI_PROTOCOL && OF_CONTROL - help - This enables the clock driver support over TI System Control Interface - available on some new TI's SoCs. If you wish to use clock resources - managed by the TI System Controller, say Y here. Otherwise, say N. - config CLK_HSDK bool "Enable cgu clock driver for HSDK boards" depends on CLK && TARGET_HSDK diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 2581fe0a198..f8383e523d6 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -48,6 +48,5 @@ obj-$(CONFIG_SANDBOX) += clk_sandbox.o 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-ti-sci.c b/drivers/clk/clk-ti-sci.c deleted file mode 100644 index 6f0fdaa111c..00000000000 --- a/drivers/clk/clk-ti-sci.c +++ /dev/null @@ -1,225 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Texas Instruments System Control Interface (TI SCI) clock driver - * - * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ - * Andreas Dannenberg - * - * Loosely based on Linux kernel sci-clk.c... - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/** - * struct ti_sci_clk_data - clock controller information structure - * @sci: TI SCI handle used for communication with system controller - */ -struct ti_sci_clk_data { - const struct ti_sci_handle *sci; -}; - -static int ti_sci_clk_probe(struct udevice *dev) -{ - struct ti_sci_clk_data *data = dev_get_priv(dev); - - debug("%s(dev=%p)\n", __func__, dev); - - if (!data) - return -ENOMEM; - - /* Store handle for communication with the system controller */ - data->sci = ti_sci_get_handle(dev); - if (IS_ERR(data->sci)) - return PTR_ERR(data->sci); - - return 0; -} - -static int ti_sci_clk_of_xlate(struct clk *clk, - struct ofnode_phandle_args *args) -{ - debug("%s(clk=%p, args_count=%d)\n", __func__, clk, args->args_count); - - if (args->args_count != 2) { - debug("Invalid args_count: %d\n", args->args_count); - return -EINVAL; - } - - /* - * On TI SCI-based devices, the clock provider id field is used as a - * device ID, and the data field is used as the associated sub-ID. - */ - clk->id = args->args[0]; - clk->data = args->args[1]; - - return 0; -} - -static int ti_sci_clk_request(struct clk *clk) -{ - debug("%s(clk=%p)\n", __func__, clk); - return 0; -} - -static int ti_sci_clk_free(struct clk *clk) -{ - debug("%s(clk=%p)\n", __func__, clk); - return 0; -} - -static ulong ti_sci_clk_get_rate(struct clk *clk) -{ - struct ti_sci_clk_data *data = dev_get_priv(clk->dev); - const struct ti_sci_handle *sci = data->sci; - const struct ti_sci_clk_ops *cops = &sci->ops.clk_ops; - u64 current_freq; - int ret; - - debug("%s(clk=%p)\n", __func__, clk); - - ret = cops->get_freq(sci, clk->id, clk->data, ¤t_freq); - if (ret) { - dev_err(clk->dev, "%s: get_freq failed (%d)\n", __func__, ret); - return ret; - } - - debug("%s(current_freq=%llu)\n", __func__, current_freq); - - return current_freq; -} - -static ulong ti_sci_clk_set_rate(struct clk *clk, ulong rate) -{ - struct ti_sci_clk_data *data = dev_get_priv(clk->dev); - const struct ti_sci_handle *sci = data->sci; - const struct ti_sci_clk_ops *cops = &sci->ops.clk_ops; - int ret; - - debug("%s(clk=%p, rate=%lu)\n", __func__, clk, rate); - -#ifdef CONFIG_K3_AVS0 - k3_avs_notify_freq(clk->id, clk->data, rate); -#endif - - ret = cops->set_freq(sci, clk->id, clk->data, 0, rate, ULONG_MAX); - if (ret) - dev_err(clk->dev, "%s: set_freq failed (%d)\n", __func__, ret); - - return ret; -} - -static int ti_sci_clk_set_parent(struct clk *clk, struct clk *parent) -{ - struct ti_sci_clk_data *data = dev_get_priv(clk->dev); - const struct ti_sci_handle *sci = data->sci; - const struct ti_sci_clk_ops *cops = &sci->ops.clk_ops; - u8 num_parents; - u8 parent_cid; - int ret; - - debug("%s(clk=%p, parent=%p)\n", __func__, clk, parent); - - /* Make sure the clock parent is valid for a given device ID */ - if (clk->id != parent->id) - return -EINVAL; - - /* Make sure clock has parents that can be set */ - ret = cops->get_num_parents(sci, clk->id, clk->data, &num_parents); - if (ret) { - dev_err(clk->dev, "%s: get_num_parents failed (%d)\n", - __func__, ret); - return ret; - } - if (num_parents < 2) { - dev_err(clk->dev, "%s: clock has no settable parents!\n", - __func__); - return -EINVAL; - } - - /* Make sure parent clock ID is valid */ - parent_cid = parent->data - clk->data - 1; - if (parent_cid >= num_parents) { - dev_err(clk->dev, "%s: invalid parent clock!\n", __func__); - return -EINVAL; - } - - /* Ready to proceed to configure the new clock parent */ - ret = cops->set_parent(sci, clk->id, clk->data, parent->data); - if (ret) - dev_err(clk->dev, "%s: set_parent failed (%d)\n", __func__, - ret); - - return ret; -} - -static int ti_sci_clk_enable(struct clk *clk) -{ - struct ti_sci_clk_data *data = dev_get_priv(clk->dev); - const struct ti_sci_handle *sci = data->sci; - const struct ti_sci_clk_ops *cops = &sci->ops.clk_ops; - int ret; - - debug("%s(clk=%p)\n", __func__, clk); - - /* - * Allow the System Controller to automatically manage the state of - * this clock. If the device is enabled, then the clock is enabled. - */ - ret = cops->put_clock(sci, clk->id, clk->data); - if (ret) - dev_err(clk->dev, "%s: put_clock failed (%d)\n", __func__, ret); - - return ret; -} - -static int ti_sci_clk_disable(struct clk *clk) -{ - struct ti_sci_clk_data *data = dev_get_priv(clk->dev); - const struct ti_sci_handle *sci = data->sci; - const struct ti_sci_clk_ops *cops = &sci->ops.clk_ops; - int ret; - - debug("%s(clk=%p)\n", __func__, clk); - - /* Unconditionally disable clock, regardless of state of the device */ - ret = cops->idle_clock(sci, clk->id, clk->data); - if (ret) - dev_err(clk->dev, "%s: idle_clock failed (%d)\n", __func__, - ret); - - return ret; -} - -static const struct udevice_id ti_sci_clk_of_match[] = { - { .compatible = "ti,k2g-sci-clk" }, - { /* sentinel */ }, -}; - -static struct clk_ops ti_sci_clk_ops = { - .of_xlate = ti_sci_clk_of_xlate, - .request = ti_sci_clk_request, - .rfree = ti_sci_clk_free, - .get_rate = ti_sci_clk_get_rate, - .set_rate = ti_sci_clk_set_rate, - .set_parent = ti_sci_clk_set_parent, - .enable = ti_sci_clk_enable, - .disable = ti_sci_clk_disable, -}; - -U_BOOT_DRIVER(ti_sci_clk) = { - .name = "ti-sci-clk", - .id = UCLASS_CLK, - .of_match = ti_sci_clk_of_match, - .probe = ti_sci_clk_probe, - .priv_auto = sizeof(struct ti_sci_clk_data), - .ops = &ti_sci_clk_ops, -}; diff --git a/drivers/clk/ti/Kconfig b/drivers/clk/ti/Kconfig index 9e257a2eb7c..2dc86d44a98 100644 --- a/drivers/clk/ti/Kconfig +++ b/drivers/clk/ti/Kconfig @@ -33,3 +33,11 @@ config CLK_TI_MUX depends on CLK && OF_CONTROL && CLK_CCF help This enables the mux clock driver support on TI's SoCs. + +config CLK_TI_SCI + bool "TI System Control Interface (TI SCI) clock driver" + depends on CLK && TI_SCI_PROTOCOL && OF_CONTROL + help + This enables the clock driver support over TI System Control Interface + available on some new TI's SoCs. If you wish to use clock resources + managed by the TI System Controller, say Y here. Otherwise, say N. diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile index dbd343069c6..9f56b477360 100644 --- a/drivers/clk/ti/Makefile +++ b/drivers/clk/ti/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_CLK_TI_CTRL) += clk-ctrl.o obj-$(CONFIG_CLK_TI_DIVIDER) += clk-divider.o obj-$(CONFIG_CLK_TI_GATE) += clk-gate.o obj-$(CONFIG_CLK_TI_MUX) += clk-mux.o +obj-$(CONFIG_CLK_TI_SCI) += clk-sci.o diff --git a/drivers/clk/ti/clk-sci.c b/drivers/clk/ti/clk-sci.c new file mode 100644 index 00000000000..6f0fdaa111c --- /dev/null +++ b/drivers/clk/ti/clk-sci.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Texas Instruments System Control Interface (TI SCI) clock driver + * + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ + * Andreas Dannenberg + * + * Loosely based on Linux kernel sci-clk.c... + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * struct ti_sci_clk_data - clock controller information structure + * @sci: TI SCI handle used for communication with system controller + */ +struct ti_sci_clk_data { + const struct ti_sci_handle *sci; +}; + +static int ti_sci_clk_probe(struct udevice *dev) +{ + struct ti_sci_clk_data *data = dev_get_priv(dev); + + debug("%s(dev=%p)\n", __func__, dev); + + if (!data) + return -ENOMEM; + + /* Store handle for communication with the system controller */ + data->sci = ti_sci_get_handle(dev); + if (IS_ERR(data->sci)) + return PTR_ERR(data->sci); + + return 0; +} + +static int ti_sci_clk_of_xlate(struct clk *clk, + struct ofnode_phandle_args *args) +{ + debug("%s(clk=%p, args_count=%d)\n", __func__, clk, args->args_count); + + if (args->args_count != 2) { + debug("Invalid args_count: %d\n", args->args_count); + return -EINVAL; + } + + /* + * On TI SCI-based devices, the clock provider id field is used as a + * device ID, and the data field is used as the associated sub-ID. + */ + clk->id = args->args[0]; + clk->data = args->args[1]; + + return 0; +} + +static int ti_sci_clk_request(struct clk *clk) +{ + debug("%s(clk=%p)\n", __func__, clk); + return 0; +} + +static int ti_sci_clk_free(struct clk *clk) +{ + debug("%s(clk=%p)\n", __func__, clk); + return 0; +} + +static ulong ti_sci_clk_get_rate(struct clk *clk) +{ + struct ti_sci_clk_data *data = dev_get_priv(clk->dev); + const struct ti_sci_handle *sci = data->sci; + const struct ti_sci_clk_ops *cops = &sci->ops.clk_ops; + u64 current_freq; + int ret; + + debug("%s(clk=%p)\n", __func__, clk); + + ret = cops->get_freq(sci, clk->id, clk->data, ¤t_freq); + if (ret) { + dev_err(clk->dev, "%s: get_freq failed (%d)\n", __func__, ret); + return ret; + } + + debug("%s(current_freq=%llu)\n", __func__, current_freq); + + return current_freq; +} + +static ulong ti_sci_clk_set_rate(struct clk *clk, ulong rate) +{ + struct ti_sci_clk_data *data = dev_get_priv(clk->dev); + const struct ti_sci_handle *sci = data->sci; + const struct ti_sci_clk_ops *cops = &sci->ops.clk_ops; + int ret; + + debug("%s(clk=%p, rate=%lu)\n", __func__, clk, rate); + +#ifdef CONFIG_K3_AVS0 + k3_avs_notify_freq(clk->id, clk->data, rate); +#endif + + ret = cops->set_freq(sci, clk->id, clk->data, 0, rate, ULONG_MAX); + if (ret) + dev_err(clk->dev, "%s: set_freq failed (%d)\n", __func__, ret); + + return ret; +} + +static int ti_sci_clk_set_parent(struct clk *clk, struct clk *parent) +{ + struct ti_sci_clk_data *data = dev_get_priv(clk->dev); + const struct ti_sci_handle *sci = data->sci; + const struct ti_sci_clk_ops *cops = &sci->ops.clk_ops; + u8 num_parents; + u8 parent_cid; + int ret; + + debug("%s(clk=%p, parent=%p)\n", __func__, clk, parent); + + /* Make sure the clock parent is valid for a given device ID */ + if (clk->id != parent->id) + return -EINVAL; + + /* Make sure clock has parents that can be set */ + ret = cops->get_num_parents(sci, clk->id, clk->data, &num_parents); + if (ret) { + dev_err(clk->dev, "%s: get_num_parents failed (%d)\n", + __func__, ret); + return ret; + } + if (num_parents < 2) { + dev_err(clk->dev, "%s: clock has no settable parents!\n", + __func__); + return -EINVAL; + } + + /* Make sure parent clock ID is valid */ + parent_cid = parent->data - clk->data - 1; + if (parent_cid >= num_parents) { + dev_err(clk->dev, "%s: invalid parent clock!\n", __func__); + return -EINVAL; + } + + /* Ready to proceed to configure the new clock parent */ + ret = cops->set_parent(sci, clk->id, clk->data, parent->data); + if (ret) + dev_err(clk->dev, "%s: set_parent failed (%d)\n", __func__, + ret); + + return ret; +} + +static int ti_sci_clk_enable(struct clk *clk) +{ + struct ti_sci_clk_data *data = dev_get_priv(clk->dev); + const struct ti_sci_handle *sci = data->sci; + const struct ti_sci_clk_ops *cops = &sci->ops.clk_ops; + int ret; + + debug("%s(clk=%p)\n", __func__, clk); + + /* + * Allow the System Controller to automatically manage the state of + * this clock. If the device is enabled, then the clock is enabled. + */ + ret = cops->put_clock(sci, clk->id, clk->data); + if (ret) + dev_err(clk->dev, "%s: put_clock failed (%d)\n", __func__, ret); + + return ret; +} + +static int ti_sci_clk_disable(struct clk *clk) +{ + struct ti_sci_clk_data *data = dev_get_priv(clk->dev); + const struct ti_sci_handle *sci = data->sci; + const struct ti_sci_clk_ops *cops = &sci->ops.clk_ops; + int ret; + + debug("%s(clk=%p)\n", __func__, clk); + + /* Unconditionally disable clock, regardless of state of the device */ + ret = cops->idle_clock(sci, clk->id, clk->data); + if (ret) + dev_err(clk->dev, "%s: idle_clock failed (%d)\n", __func__, + ret); + + return ret; +} + +static const struct udevice_id ti_sci_clk_of_match[] = { + { .compatible = "ti,k2g-sci-clk" }, + { /* sentinel */ }, +}; + +static struct clk_ops ti_sci_clk_ops = { + .of_xlate = ti_sci_clk_of_xlate, + .request = ti_sci_clk_request, + .rfree = ti_sci_clk_free, + .get_rate = ti_sci_clk_get_rate, + .set_rate = ti_sci_clk_set_rate, + .set_parent = ti_sci_clk_set_parent, + .enable = ti_sci_clk_enable, + .disable = ti_sci_clk_disable, +}; + +U_BOOT_DRIVER(ti_sci_clk) = { + .name = "ti-sci-clk", + .id = UCLASS_CLK, + .of_match = ti_sci_clk_of_match, + .probe = ti_sci_clk_probe, + .priv_auto = sizeof(struct ti_sci_clk_data), + .ops = &ti_sci_clk_ops, +}; -- cgit v1.3.1 From d64b9cdcd475eb7f07b49741ded87e24dae4a5fc Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:16:21 +0100 Subject: fdt: translate address if #size-cells = <0> The __of_translate_address routine translates an address from the device tree into a CPU physical address. A note in the description of the routine explains that the crossing of any level with since inherited from IBM. This does not happen for Texas Instruments, or at least for the beaglebone device tree. Without this patch, in fact, the translation into physical addresses of the registers contained in the am33xx-clocks.dtsi nodes would not be possible. They all have a parent with #size-cells = <0>. The CONFIG_OF_TRANSLATE_ZERO_SIZE_CELLS symbol makes translation possible even in the case of crossing levels with #size-cells = <0>. The patch acts conservatively on address translation, except for removing a check within the of_translate_one function in the drivers/core/of_addr.c file: + ranges = of_get_property(parent, rprop, &rlen); - if (ranges == NULL && !of_empty_ranges_quirk(parent)) { - debug("no ranges; cannot translate\n"); - return 1; - } if (ranges == NULL || rlen == 0) { offset = of_read_number(addr, na); memset(addr, 0, pna * 4); debug("empty ranges; 1:1 translation\n"); There are two reasons: 1 The function of_empty_ranges_quirk always returns false, invalidating the following if statement in case of null ranges. Therefore one of the two checks is useless. 2 The implementation of the of_translate_one function found in the common/fdt_support.c file has removed this check while keeping the one about the 1:1 translation. The patch adds a test and modifies a check for the correctness of an address in the case of enabling translation also for zero size cells. The added test checks translations of addresses generated by nodes of a device tree similar to those you can find in the files am33xx.dtsi and am33xx-clocks.dtsi for which the patch was created. The patch was also tested on a beaglebone black board. The addresses generated for the registers of the loaded drivers are those specified by the AM335x reference manual. Signed-off-by: Dario Binacchi Tested-by: Dario Binacchi Reviewed-by: Simon Glass --- arch/sandbox/dts/test.dts | 21 ++++++++++++ common/fdt_support.c | 6 ++-- drivers/core/Kconfig | 12 +++++++ drivers/core/fdtaddr.c | 2 +- drivers/core/of_addr.c | 13 ++------ drivers/core/ofnode.c | 7 ++-- drivers/core/root.c | 3 ++ include/asm-generic/global_data.h | 24 ++++++++++++++ test/dm/test-fdt.c | 68 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 141 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index c9b9b7b75ea..efc440a1f8a 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -42,6 +42,7 @@ fdt-dummy1 = "/translation-test@8000/dev@1,100"; fdt-dummy2 = "/translation-test@8000/dev@2,200"; fdt-dummy3 = "/translation-test@8000/noxlatebus@3,300/dev@42"; + fdt-dummy4 = "/translation-test@8000/xlatebus@4,400/devs/dev@19"; usb0 = &usb_0; usb1 = &usb_1; usb2 = &usb_2; @@ -1082,6 +1083,7 @@ 1 0x100 0x9000 0x1000 2 0x200 0xA000 0x1000 3 0x300 0xB000 0x1000 + 4 0x400 0xC000 0x1000 >; dma-ranges = <0 0x000 0x10000000 0x1000 @@ -1118,6 +1120,25 @@ reg = <0x42>; }; }; + + xlatebus@4,400 { + compatible = "sandbox,zero-size-cells-bus"; + reg = <4 0x400 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 4 0x400 0x1000>; + + devs { + #address-cells = <1>; + #size-cells = <0>; + + dev@19 { + compatible = "denx,u-boot-fdt-dummy"; + reg = <0x19>; + }; + }; + }; + }; osd { diff --git a/common/fdt_support.c b/common/fdt_support.c index 5ae75df3c65..638eca983e8 100644 --- a/common/fdt_support.c +++ b/common/fdt_support.c @@ -20,6 +20,8 @@ #include #include +DECLARE_GLOBAL_DATA_PTR; + /** * fdt_getprop_u32_default_node - Return a node's property or a default * @@ -996,8 +998,8 @@ void fdt_del_node_and_alias(void *blob, const char *alias) /* Max address size we deal with */ #define OF_MAX_ADDR_CELLS 4 #define OF_BAD_ADDR FDT_ADDR_T_NONE -#define OF_CHECK_COUNTS(na, ns) ((na) > 0 && (na) <= OF_MAX_ADDR_CELLS && \ - (ns) > 0) +#define OF_CHECK_COUNTS(na, ns) (((na) > 0 && (na) <= OF_MAX_ADDR_CELLS) && \ + ((ns) > 0 || gd_size_cells_0())) /* Debug utility */ #ifdef DEBUG diff --git a/drivers/core/Kconfig b/drivers/core/Kconfig index 65a503e76d1..dbfe51c6e87 100644 --- a/drivers/core/Kconfig +++ b/drivers/core/Kconfig @@ -247,6 +247,18 @@ config OF_TRANSLATE used for the address translation. This function is faster and smaller in size than fdt_translate_address(). +config OF_TRANSLATE_ZERO_SIZE_CELLS + bool "Enable translation for zero size cells" + depends on OF_TRANSLATE + default n + help + The routine used to translate an FDT address into a physical CPU + address was developed by IBM. It considers that crossing any level + with #size-cells = <0> makes translation impossible, even if it is + not the way it was specified. + Enabling this option makes translation possible even in the case + of crossing levels with #size-cells = <0>. + config SPL_OF_TRANSLATE bool "Translate addresses using fdt_translate_address in SPL" depends on SPL_DM && SPL_OF_CONTROL diff --git a/drivers/core/fdtaddr.c b/drivers/core/fdtaddr.c index 8b48aa5bc5b..ed55f69de12 100644 --- a/drivers/core/fdtaddr.c +++ b/drivers/core/fdtaddr.c @@ -49,7 +49,7 @@ fdt_addr_t devfdt_get_addr_index(const struct udevice *dev, int index) reg += index * (na + ns); - if (ns) { + if (ns || gd_size_cells_0()) { /* * Use the full-fledged translate function for complex * bus setups. diff --git a/drivers/core/of_addr.c b/drivers/core/of_addr.c index ca34d84922b..bbe80136ba8 100644 --- a/drivers/core/of_addr.c +++ b/drivers/core/of_addr.c @@ -18,7 +18,8 @@ /* Max address size we deal with */ #define OF_MAX_ADDR_CELLS 4 #define OF_CHECK_ADDR_COUNT(na) ((na) > 0 && (na) <= OF_MAX_ADDR_CELLS) -#define OF_CHECK_COUNTS(na, ns) (OF_CHECK_ADDR_COUNT(na) && (ns) > 0) +#define OF_CHECK_COUNTS(na, ns) (OF_CHECK_ADDR_COUNT(na) && \ + ((ns) > 0 || gd_size_cells_0())) static struct of_bus *of_match_bus(struct device_node *np); @@ -162,11 +163,6 @@ const __be32 *of_get_address(const struct device_node *dev, int index, } EXPORT_SYMBOL(of_get_address); -static int of_empty_ranges_quirk(const struct device_node *np) -{ - return false; -} - static int of_translate_one(const struct device_node *parent, struct of_bus *bus, struct of_bus *pbus, __be32 *addr, int na, int ns, int pna, @@ -193,11 +189,8 @@ static int of_translate_one(const struct device_node *parent, * As far as we know, this damage only exists on Apple machines, so * This code is only enabled on powerpc. --gcl */ + ranges = of_get_property(parent, rprop, &rlen); - if (ranges == NULL && !of_empty_ranges_quirk(parent)) { - debug("no ranges; cannot translate\n"); - return 1; - } if (ranges == NULL || rlen == 0) { offset = of_read_number(addr, na); memset(addr, 0, pna * 4); diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c index 2a6e43ddc6b..7a5f4c0a73d 100644 --- a/drivers/core/ofnode.c +++ b/drivers/core/ofnode.c @@ -316,7 +316,8 @@ fdt_addr_t ofnode_get_addr_size_index(ofnode node, int index, fdt_size_t *size) ns = of_n_size_cells(ofnode_to_np(node)); - if (IS_ENABLED(CONFIG_OF_TRANSLATE) && ns > 0) { + if (IS_ENABLED(CONFIG_OF_TRANSLATE) && + (ns > 0 || gd_size_cells_0())) { return of_translate_address(ofnode_to_np(node), prop_val); } else { na = of_n_addr_cells(ofnode_to_np(node)); @@ -690,8 +691,10 @@ fdt_addr_t ofnode_get_addr_size(ofnode node, const char *property, ns = of_n_size_cells(np); *sizep = of_read_number(prop + na, ns); - if (CONFIG_IS_ENABLED(OF_TRANSLATE) && ns > 0) + if (CONFIG_IS_ENABLED(OF_TRANSLATE) && + (ns > 0 || gd_size_cells_0())) { return of_translate_address(np, prop); + } else return of_read_number(prop, na); } else { diff --git a/drivers/core/root.c b/drivers/core/root.c index 78de7cdf875..2bfa75b4725 100644 --- a/drivers/core/root.c +++ b/drivers/core/root.c @@ -132,6 +132,9 @@ int dm_init(bool of_live) { int ret; + if (IS_ENABLED(CONFIG_OF_TRANSLATE_ZERO_SIZE_CELLS)) + gd->dm_flags |= GD_DM_FLG_SIZE_CELLS_0; + if (gd->dm_root) { dm_warn("Virtual root driver already exists!\n"); return -EINVAL; diff --git a/include/asm-generic/global_data.h b/include/asm-generic/global_data.h index 31e249177c3..efa09a1943e 100644 --- a/include/asm-generic/global_data.h +++ b/include/asm-generic/global_data.h @@ -183,6 +183,12 @@ struct global_data { struct global_data *new_gd; #ifdef CONFIG_DM + /** + * @dm_flags: additional flags for Driver Model + * + * See &enum gd_dm_flags + */ + unsigned long dm_flags; /** * @dm_root: root instance for Driver Model */ @@ -471,6 +477,12 @@ struct global_data { #define gd_acpi_ctx() NULL #endif +#if CONFIG_IS_ENABLED(DM) +#define gd_size_cells_0() (gd->dm_flags & GD_DM_FLG_SIZE_CELLS_0) +#else +#define gd_size_cells_0() (0) +#endif + /** * enum gd_flags - global data flags * @@ -555,6 +567,18 @@ enum gd_flags { GD_FLG_SMP_READY = 0x40000, }; +/** + * enum gd_dm_flags - global data flags for Driver Model + * + * See field dm_flags of &struct global_data. + */ +enum gd_dm_flags { + /** + * @GD_DM_FLG_SIZE_CELLS_0: Enable #size-cells=<0> translation + */ + GD_DM_FLG_SIZE_CELLS_0 = 0x00001, +}; + #endif /* __ASSEMBLY__ */ #endif /* __ASM_GENERIC_GBL_DATA_H */ diff --git a/test/dm/test-fdt.c b/test/dm/test-fdt.c index b53539055b6..31fb6663a25 100644 --- a/test/dm/test-fdt.c +++ b/test/dm/test-fdt.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -549,6 +550,64 @@ U_BOOT_DRIVER(fdt_dummy_drv) = { .id = UCLASS_TEST_DUMMY, }; +static int zero_size_cells_bus_bind(struct udevice *dev) +{ + ofnode child; + int err; + + ofnode_for_each_subnode(child, dev_ofnode(dev)) { + if (ofnode_get_property(child, "compatible", NULL)) + continue; + + err = device_bind_driver_to_node(dev, + "zero_size_cells_bus_child_drv", + "zero_size_cells_bus_child", + child, NULL); + if (err) { + dev_err(dev, "%s: failed to bind %s\n", __func__, + ofnode_get_name(child)); + return err; + } + } + + return 0; +} + +static const struct udevice_id zero_size_cells_bus_ids[] = { + { .compatible = "sandbox,zero-size-cells-bus" }, + { } +}; + +U_BOOT_DRIVER(zero_size_cells_bus) = { + .name = "zero_size_cells_bus_drv", + .id = UCLASS_TEST_DUMMY, + .of_match = zero_size_cells_bus_ids, + .bind = zero_size_cells_bus_bind, +}; + +static int zero_size_cells_bus_child_bind(struct udevice *dev) +{ + ofnode child; + int err; + + ofnode_for_each_subnode(child, dev_ofnode(dev)) { + err = lists_bind_fdt(dev, child, NULL, false); + if (err) { + dev_err(dev, "%s: lists_bind_fdt, err=%d\n", + __func__, err); + return err; + } + } + + return 0; +} + +U_BOOT_DRIVER(zero_size_cells_bus_child_drv) = { + .name = "zero_size_cells_bus_child_drv", + .id = UCLASS_TEST_DUMMY, + .bind = zero_size_cells_bus_child_bind, +}; + static int dm_test_fdt_translation(struct unit_test_state *uts) { struct udevice *dev; @@ -570,8 +629,17 @@ static int dm_test_fdt_translation(struct unit_test_state *uts) /* No translation for busses with #size-cells == 0 */ ut_assertok(uclass_find_device_by_seq(UCLASS_TEST_DUMMY, 3, &dev)); ut_asserteq_str("dev@42", dev->name); + /* No translation for busses with #size-cells == 0 */ ut_asserteq(0x42, dev_read_addr(dev)); + /* Translation for busses with #size-cells == 0 */ + gd->dm_flags |= GD_DM_FLG_SIZE_CELLS_0; + ut_asserteq(0x8042, dev_read_addr(dev)); + ut_assertok(uclass_find_device_by_seq(UCLASS_TEST_DUMMY, 4, &dev)); + ut_asserteq_str("dev@19", dev->name); + ut_asserteq(0xc019, dev_read_addr(dev)); + gd->dm_flags &= ~GD_DM_FLG_SIZE_CELLS_0; + /* dma address translation */ ut_assertok(uclass_find_device_by_seq(UCLASS_TEST_DUMMY, 0, &dev)); dma_addr[0] = cpu_to_be32(0); -- cgit v1.3.1 From 11326f379329d979bc39d26896190b799103a407 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:16:22 +0100 Subject: omap: timer: fix the rate setting The prescaler (PTV) setting must be taken into account even when the timer input clock frequency has been set. Signed-off-by: Dario Binacchi --- drivers/timer/omap-timer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/timer/omap-timer.c b/drivers/timer/omap-timer.c index 7ac20d78dd8..721e385fd15 100644 --- a/drivers/timer/omap-timer.c +++ b/drivers/timer/omap-timer.c @@ -19,8 +19,6 @@ #define TCLR_PRE_EN BIT(5) /* Pre-scaler enable */ #define TCLR_PTV_SHIFT (2) /* Pre-scaler shift value */ -#define TIMER_CLOCK (V_SCLK / (2 << CONFIG_SYS_PTV)) - struct omap_gptimer_regs { unsigned int tidr; /* offset 0x00 */ unsigned char res1[12]; @@ -61,7 +59,9 @@ static int omap_timer_probe(struct udevice *dev) struct omap_timer_priv *priv = dev_get_priv(dev); if (!uc_priv->clock_rate) - uc_priv->clock_rate = TIMER_CLOCK; + uc_priv->clock_rate = V_SCLK; + + uc_priv->clock_rate /= (2 << CONFIG_SYS_PTV); /* start the counter ticking up, reload value on overflow */ writel(0, &priv->regs->tldr); -- cgit v1.3.1 From ade7f0d00e598387fe67475d54511df4fe25d83f Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:16:24 +0100 Subject: pwm: ti: am33xx: add enhanced pwm driver Enhanced high resolution PWM module (EHRPWM) hardware can be used to generate PWM output over 2 channels. This commit adds PWM driver support for EHRPWM device present on AM33XX SOC. The code is based on the drivers/pwm/pwm-tiehrpwm.c driver of the Linux kernel version 5.9-rc7. For DT binding details see: - Documentation/devicetree/bindings/pwm/pwm-tiehrpwm.txt Signed-off-by: Dario Binacchi --- drivers/pwm/Kconfig | 7 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-ti-ehrpwm.c | 468 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 476 insertions(+) create mode 100644 drivers/pwm/pwm-ti-ehrpwm.c (limited to 'drivers') diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index b3bd5c6bb7a..ccf81abbe93 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -75,3 +75,10 @@ config PWM_SUNXI help This PWM is found on H3, A64 and other Allwinner SoCs. It supports a programmable period and duty cycle. A 16-bit counter is used. + +config PWM_TI_EHRPWM + bool "Enable support for EHRPWM PWM" + depends on DM_PWM && ARCH_OMAP2PLUS + default y + help + PWM driver support for the EHRPWM controller found on TI SOCs. diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index f21ae7d76ee..0b9d2698a31 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -19,3 +19,4 @@ obj-$(CONFIG_PWM_SANDBOX) += sandbox_pwm.o obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o obj-$(CONFIG_PWM_TEGRA) += tegra_pwm.o obj-$(CONFIG_PWM_SUNXI) += sunxi_pwm.o +obj-$(CONFIG_PWM_TI_EHRPWM) += pwm-ti-ehrpwm.o diff --git a/drivers/pwm/pwm-ti-ehrpwm.c b/drivers/pwm/pwm-ti-ehrpwm.c new file mode 100644 index 00000000000..ac3d731d228 --- /dev/null +++ b/drivers/pwm/pwm-ti-ehrpwm.c @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EHRPWM PWM driver + * + * Copyright (C) 2020 Dario Binacchi + * + * Based on Linux kernel drivers/pwm/pwm-tiehrpwm.c + */ + +#include +#include +#include +#include +#include +#include +#include + +#define NSEC_PER_SEC 1000000000L + +/* Time base module registers */ +#define TI_EHRPWM_TBCTL 0x00 +#define TI_EHRPWM_TBPRD 0x0A + +#define TI_EHRPWM_TBCTL_PRDLD_MASK BIT(3) +#define TI_EHRPWM_TBCTL_PRDLD_SHDW 0 +#define TI_EHRPWM_TBCTL_PRDLD_IMDT BIT(3) +#define TI_EHRPWM_TBCTL_CLKDIV_MASK GENMASK(12, 7) +#define TI_EHRPWM_TBCTL_CTRMODE_MASK GENMASK(1, 0) +#define TI_EHRPWM_TBCTL_CTRMODE_UP 0 +#define TI_EHRPWM_TBCTL_CTRMODE_DOWN BIT(0) +#define TI_EHRPWM_TBCTL_CTRMODE_UPDOWN BIT(1) +#define TI_EHRPWM_TBCTL_CTRMODE_FREEZE GENMASK(1, 0) + +#define TI_EHRPWM_TBCTL_HSPCLKDIV_SHIFT 7 +#define TI_EHRPWM_TBCTL_CLKDIV_SHIFT 10 + +#define TI_EHRPWM_CLKDIV_MAX 7 +#define TI_EHRPWM_HSPCLKDIV_MAX 7 +#define TI_EHRPWM_PERIOD_MAX 0xFFFF + +/* Counter compare module registers */ +#define TI_EHRPWM_CMPA 0x12 +#define TI_EHRPWM_CMPB 0x14 + +/* Action qualifier module registers */ +#define TI_EHRPWM_AQCTLA 0x16 +#define TI_EHRPWM_AQCTLB 0x18 +#define TI_EHRPWM_AQSFRC 0x1A +#define TI_EHRPWM_AQCSFRC 0x1C + +#define TI_EHRPWM_AQCTL_CBU_MASK GENMASK(9, 8) +#define TI_EHRPWM_AQCTL_CBU_FRCLOW BIT(8) +#define TI_EHRPWM_AQCTL_CBU_FRCHIGH BIT(9) +#define TI_EHRPWM_AQCTL_CBU_FRCTOGGLE GENMASK(9, 8) +#define TI_EHRPWM_AQCTL_CAU_MASK GENMASK(5, 4) +#define TI_EHRPWM_AQCTL_CAU_FRCLOW BIT(4) +#define TI_EHRPWM_AQCTL_CAU_FRCHIGH BIT(5) +#define TI_EHRPWM_AQCTL_CAU_FRCTOGGLE GENMASK(5, 4) +#define TI_EHRPWM_AQCTL_PRD_MASK GENMASK(3, 2) +#define TI_EHRPWM_AQCTL_PRD_FRCLOW BIT(2) +#define TI_EHRPWM_AQCTL_PRD_FRCHIGH BIT(3) +#define TI_EHRPWM_AQCTL_PRD_FRCTOGGLE GENMASK(3, 2) +#define TI_EHRPWM_AQCTL_ZRO_MASK GENMASK(1, 0) +#define TI_EHRPWM_AQCTL_ZRO_FRCLOW BIT(0) +#define TI_EHRPWM_AQCTL_ZRO_FRCHIGH BIT(1) +#define TI_EHRPWM_AQCTL_ZRO_FRCTOGGLE GENMASK(1, 0) + +#define TI_EHRPWM_AQCTL_CHANA_POLNORMAL (TI_EHRPWM_AQCTL_CAU_FRCLOW | \ + TI_EHRPWM_AQCTL_PRD_FRCHIGH | \ + TI_EHRPWM_AQCTL_ZRO_FRCHIGH) +#define TI_EHRPWM_AQCTL_CHANA_POLINVERSED (TI_EHRPWM_AQCTL_CAU_FRCHIGH | \ + TI_EHRPWM_AQCTL_PRD_FRCLOW | \ + TI_EHRPWM_AQCTL_ZRO_FRCLOW) +#define TI_EHRPWM_AQCTL_CHANB_POLNORMAL (TI_EHRPWM_AQCTL_CBU_FRCLOW | \ + TI_EHRPWM_AQCTL_PRD_FRCHIGH | \ + TI_EHRPWM_AQCTL_ZRO_FRCHIGH) +#define TI_EHRPWM_AQCTL_CHANB_POLINVERSED (TI_EHRPWM_AQCTL_CBU_FRCHIGH | \ + TI_EHRPWM_AQCTL_PRD_FRCLOW | \ + TI_EHRPWM_AQCTL_ZRO_FRCLOW) + +#define TI_EHRPWM_AQSFRC_RLDCSF_MASK GENMASK(7, 6) +#define TI_EHRPWM_AQSFRC_RLDCSF_ZRO 0 +#define TI_EHRPWM_AQSFRC_RLDCSF_PRD BIT(6) +#define TI_EHRPWM_AQSFRC_RLDCSF_ZROPRD BIT(7) +#define TI_EHRPWM_AQSFRC_RLDCSF_IMDT GENMASK(7, 6) + +#define TI_EHRPWM_AQCSFRC_CSFB_MASK GENMASK(3, 2) +#define TI_EHRPWM_AQCSFRC_CSFB_FRCDIS 0 +#define TI_EHRPWM_AQCSFRC_CSFB_FRCLOW BIT(2) +#define TI_EHRPWM_AQCSFRC_CSFB_FRCHIGH BIT(3) +#define TI_EHRPWM_AQCSFRC_CSFB_DISSWFRC GENMASK(3, 2) +#define TI_EHRPWM_AQCSFRC_CSFA_MASK GENMASK(1, 0) +#define TI_EHRPWM_AQCSFRC_CSFA_FRCDIS 0 +#define TI_EHRPWM_AQCSFRC_CSFA_FRCLOW BIT(0) +#define TI_EHRPWM_AQCSFRC_CSFA_FRCHIGH BIT(1) +#define TI_EHRPWM_AQCSFRC_CSFA_DISSWFRC GENMASK(1, 0) + +#define TI_EHRPWM_NUM_CHANNELS 2 + +struct ti_ehrpwm_priv { + fdt_addr_t regs; + u32 clk_rate; + struct clk tbclk; + unsigned long period_cycles[TI_EHRPWM_NUM_CHANNELS]; + bool polarity_reversed[TI_EHRPWM_NUM_CHANNELS]; +}; + +static void ti_ehrpwm_modify(u16 val, u16 mask, fdt_addr_t reg) +{ + unsigned short v; + + v = readw(reg); + v &= ~mask; + v |= val & mask; + writew(v, reg); +} + +static int ti_ehrpwm_set_invert(struct udevice *dev, uint channel, + bool polarity) +{ + struct ti_ehrpwm_priv *priv = dev_get_priv(dev); + + if (channel >= TI_EHRPWM_NUM_CHANNELS) + return -ENOSPC; + + /* Configuration of polarity in hardware delayed, do at enable */ + priv->polarity_reversed[channel] = polarity; + return 0; +} + +/** + * set_prescale_div - Set up the prescaler divider function + * @rqst_prescaler: prescaler value min + * @prescale_div: prescaler value set + * @tb_clk_div: Time Base Control prescaler bits + */ +static int set_prescale_div(unsigned long rqst_prescaler, u16 *prescale_div, + u16 *tb_clk_div) +{ + unsigned int clkdiv, hspclkdiv; + + for (clkdiv = 0; clkdiv <= TI_EHRPWM_CLKDIV_MAX; clkdiv++) { + for (hspclkdiv = 0; hspclkdiv <= TI_EHRPWM_HSPCLKDIV_MAX; + hspclkdiv++) { + /* + * calculations for prescaler value : + * prescale_div = HSPCLKDIVIDER * CLKDIVIDER. + * HSPCLKDIVIDER = 2 ** hspclkdiv + * CLKDIVIDER = (1), if clkdiv == 0 *OR* + * (2 * clkdiv), if clkdiv != 0 + * + * Configure prescale_div value such that period + * register value is less than 65535. + */ + + *prescale_div = (1 << clkdiv) * + (hspclkdiv ? (hspclkdiv * 2) : 1); + if (*prescale_div > rqst_prescaler) { + *tb_clk_div = + (clkdiv << TI_EHRPWM_TBCTL_CLKDIV_SHIFT) | + (hspclkdiv << + TI_EHRPWM_TBCTL_HSPCLKDIV_SHIFT); + return 0; + } + } + } + + return 1; +} + +static void ti_ehrpwm_configure_polarity(struct udevice *dev, uint channel) +{ + struct ti_ehrpwm_priv *priv = dev_get_priv(dev); + u16 aqctl_val, aqctl_mask; + unsigned int aqctl_reg; + + /* + * Configure PWM output to HIGH/LOW level on counter + * reaches compare register value and LOW/HIGH level + * on counter value reaches period register value and + * zero value on counter + */ + if (channel == 1) { + aqctl_reg = TI_EHRPWM_AQCTLB; + aqctl_mask = TI_EHRPWM_AQCTL_CBU_MASK; + + if (priv->polarity_reversed[channel]) + aqctl_val = TI_EHRPWM_AQCTL_CHANB_POLINVERSED; + else + aqctl_val = TI_EHRPWM_AQCTL_CHANB_POLNORMAL; + } else { + aqctl_reg = TI_EHRPWM_AQCTLA; + aqctl_mask = TI_EHRPWM_AQCTL_CAU_MASK; + + if (priv->polarity_reversed[channel]) + aqctl_val = TI_EHRPWM_AQCTL_CHANA_POLINVERSED; + else + aqctl_val = TI_EHRPWM_AQCTL_CHANA_POLNORMAL; + } + + aqctl_mask |= TI_EHRPWM_AQCTL_PRD_MASK | TI_EHRPWM_AQCTL_ZRO_MASK; + ti_ehrpwm_modify(aqctl_val, aqctl_mask, priv->regs + aqctl_reg); +} + +/* + * period_ns = 10^9 * (ps_divval * period_cycles) / PWM_CLK_RATE + * duty_ns = 10^9 * (ps_divval * duty_cycles) / PWM_CLK_RATE + */ +static int ti_ehrpwm_set_config(struct udevice *dev, uint channel, + uint period_ns, uint duty_ns) +{ + struct ti_ehrpwm_priv *priv = dev_get_priv(dev); + u32 period_cycles, duty_cycles; + u16 ps_divval, tb_divval; + unsigned int i, cmp_reg; + unsigned long long c; + + if (channel >= TI_EHRPWM_NUM_CHANNELS) + return -ENOSPC; + + if (period_ns > NSEC_PER_SEC) + return -ERANGE; + + c = priv->clk_rate; + c = c * period_ns; + do_div(c, NSEC_PER_SEC); + period_cycles = (unsigned long)c; + + if (period_cycles < 1) { + period_cycles = 1; + duty_cycles = 1; + } else { + c = priv->clk_rate; + c = c * duty_ns; + do_div(c, NSEC_PER_SEC); + duty_cycles = (unsigned long)c; + } + + dev_dbg(dev, "channel=%d, period_ns=%d, duty_ns=%d\n", + channel, period_ns, duty_ns); + + /* + * Period values should be same for multiple PWM channels as IP uses + * same period register for multiple channels. + */ + for (i = 0; i < TI_EHRPWM_NUM_CHANNELS; i++) { + if (priv->period_cycles[i] && + priv->period_cycles[i] != period_cycles) { + /* + * Allow channel to reconfigure period if no other + * channels being configured. + */ + if (i == channel) + continue; + + dev_err(dev, "period value conflicts with channel %u\n", + i); + return -EINVAL; + } + } + + priv->period_cycles[channel] = period_cycles; + + /* Configure clock prescaler to support Low frequency PWM wave */ + if (set_prescale_div(period_cycles / TI_EHRPWM_PERIOD_MAX, &ps_divval, + &tb_divval)) { + dev_err(dev, "unsupported values\n"); + return -EINVAL; + } + + /* Update clock prescaler values */ + ti_ehrpwm_modify(tb_divval, TI_EHRPWM_TBCTL_CLKDIV_MASK, + priv->regs + TI_EHRPWM_TBCTL); + + /* Update period & duty cycle with presacler division */ + period_cycles = period_cycles / ps_divval; + duty_cycles = duty_cycles / ps_divval; + + /* Configure shadow loading on Period register */ + ti_ehrpwm_modify(TI_EHRPWM_TBCTL_PRDLD_SHDW, TI_EHRPWM_TBCTL_PRDLD_MASK, + priv->regs + TI_EHRPWM_TBCTL); + + writew(period_cycles, priv->regs + TI_EHRPWM_TBPRD); + + /* Configure ehrpwm counter for up-count mode */ + ti_ehrpwm_modify(TI_EHRPWM_TBCTL_CTRMODE_UP, + TI_EHRPWM_TBCTL_CTRMODE_MASK, + priv->regs + TI_EHRPWM_TBCTL); + + if (channel == 1) + /* Channel 1 configured with compare B register */ + cmp_reg = TI_EHRPWM_CMPB; + else + /* Channel 0 configured with compare A register */ + cmp_reg = TI_EHRPWM_CMPA; + + writew(duty_cycles, priv->regs + cmp_reg); + return 0; +} + +static int ti_ehrpwm_disable(struct udevice *dev, uint channel) +{ + struct ti_ehrpwm_priv *priv = dev_get_priv(dev); + u16 aqcsfrc_val, aqcsfrc_mask; + int err; + + if (channel >= TI_EHRPWM_NUM_CHANNELS) + return -ENOSPC; + + /* Action Qualifier puts PWM output low forcefully */ + if (channel) { + aqcsfrc_val = TI_EHRPWM_AQCSFRC_CSFB_FRCLOW; + aqcsfrc_mask = TI_EHRPWM_AQCSFRC_CSFB_MASK; + } else { + aqcsfrc_val = TI_EHRPWM_AQCSFRC_CSFA_FRCLOW; + aqcsfrc_mask = TI_EHRPWM_AQCSFRC_CSFA_MASK; + } + + /* Update shadow register first before modifying active register */ + ti_ehrpwm_modify(TI_EHRPWM_AQSFRC_RLDCSF_ZRO, + TI_EHRPWM_AQSFRC_RLDCSF_MASK, + priv->regs + TI_EHRPWM_AQSFRC); + + ti_ehrpwm_modify(aqcsfrc_val, aqcsfrc_mask, + priv->regs + TI_EHRPWM_AQCSFRC); + + /* + * Changes to immediate action on Action Qualifier. This puts + * Action Qualifier control on PWM output from next TBCLK + */ + ti_ehrpwm_modify(TI_EHRPWM_AQSFRC_RLDCSF_IMDT, + TI_EHRPWM_AQSFRC_RLDCSF_MASK, + priv->regs + TI_EHRPWM_AQSFRC); + + ti_ehrpwm_modify(aqcsfrc_val, aqcsfrc_mask, + priv->regs + TI_EHRPWM_AQCSFRC); + + /* Disabling TBCLK on PWM disable */ + err = clk_disable(&priv->tbclk); + if (err) { + dev_err(dev, "failed to disable tbclk\n"); + return err; + } + + return 0; +} + +static int ti_ehrpwm_enable(struct udevice *dev, uint channel) +{ + struct ti_ehrpwm_priv *priv = dev_get_priv(dev); + u16 aqcsfrc_val, aqcsfrc_mask; + int err; + + if (channel >= TI_EHRPWM_NUM_CHANNELS) + return -ENOSPC; + + /* Disabling Action Qualifier on PWM output */ + if (channel) { + aqcsfrc_val = TI_EHRPWM_AQCSFRC_CSFB_FRCDIS; + aqcsfrc_mask = TI_EHRPWM_AQCSFRC_CSFB_MASK; + } else { + aqcsfrc_val = TI_EHRPWM_AQCSFRC_CSFA_FRCDIS; + aqcsfrc_mask = TI_EHRPWM_AQCSFRC_CSFA_MASK; + } + + /* Changes to shadow mode */ + ti_ehrpwm_modify(TI_EHRPWM_AQSFRC_RLDCSF_ZRO, + TI_EHRPWM_AQSFRC_RLDCSF_MASK, + priv->regs + TI_EHRPWM_AQSFRC); + + ti_ehrpwm_modify(aqcsfrc_val, aqcsfrc_mask, + priv->regs + TI_EHRPWM_AQCSFRC); + + /* Channels polarity can be configured from action qualifier module */ + ti_ehrpwm_configure_polarity(dev, channel); + + err = clk_enable(&priv->tbclk); + if (err) { + dev_err(dev, "failed to enable tbclk\n"); + return err; + } + + return 0; +} + +static int ti_ehrpwm_set_enable(struct udevice *dev, uint channel, bool enable) +{ + if (enable) + return ti_ehrpwm_enable(dev, channel); + + return ti_ehrpwm_disable(dev, channel); +} + +static int ti_ehrpwm_of_to_plat(struct udevice *dev) +{ + struct ti_ehrpwm_priv *priv = dev_get_priv(dev); + + priv->regs = dev_read_addr(dev); + if (priv->regs == FDT_ADDR_T_NONE) { + dev_err(dev, "invalid address\n"); + return -EINVAL; + } + + dev_dbg(dev, "regs=0x%08lx\n", priv->regs); + return 0; +} + +static int ti_ehrpwm_remove(struct udevice *dev) +{ + struct ti_ehrpwm_priv *priv = dev_get_priv(dev); + + clk_release_all(&priv->tbclk, 1); + return 0; +} + +static int ti_ehrpwm_probe(struct udevice *dev) +{ + struct ti_ehrpwm_priv *priv = dev_get_priv(dev); + struct clk clk; + int err; + + err = clk_get_by_name(dev, "fck", &clk); + if (err) { + dev_err(dev, "failed to get clock\n"); + return err; + } + + priv->clk_rate = clk_get_rate(&clk); + if (IS_ERR_VALUE(priv->clk_rate) || !priv->clk_rate) { + dev_err(dev, "failed to get clock rate\n"); + if (IS_ERR_VALUE(priv->clk_rate)) + return priv->clk_rate; + + return -EINVAL; + } + + /* Acquire tbclk for Time Base EHRPWM submodule */ + err = clk_get_by_name(dev, "tbclk", &priv->tbclk); + if (err) { + dev_err(dev, "failed to get tbclk clock\n"); + return err; + } + + return 0; +} + +static const struct pwm_ops ti_ehrpwm_ops = { + .set_config = ti_ehrpwm_set_config, + .set_enable = ti_ehrpwm_set_enable, + .set_invert = ti_ehrpwm_set_invert, +}; + +static const struct udevice_id ti_ehrpwm_ids[] = { + {.compatible = "ti,am3352-ehrpwm"}, + {.compatible = "ti,am33xx-ehrpwm"}, + {} +}; + +U_BOOT_DRIVER(ti_ehrpwm) = { + .name = "ti_ehrpwm", + .id = UCLASS_PWM, + .of_match = ti_ehrpwm_ids, + .ops = &ti_ehrpwm_ops, + .ofdata_to_platdata = ti_ehrpwm_of_to_plat, + .probe = ti_ehrpwm_probe, + .remove = ti_ehrpwm_remove, + .priv_auto = sizeof(struct ti_ehrpwm_priv), +}; -- cgit v1.3.1 From 0f4effb05b275f36c490673313317cfe897c9b79 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:16:25 +0100 Subject: bus: ti: am33xx: add pwm subsystem driver The TI PWMSS driver is a simple bus driver for providing clock and power management for the PWM peripherals on TI AM33xx SoCs, namely eCAP, eHRPWM and eQEP. For DT binding details see Linux doc: - Documentation/devicetree/bindings/pwm/pwm-tipwmss.txt Signed-off-by: Dario Binacchi --- drivers/bus/Kconfig | 6 ++++++ drivers/bus/Makefile | 1 + drivers/bus/ti-pwmss.c | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 drivers/bus/ti-pwmss.c (limited to 'drivers') diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 733bec5a56c..d742ed333b1 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -5,6 +5,12 @@ menu "Bus devices" +config TI_PWMSS + bool + default y if ARCH_OMAP2PLUS && PWM_TI_EHRPWM + help + PWM Subsystem driver support for AM33xx SOC. + config TI_SYSC bool "TI sysc interconnect target module driver" depends on ARCH_OMAP2PLUS diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index 875bb4ed424..a2e71c7b3b5 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -3,5 +3,6 @@ # Makefile for the bus drivers. # +obj-$(CONFIG_TI_PWMSS) += ti-pwmss.o obj-$(CONFIG_TI_SYSC) += ti-sysc.o obj-$(CONFIG_UNIPHIER_SYSTEM_BUS) += uniphier-system-bus.o diff --git a/drivers/bus/ti-pwmss.c b/drivers/bus/ti-pwmss.c new file mode 100644 index 00000000000..265b4cf83b5 --- /dev/null +++ b/drivers/bus/ti-pwmss.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Pulse-Width Modulation Subsystem (pwmss) + * + * Copyright (C) 2020 Dario Binacchi + */ + +#include +#include + +static const struct udevice_id ti_pwmss_ids[] = { + {.compatible = "ti,am33xx-pwmss"}, + {} +}; + +U_BOOT_DRIVER(ti_pwmss) = { + .name = "ti_pwmss", + .id = UCLASS_SIMPLE_BUS, + .of_match = ti_pwmss_ids, + .bind = dm_scan_fdt_dev, +}; -- cgit v1.3.1 From 15daa4860bf3c49f53ae76812e0033e4d5faa0a2 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:16:26 +0100 Subject: dm: core: add a function to decode display timings The patch adds a function to get display timings from the device tree node attached to the device. Signed-off-by: Dario Binacchi Reviewed-by: Simon Glass --- arch/sandbox/dts/test.dts | 46 +++++++++++++++++++++++++++ drivers/core/read.c | 6 ++++ include/dm/read.h | 24 ++++++++++++++ test/dm/test-fdt.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+) (limited to 'drivers') diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index efc440a1f8a..f86cd0d3b27 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -142,6 +142,52 @@ <&muxcontroller1>; mux-control-names = "mux0", "mux1", "mux2", "mux3", "mux4"; mux-syscon = <&syscon3>; + display-timings { + timing0: 240x320 { + clock-frequency = <6500000>; + hactive = <240>; + vactive = <320>; + hfront-porch = <6>; + hback-porch = <7>; + hsync-len = <1>; + vback-porch = <5>; + vfront-porch = <8>; + vsync-len = <2>; + hsync-active = <1>; + vsync-active = <0>; + de-active = <1>; + pixelclk-active = <1>; + interlaced; + doublescan; + doubleclk; + }; + timing1: 480x800 { + clock-frequency = <9000000>; + hactive = <480>; + vactive = <800>; + hfront-porch = <10>; + hback-porch = <59>; + hsync-len = <12>; + vback-porch = <15>; + vfront-porch = <17>; + vsync-len = <16>; + hsync-active = <0>; + vsync-active = <1>; + de-active = <0>; + pixelclk-active = <0>; + }; + timing2: 800x480 { + clock-frequency = <33500000>; + hactive = <800>; + vactive = <480>; + hback-porch = <89>; + hfront-porch = <164>; + vback-porch = <23>; + vfront-porch = <10>; + hsync-len = <11>; + vsync-len = <13>; + }; + }; }; junk { diff --git a/drivers/core/read.c b/drivers/core/read.c index fc74d64814f..4d9b5dd0384 100644 --- a/drivers/core/read.c +++ b/drivers/core/read.c @@ -379,3 +379,9 @@ int dev_read_pci_bus_range(const struct udevice *dev, return 0; } + +int dev_decode_display_timing(const struct udevice *dev, int index, + struct display_timing *config) +{ + return ofnode_decode_display_timing(dev_ofnode(dev), index, config); +} diff --git a/include/dm/read.h b/include/dm/read.h index fc987f77598..c875e11a132 100644 --- a/include/dm/read.h +++ b/include/dm/read.h @@ -678,6 +678,23 @@ int dev_get_child_count(const struct udevice *dev); */ int dev_read_pci_bus_range(const struct udevice *dev, struct resource *res); +/** + * dev_decode_display_timing() - decode display timings + * + * Decode display timings from the supplied 'display-timings' node. + * See doc/device-tree-bindings/video/display-timing.txt for binding + * information. + * + * @dev: device to read DT display timings from. The node linked to the device + * contains a child node called 'display-timings' which in turn contains + * one or more display timing nodes. + * @index: index number to read (0=first timing subnode) + * @config: place to put timings + * @return 0 if OK, -FDT_ERR_NOTFOUND if not found + */ +int dev_decode_display_timing(const struct udevice *dev, int index, + struct display_timing *config); + #else /* CONFIG_DM_DEV_READ_INLINE is enabled */ static inline int dev_read_u32(const struct udevice *dev, @@ -1000,6 +1017,13 @@ static inline int dev_get_child_count(const struct udevice *dev) return ofnode_get_child_count(dev_ofnode(dev)); } +static inline int dev_decode_display_timing(const struct udevice *dev, + int index, + struct display_timing *config) +{ + return ofnode_decode_display_timing(dev_ofnode(dev), index, config); +} + #endif /* CONFIG_DM_DEV_READ_INLINE */ /** diff --git a/test/dm/test-fdt.c b/test/dm/test-fdt.c index 31fb6663a25..b5ac9bba24e 100644 --- a/test/dm/test-fdt.c +++ b/test/dm/test-fdt.c @@ -1152,3 +1152,83 @@ static int dm_test_ofdata_order(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_ofdata_order, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT); + +/* Test dev_decode_display_timing() */ +static int dm_test_decode_display_timing(struct unit_test_state *uts) +{ + struct udevice *dev; + struct display_timing timing; + + ut_assertok(uclass_first_device_err(UCLASS_TEST_FDT, &dev)); + ut_asserteq_str("a-test", dev->name); + + ut_assertok(dev_decode_display_timing(dev, 0, &timing)); + ut_assert(timing.hactive.typ == 240); + ut_assert(timing.hback_porch.typ == 7); + ut_assert(timing.hfront_porch.typ == 6); + ut_assert(timing.hsync_len.typ == 1); + ut_assert(timing.vactive.typ == 320); + ut_assert(timing.vback_porch.typ == 5); + ut_assert(timing.vfront_porch.typ == 8); + ut_assert(timing.vsync_len.typ == 2); + ut_assert(timing.pixelclock.typ == 6500000); + ut_assert(timing.flags & DISPLAY_FLAGS_HSYNC_HIGH); + ut_assert(!(timing.flags & DISPLAY_FLAGS_HSYNC_LOW)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_VSYNC_HIGH)); + ut_assert(timing.flags & DISPLAY_FLAGS_VSYNC_LOW); + ut_assert(timing.flags & DISPLAY_FLAGS_DE_HIGH); + ut_assert(!(timing.flags & DISPLAY_FLAGS_DE_LOW)); + ut_assert(timing.flags & DISPLAY_FLAGS_PIXDATA_POSEDGE); + ut_assert(!(timing.flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)); + ut_assert(timing.flags & DISPLAY_FLAGS_INTERLACED); + ut_assert(timing.flags & DISPLAY_FLAGS_DOUBLESCAN); + ut_assert(timing.flags & DISPLAY_FLAGS_DOUBLECLK); + + ut_assertok(dev_decode_display_timing(dev, 1, &timing)); + ut_assert(timing.hactive.typ == 480); + ut_assert(timing.hback_porch.typ == 59); + ut_assert(timing.hfront_porch.typ == 10); + ut_assert(timing.hsync_len.typ == 12); + ut_assert(timing.vactive.typ == 800); + ut_assert(timing.vback_porch.typ == 15); + ut_assert(timing.vfront_porch.typ == 17); + ut_assert(timing.vsync_len.typ == 16); + ut_assert(timing.pixelclock.typ == 9000000); + ut_assert(!(timing.flags & DISPLAY_FLAGS_HSYNC_HIGH)); + ut_assert(timing.flags & DISPLAY_FLAGS_HSYNC_LOW); + ut_assert(timing.flags & DISPLAY_FLAGS_VSYNC_HIGH); + ut_assert(!(timing.flags & DISPLAY_FLAGS_VSYNC_LOW)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_DE_HIGH)); + ut_assert(timing.flags & DISPLAY_FLAGS_DE_LOW); + ut_assert(!(timing.flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)); + ut_assert(timing.flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE); + ut_assert(!(timing.flags & DISPLAY_FLAGS_INTERLACED)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_DOUBLESCAN)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_DOUBLECLK)); + + ut_assertok(dev_decode_display_timing(dev, 2, &timing)); + ut_assert(timing.hactive.typ == 800); + ut_assert(timing.hback_porch.typ == 89); + ut_assert(timing.hfront_porch.typ == 164); + ut_assert(timing.hsync_len.typ == 11); + ut_assert(timing.vactive.typ == 480); + ut_assert(timing.vback_porch.typ == 23); + ut_assert(timing.vfront_porch.typ == 10); + ut_assert(timing.vsync_len.typ == 13); + ut_assert(timing.pixelclock.typ == 33500000); + ut_assert(!(timing.flags & DISPLAY_FLAGS_HSYNC_HIGH)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_HSYNC_LOW)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_VSYNC_HIGH)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_VSYNC_LOW)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_DE_HIGH)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_DE_LOW)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_INTERLACED)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_DOUBLESCAN)); + ut_assert(!(timing.flags & DISPLAY_FLAGS_DOUBLECLK)); + + ut_assert(dev_decode_display_timing(dev, 3, &timing)); + return 0; +} +DM_TEST(dm_test_decode_display_timing, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT); -- cgit v1.3.1 From ff94c15a3cf1ded3b851c65bb86168f6428e0d06 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:16:27 +0100 Subject: video: omap: add panel driver The previous version of am335x-fb.c contained the functionalities of two drivers that this patch has split. It was a video type driver that used the same registration compatible string that now registers a panel type driver. The proof of this is that two compatible strings were referred to within the same driver. There are now two drivers, each with its own compatible string, functions and API. Furthermore, the panel driver, in addition to decoding the display timings, is now also able to manage the backlight. Signed-off-by: Dario Binacchi Reviewed-by: Simon Glass --- arch/arm/dts/am335x-brppt1-mmc.dts | 17 ++- arch/arm/dts/am335x-brppt1-nand.dts | 17 ++- arch/arm/dts/am335x-brppt1-spi.dts | 17 ++- arch/arm/dts/am335x-brsmarc1.dts | 20 ++- arch/arm/dts/am335x-brxre1.dts | 21 ++- arch/arm/dts/am335x-evm-u-boot.dtsi | 15 +- arch/arm/dts/am335x-evmsk-u-boot.dtsi | 14 +- arch/arm/dts/am335x-guardian-u-boot.dtsi | 18 ++- arch/arm/dts/am335x-pdu001-u-boot.dtsi | 18 ++- arch/arm/dts/am335x-pxm50-u-boot.dtsi | 14 +- arch/arm/dts/am335x-rut-u-boot.dtsi | 14 +- drivers/video/Makefile | 1 + drivers/video/am335x-fb.c | 255 +++++++++++++------------------ drivers/video/am335x-fb.h | 31 ++++ drivers/video/tilcdc-panel.c | 172 +++++++++++++++++++++ drivers/video/tilcdc-panel.h | 14 ++ 16 files changed, 464 insertions(+), 194 deletions(-) create mode 100644 drivers/video/tilcdc-panel.c create mode 100644 drivers/video/tilcdc-panel.h (limited to 'drivers') diff --git a/arch/arm/dts/am335x-brppt1-mmc.dts b/arch/arm/dts/am335x-brppt1-mmc.dts index 6f919711f0a..bd2f6c2e3e1 100644 --- a/arch/arm/dts/am335x-brppt1-mmc.dts +++ b/arch/arm/dts/am335x-brppt1-mmc.dts @@ -53,8 +53,6 @@ bkl-pwm = <&pwmbacklight>; bkl-tps = <&tps_bl>; - u-boot,dm-pre-reloc; - panel-info { ac-bias = <255>; ac-bias-intrpt = <0>; @@ -238,8 +236,19 @@ status = "okay"; }; -&lcdc { - status = "disabled"; +&l4_per { + + segment@300000 { + + target-module@e000 { + u-boot,dm-pre-reloc; + + lcdc: lcdc@0 { + u-boot,dm-pre-reloc; + status = "disabled"; + }; + }; + }; }; &elm { diff --git a/arch/arm/dts/am335x-brppt1-nand.dts b/arch/arm/dts/am335x-brppt1-nand.dts index 9d4340f5914..67c609739fc 100644 --- a/arch/arm/dts/am335x-brppt1-nand.dts +++ b/arch/arm/dts/am335x-brppt1-nand.dts @@ -53,8 +53,6 @@ bkl-pwm = <&pwmbacklight>; bkl-tps = <&tps_bl>; - u-boot,dm-pre-reloc; - panel-info { ac-bias = <255>; ac-bias-intrpt = <0>; @@ -228,8 +226,19 @@ status = "disabled"; }; -&lcdc { - status = "disabled"; +&l4_per { + + segment@300000 { + + target-module@e000 { + u-boot,dm-pre-reloc; + + lcdc: lcdc@0 { + u-boot,dm-pre-reloc; + status = "disabled"; + }; + }; + }; }; &elm { diff --git a/arch/arm/dts/am335x-brppt1-spi.dts b/arch/arm/dts/am335x-brppt1-spi.dts index c078af8fbae..ce3dce204d6 100644 --- a/arch/arm/dts/am335x-brppt1-spi.dts +++ b/arch/arm/dts/am335x-brppt1-spi.dts @@ -54,8 +54,6 @@ bkl-pwm = <&pwmbacklight>; bkl-tps = <&tps_bl>; - u-boot,dm-pre-reloc; - panel-info { ac-bias = <255>; ac-bias-intrpt = <0>; @@ -259,8 +257,19 @@ status = "okay"; }; -&lcdc { - status = "disabled"; +&l4_per { + + segment@300000 { + + target-module@e000 { + u-boot,dm-pre-reloc; + + lcdc: lcdc@0 { + u-boot,dm-pre-reloc; + status = "disabled"; + }; + }; + }; }; &elm { diff --git a/arch/arm/dts/am335x-brsmarc1.dts b/arch/arm/dts/am335x-brsmarc1.dts index 7e9516e8f88..25cdb111648 100644 --- a/arch/arm/dts/am335x-brsmarc1.dts +++ b/arch/arm/dts/am335x-brsmarc1.dts @@ -59,7 +59,6 @@ /*backlight = <&tps_bl>; */ compatible = "ti,tilcdc,panel"; status = "okay"; - u-boot,dm-pre-reloc; panel-info { ac-bias = <255>; @@ -298,10 +297,21 @@ status = "okay"; }; -&lcdc { - status = "okay"; - ti,no-reset-on-init; - ti,no-idle-on-init; +&l4_per { + + segment@300000 { + + target-module@e000 { + u-boot,dm-pre-reloc; + + lcdc: lcdc@0 { + u-boot,dm-pre-reloc; + status = "okay"; + ti,no-reset-on-init; + ti,no-idle-on-init; + }; + }; + }; }; &elm { diff --git a/arch/arm/dts/am335x-brxre1.dts b/arch/arm/dts/am335x-brxre1.dts index 6091a12fb70..485c8e3613d 100644 --- a/arch/arm/dts/am335x-brxre1.dts +++ b/arch/arm/dts/am335x-brxre1.dts @@ -79,8 +79,6 @@ backlight = <&tps_bl>; - u-boot,dm-pre-reloc; - panel-info { ac-bias = <255>; ac-bias-intrpt = <0>; @@ -254,10 +252,21 @@ status = "okay"; }; -&lcdc { - status = "okay"; - ti,no-reset-on-init; - ti,no-idle-on-init; +&l4_per { + + segment@300000 { + + target-module@e000 { + u-boot,dm-pre-reloc; + + lcdc: lcdc@0 { + u-boot,dm-pre-reloc; + status = "okay"; + ti,no-reset-on-init; + ti,no-idle-on-init; + }; + }; + }; }; &elm { diff --git a/arch/arm/dts/am335x-evm-u-boot.dtsi b/arch/arm/dts/am335x-evm-u-boot.dtsi index 400a1d2cecb..4cf5f9928d5 100644 --- a/arch/arm/dts/am335x-evm-u-boot.dtsi +++ b/arch/arm/dts/am335x-evm-u-boot.dtsi @@ -5,13 +5,20 @@ #include "am33xx-u-boot.dtsi" -/ { - panel { - u-boot,dm-pre-reloc; +&l4_per { + + segment@300000 { + + target-module@e000 { + u-boot,dm-pre-reloc; + + lcdc: lcdc@0 { + u-boot,dm-pre-reloc; + }; + }; }; }; - &mmc3 { status = "disabled"; }; diff --git a/arch/arm/dts/am335x-evmsk-u-boot.dtsi b/arch/arm/dts/am335x-evmsk-u-boot.dtsi index 96798330b1f..1003f4d31ad 100644 --- a/arch/arm/dts/am335x-evmsk-u-boot.dtsi +++ b/arch/arm/dts/am335x-evmsk-u-boot.dtsi @@ -7,8 +7,16 @@ #include "am33xx-u-boot.dtsi" -/ { - panel { - u-boot,dm-pre-reloc; +&l4_per { + + segment@300000 { + + target-module@e000 { + u-boot,dm-pre-reloc; + + lcdc: lcdc@0 { + u-boot,dm-pre-reloc; + }; + }; }; }; diff --git a/arch/arm/dts/am335x-guardian-u-boot.dtsi b/arch/arm/dts/am335x-guardian-u-boot.dtsi index c866ce83f30..986f58e6648 100644 --- a/arch/arm/dts/am335x-guardian-u-boot.dtsi +++ b/arch/arm/dts/am335x-guardian-u-boot.dtsi @@ -10,16 +10,26 @@ ocp { u-boot,dm-pre-reloc; }; - - panel { - u-boot,dm-pre-reloc; - }; }; &l4_wkup { u-boot,dm-pre-reloc; }; +&l4_per { + + segment@300000 { + + target-module@e000 { + u-boot,dm-pre-reloc; + + lcdc: lcdc@0 { + u-boot,dm-pre-reloc; + }; + }; + }; +}; + &mmc1 { u-boot,dm-pre-reloc; }; diff --git a/arch/arm/dts/am335x-pdu001-u-boot.dtsi b/arch/arm/dts/am335x-pdu001-u-boot.dtsi index 4f4fc411f91..686a152fd9d 100644 --- a/arch/arm/dts/am335x-pdu001-u-boot.dtsi +++ b/arch/arm/dts/am335x-pdu001-u-boot.dtsi @@ -9,16 +9,26 @@ ocp { u-boot,dm-pre-reloc; }; - - panel { - u-boot,dm-pre-reloc; - }; }; &l4_wkup { u-boot,dm-pre-reloc; }; +&l4_per { + + segment@300000 { + + target-module@e000 { + u-boot,dm-pre-reloc; + + lcdc: lcdc@0 { + u-boot,dm-pre-reloc; + }; + }; + }; +}; + &scm { u-boot,dm-pre-reloc; }; diff --git a/arch/arm/dts/am335x-pxm50-u-boot.dtsi b/arch/arm/dts/am335x-pxm50-u-boot.dtsi index 65ed948c585..e5af9fdf893 100644 --- a/arch/arm/dts/am335x-pxm50-u-boot.dtsi +++ b/arch/arm/dts/am335x-pxm50-u-boot.dtsi @@ -7,8 +7,16 @@ #include "am33xx-u-boot.dtsi" -/ { - panel { - u-boot,dm-pre-reloc; +&l4_per { + + segment@300000 { + + target-module@e000 { + u-boot,dm-pre-reloc; + + lcdc: lcdc@0 { + u-boot,dm-pre-reloc; + }; + }; }; }; diff --git a/arch/arm/dts/am335x-rut-u-boot.dtsi b/arch/arm/dts/am335x-rut-u-boot.dtsi index b16f75a7647..a38c2dc6072 100644 --- a/arch/arm/dts/am335x-rut-u-boot.dtsi +++ b/arch/arm/dts/am335x-rut-u-boot.dtsi @@ -7,8 +7,16 @@ #include "am33xx-u-boot.dtsi" -/ { - panel { - u-boot,dm-pre-reloc; +&l4_per { + + segment@300000 { + + target-module@e000 { + u-boot,dm-pre-reloc; + + lcdc: lcdc@0 { + u-boot,dm-pre-reloc; + }; + }; }; }; diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 9db96aa891a..4629f28cbb8 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o obj-$(CONFIG_DM_VIDEO) += video_bmp.o obj-$(CONFIG_PANEL) += panel-uclass.o obj-$(CONFIG_SIMPLE_PANEL) += simple_panel.o +obj-$(CONFIG_AM335X_LCD) += tilcdc-panel.o endif obj-${CONFIG_EXYNOS_FB} += exynos/ diff --git a/drivers/video/am335x-fb.c b/drivers/video/am335x-fb.c index e99a9185a2a..a3464ae6a05 100644 --- a/drivers/video/am335x-fb.c +++ b/drivers/video/am335x-fb.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #include #include #include "am335x-fb.h" +#include "tilcdc-panel.h" #define LCDC_FMAX 200000000 @@ -323,7 +325,7 @@ int am335xfb_init(struct am335x_lcdpanel *panel) #else /* CONFIG_DM_VIDEO */ -#define FBSIZE(t, p) (((t)->hactive.typ * (t)->vactive.typ * (p)->bpp) >> 3) +#define FBSIZE(t, p) (((t).hactive.typ * (t).vactive.typ * (p).bpp) >> 3) enum { LCD_MAX_WIDTH = 2048, @@ -331,39 +333,8 @@ enum { LCD_MAX_LOG2_BPP = VIDEO_BPP32, }; -/** - * tilcdc_panel_info: Panel parameters - * - * @ac_bias: AC Bias Pin Frequency - * @ac_bias_intrpt: AC Bias Pin Transitions per Interrupt - * @dma_burst_sz: DMA burst size - * @bpp: Bits per pixel - * @fdd: FIFO DMA Request Delay - * @tft_alt_mode: TFT Alternative Signal Mapping (Only for active) - * @invert_pxl_clk: Invert pixel clock - * @sync_edge: Horizontal and Vertical Sync Edge: 0=rising 1=falling - * @sync_ctrl: Horizontal and Vertical Sync: Control: 0=ignore - * @raster_order: Raster Data Order Select: 1=Most-to-least 0=Least-to-most - * @fifo_th: DMA FIFO threshold - */ -struct tilcdc_panel_info { - u32 ac_bias; - u32 ac_bias_intrpt; - u32 dma_burst_sz; - u32 bpp; - u32 fdd; - bool tft_alt_mode; - bool invert_pxl_clk; - u32 sync_edge; - u32 sync_ctrl; - u32 raster_order; - u32 fifo_th; -}; - struct am335x_fb_priv { struct am335x_lcdhw *regs; - struct tilcdc_panel_info panel; - struct display_timing timing; }; static int am335x_fb_remove(struct udevice *dev) @@ -381,16 +352,71 @@ static int am335x_fb_probe(struct udevice *dev) struct video_priv *uc_priv = dev_get_uclass_priv(dev); struct am335x_fb_priv *priv = dev_get_priv(dev); struct am335x_lcdhw *regs = priv->regs; - struct tilcdc_panel_info *panel = &priv->panel; - struct display_timing *timing = &priv->timing; + struct udevice *panel; + struct tilcdc_panel_info info; + struct display_timing timing; struct cm_dpll *const cmdpll = (struct cm_dpll *)CM_DPLL; u32 reg; + int err; /* Before relocation we don't need to do anything */ if (!(gd->flags & GD_FLG_RELOC)) return 0; - am335x_fb_set_pixel_clk_rate(regs, timing->pixelclock.typ); + err = uclass_get_device(UCLASS_PANEL, 0, &panel); + if (err) { + dev_err(dev, "failed to get panel\n"); + return err; + } + + err = panel_get_display_timing(panel, &timing); + if (err) { + dev_err(dev, "failed to get display timing\n"); + return err; + } + + if (timing.pixelclock.typ > (LCDC_FMAX / 2)) { + dev_err(dev, "invalid display clock-frequency: %d Hz\n", + timing.pixelclock.typ); + return -EINVAL; + } + + if (timing.hactive.typ > LCD_MAX_WIDTH) + timing.hactive.typ = LCD_MAX_WIDTH; + + if (timing.vactive.typ > LCD_MAX_HEIGHT) + timing.vactive.typ = LCD_MAX_HEIGHT; + + err = tilcdc_panel_get_display_info(panel, &info); + if (err) { + dev_err(dev, "failed to get panel info\n"); + return err; + } + + switch (info.bpp) { + case 16: + case 24: + case 32: + break; + default: + dev_err(dev, "invalid seting, bpp: %d\n", info.bpp); + return -EINVAL; + } + + switch (info.dma_burst_sz) { + case 1: + case 2: + case 4: + case 8: + case 16: + break; + default: + dev_err(dev, "invalid setting, dma-burst-sz: %d\n", + info.dma_burst_sz); + return -EINVAL; + } + + am335x_fb_set_pixel_clk_rate(regs, timing.pixelclock.typ); /* clock source for LCDC from dispPLL M2 */ writel(0, &cmdpll->clklcdcpixelclk); @@ -411,14 +437,14 @@ static int am335x_fb_probe(struct udevice *dev) writel(reg, ®s->ctrl); writel(uc_plat->base, ®s->lcddma_fb0_base); - writel(uc_plat->base + FBSIZE(timing, panel), + writel(uc_plat->base + FBSIZE(timing, info), ®s->lcddma_fb0_ceiling); writel(uc_plat->base, ®s->lcddma_fb1_base); - writel(uc_plat->base + FBSIZE(timing, panel), + writel(uc_plat->base + FBSIZE(timing, info), ®s->lcddma_fb1_ceiling); - reg = LCDC_DMA_CTRL_FIFO_TH(panel->fifo_th); - switch (panel->dma_burst_sz) { + reg = LCDC_DMA_CTRL_FIFO_TH(info.fifo_th); + switch (info.dma_burst_sz) { case 1: reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_1); break; @@ -438,155 +464,84 @@ static int am335x_fb_probe(struct udevice *dev) writel(reg, ®s->lcddma_ctrl); - writel(LCDC_RASTER_TIMING_0_HORLSB(timing->hactive.typ) | - LCDC_RASTER_TIMING_0_HORMSB(timing->hactive.typ) | - LCDC_RASTER_TIMING_0_HFPLSB(timing->hfront_porch.typ) | - LCDC_RASTER_TIMING_0_HBPLSB(timing->hback_porch.typ) | - LCDC_RASTER_TIMING_0_HSWLSB(timing->hsync_len.typ), + writel(LCDC_RASTER_TIMING_0_HORLSB(timing.hactive.typ) | + LCDC_RASTER_TIMING_0_HORMSB(timing.hactive.typ) | + LCDC_RASTER_TIMING_0_HFPLSB(timing.hfront_porch.typ) | + LCDC_RASTER_TIMING_0_HBPLSB(timing.hback_porch.typ) | + LCDC_RASTER_TIMING_0_HSWLSB(timing.hsync_len.typ), ®s->raster_timing0); - writel(LCDC_RASTER_TIMING_1_VBP(timing->vback_porch.typ) | - LCDC_RASTER_TIMING_1_VFP(timing->vfront_porch.typ) | - LCDC_RASTER_TIMING_1_VSW(timing->vsync_len.typ) | - LCDC_RASTER_TIMING_1_VERLSB(timing->vactive.typ), + writel(LCDC_RASTER_TIMING_1_VBP(timing.vback_porch.typ) | + LCDC_RASTER_TIMING_1_VFP(timing.vfront_porch.typ) | + LCDC_RASTER_TIMING_1_VSW(timing.vsync_len.typ) | + LCDC_RASTER_TIMING_1_VERLSB(timing.vactive.typ), ®s->raster_timing1); - reg = LCDC_RASTER_TIMING_2_ACB(panel->ac_bias) | - LCDC_RASTER_TIMING_2_ACBI(panel->ac_bias_intrpt) | - LCDC_RASTER_TIMING_2_HSWMSB(timing->hsync_len.typ) | - LCDC_RASTER_TIMING_2_VERMSB(timing->vactive.typ) | - LCDC_RASTER_TIMING_2_HBPMSB(timing->hback_porch.typ) | - LCDC_RASTER_TIMING_2_HFPMSB(timing->hfront_porch.typ); + reg = LCDC_RASTER_TIMING_2_ACB(info.ac_bias) | + LCDC_RASTER_TIMING_2_ACBI(info.ac_bias_intrpt) | + LCDC_RASTER_TIMING_2_HSWMSB(timing.hsync_len.typ) | + LCDC_RASTER_TIMING_2_VERMSB(timing.vactive.typ) | + LCDC_RASTER_TIMING_2_HBPMSB(timing.hback_porch.typ) | + LCDC_RASTER_TIMING_2_HFPMSB(timing.hfront_porch.typ); - if (timing->flags & DISPLAY_FLAGS_VSYNC_LOW) + if (timing.flags & DISPLAY_FLAGS_VSYNC_LOW) reg |= LCDC_RASTER_TIMING_2_VSYNC_INVERT; - if (timing->flags & DISPLAY_FLAGS_HSYNC_LOW) + if (timing.flags & DISPLAY_FLAGS_HSYNC_LOW) reg |= LCDC_RASTER_TIMING_2_HSYNC_INVERT; - if (panel->invert_pxl_clk) + if (info.invert_pxl_clk) reg |= LCDC_RASTER_TIMING_2_PXCLK_INVERT; - if (panel->sync_edge) + if (info.sync_edge) reg |= LCDC_RASTER_TIMING_2_HSVS_RISEFALL; - if (panel->sync_ctrl) + if (info.sync_ctrl) reg |= LCDC_RASTER_TIMING_2_HSVS_CONTROL; writel(reg, ®s->raster_timing2); reg = LCDC_RASTER_CTRL_PALMODE_RAWDATA | LCDC_RASTER_CTRL_TFT_MODE | - LCDC_RASTER_CTRL_ENABLE | LCDC_RASTER_CTRL_REQDLY(panel->fdd); + LCDC_RASTER_CTRL_ENABLE | LCDC_RASTER_CTRL_REQDLY(info.fdd); - if (panel->tft_alt_mode) + if (info.tft_alt_mode) reg |= LCDC_RASTER_CTRL_TFT_ALT_ENABLE; - if (panel->bpp == 24) + if (info.bpp == 24) reg |= LCDC_RASTER_CTRL_TFT_24BPP_MODE; - else if (panel->bpp == 32) + else if (info.bpp == 32) reg |= LCDC_RASTER_CTRL_TFT_24BPP_MODE | LCDC_RASTER_CTRL_TFT_24BPP_UNPACK; - if (panel->raster_order) + if (info.raster_order) reg |= LCDC_RASTER_CTRL_DATA_ORDER; writel(reg, ®s->raster_ctrl); - uc_priv->xsize = timing->hactive.typ; - uc_priv->ysize = timing->vactive.typ; - uc_priv->bpix = log_2_n_round_up(panel->bpp); - return 0; -} - -static int am335x_fb_of_to_plat(struct udevice *dev) -{ - struct am335x_fb_priv *priv = dev_get_priv(dev); - struct tilcdc_panel_info *panel = &priv->panel; - struct display_timing *timing = &priv->timing; - ofnode node; - int err; + uc_priv->xsize = timing.hactive.typ; + uc_priv->ysize = timing.vactive.typ; + uc_priv->bpix = log_2_n_round_up(info.bpp); - node = ofnode_by_compatible(ofnode_null(), "ti,am33xx-tilcdc"); - if (!ofnode_valid(node)) { - dev_err(dev, "missing 'ti,am33xx-tilcdc' node\n"); - return -ENXIO; - } - - priv->regs = (struct am335x_lcdhw *)ofnode_get_addr(node); - dev_dbg(dev, "LCD: base address=0x%x\n", (unsigned int)priv->regs); - - err = ofnode_decode_display_timing(dev_ofnode(dev), 0, timing); + err = panel_enable_backlight(panel); if (err) { - dev_err(dev, "failed to get display timing\n"); + dev_err(dev, "failed to enable panel backlight\n"); return err; } - if (timing->pixelclock.typ > (LCDC_FMAX / 2)) { - dev_err(dev, "invalid display clock-frequency: %d Hz\n", - timing->pixelclock.typ); - return -EINVAL; - } - - if (timing->hactive.typ > LCD_MAX_WIDTH) - timing->hactive.typ = LCD_MAX_WIDTH; - - if (timing->vactive.typ > LCD_MAX_HEIGHT) - timing->vactive.typ = LCD_MAX_HEIGHT; - - node = ofnode_find_subnode(dev_ofnode(dev), "panel-info"); - if (!ofnode_valid(node)) { - dev_err(dev, "missing 'panel-info' node\n"); - return -ENXIO; - } - - err |= ofnode_read_u32(node, "ac-bias", &panel->ac_bias); - err |= ofnode_read_u32(node, "ac-bias-intrpt", &panel->ac_bias_intrpt); - err |= ofnode_read_u32(node, "dma-burst-sz", &panel->dma_burst_sz); - err |= ofnode_read_u32(node, "bpp", &panel->bpp); - err |= ofnode_read_u32(node, "fdd", &panel->fdd); - err |= ofnode_read_u32(node, "sync-edge", &panel->sync_edge); - err |= ofnode_read_u32(node, "sync-ctrl", &panel->sync_ctrl); - err |= ofnode_read_u32(node, "raster-order", &panel->raster_order); - err |= ofnode_read_u32(node, "fifo-th", &panel->fifo_th); - if (err) { - dev_err(dev, "failed to get panel info\n"); - return err; - } + return 0; +} - switch (panel->bpp) { - case 16: - case 24: - case 32: - break; - default: - dev_err(dev, "invalid seting, bpp: %d\n", panel->bpp); - return -EINVAL; - } +static int am335x_fb_ofdata_to_platdata(struct udevice *dev) +{ + struct am335x_fb_priv *priv = dev_get_priv(dev); - switch (panel->dma_burst_sz) { - case 1: - case 2: - case 4: - case 8: - case 16: - break; - default: - dev_err(dev, "invalid setting, dma-burst-sz: %d\n", - panel->dma_burst_sz); + priv->regs = (struct am335x_lcdhw *)dev_read_addr(dev); + if ((fdt_addr_t)priv->regs == FDT_ADDR_T_NONE) { + dev_err(dev, "failed to get base address\n"); return -EINVAL; } - /* optional */ - panel->tft_alt_mode = ofnode_read_bool(node, "tft-alt-mode"); - panel->invert_pxl_clk = ofnode_read_bool(node, "invert-pxl-clk"); - - dev_dbg(dev, "LCD: %dx%d, bpp=%d, clk=%d Hz\n", timing->hactive.typ, - timing->vactive.typ, panel->bpp, timing->pixelclock.typ); - dev_dbg(dev, " hbp=%d, hfp=%d, hsw=%d\n", timing->hback_porch.typ, - timing->hfront_porch.typ, timing->hsync_len.typ); - dev_dbg(dev, " vbp=%d, vfp=%d, vsw=%d\n", timing->vback_porch.typ, - timing->vfront_porch.typ, timing->vsync_len.typ); - + dev_dbg(dev, "LCD: base address=0x%x\n", (unsigned int)priv->regs); return 0; } @@ -602,7 +557,7 @@ static int am335x_fb_bind(struct udevice *dev) } static const struct udevice_id am335x_fb_ids[] = { - { .compatible = "ti,tilcdc,panel" }, + { .compatible = "ti,am33xx-tilcdc" }, { } }; diff --git a/drivers/video/am335x-fb.h b/drivers/video/am335x-fb.h index c9f92bc3895..4952dd96e9b 100644 --- a/drivers/video/am335x-fb.h +++ b/drivers/video/am335x-fb.h @@ -70,6 +70,37 @@ struct am335x_lcdpanel { int am335xfb_init(struct am335x_lcdpanel *panel); +#else /* CONFIG_DM_VIDEO */ + +/** + * tilcdc_panel_info: Panel parameters + * + * @ac_bias: AC Bias Pin Frequency + * @ac_bias_intrpt: AC Bias Pin Transitions per Interrupt + * @dma_burst_sz: DMA burst size + * @bpp: Bits per pixel + * @fdd: FIFO DMA Request Delay + * @tft_alt_mode: TFT Alternative Signal Mapping (Only for active) + * @invert_pxl_clk: Invert pixel clock + * @sync_edge: Horizontal and Vertical Sync Edge: 0=rising 1=falling + * @sync_ctrl: Horizontal and Vertical Sync: Control: 0=ignore + * @raster_order: Raster Data Order Select: 1=Most-to-least 0=Least-to-most + * @fifo_th: DMA FIFO threshold + */ +struct tilcdc_panel_info { + u32 ac_bias; + u32 ac_bias_intrpt; + u32 dma_burst_sz; + u32 bpp; + u32 fdd; + bool tft_alt_mode; + bool invert_pxl_clk; + u32 sync_edge; + u32 sync_ctrl; + u32 raster_order; + u32 fifo_th; +}; + #endif /* CONFIG_DM_VIDEO */ #endif /* AM335X_FB_H */ diff --git a/drivers/video/tilcdc-panel.c b/drivers/video/tilcdc-panel.c new file mode 100644 index 00000000000..84303a6b719 --- /dev/null +++ b/drivers/video/tilcdc-panel.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * OMAP panel support + * + * Copyright (C) 2020 Dario Binacchi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "am335x-fb.h" + +struct tilcdc_panel_priv { + struct tilcdc_panel_info info; + struct display_timing timing; + struct udevice *backlight; + struct gpio_desc enable; +}; + +static int tilcdc_panel_enable_backlight(struct udevice *dev) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + + if (dm_gpio_is_valid(&priv->enable)) + dm_gpio_set_value(&priv->enable, 1); + + if (priv->backlight) + return backlight_enable(priv->backlight); + + return 0; +} + +static int tilcdc_panel_set_backlight(struct udevice *dev, int percent) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + + if (dm_gpio_is_valid(&priv->enable)) + dm_gpio_set_value(&priv->enable, 1); + + if (priv->backlight) + return backlight_set_brightness(priv->backlight, percent); + + return 0; +} + +int tilcdc_panel_get_display_info(struct udevice *dev, + struct tilcdc_panel_info *info) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + + memcpy(info, &priv->info, sizeof(*info)); + return 0; +} + +static int tilcdc_panel_get_display_timing(struct udevice *dev, + struct display_timing *timing) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + + memcpy(timing, &priv->timing, sizeof(*timing)); + return 0; +} + +static int tilcdc_panel_remove(struct udevice *dev) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + + if (dm_gpio_is_valid(&priv->enable)) + dm_gpio_free(dev, &priv->enable); + + return 0; +} + +static int tilcdc_panel_probe(struct udevice *dev) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + int err; + + err = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev, + "backlight", &priv->backlight); + if (err) + dev_warn(dev, "failed to get backlight\n"); + + err = gpio_request_by_name(dev, "enable-gpios", 0, &priv->enable, + GPIOD_IS_OUT); + if (err) { + dev_warn(dev, "failed to get enable GPIO\n"); + if (err != -ENOENT) + return err; + } + + return 0; +} + +static int tilcdc_panel_of_to_plat(struct udevice *dev) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + ofnode node; + int err; + + err = ofnode_decode_display_timing(dev_ofnode(dev), 0, &priv->timing); + if (err) { + dev_err(dev, "failed to get display timing\n"); + return err; + } + + node = dev_read_subnode(dev, "panel-info"); + if (!ofnode_valid(node)) { + dev_err(dev, "missing 'panel-info' node\n"); + return -ENXIO; + } + + err |= ofnode_read_u32(node, "ac-bias", &priv->info.ac_bias); + err |= ofnode_read_u32(node, "ac-bias-intrpt", + &priv->info.ac_bias_intrpt); + err |= ofnode_read_u32(node, "dma-burst-sz", &priv->info.dma_burst_sz); + err |= ofnode_read_u32(node, "bpp", &priv->info.bpp); + err |= ofnode_read_u32(node, "fdd", &priv->info.fdd); + err |= ofnode_read_u32(node, "sync-edge", &priv->info.sync_edge); + err |= ofnode_read_u32(node, "sync-ctrl", &priv->info.sync_ctrl); + err |= ofnode_read_u32(node, "raster-order", &priv->info.raster_order); + err |= ofnode_read_u32(node, "fifo-th", &priv->info.fifo_th); + if (err) { + dev_err(dev, "failed to get panel info\n"); + return err; + } + + /* optional */ + priv->info.tft_alt_mode = ofnode_read_bool(node, "tft-alt-mode"); + priv->info.invert_pxl_clk = ofnode_read_bool(node, "invert-pxl-clk"); + + dev_dbg(dev, "LCD: %dx%d, bpp=%d, clk=%d Hz\n", + priv->timing.hactive.typ, priv->timing.vactive.typ, + priv->info.bpp, priv->timing.pixelclock.typ); + dev_dbg(dev, " hbp=%d, hfp=%d, hsw=%d\n", + priv->timing.hback_porch.typ, priv->timing.hfront_porch.typ, + priv->timing.hsync_len.typ); + dev_dbg(dev, " vbp=%d, vfp=%d, vsw=%d\n", + priv->timing.vback_porch.typ, priv->timing.vfront_porch.typ, + priv->timing.vsync_len.typ); + + return 0; +} + +static const struct panel_ops tilcdc_panel_ops = { + .enable_backlight = tilcdc_panel_enable_backlight, + .set_backlight = tilcdc_panel_set_backlight, + .get_display_timing = tilcdc_panel_get_display_timing, +}; + +static const struct udevice_id tilcdc_panel_ids[] = { + {.compatible = "ti,tilcdc,panel"}, + {} +}; + +U_BOOT_DRIVER(tilcdc_panel) = { + .name = "tilcdc_panel", + .id = UCLASS_PANEL, + .of_match = tilcdc_panel_ids, + .ops = &tilcdc_panel_ops, + .ofdata_to_platdata = tilcdc_panel_of_to_plat, + .probe = tilcdc_panel_probe, + .remove = tilcdc_panel_remove, + .priv_auto = sizeof(struct tilcdc_panel_priv), +}; diff --git a/drivers/video/tilcdc-panel.h b/drivers/video/tilcdc-panel.h new file mode 100644 index 00000000000..6b40731304c --- /dev/null +++ b/drivers/video/tilcdc-panel.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2020 Dario Binacchi + */ + +#ifndef _TILCDC_PANEL_H +#define _TILCDC_PANEL_H + +#include "am335x-fb.h" + +int tilcdc_panel_get_display_info(struct udevice *dev, + struct tilcdc_panel_info *info); + +#endif /* _TILCDC_PANEL_H */ -- cgit v1.3.1 From 91337f59dd6f5ca4baf1cdc86156fff65f7efb38 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:16:29 +0100 Subject: video: omap: set LCD clock rate through DM API The patch configures the display DPLL using the functions provided by the driver model API for the clock. The device tree contains everything needed to get the DPLL clock. The round rate function developed for calculating the DPLL multiplier and divisor and the platform routines for accessing the DPLL registers are removed from the LCD driver code because they are implemented inside the DPLL clock driver. Signed-off-by: Dario Binacchi --- drivers/video/am335x-fb.c | 129 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 103 insertions(+), 26 deletions(-) (limited to 'drivers') diff --git a/drivers/video/am335x-fb.c b/drivers/video/am335x-fb.c index a3464ae6a05..a31f403a841 100644 --- a/drivers/video/am335x-fb.c +++ b/drivers/video/am335x-fb.c @@ -12,6 +12,7 @@ * - starts output DMA from gd->fb_base buffer */ #include +#include #include #include #include @@ -112,6 +113,27 @@ struct am335x_lcdhw { unsigned int clkc_reset; /* 0x70 */ }; +DECLARE_GLOBAL_DATA_PTR; + +#if !CONFIG_IS_ENABLED(DM_VIDEO) + +#if !defined(LCD_CNTL_BASE) +#error "hw-base address of LCD-Controller (LCD_CNTL_BASE) not defined!" +#endif + +/* Macro definitions */ +#define FBSIZE(x) (((x)->hactive * (x)->vactive * (x)->bpp) >> 3) + +#define LCDC_RASTER_TIMING_2_INVMASK(x) ((x) & GENMASK(25, 20)) + +static struct am335x_lcdhw *lcdhw = (void *)LCD_CNTL_BASE; + +int lcd_get_size(int *line_length) +{ + *line_length = (panel_info.vl_col * NBITS(panel_info.vl_bpix)) / 8; + return *line_length * panel_info.vl_row + 0x20; +} + struct dpll_data { unsigned long rounded_rate; u16 rounded_m; @@ -119,8 +141,6 @@ struct dpll_data { u8 rounded_div; }; -DECLARE_GLOBAL_DATA_PTR; - /** * am335x_dpll_round_rate() - Round a target rate for an OMAP DPLL * @@ -199,25 +219,6 @@ static ulong am335x_fb_set_pixel_clk_rate(struct am335x_lcdhw *regs, ulong rate) return round_rate; } -#if !CONFIG_IS_ENABLED(DM_VIDEO) - -#if !defined(LCD_CNTL_BASE) -#error "hw-base address of LCD-Controller (LCD_CNTL_BASE) not defined!" -#endif - -/* Macro definitions */ -#define FBSIZE(x) (((x)->hactive * (x)->vactive * (x)->bpp) >> 3) - -#define LCDC_RASTER_TIMING_2_INVMASK(x) ((x) & GENMASK(25, 20)) - -static struct am335x_lcdhw *lcdhw = (void *)LCD_CNTL_BASE; - -int lcd_get_size(int *line_length) -{ - *line_length = (panel_info.vl_col * NBITS(panel_info.vl_bpix)) / 8; - return *line_length * panel_info.vl_row + 0x20; -} - int am335xfb_init(struct am335x_lcdpanel *panel) { u32 raster_ctrl = 0; @@ -335,14 +336,58 @@ enum { struct am335x_fb_priv { struct am335x_lcdhw *regs; + struct clk gclk; + struct clk dpll_m2_clk; }; +static ulong tilcdc_set_pixel_clk_rate(struct udevice *dev, ulong rate) +{ + struct am335x_fb_priv *priv = dev_get_priv(dev); + struct am335x_lcdhw *regs = priv->regs; + ulong mult_rate, mult_round_rate, best_err, err; + u32 v; + int div, i; + + best_err = rate; + div = 0; + for (i = 2; i <= 255; i++) { + mult_rate = rate * i; + mult_round_rate = clk_round_rate(&priv->gclk, mult_rate); + if (IS_ERR_VALUE(mult_round_rate)) + return mult_round_rate; + + err = mult_rate - mult_round_rate; + if (err < best_err) { + best_err = err; + div = i; + if (err == 0) + break; + } + } + + if (div == 0) { + dev_err(dev, "failed to find a divisor\n"); + return -EFAULT; + } + + mult_rate = clk_set_rate(&priv->gclk, rate * div); + v = readl(®s->ctrl) & ~LCDC_CTRL_CLK_DIVISOR_MASK; + v |= LCDC_CTRL_CLK_DIVISOR(div); + writel(v, ®s->ctrl); + rate = mult_rate / div; + dev_dbg(dev, "rate=%ld, div=%d, err=%ld\n", rate, div, err); + return rate; +} + static int am335x_fb_remove(struct udevice *dev) { struct video_uc_plat *uc_plat = dev_get_uclass_plat(dev); + struct am335x_fb_priv *priv = dev_get_priv(dev); uc_plat->base -= 0x20; uc_plat->size += 0x20; + clk_release_all(&priv->gclk, 1); + clk_release_all(&priv->dpll_m2_clk, 1); return 0; } @@ -352,10 +397,10 @@ static int am335x_fb_probe(struct udevice *dev) struct video_priv *uc_priv = dev_get_uclass_priv(dev); struct am335x_fb_priv *priv = dev_get_priv(dev); struct am335x_lcdhw *regs = priv->regs; - struct udevice *panel; + struct udevice *panel, *clk_dev; struct tilcdc_panel_info info; struct display_timing timing; - struct cm_dpll *const cmdpll = (struct cm_dpll *)CM_DPLL; + ulong rate; u32 reg; int err; @@ -416,10 +461,42 @@ static int am335x_fb_probe(struct udevice *dev) return -EINVAL; } - am335x_fb_set_pixel_clk_rate(regs, timing.pixelclock.typ); + err = uclass_get_device_by_name(UCLASS_CLK, "lcd_gclk@534", &clk_dev); + if (err) { + dev_err(dev, "failed to get lcd_gclk device\n"); + return err; + } - /* clock source for LCDC from dispPLL M2 */ - writel(0, &cmdpll->clklcdcpixelclk); + err = clk_request(clk_dev, &priv->gclk); + if (err) { + dev_err(dev, "failed to get %s clock\n", clk_dev->name); + return err; + } + + rate = tilcdc_set_pixel_clk_rate(dev, timing.pixelclock.typ); + if (IS_ERR_VALUE(rate)) { + dev_err(dev, "failed to set pixel clock rate\n"); + return rate; + } + + err = uclass_get_device_by_name(UCLASS_CLK, "dpll_disp_m2_ck@4a4", &clk_dev); + if (err) { + dev_err(dev, "failed to get dpll_disp_m2 clock device\n"); + return err; + } + + err = clk_request(clk_dev, &priv->dpll_m2_clk); + if (err) { + dev_err(dev, "failed to get %s clock\n", clk_dev->name); + return err; + } + + err = clk_set_parent(&priv->gclk, &priv->dpll_m2_clk); + if (err) { + dev_err(dev, "failed to set %s clock as %s's parent\n", + priv->dpll_m2_clk.dev->name, priv->gclk.dev->name); + return err; + } /* palette default entry */ memset((void *)uc_plat->base, 0, 0x20); -- cgit v1.3.1 From 35ab1b6ef7f3a804babbb77d28041967539cd5e6 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:16:30 +0100 Subject: video: omap: split the legacy code from the DM code The schedule for deprecating the features of the pre-driver-model puts 2019.17 as the deadline for the video subsystem. Furthermore, the latest patches applied to the am335x-fb.c module have decreased the amount of code shared with the pre-driver-model implementation. Splitting the two implementations into two modules improves the readability of the code and will make it easier to drop the pre-driver-model code. I have not created a header file with the data structures and the constants for accessing the LCD controller registers, but I preferred to keep them inside the two c modules. This is a code replication until the pre-driver-model version is dropped. Signed-off-by: Dario Binacchi --- drivers/video/Makefile | 5 +- drivers/video/am335x-fb.c | 335 ---------------------------------- drivers/video/am335x-fb.h | 35 ---- drivers/video/tilcdc-panel.c | 2 +- drivers/video/tilcdc-panel.h | 2 +- drivers/video/tilcdc.c | 425 +++++++++++++++++++++++++++++++++++++++++++ drivers/video/tilcdc.h | 38 ++++ 7 files changed, 468 insertions(+), 374 deletions(-) create mode 100644 drivers/video/tilcdc.c create mode 100644 drivers/video/tilcdc.h (limited to 'drivers') diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 4629f28cbb8..5f0f7b440c7 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -16,7 +16,9 @@ obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o obj-$(CONFIG_DM_VIDEO) += video_bmp.o obj-$(CONFIG_PANEL) += panel-uclass.o obj-$(CONFIG_SIMPLE_PANEL) += simple_panel.o -obj-$(CONFIG_AM335X_LCD) += tilcdc-panel.o +obj-$(CONFIG_AM335X_LCD) += tilcdc.o tilcdc-panel.o +else +obj-$(CONFIG_AM335X_LCD) += am335x-fb.o endif obj-${CONFIG_EXYNOS_FB} += exynos/ @@ -24,7 +26,6 @@ obj-${CONFIG_VIDEO_ROCKCHIP} += rockchip/ obj-${CONFIG_VIDEO_STM32} += stm32/ obj-${CONFIG_VIDEO_TEGRA124} += tegra124/ -obj-$(CONFIG_AM335X_LCD) += am335x-fb.o obj-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o obj-$(CONFIG_ATMEL_HLCD) += atmel_hlcdfb.o obj-$(CONFIG_ATMEL_LCD) += atmel_lcdfb.o diff --git a/drivers/video/am335x-fb.c b/drivers/video/am335x-fb.c index a31f403a841..5fa6f794ecc 100644 --- a/drivers/video/am335x-fb.c +++ b/drivers/video/am335x-fb.c @@ -12,22 +12,16 @@ * - starts output DMA from gd->fb_base buffer */ #include -#include -#include #include #include -#include -#include #include #include #include #include #include -#include #include #include #include "am335x-fb.h" -#include "tilcdc-panel.h" #define LCDC_FMAX 200000000 @@ -115,8 +109,6 @@ struct am335x_lcdhw { DECLARE_GLOBAL_DATA_PTR; -#if !CONFIG_IS_ENABLED(DM_VIDEO) - #if !defined(LCD_CNTL_BASE) #error "hw-base address of LCD-Controller (LCD_CNTL_BASE) not defined!" #endif @@ -323,330 +315,3 @@ int am335xfb_init(struct am335x_lcdpanel *panel) return 0; } - -#else /* CONFIG_DM_VIDEO */ - -#define FBSIZE(t, p) (((t).hactive.typ * (t).vactive.typ * (p).bpp) >> 3) - -enum { - LCD_MAX_WIDTH = 2048, - LCD_MAX_HEIGHT = 2048, - LCD_MAX_LOG2_BPP = VIDEO_BPP32, -}; - -struct am335x_fb_priv { - struct am335x_lcdhw *regs; - struct clk gclk; - struct clk dpll_m2_clk; -}; - -static ulong tilcdc_set_pixel_clk_rate(struct udevice *dev, ulong rate) -{ - struct am335x_fb_priv *priv = dev_get_priv(dev); - struct am335x_lcdhw *regs = priv->regs; - ulong mult_rate, mult_round_rate, best_err, err; - u32 v; - int div, i; - - best_err = rate; - div = 0; - for (i = 2; i <= 255; i++) { - mult_rate = rate * i; - mult_round_rate = clk_round_rate(&priv->gclk, mult_rate); - if (IS_ERR_VALUE(mult_round_rate)) - return mult_round_rate; - - err = mult_rate - mult_round_rate; - if (err < best_err) { - best_err = err; - div = i; - if (err == 0) - break; - } - } - - if (div == 0) { - dev_err(dev, "failed to find a divisor\n"); - return -EFAULT; - } - - mult_rate = clk_set_rate(&priv->gclk, rate * div); - v = readl(®s->ctrl) & ~LCDC_CTRL_CLK_DIVISOR_MASK; - v |= LCDC_CTRL_CLK_DIVISOR(div); - writel(v, ®s->ctrl); - rate = mult_rate / div; - dev_dbg(dev, "rate=%ld, div=%d, err=%ld\n", rate, div, err); - return rate; -} - -static int am335x_fb_remove(struct udevice *dev) -{ - struct video_uc_plat *uc_plat = dev_get_uclass_plat(dev); - struct am335x_fb_priv *priv = dev_get_priv(dev); - - uc_plat->base -= 0x20; - uc_plat->size += 0x20; - clk_release_all(&priv->gclk, 1); - clk_release_all(&priv->dpll_m2_clk, 1); - return 0; -} - -static int am335x_fb_probe(struct udevice *dev) -{ - struct video_uc_plat *uc_plat = dev_get_uclass_plat(dev); - struct video_priv *uc_priv = dev_get_uclass_priv(dev); - struct am335x_fb_priv *priv = dev_get_priv(dev); - struct am335x_lcdhw *regs = priv->regs; - struct udevice *panel, *clk_dev; - struct tilcdc_panel_info info; - struct display_timing timing; - ulong rate; - u32 reg; - int err; - - /* Before relocation we don't need to do anything */ - if (!(gd->flags & GD_FLG_RELOC)) - return 0; - - err = uclass_get_device(UCLASS_PANEL, 0, &panel); - if (err) { - dev_err(dev, "failed to get panel\n"); - return err; - } - - err = panel_get_display_timing(panel, &timing); - if (err) { - dev_err(dev, "failed to get display timing\n"); - return err; - } - - if (timing.pixelclock.typ > (LCDC_FMAX / 2)) { - dev_err(dev, "invalid display clock-frequency: %d Hz\n", - timing.pixelclock.typ); - return -EINVAL; - } - - if (timing.hactive.typ > LCD_MAX_WIDTH) - timing.hactive.typ = LCD_MAX_WIDTH; - - if (timing.vactive.typ > LCD_MAX_HEIGHT) - timing.vactive.typ = LCD_MAX_HEIGHT; - - err = tilcdc_panel_get_display_info(panel, &info); - if (err) { - dev_err(dev, "failed to get panel info\n"); - return err; - } - - switch (info.bpp) { - case 16: - case 24: - case 32: - break; - default: - dev_err(dev, "invalid seting, bpp: %d\n", info.bpp); - return -EINVAL; - } - - switch (info.dma_burst_sz) { - case 1: - case 2: - case 4: - case 8: - case 16: - break; - default: - dev_err(dev, "invalid setting, dma-burst-sz: %d\n", - info.dma_burst_sz); - return -EINVAL; - } - - err = uclass_get_device_by_name(UCLASS_CLK, "lcd_gclk@534", &clk_dev); - if (err) { - dev_err(dev, "failed to get lcd_gclk device\n"); - return err; - } - - err = clk_request(clk_dev, &priv->gclk); - if (err) { - dev_err(dev, "failed to get %s clock\n", clk_dev->name); - return err; - } - - rate = tilcdc_set_pixel_clk_rate(dev, timing.pixelclock.typ); - if (IS_ERR_VALUE(rate)) { - dev_err(dev, "failed to set pixel clock rate\n"); - return rate; - } - - err = uclass_get_device_by_name(UCLASS_CLK, "dpll_disp_m2_ck@4a4", &clk_dev); - if (err) { - dev_err(dev, "failed to get dpll_disp_m2 clock device\n"); - return err; - } - - err = clk_request(clk_dev, &priv->dpll_m2_clk); - if (err) { - dev_err(dev, "failed to get %s clock\n", clk_dev->name); - return err; - } - - err = clk_set_parent(&priv->gclk, &priv->dpll_m2_clk); - if (err) { - dev_err(dev, "failed to set %s clock as %s's parent\n", - priv->dpll_m2_clk.dev->name, priv->gclk.dev->name); - return err; - } - - /* palette default entry */ - memset((void *)uc_plat->base, 0, 0x20); - *(unsigned int *)uc_plat->base = 0x4000; - /* point fb behind palette */ - uc_plat->base += 0x20; - uc_plat->size -= 0x20; - - writel(LCDC_CLKC_ENABLE_CORECLKEN | LCDC_CLKC_ENABLE_LIDDCLKEN | - LCDC_CLKC_ENABLE_DMACLKEN, ®s->clkc_enable); - writel(0, ®s->raster_ctrl); - - reg = readl(®s->ctrl) & LCDC_CTRL_CLK_DIVISOR_MASK; - reg |= LCDC_CTRL_RASTER_MODE; - writel(reg, ®s->ctrl); - - writel(uc_plat->base, ®s->lcddma_fb0_base); - writel(uc_plat->base + FBSIZE(timing, info), - ®s->lcddma_fb0_ceiling); - writel(uc_plat->base, ®s->lcddma_fb1_base); - writel(uc_plat->base + FBSIZE(timing, info), - ®s->lcddma_fb1_ceiling); - - reg = LCDC_DMA_CTRL_FIFO_TH(info.fifo_th); - switch (info.dma_burst_sz) { - case 1: - reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_1); - break; - case 2: - reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_2); - break; - case 4: - reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_4); - break; - case 8: - reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_8); - break; - case 16: - reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_16); - break; - } - - writel(reg, ®s->lcddma_ctrl); - - writel(LCDC_RASTER_TIMING_0_HORLSB(timing.hactive.typ) | - LCDC_RASTER_TIMING_0_HORMSB(timing.hactive.typ) | - LCDC_RASTER_TIMING_0_HFPLSB(timing.hfront_porch.typ) | - LCDC_RASTER_TIMING_0_HBPLSB(timing.hback_porch.typ) | - LCDC_RASTER_TIMING_0_HSWLSB(timing.hsync_len.typ), - ®s->raster_timing0); - - writel(LCDC_RASTER_TIMING_1_VBP(timing.vback_porch.typ) | - LCDC_RASTER_TIMING_1_VFP(timing.vfront_porch.typ) | - LCDC_RASTER_TIMING_1_VSW(timing.vsync_len.typ) | - LCDC_RASTER_TIMING_1_VERLSB(timing.vactive.typ), - ®s->raster_timing1); - - reg = LCDC_RASTER_TIMING_2_ACB(info.ac_bias) | - LCDC_RASTER_TIMING_2_ACBI(info.ac_bias_intrpt) | - LCDC_RASTER_TIMING_2_HSWMSB(timing.hsync_len.typ) | - LCDC_RASTER_TIMING_2_VERMSB(timing.vactive.typ) | - LCDC_RASTER_TIMING_2_HBPMSB(timing.hback_porch.typ) | - LCDC_RASTER_TIMING_2_HFPMSB(timing.hfront_porch.typ); - - if (timing.flags & DISPLAY_FLAGS_VSYNC_LOW) - reg |= LCDC_RASTER_TIMING_2_VSYNC_INVERT; - - if (timing.flags & DISPLAY_FLAGS_HSYNC_LOW) - reg |= LCDC_RASTER_TIMING_2_HSYNC_INVERT; - - if (info.invert_pxl_clk) - reg |= LCDC_RASTER_TIMING_2_PXCLK_INVERT; - - if (info.sync_edge) - reg |= LCDC_RASTER_TIMING_2_HSVS_RISEFALL; - - if (info.sync_ctrl) - reg |= LCDC_RASTER_TIMING_2_HSVS_CONTROL; - - writel(reg, ®s->raster_timing2); - - reg = LCDC_RASTER_CTRL_PALMODE_RAWDATA | LCDC_RASTER_CTRL_TFT_MODE | - LCDC_RASTER_CTRL_ENABLE | LCDC_RASTER_CTRL_REQDLY(info.fdd); - - if (info.tft_alt_mode) - reg |= LCDC_RASTER_CTRL_TFT_ALT_ENABLE; - - if (info.bpp == 24) - reg |= LCDC_RASTER_CTRL_TFT_24BPP_MODE; - else if (info.bpp == 32) - reg |= LCDC_RASTER_CTRL_TFT_24BPP_MODE | - LCDC_RASTER_CTRL_TFT_24BPP_UNPACK; - - if (info.raster_order) - reg |= LCDC_RASTER_CTRL_DATA_ORDER; - - writel(reg, ®s->raster_ctrl); - - uc_priv->xsize = timing.hactive.typ; - uc_priv->ysize = timing.vactive.typ; - uc_priv->bpix = log_2_n_round_up(info.bpp); - - err = panel_enable_backlight(panel); - if (err) { - dev_err(dev, "failed to enable panel backlight\n"); - return err; - } - - return 0; -} - -static int am335x_fb_ofdata_to_platdata(struct udevice *dev) -{ - struct am335x_fb_priv *priv = dev_get_priv(dev); - - priv->regs = (struct am335x_lcdhw *)dev_read_addr(dev); - if ((fdt_addr_t)priv->regs == FDT_ADDR_T_NONE) { - dev_err(dev, "failed to get base address\n"); - return -EINVAL; - } - - dev_dbg(dev, "LCD: base address=0x%x\n", (unsigned int)priv->regs); - return 0; -} - -static int am335x_fb_bind(struct udevice *dev) -{ - struct video_uc_plat *uc_plat = dev_get_uclass_plat(dev); - - uc_plat->size = ((LCD_MAX_WIDTH * LCD_MAX_HEIGHT * - (1 << LCD_MAX_LOG2_BPP)) >> 3) + 0x20; - - dev_dbg(dev, "frame buffer size 0x%x\n", uc_plat->size); - return 0; -} - -static const struct udevice_id am335x_fb_ids[] = { - { .compatible = "ti,am33xx-tilcdc" }, - { } -}; - -U_BOOT_DRIVER(am335x_fb) = { - .name = "am335x_fb", - .id = UCLASS_VIDEO, - .of_match = am335x_fb_ids, - .bind = am335x_fb_bind, - .of_to_plat = am335x_fb_of_to_plat, - .probe = am335x_fb_probe, - .remove = am335x_fb_remove, - .priv_auto = sizeof(struct am335x_fb_priv), -}; - -#endif /* CONFIG_DM_VIDEO */ diff --git a/drivers/video/am335x-fb.h b/drivers/video/am335x-fb.h index 4952dd96e9b..ad9b015e090 100644 --- a/drivers/video/am335x-fb.h +++ b/drivers/video/am335x-fb.h @@ -7,8 +7,6 @@ #ifndef AM335X_FB_H #define AM335X_FB_H -#if !CONFIG_IS_ENABLED(DM_VIDEO) - #define HSVS_CONTROL BIT(25) /* * 0 = lcd_lp and lcd_fp are driven on * opposite edges of pixel clock than @@ -70,37 +68,4 @@ struct am335x_lcdpanel { int am335xfb_init(struct am335x_lcdpanel *panel); -#else /* CONFIG_DM_VIDEO */ - -/** - * tilcdc_panel_info: Panel parameters - * - * @ac_bias: AC Bias Pin Frequency - * @ac_bias_intrpt: AC Bias Pin Transitions per Interrupt - * @dma_burst_sz: DMA burst size - * @bpp: Bits per pixel - * @fdd: FIFO DMA Request Delay - * @tft_alt_mode: TFT Alternative Signal Mapping (Only for active) - * @invert_pxl_clk: Invert pixel clock - * @sync_edge: Horizontal and Vertical Sync Edge: 0=rising 1=falling - * @sync_ctrl: Horizontal and Vertical Sync: Control: 0=ignore - * @raster_order: Raster Data Order Select: 1=Most-to-least 0=Least-to-most - * @fifo_th: DMA FIFO threshold - */ -struct tilcdc_panel_info { - u32 ac_bias; - u32 ac_bias_intrpt; - u32 dma_burst_sz; - u32 bpp; - u32 fdd; - bool tft_alt_mode; - bool invert_pxl_clk; - u32 sync_edge; - u32 sync_ctrl; - u32 raster_order; - u32 fifo_th; -}; - -#endif /* CONFIG_DM_VIDEO */ - #endif /* AM335X_FB_H */ diff --git a/drivers/video/tilcdc-panel.c b/drivers/video/tilcdc-panel.c index 84303a6b719..b90dfae4ae9 100644 --- a/drivers/video/tilcdc-panel.c +++ b/drivers/video/tilcdc-panel.c @@ -15,7 +15,7 @@ #include #include #include -#include "am335x-fb.h" +#include "tilcdc.h" struct tilcdc_panel_priv { struct tilcdc_panel_info info; diff --git a/drivers/video/tilcdc-panel.h b/drivers/video/tilcdc-panel.h index 6b40731304c..6bcfbf8a8b4 100644 --- a/drivers/video/tilcdc-panel.h +++ b/drivers/video/tilcdc-panel.h @@ -6,7 +6,7 @@ #ifndef _TILCDC_PANEL_H #define _TILCDC_PANEL_H -#include "am335x-fb.h" +#include "tilcdc.h" int tilcdc_panel_get_display_info(struct udevice *dev, struct tilcdc_panel_info *info); diff --git a/drivers/video/tilcdc.c b/drivers/video/tilcdc.c new file mode 100644 index 00000000000..d13cc11801d --- /dev/null +++ b/drivers/video/tilcdc.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2020 Dario Binacchi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tilcdc.h" +#include "tilcdc-panel.h" + +#define LCDC_FMAX 200000000 + +/* LCD Control Register */ +#define LCDC_CTRL_CLK_DIVISOR_MASK GENMASK(15, 8) +#define LCDC_CTRL_RASTER_MODE BIT(0) +#define LCDC_CTRL_CLK_DIVISOR(x) (((x) & GENMASK(7, 0)) << 8) +/* LCD Clock Enable Register */ +#define LCDC_CLKC_ENABLE_CORECLKEN BIT(0) +#define LCDC_CLKC_ENABLE_LIDDCLKEN BIT(1) +#define LCDC_CLKC_ENABLE_DMACLKEN BIT(2) +/* LCD DMA Control Register */ +#define LCDC_DMA_CTRL_BURST_SIZE(x) (((x) & GENMASK(2, 0)) << 4) +#define LCDC_DMA_CTRL_BURST_1 0x0 +#define LCDC_DMA_CTRL_BURST_2 0x1 +#define LCDC_DMA_CTRL_BURST_4 0x2 +#define LCDC_DMA_CTRL_BURST_8 0x3 +#define LCDC_DMA_CTRL_BURST_16 0x4 +#define LCDC_DMA_CTRL_FIFO_TH(x) (((x) & GENMASK(2, 0)) << 8) +/* LCD Timing_0 Register */ +#define LCDC_RASTER_TIMING_0_HORMSB(x) ((((x) - 1) & BIT(10)) >> 7) +#define LCDC_RASTER_TIMING_0_HORLSB(x) (((((x) >> 4) - 1) & GENMASK(5, 0)) << 4) +#define LCDC_RASTER_TIMING_0_HSWLSB(x) ((((x) - 1) & GENMASK(5, 0)) << 10) +#define LCDC_RASTER_TIMING_0_HFPLSB(x) ((((x) - 1) & GENMASK(7, 0)) << 16) +#define LCDC_RASTER_TIMING_0_HBPLSB(x) ((((x) - 1) & GENMASK(7, 0)) << 24) +/* LCD Timing_1 Register */ +#define LCDC_RASTER_TIMING_1_VERLSB(x) (((x) - 1) & GENMASK(9, 0)) +#define LCDC_RASTER_TIMING_1_VSW(x) ((((x) - 1) & GENMASK(5, 0)) << 10) +#define LCDC_RASTER_TIMING_1_VFP(x) (((x) & GENMASK(7, 0)) << 16) +#define LCDC_RASTER_TIMING_1_VBP(x) (((x) & GENMASK(7, 0)) << 24) +/* LCD Timing_2 Register */ +#define LCDC_RASTER_TIMING_2_HFPMSB(x) ((((x) - 1) & GENMASK(9, 8)) >> 8) +#define LCDC_RASTER_TIMING_2_HBPMSB(x) ((((x) - 1) & GENMASK(9, 8)) >> 4) +#define LCDC_RASTER_TIMING_2_ACB(x) (((x) & GENMASK(7, 0)) << 8) +#define LCDC_RASTER_TIMING_2_ACBI(x) (((x) & GENMASK(3, 0)) << 16) +#define LCDC_RASTER_TIMING_2_VSYNC_INVERT BIT(20) +#define LCDC_RASTER_TIMING_2_HSYNC_INVERT BIT(21) +#define LCDC_RASTER_TIMING_2_PXCLK_INVERT BIT(22) +#define LCDC_RASTER_TIMING_2_DE_INVERT BIT(23) +#define LCDC_RASTER_TIMING_2_HSVS_RISEFALL BIT(24) +#define LCDC_RASTER_TIMING_2_HSVS_CONTROL BIT(25) +#define LCDC_RASTER_TIMING_2_VERMSB(x) ((((x) - 1) & BIT(10)) << 16) +#define LCDC_RASTER_TIMING_2_HSWMSB(x) ((((x) - 1) & GENMASK(9, 6)) << 21) +/* LCD Raster Ctrl Register */ +#define LCDC_RASTER_CTRL_ENABLE BIT(0) +#define LCDC_RASTER_CTRL_TFT_MODE BIT(7) +#define LCDC_RASTER_CTRL_DATA_ORDER BIT(8) +#define LCDC_RASTER_CTRL_REQDLY(x) (((x) & GENMASK(7, 0)) << 12) +#define LCDC_RASTER_CTRL_PALMODE_RAWDATA (0x02 << 20) +#define LCDC_RASTER_CTRL_TFT_ALT_ENABLE BIT(23) +#define LCDC_RASTER_CTRL_TFT_24BPP_MODE BIT(25) +#define LCDC_RASTER_CTRL_TFT_24BPP_UNPACK BIT(26) + +enum { + LCDC_MAX_WIDTH = 2048, + LCDC_MAX_HEIGHT = 2048, + LCDC_MAX_LOG2_BPP = VIDEO_BPP32, +}; + +struct tilcdc_regs { + u32 pid; + u32 ctrl; + u32 gap0; + u32 lidd_ctrl; + u32 lidd_cs0_conf; + u32 lidd_cs0_addr; + u32 lidd_cs0_data; + u32 lidd_cs1_conf; + u32 lidd_cs1_addr; + u32 lidd_cs1_data; + u32 raster_ctrl; + u32 raster_timing0; + u32 raster_timing1; + u32 raster_timing2; + u32 raster_subpanel; + u32 raster_subpanel2; + u32 lcddma_ctrl; + u32 lcddma_fb0_base; + u32 lcddma_fb0_ceiling; + u32 lcddma_fb1_base; + u32 lcddma_fb1_ceiling; + u32 sysconfig; + u32 irqstatus_raw; + u32 irqstatus; + u32 irqenable_set; + u32 irqenable_clear; + u32 gap1; + u32 clkc_enable; + u32 clkc_reset; +}; + +struct tilcdc_priv { + struct tilcdc_regs *regs; + struct clk gclk; + struct clk dpll_m2_clk; +}; + +DECLARE_GLOBAL_DATA_PTR; + +static ulong tilcdc_set_pixel_clk_rate(struct udevice *dev, ulong rate) +{ + struct tilcdc_priv *priv = dev_get_priv(dev); + struct tilcdc_regs *regs = priv->regs; + ulong mult_rate, mult_round_rate, best_err, err; + u32 v; + int div, i; + + best_err = rate; + div = 0; + for (i = 2; i <= 255; i++) { + mult_rate = rate * i; + mult_round_rate = clk_round_rate(&priv->gclk, mult_rate); + if (IS_ERR_VALUE(mult_round_rate)) + return mult_round_rate; + + err = mult_rate - mult_round_rate; + if (err < best_err) { + best_err = err; + div = i; + if (err == 0) + break; + } + } + + if (div == 0) { + dev_err(dev, "failed to find a divisor\n"); + return -EFAULT; + } + + mult_rate = clk_set_rate(&priv->gclk, rate * div); + v = readl(®s->ctrl) & ~LCDC_CTRL_CLK_DIVISOR_MASK; + v |= LCDC_CTRL_CLK_DIVISOR(div); + writel(v, ®s->ctrl); + rate = mult_rate / div; + dev_dbg(dev, "rate=%ld, div=%d, err=%ld\n", rate, div, err); + return rate; +} + +static int tilcdc_remove(struct udevice *dev) +{ + struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev); + struct tilcdc_priv *priv = dev_get_priv(dev); + + uc_plat->base -= 0x20; + uc_plat->size += 0x20; + clk_release_all(&priv->gclk, 1); + clk_release_all(&priv->dpll_m2_clk, 1); + return 0; +} + +static int tilcdc_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 tilcdc_priv *priv = dev_get_priv(dev); + struct tilcdc_regs *regs = priv->regs; + struct udevice *panel, *clk_dev; + struct tilcdc_panel_info info; + struct display_timing timing; + ulong rate; + u32 reg; + int err; + + /* Before relocation we don't need to do anything */ + if (!(gd->flags & GD_FLG_RELOC)) + return 0; + + err = uclass_get_device(UCLASS_PANEL, 0, &panel); + if (err) { + dev_err(dev, "failed to get panel\n"); + return err; + } + + err = panel_get_display_timing(panel, &timing); + if (err) { + dev_err(dev, "failed to get display timing\n"); + return err; + } + + if (timing.pixelclock.typ > (LCDC_FMAX / 2)) { + dev_err(dev, "invalid display clock-frequency: %d Hz\n", + timing.pixelclock.typ); + return -EINVAL; + } + + if (timing.hactive.typ > LCDC_MAX_WIDTH) + timing.hactive.typ = LCDC_MAX_WIDTH; + + if (timing.vactive.typ > LCDC_MAX_HEIGHT) + timing.vactive.typ = LCDC_MAX_HEIGHT; + + err = tilcdc_panel_get_display_info(panel, &info); + if (err) { + dev_err(dev, "failed to get panel info\n"); + return err; + } + + switch (info.bpp) { + case 16: + case 24: + case 32: + break; + default: + dev_err(dev, "invalid seting, bpp: %d\n", info.bpp); + return -EINVAL; + } + + switch (info.dma_burst_sz) { + case 1: + case 2: + case 4: + case 8: + case 16: + break; + default: + dev_err(dev, "invalid setting, dma-burst-sz: %d\n", + info.dma_burst_sz); + return -EINVAL; + } + + err = uclass_get_device_by_name(UCLASS_CLK, "lcd_gclk@534", &clk_dev); + if (err) { + dev_err(dev, "failed to get lcd_gclk device\n"); + return err; + } + + err = clk_request(clk_dev, &priv->gclk); + if (err) { + dev_err(dev, "failed to get %s clock\n", clk_dev->name); + return err; + } + + rate = tilcdc_set_pixel_clk_rate(dev, timing.pixelclock.typ); + if (IS_ERR_VALUE(rate)) { + dev_err(dev, "failed to set pixel clock rate\n"); + return rate; + } + + err = uclass_get_device_by_name(UCLASS_CLK, "dpll_disp_m2_ck@4a4", + &clk_dev); + if (err) { + dev_err(dev, "failed to get dpll_disp_m2 clock device\n"); + return err; + } + + err = clk_request(clk_dev, &priv->dpll_m2_clk); + if (err) { + dev_err(dev, "failed to get %s clock\n", clk_dev->name); + return err; + } + + err = clk_set_parent(&priv->gclk, &priv->dpll_m2_clk); + if (err) { + dev_err(dev, "failed to set %s clock as %s's parent\n", + priv->dpll_m2_clk.dev->name, priv->gclk.dev->name); + return err; + } + + /* palette default entry */ + memset((void *)uc_plat->base, 0, 0x20); + *(unsigned int *)uc_plat->base = 0x4000; + /* point fb behind palette */ + uc_plat->base += 0x20; + uc_plat->size -= 0x20; + + writel(LCDC_CLKC_ENABLE_CORECLKEN | LCDC_CLKC_ENABLE_LIDDCLKEN | + LCDC_CLKC_ENABLE_DMACLKEN, ®s->clkc_enable); + writel(0, ®s->raster_ctrl); + + reg = readl(®s->ctrl) & LCDC_CTRL_CLK_DIVISOR_MASK; + reg |= LCDC_CTRL_RASTER_MODE; + writel(reg, ®s->ctrl); + + reg = (timing.hactive.typ * timing.vactive.typ * info.bpp) >> 3; + reg += uc_plat->base; + writel(uc_plat->base, ®s->lcddma_fb0_base); + writel(reg, ®s->lcddma_fb0_ceiling); + writel(uc_plat->base, ®s->lcddma_fb1_base); + writel(reg, ®s->lcddma_fb1_ceiling); + + reg = LCDC_DMA_CTRL_FIFO_TH(info.fifo_th); + switch (info.dma_burst_sz) { + case 1: + reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_1); + break; + case 2: + reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_2); + break; + case 4: + reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_4); + break; + case 8: + reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_8); + break; + case 16: + reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_16); + break; + } + + writel(reg, ®s->lcddma_ctrl); + + writel(LCDC_RASTER_TIMING_0_HORLSB(timing.hactive.typ) | + LCDC_RASTER_TIMING_0_HORMSB(timing.hactive.typ) | + LCDC_RASTER_TIMING_0_HFPLSB(timing.hfront_porch.typ) | + LCDC_RASTER_TIMING_0_HBPLSB(timing.hback_porch.typ) | + LCDC_RASTER_TIMING_0_HSWLSB(timing.hsync_len.typ), + ®s->raster_timing0); + + writel(LCDC_RASTER_TIMING_1_VBP(timing.vback_porch.typ) | + LCDC_RASTER_TIMING_1_VFP(timing.vfront_porch.typ) | + LCDC_RASTER_TIMING_1_VSW(timing.vsync_len.typ) | + LCDC_RASTER_TIMING_1_VERLSB(timing.vactive.typ), + ®s->raster_timing1); + + reg = LCDC_RASTER_TIMING_2_ACB(info.ac_bias) | + LCDC_RASTER_TIMING_2_ACBI(info.ac_bias_intrpt) | + LCDC_RASTER_TIMING_2_HSWMSB(timing.hsync_len.typ) | + LCDC_RASTER_TIMING_2_VERMSB(timing.vactive.typ) | + LCDC_RASTER_TIMING_2_HBPMSB(timing.hback_porch.typ) | + LCDC_RASTER_TIMING_2_HFPMSB(timing.hfront_porch.typ); + + if (timing.flags & DISPLAY_FLAGS_VSYNC_LOW) + reg |= LCDC_RASTER_TIMING_2_VSYNC_INVERT; + + if (timing.flags & DISPLAY_FLAGS_HSYNC_LOW) + reg |= LCDC_RASTER_TIMING_2_HSYNC_INVERT; + + if (info.invert_pxl_clk) + reg |= LCDC_RASTER_TIMING_2_PXCLK_INVERT; + + if (info.sync_edge) + reg |= LCDC_RASTER_TIMING_2_HSVS_RISEFALL; + + if (info.sync_ctrl) + reg |= LCDC_RASTER_TIMING_2_HSVS_CONTROL; + + writel(reg, ®s->raster_timing2); + + reg = LCDC_RASTER_CTRL_PALMODE_RAWDATA | LCDC_RASTER_CTRL_TFT_MODE | + LCDC_RASTER_CTRL_ENABLE | LCDC_RASTER_CTRL_REQDLY(info.fdd); + + if (info.tft_alt_mode) + reg |= LCDC_RASTER_CTRL_TFT_ALT_ENABLE; + + if (info.bpp == 24) + reg |= LCDC_RASTER_CTRL_TFT_24BPP_MODE; + else if (info.bpp == 32) + reg |= LCDC_RASTER_CTRL_TFT_24BPP_MODE | + LCDC_RASTER_CTRL_TFT_24BPP_UNPACK; + + if (info.raster_order) + reg |= LCDC_RASTER_CTRL_DATA_ORDER; + + writel(reg, ®s->raster_ctrl); + + uc_priv->xsize = timing.hactive.typ; + uc_priv->ysize = timing.vactive.typ; + uc_priv->bpix = log_2_n_round_up(info.bpp); + + err = panel_enable_backlight(panel); + if (err) { + dev_err(dev, "failed to enable panel backlight\n"); + return err; + } + + return 0; +} + +static int tilcdc_of_to_plat(struct udevice *dev) +{ + struct tilcdc_priv *priv = dev_get_priv(dev); + + priv->regs = (struct tilcdc_regs *)dev_read_addr(dev); + if ((fdt_addr_t)priv->regs == FDT_ADDR_T_NONE) { + dev_err(dev, "failed to get base address\n"); + return -EINVAL; + } + + dev_dbg(dev, "LCD: base address=0x%x\n", (unsigned int)priv->regs); + return 0; +} + +static int tilcdc_bind(struct udevice *dev) +{ + struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev); + + uc_plat->size = ((LCDC_MAX_WIDTH * LCDC_MAX_HEIGHT * + (1 << LCDC_MAX_LOG2_BPP)) >> 3) + 0x20; + + dev_dbg(dev, "frame buffer size 0x%x\n", uc_plat->size); + return 0; +} + +static const struct udevice_id tilcdc_ids[] = { + {.compatible = "ti,am33xx-tilcdc"}, + {} +}; + +U_BOOT_DRIVER(tilcdc) = { + .name = "tilcdc", + .id = UCLASS_VIDEO, + .of_match = tilcdc_ids, + .bind = tilcdc_bind, + .ofdata_to_platdata = tilcdc_of_to_plat, + .probe = tilcdc_probe, + .remove = tilcdc_remove, + .priv_auto = sizeof(struct tilcdc_priv) +}; diff --git a/drivers/video/tilcdc.h b/drivers/video/tilcdc.h new file mode 100644 index 00000000000..2645921df65 --- /dev/null +++ b/drivers/video/tilcdc.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2020 Dario Binacchi + */ + +#ifndef _TILCDC_H +#define _TILCDC_H + +/** + * tilcdc_panel_info: Panel parameters + * + * @ac_bias: AC Bias Pin Frequency + * @ac_bias_intrpt: AC Bias Pin Transitions per Interrupt + * @dma_burst_sz: DMA burst size + * @bpp: Bits per pixel + * @fdd: FIFO DMA Request Delay + * @tft_alt_mode: TFT Alternative Signal Mapping (Only for active) + * @invert_pxl_clk: Invert pixel clock + * @sync_edge: Horizontal and Vertical Sync Edge: 0=rising 1=falling + * @sync_ctrl: Horizontal and Vertical Sync: Control: 0=ignore + * @raster_order: Raster Data Order Select: 1=Most-to-least 0=Least-to-most + * @fifo_th: DMA FIFO threshold + */ +struct tilcdc_panel_info { + u32 ac_bias; + u32 ac_bias_intrpt; + u32 dma_burst_sz; + u32 bpp; + u32 fdd; + bool tft_alt_mode; + bool invert_pxl_clk; + u32 sync_edge; + u32 sync_ctrl; + u32 raster_order; + u32 fifo_th; +}; + +#endif /* _TILCDC_H */ -- cgit v1.3.1 From 260cbc9af283e02bd184d0becd3466222f2e1db1 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Wed, 30 Dec 2020 00:16:31 +0100 Subject: video: omap: move drivers to 'ti' directory Add drivers/video/ti/ folder and move all TI's code in this folder for better maintenance. Signed-off-by: Dario Binacchi --- board/BuR/common/bur_common.h | 2 +- board/BuR/common/common.c | 2 +- drivers/video/Kconfig | 5 +- drivers/video/Makefile | 4 +- drivers/video/am335x-fb.c | 317 ------------------------------ drivers/video/am335x-fb.h | 71 ------- drivers/video/ti/Kconfig | 8 + drivers/video/ti/Makefile | 10 + drivers/video/ti/am335x-fb.c | 317 ++++++++++++++++++++++++++++++ drivers/video/ti/am335x-fb.h | 71 +++++++ drivers/video/ti/tilcdc-panel.c | 172 ++++++++++++++++ drivers/video/ti/tilcdc-panel.h | 14 ++ drivers/video/ti/tilcdc.c | 425 ++++++++++++++++++++++++++++++++++++++++ drivers/video/ti/tilcdc.h | 38 ++++ drivers/video/tilcdc-panel.c | 172 ---------------- drivers/video/tilcdc-panel.h | 14 -- drivers/video/tilcdc.c | 425 ---------------------------------------- drivers/video/tilcdc.h | 38 ---- 18 files changed, 1059 insertions(+), 1046 deletions(-) delete mode 100644 drivers/video/am335x-fb.c delete mode 100644 drivers/video/am335x-fb.h create mode 100644 drivers/video/ti/Kconfig create mode 100644 drivers/video/ti/Makefile create mode 100644 drivers/video/ti/am335x-fb.c create mode 100644 drivers/video/ti/am335x-fb.h create mode 100644 drivers/video/ti/tilcdc-panel.c create mode 100644 drivers/video/ti/tilcdc-panel.h create mode 100644 drivers/video/ti/tilcdc.c create mode 100644 drivers/video/ti/tilcdc.h delete mode 100644 drivers/video/tilcdc-panel.c delete mode 100644 drivers/video/tilcdc-panel.h delete mode 100644 drivers/video/tilcdc.c delete mode 100644 drivers/video/tilcdc.h (limited to 'drivers') diff --git a/board/BuR/common/bur_common.h b/board/BuR/common/bur_common.h index c64ebe93b0e..79c9af1466b 100644 --- a/board/BuR/common/bur_common.h +++ b/board/BuR/common/bur_common.h @@ -12,7 +12,7 @@ #define _BUR_COMMON_H_ #if !CONFIG_IS_ENABLED(DM_VIDEO) -#include <../../../drivers/video/am335x-fb.h> +#include <../../../drivers/video/ti/am335x-fb.h> int load_lcdtiming(struct am335x_lcdpanel *panel); #endif diff --git a/board/BuR/common/common.c b/board/BuR/common/common.c index 0a5104a48f9..f676d7baa6a 100644 --- a/board/BuR/common/common.c +++ b/board/BuR/common/common.c @@ -27,7 +27,7 @@ DECLARE_GLOBAL_DATA_PTR; #include #include #include -#include "../../../drivers/video/am335x-fb.h" +#include "../../../drivers/video/ti/am335x-fb.h" void lcdbacklight(int on) { diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 71363409f04..a3f8eeba5d8 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -546,10 +546,7 @@ config ATMEL_HLCD help HLCDC supports video output to an attached LCD panel. -config AM335X_LCD - bool "Enable AM335x video support" - help - Supports video output to an attached LCD panel. +source "drivers/video/ti/Kconfig" config LOGICORE_DP_TX bool "Enable Logicore DP TX driver" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 5f0f7b440c7..76e3914678d 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -16,15 +16,13 @@ obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o obj-$(CONFIG_DM_VIDEO) += video_bmp.o obj-$(CONFIG_PANEL) += panel-uclass.o obj-$(CONFIG_SIMPLE_PANEL) += simple_panel.o -obj-$(CONFIG_AM335X_LCD) += tilcdc.o tilcdc-panel.o -else -obj-$(CONFIG_AM335X_LCD) += am335x-fb.o endif obj-${CONFIG_EXYNOS_FB} += exynos/ obj-${CONFIG_VIDEO_ROCKCHIP} += rockchip/ obj-${CONFIG_VIDEO_STM32} += stm32/ obj-${CONFIG_VIDEO_TEGRA124} += tegra124/ +obj-y += ti/ obj-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o obj-$(CONFIG_ATMEL_HLCD) += atmel_hlcdfb.o diff --git a/drivers/video/am335x-fb.c b/drivers/video/am335x-fb.c deleted file mode 100644 index 5fa6f794ecc..00000000000 --- a/drivers/video/am335x-fb.c +++ /dev/null @@ -1,317 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (C) 2013-2018 Hannes Schmelzer - * B&R Industrial Automation GmbH - http://www.br-automation.com - * Copyright (C) 2020 Dario Binacchi - * - * minimal framebuffer driver for TI's AM335x SoC to be compatible with - * Wolfgang Denk's LCD-Framework (CONFIG_LCD, common/lcd.c) - * - * - supporting 16/24/32bit RGB/TFT raster Mode (not using palette) - * - sets up LCD controller as in 'am335x_lcdpanel' struct given - * - starts output DMA from gd->fb_base buffer - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "am335x-fb.h" - -#define LCDC_FMAX 200000000 - -/* LCD Control Register */ -#define LCDC_CTRL_CLK_DIVISOR_MASK GENMASK(15, 8) -#define LCDC_CTRL_RASTER_MODE BIT(0) -#define LCDC_CTRL_CLK_DIVISOR(x) (((x) & GENMASK(7, 0)) << 8) -/* LCD Clock Enable Register */ -#define LCDC_CLKC_ENABLE_CORECLKEN BIT(0) -#define LCDC_CLKC_ENABLE_LIDDCLKEN BIT(1) -#define LCDC_CLKC_ENABLE_DMACLKEN BIT(2) -/* LCD DMA Control Register */ -#define LCDC_DMA_CTRL_BURST_SIZE(x) (((x) & GENMASK(2, 0)) << 4) -#define LCDC_DMA_CTRL_BURST_1 0x0 -#define LCDC_DMA_CTRL_BURST_2 0x1 -#define LCDC_DMA_CTRL_BURST_4 0x2 -#define LCDC_DMA_CTRL_BURST_8 0x3 -#define LCDC_DMA_CTRL_BURST_16 0x4 -#define LCDC_DMA_CTRL_FIFO_TH(x) (((x) & GENMASK(2, 0)) << 8) -/* LCD Timing_0 Register */ -#define LCDC_RASTER_TIMING_0_HORMSB(x) ((((x) - 1) & BIT(10)) >> 7) -#define LCDC_RASTER_TIMING_0_HORLSB(x) (((((x) >> 4) - 1) & GENMASK(5, 0)) << 4) -#define LCDC_RASTER_TIMING_0_HSWLSB(x) ((((x) - 1) & GENMASK(5, 0)) << 10) -#define LCDC_RASTER_TIMING_0_HFPLSB(x) ((((x) - 1) & GENMASK(7, 0)) << 16) -#define LCDC_RASTER_TIMING_0_HBPLSB(x) ((((x) - 1) & GENMASK(7, 0)) << 24) -/* LCD Timing_1 Register */ -#define LCDC_RASTER_TIMING_1_VERLSB(x) (((x) - 1) & GENMASK(9, 0)) -#define LCDC_RASTER_TIMING_1_VSW(x) ((((x) - 1) & GENMASK(5, 0)) << 10) -#define LCDC_RASTER_TIMING_1_VFP(x) (((x) & GENMASK(7, 0)) << 16) -#define LCDC_RASTER_TIMING_1_VBP(x) (((x) & GENMASK(7, 0)) << 24) -/* LCD Timing_2 Register */ -#define LCDC_RASTER_TIMING_2_HFPMSB(x) ((((x) - 1) & GENMASK(9, 8)) >> 8) -#define LCDC_RASTER_TIMING_2_HBPMSB(x) ((((x) - 1) & GENMASK(9, 8)) >> 4) -#define LCDC_RASTER_TIMING_2_ACB(x) (((x) & GENMASK(7, 0)) << 8) -#define LCDC_RASTER_TIMING_2_ACBI(x) (((x) & GENMASK(3, 0)) << 16) -#define LCDC_RASTER_TIMING_2_VSYNC_INVERT BIT(20) -#define LCDC_RASTER_TIMING_2_HSYNC_INVERT BIT(21) -#define LCDC_RASTER_TIMING_2_PXCLK_INVERT BIT(22) -#define LCDC_RASTER_TIMING_2_DE_INVERT BIT(23) -#define LCDC_RASTER_TIMING_2_HSVS_RISEFALL BIT(24) -#define LCDC_RASTER_TIMING_2_HSVS_CONTROL BIT(25) -#define LCDC_RASTER_TIMING_2_VERMSB(x) ((((x) - 1) & BIT(10)) << 16) -#define LCDC_RASTER_TIMING_2_HSWMSB(x) ((((x) - 1) & GENMASK(9, 6)) << 21) -/* LCD Raster Ctrl Register */ -#define LCDC_RASTER_CTRL_ENABLE BIT(0) -#define LCDC_RASTER_CTRL_TFT_MODE BIT(7) -#define LCDC_RASTER_CTRL_DATA_ORDER BIT(8) -#define LCDC_RASTER_CTRL_REQDLY(x) (((x) & GENMASK(7, 0)) << 12) -#define LCDC_RASTER_CTRL_PALMODE_RAWDATA (0x02 << 20) -#define LCDC_RASTER_CTRL_TFT_ALT_ENABLE BIT(23) -#define LCDC_RASTER_CTRL_TFT_24BPP_MODE BIT(25) -#define LCDC_RASTER_CTRL_TFT_24BPP_UNPACK BIT(26) - -struct am335x_lcdhw { - unsigned int pid; /* 0x00 */ - unsigned int ctrl; /* 0x04 */ - unsigned int gap0; /* 0x08 */ - unsigned int lidd_ctrl; /* 0x0C */ - unsigned int lidd_cs0_conf; /* 0x10 */ - unsigned int lidd_cs0_addr; /* 0x14 */ - unsigned int lidd_cs0_data; /* 0x18 */ - unsigned int lidd_cs1_conf; /* 0x1C */ - unsigned int lidd_cs1_addr; /* 0x20 */ - unsigned int lidd_cs1_data; /* 0x24 */ - unsigned int raster_ctrl; /* 0x28 */ - unsigned int raster_timing0; /* 0x2C */ - unsigned int raster_timing1; /* 0x30 */ - unsigned int raster_timing2; /* 0x34 */ - unsigned int raster_subpanel; /* 0x38 */ - unsigned int raster_subpanel2; /* 0x3C */ - unsigned int lcddma_ctrl; /* 0x40 */ - unsigned int lcddma_fb0_base; /* 0x44 */ - unsigned int lcddma_fb0_ceiling; /* 0x48 */ - unsigned int lcddma_fb1_base; /* 0x4C */ - unsigned int lcddma_fb1_ceiling; /* 0x50 */ - unsigned int sysconfig; /* 0x54 */ - unsigned int irqstatus_raw; /* 0x58 */ - unsigned int irqstatus; /* 0x5C */ - unsigned int irqenable_set; /* 0x60 */ - unsigned int irqenable_clear; /* 0x64 */ - unsigned int gap1; /* 0x68 */ - unsigned int clkc_enable; /* 0x6C */ - unsigned int clkc_reset; /* 0x70 */ -}; - -DECLARE_GLOBAL_DATA_PTR; - -#if !defined(LCD_CNTL_BASE) -#error "hw-base address of LCD-Controller (LCD_CNTL_BASE) not defined!" -#endif - -/* Macro definitions */ -#define FBSIZE(x) (((x)->hactive * (x)->vactive * (x)->bpp) >> 3) - -#define LCDC_RASTER_TIMING_2_INVMASK(x) ((x) & GENMASK(25, 20)) - -static struct am335x_lcdhw *lcdhw = (void *)LCD_CNTL_BASE; - -int lcd_get_size(int *line_length) -{ - *line_length = (panel_info.vl_col * NBITS(panel_info.vl_bpix)) / 8; - return *line_length * panel_info.vl_row + 0x20; -} - -struct dpll_data { - unsigned long rounded_rate; - u16 rounded_m; - u8 rounded_n; - u8 rounded_div; -}; - -/** - * am335x_dpll_round_rate() - Round a target rate for an OMAP DPLL - * - * @dpll_data: struct dpll_data pointer for the DPLL - * @rate: New DPLL clock rate - * @return rounded rate and the computed m, n and div values in the dpll_data - * structure, or -ve error code. - */ -static ulong am335x_dpll_round_rate(struct dpll_data *dd, ulong rate) -{ - unsigned int m, n, d; - unsigned long rounded_rate; - int err, err_r; - - dd->rounded_rate = -EFAULT; - err = rate; - err_r = err; - - for (d = 2; err && d < 255; d++) { - for (m = 2; m < 2047; m++) { - if ((V_OSCK * m) < (rate * d)) - continue; - - n = (V_OSCK * m) / (rate * d); - if (n > 127) - break; - - if (((V_OSCK * m) / n) > LCDC_FMAX) - break; - - rounded_rate = (V_OSCK * m) / n / d; - err = abs(rounded_rate - rate); - if (err < err_r) { - err_r = err; - dd->rounded_rate = rounded_rate; - dd->rounded_m = m; - dd->rounded_n = n; - dd->rounded_div = d; - if (err == 0) - break; - } - } - } - - debug("DPLL display: best error %d Hz (M %d, N %d, DIV %d)\n", - err_r, dd->rounded_m, dd->rounded_n, dd->rounded_div); - - return dd->rounded_rate; -} - -/** - * am335x_fb_set_pixel_clk_rate() - Set pixel clock rate. - * - * @am335x_lcdhw: Base address of the LCD controller registers. - * @rate: New clock rate in Hz. - * @return new rate, or -ve error code. - */ -static ulong am335x_fb_set_pixel_clk_rate(struct am335x_lcdhw *regs, ulong rate) -{ - struct dpll_params dpll_disp = { 1, 0, 1, -1, -1, -1, -1 }; - struct dpll_data dd; - ulong round_rate; - u32 reg; - - round_rate = am335x_dpll_round_rate(&dd, rate); - if (IS_ERR_VALUE(round_rate)) - return round_rate; - - dpll_disp.m = dd.rounded_m; - dpll_disp.n = dd.rounded_n; - do_setup_dpll(&dpll_disp_regs, &dpll_disp); - - reg = readl(®s->ctrl) & ~LCDC_CTRL_CLK_DIVISOR_MASK; - reg |= LCDC_CTRL_CLK_DIVISOR(dd.rounded_div); - writel(reg, ®s->ctrl); - return round_rate; -} - -int am335xfb_init(struct am335x_lcdpanel *panel) -{ - u32 raster_ctrl = 0; - struct cm_dpll *const cmdpll = (struct cm_dpll *)CM_DPLL; - ulong rate; - u32 reg; - - if (gd->fb_base == 0) { - printf("ERROR: no valid fb_base stored in GLOBAL_DATA_PTR!\n"); - return -1; - } - if (panel == NULL) { - printf("ERROR: missing ptr to am335x_lcdpanel!\n"); - return -1; - } - - /* We can already set the bits for the raster_ctrl in this check */ - switch (panel->bpp) { - case 16: - break; - case 32: - raster_ctrl |= LCDC_RASTER_CTRL_TFT_24BPP_UNPACK; - /* fallthrough */ - case 24: - raster_ctrl |= LCDC_RASTER_CTRL_TFT_24BPP_MODE; - break; - default: - pr_err("am335x-fb: invalid bpp value: %d\n", panel->bpp); - return -1; - } - - /* check given clock-frequency */ - if (panel->pxl_clk > (LCDC_FMAX / 2)) { - pr_err("am335x-fb: requested pxl-clk: %d not supported!\n", - panel->pxl_clk); - return -1; - } - - debug("setting up LCD-Controller for %dx%dx%d (hfp=%d,hbp=%d,hsw=%d / ", - panel->hactive, panel->vactive, panel->bpp, - panel->hfp, panel->hbp, panel->hsw); - debug("vfp=%d,vbp=%d,vsw=%d / clk=%d)\n", - panel->vfp, panel->vfp, panel->vsw, panel->pxl_clk); - debug("using frambuffer at 0x%08x with size %d.\n", - (unsigned int)gd->fb_base, FBSIZE(panel)); - - rate = am335x_fb_set_pixel_clk_rate(lcdhw, panel->pxl_clk); - if (IS_ERR_VALUE(rate)) - return rate; - - /* clock source for LCDC from dispPLL M2 */ - writel(0x0, &cmdpll->clklcdcpixelclk); - - /* palette default entry */ - memset((void *)gd->fb_base, 0, 0x20); - *(unsigned int *)gd->fb_base = 0x4000; - /* point fb behind palette */ - gd->fb_base += 0x20; - - /* turn ON display through powercontrol function if accessible */ - if (panel->panel_power_ctrl != NULL) - panel->panel_power_ctrl(1); - - debug("am335x-fb: wait for stable power ...\n"); - mdelay(panel->pup_delay); - lcdhw->clkc_enable = LCDC_CLKC_ENABLE_CORECLKEN | - LCDC_CLKC_ENABLE_LIDDCLKEN | LCDC_CLKC_ENABLE_DMACLKEN; - lcdhw->raster_ctrl = 0; - - reg = lcdhw->ctrl & LCDC_CTRL_CLK_DIVISOR_MASK; - reg |= LCDC_CTRL_RASTER_MODE; - lcdhw->ctrl = reg; - - lcdhw->lcddma_fb0_base = gd->fb_base; - lcdhw->lcddma_fb0_ceiling = gd->fb_base + FBSIZE(panel); - lcdhw->lcddma_fb1_base = gd->fb_base; - lcdhw->lcddma_fb1_ceiling = gd->fb_base + FBSIZE(panel); - lcdhw->lcddma_ctrl = LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_16); - - lcdhw->raster_timing0 = LCDC_RASTER_TIMING_0_HORLSB(panel->hactive) | - LCDC_RASTER_TIMING_0_HORMSB(panel->hactive) | - LCDC_RASTER_TIMING_0_HFPLSB(panel->hfp) | - LCDC_RASTER_TIMING_0_HBPLSB(panel->hbp) | - LCDC_RASTER_TIMING_0_HSWLSB(panel->hsw); - lcdhw->raster_timing1 = LCDC_RASTER_TIMING_1_VBP(panel->vbp) | - LCDC_RASTER_TIMING_1_VFP(panel->vfp) | - LCDC_RASTER_TIMING_1_VSW(panel->vsw) | - LCDC_RASTER_TIMING_1_VERLSB(panel->vactive); - lcdhw->raster_timing2 = LCDC_RASTER_TIMING_2_HSWMSB(panel->hsw) | - LCDC_RASTER_TIMING_2_VERMSB(panel->vactive) | - LCDC_RASTER_TIMING_2_INVMASK(panel->pol) | - LCDC_RASTER_TIMING_2_HBPMSB(panel->hbp) | - LCDC_RASTER_TIMING_2_HFPMSB(panel->hfp) | - 0x0000FF00; /* clk cycles for ac-bias */ - lcdhw->raster_ctrl = raster_ctrl | - LCDC_RASTER_CTRL_PALMODE_RAWDATA | - LCDC_RASTER_CTRL_TFT_MODE | - LCDC_RASTER_CTRL_ENABLE; - - debug("am335x-fb: waiting picture to be stable.\n."); - mdelay(panel->pon_delay); - - return 0; -} diff --git a/drivers/video/am335x-fb.h b/drivers/video/am335x-fb.h deleted file mode 100644 index ad9b015e090..00000000000 --- a/drivers/video/am335x-fb.h +++ /dev/null @@ -1,71 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Copyright (C) 2013-2018 Hannes Schmelzer - - * B&R Industrial Automation GmbH - http://www.br-automation.com - */ - -#ifndef AM335X_FB_H -#define AM335X_FB_H - -#define HSVS_CONTROL BIT(25) /* - * 0 = lcd_lp and lcd_fp are driven on - * opposite edges of pixel clock than - * the lcd_pixel_o - * 1 = lcd_lp and lcd_fp are driven - * according to bit 24 Note that this - * bit MUST be set to '0' for Passive - * Matrix displays the edge timing is - * fixed - */ -#define HSVS_RISEFALL BIT(24) /* - * 0 = lcd_lp and lcd_fp are driven on - * the rising edge of pixel clock (bit - * 25 must be set to 1) - * 1 = lcd_lp and lcd_fp are driven on - * the falling edge of pixel clock (bit - * 25 must be set to 1) - */ -#define DE_INVERT BIT(23) /* - * 0 = DE is low-active - * 1 = DE is high-active - */ -#define PXCLK_INVERT BIT(22) /* - * 0 = pix-clk is high-active - * 1 = pic-clk is low-active - */ -#define HSYNC_INVERT BIT(21) /* - * 0 = HSYNC is active high - * 1 = HSYNC is avtive low - */ -#define VSYNC_INVERT BIT(20) /* - * 0 = VSYNC is active high - * 1 = VSYNC is active low - */ - -struct am335x_lcdpanel { - unsigned int hactive; /* Horizontal active area */ - unsigned int vactive; /* Vertical active area */ - unsigned int bpp; /* bits per pixel */ - unsigned int hfp; /* Horizontal front porch */ - unsigned int hbp; /* Horizontal back porch */ - unsigned int hsw; /* Horizontal Sync Pulse Width */ - unsigned int vfp; /* Vertical front porch */ - unsigned int vbp; /* Vertical back porch */ - unsigned int vsw; /* Vertical Sync Pulse Width */ - unsigned int pxl_clk; /* Pixel clock */ - unsigned int pol; /* polarity of sync, clock signals */ - unsigned int pup_delay; /* - * time in ms after power on to - * initialization of lcd-controller - * (VCC ramp up time) - */ - unsigned int pon_delay; /* - * time in ms after initialization of - * lcd-controller (pic stabilization) - */ - void (*panel_power_ctrl)(int); /* fp for power on/off display */ -}; - -int am335xfb_init(struct am335x_lcdpanel *panel); - -#endif /* AM335X_FB_H */ diff --git a/drivers/video/ti/Kconfig b/drivers/video/ti/Kconfig new file mode 100644 index 00000000000..3081e9e8c09 --- /dev/null +++ b/drivers/video/ti/Kconfig @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (C) 2020 Dario Binacchi +# +config AM335X_LCD + bool "Enable AM335x video support" + help + Supports video output to an attached LCD panel. diff --git a/drivers/video/ti/Makefile b/drivers/video/ti/Makefile new file mode 100644 index 00000000000..ddddd592167 --- /dev/null +++ b/drivers/video/ti/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (C) 2020 Dario Binacchi +# + +ifdef CONFIG_DM_VIDEO +obj-$(CONFIG_AM335X_LCD) += tilcdc.o tilcdc-panel.o +else +obj-$(CONFIG_AM335X_LCD) += am335x-fb.o +endif diff --git a/drivers/video/ti/am335x-fb.c b/drivers/video/ti/am335x-fb.c new file mode 100644 index 00000000000..5fa6f794ecc --- /dev/null +++ b/drivers/video/ti/am335x-fb.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2013-2018 Hannes Schmelzer + * B&R Industrial Automation GmbH - http://www.br-automation.com + * Copyright (C) 2020 Dario Binacchi + * + * minimal framebuffer driver for TI's AM335x SoC to be compatible with + * Wolfgang Denk's LCD-Framework (CONFIG_LCD, common/lcd.c) + * + * - supporting 16/24/32bit RGB/TFT raster Mode (not using palette) + * - sets up LCD controller as in 'am335x_lcdpanel' struct given + * - starts output DMA from gd->fb_base buffer + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "am335x-fb.h" + +#define LCDC_FMAX 200000000 + +/* LCD Control Register */ +#define LCDC_CTRL_CLK_DIVISOR_MASK GENMASK(15, 8) +#define LCDC_CTRL_RASTER_MODE BIT(0) +#define LCDC_CTRL_CLK_DIVISOR(x) (((x) & GENMASK(7, 0)) << 8) +/* LCD Clock Enable Register */ +#define LCDC_CLKC_ENABLE_CORECLKEN BIT(0) +#define LCDC_CLKC_ENABLE_LIDDCLKEN BIT(1) +#define LCDC_CLKC_ENABLE_DMACLKEN BIT(2) +/* LCD DMA Control Register */ +#define LCDC_DMA_CTRL_BURST_SIZE(x) (((x) & GENMASK(2, 0)) << 4) +#define LCDC_DMA_CTRL_BURST_1 0x0 +#define LCDC_DMA_CTRL_BURST_2 0x1 +#define LCDC_DMA_CTRL_BURST_4 0x2 +#define LCDC_DMA_CTRL_BURST_8 0x3 +#define LCDC_DMA_CTRL_BURST_16 0x4 +#define LCDC_DMA_CTRL_FIFO_TH(x) (((x) & GENMASK(2, 0)) << 8) +/* LCD Timing_0 Register */ +#define LCDC_RASTER_TIMING_0_HORMSB(x) ((((x) - 1) & BIT(10)) >> 7) +#define LCDC_RASTER_TIMING_0_HORLSB(x) (((((x) >> 4) - 1) & GENMASK(5, 0)) << 4) +#define LCDC_RASTER_TIMING_0_HSWLSB(x) ((((x) - 1) & GENMASK(5, 0)) << 10) +#define LCDC_RASTER_TIMING_0_HFPLSB(x) ((((x) - 1) & GENMASK(7, 0)) << 16) +#define LCDC_RASTER_TIMING_0_HBPLSB(x) ((((x) - 1) & GENMASK(7, 0)) << 24) +/* LCD Timing_1 Register */ +#define LCDC_RASTER_TIMING_1_VERLSB(x) (((x) - 1) & GENMASK(9, 0)) +#define LCDC_RASTER_TIMING_1_VSW(x) ((((x) - 1) & GENMASK(5, 0)) << 10) +#define LCDC_RASTER_TIMING_1_VFP(x) (((x) & GENMASK(7, 0)) << 16) +#define LCDC_RASTER_TIMING_1_VBP(x) (((x) & GENMASK(7, 0)) << 24) +/* LCD Timing_2 Register */ +#define LCDC_RASTER_TIMING_2_HFPMSB(x) ((((x) - 1) & GENMASK(9, 8)) >> 8) +#define LCDC_RASTER_TIMING_2_HBPMSB(x) ((((x) - 1) & GENMASK(9, 8)) >> 4) +#define LCDC_RASTER_TIMING_2_ACB(x) (((x) & GENMASK(7, 0)) << 8) +#define LCDC_RASTER_TIMING_2_ACBI(x) (((x) & GENMASK(3, 0)) << 16) +#define LCDC_RASTER_TIMING_2_VSYNC_INVERT BIT(20) +#define LCDC_RASTER_TIMING_2_HSYNC_INVERT BIT(21) +#define LCDC_RASTER_TIMING_2_PXCLK_INVERT BIT(22) +#define LCDC_RASTER_TIMING_2_DE_INVERT BIT(23) +#define LCDC_RASTER_TIMING_2_HSVS_RISEFALL BIT(24) +#define LCDC_RASTER_TIMING_2_HSVS_CONTROL BIT(25) +#define LCDC_RASTER_TIMING_2_VERMSB(x) ((((x) - 1) & BIT(10)) << 16) +#define LCDC_RASTER_TIMING_2_HSWMSB(x) ((((x) - 1) & GENMASK(9, 6)) << 21) +/* LCD Raster Ctrl Register */ +#define LCDC_RASTER_CTRL_ENABLE BIT(0) +#define LCDC_RASTER_CTRL_TFT_MODE BIT(7) +#define LCDC_RASTER_CTRL_DATA_ORDER BIT(8) +#define LCDC_RASTER_CTRL_REQDLY(x) (((x) & GENMASK(7, 0)) << 12) +#define LCDC_RASTER_CTRL_PALMODE_RAWDATA (0x02 << 20) +#define LCDC_RASTER_CTRL_TFT_ALT_ENABLE BIT(23) +#define LCDC_RASTER_CTRL_TFT_24BPP_MODE BIT(25) +#define LCDC_RASTER_CTRL_TFT_24BPP_UNPACK BIT(26) + +struct am335x_lcdhw { + unsigned int pid; /* 0x00 */ + unsigned int ctrl; /* 0x04 */ + unsigned int gap0; /* 0x08 */ + unsigned int lidd_ctrl; /* 0x0C */ + unsigned int lidd_cs0_conf; /* 0x10 */ + unsigned int lidd_cs0_addr; /* 0x14 */ + unsigned int lidd_cs0_data; /* 0x18 */ + unsigned int lidd_cs1_conf; /* 0x1C */ + unsigned int lidd_cs1_addr; /* 0x20 */ + unsigned int lidd_cs1_data; /* 0x24 */ + unsigned int raster_ctrl; /* 0x28 */ + unsigned int raster_timing0; /* 0x2C */ + unsigned int raster_timing1; /* 0x30 */ + unsigned int raster_timing2; /* 0x34 */ + unsigned int raster_subpanel; /* 0x38 */ + unsigned int raster_subpanel2; /* 0x3C */ + unsigned int lcddma_ctrl; /* 0x40 */ + unsigned int lcddma_fb0_base; /* 0x44 */ + unsigned int lcddma_fb0_ceiling; /* 0x48 */ + unsigned int lcddma_fb1_base; /* 0x4C */ + unsigned int lcddma_fb1_ceiling; /* 0x50 */ + unsigned int sysconfig; /* 0x54 */ + unsigned int irqstatus_raw; /* 0x58 */ + unsigned int irqstatus; /* 0x5C */ + unsigned int irqenable_set; /* 0x60 */ + unsigned int irqenable_clear; /* 0x64 */ + unsigned int gap1; /* 0x68 */ + unsigned int clkc_enable; /* 0x6C */ + unsigned int clkc_reset; /* 0x70 */ +}; + +DECLARE_GLOBAL_DATA_PTR; + +#if !defined(LCD_CNTL_BASE) +#error "hw-base address of LCD-Controller (LCD_CNTL_BASE) not defined!" +#endif + +/* Macro definitions */ +#define FBSIZE(x) (((x)->hactive * (x)->vactive * (x)->bpp) >> 3) + +#define LCDC_RASTER_TIMING_2_INVMASK(x) ((x) & GENMASK(25, 20)) + +static struct am335x_lcdhw *lcdhw = (void *)LCD_CNTL_BASE; + +int lcd_get_size(int *line_length) +{ + *line_length = (panel_info.vl_col * NBITS(panel_info.vl_bpix)) / 8; + return *line_length * panel_info.vl_row + 0x20; +} + +struct dpll_data { + unsigned long rounded_rate; + u16 rounded_m; + u8 rounded_n; + u8 rounded_div; +}; + +/** + * am335x_dpll_round_rate() - Round a target rate for an OMAP DPLL + * + * @dpll_data: struct dpll_data pointer for the DPLL + * @rate: New DPLL clock rate + * @return rounded rate and the computed m, n and div values in the dpll_data + * structure, or -ve error code. + */ +static ulong am335x_dpll_round_rate(struct dpll_data *dd, ulong rate) +{ + unsigned int m, n, d; + unsigned long rounded_rate; + int err, err_r; + + dd->rounded_rate = -EFAULT; + err = rate; + err_r = err; + + for (d = 2; err && d < 255; d++) { + for (m = 2; m < 2047; m++) { + if ((V_OSCK * m) < (rate * d)) + continue; + + n = (V_OSCK * m) / (rate * d); + if (n > 127) + break; + + if (((V_OSCK * m) / n) > LCDC_FMAX) + break; + + rounded_rate = (V_OSCK * m) / n / d; + err = abs(rounded_rate - rate); + if (err < err_r) { + err_r = err; + dd->rounded_rate = rounded_rate; + dd->rounded_m = m; + dd->rounded_n = n; + dd->rounded_div = d; + if (err == 0) + break; + } + } + } + + debug("DPLL display: best error %d Hz (M %d, N %d, DIV %d)\n", + err_r, dd->rounded_m, dd->rounded_n, dd->rounded_div); + + return dd->rounded_rate; +} + +/** + * am335x_fb_set_pixel_clk_rate() - Set pixel clock rate. + * + * @am335x_lcdhw: Base address of the LCD controller registers. + * @rate: New clock rate in Hz. + * @return new rate, or -ve error code. + */ +static ulong am335x_fb_set_pixel_clk_rate(struct am335x_lcdhw *regs, ulong rate) +{ + struct dpll_params dpll_disp = { 1, 0, 1, -1, -1, -1, -1 }; + struct dpll_data dd; + ulong round_rate; + u32 reg; + + round_rate = am335x_dpll_round_rate(&dd, rate); + if (IS_ERR_VALUE(round_rate)) + return round_rate; + + dpll_disp.m = dd.rounded_m; + dpll_disp.n = dd.rounded_n; + do_setup_dpll(&dpll_disp_regs, &dpll_disp); + + reg = readl(®s->ctrl) & ~LCDC_CTRL_CLK_DIVISOR_MASK; + reg |= LCDC_CTRL_CLK_DIVISOR(dd.rounded_div); + writel(reg, ®s->ctrl); + return round_rate; +} + +int am335xfb_init(struct am335x_lcdpanel *panel) +{ + u32 raster_ctrl = 0; + struct cm_dpll *const cmdpll = (struct cm_dpll *)CM_DPLL; + ulong rate; + u32 reg; + + if (gd->fb_base == 0) { + printf("ERROR: no valid fb_base stored in GLOBAL_DATA_PTR!\n"); + return -1; + } + if (panel == NULL) { + printf("ERROR: missing ptr to am335x_lcdpanel!\n"); + return -1; + } + + /* We can already set the bits for the raster_ctrl in this check */ + switch (panel->bpp) { + case 16: + break; + case 32: + raster_ctrl |= LCDC_RASTER_CTRL_TFT_24BPP_UNPACK; + /* fallthrough */ + case 24: + raster_ctrl |= LCDC_RASTER_CTRL_TFT_24BPP_MODE; + break; + default: + pr_err("am335x-fb: invalid bpp value: %d\n", panel->bpp); + return -1; + } + + /* check given clock-frequency */ + if (panel->pxl_clk > (LCDC_FMAX / 2)) { + pr_err("am335x-fb: requested pxl-clk: %d not supported!\n", + panel->pxl_clk); + return -1; + } + + debug("setting up LCD-Controller for %dx%dx%d (hfp=%d,hbp=%d,hsw=%d / ", + panel->hactive, panel->vactive, panel->bpp, + panel->hfp, panel->hbp, panel->hsw); + debug("vfp=%d,vbp=%d,vsw=%d / clk=%d)\n", + panel->vfp, panel->vfp, panel->vsw, panel->pxl_clk); + debug("using frambuffer at 0x%08x with size %d.\n", + (unsigned int)gd->fb_base, FBSIZE(panel)); + + rate = am335x_fb_set_pixel_clk_rate(lcdhw, panel->pxl_clk); + if (IS_ERR_VALUE(rate)) + return rate; + + /* clock source for LCDC from dispPLL M2 */ + writel(0x0, &cmdpll->clklcdcpixelclk); + + /* palette default entry */ + memset((void *)gd->fb_base, 0, 0x20); + *(unsigned int *)gd->fb_base = 0x4000; + /* point fb behind palette */ + gd->fb_base += 0x20; + + /* turn ON display through powercontrol function if accessible */ + if (panel->panel_power_ctrl != NULL) + panel->panel_power_ctrl(1); + + debug("am335x-fb: wait for stable power ...\n"); + mdelay(panel->pup_delay); + lcdhw->clkc_enable = LCDC_CLKC_ENABLE_CORECLKEN | + LCDC_CLKC_ENABLE_LIDDCLKEN | LCDC_CLKC_ENABLE_DMACLKEN; + lcdhw->raster_ctrl = 0; + + reg = lcdhw->ctrl & LCDC_CTRL_CLK_DIVISOR_MASK; + reg |= LCDC_CTRL_RASTER_MODE; + lcdhw->ctrl = reg; + + lcdhw->lcddma_fb0_base = gd->fb_base; + lcdhw->lcddma_fb0_ceiling = gd->fb_base + FBSIZE(panel); + lcdhw->lcddma_fb1_base = gd->fb_base; + lcdhw->lcddma_fb1_ceiling = gd->fb_base + FBSIZE(panel); + lcdhw->lcddma_ctrl = LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_16); + + lcdhw->raster_timing0 = LCDC_RASTER_TIMING_0_HORLSB(panel->hactive) | + LCDC_RASTER_TIMING_0_HORMSB(panel->hactive) | + LCDC_RASTER_TIMING_0_HFPLSB(panel->hfp) | + LCDC_RASTER_TIMING_0_HBPLSB(panel->hbp) | + LCDC_RASTER_TIMING_0_HSWLSB(panel->hsw); + lcdhw->raster_timing1 = LCDC_RASTER_TIMING_1_VBP(panel->vbp) | + LCDC_RASTER_TIMING_1_VFP(panel->vfp) | + LCDC_RASTER_TIMING_1_VSW(panel->vsw) | + LCDC_RASTER_TIMING_1_VERLSB(panel->vactive); + lcdhw->raster_timing2 = LCDC_RASTER_TIMING_2_HSWMSB(panel->hsw) | + LCDC_RASTER_TIMING_2_VERMSB(panel->vactive) | + LCDC_RASTER_TIMING_2_INVMASK(panel->pol) | + LCDC_RASTER_TIMING_2_HBPMSB(panel->hbp) | + LCDC_RASTER_TIMING_2_HFPMSB(panel->hfp) | + 0x0000FF00; /* clk cycles for ac-bias */ + lcdhw->raster_ctrl = raster_ctrl | + LCDC_RASTER_CTRL_PALMODE_RAWDATA | + LCDC_RASTER_CTRL_TFT_MODE | + LCDC_RASTER_CTRL_ENABLE; + + debug("am335x-fb: waiting picture to be stable.\n."); + mdelay(panel->pon_delay); + + return 0; +} diff --git a/drivers/video/ti/am335x-fb.h b/drivers/video/ti/am335x-fb.h new file mode 100644 index 00000000000..ad9b015e090 --- /dev/null +++ b/drivers/video/ti/am335x-fb.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2013-2018 Hannes Schmelzer - + * B&R Industrial Automation GmbH - http://www.br-automation.com + */ + +#ifndef AM335X_FB_H +#define AM335X_FB_H + +#define HSVS_CONTROL BIT(25) /* + * 0 = lcd_lp and lcd_fp are driven on + * opposite edges of pixel clock than + * the lcd_pixel_o + * 1 = lcd_lp and lcd_fp are driven + * according to bit 24 Note that this + * bit MUST be set to '0' for Passive + * Matrix displays the edge timing is + * fixed + */ +#define HSVS_RISEFALL BIT(24) /* + * 0 = lcd_lp and lcd_fp are driven on + * the rising edge of pixel clock (bit + * 25 must be set to 1) + * 1 = lcd_lp and lcd_fp are driven on + * the falling edge of pixel clock (bit + * 25 must be set to 1) + */ +#define DE_INVERT BIT(23) /* + * 0 = DE is low-active + * 1 = DE is high-active + */ +#define PXCLK_INVERT BIT(22) /* + * 0 = pix-clk is high-active + * 1 = pic-clk is low-active + */ +#define HSYNC_INVERT BIT(21) /* + * 0 = HSYNC is active high + * 1 = HSYNC is avtive low + */ +#define VSYNC_INVERT BIT(20) /* + * 0 = VSYNC is active high + * 1 = VSYNC is active low + */ + +struct am335x_lcdpanel { + unsigned int hactive; /* Horizontal active area */ + unsigned int vactive; /* Vertical active area */ + unsigned int bpp; /* bits per pixel */ + unsigned int hfp; /* Horizontal front porch */ + unsigned int hbp; /* Horizontal back porch */ + unsigned int hsw; /* Horizontal Sync Pulse Width */ + unsigned int vfp; /* Vertical front porch */ + unsigned int vbp; /* Vertical back porch */ + unsigned int vsw; /* Vertical Sync Pulse Width */ + unsigned int pxl_clk; /* Pixel clock */ + unsigned int pol; /* polarity of sync, clock signals */ + unsigned int pup_delay; /* + * time in ms after power on to + * initialization of lcd-controller + * (VCC ramp up time) + */ + unsigned int pon_delay; /* + * time in ms after initialization of + * lcd-controller (pic stabilization) + */ + void (*panel_power_ctrl)(int); /* fp for power on/off display */ +}; + +int am335xfb_init(struct am335x_lcdpanel *panel); + +#endif /* AM335X_FB_H */ diff --git a/drivers/video/ti/tilcdc-panel.c b/drivers/video/ti/tilcdc-panel.c new file mode 100644 index 00000000000..b90dfae4ae9 --- /dev/null +++ b/drivers/video/ti/tilcdc-panel.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * OMAP panel support + * + * Copyright (C) 2020 Dario Binacchi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tilcdc.h" + +struct tilcdc_panel_priv { + struct tilcdc_panel_info info; + struct display_timing timing; + struct udevice *backlight; + struct gpio_desc enable; +}; + +static int tilcdc_panel_enable_backlight(struct udevice *dev) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + + if (dm_gpio_is_valid(&priv->enable)) + dm_gpio_set_value(&priv->enable, 1); + + if (priv->backlight) + return backlight_enable(priv->backlight); + + return 0; +} + +static int tilcdc_panel_set_backlight(struct udevice *dev, int percent) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + + if (dm_gpio_is_valid(&priv->enable)) + dm_gpio_set_value(&priv->enable, 1); + + if (priv->backlight) + return backlight_set_brightness(priv->backlight, percent); + + return 0; +} + +int tilcdc_panel_get_display_info(struct udevice *dev, + struct tilcdc_panel_info *info) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + + memcpy(info, &priv->info, sizeof(*info)); + return 0; +} + +static int tilcdc_panel_get_display_timing(struct udevice *dev, + struct display_timing *timing) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + + memcpy(timing, &priv->timing, sizeof(*timing)); + return 0; +} + +static int tilcdc_panel_remove(struct udevice *dev) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + + if (dm_gpio_is_valid(&priv->enable)) + dm_gpio_free(dev, &priv->enable); + + return 0; +} + +static int tilcdc_panel_probe(struct udevice *dev) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + int err; + + err = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev, + "backlight", &priv->backlight); + if (err) + dev_warn(dev, "failed to get backlight\n"); + + err = gpio_request_by_name(dev, "enable-gpios", 0, &priv->enable, + GPIOD_IS_OUT); + if (err) { + dev_warn(dev, "failed to get enable GPIO\n"); + if (err != -ENOENT) + return err; + } + + return 0; +} + +static int tilcdc_panel_of_to_plat(struct udevice *dev) +{ + struct tilcdc_panel_priv *priv = dev_get_priv(dev); + ofnode node; + int err; + + err = ofnode_decode_display_timing(dev_ofnode(dev), 0, &priv->timing); + if (err) { + dev_err(dev, "failed to get display timing\n"); + return err; + } + + node = dev_read_subnode(dev, "panel-info"); + if (!ofnode_valid(node)) { + dev_err(dev, "missing 'panel-info' node\n"); + return -ENXIO; + } + + err |= ofnode_read_u32(node, "ac-bias", &priv->info.ac_bias); + err |= ofnode_read_u32(node, "ac-bias-intrpt", + &priv->info.ac_bias_intrpt); + err |= ofnode_read_u32(node, "dma-burst-sz", &priv->info.dma_burst_sz); + err |= ofnode_read_u32(node, "bpp", &priv->info.bpp); + err |= ofnode_read_u32(node, "fdd", &priv->info.fdd); + err |= ofnode_read_u32(node, "sync-edge", &priv->info.sync_edge); + err |= ofnode_read_u32(node, "sync-ctrl", &priv->info.sync_ctrl); + err |= ofnode_read_u32(node, "raster-order", &priv->info.raster_order); + err |= ofnode_read_u32(node, "fifo-th", &priv->info.fifo_th); + if (err) { + dev_err(dev, "failed to get panel info\n"); + return err; + } + + /* optional */ + priv->info.tft_alt_mode = ofnode_read_bool(node, "tft-alt-mode"); + priv->info.invert_pxl_clk = ofnode_read_bool(node, "invert-pxl-clk"); + + dev_dbg(dev, "LCD: %dx%d, bpp=%d, clk=%d Hz\n", + priv->timing.hactive.typ, priv->timing.vactive.typ, + priv->info.bpp, priv->timing.pixelclock.typ); + dev_dbg(dev, " hbp=%d, hfp=%d, hsw=%d\n", + priv->timing.hback_porch.typ, priv->timing.hfront_porch.typ, + priv->timing.hsync_len.typ); + dev_dbg(dev, " vbp=%d, vfp=%d, vsw=%d\n", + priv->timing.vback_porch.typ, priv->timing.vfront_porch.typ, + priv->timing.vsync_len.typ); + + return 0; +} + +static const struct panel_ops tilcdc_panel_ops = { + .enable_backlight = tilcdc_panel_enable_backlight, + .set_backlight = tilcdc_panel_set_backlight, + .get_display_timing = tilcdc_panel_get_display_timing, +}; + +static const struct udevice_id tilcdc_panel_ids[] = { + {.compatible = "ti,tilcdc,panel"}, + {} +}; + +U_BOOT_DRIVER(tilcdc_panel) = { + .name = "tilcdc_panel", + .id = UCLASS_PANEL, + .of_match = tilcdc_panel_ids, + .ops = &tilcdc_panel_ops, + .ofdata_to_platdata = tilcdc_panel_of_to_plat, + .probe = tilcdc_panel_probe, + .remove = tilcdc_panel_remove, + .priv_auto = sizeof(struct tilcdc_panel_priv), +}; diff --git a/drivers/video/ti/tilcdc-panel.h b/drivers/video/ti/tilcdc-panel.h new file mode 100644 index 00000000000..6bcfbf8a8b4 --- /dev/null +++ b/drivers/video/ti/tilcdc-panel.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2020 Dario Binacchi + */ + +#ifndef _TILCDC_PANEL_H +#define _TILCDC_PANEL_H + +#include "tilcdc.h" + +int tilcdc_panel_get_display_info(struct udevice *dev, + struct tilcdc_panel_info *info); + +#endif /* _TILCDC_PANEL_H */ diff --git a/drivers/video/ti/tilcdc.c b/drivers/video/ti/tilcdc.c new file mode 100644 index 00000000000..d13cc11801d --- /dev/null +++ b/drivers/video/ti/tilcdc.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2020 Dario Binacchi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tilcdc.h" +#include "tilcdc-panel.h" + +#define LCDC_FMAX 200000000 + +/* LCD Control Register */ +#define LCDC_CTRL_CLK_DIVISOR_MASK GENMASK(15, 8) +#define LCDC_CTRL_RASTER_MODE BIT(0) +#define LCDC_CTRL_CLK_DIVISOR(x) (((x) & GENMASK(7, 0)) << 8) +/* LCD Clock Enable Register */ +#define LCDC_CLKC_ENABLE_CORECLKEN BIT(0) +#define LCDC_CLKC_ENABLE_LIDDCLKEN BIT(1) +#define LCDC_CLKC_ENABLE_DMACLKEN BIT(2) +/* LCD DMA Control Register */ +#define LCDC_DMA_CTRL_BURST_SIZE(x) (((x) & GENMASK(2, 0)) << 4) +#define LCDC_DMA_CTRL_BURST_1 0x0 +#define LCDC_DMA_CTRL_BURST_2 0x1 +#define LCDC_DMA_CTRL_BURST_4 0x2 +#define LCDC_DMA_CTRL_BURST_8 0x3 +#define LCDC_DMA_CTRL_BURST_16 0x4 +#define LCDC_DMA_CTRL_FIFO_TH(x) (((x) & GENMASK(2, 0)) << 8) +/* LCD Timing_0 Register */ +#define LCDC_RASTER_TIMING_0_HORMSB(x) ((((x) - 1) & BIT(10)) >> 7) +#define LCDC_RASTER_TIMING_0_HORLSB(x) (((((x) >> 4) - 1) & GENMASK(5, 0)) << 4) +#define LCDC_RASTER_TIMING_0_HSWLSB(x) ((((x) - 1) & GENMASK(5, 0)) << 10) +#define LCDC_RASTER_TIMING_0_HFPLSB(x) ((((x) - 1) & GENMASK(7, 0)) << 16) +#define LCDC_RASTER_TIMING_0_HBPLSB(x) ((((x) - 1) & GENMASK(7, 0)) << 24) +/* LCD Timing_1 Register */ +#define LCDC_RASTER_TIMING_1_VERLSB(x) (((x) - 1) & GENMASK(9, 0)) +#define LCDC_RASTER_TIMING_1_VSW(x) ((((x) - 1) & GENMASK(5, 0)) << 10) +#define LCDC_RASTER_TIMING_1_VFP(x) (((x) & GENMASK(7, 0)) << 16) +#define LCDC_RASTER_TIMING_1_VBP(x) (((x) & GENMASK(7, 0)) << 24) +/* LCD Timing_2 Register */ +#define LCDC_RASTER_TIMING_2_HFPMSB(x) ((((x) - 1) & GENMASK(9, 8)) >> 8) +#define LCDC_RASTER_TIMING_2_HBPMSB(x) ((((x) - 1) & GENMASK(9, 8)) >> 4) +#define LCDC_RASTER_TIMING_2_ACB(x) (((x) & GENMASK(7, 0)) << 8) +#define LCDC_RASTER_TIMING_2_ACBI(x) (((x) & GENMASK(3, 0)) << 16) +#define LCDC_RASTER_TIMING_2_VSYNC_INVERT BIT(20) +#define LCDC_RASTER_TIMING_2_HSYNC_INVERT BIT(21) +#define LCDC_RASTER_TIMING_2_PXCLK_INVERT BIT(22) +#define LCDC_RASTER_TIMING_2_DE_INVERT BIT(23) +#define LCDC_RASTER_TIMING_2_HSVS_RISEFALL BIT(24) +#define LCDC_RASTER_TIMING_2_HSVS_CONTROL BIT(25) +#define LCDC_RASTER_TIMING_2_VERMSB(x) ((((x) - 1) & BIT(10)) << 16) +#define LCDC_RASTER_TIMING_2_HSWMSB(x) ((((x) - 1) & GENMASK(9, 6)) << 21) +/* LCD Raster Ctrl Register */ +#define LCDC_RASTER_CTRL_ENABLE BIT(0) +#define LCDC_RASTER_CTRL_TFT_MODE BIT(7) +#define LCDC_RASTER_CTRL_DATA_ORDER BIT(8) +#define LCDC_RASTER_CTRL_REQDLY(x) (((x) & GENMASK(7, 0)) << 12) +#define LCDC_RASTER_CTRL_PALMODE_RAWDATA (0x02 << 20) +#define LCDC_RASTER_CTRL_TFT_ALT_ENABLE BIT(23) +#define LCDC_RASTER_CTRL_TFT_24BPP_MODE BIT(25) +#define LCDC_RASTER_CTRL_TFT_24BPP_UNPACK BIT(26) + +enum { + LCDC_MAX_WIDTH = 2048, + LCDC_MAX_HEIGHT = 2048, + LCDC_MAX_LOG2_BPP = VIDEO_BPP32, +}; + +struct tilcdc_regs { + u32 pid; + u32 ctrl; + u32 gap0; + u32 lidd_ctrl; + u32 lidd_cs0_conf; + u32 lidd_cs0_addr; + u32 lidd_cs0_data; + u32 lidd_cs1_conf; + u32 lidd_cs1_addr; + u32 lidd_cs1_data; + u32 raster_ctrl; + u32 raster_timing0; + u32 raster_timing1; + u32 raster_timing2; + u32 raster_subpanel; + u32 raster_subpanel2; + u32 lcddma_ctrl; + u32 lcddma_fb0_base; + u32 lcddma_fb0_ceiling; + u32 lcddma_fb1_base; + u32 lcddma_fb1_ceiling; + u32 sysconfig; + u32 irqstatus_raw; + u32 irqstatus; + u32 irqenable_set; + u32 irqenable_clear; + u32 gap1; + u32 clkc_enable; + u32 clkc_reset; +}; + +struct tilcdc_priv { + struct tilcdc_regs *regs; + struct clk gclk; + struct clk dpll_m2_clk; +}; + +DECLARE_GLOBAL_DATA_PTR; + +static ulong tilcdc_set_pixel_clk_rate(struct udevice *dev, ulong rate) +{ + struct tilcdc_priv *priv = dev_get_priv(dev); + struct tilcdc_regs *regs = priv->regs; + ulong mult_rate, mult_round_rate, best_err, err; + u32 v; + int div, i; + + best_err = rate; + div = 0; + for (i = 2; i <= 255; i++) { + mult_rate = rate * i; + mult_round_rate = clk_round_rate(&priv->gclk, mult_rate); + if (IS_ERR_VALUE(mult_round_rate)) + return mult_round_rate; + + err = mult_rate - mult_round_rate; + if (err < best_err) { + best_err = err; + div = i; + if (err == 0) + break; + } + } + + if (div == 0) { + dev_err(dev, "failed to find a divisor\n"); + return -EFAULT; + } + + mult_rate = clk_set_rate(&priv->gclk, rate * div); + v = readl(®s->ctrl) & ~LCDC_CTRL_CLK_DIVISOR_MASK; + v |= LCDC_CTRL_CLK_DIVISOR(div); + writel(v, ®s->ctrl); + rate = mult_rate / div; + dev_dbg(dev, "rate=%ld, div=%d, err=%ld\n", rate, div, err); + return rate; +} + +static int tilcdc_remove(struct udevice *dev) +{ + struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev); + struct tilcdc_priv *priv = dev_get_priv(dev); + + uc_plat->base -= 0x20; + uc_plat->size += 0x20; + clk_release_all(&priv->gclk, 1); + clk_release_all(&priv->dpll_m2_clk, 1); + return 0; +} + +static int tilcdc_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 tilcdc_priv *priv = dev_get_priv(dev); + struct tilcdc_regs *regs = priv->regs; + struct udevice *panel, *clk_dev; + struct tilcdc_panel_info info; + struct display_timing timing; + ulong rate; + u32 reg; + int err; + + /* Before relocation we don't need to do anything */ + if (!(gd->flags & GD_FLG_RELOC)) + return 0; + + err = uclass_get_device(UCLASS_PANEL, 0, &panel); + if (err) { + dev_err(dev, "failed to get panel\n"); + return err; + } + + err = panel_get_display_timing(panel, &timing); + if (err) { + dev_err(dev, "failed to get display timing\n"); + return err; + } + + if (timing.pixelclock.typ > (LCDC_FMAX / 2)) { + dev_err(dev, "invalid display clock-frequency: %d Hz\n", + timing.pixelclock.typ); + return -EINVAL; + } + + if (timing.hactive.typ > LCDC_MAX_WIDTH) + timing.hactive.typ = LCDC_MAX_WIDTH; + + if (timing.vactive.typ > LCDC_MAX_HEIGHT) + timing.vactive.typ = LCDC_MAX_HEIGHT; + + err = tilcdc_panel_get_display_info(panel, &info); + if (err) { + dev_err(dev, "failed to get panel info\n"); + return err; + } + + switch (info.bpp) { + case 16: + case 24: + case 32: + break; + default: + dev_err(dev, "invalid seting, bpp: %d\n", info.bpp); + return -EINVAL; + } + + switch (info.dma_burst_sz) { + case 1: + case 2: + case 4: + case 8: + case 16: + break; + default: + dev_err(dev, "invalid setting, dma-burst-sz: %d\n", + info.dma_burst_sz); + return -EINVAL; + } + + err = uclass_get_device_by_name(UCLASS_CLK, "lcd_gclk@534", &clk_dev); + if (err) { + dev_err(dev, "failed to get lcd_gclk device\n"); + return err; + } + + err = clk_request(clk_dev, &priv->gclk); + if (err) { + dev_err(dev, "failed to get %s clock\n", clk_dev->name); + return err; + } + + rate = tilcdc_set_pixel_clk_rate(dev, timing.pixelclock.typ); + if (IS_ERR_VALUE(rate)) { + dev_err(dev, "failed to set pixel clock rate\n"); + return rate; + } + + err = uclass_get_device_by_name(UCLASS_CLK, "dpll_disp_m2_ck@4a4", + &clk_dev); + if (err) { + dev_err(dev, "failed to get dpll_disp_m2 clock device\n"); + return err; + } + + err = clk_request(clk_dev, &priv->dpll_m2_clk); + if (err) { + dev_err(dev, "failed to get %s clock\n", clk_dev->name); + return err; + } + + err = clk_set_parent(&priv->gclk, &priv->dpll_m2_clk); + if (err) { + dev_err(dev, "failed to set %s clock as %s's parent\n", + priv->dpll_m2_clk.dev->name, priv->gclk.dev->name); + return err; + } + + /* palette default entry */ + memset((void *)uc_plat->base, 0, 0x20); + *(unsigned int *)uc_plat->base = 0x4000; + /* point fb behind palette */ + uc_plat->base += 0x20; + uc_plat->size -= 0x20; + + writel(LCDC_CLKC_ENABLE_CORECLKEN | LCDC_CLKC_ENABLE_LIDDCLKEN | + LCDC_CLKC_ENABLE_DMACLKEN, ®s->clkc_enable); + writel(0, ®s->raster_ctrl); + + reg = readl(®s->ctrl) & LCDC_CTRL_CLK_DIVISOR_MASK; + reg |= LCDC_CTRL_RASTER_MODE; + writel(reg, ®s->ctrl); + + reg = (timing.hactive.typ * timing.vactive.typ * info.bpp) >> 3; + reg += uc_plat->base; + writel(uc_plat->base, ®s->lcddma_fb0_base); + writel(reg, ®s->lcddma_fb0_ceiling); + writel(uc_plat->base, ®s->lcddma_fb1_base); + writel(reg, ®s->lcddma_fb1_ceiling); + + reg = LCDC_DMA_CTRL_FIFO_TH(info.fifo_th); + switch (info.dma_burst_sz) { + case 1: + reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_1); + break; + case 2: + reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_2); + break; + case 4: + reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_4); + break; + case 8: + reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_8); + break; + case 16: + reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_16); + break; + } + + writel(reg, ®s->lcddma_ctrl); + + writel(LCDC_RASTER_TIMING_0_HORLSB(timing.hactive.typ) | + LCDC_RASTER_TIMING_0_HORMSB(timing.hactive.typ) | + LCDC_RASTER_TIMING_0_HFPLSB(timing.hfront_porch.typ) | + LCDC_RASTER_TIMING_0_HBPLSB(timing.hback_porch.typ) | + LCDC_RASTER_TIMING_0_HSWLSB(timing.hsync_len.typ), + ®s->raster_timing0); + + writel(LCDC_RASTER_TIMING_1_VBP(timing.vback_porch.typ) | + LCDC_RASTER_TIMING_1_VFP(timing.vfront_porch.typ) | + LCDC_RASTER_TIMING_1_VSW(timing.vsync_len.typ) | + LCDC_RASTER_TIMING_1_VERLSB(timing.vactive.typ), + ®s->raster_timing1); + + reg = LCDC_RASTER_TIMING_2_ACB(info.ac_bias) | + LCDC_RASTER_TIMING_2_ACBI(info.ac_bias_intrpt) | + LCDC_RASTER_TIMING_2_HSWMSB(timing.hsync_len.typ) | + LCDC_RASTER_TIMING_2_VERMSB(timing.vactive.typ) | + LCDC_RASTER_TIMING_2_HBPMSB(timing.hback_porch.typ) | + LCDC_RASTER_TIMING_2_HFPMSB(timing.hfront_porch.typ); + + if (timing.flags & DISPLAY_FLAGS_VSYNC_LOW) + reg |= LCDC_RASTER_TIMING_2_VSYNC_INVERT; + + if (timing.flags & DISPLAY_FLAGS_HSYNC_LOW) + reg |= LCDC_RASTER_TIMING_2_HSYNC_INVERT; + + if (info.invert_pxl_clk) + reg |= LCDC_RASTER_TIMING_2_PXCLK_INVERT; + + if (info.sync_edge) + reg |= LCDC_RASTER_TIMING_2_HSVS_RISEFALL; + + if (info.sync_ctrl) + reg |= LCDC_RASTER_TIMING_2_HSVS_CONTROL; + + writel(reg, ®s->raster_timing2); + + reg = LCDC_RASTER_CTRL_PALMODE_RAWDATA | LCDC_RASTER_CTRL_TFT_MODE | + LCDC_RASTER_CTRL_ENABLE | LCDC_RASTER_CTRL_REQDLY(info.fdd); + + if (info.tft_alt_mode) + reg |= LCDC_RASTER_CTRL_TFT_ALT_ENABLE; + + if (info.bpp == 24) + reg |= LCDC_RASTER_CTRL_TFT_24BPP_MODE; + else if (info.bpp == 32) + reg |= LCDC_RASTER_CTRL_TFT_24BPP_MODE | + LCDC_RASTER_CTRL_TFT_24BPP_UNPACK; + + if (info.raster_order) + reg |= LCDC_RASTER_CTRL_DATA_ORDER; + + writel(reg, ®s->raster_ctrl); + + uc_priv->xsize = timing.hactive.typ; + uc_priv->ysize = timing.vactive.typ; + uc_priv->bpix = log_2_n_round_up(info.bpp); + + err = panel_enable_backlight(panel); + if (err) { + dev_err(dev, "failed to enable panel backlight\n"); + return err; + } + + return 0; +} + +static int tilcdc_of_to_plat(struct udevice *dev) +{ + struct tilcdc_priv *priv = dev_get_priv(dev); + + priv->regs = (struct tilcdc_regs *)dev_read_addr(dev); + if ((fdt_addr_t)priv->regs == FDT_ADDR_T_NONE) { + dev_err(dev, "failed to get base address\n"); + return -EINVAL; + } + + dev_dbg(dev, "LCD: base address=0x%x\n", (unsigned int)priv->regs); + return 0; +} + +static int tilcdc_bind(struct udevice *dev) +{ + struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev); + + uc_plat->size = ((LCDC_MAX_WIDTH * LCDC_MAX_HEIGHT * + (1 << LCDC_MAX_LOG2_BPP)) >> 3) + 0x20; + + dev_dbg(dev, "frame buffer size 0x%x\n", uc_plat->size); + return 0; +} + +static const struct udevice_id tilcdc_ids[] = { + {.compatible = "ti,am33xx-tilcdc"}, + {} +}; + +U_BOOT_DRIVER(tilcdc) = { + .name = "tilcdc", + .id = UCLASS_VIDEO, + .of_match = tilcdc_ids, + .bind = tilcdc_bind, + .ofdata_to_platdata = tilcdc_of_to_plat, + .probe = tilcdc_probe, + .remove = tilcdc_remove, + .priv_auto = sizeof(struct tilcdc_priv) +}; diff --git a/drivers/video/ti/tilcdc.h b/drivers/video/ti/tilcdc.h new file mode 100644 index 00000000000..2645921df65 --- /dev/null +++ b/drivers/video/ti/tilcdc.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2020 Dario Binacchi + */ + +#ifndef _TILCDC_H +#define _TILCDC_H + +/** + * tilcdc_panel_info: Panel parameters + * + * @ac_bias: AC Bias Pin Frequency + * @ac_bias_intrpt: AC Bias Pin Transitions per Interrupt + * @dma_burst_sz: DMA burst size + * @bpp: Bits per pixel + * @fdd: FIFO DMA Request Delay + * @tft_alt_mode: TFT Alternative Signal Mapping (Only for active) + * @invert_pxl_clk: Invert pixel clock + * @sync_edge: Horizontal and Vertical Sync Edge: 0=rising 1=falling + * @sync_ctrl: Horizontal and Vertical Sync: Control: 0=ignore + * @raster_order: Raster Data Order Select: 1=Most-to-least 0=Least-to-most + * @fifo_th: DMA FIFO threshold + */ +struct tilcdc_panel_info { + u32 ac_bias; + u32 ac_bias_intrpt; + u32 dma_burst_sz; + u32 bpp; + u32 fdd; + bool tft_alt_mode; + bool invert_pxl_clk; + u32 sync_edge; + u32 sync_ctrl; + u32 raster_order; + u32 fifo_th; +}; + +#endif /* _TILCDC_H */ diff --git a/drivers/video/tilcdc-panel.c b/drivers/video/tilcdc-panel.c deleted file mode 100644 index b90dfae4ae9..00000000000 --- a/drivers/video/tilcdc-panel.c +++ /dev/null @@ -1,172 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * OMAP panel support - * - * Copyright (C) 2020 Dario Binacchi - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "tilcdc.h" - -struct tilcdc_panel_priv { - struct tilcdc_panel_info info; - struct display_timing timing; - struct udevice *backlight; - struct gpio_desc enable; -}; - -static int tilcdc_panel_enable_backlight(struct udevice *dev) -{ - struct tilcdc_panel_priv *priv = dev_get_priv(dev); - - if (dm_gpio_is_valid(&priv->enable)) - dm_gpio_set_value(&priv->enable, 1); - - if (priv->backlight) - return backlight_enable(priv->backlight); - - return 0; -} - -static int tilcdc_panel_set_backlight(struct udevice *dev, int percent) -{ - struct tilcdc_panel_priv *priv = dev_get_priv(dev); - - if (dm_gpio_is_valid(&priv->enable)) - dm_gpio_set_value(&priv->enable, 1); - - if (priv->backlight) - return backlight_set_brightness(priv->backlight, percent); - - return 0; -} - -int tilcdc_panel_get_display_info(struct udevice *dev, - struct tilcdc_panel_info *info) -{ - struct tilcdc_panel_priv *priv = dev_get_priv(dev); - - memcpy(info, &priv->info, sizeof(*info)); - return 0; -} - -static int tilcdc_panel_get_display_timing(struct udevice *dev, - struct display_timing *timing) -{ - struct tilcdc_panel_priv *priv = dev_get_priv(dev); - - memcpy(timing, &priv->timing, sizeof(*timing)); - return 0; -} - -static int tilcdc_panel_remove(struct udevice *dev) -{ - struct tilcdc_panel_priv *priv = dev_get_priv(dev); - - if (dm_gpio_is_valid(&priv->enable)) - dm_gpio_free(dev, &priv->enable); - - return 0; -} - -static int tilcdc_panel_probe(struct udevice *dev) -{ - struct tilcdc_panel_priv *priv = dev_get_priv(dev); - int err; - - err = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev, - "backlight", &priv->backlight); - if (err) - dev_warn(dev, "failed to get backlight\n"); - - err = gpio_request_by_name(dev, "enable-gpios", 0, &priv->enable, - GPIOD_IS_OUT); - if (err) { - dev_warn(dev, "failed to get enable GPIO\n"); - if (err != -ENOENT) - return err; - } - - return 0; -} - -static int tilcdc_panel_of_to_plat(struct udevice *dev) -{ - struct tilcdc_panel_priv *priv = dev_get_priv(dev); - ofnode node; - int err; - - err = ofnode_decode_display_timing(dev_ofnode(dev), 0, &priv->timing); - if (err) { - dev_err(dev, "failed to get display timing\n"); - return err; - } - - node = dev_read_subnode(dev, "panel-info"); - if (!ofnode_valid(node)) { - dev_err(dev, "missing 'panel-info' node\n"); - return -ENXIO; - } - - err |= ofnode_read_u32(node, "ac-bias", &priv->info.ac_bias); - err |= ofnode_read_u32(node, "ac-bias-intrpt", - &priv->info.ac_bias_intrpt); - err |= ofnode_read_u32(node, "dma-burst-sz", &priv->info.dma_burst_sz); - err |= ofnode_read_u32(node, "bpp", &priv->info.bpp); - err |= ofnode_read_u32(node, "fdd", &priv->info.fdd); - err |= ofnode_read_u32(node, "sync-edge", &priv->info.sync_edge); - err |= ofnode_read_u32(node, "sync-ctrl", &priv->info.sync_ctrl); - err |= ofnode_read_u32(node, "raster-order", &priv->info.raster_order); - err |= ofnode_read_u32(node, "fifo-th", &priv->info.fifo_th); - if (err) { - dev_err(dev, "failed to get panel info\n"); - return err; - } - - /* optional */ - priv->info.tft_alt_mode = ofnode_read_bool(node, "tft-alt-mode"); - priv->info.invert_pxl_clk = ofnode_read_bool(node, "invert-pxl-clk"); - - dev_dbg(dev, "LCD: %dx%d, bpp=%d, clk=%d Hz\n", - priv->timing.hactive.typ, priv->timing.vactive.typ, - priv->info.bpp, priv->timing.pixelclock.typ); - dev_dbg(dev, " hbp=%d, hfp=%d, hsw=%d\n", - priv->timing.hback_porch.typ, priv->timing.hfront_porch.typ, - priv->timing.hsync_len.typ); - dev_dbg(dev, " vbp=%d, vfp=%d, vsw=%d\n", - priv->timing.vback_porch.typ, priv->timing.vfront_porch.typ, - priv->timing.vsync_len.typ); - - return 0; -} - -static const struct panel_ops tilcdc_panel_ops = { - .enable_backlight = tilcdc_panel_enable_backlight, - .set_backlight = tilcdc_panel_set_backlight, - .get_display_timing = tilcdc_panel_get_display_timing, -}; - -static const struct udevice_id tilcdc_panel_ids[] = { - {.compatible = "ti,tilcdc,panel"}, - {} -}; - -U_BOOT_DRIVER(tilcdc_panel) = { - .name = "tilcdc_panel", - .id = UCLASS_PANEL, - .of_match = tilcdc_panel_ids, - .ops = &tilcdc_panel_ops, - .ofdata_to_platdata = tilcdc_panel_of_to_plat, - .probe = tilcdc_panel_probe, - .remove = tilcdc_panel_remove, - .priv_auto = sizeof(struct tilcdc_panel_priv), -}; diff --git a/drivers/video/tilcdc-panel.h b/drivers/video/tilcdc-panel.h deleted file mode 100644 index 6bcfbf8a8b4..00000000000 --- a/drivers/video/tilcdc-panel.h +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Copyright (C) 2020 Dario Binacchi - */ - -#ifndef _TILCDC_PANEL_H -#define _TILCDC_PANEL_H - -#include "tilcdc.h" - -int tilcdc_panel_get_display_info(struct udevice *dev, - struct tilcdc_panel_info *info); - -#endif /* _TILCDC_PANEL_H */ diff --git a/drivers/video/tilcdc.c b/drivers/video/tilcdc.c deleted file mode 100644 index d13cc11801d..00000000000 --- a/drivers/video/tilcdc.c +++ /dev/null @@ -1,425 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (C) 2020 Dario Binacchi - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "tilcdc.h" -#include "tilcdc-panel.h" - -#define LCDC_FMAX 200000000 - -/* LCD Control Register */ -#define LCDC_CTRL_CLK_DIVISOR_MASK GENMASK(15, 8) -#define LCDC_CTRL_RASTER_MODE BIT(0) -#define LCDC_CTRL_CLK_DIVISOR(x) (((x) & GENMASK(7, 0)) << 8) -/* LCD Clock Enable Register */ -#define LCDC_CLKC_ENABLE_CORECLKEN BIT(0) -#define LCDC_CLKC_ENABLE_LIDDCLKEN BIT(1) -#define LCDC_CLKC_ENABLE_DMACLKEN BIT(2) -/* LCD DMA Control Register */ -#define LCDC_DMA_CTRL_BURST_SIZE(x) (((x) & GENMASK(2, 0)) << 4) -#define LCDC_DMA_CTRL_BURST_1 0x0 -#define LCDC_DMA_CTRL_BURST_2 0x1 -#define LCDC_DMA_CTRL_BURST_4 0x2 -#define LCDC_DMA_CTRL_BURST_8 0x3 -#define LCDC_DMA_CTRL_BURST_16 0x4 -#define LCDC_DMA_CTRL_FIFO_TH(x) (((x) & GENMASK(2, 0)) << 8) -/* LCD Timing_0 Register */ -#define LCDC_RASTER_TIMING_0_HORMSB(x) ((((x) - 1) & BIT(10)) >> 7) -#define LCDC_RASTER_TIMING_0_HORLSB(x) (((((x) >> 4) - 1) & GENMASK(5, 0)) << 4) -#define LCDC_RASTER_TIMING_0_HSWLSB(x) ((((x) - 1) & GENMASK(5, 0)) << 10) -#define LCDC_RASTER_TIMING_0_HFPLSB(x) ((((x) - 1) & GENMASK(7, 0)) << 16) -#define LCDC_RASTER_TIMING_0_HBPLSB(x) ((((x) - 1) & GENMASK(7, 0)) << 24) -/* LCD Timing_1 Register */ -#define LCDC_RASTER_TIMING_1_VERLSB(x) (((x) - 1) & GENMASK(9, 0)) -#define LCDC_RASTER_TIMING_1_VSW(x) ((((x) - 1) & GENMASK(5, 0)) << 10) -#define LCDC_RASTER_TIMING_1_VFP(x) (((x) & GENMASK(7, 0)) << 16) -#define LCDC_RASTER_TIMING_1_VBP(x) (((x) & GENMASK(7, 0)) << 24) -/* LCD Timing_2 Register */ -#define LCDC_RASTER_TIMING_2_HFPMSB(x) ((((x) - 1) & GENMASK(9, 8)) >> 8) -#define LCDC_RASTER_TIMING_2_HBPMSB(x) ((((x) - 1) & GENMASK(9, 8)) >> 4) -#define LCDC_RASTER_TIMING_2_ACB(x) (((x) & GENMASK(7, 0)) << 8) -#define LCDC_RASTER_TIMING_2_ACBI(x) (((x) & GENMASK(3, 0)) << 16) -#define LCDC_RASTER_TIMING_2_VSYNC_INVERT BIT(20) -#define LCDC_RASTER_TIMING_2_HSYNC_INVERT BIT(21) -#define LCDC_RASTER_TIMING_2_PXCLK_INVERT BIT(22) -#define LCDC_RASTER_TIMING_2_DE_INVERT BIT(23) -#define LCDC_RASTER_TIMING_2_HSVS_RISEFALL BIT(24) -#define LCDC_RASTER_TIMING_2_HSVS_CONTROL BIT(25) -#define LCDC_RASTER_TIMING_2_VERMSB(x) ((((x) - 1) & BIT(10)) << 16) -#define LCDC_RASTER_TIMING_2_HSWMSB(x) ((((x) - 1) & GENMASK(9, 6)) << 21) -/* LCD Raster Ctrl Register */ -#define LCDC_RASTER_CTRL_ENABLE BIT(0) -#define LCDC_RASTER_CTRL_TFT_MODE BIT(7) -#define LCDC_RASTER_CTRL_DATA_ORDER BIT(8) -#define LCDC_RASTER_CTRL_REQDLY(x) (((x) & GENMASK(7, 0)) << 12) -#define LCDC_RASTER_CTRL_PALMODE_RAWDATA (0x02 << 20) -#define LCDC_RASTER_CTRL_TFT_ALT_ENABLE BIT(23) -#define LCDC_RASTER_CTRL_TFT_24BPP_MODE BIT(25) -#define LCDC_RASTER_CTRL_TFT_24BPP_UNPACK BIT(26) - -enum { - LCDC_MAX_WIDTH = 2048, - LCDC_MAX_HEIGHT = 2048, - LCDC_MAX_LOG2_BPP = VIDEO_BPP32, -}; - -struct tilcdc_regs { - u32 pid; - u32 ctrl; - u32 gap0; - u32 lidd_ctrl; - u32 lidd_cs0_conf; - u32 lidd_cs0_addr; - u32 lidd_cs0_data; - u32 lidd_cs1_conf; - u32 lidd_cs1_addr; - u32 lidd_cs1_data; - u32 raster_ctrl; - u32 raster_timing0; - u32 raster_timing1; - u32 raster_timing2; - u32 raster_subpanel; - u32 raster_subpanel2; - u32 lcddma_ctrl; - u32 lcddma_fb0_base; - u32 lcddma_fb0_ceiling; - u32 lcddma_fb1_base; - u32 lcddma_fb1_ceiling; - u32 sysconfig; - u32 irqstatus_raw; - u32 irqstatus; - u32 irqenable_set; - u32 irqenable_clear; - u32 gap1; - u32 clkc_enable; - u32 clkc_reset; -}; - -struct tilcdc_priv { - struct tilcdc_regs *regs; - struct clk gclk; - struct clk dpll_m2_clk; -}; - -DECLARE_GLOBAL_DATA_PTR; - -static ulong tilcdc_set_pixel_clk_rate(struct udevice *dev, ulong rate) -{ - struct tilcdc_priv *priv = dev_get_priv(dev); - struct tilcdc_regs *regs = priv->regs; - ulong mult_rate, mult_round_rate, best_err, err; - u32 v; - int div, i; - - best_err = rate; - div = 0; - for (i = 2; i <= 255; i++) { - mult_rate = rate * i; - mult_round_rate = clk_round_rate(&priv->gclk, mult_rate); - if (IS_ERR_VALUE(mult_round_rate)) - return mult_round_rate; - - err = mult_rate - mult_round_rate; - if (err < best_err) { - best_err = err; - div = i; - if (err == 0) - break; - } - } - - if (div == 0) { - dev_err(dev, "failed to find a divisor\n"); - return -EFAULT; - } - - mult_rate = clk_set_rate(&priv->gclk, rate * div); - v = readl(®s->ctrl) & ~LCDC_CTRL_CLK_DIVISOR_MASK; - v |= LCDC_CTRL_CLK_DIVISOR(div); - writel(v, ®s->ctrl); - rate = mult_rate / div; - dev_dbg(dev, "rate=%ld, div=%d, err=%ld\n", rate, div, err); - return rate; -} - -static int tilcdc_remove(struct udevice *dev) -{ - struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev); - struct tilcdc_priv *priv = dev_get_priv(dev); - - uc_plat->base -= 0x20; - uc_plat->size += 0x20; - clk_release_all(&priv->gclk, 1); - clk_release_all(&priv->dpll_m2_clk, 1); - return 0; -} - -static int tilcdc_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 tilcdc_priv *priv = dev_get_priv(dev); - struct tilcdc_regs *regs = priv->regs; - struct udevice *panel, *clk_dev; - struct tilcdc_panel_info info; - struct display_timing timing; - ulong rate; - u32 reg; - int err; - - /* Before relocation we don't need to do anything */ - if (!(gd->flags & GD_FLG_RELOC)) - return 0; - - err = uclass_get_device(UCLASS_PANEL, 0, &panel); - if (err) { - dev_err(dev, "failed to get panel\n"); - return err; - } - - err = panel_get_display_timing(panel, &timing); - if (err) { - dev_err(dev, "failed to get display timing\n"); - return err; - } - - if (timing.pixelclock.typ > (LCDC_FMAX / 2)) { - dev_err(dev, "invalid display clock-frequency: %d Hz\n", - timing.pixelclock.typ); - return -EINVAL; - } - - if (timing.hactive.typ > LCDC_MAX_WIDTH) - timing.hactive.typ = LCDC_MAX_WIDTH; - - if (timing.vactive.typ > LCDC_MAX_HEIGHT) - timing.vactive.typ = LCDC_MAX_HEIGHT; - - err = tilcdc_panel_get_display_info(panel, &info); - if (err) { - dev_err(dev, "failed to get panel info\n"); - return err; - } - - switch (info.bpp) { - case 16: - case 24: - case 32: - break; - default: - dev_err(dev, "invalid seting, bpp: %d\n", info.bpp); - return -EINVAL; - } - - switch (info.dma_burst_sz) { - case 1: - case 2: - case 4: - case 8: - case 16: - break; - default: - dev_err(dev, "invalid setting, dma-burst-sz: %d\n", - info.dma_burst_sz); - return -EINVAL; - } - - err = uclass_get_device_by_name(UCLASS_CLK, "lcd_gclk@534", &clk_dev); - if (err) { - dev_err(dev, "failed to get lcd_gclk device\n"); - return err; - } - - err = clk_request(clk_dev, &priv->gclk); - if (err) { - dev_err(dev, "failed to get %s clock\n", clk_dev->name); - return err; - } - - rate = tilcdc_set_pixel_clk_rate(dev, timing.pixelclock.typ); - if (IS_ERR_VALUE(rate)) { - dev_err(dev, "failed to set pixel clock rate\n"); - return rate; - } - - err = uclass_get_device_by_name(UCLASS_CLK, "dpll_disp_m2_ck@4a4", - &clk_dev); - if (err) { - dev_err(dev, "failed to get dpll_disp_m2 clock device\n"); - return err; - } - - err = clk_request(clk_dev, &priv->dpll_m2_clk); - if (err) { - dev_err(dev, "failed to get %s clock\n", clk_dev->name); - return err; - } - - err = clk_set_parent(&priv->gclk, &priv->dpll_m2_clk); - if (err) { - dev_err(dev, "failed to set %s clock as %s's parent\n", - priv->dpll_m2_clk.dev->name, priv->gclk.dev->name); - return err; - } - - /* palette default entry */ - memset((void *)uc_plat->base, 0, 0x20); - *(unsigned int *)uc_plat->base = 0x4000; - /* point fb behind palette */ - uc_plat->base += 0x20; - uc_plat->size -= 0x20; - - writel(LCDC_CLKC_ENABLE_CORECLKEN | LCDC_CLKC_ENABLE_LIDDCLKEN | - LCDC_CLKC_ENABLE_DMACLKEN, ®s->clkc_enable); - writel(0, ®s->raster_ctrl); - - reg = readl(®s->ctrl) & LCDC_CTRL_CLK_DIVISOR_MASK; - reg |= LCDC_CTRL_RASTER_MODE; - writel(reg, ®s->ctrl); - - reg = (timing.hactive.typ * timing.vactive.typ * info.bpp) >> 3; - reg += uc_plat->base; - writel(uc_plat->base, ®s->lcddma_fb0_base); - writel(reg, ®s->lcddma_fb0_ceiling); - writel(uc_plat->base, ®s->lcddma_fb1_base); - writel(reg, ®s->lcddma_fb1_ceiling); - - reg = LCDC_DMA_CTRL_FIFO_TH(info.fifo_th); - switch (info.dma_burst_sz) { - case 1: - reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_1); - break; - case 2: - reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_2); - break; - case 4: - reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_4); - break; - case 8: - reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_8); - break; - case 16: - reg |= LCDC_DMA_CTRL_BURST_SIZE(LCDC_DMA_CTRL_BURST_16); - break; - } - - writel(reg, ®s->lcddma_ctrl); - - writel(LCDC_RASTER_TIMING_0_HORLSB(timing.hactive.typ) | - LCDC_RASTER_TIMING_0_HORMSB(timing.hactive.typ) | - LCDC_RASTER_TIMING_0_HFPLSB(timing.hfront_porch.typ) | - LCDC_RASTER_TIMING_0_HBPLSB(timing.hback_porch.typ) | - LCDC_RASTER_TIMING_0_HSWLSB(timing.hsync_len.typ), - ®s->raster_timing0); - - writel(LCDC_RASTER_TIMING_1_VBP(timing.vback_porch.typ) | - LCDC_RASTER_TIMING_1_VFP(timing.vfront_porch.typ) | - LCDC_RASTER_TIMING_1_VSW(timing.vsync_len.typ) | - LCDC_RASTER_TIMING_1_VERLSB(timing.vactive.typ), - ®s->raster_timing1); - - reg = LCDC_RASTER_TIMING_2_ACB(info.ac_bias) | - LCDC_RASTER_TIMING_2_ACBI(info.ac_bias_intrpt) | - LCDC_RASTER_TIMING_2_HSWMSB(timing.hsync_len.typ) | - LCDC_RASTER_TIMING_2_VERMSB(timing.vactive.typ) | - LCDC_RASTER_TIMING_2_HBPMSB(timing.hback_porch.typ) | - LCDC_RASTER_TIMING_2_HFPMSB(timing.hfront_porch.typ); - - if (timing.flags & DISPLAY_FLAGS_VSYNC_LOW) - reg |= LCDC_RASTER_TIMING_2_VSYNC_INVERT; - - if (timing.flags & DISPLAY_FLAGS_HSYNC_LOW) - reg |= LCDC_RASTER_TIMING_2_HSYNC_INVERT; - - if (info.invert_pxl_clk) - reg |= LCDC_RASTER_TIMING_2_PXCLK_INVERT; - - if (info.sync_edge) - reg |= LCDC_RASTER_TIMING_2_HSVS_RISEFALL; - - if (info.sync_ctrl) - reg |= LCDC_RASTER_TIMING_2_HSVS_CONTROL; - - writel(reg, ®s->raster_timing2); - - reg = LCDC_RASTER_CTRL_PALMODE_RAWDATA | LCDC_RASTER_CTRL_TFT_MODE | - LCDC_RASTER_CTRL_ENABLE | LCDC_RASTER_CTRL_REQDLY(info.fdd); - - if (info.tft_alt_mode) - reg |= LCDC_RASTER_CTRL_TFT_ALT_ENABLE; - - if (info.bpp == 24) - reg |= LCDC_RASTER_CTRL_TFT_24BPP_MODE; - else if (info.bpp == 32) - reg |= LCDC_RASTER_CTRL_TFT_24BPP_MODE | - LCDC_RASTER_CTRL_TFT_24BPP_UNPACK; - - if (info.raster_order) - reg |= LCDC_RASTER_CTRL_DATA_ORDER; - - writel(reg, ®s->raster_ctrl); - - uc_priv->xsize = timing.hactive.typ; - uc_priv->ysize = timing.vactive.typ; - uc_priv->bpix = log_2_n_round_up(info.bpp); - - err = panel_enable_backlight(panel); - if (err) { - dev_err(dev, "failed to enable panel backlight\n"); - return err; - } - - return 0; -} - -static int tilcdc_of_to_plat(struct udevice *dev) -{ - struct tilcdc_priv *priv = dev_get_priv(dev); - - priv->regs = (struct tilcdc_regs *)dev_read_addr(dev); - if ((fdt_addr_t)priv->regs == FDT_ADDR_T_NONE) { - dev_err(dev, "failed to get base address\n"); - return -EINVAL; - } - - dev_dbg(dev, "LCD: base address=0x%x\n", (unsigned int)priv->regs); - return 0; -} - -static int tilcdc_bind(struct udevice *dev) -{ - struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev); - - uc_plat->size = ((LCDC_MAX_WIDTH * LCDC_MAX_HEIGHT * - (1 << LCDC_MAX_LOG2_BPP)) >> 3) + 0x20; - - dev_dbg(dev, "frame buffer size 0x%x\n", uc_plat->size); - return 0; -} - -static const struct udevice_id tilcdc_ids[] = { - {.compatible = "ti,am33xx-tilcdc"}, - {} -}; - -U_BOOT_DRIVER(tilcdc) = { - .name = "tilcdc", - .id = UCLASS_VIDEO, - .of_match = tilcdc_ids, - .bind = tilcdc_bind, - .ofdata_to_platdata = tilcdc_of_to_plat, - .probe = tilcdc_probe, - .remove = tilcdc_remove, - .priv_auto = sizeof(struct tilcdc_priv) -}; diff --git a/drivers/video/tilcdc.h b/drivers/video/tilcdc.h deleted file mode 100644 index 2645921df65..00000000000 --- a/drivers/video/tilcdc.h +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Copyright (C) 2020 Dario Binacchi - */ - -#ifndef _TILCDC_H -#define _TILCDC_H - -/** - * tilcdc_panel_info: Panel parameters - * - * @ac_bias: AC Bias Pin Frequency - * @ac_bias_intrpt: AC Bias Pin Transitions per Interrupt - * @dma_burst_sz: DMA burst size - * @bpp: Bits per pixel - * @fdd: FIFO DMA Request Delay - * @tft_alt_mode: TFT Alternative Signal Mapping (Only for active) - * @invert_pxl_clk: Invert pixel clock - * @sync_edge: Horizontal and Vertical Sync Edge: 0=rising 1=falling - * @sync_ctrl: Horizontal and Vertical Sync: Control: 0=ignore - * @raster_order: Raster Data Order Select: 1=Most-to-least 0=Least-to-most - * @fifo_th: DMA FIFO threshold - */ -struct tilcdc_panel_info { - u32 ac_bias; - u32 ac_bias_intrpt; - u32 dma_burst_sz; - u32 bpp; - u32 fdd; - bool tft_alt_mode; - bool invert_pxl_clk; - u32 sync_edge; - u32 sync_ctrl; - u32 raster_order; - u32 fifo_th; -}; - -#endif /* _TILCDC_H */ -- cgit v1.3.1 From ea3f5348063ebe4f41be7d1ba3ef0afe56a04a40 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Wed, 6 Jan 2021 13:20:32 -0600 Subject: remoteproc: ti_k3_arm64: Program CNTFID0 register in GTC ARMv8's generic timer[1] picks up it's graycode from GTC. However, the frequency of the GTC is supposed to be programmed in CNTFID0[2] register prior to enabling the GTC in CNTCR[3] register. In K3 architecture, GTC provides a central time to many parts of the SoC including graycode to the generic timer in the ARMv8 subsystem. However, due to the central nature and the need to enable the counter early in the boot process, the R5 based u-boot enables GTC and programs it's frequency based on central needs of the system. This may not be a constant 200MHz based on the system. The bootloader is supposed to program the FID0 register with the correct frequency it has sourced for GTC from the central system controller OR from PLLs as appropriate, and TF-A is supposed[4] to use that as the frequency for it's local timer. Currently we are programming just the CNTCR[3] register to enable the GTC, however we dont let TF-A know the frequency that GTC is actually running at. A mismatch in programmed frequency and what we program for generic timer will, as we can imagine, all kind of weird mayhem. So, program the CNTFID0 register with the clock frequency. Note: assigned-clock-rates should have set the clock frequency, so the only operation we need to explicitly do is to retrieve the frequency and program it in FID0 register. Since the valid in K3 for GTC clock frequencies are < U32_MAX, we can just cast the ulong and continue. [1] https://developer.arm.com/documentation/100095/0002/generic-timer/generic-timer-register-summary/aarch64-generic-timer-register-summary [2] https://developer.arm.com/docs/ddi0595/h/external-system-registers/cntfid0 [3] https://developer.arm.com/docs/ddi0595/h/external-system-registers/cntcr [4] https://github.com/ARM-software/arm-trusted-firmware/commit/6a22d9ea3c7fa28d053d3ba264b49b7396a86f9e Signed-off-by: Nishanth Menon --- drivers/remoteproc/ti_k3_arm64_rproc.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers') diff --git a/drivers/remoteproc/ti_k3_arm64_rproc.c b/drivers/remoteproc/ti_k3_arm64_rproc.c index 1041f3805fa..1f2415dc1a6 100644 --- a/drivers/remoteproc/ti_k3_arm64_rproc.c +++ b/drivers/remoteproc/ti_k3_arm64_rproc.c @@ -23,6 +23,7 @@ #define INVALID_ID 0xffff #define GTC_CNTCR_REG 0x0 +#define GTC_CNTFID0_REG 0x20 #define GTC_CNTR_EN 0x3 /** @@ -31,6 +32,7 @@ * @rproc_rst: rproc reset control data * @sci: Pointer to TISCI handle * @tsp: TISCI processor control helper structure + * @gtc_clk: GTC clock description * @gtc_base: Timer base address. */ struct k3_arm64_privdata { @@ -38,6 +40,7 @@ struct k3_arm64_privdata { struct power_domain gtc_pwrdmn; struct reset_ctl rproc_rst; struct ti_sci_proc tsp; + struct clk gtc_clk; void *gtc_base; }; @@ -73,6 +76,7 @@ static int k3_arm64_load(struct udevice *dev, ulong addr, ulong size) static int k3_arm64_start(struct udevice *dev) { struct k3_arm64_privdata *rproc = dev_get_priv(dev); + ulong gtc_rate; int ret; dev_dbg(dev, "%s\n", __func__); @@ -83,6 +87,11 @@ static int k3_arm64_start(struct udevice *dev) return ret; } + gtc_rate = clk_get_rate(&rproc->gtc_clk); + dev_dbg(dev, "GTC RATE= %d\n", (u32) gtc_rate); + /* Store the clock frequency down for GTC users to pick up */ + writel((u32)gtc_rate, rproc->gtc_base + GTC_CNTFID0_REG); + /* Enable the timer before starting remote core */ writel(GTC_CNTR_EN, rproc->gtc_base + GTC_CNTCR_REG); @@ -169,6 +178,12 @@ static int k3_arm64_of_to_priv(struct udevice *dev, return ret; } + ret = clk_get_by_index(dev, 0, &rproc->gtc_clk); + if (ret) { + dev_err(dev, "clk_get failed: %d\n", ret); + return ret; + } + ret = reset_get_by_index(dev, 0, &rproc->rproc_rst); if (ret) { dev_err(dev, "reset_get() failed: %d\n", ret); -- cgit v1.3.1