Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | // SPDX-License-Identifier: GPL-2.0+ /* * Copyright 2016 Google Inc. */ #include <dm.h> #include <clk.h> #include <div64.h> #include <log.h> #include <pwm.h> #include <asm/io.h> #include <asm/arch/clock.h> #include <asm/arch/pwm.h> #include <linux/time.h> #define PWM_PDIV_WIDTH 8 #define PWM_PDIV_MAX BIT(PWM_PDIV_WIDTH) #define PWM_FDIV_WIDTH 13 struct tegra_pwm_priv { struct pwm_ctlr *regs; u64 clk_rate; u32 min_period_ns; u8 polarity; }; static int tegra_pwm_set_invert(struct udevice *dev, uint channel, bool polarity) { struct tegra_pwm_priv *priv = dev_get_priv(dev); if (channel >= 4) return -EINVAL; clrsetbits_8(&priv->polarity, BIT(channel), (polarity << channel)); return 0; } static int tegra_pwm_set_config(struct udevice *dev, uint channel, uint period_ns, uint duty_ns) { struct tegra_pwm_priv *priv = dev_get_priv(dev); struct pwm_ctlr *regs = priv->regs; u64 pulse_width; u32 reg; s64 rate; if (channel >= 4) return -EINVAL; debug("%s: Configure '%s' channel %u\n", __func__, dev->name, channel); if (period_ns < priv->min_period_ns) { debug("%s: Channel %u period too low, period_ns %u minimum %u\n", __func__, channel, period_ns, priv->min_period_ns); return -EINVAL; } /* * Convert from duty_ns / period_ns to a fixed number of duty ticks * per (1 << PWM_PDIV_WIDTH) cycles and make sure to round to the * nearest integer during division. */ pulse_width = duty_ns * PWM_PDIV_MAX; pulse_width = DIV_ROUND_CLOSEST_ULL(pulse_width, period_ns); if (priv->polarity & BIT(channel)) pulse_width = PWM_PDIV_MAX - pulse_width; if (pulse_width > PWM_PDIV_MAX) { debug("%s: Channel %u pulse_width too high %llu\n", __func__, channel, pulse_width); return -EINVAL; } /* * Since the actual PWM divider is the register's frequency divider * field plus 1, we need to decrement to get the correct value to * write to the register. */ rate = (priv->clk_rate * period_ns) / ((u64)NSEC_PER_SEC << PWM_PDIV_WIDTH) - 1; if (rate < 0) { debug("%s: Channel %u rate is not positive\n", __func__, channel); return -EINVAL; } if (rate >> PWM_FDIV_WIDTH) { debug("%s: Channel %u rate too high %llu\n", __func__, channel, rate); return -EINVAL; } reg = pulse_width << PWM_WIDTH_SHIFT; reg |= rate << PWM_DIVIDER_SHIFT; reg |= PWM_ENABLE_MASK; writel(reg, ®s[channel].control); return 0; } static int tegra_pwm_set_enable(struct udevice *dev, uint channel, bool enable) { struct tegra_pwm_priv *priv = dev_get_priv(dev); struct pwm_ctlr *regs = priv->regs; if (channel >= 4) return -EINVAL; debug("%s: Enable '%s' channel %u\n", __func__, dev->name, channel); clrsetbits_le32(®s[channel].control, PWM_ENABLE_MASK, enable ? PWM_ENABLE_MASK : 0); return 0; } static int tegra_pwm_of_to_plat(struct udevice *dev) { struct tegra_pwm_priv *priv = dev_get_priv(dev); priv->regs = dev_read_addr_ptr(dev); return 0; } static int tegra_pwm_probe(struct udevice *dev) { struct tegra_pwm_priv *priv = dev_get_priv(dev); const u32 pwm_max_freq = dev_get_driver_data(dev); struct clk *clk; clk = devm_clk_get(dev, NULL); if (IS_ERR(clk)) { debug("%s: Could not get PWM clock: %ld\n", __func__, PTR_ERR(clk)); return PTR_ERR(clk); } priv->clk_rate = clock_start_periph_pll(clk->id, CLOCK_ID_PERIPH, pwm_max_freq); priv->min_period_ns = (NSEC_PER_SEC / (pwm_max_freq >> PWM_PDIV_WIDTH)) + 1; debug("%s: clk_rate = %llu min_period_ns = %u\n", __func__, priv->clk_rate, priv->min_period_ns); return 0; } static const struct pwm_ops tegra_pwm_ops = { .set_config = tegra_pwm_set_config, .set_enable = tegra_pwm_set_enable, .set_invert = tegra_pwm_set_invert, }; static const struct udevice_id tegra_pwm_ids[] = { { .compatible = "nvidia,tegra20-pwm", .data = 48 * 1000000 }, { .compatible = "nvidia,tegra114-pwm", .data = 408 * 1000000 }, { } }; U_BOOT_DRIVER(tegra_pwm) = { .name = "tegra_pwm", .id = UCLASS_PWM, .of_match = tegra_pwm_ids, .ops = &tegra_pwm_ops, .of_to_plat = tegra_pwm_of_to_plat, .probe = tegra_pwm_probe, .priv_auto = sizeof(struct tegra_pwm_priv), }; |