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

pwm: Add a driver for the STMPE PWM

This adds a driver for the PWM block found in chips of the STMPE 24xx
series of multi-purpose I2C expanders. (I think STMPE means ST
Microelectronics Multi-Purpose Expander.) This PWM was designed in
accordance with Nokia specifications and is kind of weird and usually
just switched between max and zero duty cycle. However it is indeed a
PWM so it needs to live in the PWM subsystem.

This PWM is mostly used for white LED backlight.

Cc: Lee Jones <lee.jones@linaro.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>

authored by

Linus Walleij and committed by
Thierry Reding
ef1f09ec 27e8c8f3

+327
+7
drivers/pwm/Kconfig
··· 372 372 To compile this driver as a module, choose M here: the module 373 373 will be called pwm-sti. 374 374 375 + config PWM_STMPE 376 + bool "STMPE expander PWM export" 377 + depends on MFD_STMPE 378 + help 379 + This enables support for the PWMs found in the STMPE I/O 380 + expanders. 381 + 375 382 config PWM_SUN4I 376 383 tristate "Allwinner PWM support" 377 384 depends on ARCH_SUNXI || COMPILE_TEST
+1
drivers/pwm/Makefile
··· 35 35 obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o 36 36 obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o 37 37 obj-$(CONFIG_PWM_STI) += pwm-sti.o 38 + obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o 38 39 obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o 39 40 obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o 40 41 obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o
+319
drivers/pwm/pwm-stmpe.c
··· 1 + /* 2 + * Copyright (C) 2016 Linaro Ltd. 3 + * 4 + * Author: Linus Walleij <linus.walleij@linaro.org> 5 + * 6 + * This program is free software; you can redistribute it and/or modify 7 + * it under the terms of the GNU General Public License version 2, as 8 + * published by the Free Software Foundation. 9 + * 10 + */ 11 + 12 + #include <linux/bitops.h> 13 + #include <linux/delay.h> 14 + #include <linux/err.h> 15 + #include <linux/mfd/stmpe.h> 16 + #include <linux/module.h> 17 + #include <linux/of.h> 18 + #include <linux/platform_device.h> 19 + #include <linux/pwm.h> 20 + #include <linux/slab.h> 21 + 22 + #define STMPE24XX_PWMCS 0x30 23 + #define PWMCS_EN_PWM0 BIT(0) 24 + #define PWMCS_EN_PWM1 BIT(1) 25 + #define PWMCS_EN_PWM2 BIT(2) 26 + #define STMPE24XX_PWMIC0 0x38 27 + #define STMPE24XX_PWMIC1 0x39 28 + #define STMPE24XX_PWMIC2 0x3a 29 + 30 + #define STMPE_PWM_24XX_PINBASE 21 31 + 32 + struct stmpe_pwm { 33 + struct stmpe *stmpe; 34 + struct pwm_chip chip; 35 + u8 last_duty; 36 + }; 37 + 38 + static inline struct stmpe_pwm *to_stmpe_pwm(struct pwm_chip *chip) 39 + { 40 + return container_of(chip, struct stmpe_pwm, chip); 41 + } 42 + 43 + static int stmpe_24xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) 44 + { 45 + struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip); 46 + u8 value; 47 + int ret; 48 + 49 + ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS); 50 + if (ret < 0) { 51 + dev_err(chip->dev, "error reading PWM#%u control\n", 52 + pwm->hwpwm); 53 + return ret; 54 + } 55 + 56 + value = ret | BIT(pwm->hwpwm); 57 + 58 + ret = stmpe_reg_write(stmpe_pwm->stmpe, STMPE24XX_PWMCS, value); 59 + if (ret) { 60 + dev_err(chip->dev, "error writing PWM#%u control\n", 61 + pwm->hwpwm); 62 + return ret; 63 + } 64 + 65 + return 0; 66 + } 67 + 68 + static void stmpe_24xx_pwm_disable(struct pwm_chip *chip, 69 + struct pwm_device *pwm) 70 + { 71 + struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip); 72 + u8 value; 73 + int ret; 74 + 75 + ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS); 76 + if (ret < 0) { 77 + dev_err(chip->dev, "error reading PWM#%u control\n", 78 + pwm->hwpwm); 79 + return; 80 + } 81 + 82 + value = ret & ~BIT(pwm->hwpwm); 83 + 84 + ret = stmpe_reg_write(stmpe_pwm->stmpe, STMPE24XX_PWMCS, value); 85 + if (ret) { 86 + dev_err(chip->dev, "error writing PWM#%u control\n", 87 + pwm->hwpwm); 88 + return; 89 + } 90 + } 91 + 92 + /* STMPE 24xx PWM instructions */ 93 + #define SMAX 0x007f 94 + #define SMIN 0x00ff 95 + #define GTS 0x0000 96 + #define LOAD BIT(14) /* Only available on 2403 */ 97 + #define RAMPUP 0x0000 98 + #define RAMPDOWN BIT(7) 99 + #define PRESCALE_512 BIT(14) 100 + #define STEPTIME_1 BIT(8) 101 + #define BRANCH (BIT(15) | BIT(13)) 102 + 103 + static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, 104 + int duty_ns, int period_ns) 105 + { 106 + struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip); 107 + unsigned int i, pin; 108 + u16 program[3] = { 109 + SMAX, 110 + GTS, 111 + GTS, 112 + }; 113 + u8 offset; 114 + int ret; 115 + 116 + /* Make sure we are disabled */ 117 + if (pwm_is_enabled(pwm)) { 118 + stmpe_24xx_pwm_disable(chip, pwm); 119 + } else { 120 + /* Connect the PWM to the pin */ 121 + pin = pwm->hwpwm; 122 + 123 + /* On STMPE2401 and 2403 pins 21,22,23 are used */ 124 + if (stmpe_pwm->stmpe->partnum == STMPE2401 || 125 + stmpe_pwm->stmpe->partnum == STMPE2403) 126 + pin += STMPE_PWM_24XX_PINBASE; 127 + 128 + ret = stmpe_set_altfunc(stmpe_pwm->stmpe, BIT(pin), 129 + STMPE_BLOCK_PWM); 130 + if (ret) { 131 + dev_err(chip->dev, "unable to connect PWM#%u to pin\n", 132 + pwm->hwpwm); 133 + return ret; 134 + } 135 + } 136 + 137 + /* STMPE24XX */ 138 + switch (pwm->hwpwm) { 139 + case 0: 140 + offset = STMPE24XX_PWMIC0; 141 + break; 142 + 143 + case 1: 144 + offset = STMPE24XX_PWMIC1; 145 + break; 146 + 147 + case 2: 148 + offset = STMPE24XX_PWMIC1; 149 + break; 150 + 151 + default: 152 + /* Should not happen as npwm is 3 */ 153 + return -ENODEV; 154 + } 155 + 156 + dev_dbg(chip->dev, "PWM#%u: config duty %d ns, period %d ns\n", 157 + pwm->hwpwm, duty_ns, period_ns); 158 + 159 + if (duty_ns == 0) { 160 + if (stmpe_pwm->stmpe->partnum == STMPE2401) 161 + program[0] = SMAX; /* off all the time */ 162 + 163 + if (stmpe_pwm->stmpe->partnum == STMPE2403) 164 + program[0] = LOAD | 0xff; /* LOAD 0xff */ 165 + 166 + stmpe_pwm->last_duty = 0x00; 167 + } else if (duty_ns == period_ns) { 168 + if (stmpe_pwm->stmpe->partnum == STMPE2401) 169 + program[0] = SMIN; /* on all the time */ 170 + 171 + if (stmpe_pwm->stmpe->partnum == STMPE2403) 172 + program[0] = LOAD | 0x00; /* LOAD 0x00 */ 173 + 174 + stmpe_pwm->last_duty = 0xff; 175 + } else { 176 + u8 value, last = stmpe_pwm->last_duty; 177 + unsigned long duty; 178 + 179 + /* 180 + * Counter goes from 0x00 to 0xff repeatedly at 32768 Hz, 181 + * (means a period of 30517 ns) then this is compared to the 182 + * counter from the ramp, if this is >= PWM counter the output 183 + * is high. With LOAD we can define how much of the cycle it 184 + * is on. 185 + * 186 + * Prescale = 0 -> 2 kHz -> T = 1/f = 488281.25 ns 187 + */ 188 + 189 + /* Scale to 0..0xff */ 190 + duty = duty_ns * 256; 191 + duty = DIV_ROUND_CLOSEST(duty, period_ns); 192 + value = duty; 193 + 194 + if (value == last) { 195 + /* Run the old program */ 196 + if (pwm_is_enabled(pwm)) 197 + stmpe_24xx_pwm_enable(chip, pwm); 198 + 199 + return 0; 200 + } else if (stmpe_pwm->stmpe->partnum == STMPE2403) { 201 + /* STMPE2403 can simply set the right PWM value */ 202 + program[0] = LOAD | value; 203 + program[1] = 0x0000; 204 + } else if (stmpe_pwm->stmpe->partnum == STMPE2401) { 205 + /* STMPE2401 need a complex program */ 206 + u16 incdec = 0x0000; 207 + 208 + if (last < value) 209 + /* Count up */ 210 + incdec = RAMPUP | (value - last); 211 + else 212 + /* Count down */ 213 + incdec = RAMPDOWN | (last - value); 214 + 215 + /* Step to desired value, smoothly */ 216 + program[0] = PRESCALE_512 | STEPTIME_1 | incdec; 217 + 218 + /* Loop eternally to 0x00 */ 219 + program[1] = BRANCH; 220 + } 221 + 222 + dev_dbg(chip->dev, 223 + "PWM#%u: value = %02x, last_duty = %02x, program=%04x,%04x,%04x\n", 224 + pwm->hwpwm, value, last, program[0], program[1], 225 + program[2]); 226 + stmpe_pwm->last_duty = value; 227 + } 228 + 229 + /* 230 + * We can write programs of up to 64 16-bit words into this channel. 231 + */ 232 + for (i = 0; i < ARRAY_SIZE(program); i++) { 233 + u8 value; 234 + 235 + value = (program[i] >> 8) & 0xff; 236 + 237 + ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value); 238 + if (ret) { 239 + dev_err(chip->dev, "error writing register %02x: %d\n", 240 + offset, ret); 241 + return ret; 242 + } 243 + 244 + value = program[i] & 0xff; 245 + 246 + ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value); 247 + if (ret) { 248 + dev_err(chip->dev, "error writing register %02x: %d\n", 249 + offset, ret); 250 + return ret; 251 + } 252 + } 253 + 254 + /* If we were enabled, re-enable this PWM */ 255 + if (pwm_is_enabled(pwm)) 256 + stmpe_24xx_pwm_enable(chip, pwm); 257 + 258 + /* Sleep for 200ms so we're sure it will take effect */ 259 + msleep(200); 260 + 261 + dev_dbg(chip->dev, "programmed PWM#%u, %u bytes\n", pwm->hwpwm, i); 262 + 263 + return 0; 264 + } 265 + 266 + static const struct pwm_ops stmpe_24xx_pwm_ops = { 267 + .config = stmpe_24xx_pwm_config, 268 + .enable = stmpe_24xx_pwm_enable, 269 + .disable = stmpe_24xx_pwm_disable, 270 + .owner = THIS_MODULE, 271 + }; 272 + 273 + static int __init stmpe_pwm_probe(struct platform_device *pdev) 274 + { 275 + struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent); 276 + struct stmpe_pwm *pwm; 277 + int ret; 278 + 279 + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); 280 + if (!pwm) 281 + return -ENOMEM; 282 + 283 + pwm->stmpe = stmpe; 284 + pwm->chip.dev = &pdev->dev; 285 + pwm->chip.base = -1; 286 + 287 + if (stmpe->partnum == STMPE2401 || stmpe->partnum == STMPE2403) { 288 + pwm->chip.ops = &stmpe_24xx_pwm_ops; 289 + pwm->chip.npwm = 3; 290 + } else { 291 + if (stmpe->partnum == STMPE1601) 292 + dev_err(&pdev->dev, "STMPE1601 not yet supported\n"); 293 + else 294 + dev_err(&pdev->dev, "Unknown STMPE PWM\n"); 295 + 296 + return -ENODEV; 297 + } 298 + 299 + ret = stmpe_enable(stmpe, STMPE_BLOCK_PWM); 300 + if (ret) 301 + return ret; 302 + 303 + ret = pwmchip_add(&pwm->chip); 304 + if (ret) { 305 + stmpe_disable(stmpe, STMPE_BLOCK_PWM); 306 + return ret; 307 + } 308 + 309 + platform_set_drvdata(pdev, pwm); 310 + 311 + return 0; 312 + } 313 + 314 + static struct platform_driver stmpe_pwm_driver = { 315 + .driver = { 316 + .name = "stmpe-pwm", 317 + }, 318 + }; 319 + builtin_platform_driver_probe(stmpe_pwm_driver, stmpe_pwm_probe);