Blame view

drivers/pwm/sunxi_pwm.c 4.1 KB
1c353aea2   Vasily Khoruzhick   pwm: sunxi: add s...
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
  // SPDX-License-Identifier: GPL-2.0+
  /*
   * Copyright (c) 2017-2018 Vasily Khoruzhick <anarsoul@gmail.com>
   */
  
  #include <common.h>
  #include <div64.h>
  #include <dm.h>
  #include <pwm.h>
  #include <regmap.h>
  #include <syscon.h>
  #include <asm/io.h>
  #include <asm/arch/pwm.h>
  #include <asm/arch/gpio.h>
  #include <power/regulator.h>
  
  DECLARE_GLOBAL_DATA_PTR;
  
  #define OSC_24MHZ 24000000
  
  struct sunxi_pwm_priv {
  	struct sunxi_pwm *regs;
  	bool invert;
  	u32 prescaler;
  };
  
  static const u32 prescaler_table[] = {
  	120,	/* 0000 */
  	180,	/* 0001 */
  	240,	/* 0010 */
  	360,	/* 0011 */
  	480,	/* 0100 */
  	0,	/* 0101 */
  	0,	/* 0110 */
  	0,	/* 0111 */
  	12000,	/* 1000 */
  	24000,	/* 1001 */
  	36000,	/* 1010 */
  	48000,	/* 1011 */
  	72000,	/* 1100 */
  	0,	/* 1101 */
  	0,	/* 1110 */
  	1,	/* 1111 */
  };
  
  static int sunxi_pwm_config_pinmux(void)
  {
  #ifdef CONFIG_MACH_SUN50I
  	sunxi_gpio_set_cfgpin(SUNXI_GPD(22), SUNXI_GPD_PWM);
  #endif
  	return 0;
  }
  
  static int sunxi_pwm_set_invert(struct udevice *dev, uint channel,
  				bool polarity)
  {
  	struct sunxi_pwm_priv *priv = dev_get_priv(dev);
  
  	debug("%s: polarity=%u
  ", __func__, polarity);
  	priv->invert = polarity;
  
  	return 0;
  }
  
  static int sunxi_pwm_set_config(struct udevice *dev, uint channel,
  				uint period_ns, uint duty_ns)
  {
  	struct sunxi_pwm_priv *priv = dev_get_priv(dev);
  	struct sunxi_pwm *regs = priv->regs;
c33ba7ec8   Vasily Khoruzhick   pwm: sunxi: choos...
71
72
73
  	int best_prescaler = 0;
  	u32 v, best_period = 0, duty;
  	u64 best_scaled_freq = 0;
1c353aea2   Vasily Khoruzhick   pwm: sunxi: add s...
74
75
76
77
  	const u32 nsecs_per_sec = 1000000000U;
  
  	debug("%s: period_ns=%u, duty_ns=%u
  ", __func__, period_ns, duty_ns);
c33ba7ec8   Vasily Khoruzhick   pwm: sunxi: choos...
78
  	for (int prescaler = 0; prescaler <= SUNXI_PWM_CTRL_PRESCALE0_MASK;
1c353aea2   Vasily Khoruzhick   pwm: sunxi: add s...
79
  	     prescaler++) {
c33ba7ec8   Vasily Khoruzhick   pwm: sunxi: choos...
80
81
  		u32 period = 0;
  		u64 scaled_freq = 0;
1c353aea2   Vasily Khoruzhick   pwm: sunxi: add s...
82
83
84
85
  		if (!prescaler_table[prescaler])
  			continue;
  		scaled_freq = lldiv(OSC_24MHZ, prescaler_table[prescaler]);
  		period = lldiv(scaled_freq * period_ns, nsecs_per_sec);
c33ba7ec8   Vasily Khoruzhick   pwm: sunxi: choos...
86
87
88
89
90
91
  		if ((period - 1 <= SUNXI_PWM_CH0_PERIOD_MAX) &&
  		    best_period < period) {
  			best_period = period;
  			best_scaled_freq = scaled_freq;
  			best_prescaler = prescaler;
  		}
1c353aea2   Vasily Khoruzhick   pwm: sunxi: add s...
92
  	}
c33ba7ec8   Vasily Khoruzhick   pwm: sunxi: choos...
93
  	if (best_period - 1 > SUNXI_PWM_CH0_PERIOD_MAX) {
1c353aea2   Vasily Khoruzhick   pwm: sunxi: add s...
94
95
96
97
  		debug("%s: failed to find prescaler value
  ", __func__);
  		return -EINVAL;
  	}
c33ba7ec8   Vasily Khoruzhick   pwm: sunxi: choos...
98
  	duty = lldiv(best_scaled_freq * duty_ns, nsecs_per_sec);
1c353aea2   Vasily Khoruzhick   pwm: sunxi: add s...
99

c33ba7ec8   Vasily Khoruzhick   pwm: sunxi: choos...
100
  	if (priv->prescaler != best_prescaler) {
1c353aea2   Vasily Khoruzhick   pwm: sunxi: add s...
101
102
103
104
105
  		/* Mask clock to update prescaler */
  		v = readl(&regs->ctrl);
  		v &= ~SUNXI_PWM_CTRL_CLK_GATE;
  		writel(v, &regs->ctrl);
  		v &= ~SUNXI_PWM_CTRL_PRESCALE0_MASK;
c33ba7ec8   Vasily Khoruzhick   pwm: sunxi: choos...
106
  		v |= (best_prescaler & SUNXI_PWM_CTRL_PRESCALE0_MASK);
1c353aea2   Vasily Khoruzhick   pwm: sunxi: add s...
107
108
109
  		writel(v, &regs->ctrl);
  		v |= SUNXI_PWM_CTRL_CLK_GATE;
  		writel(v, &regs->ctrl);
c33ba7ec8   Vasily Khoruzhick   pwm: sunxi: choos...
110
  		priv->prescaler = best_prescaler;
1c353aea2   Vasily Khoruzhick   pwm: sunxi: add s...
111
  	}
c33ba7ec8   Vasily Khoruzhick   pwm: sunxi: choos...
112
  	writel(SUNXI_PWM_CH0_PERIOD_PRD(best_period) |
1c353aea2   Vasily Khoruzhick   pwm: sunxi: add s...
113
114
115
116
117
  	       SUNXI_PWM_CH0_PERIOD_DUTY(duty), &regs->ch0_period);
  
  	debug("%s: prescaler: %d, period: %d, duty: %d
  ",
  	      __func__, priv->prescaler,
c33ba7ec8   Vasily Khoruzhick   pwm: sunxi: choos...
118
  	      best_period, duty);
1c353aea2   Vasily Khoruzhick   pwm: sunxi: add s...
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
  
  	return 0;
  }
  
  static int sunxi_pwm_set_enable(struct udevice *dev, uint channel, bool enable)
  {
  	struct sunxi_pwm_priv *priv = dev_get_priv(dev);
  	struct sunxi_pwm *regs = priv->regs;
  	u32 v;
  
  	debug("%s: Enable '%s'
  ", __func__, dev->name);
  
  	v = readl(&regs->ctrl);
  	if (!enable) {
  		v &= ~SUNXI_PWM_CTRL_ENABLE0;
  		writel(v, &regs->ctrl);
  		return 0;
  	}
  
  	sunxi_pwm_config_pinmux();
  
  	if (priv->invert)
  		v &= ~SUNXI_PWM_CTRL_CH0_ACT_STA;
  	else
  		v |= SUNXI_PWM_CTRL_CH0_ACT_STA;
  	v |= SUNXI_PWM_CTRL_ENABLE0;
  	writel(v, &regs->ctrl);
  
  	return 0;
  }
  
  static int sunxi_pwm_ofdata_to_platdata(struct udevice *dev)
  {
  	struct sunxi_pwm_priv *priv = dev_get_priv(dev);
  
  	priv->regs = (struct sunxi_pwm *)devfdt_get_addr(dev);
  
  	return 0;
  }
  
  static int sunxi_pwm_probe(struct udevice *dev)
  {
  	return 0;
  }
  
  static const struct pwm_ops sunxi_pwm_ops = {
  	.set_invert	= sunxi_pwm_set_invert,
  	.set_config	= sunxi_pwm_set_config,
  	.set_enable	= sunxi_pwm_set_enable,
  };
  
  static const struct udevice_id sunxi_pwm_ids[] = {
  	{ .compatible = "allwinner,sun5i-a13-pwm" },
  	{ .compatible = "allwinner,sun50i-a64-pwm" },
  	{ }
  };
  
  U_BOOT_DRIVER(sunxi_pwm) = {
  	.name	= "sunxi_pwm",
  	.id	= UCLASS_PWM,
  	.of_match = sunxi_pwm_ids,
  	.ops	= &sunxi_pwm_ops,
  	.ofdata_to_platdata	= sunxi_pwm_ofdata_to_platdata,
  	.probe		= sunxi_pwm_probe,
  	.priv_auto_alloc_size	= sizeof(struct sunxi_pwm_priv),
  };