1 // SPDX-License-Identifier: GPL-2.0
3 * Analog Devices AXI PWM generator
5 * Copyright 2024 Analog Devices Inc.
6 * Copyright 2024 Baylibre SAS
8 * Device docs: https://analogdevicesinc.github.io/hdl/library/axi_pwm_gen/index.html
11 * - The writes to registers for period and duty are shadowed until
12 * LOAD_CONFIG is written to AXI_PWMGEN_REG_CONFIG, at which point
14 * - Writing LOAD_CONFIG also has the effect of re-synchronizing all
15 * enabled channels, which could cause glitching on other channels. It
16 * is therefore expected that channels are assigned harmonic periods
17 * and all have a single user coordinating this.
18 * - Supports normal polarity. Does not support changing polarity.
19 * - On disable, the PWM output becomes low (inactive).
21 #include <linux/bits.h>
22 #include <linux/clk.h>
23 #include <linux/err.h>
24 #include <linux/fpga/adi-axi-common.h>
26 #include <linux/module.h>
27 #include <linux/platform_device.h>
28 #include <linux/pwm.h>
29 #include <linux/regmap.h>
30 #include <linux/slab.h>
32 #define AXI_PWMGEN_REG_ID 0x04
33 #define AXI_PWMGEN_REG_SCRATCHPAD 0x08
34 #define AXI_PWMGEN_REG_CORE_MAGIC 0x0C
35 #define AXI_PWMGEN_REG_CONFIG 0x10
36 #define AXI_PWMGEN_REG_NPWM 0x14
37 #define AXI_PWMGEN_CHX_PERIOD(ch) (0x40 + (4 * (ch)))
38 #define AXI_PWMGEN_CHX_DUTY(ch) (0x80 + (4 * (ch)))
39 #define AXI_PWMGEN_CHX_OFFSET(ch) (0xC0 + (4 * (ch)))
40 #define AXI_PWMGEN_REG_CORE_MAGIC_VAL 0x601A3471 /* Identification number to test during setup */
41 #define AXI_PWMGEN_LOAD_CONFIG BIT(1)
42 #define AXI_PWMGEN_REG_CONFIG_RESET BIT(0)
44 struct axi_pwmgen_ddata
{
45 struct regmap
*regmap
;
46 unsigned long clk_rate_hz
;
49 static const struct regmap_config axi_pwmgen_regmap_config
= {
56 static int axi_pwmgen_apply(struct pwm_chip
*chip
, struct pwm_device
*pwm
,
57 const struct pwm_state
*state
)
59 struct axi_pwmgen_ddata
*ddata
= pwmchip_get_drvdata(chip
);
60 unsigned int ch
= pwm
->hwpwm
;
61 struct regmap
*regmap
= ddata
->regmap
;
62 u64 period_cnt
, duty_cnt
;
65 if (state
->polarity
!= PWM_POLARITY_NORMAL
)
69 period_cnt
= mul_u64_u64_div_u64(state
->period
, ddata
->clk_rate_hz
, NSEC_PER_SEC
);
70 if (period_cnt
> UINT_MAX
)
71 period_cnt
= UINT_MAX
;
76 ret
= regmap_write(regmap
, AXI_PWMGEN_CHX_PERIOD(ch
), period_cnt
);
80 duty_cnt
= mul_u64_u64_div_u64(state
->duty_cycle
, ddata
->clk_rate_hz
, NSEC_PER_SEC
);
81 if (duty_cnt
> UINT_MAX
)
84 ret
= regmap_write(regmap
, AXI_PWMGEN_CHX_DUTY(ch
), duty_cnt
);
88 ret
= regmap_write(regmap
, AXI_PWMGEN_CHX_PERIOD(ch
), 0);
92 ret
= regmap_write(regmap
, AXI_PWMGEN_CHX_DUTY(ch
), 0);
97 return regmap_write(regmap
, AXI_PWMGEN_REG_CONFIG
, AXI_PWMGEN_LOAD_CONFIG
);
100 static int axi_pwmgen_get_state(struct pwm_chip
*chip
, struct pwm_device
*pwm
,
101 struct pwm_state
*state
)
103 struct axi_pwmgen_ddata
*ddata
= pwmchip_get_drvdata(chip
);
104 struct regmap
*regmap
= ddata
->regmap
;
105 unsigned int ch
= pwm
->hwpwm
;
109 ret
= regmap_read(regmap
, AXI_PWMGEN_CHX_PERIOD(ch
), &cnt
);
113 state
->enabled
= cnt
!= 0;
115 state
->period
= DIV_ROUND_UP_ULL((u64
)cnt
* NSEC_PER_SEC
, ddata
->clk_rate_hz
);
117 ret
= regmap_read(regmap
, AXI_PWMGEN_CHX_DUTY(ch
), &cnt
);
121 state
->duty_cycle
= DIV_ROUND_UP_ULL((u64
)cnt
* NSEC_PER_SEC
, ddata
->clk_rate_hz
);
123 state
->polarity
= PWM_POLARITY_NORMAL
;
128 static const struct pwm_ops axi_pwmgen_pwm_ops
= {
129 .apply
= axi_pwmgen_apply
,
130 .get_state
= axi_pwmgen_get_state
,
133 static int axi_pwmgen_setup(struct regmap
*regmap
, struct device
*dev
)
138 ret
= regmap_read(regmap
, AXI_PWMGEN_REG_CORE_MAGIC
, &val
);
142 if (val
!= AXI_PWMGEN_REG_CORE_MAGIC_VAL
)
143 return dev_err_probe(dev
, -ENODEV
,
144 "failed to read expected value from register: got %08x, expected %08x\n",
145 val
, AXI_PWMGEN_REG_CORE_MAGIC_VAL
);
147 ret
= regmap_read(regmap
, ADI_AXI_REG_VERSION
, &val
);
151 if (ADI_AXI_PCORE_VER_MAJOR(val
) != 2) {
152 return dev_err_probe(dev
, -ENODEV
, "Unsupported peripheral version %u.%u.%u\n",
153 ADI_AXI_PCORE_VER_MAJOR(val
),
154 ADI_AXI_PCORE_VER_MINOR(val
),
155 ADI_AXI_PCORE_VER_PATCH(val
));
158 /* Enable the core */
159 ret
= regmap_clear_bits(regmap
, AXI_PWMGEN_REG_CONFIG
, AXI_PWMGEN_REG_CONFIG_RESET
);
163 ret
= regmap_read(regmap
, AXI_PWMGEN_REG_NPWM
, &val
);
167 /* Return the number of PWMs */
171 static int axi_pwmgen_probe(struct platform_device
*pdev
)
173 struct device
*dev
= &pdev
->dev
;
174 struct regmap
*regmap
;
175 struct pwm_chip
*chip
;
176 struct axi_pwmgen_ddata
*ddata
;
178 void __iomem
*io_base
;
181 io_base
= devm_platform_ioremap_resource(pdev
, 0);
183 return PTR_ERR(io_base
);
185 regmap
= devm_regmap_init_mmio(dev
, io_base
, &axi_pwmgen_regmap_config
);
187 return dev_err_probe(dev
, PTR_ERR(regmap
),
188 "failed to init register map\n");
190 ret
= axi_pwmgen_setup(regmap
, dev
);
194 chip
= devm_pwmchip_alloc(dev
, ret
, sizeof(*ddata
));
196 return PTR_ERR(chip
);
197 ddata
= pwmchip_get_drvdata(chip
);
198 ddata
->regmap
= regmap
;
200 clk
= devm_clk_get_enabled(dev
, NULL
);
202 return dev_err_probe(dev
, PTR_ERR(clk
), "failed to get clock\n");
204 ret
= devm_clk_rate_exclusive_get(dev
, clk
);
206 return dev_err_probe(dev
, ret
, "failed to get exclusive rate\n");
208 ddata
->clk_rate_hz
= clk_get_rate(clk
);
209 if (!ddata
->clk_rate_hz
|| ddata
->clk_rate_hz
> NSEC_PER_SEC
)
210 return dev_err_probe(dev
, -EINVAL
,
211 "Invalid clock rate: %lu\n", ddata
->clk_rate_hz
);
213 chip
->ops
= &axi_pwmgen_pwm_ops
;
216 ret
= devm_pwmchip_add(dev
, chip
);
218 return dev_err_probe(dev
, ret
, "could not add PWM chip\n");
223 static const struct of_device_id axi_pwmgen_ids
[] = {
224 { .compatible
= "adi,axi-pwmgen-2.00.a" },
227 MODULE_DEVICE_TABLE(of
, axi_pwmgen_ids
);
229 static struct platform_driver axi_pwmgen_driver
= {
231 .name
= "axi-pwmgen",
232 .of_match_table
= axi_pwmgen_ids
,
234 .probe
= axi_pwmgen_probe
,
236 module_platform_driver(axi_pwmgen_driver
);
238 MODULE_LICENSE("GPL");
239 MODULE_AUTHOR("Sergiu Cuciurean <sergiu.cuciurean@analog.com>");
240 MODULE_AUTHOR("Trevor Gamblin <tgamblin@baylibre.com>");
241 MODULE_DESCRIPTION("Driver for the Analog Devices AXI PWM generator");