summaryrefslogtreecommitdiff
path: root/drivers/pwm/pwm-mtk.c
blob: 7bd82518d67ba109bf748dd430617809f132ec74 (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
187
188
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
 *
 * Author: Sam Shih <sam.shih@mediatek.com>
 */

#include <common.h>
#include <clk.h>
#include <dm.h>
#include <pwm.h>
#include <div64.h>
#include <linux/bitops.h>
#include <linux/io.h>

/* PWM registers and bits definitions */
#define PWMCON			0x00
#define PWMHDUR			0x04
#define PWMLDUR			0x08
#define PWMGDUR			0x0c
#define PWMWAVENUM		0x28
#define PWMDWIDTH		0x2c
#define PWM45DWIDTH_FIXUP	0x30
#define PWMTHRES		0x30
#define PWM45THRES_FIXUP	0x34

#define PWM_CLK_DIV_MAX		7
#define MAX_PWM_NUM		8

#define NSEC_PER_SEC 1000000000L

static const unsigned int mtk_pwm_reg_offset[] = {
	0x0010, 0x0050, 0x0090, 0x00d0, 0x0110, 0x0150, 0x0190, 0x0220
};

struct mtk_pwm_soc {
	unsigned int num_pwms;
	bool pwm45_fixup;
};

struct mtk_pwm_priv {
	void __iomem *base;
	struct clk top_clk;
	struct clk main_clk;
	struct clk pwm_clks[MAX_PWM_NUM];
	const struct mtk_pwm_soc *soc;
};

static void mtk_pwm_w32(struct udevice *dev, uint channel, uint reg, uint val)
{
	struct mtk_pwm_priv *priv = dev_get_priv(dev);
	u32 offset = mtk_pwm_reg_offset[channel];

	writel(val, priv->base + offset + reg);
}

static int mtk_pwm_set_config(struct udevice *dev, uint channel,
			      uint period_ns, uint duty_ns)
{
	struct mtk_pwm_priv *priv = dev_get_priv(dev);
	u32 clkdiv = 0, clksel = 0, cnt_period, cnt_duty,
	    reg_width = PWMDWIDTH, reg_thres = PWMTHRES;
	u64 resolution;
	int ret = 0;

	clk_enable(&priv->top_clk);
	clk_enable(&priv->main_clk);
	/* Using resolution in picosecond gets accuracy higher */
	resolution = (u64)NSEC_PER_SEC * 1000;
	do_div(resolution, clk_get_rate(&priv->pwm_clks[channel]));
	cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution);
	while (cnt_period > 8191) {
		resolution *= 2;
		clkdiv++;
		cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000,
						   resolution);
		if (clkdiv > PWM_CLK_DIV_MAX && clksel == 0) {
			clksel = 1;
			clkdiv = 0;
			resolution = (u64)NSEC_PER_SEC * 1000 * 1625;
			do_div(resolution,
			       clk_get_rate(&priv->pwm_clks[channel]));
			cnt_period = DIV_ROUND_CLOSEST_ULL(
					(u64)period_ns * 1000, resolution);
			clk_enable(&priv->pwm_clks[channel]);
		}
	}
	if (clkdiv > PWM_CLK_DIV_MAX && clksel == 1) {
		printf("pwm period %u not supported\n", period_ns);
		return -EINVAL;
	}
	if (priv->soc->pwm45_fixup && channel > 2) {
		/*
		 * PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
		 * from the other PWMs on MT7623.
		 */
		reg_width = PWM45DWIDTH_FIXUP;
		reg_thres = PWM45THRES_FIXUP;
	}
	cnt_duty = DIV_ROUND_CLOSEST_ULL((u64)duty_ns * 1000, resolution);
	if (clksel == 1)
		mtk_pwm_w32(dev, channel, PWMCON, BIT(15) | BIT(3) | clkdiv);
	else
		mtk_pwm_w32(dev, channel, PWMCON, BIT(15) | clkdiv);
	mtk_pwm_w32(dev, channel, reg_width, cnt_period);
	mtk_pwm_w32(dev, channel, reg_thres, cnt_duty);

	return ret;
};

static int mtk_pwm_set_enable(struct udevice *dev, uint channel, bool enable)
{
	struct mtk_pwm_priv *priv = dev_get_priv(dev);
	u32 val = 0;

	val = readl(priv->base);
	if (enable)
		val |= BIT(channel);
	else
		val &= ~BIT(channel);
	writel(val, priv->base);

	return 0;
};

static int mtk_pwm_probe(struct udevice *dev)
{
	struct mtk_pwm_priv *priv = dev_get_priv(dev);
	int ret = 0;
	int i;

	priv->soc = (struct mtk_pwm_soc *)dev_get_driver_data(dev);
	priv->base = dev_read_addr_ptr(dev);
	if (!priv->base)
		return -EINVAL;
	ret = clk_get_by_name(dev, "top", &priv->top_clk);
	if (ret < 0)
		return ret;
	ret = clk_get_by_name(dev, "main", &priv->main_clk);
	if (ret < 0)
		return ret;
	for (i = 0; i < priv->soc->num_pwms; i++) {
		char name[8];

		snprintf(name, sizeof(name), "pwm%d", i + 1);
		ret = clk_get_by_name(dev, name, &priv->pwm_clks[i]);
		if (ret < 0)
			return ret;
	}

	return ret;
}

static const struct pwm_ops mtk_pwm_ops = {
	.set_config	= mtk_pwm_set_config,
	.set_enable	= mtk_pwm_set_enable,
};

static const struct mtk_pwm_soc mt7622_data = {
	.num_pwms = 6,
	.pwm45_fixup = false,
};

static const struct mtk_pwm_soc mt7623_data = {
	.num_pwms = 5,
	.pwm45_fixup = true,
};

static const struct mtk_pwm_soc mt7629_data = {
	.num_pwms = 1,
	.pwm45_fixup = false,
};

static const struct udevice_id mtk_pwm_ids[] = {
	{ .compatible = "mediatek,mt7622-pwm", .data = (ulong)&mt7622_data },
	{ .compatible = "mediatek,mt7623-pwm", .data = (ulong)&mt7623_data },
	{ .compatible = "mediatek,mt7629-pwm", .data = (ulong)&mt7629_data },
	{ }
};

U_BOOT_DRIVER(mtk_pwm) = {
	.name = "mtk_pwm",
	.id = UCLASS_PWM,
	.of_match = mtk_pwm_ids,
	.ops = &mtk_pwm_ops,
	.probe = mtk_pwm_probe,
	.priv_auto_alloc_size = sizeof(struct mtk_pwm_priv),
};