]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
5c2dd4cd SG |
2 | /* |
3 | * Copyright 2016 Google Inc. | |
5c2dd4cd SG |
4 | */ |
5 | ||
6 | #include <common.h> | |
7 | #include <dm.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 | ||
5c2dd4cd SG |
14 | struct exynos_pwm_priv { |
15 | struct s5p_timer *regs; | |
16 | }; | |
17 | ||
18 | static 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 | offset = channel * 3; | |
46 | writel(tcnt, ®s->tcntb0 + offset); | |
47 | writel(tcmp, ®s->tcmpb0 + offset); | |
48 | } | |
49 | ||
50 | tcon = readl(®s->tcon); | |
51 | tcon |= TCON_UPDATE(channel); | |
52 | if (channel < 4) | |
53 | tcon |= TCON_AUTO_RELOAD(channel); | |
54 | else | |
55 | tcon |= TCON4_AUTO_RELOAD; | |
56 | writel(tcon, ®s->tcon); | |
57 | ||
58 | tcon &= ~TCON_UPDATE(channel); | |
59 | writel(tcon, ®s->tcon); | |
60 | ||
61 | return 0; | |
62 | } | |
63 | ||
64 | static int exynos_pwm_set_enable(struct udevice *dev, uint channel, | |
65 | bool enable) | |
66 | { | |
67 | struct exynos_pwm_priv *priv = dev_get_priv(dev); | |
68 | struct s5p_timer *regs = priv->regs; | |
69 | u32 mask; | |
70 | ||
71 | if (channel >= 4) | |
72 | return -EINVAL; | |
73 | debug("%s: Enable '%s' channel %u\n", __func__, dev->name, channel); | |
74 | mask = TCON_START(channel); | |
75 | clrsetbits_le32(®s->tcon, mask, enable ? mask : 0); | |
76 | ||
77 | return 0; | |
78 | } | |
79 | ||
80 | static int exynos_pwm_probe(struct udevice *dev) | |
81 | { | |
82 | struct exynos_pwm_priv *priv = dev_get_priv(dev); | |
83 | struct s5p_timer *regs = priv->regs; | |
84 | ||
85 | writel(PRESCALER_0 | PRESCALER_1 << 8, ®s->tcfg0); | |
86 | ||
87 | return 0; | |
88 | } | |
89 | ||
90 | static int exynos_pwm_ofdata_to_platdata(struct udevice *dev) | |
91 | { | |
92 | struct exynos_pwm_priv *priv = dev_get_priv(dev); | |
93 | ||
a821c4af | 94 | priv->regs = (struct s5p_timer *)devfdt_get_addr(dev); |
5c2dd4cd SG |
95 | |
96 | return 0; | |
97 | } | |
98 | ||
99 | static const struct pwm_ops exynos_pwm_ops = { | |
100 | .set_config = exynos_pwm_set_config, | |
101 | .set_enable = exynos_pwm_set_enable, | |
102 | }; | |
103 | ||
104 | static const struct udevice_id exynos_channels[] = { | |
105 | { .compatible = "samsung,exynos4210-pwm" }, | |
106 | { } | |
107 | }; | |
108 | ||
109 | U_BOOT_DRIVER(exynos_pwm) = { | |
110 | .name = "exynos_pwm", | |
111 | .id = UCLASS_PWM, | |
112 | .of_match = exynos_channels, | |
113 | .ops = &exynos_pwm_ops, | |
114 | .probe = exynos_pwm_probe, | |
115 | .ofdata_to_platdata = exynos_pwm_ofdata_to_platdata, | |
116 | .priv_auto_alloc_size = sizeof(struct exynos_pwm_priv), | |
117 | }; |