blob: 30c30430bea7c634ed622bf7add70c5e8d11eabc [file] [log] [blame]
Yash Shah7239a612020-04-23 16:57:16 +05301// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (C) 2020 SiFive, Inc
4 * For SiFive's PWM IP block documentation please refer Chapter 14 of
5 * Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf
6 *
7 * Limitations:
8 * - When changing both duty cycle and period, we cannot prevent in
9 * software that the output might produce a period with mixed
10 * settings (new period length and old duty cycle).
11 * - The hardware cannot generate a 100% duty cycle.
12 * - The hardware generates only inverted output.
13 */
14
15#include <common.h>
16#include <clk.h>
17#include <div64.h>
18#include <dm.h>
19#include <pwm.h>
20#include <regmap.h>
21#include <linux/io.h>
22#include <linux/log2.h>
23#include <linux/bitfield.h>
24
25/* PWMCFG fields */
26#define PWM_SIFIVE_PWMCFG_SCALE GENMASK(3, 0)
27#define PWM_SIFIVE_PWMCFG_STICKY BIT(8)
28#define PWM_SIFIVE_PWMCFG_ZERO_CMP BIT(9)
29#define PWM_SIFIVE_PWMCFG_DEGLITCH BIT(10)
30#define PWM_SIFIVE_PWMCFG_EN_ALWAYS BIT(12)
31#define PWM_SIFIVE_PWMCFG_EN_ONCE BIT(13)
32#define PWM_SIFIVE_PWMCFG_CENTER BIT(16)
33#define PWM_SIFIVE_PWMCFG_GANG BIT(24)
34#define PWM_SIFIVE_PWMCFG_IP BIT(28)
35
36/* PWM_SIFIVE_SIZE_PWMCMP is used to calculate offset for pwmcmpX registers */
37#define PWM_SIFIVE_SIZE_PWMCMP 4
38#define PWM_SIFIVE_CMPWIDTH 16
39
40DECLARE_GLOBAL_DATA_PTR;
41
42struct pwm_sifive_regs {
43 unsigned long cfg;
44 unsigned long cnt;
45 unsigned long pwms;
46 unsigned long cmp0;
47};
48
49struct pwm_sifive_data {
50 struct pwm_sifive_regs regs;
51};
52
53struct pwm_sifive_priv {
54 void __iomem *base;
55 ulong freq;
56 const struct pwm_sifive_data *data;
57};
58
59static int pwm_sifive_set_config(struct udevice *dev, uint channel,
60 uint period_ns, uint duty_ns)
61{
62 struct pwm_sifive_priv *priv = dev_get_priv(dev);
63 const struct pwm_sifive_regs *regs = &priv->data->regs;
64 unsigned long scale_pow;
65 unsigned long long num;
66 u32 scale, val = 0, frac;
67
68 debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns);
69
70 /*
71 * The PWM unit is used with pwmzerocmp=0, so the only way to modify the
72 * period length is using pwmscale which provides the number of bits the
73 * counter is shifted before being feed to the comparators. A period
74 * lasts (1 << (PWM_SIFIVE_CMPWIDTH + pwmscale)) clock ticks.
75 * (1 << (PWM_SIFIVE_CMPWIDTH + scale)) * 10^9/rate = period
76 */
77 scale_pow = lldiv((uint64_t)priv->freq * period_ns, 1000000000);
78 scale = clamp(ilog2(scale_pow) - PWM_SIFIVE_CMPWIDTH, 0, 0xf);
79 val |= FIELD_PREP(PWM_SIFIVE_PWMCFG_SCALE, scale);
80
81 /*
82 * The problem of output producing mixed setting as mentioned at top,
83 * occurs here. To minimize the window for this problem, we are
84 * calculating the register values first and then writing them
85 * consecutively
86 */
87 num = (u64)duty_ns * (1U << PWM_SIFIVE_CMPWIDTH);
88 frac = DIV_ROUND_CLOSEST_ULL(num, period_ns);
89 frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1);
90
91 writel(val, priv->base + regs->cfg);
92 writel(frac, priv->base + regs->cmp0 + channel *
93 PWM_SIFIVE_SIZE_PWMCMP);
94
95 return 0;
96}
97
98static int pwm_sifive_set_enable(struct udevice *dev, uint channel, bool enable)
99{
100 struct pwm_sifive_priv *priv = dev_get_priv(dev);
101 const struct pwm_sifive_regs *regs = &priv->data->regs;
102 u32 val;
103
104 debug("%s: Enable '%s'\n", __func__, dev->name);
105
106 if (enable) {
107 val = readl(priv->base + regs->cfg);
108 val |= PWM_SIFIVE_PWMCFG_EN_ALWAYS;
109 writel(val, priv->base + regs->cfg);
110 } else {
111 writel(0, priv->base + regs->cmp0 + channel *
112 PWM_SIFIVE_SIZE_PWMCMP);
113 }
114
115 return 0;
116}
117
Simon Glassd1998a92020-12-03 16:55:21 -0700118static int pwm_sifive_of_to_plat(struct udevice *dev)
Yash Shah7239a612020-04-23 16:57:16 +0530119{
120 struct pwm_sifive_priv *priv = dev_get_priv(dev);
121
122 priv->base = dev_read_addr_ptr(dev);
123
124 return 0;
125}
126
127static int pwm_sifive_probe(struct udevice *dev)
128{
129 struct pwm_sifive_priv *priv = dev_get_priv(dev);
130 struct clk clk;
131 int ret = 0;
132
133 ret = clk_get_by_index(dev, 0, &clk);
134 if (ret < 0) {
135 debug("%s get clock fail!\n", __func__);
136 return -EINVAL;
137 }
138
139 priv->freq = clk_get_rate(&clk);
140 priv->data = (struct pwm_sifive_data *)dev_get_driver_data(dev);
141
142 return 0;
143}
144
145static const struct pwm_ops pwm_sifive_ops = {
146 .set_config = pwm_sifive_set_config,
147 .set_enable = pwm_sifive_set_enable,
148};
149
150static const struct pwm_sifive_data pwm_data = {
151 .regs = {
152 .cfg = 0x00,
153 .cnt = 0x08,
154 .pwms = 0x10,
155 .cmp0 = 0x20,
156 },
157};
158
159static const struct udevice_id pwm_sifive_ids[] = {
160 { .compatible = "sifive,pwm0", .data = (ulong)&pwm_data},
161 { }
162};
163
164U_BOOT_DRIVER(pwm_sifive) = {
165 .name = "pwm_sifive",
166 .id = UCLASS_PWM,
167 .of_match = pwm_sifive_ids,
168 .ops = &pwm_sifive_ops,
Simon Glassd1998a92020-12-03 16:55:21 -0700169 .of_to_plat = pwm_sifive_of_to_plat,
Yash Shah7239a612020-04-23 16:57:16 +0530170 .probe = pwm_sifive_probe,
Simon Glass41575d82020-12-03 16:55:17 -0700171 .priv_auto = sizeof(struct pwm_sifive_priv),
Yash Shah7239a612020-04-23 16:57:16 +0530172};