diff options
| author | Tom Rini <trini@konsulko.com> | 2021-01-12 09:32:48 -0500 |
|---|---|---|
| committer | Tom Rini <trini@konsulko.com> | 2021-01-12 09:32:48 -0500 |
| commit | ee6726be4f0dccb612f0193c62ca149164c8a5af (patch) | |
| tree | c58716b51bec487da0c5ac8929bc072549c90b07 /drivers | |
| parent | 996f217ea368ecaef84863bb29699c0e185b9be7 (diff) | |
| parent | ea3f5348063ebe4f41be7d1ba3ef0afe56a04a40 (diff) | |
| download | u-boot-ee6726be4f0dccb612f0193c62ca149164c8a5af.tar.gz u-boot-ee6726be4f0dccb612f0193c62ca149164c8a5af.tar.xz u-boot-ee6726be4f0dccb612f0193c62ca149164c8a5af.zip | |
Merge tag 'ti-v2021.04-rc1' of https://gitlab.denx.de/u-boot/custodians/u-boot-ti
- DM support for OMAP PWM backlight
- USB host mode support for AM654
- Minor SPI fixes
- Add support k2g ice board with 1GHz silicon
- Fix GTC programming for K3 devices
Diffstat (limited to 'drivers')
46 files changed, 2874 insertions, 387 deletions
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 07a33c6287..d742ed333b 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -5,6 +5,19 @@ 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 + 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 0b97fc1f8b..a2e71c7b3b 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -3,4 +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 0000000000..265b4cf83b --- /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 <dariobin@libero.it> + */ + +#include <common.h> +#include <dm.h> + +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, +}; diff --git a/drivers/bus/ti-sysc.c b/drivers/bus/ti-sysc.c new file mode 100644 index 0000000000..4e3d610300 --- /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 <dariobin@libero.it> + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <dm/device_compat.h> + +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) +}; diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 4dfbad7986..db06f276ec 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 @@ -179,6 +171,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 d1e295ac7c..f8383e523d 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/ @@ -47,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-divider.c b/drivers/clk/clk-divider.c index 8f59d7fb72..9df50a5e72 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/drivers/clk/clk-uclass.c b/drivers/clk/clk-uclass.c index 5cfd00ce77..b75056718b 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 2c6c0e239f..b28b67b448 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 e9eb738684..c4e4481508 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/drivers/clk/ti/Kconfig b/drivers/clk/ti/Kconfig new file mode 100644 index 0000000000..2dc86d44a9 --- /dev/null +++ b/drivers/clk/ti/Kconfig @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (C) 2020 Dario Binacchi <dariobin@libero.it> +# + +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_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 + 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 + 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 new file mode 100644 index 0000000000..9f56b47736 --- /dev/null +++ b/drivers/clk/ti/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (C) 2020 Dario Binacchi <dariobin@libero.it> +# + +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 +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-am3-dpll-x2.c b/drivers/clk/ti/clk-am3-dpll-x2.c new file mode 100644 index 0000000000..3cf279d6a3 --- /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 <dariobin@libero.it> + * + * Loosely based on Linux kernel drivers/clk/ti/dpll.c + */ + +#include <common.h> +#include <clk-uclass.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <linux/clk-provider.h> + +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 0000000000..7916a24538 --- /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 <dariobin@libero.it> + * + * Loosely based on Linux kernel drivers/clk/ti/dpll.c + */ + +#include <common.h> +#include <clk.h> +#include <clk-uclass.h> +#include <div64.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <hang.h> +#include <asm/arch/clock.h> +#include <asm/arch/sys_proto.h> +#include <asm/io.h> + +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, +}; diff --git a/drivers/clk/ti/clk-ctrl.c b/drivers/clk/ti/clk-ctrl.c new file mode 100644 index 0000000000..940e8d6caf --- /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 <dariobin@libero.it> + */ + +#include <common.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <clk-uclass.h> +#include <asm/arch-am33xx/clock.h> + +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), +}; diff --git a/drivers/clk/ti/clk-divider.c b/drivers/clk/ti/clk-divider.c new file mode 100644 index 0000000000..a862637785 --- /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 <dariobin@libero.it> + * + * Loosely based on Linux kernel drivers/clk/ti/divider.c + */ + +#include <common.h> +#include <clk.h> +#include <clk-uclass.h> +#include <div64.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <asm/io.h> +#include <linux/clk-provider.h> +#include <linux/kernel.h> +#include <linux/log2.h> +#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-gate.c b/drivers/clk/ti/clk-gate.c new file mode 100644 index 0000000000..236eaed6df --- /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 <dariobin@libero.it> + * + * Loosely based on Linux kernel drivers/clk/ti/gate.c + */ + +#include <common.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <clk-uclass.h> +#include <asm/io.h> +#include <linux/clk-provider.h> + +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, +}; diff --git a/drivers/clk/ti/clk-mux.c b/drivers/clk/ti/clk-mux.c new file mode 100644 index 0000000000..419502c389 --- /dev/null +++ b/drivers/clk/ti/clk-mux.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * TI multiplexer clock support + * + * Copyright (C) 2020 Dario Binacchi <dariobin@libero.it> + * + * Based on Linux kernel drivers/clk/ti/mux.c + */ + +#include <common.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <clk-uclass.h> +#include <asm/io.h> +#include <linux/clk-provider.h> +#include "clk.h" + +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 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_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, +}; diff --git a/drivers/clk/clk-ti-sci.c b/drivers/clk/ti/clk-sci.c index 6f0fdaa111..6f0fdaa111 100644 --- a/drivers/clk/clk-ti-sci.c +++ b/drivers/clk/ti/clk-sci.c diff --git a/drivers/clk/ti/clk.c b/drivers/clk/ti/clk.c new file mode 100644 index 0000000000..e44b90ad6a --- /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 <dariobin@libero.it> + */ + +#include <common.h> +#include <asm/io.h> +#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 0000000000..601c3823f7 --- /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 <dariobin@libero.it> + */ + +#ifndef _CLK_TI_H +#define _CLK_TI_H + +void clk_ti_latch(fdt_addr_t reg, s8 shift); + +#endif /* #ifndef _CLK_TI_H */ diff --git a/drivers/clk/ti/omap4-cm.c b/drivers/clk/ti/omap4-cm.c new file mode 100644 index 0000000000..3cdc9b2888 --- /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 <dariobin@libero.it> + */ + +#include <common.h> +#include <dm.h> +#include <dm/lists.h> + +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, +}; diff --git a/drivers/core/Kconfig b/drivers/core/Kconfig index 65a503e76d..dbfe51c6e8 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 8b48aa5bc5..ed55f69de1 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 ca34d84922..bbe80136ba 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 2a6e43ddc6..7a5f4c0a73 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/read.c b/drivers/core/read.c index fc74d64814..4d9b5dd038 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/drivers/core/root.c b/drivers/core/root.c index 78de7cdf87..2bfa75b472 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/drivers/gpio/tca642x.c b/drivers/gpio/tca642x.c index 463cfe879a..7007c7a002 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)); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index b3bd5c6bb7..ccf81abbe9 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 f21ae7d76e..0b9d2698a3 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 0000000000..ac3d731d22 --- /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 <dariobin@libero.it> + * + * Based on Linux kernel drivers/pwm/pwm-tiehrpwm.c + */ + +#include <common.h> +#include <clk.h> +#include <div64.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <pwm.h> +#include <asm/io.h> + +#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), +}; diff --git a/drivers/remoteproc/ti_k3_arm64_rproc.c b/drivers/remoteproc/ti_k3_arm64_rproc.c index 1041f3805f..1f2415dc1a 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); diff --git a/drivers/spi/omap3_spi.c b/drivers/spi/omap3_spi.c index 78e2a25cdb..74931768c0 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; } diff --git a/drivers/spi/ti_qspi.c b/drivers/spi/ti_qspi.c index 7c3b1f7b88..76bc480f43 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; } diff --git a/drivers/timer/omap-timer.c b/drivers/timer/omap-timer.c index 7ac20d78dd..721e385fd1 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); diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 71363409f0..a3f8eeba5d 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 9db96aa891..76e3914678 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -22,8 +22,8 @@ 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_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/ti/Kconfig b/drivers/video/ti/Kconfig new file mode 100644 index 0000000000..3081e9e8c0 --- /dev/null +++ b/drivers/video/ti/Kconfig @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (C) 2020 Dario Binacchi <dariobin@libero.it> +# +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 0000000000..ddddd59216 --- /dev/null +++ b/drivers/video/ti/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (C) 2020 Dario Binacchi <dariobin@libero.it> +# + +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/am335x-fb.c b/drivers/video/ti/am335x-fb.c index e99a9185a2..5fa6f794ec 100644 --- a/drivers/video/am335x-fb.c +++ b/drivers/video/ti/am335x-fb.c @@ -12,16 +12,13 @@ * - starts output DMA from gd->fb_base buffer */ #include <common.h> -#include <dm.h> #include <lcd.h> #include <log.h> -#include <video.h> #include <asm/arch/clock.h> #include <asm/arch/hardware.h> #include <asm/arch/omap.h> #include <asm/arch/sys_proto.h> #include <asm/io.h> -#include <asm/utils.h> #include <linux/delay.h> #include <linux/err.h> #include "am335x-fb.h" @@ -110,6 +107,25 @@ struct am335x_lcdhw { 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; @@ -117,8 +133,6 @@ struct dpll_data { u8 rounded_div; }; -DECLARE_GLOBAL_DATA_PTR; - /** * am335x_dpll_round_rate() - Round a target rate for an OMAP DPLL * @@ -197,25 +211,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; @@ -320,301 +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, -}; - -/** - * 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) -{ - struct video_uc_plat *uc_plat = dev_get_uclass_plat(dev); - - uc_plat->base -= 0x20; - uc_plat->size += 0x20; - 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 tilcdc_panel_info *panel = &priv->panel; - struct display_timing *timing = &priv->timing; - struct cm_dpll *const cmdpll = (struct cm_dpll *)CM_DPLL; - u32 reg; - - /* 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); - - /* clock source for LCDC from dispPLL M2 */ - writel(0, &cmdpll->clklcdcpixelclk); - - /* 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, panel), - ®s->lcddma_fb0_ceiling); - writel(uc_plat->base, ®s->lcddma_fb1_base); - writel(uc_plat->base + FBSIZE(timing, panel), - ®s->lcddma_fb1_ceiling); - - reg = LCDC_DMA_CTRL_FIFO_TH(panel->fifo_th); - switch (panel->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(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); - - 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 (panel->invert_pxl_clk) - reg |= LCDC_RASTER_TIMING_2_PXCLK_INVERT; - - if (panel->sync_edge) - reg |= LCDC_RASTER_TIMING_2_HSVS_RISEFALL; - - if (panel->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); - - if (panel->tft_alt_mode) - reg |= LCDC_RASTER_CTRL_TFT_ALT_ENABLE; - - if (panel->bpp == 24) - reg |= LCDC_RASTER_CTRL_TFT_24BPP_MODE; - else if (panel->bpp == 32) - reg |= LCDC_RASTER_CTRL_TFT_24BPP_MODE | - LCDC_RASTER_CTRL_TFT_24BPP_UNPACK; - - if (panel->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; - - 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); - 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; - - 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; - } - - switch (panel->bpp) { - case 16: - case 24: - case 32: - break; - default: - dev_err(dev, "invalid seting, bpp: %d\n", panel->bpp); - return -EINVAL; - } - - 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); - 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); - - 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,tilcdc,panel" }, - { } -}; - -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/ti/am335x-fb.h index c9f92bc389..ad9b015e09 100644 --- a/drivers/video/am335x-fb.h +++ b/drivers/video/ti/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,6 +68,4 @@ struct am335x_lcdpanel { int am335xfb_init(struct am335x_lcdpanel *panel); -#endif /* CONFIG_DM_VIDEO */ - #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 0000000000..b90dfae4ae --- /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 <dariobin@libero.it> + */ + +#include <common.h> +#include <backlight.h> +#include <clk.h> +#include <display.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <log.h> +#include <panel.h> +#include <asm/gpio.h> +#include <linux/err.h> +#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 0000000000..6bcfbf8a8b --- /dev/null +++ b/drivers/video/ti/tilcdc-panel.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2020 Dario Binacchi <dariobin@libero.it> + */ + +#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 0000000000..d13cc11801 --- /dev/null +++ b/drivers/video/ti/tilcdc.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2020 Dario Binacchi <dariobin@libero.it> + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <lcd.h> +#include <log.h> +#include <panel.h> +#include <video.h> +#include <asm/io.h> +#include <asm/utils.h> +#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 0000000000..2645921df6 --- /dev/null +++ b/drivers/video/ti/tilcdc.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2020 Dario Binacchi <dariobin@libero.it> + */ + +#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 */ |
