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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2025 Bootlin
*
* Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
* Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
*
* PWM functionality of the MAX7360 multi-function device.
* https://www.analog.com/media/en/technical-documentation/data-sheets/MAX7360.pdf
*
* Limitations:
* - Only supports normal polarity.
* - The period is fixed to 2 ms.
* - Only the duty cycle can be changed, new values are applied at the beginning
* of the next cycle.
* - When disabled, the output is put in Hi-Z immediately.
*/
#include <linux/bits.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/math64.h>
#include <linux/mfd/max7360.h>
#include <linux/minmax.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/time.h>
#include <linux/types.h>
#define MAX7360_NUM_PWMS 8
#define MAX7360_PWM_MAX 255
#define MAX7360_PWM_STEPS 256
#define MAX7360_PWM_PERIOD_NS (2 * NSEC_PER_MSEC)
struct max7360_pwm_waveform {
u8 duty_steps;
bool enabled;
};
static int max7360_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct regmap *regmap = pwmchip_get_drvdata(chip);
/*
* Make sure we use the individual PWM configuration register and not
* the global one.
* We never need to use the global one, so there is no need to revert
* that in the .free() callback.
*/
return regmap_write_bits(regmap, MAX7360_REG_PWMCFG(pwm->hwpwm),
MAX7360_PORT_CFG_COMMON_PWM, 0);
}
static int max7360_pwm_round_waveform_tohw(struct pwm_chip *chip,
struct pwm_device *pwm,
const struct pwm_waveform *wf,
void *_wfhw)
{
struct max7360_pwm_waveform *wfhw = _wfhw;
u64 duty_steps;
/*
* Ignore user provided values for period_length_ns and duty_offset_ns:
* we only support fixed period of MAX7360_PWM_PERIOD_NS and offset of 0.
* Values from 0 to 254 as duty_steps will provide duty cycles of 0/256
* to 254/256, while value 255 will provide a duty cycle of 100%.
*/
if (wf->duty_length_ns >= MAX7360_PWM_PERIOD_NS) {
duty_steps = MAX7360_PWM_MAX;
} else {
duty_steps = (u32)wf->duty_length_ns * MAX7360_PWM_STEPS / MAX7360_PWM_PERIOD_NS;
if (duty_steps == MAX7360_PWM_MAX)
duty_steps = MAX7360_PWM_MAX - 1;
}
wfhw->duty_steps = min(MAX7360_PWM_MAX, duty_steps);
wfhw->enabled = !!wf->period_length_ns;
if (wf->period_length_ns && wf->period_length_ns < MAX7360_PWM_PERIOD_NS)
return 1;
else
return 0;
}
static int max7360_pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
const void *_wfhw, struct pwm_waveform *wf)
{
const struct max7360_pwm_waveform *wfhw = _wfhw;
wf->period_length_ns = wfhw->enabled ? MAX7360_PWM_PERIOD_NS : 0;
wf->duty_offset_ns = 0;
if (wfhw->enabled) {
if (wfhw->duty_steps == MAX7360_PWM_MAX)
wf->duty_length_ns = MAX7360_PWM_PERIOD_NS;
else
wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS,
MAX7360_PWM_STEPS);
} else {
wf->duty_length_ns = 0;
}
return 0;
}
static int max7360_pwm_write_waveform(struct pwm_chip *chip,
struct pwm_device *pwm,
const void *_wfhw)
{
struct regmap *regmap = pwmchip_get_drvdata(chip);
const struct max7360_pwm_waveform *wfhw = _wfhw;
unsigned int val;
int ret;
if (wfhw->enabled) {
ret = regmap_write(regmap, MAX7360_REG_PWM(pwm->hwpwm), wfhw->duty_steps);
if (ret)
return ret;
}
val = wfhw->enabled ? BIT(pwm->hwpwm) : 0;
return regmap_write_bits(regmap, MAX7360_REG_GPIOCTRL, BIT(pwm->hwpwm), val);
}
static int max7360_pwm_read_waveform(struct pwm_chip *chip,
struct pwm_device *pwm,
void *_wfhw)
{
struct regmap *regmap = pwmchip_get_drvdata(chip);
struct max7360_pwm_waveform *wfhw = _wfhw;
unsigned int val;
int ret;
ret = regmap_read(regmap, MAX7360_REG_GPIOCTRL, &val);
if (ret)
return ret;
if (val & BIT(pwm->hwpwm)) {
wfhw->enabled = true;
ret = regmap_read(regmap, MAX7360_REG_PWM(pwm->hwpwm), &val);
if (ret)
return ret;
wfhw->duty_steps = val;
} else {
wfhw->enabled = false;
wfhw->duty_steps = 0;
}
return 0;
}
static const struct pwm_ops max7360_pwm_ops = {
.request = max7360_pwm_request,
.round_waveform_tohw = max7360_pwm_round_waveform_tohw,
.round_waveform_fromhw = max7360_pwm_round_waveform_fromhw,
.read_waveform = max7360_pwm_read_waveform,
.write_waveform = max7360_pwm_write_waveform,
};
static int max7360_pwm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct pwm_chip *chip;
struct regmap *regmap;
int ret;
regmap = dev_get_regmap(dev->parent, NULL);
if (!regmap)
return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
/*
* This MFD sub-device does not have any associated device tree node:
* properties are stored in the device node of the parent (MFD) device
* and this same node is used in phandles of client devices.
* Reuse this device tree node here, as otherwise the PWM subsystem
* would be confused by this topology.
*/
device_set_of_node_from_dev(dev, dev->parent);
chip = devm_pwmchip_alloc(dev, MAX7360_NUM_PWMS, 0);
if (IS_ERR(chip))
return PTR_ERR(chip);
chip->ops = &max7360_pwm_ops;
pwmchip_set_drvdata(chip, regmap);
ret = devm_pwmchip_add(dev, chip);
if (ret)
return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
return 0;
}
static struct platform_driver max7360_pwm_driver = {
.driver = {
.name = "max7360-pwm",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.probe = max7360_pwm_probe,
};
module_platform_driver(max7360_pwm_driver);
MODULE_DESCRIPTION("MAX7360 PWM driver");
MODULE_AUTHOR("Kamel BOUHARA <kamel.bouhara@bootlin.com>");
MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
MODULE_LICENSE("GPL");
|