hc
2024-08-12 233ab1bd4c5697f5cdec94e60206e8c6ac609b4c
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
/*
 * 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(&regs->tcfg0);
   prescaler = (channel < 2 ? val : (val >> 8)) & 0xff;
   div = (readl(&regs->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, &regs->tcntb0 + offset);
       writel(tcmp, &regs->tcmpb0 + offset);
   }
 
   tcon = readl(&regs->tcon);
   tcon |= TCON_UPDATE(channel);
   if (channel < 4)
       tcon |= TCON_AUTO_RELOAD(channel);
   else
       tcon |= TCON4_AUTO_RELOAD;
   writel(tcon, &regs->tcon);
 
   tcon &= ~TCON_UPDATE(channel);
   writel(tcon, &regs->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(&regs->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, &regs->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 *)devfdt_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),
};