Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | // SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> * JZ4740 platform PWM support * * Limitations: * - The .apply callback doesn't complete the currently running period before * reconfiguring the hardware. * - Each period starts with the inactive part. */ #include <linux/clk.h> #include <linux/err.h> #include <linux/gpio.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/pwm.h> #include <asm/mach-jz4740/timer.h> #define NUM_PWM 8 struct jz4740_pwm_chip { struct pwm_chip chip; struct clk *clk; }; static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip) { return container_of(chip, struct jz4740_pwm_chip, chip); } static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { /* * Timers 0 and 1 are used for system tasks, so they are unavailable * for use as PWMs. */ if (pwm->hwpwm < 2) return -EBUSY; jz4740_timer_start(pwm->hwpwm); return 0; } static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) { jz4740_timer_set_ctrl(pwm->hwpwm, 0); jz4740_timer_stop(pwm->hwpwm); } static int jz4740_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { uint32_t ctrl = jz4740_timer_get_ctrl(pwm->pwm); ctrl |= JZ_TIMER_CTRL_PWM_ENABLE; jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); jz4740_timer_enable(pwm->hwpwm); return 0; } static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { uint32_t ctrl = jz4740_timer_get_ctrl(pwm->hwpwm); /* * Set duty > period. This trick allows the TCU channels in TCU2 mode to * properly return to their init level. */ jz4740_timer_set_duty(pwm->hwpwm, 0xffff); jz4740_timer_set_period(pwm->hwpwm, 0x0); /* * Disable PWM output. * In TCU2 mode (channel 1/2 on JZ4750+), this must be done before the * counter is stopped, while in TCU1 mode the order does not matter. */ ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE; jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); /* Stop counter */ jz4740_timer_disable(pwm->hwpwm); } static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip); unsigned long long tmp; unsigned long period, duty; unsigned int prescaler = 0; uint16_t ctrl; tmp = (unsigned long long)clk_get_rate(jz4740->clk) * state->period; do_div(tmp, 1000000000); period = tmp; while (period > 0xffff && prescaler < 6) { period >>= 2; ++prescaler; } if (prescaler == 6) return -EINVAL; tmp = (unsigned long long)period * state->duty_cycle; do_div(tmp, state->period); duty = period - tmp; if (duty >= period) duty = period - 1; jz4740_pwm_disable(chip, pwm); jz4740_timer_set_count(pwm->hwpwm, 0); jz4740_timer_set_duty(pwm->hwpwm, duty); jz4740_timer_set_period(pwm->hwpwm, period); ctrl = JZ_TIMER_CTRL_PRESCALER(prescaler) | JZ_TIMER_CTRL_SRC_EXT | JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN; jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); switch (state->polarity) { case PWM_POLARITY_NORMAL: ctrl &= ~JZ_TIMER_CTRL_PWM_ACTIVE_LOW; break; case PWM_POLARITY_INVERSED: ctrl |= JZ_TIMER_CTRL_PWM_ACTIVE_LOW; break; } jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); if (state->enabled) jz4740_pwm_enable(chip, pwm); return 0; } static const struct pwm_ops jz4740_pwm_ops = { .request = jz4740_pwm_request, .free = jz4740_pwm_free, .apply = jz4740_pwm_apply, .owner = THIS_MODULE, }; static int jz4740_pwm_probe(struct platform_device *pdev) { struct jz4740_pwm_chip *jz4740; jz4740 = devm_kzalloc(&pdev->dev, sizeof(*jz4740), GFP_KERNEL); if (!jz4740) return -ENOMEM; jz4740->clk = devm_clk_get(&pdev->dev, "ext"); if (IS_ERR(jz4740->clk)) return PTR_ERR(jz4740->clk); jz4740->chip.dev = &pdev->dev; jz4740->chip.ops = &jz4740_pwm_ops; jz4740->chip.npwm = NUM_PWM; jz4740->chip.base = -1; jz4740->chip.of_xlate = of_pwm_xlate_with_flags; jz4740->chip.of_pwm_n_cells = 3; platform_set_drvdata(pdev, jz4740); return pwmchip_add(&jz4740->chip); } static int jz4740_pwm_remove(struct platform_device *pdev) { struct jz4740_pwm_chip *jz4740 = platform_get_drvdata(pdev); return pwmchip_remove(&jz4740->chip); } #ifdef CONFIG_OF static const struct of_device_id jz4740_pwm_dt_ids[] = { { .compatible = "ingenic,jz4740-pwm", }, {}, }; MODULE_DEVICE_TABLE(of, jz4740_pwm_dt_ids); #endif static struct platform_driver jz4740_pwm_driver = { .driver = { .name = "jz4740-pwm", .of_match_table = of_match_ptr(jz4740_pwm_dt_ids), }, .probe = jz4740_pwm_probe, .remove = jz4740_pwm_remove, }; module_platform_driver(jz4740_pwm_driver); MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); MODULE_DESCRIPTION("Ingenic JZ4740 PWM driver"); MODULE_ALIAS("platform:jz4740-pwm"); MODULE_LICENSE("GPL"); |