blob: b9813a3b6bbeadda0990223af5d391375654a382 [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>
Simon Glass401d1c42020-10-30 21:38:53 -060021#include <asm/global_data.h>
Yash Shah7239a612020-04-23 16:57:16 +053022#include <linux/io.h>
23#include <linux/log2.h>
24#include <linux/bitfield.h>
25
26/* PWMCFG fields */
27#define PWM_SIFIVE_PWMCFG_SCALE GENMASK(3, 0)
28#define PWM_SIFIVE_PWMCFG_STICKY BIT(8)
29#define PWM_SIFIVE_PWMCFG_ZERO_CMP BIT(9)
30#define PWM_SIFIVE_PWMCFG_DEGLITCH BIT(10)
31#define PWM_SIFIVE_PWMCFG_EN_ALWAYS BIT(12)
32#define PWM_SIFIVE_PWMCFG_EN_ONCE BIT(13)
33#define PWM_SIFIVE_PWMCFG_CENTER BIT(16)
34#define PWM_SIFIVE_PWMCFG_GANG BIT(24)
35#define PWM_SIFIVE_PWMCFG_IP BIT(28)
36
37/* PWM_SIFIVE_SIZE_PWMCMP is used to calculate offset for pwmcmpX registers */
38#define PWM_SIFIVE_SIZE_PWMCMP 4
39#define PWM_SIFIVE_CMPWIDTH 16
40
Vincent Chencc25f342021-05-03 15:26:49 +080041#define PWM_SIFIVE_CHANNEL_ENABLE_VAL 0
42#define PWM_SIFIVE_CHANNEL_DISABLE_VAL 0xffff
43
Yash Shah7239a612020-04-23 16:57:16 +053044DECLARE_GLOBAL_DATA_PTR;
45
46struct pwm_sifive_regs {
47 unsigned long cfg;
48 unsigned long cnt;
49 unsigned long pwms;
50 unsigned long cmp0;
51};
52
53struct pwm_sifive_data {
54 struct pwm_sifive_regs regs;
55};
56
57struct pwm_sifive_priv {
58 void __iomem *base;
59 ulong freq;
60 const struct pwm_sifive_data *data;
61};
62
63static int pwm_sifive_set_config(struct udevice *dev, uint channel,
64 uint period_ns, uint duty_ns)
65{
66 struct pwm_sifive_priv *priv = dev_get_priv(dev);
67 const struct pwm_sifive_regs *regs = &priv->data->regs;
68 unsigned long scale_pow;
69 unsigned long long num;
70 u32 scale, val = 0, frac;
71
72 debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns);
73
74 /*
75 * The PWM unit is used with pwmzerocmp=0, so the only way to modify the
76 * period length is using pwmscale which provides the number of bits the
77 * counter is shifted before being feed to the comparators. A period
78 * lasts (1 << (PWM_SIFIVE_CMPWIDTH + pwmscale)) clock ticks.
79 * (1 << (PWM_SIFIVE_CMPWIDTH + scale)) * 10^9/rate = period
80 */
81 scale_pow = lldiv((uint64_t)priv->freq * period_ns, 1000000000);
82 scale = clamp(ilog2(scale_pow) - PWM_SIFIVE_CMPWIDTH, 0, 0xf);
Vincent Chencc25f342021-05-03 15:26:49 +080083 val |= (FIELD_PREP(PWM_SIFIVE_PWMCFG_SCALE, scale) | PWM_SIFIVE_PWMCFG_EN_ALWAYS);
Yash Shah7239a612020-04-23 16:57:16 +053084
85 /*
86 * The problem of output producing mixed setting as mentioned at top,
87 * occurs here. To minimize the window for this problem, we are
88 * calculating the register values first and then writing them
89 * consecutively
90 */
91 num = (u64)duty_ns * (1U << PWM_SIFIVE_CMPWIDTH);
92 frac = DIV_ROUND_CLOSEST_ULL(num, period_ns);
93 frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1);
Vincent Chencc25f342021-05-03 15:26:49 +080094 frac = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - frac;
Yash Shah7239a612020-04-23 16:57:16 +053095
96 writel(val, priv->base + regs->cfg);
97 writel(frac, priv->base + regs->cmp0 + channel *
98 PWM_SIFIVE_SIZE_PWMCMP);
99
100 return 0;
101}
102
103static int pwm_sifive_set_enable(struct udevice *dev, uint channel, bool enable)
104{
105 struct pwm_sifive_priv *priv = dev_get_priv(dev);
106 const struct pwm_sifive_regs *regs = &priv->data->regs;
Yash Shah7239a612020-04-23 16:57:16 +0530107
108 debug("%s: Enable '%s'\n", __func__, dev->name);
109
Vincent Chencc25f342021-05-03 15:26:49 +0800110 if (enable)
111 writel(PWM_SIFIVE_CHANNEL_ENABLE_VAL, priv->base +
112 regs->cmp0 + channel * PWM_SIFIVE_SIZE_PWMCMP);
113 else
114 writel(PWM_SIFIVE_CHANNEL_DISABLE_VAL, priv->base +
115 regs->cmp0 + channel * PWM_SIFIVE_SIZE_PWMCMP);
Yash Shah7239a612020-04-23 16:57:16 +0530116
117 return 0;
118}
119
Simon Glassd1998a92020-12-03 16:55:21 -0700120static int pwm_sifive_of_to_plat(struct udevice *dev)
Yash Shah7239a612020-04-23 16:57:16 +0530121{
122 struct pwm_sifive_priv *priv = dev_get_priv(dev);
123
124 priv->base = dev_read_addr_ptr(dev);
125
126 return 0;
127}
128
129static int pwm_sifive_probe(struct udevice *dev)
130{
131 struct pwm_sifive_priv *priv = dev_get_priv(dev);
132 struct clk clk;
133 int ret = 0;
134
135 ret = clk_get_by_index(dev, 0, &clk);
136 if (ret < 0) {
137 debug("%s get clock fail!\n", __func__);
138 return -EINVAL;
139 }
140
141 priv->freq = clk_get_rate(&clk);
142 priv->data = (struct pwm_sifive_data *)dev_get_driver_data(dev);
143
144 return 0;
145}
146
147static const struct pwm_ops pwm_sifive_ops = {
148 .set_config = pwm_sifive_set_config,
149 .set_enable = pwm_sifive_set_enable,
150};
151
152static const struct pwm_sifive_data pwm_data = {
153 .regs = {
154 .cfg = 0x00,
155 .cnt = 0x08,
156 .pwms = 0x10,
157 .cmp0 = 0x20,
158 },
159};
160
161static const struct udevice_id pwm_sifive_ids[] = {
162 { .compatible = "sifive,pwm0", .data = (ulong)&pwm_data},
163 { }
164};
165
166U_BOOT_DRIVER(pwm_sifive) = {
167 .name = "pwm_sifive",
168 .id = UCLASS_PWM,
169 .of_match = pwm_sifive_ids,
170 .ops = &pwm_sifive_ops,
Simon Glassd1998a92020-12-03 16:55:21 -0700171 .of_to_plat = pwm_sifive_of_to_plat,
Yash Shah7239a612020-04-23 16:57:16 +0530172 .probe = pwm_sifive_probe,
Simon Glass41575d82020-12-03 16:55:17 -0700173 .priv_auto = sizeof(struct pwm_sifive_priv),
Yash Shah7239a612020-04-23 16:57:16 +0530174};