| // 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), |
| }; |