diff options
Diffstat (limited to 'drivers/pwm/exynos_pwm.c')
-rw-r--r-- | drivers/pwm/exynos_pwm.c | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/drivers/pwm/exynos_pwm.c b/drivers/pwm/exynos_pwm.c new file mode 100644 index 00000000000..a0edafce40b --- /dev/null +++ b/drivers/pwm/exynos_pwm.c @@ -0,0 +1,120 @@ +/* + * Copyright 2016 Google Inc. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <pwm.h> +#include <asm/io.h> +#include <asm/arch/clk.h> +#include <asm/arch/clock.h> +#include <asm/arch/pwm.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct exynos_pwm_priv { + struct s5p_timer *regs; +}; + +static int exynos_pwm_set_config(struct udevice *dev, uint channel, + uint period_ns, uint duty_ns) +{ + struct exynos_pwm_priv *priv = dev_get_priv(dev); + struct s5p_timer *regs = priv->regs; + unsigned int offset, prescaler; + uint div = 4, rate, rate_ns; + u32 val; + u32 tcnt, tcmp, tcon; + + if (channel >= 5) + return -EINVAL; + debug("%s: Configure '%s' channel %u, period_ns %u, duty_ns %u\n", + __func__, dev->name, channel, period_ns, duty_ns); + + val = readl(®s->tcfg0); + prescaler = (channel < 2 ? val : (val >> 8)) & 0xff; + div = (readl(®s->tcfg1) >> MUX_DIV_SHIFT(channel)) & 0xf; + + rate = get_pwm_clk() / ((prescaler + 1) * (1 << div)); + debug("%s: pwm_clk %lu, rate %u\n", __func__, get_pwm_clk(), rate); + + if (channel < 4) { + rate_ns = 1000000000 / rate; + tcnt = period_ns / rate_ns; + tcmp = duty_ns / rate_ns; + debug("%s: tcnt %u, tcmp %u\n", __func__, tcnt, tcmp); + offset = channel * 3; + writel(tcnt, ®s->tcntb0 + offset); + writel(tcmp, ®s->tcmpb0 + offset); + } + + tcon = readl(®s->tcon); + tcon |= TCON_UPDATE(channel); + if (channel < 4) + tcon |= TCON_AUTO_RELOAD(channel); + else + tcon |= TCON4_AUTO_RELOAD; + writel(tcon, ®s->tcon); + + tcon &= ~TCON_UPDATE(channel); + writel(tcon, ®s->tcon); + + return 0; +} + +static int exynos_pwm_set_enable(struct udevice *dev, uint channel, + bool enable) +{ + struct exynos_pwm_priv *priv = dev_get_priv(dev); + struct s5p_timer *regs = priv->regs; + u32 mask; + + if (channel >= 4) + return -EINVAL; + debug("%s: Enable '%s' channel %u\n", __func__, dev->name, channel); + mask = TCON_START(channel); + clrsetbits_le32(®s->tcon, mask, enable ? mask : 0); + + return 0; +} + +static int exynos_pwm_probe(struct udevice *dev) +{ + struct exynos_pwm_priv *priv = dev_get_priv(dev); + struct s5p_timer *regs = priv->regs; + + writel(PRESCALER_0 | PRESCALER_1 << 8, ®s->tcfg0); + + return 0; +} + +static int exynos_pwm_ofdata_to_platdata(struct udevice *dev) +{ + struct exynos_pwm_priv *priv = dev_get_priv(dev); + + priv->regs = (struct s5p_timer *)dev_get_addr(dev); + + return 0; +} + +static const struct pwm_ops exynos_pwm_ops = { + .set_config = exynos_pwm_set_config, + .set_enable = exynos_pwm_set_enable, +}; + +static const struct udevice_id exynos_channels[] = { + { .compatible = "samsung,exynos4210-pwm" }, + { } +}; + +U_BOOT_DRIVER(exynos_pwm) = { + .name = "exynos_pwm", + .id = UCLASS_PWM, + .of_match = exynos_channels, + .ops = &exynos_pwm_ops, + .probe = exynos_pwm_probe, + .ofdata_to_platdata = exynos_pwm_ofdata_to_platdata, + .priv_auto_alloc_size = sizeof(struct exynos_pwm_priv), +}; |