2759 lines · 66.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * Generic pwmlib implementation * * Copyright (C) 2011 Sascha Hauer <s.hauer@pengutronix.de> * Copyright (C) 2011-2012 Avionic Design GmbH */ #define DEFAULT_SYMBOL_NAMESPACE "PWM" #include <linux/acpi.h>#include <linux/module.h>#include <linux/idr.h>#include <linux/of.h>#include <linux/pwm.h>#include <linux/list.h>#include <linux/mutex.h>#include <linux/err.h>#include <linux/slab.h>#include <linux/device.h>#include <linux/debugfs.h>#include <linux/seq_file.h> #include <dt-bindings/pwm/pwm.h> #include <uapi/linux/pwm.h> #define CREATE_TRACE_POINTS#include <trace/events/pwm.h> #define PWM_MINOR_COUNT 256 /* protects access to pwm_chips */static DEFINE_MUTEX(pwm_lock); static DEFINE_IDR(pwm_chips); static void pwmchip_lock(struct pwm_chip *chip){	if (chip->atomic)		spin_lock(&chip->atomic_lock);	else		mutex_lock(&chip->nonatomic_lock);} static void pwmchip_unlock(struct pwm_chip *chip){	if (chip->atomic)		spin_unlock(&chip->atomic_lock);	else		mutex_unlock(&chip->nonatomic_lock);} DEFINE_GUARD(pwmchip, struct pwm_chip *, pwmchip_lock(_T), pwmchip_unlock(_T)) static bool pwm_wf_valid(const struct pwm_waveform *wf){	/*	 * For now restrict waveforms to period_length_ns <= S64_MAX to provide	 * some space for future extensions. One possibility is to simplify	 * representing waveforms with inverted polarity using negative values	 * somehow.	 */	if (wf->period_length_ns > S64_MAX)		return false; 	if (wf->duty_length_ns > wf->period_length_ns)		return false; 	/*	 * .duty_offset_ns is supposed to be smaller than .period_length_ns, apart	 * from the corner case .duty_offset_ns == 0 && .period_length_ns == 0.	 */	if (wf->duty_offset_ns && wf->duty_offset_ns >= wf->period_length_ns)		return false; 	return true;} static void pwm_wf2state(const struct pwm_waveform *wf, struct pwm_state *state){	if (wf->period_length_ns) {		if (wf->duty_length_ns + wf->duty_offset_ns < wf->period_length_ns)			*state = (struct pwm_state){				.enabled = true,				.polarity = PWM_POLARITY_NORMAL,				.period = wf->period_length_ns,				.duty_cycle = wf->duty_length_ns,			};		else			*state = (struct pwm_state){				.enabled = true,				.polarity = PWM_POLARITY_INVERSED,				.period = wf->period_length_ns,				.duty_cycle = wf->period_length_ns - wf->duty_length_ns,			};	} else {		*state = (struct pwm_state){			.enabled = false,		};	}} static void pwm_state2wf(const struct pwm_state *state, struct pwm_waveform *wf){	if (state->enabled) {		if (state->polarity == PWM_POLARITY_NORMAL)			*wf = (struct pwm_waveform){				.period_length_ns = state->period,				.duty_length_ns = state->duty_cycle,				.duty_offset_ns = 0,			};		else			*wf = (struct pwm_waveform){				.period_length_ns = state->period,				.duty_length_ns = state->period - state->duty_cycle,				.duty_offset_ns = state->duty_cycle,			};	} else {		*wf = (struct pwm_waveform){			.period_length_ns = 0,		};	}} static int pwmwfcmp(const struct pwm_waveform *a, const struct pwm_waveform *b){	if (a->period_length_ns > b->period_length_ns)		return 1; 	if (a->period_length_ns < b->period_length_ns)		return -1; 	if (a->duty_length_ns > b->duty_length_ns)		return 1; 	if (a->duty_length_ns < b->duty_length_ns)		return -1; 	if (a->duty_offset_ns > b->duty_offset_ns)		return 1; 	if (a->duty_offset_ns < b->duty_offset_ns)		return -1; 	return 0;} static bool pwm_check_rounding(const struct pwm_waveform *wf,			       const struct pwm_waveform *wf_rounded){	if (!wf->period_length_ns)		return true; 	if (wf->period_length_ns < wf_rounded->period_length_ns)		return false; 	if (wf->duty_length_ns < wf_rounded->duty_length_ns)		return false; 	if (wf->duty_offset_ns < wf_rounded->duty_offset_ns)		return false; 	return true;} static int __pwm_round_waveform_tohw(struct pwm_chip *chip, struct pwm_device *pwm,				     const struct pwm_waveform *wf, void *wfhw){	const struct pwm_ops *ops = chip->ops;	int ret; 	ret = ops->round_waveform_tohw(chip, pwm, wf, wfhw);	trace_pwm_round_waveform_tohw(pwm, wf, wfhw, ret); 	return ret;} static int __pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,				       const void *wfhw, struct pwm_waveform *wf){	const struct pwm_ops *ops = chip->ops;	int ret; 	ret = ops->round_waveform_fromhw(chip, pwm, wfhw, wf);	trace_pwm_round_waveform_fromhw(pwm, wfhw, wf, ret); 	return ret;} static int __pwm_read_waveform(struct pwm_chip *chip, struct pwm_device *pwm, void *wfhw){	const struct pwm_ops *ops = chip->ops;	int ret; 	ret = ops->read_waveform(chip, pwm, wfhw);	trace_pwm_read_waveform(pwm, wfhw, ret); 	return ret;} static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, const void *wfhw){	const struct pwm_ops *ops = chip->ops;	int ret; 	ret = ops->write_waveform(chip, pwm, wfhw);	trace_pwm_write_waveform(pwm, wfhw, ret); 	return ret;} /** * pwm_round_waveform_might_sleep - Query hardware capabilities * Cannot be used in atomic context. * @pwm: PWM device * @wf: waveform to round and output parameter * * Typically a given waveform cannot be implemented exactly by hardware, e.g. * because hardware only supports coarse period resolution or no duty_offset. * This function returns the actually implemented waveform if you pass @wf to * pwm_set_waveform_might_sleep() now. * * Note however that the world doesn't stop turning when you call it, so when * doing:: * *   pwm_round_waveform_might_sleep(mypwm, &wf); *   pwm_set_waveform_might_sleep(mypwm, &wf, true); * * the latter might fail, e.g. because an input clock changed its rate between * these two calls and the waveform determined by * pwm_round_waveform_might_sleep() cannot be implemented any more. * * Usually all values passed in @wf are rounded down to the nearest possible * value (in the order period_length_ns, duty_length_ns and then * duty_offset_ns). Only if this isn't possible, a value might grow. See the * documentation for pwm_set_waveform_might_sleep() for a more formal * description. * * Returns: 0 on success, 1 if at least one value had to be rounded up or a * negative errno. * Context: May sleep. */int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf){	struct pwm_chip *chip = pwm->chip;	const struct pwm_ops *ops = chip->ops;	struct pwm_waveform wf_req = *wf;	char wfhw[PWM_WFHWSIZE];	int ret_tohw, ret_fromhw; 	BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw); 	if (!pwmchip_supports_waveform(chip))		return -EOPNOTSUPP; 	if (!pwm_wf_valid(wf))		return -EINVAL; 	guard(pwmchip)(chip); 	if (!chip->operational)		return -ENODEV; 	ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, wfhw);	if (ret_tohw < 0)		return ret_tohw; 	if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_tohw > 1)		dev_err(&chip->dev, "Unexpected return value from __pwm_round_waveform_tohw: requested %llu/%llu [+%llu], return value %d\n",			wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_tohw); 	ret_fromhw = __pwm_round_waveform_fromhw(chip, pwm, wfhw, wf);	if (ret_fromhw < 0)		return ret_fromhw; 	if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_fromhw > 0)		dev_err(&chip->dev, "Unexpected return value from __pwm_round_waveform_fromhw: requested %llu/%llu [+%llu], return value %d\n",			wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_fromhw); 	if (IS_ENABLED(CONFIG_PWM_DEBUG) &&	    (ret_tohw == 0) != pwm_check_rounding(&wf_req, wf))		dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu], ret: %d\n",			wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns,			wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, ret_tohw); 	return ret_tohw;}EXPORT_SYMBOL_GPL(pwm_round_waveform_might_sleep); /** * pwm_get_waveform_might_sleep - Query hardware about current configuration * Cannot be used in atomic context. * @pwm: PWM device * @wf: output parameter * * Stores the current configuration of the PWM in @wf. Note this is the * equivalent of pwm_get_state_hw() (and not pwm_get_state()) for pwm_waveform. * * Returns: 0 on success or a negative errno * Context: May sleep. */int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf){	struct pwm_chip *chip = pwm->chip;	const struct pwm_ops *ops = chip->ops;	char wfhw[PWM_WFHWSIZE];	int err; 	BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw); 	if (!pwmchip_supports_waveform(chip) || !ops->read_waveform)		return -EOPNOTSUPP; 	guard(pwmchip)(chip); 	if (!chip->operational)		return -ENODEV; 	err = __pwm_read_waveform(chip, pwm, &wfhw);	if (err)		return err; 	return __pwm_round_waveform_fromhw(chip, pwm, &wfhw, wf);}EXPORT_SYMBOL_GPL(pwm_get_waveform_might_sleep); /* Called with the pwmchip lock held */static int __pwm_set_waveform(struct pwm_device *pwm,			      const struct pwm_waveform *wf,			      bool exact){	struct pwm_chip *chip = pwm->chip;	const struct pwm_ops *ops = chip->ops;	char wfhw[PWM_WFHWSIZE];	struct pwm_waveform wf_rounded;	int err, ret_tohw; 	BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw); 	if (!pwmchip_supports_waveform(chip))		return -EOPNOTSUPP; 	if (!pwm_wf_valid(wf))		return -EINVAL; 	ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw);	if (ret_tohw < 0)		return ret_tohw; 	if ((IS_ENABLED(CONFIG_PWM_DEBUG) || exact) && wf->period_length_ns) {		err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded);		if (err)			return err; 		if (IS_ENABLED(CONFIG_PWM_DEBUG) && (ret_tohw == 0) != pwm_check_rounding(wf, &wf_rounded))			dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu], ret: %d\n",				wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,				wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns, ret_tohw); 		if (exact && pwmwfcmp(wf, &wf_rounded)) {			dev_dbg(&chip->dev, "Requested no rounding, but %llu/%llu [+%llu] -> %llu/%llu [+%llu]\n",				wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,				wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns); 			return 1;		}	} 	err = __pwm_write_waveform(chip, pwm, &wfhw);	if (err)		return err; 	/* update .state */	pwm_wf2state(wf, &pwm->state); 	if (IS_ENABLED(CONFIG_PWM_DEBUG) && ops->read_waveform && wf->period_length_ns) {		struct pwm_waveform wf_set; 		err = __pwm_read_waveform(chip, pwm, &wfhw);		if (err)			/* maybe ignore? */			return err; 		err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_set);		if (err)			/* maybe ignore? */			return err; 		if (pwmwfcmp(&wf_set, &wf_rounded) != 0)			dev_err(&chip->dev,				"Unexpected setting: requested %llu/%llu [+%llu], expected %llu/%llu [+%llu], set %llu/%llu [+%llu]\n",				wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,				wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns,				wf_set.duty_length_ns, wf_set.period_length_ns, wf_set.duty_offset_ns);	} 	return ret_tohw;} /** * pwm_set_waveform_might_sleep - Apply a new waveform * Cannot be used in atomic context. * @pwm: PWM device * @wf: The waveform to apply * @exact: If true no rounding is allowed * * Typically a requested waveform cannot be implemented exactly, e.g. because * you requested .period_length_ns = 100 ns, but the hardware can only set * periods that are a multiple of 8.5 ns. With that hardware passing @exact = * true results in pwm_set_waveform_might_sleep() failing and returning -EDOM. * If @exact = false you get a period of 93.5 ns (i.e. the biggest period not * bigger than the requested value). * Note that even with @exact = true, some rounding by less than 1 ns is * possible/needed. In the above example requesting .period_length_ns = 94 and * @exact = true, you get the hardware configured with period = 93.5 ns. * * Let C be the set of possible hardware configurations for a given PWM device, * consisting of tuples (p, d, o) where p is the period length, d is the duty * length and o the duty offset. * * The following algorithm is implemented to pick the hardware setting * (p, d, o) ∈ C for a given request (p', d', o') with @exact = false:: * *   p = max( { ṗ | ∃ ḋ, ȯ : (ṗ, ḋ, ȯ) ∈ C ∧ ṗ ≤ p' } ∪ { min({ ṗ | ∃ ḋ, ȯ : (ṗ, ḋ, ȯ) ∈ C }) }) *   d = max( { ḋ | ∃ ȯ : (p, ḋ, ȯ) ∈ C ∧ ḋ ≤ d' } ∪ { min({ ḋ | ∃ ȯ : (p, ḋ, ȯ) ∈ C }) }) *   o = max( { ȯ | (p, d, ȯ) ∈ C ∧ ȯ ≤ o' } ∪ { min({ ȯ | (p, d, ȯ) ∈ C }) }) * * In words: The chosen period length is the maximal possible period length not * bigger than the requested period length and if that doesn't exist, the * minimal period length. The chosen duty length is the maximal possible duty * length that is compatible with the chosen period length and isn't bigger than * the requested duty length. Again if such a value doesn't exist, the minimal * duty length compatible with the chosen period is picked. After that the duty * offset compatible with the chosen period and duty length is chosen in the * same way. * * Returns: 0 on success, -EDOM if setting failed due to the exact waveform not * being possible (if @exact), or a different negative errno on failure. * Context: May sleep. */int pwm_set_waveform_might_sleep(struct pwm_device *pwm,				 const struct pwm_waveform *wf, bool exact){	struct pwm_chip *chip = pwm->chip;	int err; 	might_sleep(); 	guard(pwmchip)(chip); 	if (!chip->operational)		return -ENODEV; 	if (IS_ENABLED(CONFIG_PWM_DEBUG) && chip->atomic) {		/*		 * Catch any drivers that have been marked as atomic but		 * that will sleep anyway.		 */		non_block_start();		err = __pwm_set_waveform(pwm, wf, exact);		non_block_end();	} else {		err = __pwm_set_waveform(pwm, wf, exact);	} 	/*	 * map err == 1 to -EDOM for exact requests and 0 for !exact ones. Also	 * make sure that -EDOM is only returned in exactly that case. Note that	 * __pwm_set_waveform() should never return -EDOM which justifies the	 * unlikely().	 */	if (unlikely(err == -EDOM))		err = -EINVAL;	else if (exact && err == 1)		err = -EDOM;	else if (err == 1)		err = 0; 	return err;}EXPORT_SYMBOL_GPL(pwm_set_waveform_might_sleep); static void pwm_apply_debug(struct pwm_device *pwm,			    const struct pwm_state *state){	struct pwm_state *last = &pwm->last;	struct pwm_chip *chip = pwm->chip;	struct pwm_state s1 = { 0 }, s2 = { 0 };	int err; 	if (!IS_ENABLED(CONFIG_PWM_DEBUG))		return; 	/* No reasonable diagnosis possible without .get_state() */	if (!chip->ops->get_state)		return; 	/*	 * If a disabled PWM was requested the result is unspecified, so nothing

Lines 1–500 of 2759.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t