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