Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

pwm: add support for NXPs high-side switch MC33XS2410

The MC33XS2410 is a four channel high-side switch. Featuring advanced
monitoring and control function, the device is operational from 3.0 V to
60 V. The device is controlled by SPI port for configuration.

Signed-off-by: Dimitri Fedrau <dimitri.fedrau@liebherr.com>
Link: https://lore.kernel.org/r/20250407-mc33xs2410-v9-2-57adcb56a6e4@liebherr.com
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>

authored by

Dimitri Fedrau and committed by
Uwe Kleine-König
2006016e 8b872a91

+404
+12
drivers/pwm/Kconfig
··· 423 423 To compile this driver as a module, choose M here: the module 424 424 will be called pwm-lpss-platform. 425 425 426 + config PWM_MC33XS2410 427 + tristate "MC33XS2410 PWM support" 428 + depends on OF 429 + depends on SPI 430 + help 431 + NXP MC33XS2410 high-side switch driver. The MC33XS2410 is a four 432 + channel high-side switch. The device is operational from 3.0 V 433 + to 60 V. The device is controlled by SPI port for configuration. 434 + 435 + To compile this driver as a module, choose M here: the module 436 + will be called pwm-mc33xs2410. 437 + 426 438 config PWM_MESON 427 439 tristate "Amlogic Meson PWM driver" 428 440 depends on ARCH_MESON || COMPILE_TEST
+1
drivers/pwm/Makefile
··· 37 37 obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o 38 38 obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o 39 39 obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o 40 + obj-$(CONFIG_PWM_MC33XS2410) += pwm-mc33xs2410.o 40 41 obj-$(CONFIG_PWM_MESON) += pwm-meson.o 41 42 obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o 42 43 obj-$(CONFIG_PWM_MICROCHIP_CORE) += pwm-microchip-core.o
+391
drivers/pwm/pwm-mc33xs2410.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH 4 + * 5 + * Reference Manual : https://www.nxp.com/docs/en/data-sheet/MC33XS2410.pdf 6 + * 7 + * Limitations: 8 + * - Supports frequencies between 0.5Hz and 2048Hz with following steps: 9 + * - 0.5 Hz steps from 0.5 Hz to 32 Hz 10 + * - 2 Hz steps from 2 Hz to 128 Hz 11 + * - 8 Hz steps from 8 Hz to 512 Hz 12 + * - 32 Hz steps from 32 Hz to 2048 Hz 13 + * - Cannot generate a 0 % duty cycle. 14 + * - Always produces low output if disabled. 15 + * - Configuration isn't atomic. When changing polarity, duty cycle or period 16 + * the data is taken immediately, counters not being affected, resulting in a 17 + * behavior of the output pin that is neither the old nor the new state, 18 + * rather something in between. 19 + */ 20 + 21 + #include <linux/bitfield.h> 22 + #include <linux/delay.h> 23 + #include <linux/err.h> 24 + #include <linux/math64.h> 25 + #include <linux/minmax.h> 26 + #include <linux/module.h> 27 + #include <linux/of.h> 28 + #include <linux/pwm.h> 29 + 30 + #include <linux/spi/spi.h> 31 + 32 + #define MC33XS2410_GLB_CTRL 0x00 33 + #define MC33XS2410_GLB_CTRL_MODE GENMASK(7, 6) 34 + #define MC33XS2410_GLB_CTRL_MODE_NORMAL FIELD_PREP(MC33XS2410_GLB_CTRL_MODE, 1) 35 + 36 + #define MC33XS2410_PWM_CTRL1 0x05 37 + /* chan in { 1 ... 4 } */ 38 + #define MC33XS2410_PWM_CTRL1_POL_INV(chan) BIT((chan) + 1) 39 + 40 + #define MC33XS2410_PWM_CTRL3 0x07 41 + /* chan in { 1 ... 4 } */ 42 + #define MC33XS2410_PWM_CTRL3_EN(chan) BIT(4 + (chan) - 1) 43 + 44 + /* chan in { 1 ... 4 } */ 45 + #define MC33XS2410_PWM_FREQ(chan) (0x08 + (chan) - 1) 46 + #define MC33XS2410_PWM_FREQ_STEP GENMASK(7, 6) 47 + #define MC33XS2410_PWM_FREQ_COUNT GENMASK(5, 0) 48 + 49 + /* chan in { 1 ... 4 } */ 50 + #define MC33XS2410_PWM_DC(chan) (0x0c + (chan) - 1) 51 + 52 + #define MC33XS2410_WDT 0x14 53 + 54 + #define MC33XS2410_PWM_MIN_PERIOD 488282 55 + /* step in { 0 ... 3 } */ 56 + #define MC33XS2410_PWM_MAX_PERIOD(step) (2000000000 >> (2 * (step))) 57 + 58 + #define MC33XS2410_FRAME_IN_ADDR GENMASK(15, 8) 59 + #define MC33XS2410_FRAME_IN_DATA GENMASK(7, 0) 60 + #define MC33XS2410_FRAME_IN_ADDR_WR BIT(7) 61 + #define MC33XS2410_FRAME_IN_DATA_RD BIT(7) 62 + #define MC33XS2410_FRAME_OUT_DATA GENMASK(13, 0) 63 + 64 + #define MC33XS2410_MAX_TRANSFERS 5 65 + 66 + static int mc33xs2410_write_regs(struct spi_device *spi, u8 *reg, u8 *val, 67 + unsigned int len) 68 + { 69 + u16 tx[MC33XS2410_MAX_TRANSFERS]; 70 + int i; 71 + 72 + if (len > MC33XS2410_MAX_TRANSFERS) 73 + return -EINVAL; 74 + 75 + for (i = 0; i < len; i++) 76 + tx[i] = FIELD_PREP(MC33XS2410_FRAME_IN_DATA, val[i]) | 77 + FIELD_PREP(MC33XS2410_FRAME_IN_ADDR, 78 + MC33XS2410_FRAME_IN_ADDR_WR | reg[i]); 79 + 80 + return spi_write(spi, tx, len * 2); 81 + } 82 + 83 + static int mc33xs2410_read_regs(struct spi_device *spi, u8 *reg, u8 flag, 84 + u16 *val, unsigned int len) 85 + { 86 + u16 tx[MC33XS2410_MAX_TRANSFERS]; 87 + u16 rx[MC33XS2410_MAX_TRANSFERS]; 88 + struct spi_transfer t = { 89 + .tx_buf = tx, 90 + .rx_buf = rx, 91 + }; 92 + int i, ret; 93 + 94 + len++; 95 + if (len > MC33XS2410_MAX_TRANSFERS) 96 + return -EINVAL; 97 + 98 + t.len = len * 2; 99 + for (i = 0; i < len - 1; i++) 100 + tx[i] = FIELD_PREP(MC33XS2410_FRAME_IN_DATA, flag) | 101 + FIELD_PREP(MC33XS2410_FRAME_IN_ADDR, reg[i]); 102 + 103 + ret = spi_sync_transfer(spi, &t, 1); 104 + if (ret < 0) 105 + return ret; 106 + 107 + for (i = 1; i < len; i++) 108 + val[i - 1] = FIELD_GET(MC33XS2410_FRAME_OUT_DATA, rx[i]); 109 + 110 + return 0; 111 + } 112 + 113 + static int mc33xs2410_write_reg(struct spi_device *spi, u8 reg, u8 val) 114 + { 115 + return mc33xs2410_write_regs(spi, &reg, &val, 1); 116 + } 117 + 118 + static int mc33xs2410_read_reg(struct spi_device *spi, u8 reg, u16 *val, u8 flag) 119 + { 120 + return mc33xs2410_read_regs(spi, &reg, flag, val, 1); 121 + } 122 + 123 + static int mc33xs2410_read_reg_ctrl(struct spi_device *spi, u8 reg, u16 *val) 124 + { 125 + return mc33xs2410_read_reg(spi, reg, val, MC33XS2410_FRAME_IN_DATA_RD); 126 + } 127 + 128 + static int mc33xs2410_modify_reg(struct spi_device *spi, u8 reg, u8 mask, u8 val) 129 + { 130 + u16 tmp; 131 + int ret; 132 + 133 + ret = mc33xs2410_read_reg_ctrl(spi, reg, &tmp); 134 + if (ret < 0) 135 + return ret; 136 + 137 + tmp &= ~mask; 138 + tmp |= val & mask; 139 + 140 + return mc33xs2410_write_reg(spi, reg, tmp); 141 + } 142 + 143 + static u8 mc33xs2410_pwm_get_freq(u64 period) 144 + { 145 + u8 step, count; 146 + 147 + /* 148 + * Check which step [0 .. 3] is appropriate for the given period. The 149 + * period ranges for the different step values overlap. Prefer big step 150 + * values as these allow more finegrained period and duty cycle 151 + * selection. 152 + */ 153 + 154 + switch (period) { 155 + case MC33XS2410_PWM_MIN_PERIOD ... MC33XS2410_PWM_MAX_PERIOD(3): 156 + step = 3; 157 + break; 158 + case MC33XS2410_PWM_MAX_PERIOD(3) + 1 ... MC33XS2410_PWM_MAX_PERIOD(2): 159 + step = 2; 160 + break; 161 + case MC33XS2410_PWM_MAX_PERIOD(2) + 1 ... MC33XS2410_PWM_MAX_PERIOD(1): 162 + step = 1; 163 + break; 164 + case MC33XS2410_PWM_MAX_PERIOD(1) + 1 ... MC33XS2410_PWM_MAX_PERIOD(0): 165 + step = 0; 166 + break; 167 + } 168 + 169 + /* 170 + * Round up here because a higher count results in a higher frequency 171 + * and so a smaller period. 172 + */ 173 + count = DIV_ROUND_UP((u32)MC33XS2410_PWM_MAX_PERIOD(step), (u32)period); 174 + return FIELD_PREP(MC33XS2410_PWM_FREQ_STEP, step) | 175 + FIELD_PREP(MC33XS2410_PWM_FREQ_COUNT, count - 1); 176 + } 177 + 178 + static u64 mc33xs2410_pwm_get_period(u8 reg) 179 + { 180 + u32 doubled_freq, code, doubled_steps; 181 + 182 + /* 183 + * steps: 184 + * - 0 = 0.5Hz 185 + * - 1 = 2Hz 186 + * - 2 = 8Hz 187 + * - 3 = 32Hz 188 + * frequency = (code + 1) x steps. 189 + * 190 + * To avoid losing precision in case steps value is zero, scale the 191 + * steps value for now by two and keep it in mind when calculating the 192 + * period that the frequency had been doubled. 193 + */ 194 + doubled_steps = 1 << (FIELD_GET(MC33XS2410_PWM_FREQ_STEP, reg) * 2); 195 + code = FIELD_GET(MC33XS2410_PWM_FREQ_COUNT, reg); 196 + doubled_freq = (code + 1) * doubled_steps; 197 + 198 + /* Convert frequency to period, considering the doubled frequency. */ 199 + return DIV_ROUND_UP(2 * NSEC_PER_SEC, doubled_freq); 200 + } 201 + 202 + /* 203 + * The hardware cannot generate a 0% relative duty cycle for normal and inversed 204 + * polarity. For normal polarity, the channel must be disabled, the device then 205 + * emits a constant low signal. 206 + * For inverted polarity, the channel must be enabled, the polarity must be set 207 + * to normal and the relative duty cylce must be set to 100%. The device then 208 + * emits a constant high signal. 209 + */ 210 + static int mc33xs2410_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, 211 + const struct pwm_state *state) 212 + { 213 + struct spi_device *spi = pwmchip_get_drvdata(chip); 214 + u8 reg[4] = { 215 + MC33XS2410_PWM_FREQ(pwm->hwpwm + 1), 216 + MC33XS2410_PWM_DC(pwm->hwpwm + 1), 217 + MC33XS2410_PWM_CTRL1, 218 + MC33XS2410_PWM_CTRL3 219 + }; 220 + u64 period, duty_cycle; 221 + int ret, rel_dc; 222 + u16 rd_val[2]; 223 + u8 wr_val[4]; 224 + u8 mask; 225 + 226 + period = min(state->period, MC33XS2410_PWM_MAX_PERIOD(0)); 227 + if (period < MC33XS2410_PWM_MIN_PERIOD) 228 + return -EINVAL; 229 + 230 + ret = mc33xs2410_read_regs(spi, &reg[2], MC33XS2410_FRAME_IN_DATA_RD, rd_val, 2); 231 + if (ret < 0) 232 + return ret; 233 + 234 + /* Frequency */ 235 + wr_val[0] = mc33xs2410_pwm_get_freq(period); 236 + /* Continue calculations with the possibly truncated period */ 237 + period = mc33xs2410_pwm_get_period(wr_val[0]); 238 + 239 + /* Duty cycle */ 240 + duty_cycle = min(period, state->duty_cycle); 241 + rel_dc = div64_u64(duty_cycle * 256, period) - 1; 242 + if (rel_dc >= 0) 243 + wr_val[1] = rel_dc; 244 + else if (state->polarity == PWM_POLARITY_NORMAL) 245 + wr_val[1] = 0; 246 + else 247 + wr_val[1] = 255; 248 + 249 + /* Polarity */ 250 + mask = MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm + 1); 251 + if (state->polarity == PWM_POLARITY_INVERSED && rel_dc >= 0) 252 + wr_val[2] = rd_val[0] | mask; 253 + else 254 + wr_val[2] = rd_val[0] & ~mask; 255 + 256 + /* Enable */ 257 + mask = MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm + 1); 258 + if (state->enabled && 259 + !(state->polarity == PWM_POLARITY_NORMAL && rel_dc < 0)) 260 + wr_val[3] = rd_val[1] | mask; 261 + else 262 + wr_val[3] = rd_val[1] & ~mask; 263 + 264 + return mc33xs2410_write_regs(spi, reg, wr_val, 4); 265 + } 266 + 267 + static int mc33xs2410_pwm_get_state(struct pwm_chip *chip, 268 + struct pwm_device *pwm, 269 + struct pwm_state *state) 270 + { 271 + struct spi_device *spi = pwmchip_get_drvdata(chip); 272 + u8 reg[4] = { 273 + MC33XS2410_PWM_FREQ(pwm->hwpwm + 1), 274 + MC33XS2410_PWM_DC(pwm->hwpwm + 1), 275 + MC33XS2410_PWM_CTRL1, 276 + MC33XS2410_PWM_CTRL3, 277 + }; 278 + u16 val[4]; 279 + int ret; 280 + 281 + ret = mc33xs2410_read_regs(spi, reg, MC33XS2410_FRAME_IN_DATA_RD, val, 282 + ARRAY_SIZE(reg)); 283 + if (ret < 0) 284 + return ret; 285 + 286 + state->period = mc33xs2410_pwm_get_period(val[0]); 287 + state->polarity = (val[2] & MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm + 1)) ? 288 + PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL; 289 + state->enabled = !!(val[3] & MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm + 1)); 290 + state->duty_cycle = DIV_ROUND_UP_ULL((val[1] + 1) * state->period, 256); 291 + 292 + return 0; 293 + } 294 + 295 + static const struct pwm_ops mc33xs2410_pwm_ops = { 296 + .apply = mc33xs2410_pwm_apply, 297 + .get_state = mc33xs2410_pwm_get_state, 298 + }; 299 + 300 + static int mc33xs2410_reset(struct device *dev) 301 + { 302 + struct gpio_desc *reset_gpio; 303 + 304 + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); 305 + if (IS_ERR_OR_NULL(reset_gpio)) 306 + return PTR_ERR_OR_ZERO(reset_gpio); 307 + 308 + /* Wake-up time */ 309 + fsleep(10000); 310 + 311 + return 0; 312 + } 313 + 314 + static int mc33xs2410_probe(struct spi_device *spi) 315 + { 316 + struct device *dev = &spi->dev; 317 + struct pwm_chip *chip; 318 + int ret; 319 + 320 + chip = devm_pwmchip_alloc(dev, 4, 0); 321 + if (IS_ERR(chip)) 322 + return PTR_ERR(chip); 323 + 324 + spi->bits_per_word = 16; 325 + spi->mode |= SPI_CS_WORD; 326 + ret = spi_setup(spi); 327 + if (ret < 0) 328 + return ret; 329 + 330 + pwmchip_set_drvdata(chip, spi); 331 + chip->ops = &mc33xs2410_pwm_ops; 332 + 333 + /* 334 + * Deasserts the reset of the device. Shouldn't change the output signal 335 + * if the device was setup prior to probing. 336 + */ 337 + ret = mc33xs2410_reset(dev); 338 + if (ret) 339 + return ret; 340 + 341 + /* 342 + * Disable watchdog and keep in mind that the watchdog won't trigger a 343 + * reset of the machine when running into an timeout, instead the 344 + * control over the outputs is handed over to the INx input logic 345 + * signals of the device. Disabling it here just deactivates this 346 + * feature until a proper solution is found. 347 + */ 348 + ret = mc33xs2410_write_reg(spi, MC33XS2410_WDT, 0x0); 349 + if (ret < 0) 350 + return dev_err_probe(dev, ret, "Failed to disable watchdog\n"); 351 + 352 + /* Transition to normal mode */ 353 + ret = mc33xs2410_modify_reg(spi, MC33XS2410_GLB_CTRL, 354 + MC33XS2410_GLB_CTRL_MODE, 355 + MC33XS2410_GLB_CTRL_MODE_NORMAL); 356 + if (ret < 0) 357 + return dev_err_probe(dev, ret, 358 + "Failed to transition to normal mode\n"); 359 + 360 + ret = devm_pwmchip_add(dev, chip); 361 + if (ret < 0) 362 + return dev_err_probe(dev, ret, "Failed to add pwm chip\n"); 363 + 364 + return 0; 365 + } 366 + 367 + static const struct spi_device_id mc33xs2410_spi_id[] = { 368 + { "mc33xs2410" }, 369 + { } 370 + }; 371 + MODULE_DEVICE_TABLE(spi, mc33xs2410_spi_id); 372 + 373 + static const struct of_device_id mc33xs2410_of_match[] = { 374 + { .compatible = "nxp,mc33xs2410" }, 375 + { } 376 + }; 377 + MODULE_DEVICE_TABLE(of, mc33xs2410_of_match); 378 + 379 + static struct spi_driver mc33xs2410_driver = { 380 + .driver = { 381 + .name = "mc33xs2410-pwm", 382 + .of_match_table = mc33xs2410_of_match, 383 + }, 384 + .probe = mc33xs2410_probe, 385 + .id_table = mc33xs2410_spi_id, 386 + }; 387 + module_spi_driver(mc33xs2410_driver); 388 + 389 + MODULE_DESCRIPTION("NXP MC33XS2410 high-side switch driver"); 390 + MODULE_AUTHOR("Dimitri Fedrau <dimitri.fedrau@liebherr.com>"); 391 + MODULE_LICENSE("GPL");