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