summaryrefslogtreecommitdiffstats
path: root/drivers/pwm/sunxi_pwm.c
blob: e3d5ee456b003b59b4e59d1c0b6ce086d1239164 (plain)
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
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
186
// 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 <log.h>
#include <pwm.h>
#include <regmap.h>
#include <syscon.h>
#include <asm/global_data.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\n", __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;
	int best_prescaler = 0;
	u32 v, best_period = 0, duty;
	u64 best_scaled_freq = 0;
	const u32 nsecs_per_sec = 1000000000U;

	debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns);

	for (int prescaler = 0; prescaler <= SUNXI_PWM_CTRL_PRESCALE0_MASK;
	     prescaler++) {
		u32 period = 0;
		u64 scaled_freq = 0;
		if (!prescaler_table[prescaler])
			continue;
		scaled_freq = lldiv(OSC_24MHZ, prescaler_table[prescaler]);
		period = lldiv(scaled_freq * period_ns, nsecs_per_sec);
		if ((period - 1 <= SUNXI_PWM_CH0_PERIOD_MAX) &&
		    best_period < period) {
			best_period = period;
			best_scaled_freq = scaled_freq;
			best_prescaler = prescaler;
		}
	}

	if (best_period - 1 > SUNXI_PWM_CH0_PERIOD_MAX) {
		debug("%s: failed to find prescaler value\n", __func__);
		return -EINVAL;
	}

	duty = lldiv(best_scaled_freq * duty_ns, nsecs_per_sec);

	if (priv->prescaler != best_prescaler) {
		/* 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;
		v |= (best_prescaler & SUNXI_PWM_CTRL_PRESCALE0_MASK);
		writel(v, &regs->ctrl);
		v |= SUNXI_PWM_CTRL_CLK_GATE;
		writel(v, &regs->ctrl);
		priv->prescaler = best_prescaler;
	}

	writel(SUNXI_PWM_CH0_PERIOD_PRD(best_period) |
	       SUNXI_PWM_CH0_PERIOD_DUTY(duty), &regs->ch0_period);

	debug("%s: prescaler: %d, period: %d, duty: %d\n",
	      __func__, priv->prescaler,
	      best_period, duty);

	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'\n", __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_of_to_plat(struct udevice *dev)
{
	struct sunxi_pwm_priv *priv = dev_get_priv(dev);

	priv->regs = dev_read_addr_ptr(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,
	.of_to_plat	= sunxi_pwm_of_to_plat,
	.probe		= sunxi_pwm_probe,
	.priv_auto	= sizeof(struct sunxi_pwm_priv),
};