From 1bf82082b9e4f079adaffc1dcee5306b082dc760 Mon Sep 17 00:00:00 2001 From: Sukrut Bellary Date: Fri, 30 May 2025 14:22:30 -0700 Subject: [PATCH 1/3] pwm: ti: am33xx: Enable Auxiliary PWM using eCAP In am33xx SoC[1], enhanced capture (eCAP) supports auxiliary PWM (APWM). This series adds the PWM driver support for the APWM feature for eCAP on AM33xx. eCAP HW also supports the capture mode. Currently, this driver only supports APWM. This is based on the Linux kernel driver -> drivers/pwm/pwm-tiecap.c Version: v6.12 Tested on AM335x EVM[2]. [1] AM335x TRM - https://www.ti.com/lit/ug/spruh73q/spruh73q.pdf [2] AM335x EVM - https://www.ti.com/tool/TMDXEVM3358 Signed-off-by: Sukrut Bellary --- drivers/pwm/Kconfig | 6 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-tiecap.c | 198 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 drivers/pwm/pwm-tiecap.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index de312656746..e4c676d75c2 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -126,3 +126,9 @@ config PWM_TI_EHRPWM default y help PWM driver support for the EHRPWM controller found on TI SOCs. + +config PWM_TI_ECAP + bool "Enable support for ECAP PWM" + depends on DM_PWM && ARCH_OMAP2PLUS + help + PWM driver support for the ECAP controller found on TI SOCs. diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 76305b93bc9..2682c536c6f 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -25,3 +25,4 @@ obj-$(CONFIG_PWM_TEGRA) += tegra_pwm.o obj-$(CONFIG_PWM_STM32) += pwm-stm32.o obj-$(CONFIG_PWM_SUNXI) += sunxi_pwm.o obj-$(CONFIG_PWM_TI_EHRPWM) += pwm-ti-ehrpwm.o +obj-$(CONFIG_PWM_TI_ECAP) += pwm-tiecap.o diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c new file mode 100644 index 00000000000..cfd6c871b57 --- /dev/null +++ b/drivers/pwm/pwm-tiecap.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ECAP PWM driver + * + * Copyright (C) 2025 BayLibre, SAS + * Author: Sukrut Bellary + */ + +#include +#include +#include +#include +#include +#include + +/* eCAP module registers */ +#define ECAP_PWM_CAP1 0x08 +#define ECAP_PWM_CAP2 0x0C +#define ECAP_PWM_CAP3 0x10 +#define ECAP_PWM_CAP4 0x14 + +#define ECAP_PWM_ECCTL2 0x2A +#define ECAP_PWM_ECCTL2_APWM_POL_LOW BIT(10) +#define ECAP_PWM_ECCTL2_APWM_MODE BIT(9) +#define ECAP_PWM_ECCTL2_TSCTR_FREERUN BIT(4) +#define ECAP_PWM_ECCTL2_SYNC_SEL_DISA (BIT(7) | BIT(6)) + +#define NSEC_PER_SEC 1000000000L + +enum tiecap_pwm_polarity { + TIECAP_PWM_POLARITY_NORMAL, + TIECAP_PWM_POLARITY_INVERSED +}; + +enum tiecap_pwm_state { + TIECAP_APWM_DISABLED, + TIECAP_APWM_ENABLED +}; + +struct tiecap_pwm_priv { + fdt_addr_t regs; + u32 clk_rate; + enum tiecap_pwm_state pwm_state; +}; + +static int tiecap_pwm_set_config(struct udevice *dev, uint channel, + uint period_ns, uint duty_ns) +{ + struct tiecap_pwm_priv *priv = dev_get_priv(dev); + u32 period_cycles, duty_cycles; + unsigned long long c; + u16 value; + + c = priv->clk_rate; + c = c * period_ns; + do_div(c, NSEC_PER_SEC); + period_cycles = (u32)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 = (u32)c; + } + + value = readw(priv->regs + ECAP_PWM_ECCTL2); + + /* Configure APWM mode & disable sync option */ + value |= ECAP_PWM_ECCTL2_APWM_MODE | ECAP_PWM_ECCTL2_SYNC_SEL_DISA; + + writew(value, priv->regs + ECAP_PWM_ECCTL2); + + if (priv->pwm_state == TIECAP_APWM_DISABLED) { + /* Update active registers */ + writel(duty_cycles, priv->regs + ECAP_PWM_CAP2); + writel(period_cycles, priv->regs + ECAP_PWM_CAP1); + } else { + /* Update shadow registers to configure period and + * compare values. This helps current pwm period to + * complete on reconfiguring. + */ + writel(duty_cycles, priv->regs + ECAP_PWM_CAP4); + writel(period_cycles, priv->regs + ECAP_PWM_CAP3); + } + + return 0; +} + +static int tiecap_pwm_set_enable(struct udevice *dev, uint channel, bool enable) +{ + struct tiecap_pwm_priv *priv = dev_get_priv(dev); + u16 value; + + value = readw(priv->regs + ECAP_PWM_ECCTL2); + + if (enable) { + /* + * Enable 'Free run Time stamp counter mode' to start counter + * and 'APWM mode' to enable APWM output + */ + value |= ECAP_PWM_ECCTL2_TSCTR_FREERUN | ECAP_PWM_ECCTL2_APWM_MODE; + priv->pwm_state = TIECAP_APWM_ENABLED; + } else { + /* Disable 'Free run Time stamp counter mode' to stop counter + * and 'APWM mode' to put APWM output to low + */ + value &= ~(ECAP_PWM_ECCTL2_TSCTR_FREERUN | ECAP_PWM_ECCTL2_APWM_MODE); + priv->pwm_state = TIECAP_APWM_DISABLED; + } + + writew(value, priv->regs + ECAP_PWM_ECCTL2); + + return 0; +} + +static int tiecap_pwm_set_invert(struct udevice *dev, uint channel, + bool polarity) +{ + struct tiecap_pwm_priv *priv = dev_get_priv(dev); + u16 value; + + value = readw(priv->regs + ECAP_PWM_ECCTL2); + + if (polarity == TIECAP_PWM_POLARITY_INVERSED) + /* Duty cycle defines LOW period of PWM */ + value |= ECAP_PWM_ECCTL2_APWM_POL_LOW; + else + /* Duty cycle defines HIGH period of PWM */ + value &= ~ECAP_PWM_ECCTL2_APWM_POL_LOW; + + writew(value, priv->regs + ECAP_PWM_ECCTL2); + + return 0; +} + +static int tiecap_pwm_of_to_plat(struct udevice *dev) +{ + struct tiecap_pwm_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%08x\n", priv->regs); + + return 0; +} + +static int tiecap_pwm_probe(struct udevice *dev) +{ + struct tiecap_pwm_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; + } + + return 0; +} + +static const struct pwm_ops tiecap_pwm_ops = { + .set_config = tiecap_pwm_set_config, + .set_enable = tiecap_pwm_set_enable, + .set_invert = tiecap_pwm_set_invert, +}; + +static const struct udevice_id tiecap_pwm_ids[] = { + { .compatible = "ti,am3352-ecap" }, + { .compatible = "ti,am33xx-ecap" }, + { } +}; + +U_BOOT_DRIVER(tiecap_pwm) = { + .name = "tiecap_pwm", + .id = UCLASS_PWM, + .of_match = tiecap_pwm_ids, + .ops = &tiecap_pwm_ops, + .probe = tiecap_pwm_probe, + .of_to_plat = tiecap_pwm_of_to_plat, + .priv_auto = sizeof(struct tiecap_pwm_priv), +}; From d0bcbf782f5909b36d0af705e95c5c8ad4092bfc Mon Sep 17 00:00:00 2001 From: Sukrut Bellary Date: Fri, 30 May 2025 14:22:31 -0700 Subject: [PATCH 2/3] pwm: ti: am33xx: Fix build warnings in dev_dbg() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If CONFIG_PWM_TI_EHRPWM is enabled, it throws the build warning in dev_dbg() due to incorrect format specifier as, "warning: format ‘%lx’ expects argument of type ‘long unsigned int’, but argument 4 has type ‘fdt_addr_t’ {aka ‘unsigned int’}". Fix this with the correct format specifier. Signed-off-by: Sukrut Bellary --- drivers/pwm/pwm-ti-ehrpwm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-ti-ehrpwm.c b/drivers/pwm/pwm-ti-ehrpwm.c index 563109ef0f8..135ea3b4321 100644 --- a/drivers/pwm/pwm-ti-ehrpwm.c +++ b/drivers/pwm/pwm-ti-ehrpwm.c @@ -399,7 +399,7 @@ static int ti_ehrpwm_of_to_plat(struct udevice *dev) return -EINVAL; } - dev_dbg(dev, "regs=0x%08lx\n", priv->regs); + dev_dbg(dev, "regs=0x%08x\n", priv->regs); return 0; } From 04df37a095cb08edb865b218398550341a6118c1 Mon Sep 17 00:00:00 2001 From: Sukrut Bellary Date: Fri, 30 May 2025 14:22:32 -0700 Subject: [PATCH 3/3] board: ti: am335x: Enable eCAP0 PWM and LCD pin muxing On AM335x EVM[1], 1. pin AM335X_ECAP0_IN_PWM0_OUT is used to enable the backlight. ECAP0 can be configured in single channel PWM mode. If CONFIG_PWM_TI_ECAP is enabled, perform eCAP0 pin muxing. 2. Pins LCD_DATA0 - LCD_DATA15, gpmc_ad8 - gpmc_ad15 and lcd_vsync, lcd_sync, lcd_ac_bias_en, lcd_pclk are used for driving LCD. If CONFIG_AM335X_LCD is enabled, perform the LCD pin muxing. This is required to enable splash screen support on AM335x EVM. [1] AM335x EVM - https://www.ti.com/tool/TMDXEVM3358 Signed-off-by: Sukrut Bellary --- board/ti/am335x/mux.c | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/board/ti/am335x/mux.c b/board/ti/am335x/mux.c index 960de15398f..d416d88c1a1 100644 --- a/board/ti/am335x/mux.c +++ b/board/ti/am335x/mux.c @@ -274,6 +274,47 @@ static struct module_pin_mux uart3_icev2_pin_mux[] = { {-1}, }; +#if (IS_ENABLED(CONFIG_AM335X_LCD)) +static struct module_pin_mux lcd_pin_mux[] = { + {OFFSET(lcd_data0), (MODE(0))}, /* LCD-Data(0) */ + {OFFSET(lcd_data1), (MODE(0))}, /* LCD-Data(1) */ + {OFFSET(lcd_data2), (MODE(0))}, /* LCD-Data(2) */ + {OFFSET(lcd_data3), (MODE(0))}, /* LCD-Data(3) */ + {OFFSET(lcd_data4), (MODE(0))}, /* LCD-Data(4) */ + {OFFSET(lcd_data5), (MODE(0))}, /* LCD-Data(5) */ + {OFFSET(lcd_data6), (MODE(0))}, /* LCD-Data(6) */ + {OFFSET(lcd_data7), (MODE(0))}, /* LCD-Data(7) */ + {OFFSET(lcd_data8), (MODE(0))}, /* LCD-Data(8) */ + {OFFSET(lcd_data9), (MODE(0))}, /* LCD-Data(9) */ + {OFFSET(lcd_data10), (MODE(0))}, /* LCD-Data(10) */ + {OFFSET(lcd_data11), (MODE(0))}, /* LCD-Data(11) */ + {OFFSET(lcd_data12), (MODE(0))}, /* LCD-Data(12) */ + {OFFSET(lcd_data13), (MODE(0))}, /* LCD-Data(13) */ + {OFFSET(lcd_data14), (MODE(0))}, /* LCD-Data(14) */ + {OFFSET(lcd_data15), (MODE(0))}, /* LCD-Data(15) */ + {OFFSET(gpmc_ad15), (MODE(1))}, /* LCD-Data(16) */ + {OFFSET(gpmc_ad14), (MODE(1))}, /* LCD-Data(17) */ + {OFFSET(gpmc_ad13), (MODE(1))}, /* LCD-Data(18) */ + {OFFSET(gpmc_ad12), (MODE(1))}, /* LCD-Data(19) */ + {OFFSET(gpmc_ad11), (MODE(1))}, /* LCD-Data(20) */ + {OFFSET(gpmc_ad10), (MODE(1))}, /* LCD-Data(21) */ + {OFFSET(gpmc_ad9), (MODE(1))}, /* LCD-Data(22) */ + {OFFSET(gpmc_ad8), (MODE(1))}, /* LCD-Data(23) */ + {OFFSET(lcd_vsync), (MODE(0))}, /* LCD-VSync */ + {OFFSET(lcd_hsync), (MODE(0))}, /* LCD-HSync */ + {OFFSET(lcd_ac_bias_en), (MODE(0))}, /* LCD-DE */ + {OFFSET(lcd_pclk), (MODE(0))}, /* LCD-CLK */ + {-1}, +}; +#endif + +#if (IS_ENABLED(CONFIG_PWM_TI_ECAP)) +static struct module_pin_mux ecap_pin_mux[] = { + {OFFSET(ecap0_in_pwm0_out), (MODE(0))}, /* ecap0_in_pwm0_out */ + {-1}, +}; +#endif + #if defined(CONFIG_NOR_BOOT) void enable_norboot_pin_mux(void) { @@ -389,6 +430,13 @@ void enable_board_pin_mux(void) configure_module_pin_mux(mmc1_pin_mux); configure_module_pin_mux(spi0_pin_mux); } + #if IS_ENABLED(CONFIG_AM335X_LCD) + configure_module_pin_mux(lcd_pin_mux); + #endif + + #if IS_ENABLED(CONFIG_PWM_TI_ECAP) + configure_module_pin_mux(ecap_pin_mux); + #endif } else if (board_is_idk()) { /* Industrial Motor Control (IDK) */ configure_module_pin_mux(mii1_pin_mux);