projects/linux·blob master · refs

222 lines · 6.1 KiB

1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Analog Devices ADP5585 PWM driver
4 *
5 * Copyright 2022 NXP
6 * Copyright 2024 Ideas on Board Oy
7 *
8 * Limitations:
9 * - The .apply() operation executes atomically, but may not wait for the
10 * period to complete (this is not documented and would need to be tested).
11 * - Disabling the PWM drives the output pin to a low level immediately.
12 * - The hardware can only generate normal polarity output.
13 */
14
15#include <asm/byteorder.h>
16
17#include <linux/device.h>
18#include <linux/err.h>
19#include <linux/math64.h>
20#include <linux/mfd/adp5585.h>
21#include <linux/minmax.h>
22#include <linux/module.h>
23#include <linux/platform_device.h>
24#include <linux/pwm.h>
25#include <linux/regmap.h>
26#include <linux/time.h>
27#include <linux/types.h>
28
29#define ADP5585_PWM_CHAN_NUM 1
30
31#define ADP5585_PWM_OSC_FREQ_HZ 1000000U
32#define ADP5585_PWM_MIN_PERIOD_NS (2ULL * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ)
33#define ADP5585_PWM_MAX_PERIOD_NS (2ULL * 0xffff * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ)
34
35struct adp5585_pwm_chip {
36 unsigned int pwm_cfg;
37 unsigned int pwm_offt_low;
38 unsigned int pwm_ont_low;
39};
40
41struct adp5585_pwm {
42 const struct adp5585_pwm_chip *info;
43 struct regmap *regmap;
44 unsigned int ext_cfg;
45};
46
47static int pwm_adp5585_request(struct pwm_chip *chip, struct pwm_device *pwm)
48{
49 struct adp5585_pwm *adp5585_pwm = pwmchip_get_drvdata(chip);
50
51 /* Configure the R3 pin as PWM output. */
52 return regmap_update_bits(adp5585_pwm->regmap, adp5585_pwm->ext_cfg,
53 ADP5585_R3_EXTEND_CFG_MASK,
54 ADP5585_R3_EXTEND_CFG_PWM_OUT);
55}
56
57static void pwm_adp5585_free(struct pwm_chip *chip, struct pwm_device *pwm)
58{
59 struct adp5585_pwm *adp5585_pwm = pwmchip_get_drvdata(chip);
60
61 regmap_update_bits(adp5585_pwm->regmap, adp5585_pwm->ext_cfg,
62 ADP5585_R3_EXTEND_CFG_MASK,
63 ADP5585_R3_EXTEND_CFG_GPIO4);
64}
65
66static int pwm_adp5585_apply(struct pwm_chip *chip,
67 struct pwm_device *pwm,
68 const struct pwm_state *state)
69{
70 struct adp5585_pwm *adp5585_pwm = pwmchip_get_drvdata(chip);
71 const struct adp5585_pwm_chip *info = adp5585_pwm->info;
72 struct regmap *regmap = adp5585_pwm->regmap;
73 u64 period, duty_cycle;
74 u32 on, off;
75 __le16 val;
76 int ret;
77
78 if (!state->enabled) {
79 regmap_clear_bits(regmap, info->pwm_cfg, ADP5585_PWM_EN);
80 return 0;
81 }
82
83 if (state->polarity != PWM_POLARITY_NORMAL)
84 return -EINVAL;
85
86 if (state->period < ADP5585_PWM_MIN_PERIOD_NS)
87 return -EINVAL;
88
89 period = min(state->period, ADP5585_PWM_MAX_PERIOD_NS);
90 duty_cycle = min(state->duty_cycle, period);
91
92 /*
93 * Compute the on and off time. As the internal oscillator frequency is
94 * 1MHz, the calculation can be simplified without loss of precision.
95 */
96 on = div_u64(duty_cycle, NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
97 off = div_u64(period, NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) - on;
98
99 val = cpu_to_le16(off);
100 ret = regmap_bulk_write(regmap, info->pwm_offt_low, &val, 2);
101 if (ret)
102 return ret;
103
104 val = cpu_to_le16(on);
105 ret = regmap_bulk_write(regmap, info->pwm_ont_low, &val, 2);
106 if (ret)
107 return ret;
108
109 /* Enable PWM in continuous mode and no external AND'ing. */
110 ret = regmap_update_bits(regmap, info->pwm_cfg,
111 ADP5585_PWM_IN_AND | ADP5585_PWM_MODE |
112 ADP5585_PWM_EN, ADP5585_PWM_EN);
113 if (ret)
114 return ret;
115
116 return regmap_set_bits(regmap, info->pwm_cfg, ADP5585_PWM_EN);
117}
118
119static int pwm_adp5585_get_state(struct pwm_chip *chip,
120 struct pwm_device *pwm,
121 struct pwm_state *state)
122{
123 struct adp5585_pwm *adp5585_pwm = pwmchip_get_drvdata(chip);
124 const struct adp5585_pwm_chip *info = adp5585_pwm->info;
125 struct regmap *regmap = adp5585_pwm->regmap;
126 unsigned int on, off;
127 unsigned int val;
128 __le16 on_off;
129 int ret;
130
131 ret = regmap_bulk_read(regmap, info->pwm_offt_low, &on_off, 2);
132 if (ret)
133 return ret;
134 off = le16_to_cpu(on_off);
135
136 ret = regmap_bulk_read(regmap, info->pwm_ont_low, &on_off, 2);
137 if (ret)
138 return ret;
139 on = le16_to_cpu(on_off);
140
141 state->duty_cycle = on * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
142 state->period = (on + off) * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
143
144 state->polarity = PWM_POLARITY_NORMAL;
145
146 regmap_read(regmap, info->pwm_cfg, &val);
147 state->enabled = !!(val & ADP5585_PWM_EN);
148
149 return 0;
150}
151
152static const struct pwm_ops adp5585_pwm_ops = {
153 .request = pwm_adp5585_request,
154 .free = pwm_adp5585_free,
155 .apply = pwm_adp5585_apply,
156 .get_state = pwm_adp5585_get_state,
157};
158
159static int adp5585_pwm_probe(struct platform_device *pdev)
160{
161 const struct platform_device_id *id = platform_get_device_id(pdev);
162 struct device *dev = &pdev->dev;
163 struct adp5585_dev *adp5585 = dev_get_drvdata(dev->parent);
164 struct adp5585_pwm *adp5585_pwm;
165 struct pwm_chip *chip;
166 int ret;
167
168 chip = devm_pwmchip_alloc(dev, ADP5585_PWM_CHAN_NUM,
169 sizeof(*adp5585_pwm));
170 if (IS_ERR(chip))
171 return PTR_ERR(chip);
172
173 adp5585_pwm = pwmchip_get_drvdata(chip);
174 adp5585_pwm->regmap = adp5585->regmap;
175 adp5585_pwm->ext_cfg = adp5585->regs->ext_cfg;
176
177 adp5585_pwm->info = (const struct adp5585_pwm_chip *)id->driver_data;
178 if (!adp5585_pwm->info)
179 return -ENODEV;
180
181 device_set_of_node_from_dev(dev, dev->parent);
182
183 chip->ops = &adp5585_pwm_ops;
184
185 ret = devm_pwmchip_add(dev, chip);
186 if (ret)
187 return dev_err_probe(dev, ret, "failed to add PWM chip\n");
188
189 return 0;
190}
191
192static const struct adp5585_pwm_chip adp5585_pwm_chip_info = {
193 .pwm_cfg = ADP5585_PWM_CFG,
194 .pwm_offt_low = ADP5585_PWM_OFFT_LOW,
195 .pwm_ont_low = ADP5585_PWM_ONT_LOW,
196};
197
198static const struct adp5585_pwm_chip adp5589_pwm_chip_info = {
199 .pwm_cfg = ADP5589_PWM_CFG,
200 .pwm_offt_low = ADP5589_PWM_OFFT_LOW,
201 .pwm_ont_low = ADP5589_PWM_ONT_LOW,
202};
203
204static const struct platform_device_id adp5585_pwm_id_table[] = {
205 { .name = "adp5585-pwm", .driver_data = (kernel_ulong_t)&adp5585_pwm_chip_info },
206 { .name = "adp5589-pwm", .driver_data = (kernel_ulong_t)&adp5589_pwm_chip_info },
207 { /* Sentinel */ }
208};
209MODULE_DEVICE_TABLE(platform, adp5585_pwm_id_table);
210
211static struct platform_driver adp5585_pwm_driver = {
212 .driver = {
213 .name = "adp5585-pwm",
214 },
215 .probe = adp5585_pwm_probe,
216 .id_table = adp5585_pwm_id_table,
217};
218module_platform_driver(adp5585_pwm_driver);
219
220MODULE_AUTHOR("Xiaoning Wang <[email protected]>");
221MODULE_DESCRIPTION("ADP5585 PWM Driver");
222MODULE_LICENSE("GPL");