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 Loongson PWM controller support

This commit adds a generic PWM framework driver for the PWM controller
found on Loongson family chips.

Acked-by: Huacai Chen <chenhuacai@loongson.cn>
Co-developed-by: Juxin Gao <gaojuxin@loongson.cn>
Signed-off-by: Juxin Gao <gaojuxin@loongson.cn>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
Link: https://lore.kernel.org/r/76050a903a8015422fb9261ad88c7d9cc2edbbd8.1743403075.git.zhoubinbin@loongson.cn
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>

authored by

Binbin Zhou and committed by
Uwe Kleine-König
2b62c894 90cd430f

+304
+1
MAINTAINERS
··· 13917 13917 L: linux-pwm@vger.kernel.org 13918 13918 S: Maintained 13919 13919 F: Documentation/devicetree/bindings/pwm/loongson,ls7a-pwm.yaml 13920 + F: drivers/pwm/pwm-loongson.c 13920 13921 13921 13922 LOONGSON-2 SOC SERIES CLOCK DRIVER 13922 13923 M: Yinbo Zhu <zhuyinbo@loongson.cn>
+12
drivers/pwm/Kconfig
··· 351 351 To compile this driver as a module, choose M here: the module 352 352 will be called pwm-keembay. 353 353 354 + config PWM_LOONGSON 355 + tristate "Loongson PWM support" 356 + depends on MACH_LOONGSON64 || COMPILE_TEST 357 + depends on COMMON_CLK 358 + help 359 + Generic PWM framework driver for Loongson family. 360 + It can be found on Loongson-2K series cpus and Loongson LS7A 361 + bridge chips. 362 + 363 + To compile this driver as a module, choose M here: the module 364 + will be called pwm-loongson. 365 + 354 366 config PWM_LP3943 355 367 tristate "TI/National Semiconductor LP3943 PWM support" 356 368 depends on MFD_LP3943
+1
drivers/pwm/Makefile
··· 30 30 obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o 31 31 obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o 32 32 obj-$(CONFIG_PWM_KEEMBAY) += pwm-keembay.o 33 + obj-$(CONFIG_PWM_LOONGSON) += pwm-loongson.o 33 34 obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o 34 35 obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o 35 36 obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o
+290
drivers/pwm/pwm-loongson.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Copyright (C) 2017-2025 Loongson Technology Corporation Limited. 4 + * 5 + * Loongson PWM driver 6 + * 7 + * For Loongson's PWM IP block documentation please refer Chapter 11 of 8 + * Reference Manual: https://loongson.github.io/LoongArch-Documentation/Loongson-7A1000-usermanual-EN.pdf 9 + * 10 + * Author: Juxin Gao <gaojuxin@loongson.cn> 11 + * Further cleanup and restructuring by: 12 + * Binbin Zhou <zhoubinbin@loongson.cn> 13 + * 14 + * Limitations: 15 + * - If both DUTY and PERIOD are set to 0, the output is a constant low signal. 16 + * - When disabled the output is driven to 0 independent of the configured 17 + * polarity. 18 + * - If the register is reconfigured while PWM is running, it does not complete 19 + * the currently running period. 20 + * - Disabling the PWM stops the output immediately (without waiting for current 21 + * period to complete first). 22 + */ 23 + 24 + #include <linux/acpi.h> 25 + #include <linux/clk.h> 26 + #include <linux/device.h> 27 + #include <linux/init.h> 28 + #include <linux/io.h> 29 + #include <linux/kernel.h> 30 + #include <linux/module.h> 31 + #include <linux/platform_device.h> 32 + #include <linux/pwm.h> 33 + #include <linux/units.h> 34 + 35 + /* Loongson PWM registers */ 36 + #define LOONGSON_PWM_REG_DUTY 0x4 /* Low Pulse Buffer Register */ 37 + #define LOONGSON_PWM_REG_PERIOD 0x8 /* Pulse Period Buffer Register */ 38 + #define LOONGSON_PWM_REG_CTRL 0xc /* Control Register */ 39 + 40 + /* Control register bits */ 41 + #define LOONGSON_PWM_CTRL_REG_EN BIT(0) /* Counter Enable Bit */ 42 + #define LOONGSON_PWM_CTRL_REG_OE BIT(3) /* Pulse Output Enable Control Bit, Valid Low */ 43 + #define LOONGSON_PWM_CTRL_REG_SINGLE BIT(4) /* Single Pulse Control Bit */ 44 + #define LOONGSON_PWM_CTRL_REG_INTE BIT(5) /* Interrupt Enable Bit */ 45 + #define LOONGSON_PWM_CTRL_REG_INT BIT(6) /* Interrupt Bit */ 46 + #define LOONGSON_PWM_CTRL_REG_RST BIT(7) /* Counter Reset Bit */ 47 + #define LOONGSON_PWM_CTRL_REG_CAPTE BIT(8) /* Measurement Pulse Enable Bit */ 48 + #define LOONGSON_PWM_CTRL_REG_INVERT BIT(9) /* Output flip-flop Enable Bit */ 49 + #define LOONGSON_PWM_CTRL_REG_DZONE BIT(10) /* Anti-dead Zone Enable Bit */ 50 + 51 + /* default input clk frequency for the ACPI case */ 52 + #define LOONGSON_PWM_FREQ_DEFAULT 50000 /* Hz */ 53 + 54 + struct pwm_loongson_ddata { 55 + struct clk *clk; 56 + void __iomem *base; 57 + u64 clk_rate; 58 + }; 59 + 60 + static inline __pure struct pwm_loongson_ddata *to_pwm_loongson_ddata(struct pwm_chip *chip) 61 + { 62 + return pwmchip_get_drvdata(chip); 63 + } 64 + 65 + static inline u32 pwm_loongson_readl(struct pwm_loongson_ddata *ddata, u32 offset) 66 + { 67 + return readl(ddata->base + offset); 68 + } 69 + 70 + static inline void pwm_loongson_writel(struct pwm_loongson_ddata *ddata, 71 + u32 val, u32 offset) 72 + { 73 + writel(val, ddata->base + offset); 74 + } 75 + 76 + static int pwm_loongson_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, 77 + enum pwm_polarity polarity) 78 + { 79 + u16 val; 80 + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); 81 + 82 + val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); 83 + 84 + if (polarity == PWM_POLARITY_INVERSED) 85 + /* Duty cycle defines LOW period of PWM */ 86 + val |= LOONGSON_PWM_CTRL_REG_INVERT; 87 + else 88 + /* Duty cycle defines HIGH period of PWM */ 89 + val &= ~LOONGSON_PWM_CTRL_REG_INVERT; 90 + 91 + pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); 92 + 93 + return 0; 94 + } 95 + 96 + static void pwm_loongson_disable(struct pwm_chip *chip, struct pwm_device *pwm) 97 + { 98 + u32 val; 99 + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); 100 + 101 + val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); 102 + val &= ~LOONGSON_PWM_CTRL_REG_EN; 103 + pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); 104 + } 105 + 106 + static int pwm_loongson_enable(struct pwm_chip *chip, struct pwm_device *pwm) 107 + { 108 + u32 val; 109 + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); 110 + 111 + val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); 112 + val |= LOONGSON_PWM_CTRL_REG_EN; 113 + pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); 114 + 115 + return 0; 116 + } 117 + 118 + static int pwm_loongson_config(struct pwm_chip *chip, struct pwm_device *pwm, 119 + u64 duty_ns, u64 period_ns) 120 + { 121 + u32 duty, period; 122 + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); 123 + 124 + /* duty = duty_ns * ddata->clk_rate / NSEC_PER_SEC */ 125 + duty = mul_u64_u64_div_u64(duty_ns, ddata->clk_rate, NSEC_PER_SEC); 126 + if (duty > U32_MAX) 127 + duty = U32_MAX; 128 + 129 + /* period = period_ns * ddata->clk_rate / NSEC_PER_SEC */ 130 + period = mul_u64_u64_div_u64(period_ns, ddata->clk_rate, NSEC_PER_SEC); 131 + if (period > U32_MAX) 132 + period = U32_MAX; 133 + 134 + pwm_loongson_writel(ddata, duty, LOONGSON_PWM_REG_DUTY); 135 + pwm_loongson_writel(ddata, period, LOONGSON_PWM_REG_PERIOD); 136 + 137 + return 0; 138 + } 139 + 140 + static int pwm_loongson_apply(struct pwm_chip *chip, struct pwm_device *pwm, 141 + const struct pwm_state *state) 142 + { 143 + int ret; 144 + bool enabled = pwm->state.enabled; 145 + 146 + if (!state->enabled) { 147 + if (enabled) 148 + pwm_loongson_disable(chip, pwm); 149 + return 0; 150 + } 151 + 152 + ret = pwm_loongson_set_polarity(chip, pwm, state->polarity); 153 + if (ret) 154 + return ret; 155 + 156 + ret = pwm_loongson_config(chip, pwm, state->duty_cycle, state->period); 157 + if (ret) 158 + return ret; 159 + 160 + if (!enabled && state->enabled) 161 + ret = pwm_loongson_enable(chip, pwm); 162 + 163 + return ret; 164 + } 165 + 166 + static int pwm_loongson_get_state(struct pwm_chip *chip, struct pwm_device *pwm, 167 + struct pwm_state *state) 168 + { 169 + u32 duty, period, ctrl; 170 + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); 171 + 172 + duty = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_DUTY); 173 + period = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_PERIOD); 174 + ctrl = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); 175 + 176 + /* duty & period have a max of 2^32, so we can't overflow */ 177 + state->duty_cycle = DIV64_U64_ROUND_UP((u64)duty * NSEC_PER_SEC, ddata->clk_rate); 178 + state->period = DIV64_U64_ROUND_UP((u64)period * NSEC_PER_SEC, ddata->clk_rate); 179 + state->polarity = (ctrl & LOONGSON_PWM_CTRL_REG_INVERT) ? PWM_POLARITY_INVERSED : 180 + PWM_POLARITY_NORMAL; 181 + state->enabled = (ctrl & LOONGSON_PWM_CTRL_REG_EN) ? true : false; 182 + 183 + return 0; 184 + } 185 + 186 + static const struct pwm_ops pwm_loongson_ops = { 187 + .apply = pwm_loongson_apply, 188 + .get_state = pwm_loongson_get_state, 189 + }; 190 + 191 + static int pwm_loongson_probe(struct platform_device *pdev) 192 + { 193 + int ret; 194 + struct pwm_chip *chip; 195 + struct pwm_loongson_ddata *ddata; 196 + struct device *dev = &pdev->dev; 197 + 198 + chip = devm_pwmchip_alloc(dev, 1, sizeof(*ddata)); 199 + if (IS_ERR(chip)) 200 + return PTR_ERR(chip); 201 + ddata = to_pwm_loongson_ddata(chip); 202 + 203 + ddata->base = devm_platform_ioremap_resource(pdev, 0); 204 + if (IS_ERR(ddata->base)) 205 + return PTR_ERR(ddata->base); 206 + 207 + ddata->clk = devm_clk_get_optional_enabled(dev, NULL); 208 + if (IS_ERR(ddata->clk)) 209 + return dev_err_probe(dev, PTR_ERR(ddata->clk), 210 + "Failed to get pwm clock\n"); 211 + if (ddata->clk) { 212 + ret = devm_clk_rate_exclusive_get(dev, ddata->clk); 213 + if (ret) 214 + return dev_err_probe(dev, PTR_ERR(ddata->clk), 215 + "Failed to get exclusive rate\n"); 216 + 217 + ddata->clk_rate = clk_get_rate(ddata->clk); 218 + if (!ddata->clk_rate) 219 + return dev_err_probe(dev, -EINVAL, 220 + "Failed to get frequency\n"); 221 + } else { 222 + ddata->clk_rate = LOONGSON_PWM_FREQ_DEFAULT; 223 + } 224 + 225 + /* This check is done to prevent an overflow in .apply */ 226 + if (ddata->clk_rate > NSEC_PER_SEC) 227 + return dev_err_probe(dev, -EINVAL, "PWM clock out of range\n"); 228 + 229 + chip->ops = &pwm_loongson_ops; 230 + chip->atomic = true; 231 + dev_set_drvdata(dev, chip); 232 + 233 + ret = devm_pwmchip_add(dev, chip); 234 + if (ret < 0) 235 + return dev_err_probe(dev, ret, "Failed to add PWM chip\n"); 236 + 237 + return 0; 238 + } 239 + 240 + static int pwm_loongson_suspend(struct device *dev) 241 + { 242 + struct pwm_chip *chip = dev_get_drvdata(dev); 243 + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); 244 + struct pwm_device *pwm = &chip->pwms[0]; 245 + 246 + if (pwm->state.enabled) 247 + return -EBUSY; 248 + 249 + clk_disable_unprepare(ddata->clk); 250 + 251 + return 0; 252 + } 253 + 254 + static int pwm_loongson_resume(struct device *dev) 255 + { 256 + struct pwm_chip *chip = dev_get_drvdata(dev); 257 + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); 258 + 259 + return clk_prepare_enable(ddata->clk); 260 + } 261 + 262 + static DEFINE_SIMPLE_DEV_PM_OPS(pwm_loongson_pm_ops, pwm_loongson_suspend, 263 + pwm_loongson_resume); 264 + 265 + static const struct of_device_id pwm_loongson_of_ids[] = { 266 + { .compatible = "loongson,ls7a-pwm" }, 267 + { /* sentinel */ }, 268 + }; 269 + MODULE_DEVICE_TABLE(of, pwm_loongson_of_ids); 270 + 271 + static const struct acpi_device_id pwm_loongson_acpi_ids[] = { 272 + { "LOON0006" }, 273 + { } 274 + }; 275 + MODULE_DEVICE_TABLE(acpi, pwm_loongson_acpi_ids); 276 + 277 + static struct platform_driver pwm_loongson_driver = { 278 + .probe = pwm_loongson_probe, 279 + .driver = { 280 + .name = "loongson-pwm", 281 + .pm = pm_ptr(&pwm_loongson_pm_ops), 282 + .of_match_table = pwm_loongson_of_ids, 283 + .acpi_match_table = pwm_loongson_acpi_ids, 284 + }, 285 + }; 286 + module_platform_driver(pwm_loongson_driver); 287 + 288 + MODULE_DESCRIPTION("Loongson PWM driver"); 289 + MODULE_AUTHOR("Loongson Technology Corporation Limited."); 290 + MODULE_LICENSE("GPL");