diff options
Diffstat (limited to 'drivers/pwm/core.c')
-rw-r--r-- | drivers/pwm/core.c | 209 |
1 files changed, 148 insertions, 61 deletions
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index c8016999f0..a3f27708e6 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -1,17 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2011 Sascha Hauer <s.hauer@pengutronix.de> + /* * Generic pwmlib implementation - * - * Copyright (C) 2011 Sascha Hauer <s.hauer@pengutronix.de> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <common.h> #include <errno.h> @@ -20,22 +11,48 @@ #include <linux/list.h> #include <linux/err.h> +/** + * struct pwm_args - board-dependent PWM arguments + * @period_ns: reference period + * @polarity: reference polarity + * + * This structure describes board-dependent arguments attached to a PWM + * device. These arguments are usually retrieved from the PWM lookup table or + * device tree. + * + * Do not confuse this with the PWM state: PWM arguments represent the initial + * configuration that users want to use on this PWM device rather than the + * current PWM hardware state. + */ + +struct pwm_args { + unsigned int period_ns; + unsigned int polarity; +}; + struct pwm_device { struct pwm_chip *chip; unsigned long flags; #define FLAG_REQUESTED 0 -#define FLAG_ENABLED 1 struct list_head node; - struct device_d *hwdev; - struct device_d dev; + struct device *hwdev; + struct device dev; - unsigned int duty_ns; - unsigned int period_ns; - unsigned int p_enable; + struct pwm_state params; + struct pwm_args args; }; static LIST_HEAD(pwm_list); +void pwm_print(void) +{ + struct pwm_device *pwm; + + list_for_each_entry(pwm, &pwm_list, node) + printf("%s\n", pwm->chip->devname); +} +EXPORT_SYMBOL(pwm_print); + static struct pwm_device *_find_pwm(const char *devname) { struct pwm_device *pwm; @@ -48,20 +65,18 @@ static struct pwm_device *_find_pwm(const char *devname) return NULL; } -static int set_duty_period_ns(struct param_d *p, void *priv) +static int apply_params(struct param_d *p, void *priv) { struct pwm_device *pwm = priv; - pwm_config(pwm, pwm->chip->duty_ns, pwm->chip->period_ns); - - return 0; + return pwm_apply_state(pwm, &pwm->params); } static int set_enable(struct param_d *p, void *priv) { struct pwm_device *pwm = priv; - if (pwm->p_enable) + if (pwm->params.p_enable) pwm_enable(pwm); else pwm_disable(pwm); @@ -76,7 +91,7 @@ static int set_enable(struct param_d *p, void *priv) * register a new pwm. pwm->devname must be initialized, usually * from dev_name(dev) from the hardware driver. */ -int pwmchip_add(struct pwm_chip *chip, struct device_d *dev) +int pwmchip_add(struct pwm_chip *chip, struct device *dev) { struct pwm_device *pwm; struct param_d *p; @@ -99,22 +114,24 @@ int pwmchip_add(struct pwm_chip *chip, struct device_d *dev) list_add_tail(&pwm->node, &pwm_list); - p = dev_add_param_uint32(&pwm->dev, "duty_ns", set_duty_period_ns, - NULL, &pwm->chip->duty_ns, "%u", pwm); + p = dev_add_param_uint32(&pwm->dev, "duty_ns", apply_params, + NULL, &pwm->params.duty_ns, "%u", pwm); if (IS_ERR(p)) return PTR_ERR(p); - p = dev_add_param_uint32(&pwm->dev, "period_ns", set_duty_period_ns, - NULL, &pwm->chip->period_ns, "%u", pwm); + p = dev_add_param_uint32(&pwm->dev, "period_ns", apply_params, + NULL, &pwm->params.period_ns, "%u", pwm); if (IS_ERR(p)) return PTR_ERR(p); p = dev_add_param_bool(&pwm->dev, "enable", set_enable, - NULL, &pwm->p_enable, pwm); + NULL, &pwm->params.p_enable, pwm); if (IS_ERR(p)) return PTR_ERR(p); - return 0; + p = dev_add_param_bool(&pwm->dev, "inverted", apply_params, + NULL, &pwm->params.polarity, pwm); + return PTR_ERR_OR_ZERO(p); } EXPORT_SYMBOL_GPL(pwmchip_add); @@ -186,7 +203,7 @@ static struct pwm_device *of_node_to_pwm_device(struct device_node *np, int id) struct pwm_device *pwm; list_for_each_entry(pwm, &pwm_list, node) { - if (pwm->hwdev && pwm->hwdev->device_node == np && + if (pwm->hwdev && pwm->hwdev->of_node == np && pwm->chip->id == id) return pwm; } @@ -199,6 +216,7 @@ struct pwm_device *of_pwm_request(struct device_node *np, const char *con_id) struct of_phandle_args args; int index = 0; struct pwm_device *pwm; + struct pwm_state state; int ret; if (con_id) @@ -218,11 +236,22 @@ struct pwm_device *of_pwm_request(struct device_node *np, const char *con_id) } if (args.args_count > 1) - pwm_set_period(pwm, args.args[1]); + pwm->args.period_ns = args.args[1]; + + pwm->args.polarity = PWM_POLARITY_NORMAL; + + if (args.args_count > 2 && args.args[2] & PWM_POLARITY_INVERTED) + pwm->args.polarity = PWM_POLARITY_INVERTED; ret = __pwm_request(pwm); if (ret) - return ERR_PTR(-ret); + return ERR_PTR(ret); + + pwm_init_state(pwm, &state); + + ret = pwm_apply_state(pwm, &state); + if (ret) + return ERR_PTR(ret); return pwm; } @@ -238,42 +267,94 @@ void pwm_free(struct pwm_device *pwm) } EXPORT_SYMBOL_GPL(pwm_free); -/* - * pwm_config - change a PWM device configuration +void pwm_get_state(const struct pwm_device *pwm, struct pwm_state *state) +{ + *state = pwm->chip->state; +} +EXPORT_SYMBOL_GPL(pwm_get_state); + +static void pwm_get_args(const struct pwm_device *pwm, struct pwm_args *args) +{ + *args = pwm->args; +} + +/** + * pwm_init_state() - prepare a new state to be applied with pwm_apply_state() + * @pwm: PWM device + * @state: state to fill with the prepared PWM state + * + * This functions prepares a state that can later be tweaked and applied + * to the PWM device with pwm_apply_state(). This is a convenient function + * that first retrieves the current PWM state and the replaces the period + * and polarity fields with the reference values defined in pwm->args. + * Once the function returns, you can adjust the ->enabled and ->duty_cycle + * fields according to your needs before calling pwm_apply_state(). + * + * ->duty_cycle is initially set to zero to avoid cases where the current + * ->duty_cycle value exceed the pwm_args->period one, which would trigger + * an error if the user calls pwm_apply_state() without adjusting ->duty_cycle + * first. */ -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) +void pwm_init_state(const struct pwm_device *pwm, + struct pwm_state *state) { - pwm->chip->duty_ns = duty_ns; - pwm->chip->period_ns = period_ns; + struct pwm_args args; - if (period_ns == 0) - return -EINVAL; + /* First get the current state. */ + pwm_get_state(pwm, state); - if (duty_ns > period_ns) - return -EINVAL; + /* Then fill it with the reference config */ + pwm_get_args(pwm, &args); - return pwm->chip->ops->config(pwm->chip, duty_ns, period_ns); + state->period_ns = args.period_ns; + state->polarity = args.polarity; + state->duty_ns = 0; } -EXPORT_SYMBOL_GPL(pwm_config); +EXPORT_SYMBOL_GPL(pwm_init_state); -void pwm_set_period(struct pwm_device *pwm, unsigned int period_ns) +int pwm_apply_state(struct pwm_device *pwm, const struct pwm_state *state) { - pwm->period_ns = period_ns; -} + struct pwm_chip *chip = pwm->chip; + int ret = -EINVAL; -unsigned int pwm_get_period(struct pwm_device *pwm) -{ - return pwm->period_ns; + if (state->period_ns == 0) + goto err; + + if (state->duty_ns > state->period_ns) + goto err; + + ret = chip->ops->apply(chip, state); +err: + if (ret == 0) + chip->state = *state; + + pwm->params = chip->state; + return ret; } -void pwm_set_duty_cycle(struct pwm_device *pwm, unsigned int duty_ns) +/* + * pwm_config - change a PWM device configuration + */ +int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) { - pwm->duty_ns = duty_ns; + struct pwm_state state; + + if (duty_ns < 0 || period_ns < 0) + return -EINVAL; + + pwm_get_state(pwm, &state); + if (state.duty_ns == duty_ns && state.period_ns == period_ns) + return 0; + + state.duty_ns = duty_ns; + state.period_ns = period_ns; + return pwm_apply_state(pwm, &state); } +EXPORT_SYMBOL_GPL(pwm_config); -unsigned int pwm_get_duty_cycle(struct pwm_device *pwm) +unsigned int pwm_get_period(struct pwm_device *pwm) { - return pwm->duty_ns; + return pwm->chip->state.period_ns; } /* @@ -281,12 +362,14 @@ unsigned int pwm_get_duty_cycle(struct pwm_device *pwm) */ int pwm_enable(struct pwm_device *pwm) { - pwm->p_enable = 1; + struct pwm_state state; - if (!test_and_set_bit(FLAG_ENABLED, &pwm->flags)) - return pwm->chip->ops->enable(pwm->chip); + pwm_get_state(pwm, &state); + if (state.p_enable) + return 0; - return 0; + state.p_enable = true; + return pwm_apply_state(pwm, &state); } EXPORT_SYMBOL_GPL(pwm_enable); @@ -295,9 +378,13 @@ EXPORT_SYMBOL_GPL(pwm_enable); */ void pwm_disable(struct pwm_device *pwm) { - pwm->p_enable = 0; + struct pwm_state state; + + pwm_get_state(pwm, &state); + if (!state.p_enable) + return; - if (test_and_clear_bit(FLAG_ENABLED, &pwm->flags)) - pwm->chip->ops->disable(pwm->chip); + state.p_enable = false; + pwm_apply_state(pwm, &state); } EXPORT_SYMBOL_GPL(pwm_disable); |