diff options
Diffstat (limited to 'drivers/pwm/pwm-rockchip.c')
| -rw-r--r-- | drivers/pwm/pwm-rockchip.c | 156 |
1 files changed, 72 insertions, 84 deletions
diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c index eb8c9cb645a6..67b85bdb491b 100644 --- a/drivers/pwm/pwm-rockchip.c +++ b/drivers/pwm/pwm-rockchip.c @@ -8,10 +8,12 @@ #include <linux/clk.h> #include <linux/io.h> +#include <linux/limits.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/pwm.h> #include <linux/time.h> @@ -30,7 +32,6 @@ #define PWM_LP_DISABLE (0 << 8) struct rockchip_pwm_chip { - struct pwm_chip chip; struct clk *clk; struct clk *pclk; const struct rockchip_pwm_data *data; @@ -52,16 +53,17 @@ struct rockchip_pwm_data { u32 enable_conf; }; -static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c) +static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *chip) { - return container_of(c, struct rockchip_pwm_chip, chip); + return pwmchip_get_drvdata(chip); } -static void rockchip_pwm_get_state(struct pwm_chip *chip, - struct pwm_device *pwm, - struct pwm_state *state) +static int rockchip_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) { struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC; u32 enable_conf = pc->data->enable_conf; unsigned long clk_rate; u64 tmp; @@ -70,17 +72,21 @@ static void rockchip_pwm_get_state(struct pwm_chip *chip, ret = clk_enable(pc->pclk); if (ret) - return; + return ret; + + ret = clk_enable(pc->clk); + if (ret) + return ret; clk_rate = clk_get_rate(pc->clk); tmp = readl_relaxed(pc->base + pc->data->regs.period); - tmp *= pc->data->prescaler * NSEC_PER_SEC; - state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); + tmp *= prescaled_ns; + state->period = DIV_U64_ROUND_UP(tmp, clk_rate); tmp = readl_relaxed(pc->base + pc->data->regs.duty); - tmp *= pc->data->prescaler * NSEC_PER_SEC; - state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); + tmp *= prescaled_ns; + state->duty_cycle = DIV_U64_ROUND_UP(tmp, clk_rate); val = readl_relaxed(pc->base + pc->data->regs.ctrl); state->enabled = (val & enable_conf) == enable_conf; @@ -90,15 +96,19 @@ static void rockchip_pwm_get_state(struct pwm_chip *chip, else state->polarity = PWM_POLARITY_NORMAL; + clk_disable(pc->clk); clk_disable(pc->pclk); + + return 0; } static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); - unsigned long period, duty; - u64 clk_rate, div; + u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC; + u64 clk_rate, tmp; + u32 period_ticks, duty_ticks; u32 ctrl; clk_rate = clk_get_rate(pc->clk); @@ -108,12 +118,15 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, * bits, every possible input period can be obtained using the * default prescaler value for all practical clock rate values. */ - div = clk_rate * state->period; - period = DIV_ROUND_CLOSEST_ULL(div, - pc->data->prescaler * NSEC_PER_SEC); + tmp = mul_u64_u64_div_u64(clk_rate, state->period, prescaled_ns); + if (tmp > U32_MAX) + tmp = U32_MAX; + period_ticks = tmp; - div = clk_rate * state->duty_cycle; - duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC); + tmp = mul_u64_u64_div_u64(clk_rate, state->duty_cycle, prescaled_ns); + if (tmp > U32_MAX) + tmp = U32_MAX; + duty_ticks = tmp; /* * Lock the period and duty of previous configuration, then @@ -125,8 +138,8 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, writel_relaxed(ctrl, pc->base + pc->data->regs.ctrl); } - writel(period, pc->base + pc->data->regs.period); - writel(duty, pc->base + pc->data->regs.duty); + writel(period_ticks, pc->base + pc->data->regs.period); + writel(duty_ticks, pc->base + pc->data->regs.duty); if (pc->data->supports_polarity) { ctrl &= ~PWM_POLARITY_MASK; @@ -189,6 +202,10 @@ static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, if (ret) return ret; + ret = clk_enable(pc->clk); + if (ret) + return ret; + pwm_get_state(pwm, &curstate); enabled = curstate.enabled; @@ -208,6 +225,7 @@ static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, } out: + clk_disable(pc->clk); clk_disable(pc->pclk); return ret; @@ -216,7 +234,6 @@ out: static const struct pwm_ops rockchip_pwm_ops = { .get_state = rockchip_pwm_get_state, .apply = rockchip_pwm_apply, - .owner = THIS_MODULE, }; static const struct rockchip_pwm_data pwm_data_v1 = { @@ -285,34 +302,27 @@ MODULE_DEVICE_TABLE(of, rockchip_pwm_dt_ids); static int rockchip_pwm_probe(struct platform_device *pdev) { - const struct of_device_id *id; + struct pwm_chip *chip; struct rockchip_pwm_chip *pc; - struct resource *r; + u32 enable_conf, ctrl; + bool enabled; int ret, count; - id = of_match_device(rockchip_pwm_dt_ids, &pdev->dev); - if (!id) - return -EINVAL; + chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*pc)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + pc = to_rockchip_pwm_chip(chip); - pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); - if (!pc) - return -ENOMEM; - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - pc->base = devm_ioremap_resource(&pdev->dev, r); + pc->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(pc->base)) return PTR_ERR(pc->base); pc->clk = devm_clk_get(&pdev->dev, "pwm"); if (IS_ERR(pc->clk)) { pc->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(pc->clk)) { - ret = PTR_ERR(pc->clk); - if (ret != -EPROBE_DEFER) - dev_err(&pdev->dev, "Can't get bus clk: %d\n", - ret); - return ret; - } + if (IS_ERR(pc->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk), + "Can't get PWM clk\n"); } count = of_count_phandle_with_args(pdev->dev.of_node, @@ -322,81 +332,59 @@ static int rockchip_pwm_probe(struct platform_device *pdev) else pc->pclk = pc->clk; - if (IS_ERR(pc->pclk)) { - ret = PTR_ERR(pc->pclk); - if (ret != -EPROBE_DEFER) - dev_err(&pdev->dev, "Can't get APB clk: %d\n", ret); - return ret; - } + if (IS_ERR(pc->pclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pc->pclk), "Can't get APB clk\n"); ret = clk_prepare_enable(pc->clk); - if (ret) { - dev_err(&pdev->dev, "Can't prepare enable bus clk: %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, "Can't prepare enable PWM clk\n"); - ret = clk_prepare(pc->pclk); + ret = clk_prepare_enable(pc->pclk); if (ret) { - dev_err(&pdev->dev, "Can't prepare APB clk: %d\n", ret); + dev_err_probe(&pdev->dev, ret, "Can't prepare enable APB clk\n"); goto err_clk; } - platform_set_drvdata(pdev, pc); + platform_set_drvdata(pdev, chip); - pc->data = id->data; - pc->chip.dev = &pdev->dev; - pc->chip.ops = &rockchip_pwm_ops; - pc->chip.base = -1; - pc->chip.npwm = 1; + pc->data = device_get_match_data(&pdev->dev); + chip->ops = &rockchip_pwm_ops; - if (pc->data->supports_polarity) { - pc->chip.of_xlate = of_pwm_xlate_with_flags; - pc->chip.of_pwm_n_cells = 3; - } + enable_conf = pc->data->enable_conf; + ctrl = readl_relaxed(pc->base + pc->data->regs.ctrl); + enabled = (ctrl & enable_conf) == enable_conf; - ret = pwmchip_add(&pc->chip); + ret = pwmchip_add(chip); if (ret < 0) { - clk_unprepare(pc->clk); - dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + dev_err_probe(&pdev->dev, ret, "pwmchip_add() failed\n"); goto err_pclk; } /* Keep the PWM clk enabled if the PWM appears to be up and running. */ - if (!pwm_is_enabled(pc->chip.pwms)) + if (!enabled) clk_disable(pc->clk); + clk_disable(pc->pclk); + return 0; err_pclk: - clk_unprepare(pc->pclk); + clk_disable_unprepare(pc->pclk); err_clk: clk_disable_unprepare(pc->clk); return ret; } -static int rockchip_pwm_remove(struct platform_device *pdev) +static void rockchip_pwm_remove(struct platform_device *pdev) { - struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev); + struct pwm_chip *chip = platform_get_drvdata(pdev); + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); - /* - * Disable the PWM clk before unpreparing it if the PWM device is still - * running. This should only happen when the last PWM user left it - * enabled, or when nobody requested a PWM that was previously enabled - * by the bootloader. - * - * FIXME: Maybe the core should disable all PWM devices in - * pwmchip_remove(). In this case we'd only have to call - * clk_unprepare() after pwmchip_remove(). - * - */ - if (pwm_is_enabled(pc->chip.pwms)) - clk_disable(pc->clk); + pwmchip_remove(chip); clk_unprepare(pc->pclk); clk_unprepare(pc->clk); - - return pwmchip_remove(&pc->chip); } static struct platform_driver rockchip_pwm_driver = { |
