xref: /openbmc/u-boot/drivers/pwm/exynos_pwm.c (revision c62db35d)
1 /*
2  * Copyright 2016 Google Inc.
3  *
4  * SPDX-License-Identifier:     GPL-2.0+
5  */
6 
7 #include <common.h>
8 #include <dm.h>
9 #include <pwm.h>
10 #include <asm/io.h>
11 #include <asm/arch/clk.h>
12 #include <asm/arch/clock.h>
13 #include <asm/arch/pwm.h>
14 
15 DECLARE_GLOBAL_DATA_PTR;
16 
17 struct exynos_pwm_priv {
18 	struct s5p_timer *regs;
19 };
20 
21 static int exynos_pwm_set_config(struct udevice *dev, uint channel,
22 				uint period_ns, uint duty_ns)
23 {
24 	struct exynos_pwm_priv *priv = dev_get_priv(dev);
25 	struct s5p_timer *regs = priv->regs;
26 	unsigned int offset, prescaler;
27 	uint div = 4, rate, rate_ns;
28 	u32 val;
29 	u32 tcnt, tcmp, tcon;
30 
31 	if (channel >= 5)
32 		return -EINVAL;
33 	debug("%s: Configure '%s' channel %u, period_ns %u, duty_ns %u\n",
34 	      __func__, dev->name, channel, period_ns, duty_ns);
35 
36 	val = readl(&regs->tcfg0);
37 	prescaler = (channel < 2 ? val : (val >> 8)) & 0xff;
38 	div = (readl(&regs->tcfg1) >> MUX_DIV_SHIFT(channel)) & 0xf;
39 
40 	rate = get_pwm_clk() / ((prescaler + 1) * (1 << div));
41 	debug("%s: pwm_clk %lu, rate %u\n", __func__, get_pwm_clk(), rate);
42 
43 	if (channel < 4) {
44 		rate_ns = 1000000000 / rate;
45 		tcnt = period_ns / rate_ns;
46 		tcmp = duty_ns / rate_ns;
47 		debug("%s: tcnt %u, tcmp %u\n", __func__, tcnt, tcmp);
48 		offset = channel * 3;
49 		writel(tcnt, &regs->tcntb0 + offset);
50 		writel(tcmp, &regs->tcmpb0 + offset);
51 	}
52 
53 	tcon = readl(&regs->tcon);
54 	tcon |= TCON_UPDATE(channel);
55 	if (channel < 4)
56 		tcon |= TCON_AUTO_RELOAD(channel);
57 	else
58 		tcon |= TCON4_AUTO_RELOAD;
59 	writel(tcon, &regs->tcon);
60 
61 	tcon &= ~TCON_UPDATE(channel);
62 	writel(tcon, &regs->tcon);
63 
64 	return 0;
65 }
66 
67 static int exynos_pwm_set_enable(struct udevice *dev, uint channel,
68 				 bool enable)
69 {
70 	struct exynos_pwm_priv *priv = dev_get_priv(dev);
71 	struct s5p_timer *regs = priv->regs;
72 	u32 mask;
73 
74 	if (channel >= 4)
75 		return -EINVAL;
76 	debug("%s: Enable '%s' channel %u\n", __func__, dev->name, channel);
77 	mask = TCON_START(channel);
78 	clrsetbits_le32(&regs->tcon, mask, enable ? mask : 0);
79 
80 	return 0;
81 }
82 
83 static int exynos_pwm_probe(struct udevice *dev)
84 {
85 	struct exynos_pwm_priv *priv = dev_get_priv(dev);
86 	struct s5p_timer *regs = priv->regs;
87 
88 	writel(PRESCALER_0 | PRESCALER_1 << 8, &regs->tcfg0);
89 
90 	return 0;
91 }
92 
93 static int exynos_pwm_ofdata_to_platdata(struct udevice *dev)
94 {
95 	struct exynos_pwm_priv *priv = dev_get_priv(dev);
96 
97 	priv->regs = (struct s5p_timer *)devfdt_get_addr(dev);
98 
99 	return 0;
100 }
101 
102 static const struct pwm_ops exynos_pwm_ops = {
103 	.set_config	= exynos_pwm_set_config,
104 	.set_enable	= exynos_pwm_set_enable,
105 };
106 
107 static const struct udevice_id exynos_channels[] = {
108 	{ .compatible = "samsung,exynos4210-pwm" },
109 	{ }
110 };
111 
112 U_BOOT_DRIVER(exynos_pwm) = {
113 	.name	= "exynos_pwm",
114 	.id	= UCLASS_PWM,
115 	.of_match = exynos_channels,
116 	.ops	= &exynos_pwm_ops,
117 	.probe	= exynos_pwm_probe,
118 	.ofdata_to_platdata	= exynos_pwm_ofdata_to_platdata,
119 	.priv_auto_alloc_size	= sizeof(struct exynos_pwm_priv),
120 };
121