1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Copyright 2016 Google Inc. 4 */ 5 6#include <dm.h> 7#include <log.h> 8#include <pwm.h> 9#include <asm/io.h> 10#include <asm/arch/clk.h> 11#include <asm/arch/clock.h> 12#include <asm/arch/pwm.h> 13 14struct exynos_pwm_priv { 15 struct s5p_timer *regs; 16}; 17 18static int exynos_pwm_set_config(struct udevice *dev, uint channel, 19 uint period_ns, uint duty_ns) 20{ 21 struct exynos_pwm_priv *priv = dev_get_priv(dev); 22 struct s5p_timer *regs = priv->regs; 23 unsigned int offset, prescaler; 24 uint div = 4, rate, rate_ns; 25 u32 val; 26 u32 tcnt, tcmp, tcon; 27 28 if (channel >= 5) 29 return -EINVAL; 30 debug("%s: Configure '%s' channel %u, period_ns %u, duty_ns %u\n", 31 __func__, dev->name, channel, period_ns, duty_ns); 32 33 val = readl(®s->tcfg0); 34 prescaler = (channel < 2 ? val : (val >> 8)) & 0xff; 35 div = (readl(®s->tcfg1) >> MUX_DIV_SHIFT(channel)) & 0xf; 36 37 rate = get_pwm_clk() / ((prescaler + 1) * (1 << div)); 38 debug("%s: pwm_clk %lu, rate %u\n", __func__, get_pwm_clk(), rate); 39 40 if (channel < 4) { 41 rate_ns = 1000000000 / rate; 42 tcnt = period_ns / rate_ns; 43 tcmp = duty_ns / rate_ns; 44 debug("%s: tcnt %u, tcmp %u\n", __func__, tcnt, tcmp); 45 46 /* Ensure that the comparitor will actually hit the target */ 47 if (tcmp == tcnt) 48 tcmp = tcnt - 1; 49 offset = channel * 3; 50 writel(tcnt, ®s->tcntb0 + offset); 51 writel(tcmp, ®s->tcmpb0 + offset); 52 } 53 54 tcon = readl(®s->tcon); 55 tcon |= TCON_UPDATE(channel); 56 if (channel < 4) 57 tcon |= TCON_AUTO_RELOAD(channel); 58 else 59 tcon |= TCON4_AUTO_RELOAD; 60 writel(tcon, ®s->tcon); 61 62 tcon &= ~TCON_UPDATE(channel); 63 writel(tcon, ®s->tcon); 64 65 return 0; 66} 67 68static int exynos_pwm_set_enable(struct udevice *dev, uint channel, 69 bool enable) 70{ 71 struct exynos_pwm_priv *priv = dev_get_priv(dev); 72 struct s5p_timer *regs = priv->regs; 73 u32 mask; 74 75 if (channel >= 4) 76 return -EINVAL; 77 debug("%s: Enable '%s' channel %u\n", __func__, dev->name, channel); 78 mask = TCON_START(channel); 79 clrsetbits_le32(®s->tcon, mask, enable ? mask : 0); 80 81 return 0; 82} 83 84static int exynos_pwm_probe(struct udevice *dev) 85{ 86 struct exynos_pwm_priv *priv = dev_get_priv(dev); 87 struct s5p_timer *regs = priv->regs; 88 89 writel(PRESCALER_0 | PRESCALER_1 << 8, ®s->tcfg0); 90 91 return 0; 92} 93 94static int exynos_pwm_of_to_plat(struct udevice *dev) 95{ 96 struct exynos_pwm_priv *priv = dev_get_priv(dev); 97 98 priv->regs = dev_read_addr_ptr(dev); 99 100 return 0; 101} 102 103static const struct pwm_ops exynos_pwm_ops = { 104 .set_config = exynos_pwm_set_config, 105 .set_enable = exynos_pwm_set_enable, 106}; 107 108static const struct udevice_id exynos_channels[] = { 109 { .compatible = "samsung,exynos4210-pwm" }, 110 { } 111}; 112 113U_BOOT_DRIVER(exynos_pwm) = { 114 .name = "exynos_pwm", 115 .id = UCLASS_PWM, 116 .of_match = exynos_channels, 117 .ops = &exynos_pwm_ops, 118 .probe = exynos_pwm_probe, 119 .of_to_plat = exynos_pwm_of_to_plat, 120 .priv_auto = sizeof(struct exynos_pwm_priv), 121}; 122