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.

at v6.19-rc4 290 lines 8.6 kB view raw
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 50000000 /* Hz */ 53 54struct pwm_loongson_ddata { 55 struct clk *clk; 56 void __iomem *base; 57 u64 clk_rate; 58}; 59 60static inline __pure struct pwm_loongson_ddata *to_pwm_loongson_ddata(struct pwm_chip *chip) 61{ 62 return pwmchip_get_drvdata(chip); 63} 64 65static inline u32 pwm_loongson_readl(struct pwm_loongson_ddata *ddata, u32 offset) 66{ 67 return readl(ddata->base + offset); 68} 69 70static inline void pwm_loongson_writel(struct pwm_loongson_ddata *ddata, 71 u32 val, u32 offset) 72{ 73 writel(val, ddata->base + offset); 74} 75 76static 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 96static 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 106static 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 118static int pwm_loongson_config(struct pwm_chip *chip, struct pwm_device *pwm, 119 u64 duty_ns, u64 period_ns) 120{ 121 u64 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 140static 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 166static 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 186static const struct pwm_ops pwm_loongson_ops = { 187 .apply = pwm_loongson_apply, 188 .get_state = pwm_loongson_get_state, 189}; 190 191static 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, ret, 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 240static 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 254static 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 262static DEFINE_SIMPLE_DEV_PM_OPS(pwm_loongson_pm_ops, pwm_loongson_suspend, 263 pwm_loongson_resume); 264 265static const struct of_device_id pwm_loongson_of_ids[] = { 266 { .compatible = "loongson,ls7a-pwm" }, 267 { /* sentinel */ }, 268}; 269MODULE_DEVICE_TABLE(of, pwm_loongson_of_ids); 270 271static const struct acpi_device_id pwm_loongson_acpi_ids[] = { 272 { "LOON0006" }, 273 { } 274}; 275MODULE_DEVICE_TABLE(acpi, pwm_loongson_acpi_ids); 276 277static 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}; 286module_platform_driver(pwm_loongson_driver); 287 288MODULE_DESCRIPTION("Loongson PWM driver"); 289MODULE_AUTHOR("Loongson Technology Corporation Limited."); 290MODULE_LICENSE("GPL");