Blame view

drivers/pwm/exynos_pwm.c 2.8 KB
d41ce506b   Eric Lee   Initial Release, ...
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
121
122
123
124
  /*
   * 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
  ",
  	      __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
  ", __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
  ", __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
  ", __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),
  };