diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c index f3647b31715..627cc1931dd 100644 --- a/drivers/pwm/pwm-rockchip.c +++ b/drivers/pwm/pwm-rockchip.c @@ -1,9 +1,12 @@ -// SPDX-License-Identifier: GPL-2.0-only /* * PWM driver for Rockchip SoCs * * Copyright (C) 2014 Beniamino Galvani * Copyright (C) 2014 ROCKCHIP, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. */ #include @@ -11,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -26,15 +30,25 @@ #define PWM_INACTIVE_POSITIVE (1 << 4) #define PWM_POLARITY_MASK (PWM_DUTY_POSITIVE | PWM_INACTIVE_POSITIVE) #define PWM_OUTPUT_LEFT (0 << 5) +#define PWM_OUTPUT_CENTER (1 << 5) #define PWM_LOCK_EN (1 << 6) #define PWM_LP_DISABLE (0 << 8) +#define PWM_ONESHOT_COUNT_SHIFT 24 +#define PWM_ONESHOT_COUNT_MAX 256 + struct rockchip_pwm_chip { struct pwm_chip chip; struct clk *clk; struct clk *pclk; + struct pinctrl *pinctrl; + struct pinctrl_state *active_state; const struct rockchip_pwm_data *data; void __iomem *base; + unsigned long clk_rate; + bool vop_pwm_en; /* indicate voppwm mirror register state */ + bool center_aligned; + bool oneshot; }; struct rockchip_pwm_regs { @@ -49,7 +63,9 @@ struct rockchip_pwm_data { unsigned int prescaler; bool supports_polarity; bool supports_lock; + bool vop_pwm; u32 enable_conf; + u32 enable_conf_mask; }; static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c) @@ -63,7 +79,6 @@ static void rockchip_pwm_get_state(struct pwm_chip *chip, { struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); u32 enable_conf = pc->data->enable_conf; - unsigned long clk_rate; u64 tmp; u32 val; int ret; @@ -72,59 +87,77 @@ static void rockchip_pwm_get_state(struct pwm_chip *chip, if (ret) return; - ret = clk_enable(pc->clk); - if (ret) - return; - - 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); + state->period = DIV_ROUND_CLOSEST_ULL(tmp, pc->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); + state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, pc->clk_rate); val = readl_relaxed(pc->base + pc->data->regs.ctrl); - state->enabled = (val & enable_conf) == enable_conf; - - if (pc->data->supports_polarity && !(val & PWM_DUTY_POSITIVE)) - state->polarity = PWM_POLARITY_INVERSED; + if (pc->data->supports_polarity) + state->enabled = ((val & enable_conf) != enable_conf) ? + false : true; else - state->polarity = PWM_POLARITY_NORMAL; + state->enabled = ((val & enable_conf) == enable_conf) ? + true : false; + + if (pc->data->supports_polarity) { + if (!(val & PWM_DUTY_POSITIVE)) + state->polarity = PWM_POLARITY_INVERSED; + } - clk_disable(pc->clk); clk_disable(pc->pclk); } static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, - const struct pwm_state *state) + struct pwm_state *state) { struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); unsigned long period, duty; - u64 clk_rate, div; + unsigned long flags; + u64 div; u32 ctrl; - clk_rate = clk_get_rate(pc->clk); - /* * Since period and duty cycle registers have a width of 32 * bits, every possible input period can be obtained using the * default prescaler value for all practical clock rate values. */ - div = clk_rate * state->period; + div = (u64)pc->clk_rate * state->period; period = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC); - div = clk_rate * state->duty_cycle; + div = (u64)pc->clk_rate * state->duty_cycle; duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC); + local_irq_save(flags); /* * Lock the period and duty of previous configuration, then * change the duty and period, that would not be effective. */ ctrl = readl_relaxed(pc->base + pc->data->regs.ctrl); + if (pc->data->vop_pwm) { + if (pc->vop_pwm_en) + ctrl |= PWM_ENABLE; + else + ctrl &= ~PWM_ENABLE; + } + +#ifdef CONFIG_PWM_ROCKCHIP_ONESHOT + if (state->oneshot_count > PWM_ONESHOT_COUNT_MAX) { + pc->oneshot = false; + dev_err(chip->dev, "Oneshot_count value overflow.\n"); + } else if (state->oneshot_count > 0) { + pc->oneshot = true; + ctrl |= (state->oneshot_count - 1) << PWM_ONESHOT_COUNT_SHIFT; + } else { + pc->oneshot = false; + ctrl |= PWM_CONTINUOUS; + } +#endif + if (pc->data->supports_lock) { ctrl |= PWM_LOCK_EN; writel_relaxed(ctrl, pc->base + pc->data->regs.ctrl); @@ -150,6 +183,7 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, ctrl &= ~PWM_LOCK_EN; writel(ctrl, pc->base + pc->data->regs.ctrl); + local_irq_restore(flags); } static int rockchip_pwm_enable(struct pwm_chip *chip, @@ -168,13 +202,24 @@ static int rockchip_pwm_enable(struct pwm_chip *chip, } val = readl_relaxed(pc->base + pc->data->regs.ctrl); + val &= ~pc->data->enable_conf_mask; + + if (PWM_OUTPUT_CENTER & pc->data->enable_conf_mask) { + if (pc->center_aligned) + val |= PWM_OUTPUT_CENTER; + } - if (enable) + if (enable) { val |= enable_conf; - else + if (pc->oneshot) + val &= ~PWM_CONTINUOUS; + } else { val &= ~enable_conf; + } writel_relaxed(val, pc->base + pc->data->regs.ctrl); + if (pc->data->vop_pwm) + pc->vop_pwm_en = enable; if (!enable) clk_disable(pc->clk); @@ -194,10 +239,6 @@ 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; @@ -216,8 +257,15 @@ static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, goto out; } + /* + * Update the state with the real hardware, which can differ a bit + * because of period/duty_cycle approximation. + */ + rockchip_pwm_get_state(chip, pwm, state); + + if ((state->enabled || pc->oneshot) && pc->active_state) + ret = pinctrl_select_state(pc->pinctrl, pc->active_state); out: - clk_disable(pc->clk); clk_disable(pc->pclk); return ret; @@ -239,7 +287,9 @@ static const struct rockchip_pwm_data pwm_data_v1 = { .prescaler = 2, .supports_polarity = false, .supports_lock = false, + .vop_pwm = false, .enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN, + .enable_conf_mask = BIT(1) | BIT(3), }; static const struct rockchip_pwm_data pwm_data_v2 = { @@ -252,8 +302,10 @@ static const struct rockchip_pwm_data pwm_data_v2 = { .prescaler = 1, .supports_polarity = true, .supports_lock = false, + .vop_pwm = false, .enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | PWM_CONTINUOUS, + .enable_conf_mask = GENMASK(2, 0) | BIT(5) | BIT(8), }; static const struct rockchip_pwm_data pwm_data_vop = { @@ -266,8 +318,10 @@ static const struct rockchip_pwm_data pwm_data_vop = { .prescaler = 1, .supports_polarity = true, .supports_lock = false, + .vop_pwm = true, .enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | PWM_CONTINUOUS, + .enable_conf_mask = GENMASK(2, 0) | BIT(5) | BIT(8), }; static const struct rockchip_pwm_data pwm_data_v3 = { @@ -280,8 +334,10 @@ static const struct rockchip_pwm_data pwm_data_v3 = { .prescaler = 1, .supports_polarity = true, .supports_lock = true, + .vop_pwm = false, .enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | PWM_CONTINUOUS, + .enable_conf_mask = GENMASK(2, 0) | BIT(5) | BIT(8), }; static const struct of_device_id rockchip_pwm_dt_ids[] = { @@ -297,8 +353,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev) { const struct of_device_id *id; struct rockchip_pwm_chip *pc; - u32 enable_conf, ctrl; - bool enabled; + struct resource *r; int ret, count; id = of_match_device(rockchip_pwm_dt_ids, &pdev->dev); @@ -309,16 +364,22 @@ static int rockchip_pwm_probe(struct platform_device *pdev) if (!pc) return -ENOMEM; - pc->base = devm_platform_ioremap_resource(pdev, 0); + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pc->base = devm_ioremap(&pdev->dev, r->start, + resource_size(r)); 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)) - return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk), - "Can't get PWM clk\n"); + 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; + } } count = of_count_phandle_with_args(pdev->dev.of_node, @@ -337,26 +398,42 @@ static int rockchip_pwm_probe(struct platform_device *pdev) ret = clk_prepare_enable(pc->clk); if (ret) { - dev_err(&pdev->dev, "Can't prepare enable PWM clk: %d\n", ret); + dev_err(&pdev->dev, "Can't prepare enable bus clk: %d\n", ret); return ret; } - ret = clk_prepare_enable(pc->pclk); + ret = clk_prepare(pc->pclk); if (ret) { - dev_err(&pdev->dev, "Can't prepare enable APB clk: %d\n", ret); + dev_err(&pdev->dev, "Can't prepare APB clk: %d\n", ret); goto err_clk; } + pc->pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR(pc->pinctrl)) { + dev_err(&pdev->dev, "Get pinctrl failed!\n"); + return PTR_ERR(pc->pinctrl); + } + + pc->active_state = pinctrl_lookup_state(pc->pinctrl, "active"); + if (IS_ERR(pc->active_state)) + pc->active_state = NULL; + platform_set_drvdata(pdev, pc); pc->data = id->data; pc->chip.dev = &pdev->dev; pc->chip.ops = &rockchip_pwm_ops; + pc->chip.base = -1; pc->chip.npwm = 1; + pc->clk_rate = clk_get_rate(pc->clk); - enable_conf = pc->data->enable_conf; - ctrl = readl_relaxed(pc->base + pc->data->regs.ctrl); - enabled = (ctrl & enable_conf) == enable_conf; + if (pc->data->supports_polarity) { + pc->chip.of_xlate = of_pwm_xlate_with_flags; + pc->chip.of_pwm_n_cells = 3; + } + + pc->center_aligned = + device_property_read_bool(&pdev->dev, "center-aligned"); ret = pwmchip_add(&pc->chip); if (ret < 0) { @@ -365,15 +442,13 @@ static int rockchip_pwm_probe(struct platform_device *pdev) } /* Keep the PWM clk enabled if the PWM appears to be up and running. */ - if (!enabled) + if (!pwm_is_enabled(pc->chip.pwms)) clk_disable(pc->clk); - clk_disable(pc->pclk); - return 0; err_pclk: - clk_disable_unprepare(pc->pclk); + clk_unprepare(pc->pclk); err_clk: clk_disable_unprepare(pc->clk); @@ -384,11 +459,24 @@ static int rockchip_pwm_remove(struct platform_device *pdev) { struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev); - pwmchip_remove(&pc->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); clk_unprepare(pc->pclk); clk_unprepare(pc->clk); + pwmchip_remove(&pc->chip); return 0; } @@ -400,7 +488,21 @@ static struct platform_driver rockchip_pwm_driver = { .probe = rockchip_pwm_probe, .remove = rockchip_pwm_remove, }; +#ifdef CONFIG_ROCKCHIP_THUNDER_BOOT +static int __init rockchip_pwm_driver_init(void) +{ + return platform_driver_register(&rockchip_pwm_driver); +} +subsys_initcall(rockchip_pwm_driver_init); + +static void __exit rockchip_pwm_driver_exit(void) +{ + platform_driver_unregister(&rockchip_pwm_driver); +} +module_exit(rockchip_pwm_driver_exit); +#else module_platform_driver(rockchip_pwm_driver); +#endif MODULE_AUTHOR("Beniamino Galvani "); MODULE_DESCRIPTION("Rockchip SoC PWM driver");