summaryrefslogtreecommitdiff
path: root/drivers/pwm
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2016-05-25 10:40:15 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2016-05-25 10:40:15 -0700
commitecc5fbd5ef472a4c659dc56a5739b3f041c0530c (patch)
treeeec1c3ddd6082e6391d7d27ae78d813a8f6c216c /drivers/pwm
parent1f93d2abf488c6a41bdd5e6caf80b559493eea8d (diff)
parent18c588786c08458f5d965d8735ab48f9e51e0b4b (diff)
Merge tag 'pwm/for-4.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm
Pull pwm updates from Thierry Reding: "This set of changes introduces an atomic API to the PWM subsystem. This is influenced by the DRM atomic API that was introduced a while back, though it is obviously a lot simpler. The fundamental idea remains the same, though: drivers provide a single callback to implement the atomic configuration of a PWM channel. As a side-effect the PWM subsystem gains the ability for initial state retrieval, so that the logical state mirrors that of the hardware. Many use-cases don't care about this, but for others it is essential. These new features require changes in all users, which these patches take care of. The core is transitioned to use the atomic callback if available and provides a fallback mechanism for other drivers. Changes to transition users and drivers to the atomic API are postponed to v4.8" * tag 'pwm/for-4.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: (30 commits) pwm: Add information about polarity, duty cycle and period to debugfs pwm: Switch to the atomic API pwm: Update documentation pwm: Add core infrastructure to allow atomic updates pwm: Add hardware readout infrastructure pwm: Move the enabled/disabled info into pwm_state pwm: Introduce the pwm_state concept pwm: Keep PWM state in sync with hardware state ARM: Explicitly apply PWM config extracted from pwm_args drm: i915: Explicitly apply PWM config extracted from pwm_args input: misc: pwm-beeper: Explicitly apply PWM config extracted from pwm_args input: misc: max8997: Explicitly apply PWM config extracted from pwm_args backlight: lm3630a: explicitly apply PWM config extracted from pwm_args backlight: lp855x: Explicitly apply PWM config extracted from pwm_args backlight: lp8788: Explicitly apply PWM config extracted from pwm_args backlight: pwm_bl: Use pwm_get_args() where appropriate fbdev: ssd1307fb: Use pwm_get_args() where appropriate regulator: pwm: Use pwm_get_args() where appropriate leds: pwm: Use pwm_get_args() where appropriate input: misc: max77693: Use pwm_get_args() where appropriate ...
Diffstat (limited to 'drivers/pwm')
-rw-r--r--drivers/pwm/core.c224
-rw-r--r--drivers/pwm/pwm-crc.c2
-rw-r--r--drivers/pwm/pwm-lpc18xx-sct.c2
-rw-r--r--drivers/pwm/pwm-omap-dmtimer.c2
-rw-r--r--drivers/pwm/pwm-rcar.c2
-rw-r--r--drivers/pwm/pwm-sun4i.c3
-rw-r--r--drivers/pwm/sysfs.c70
7 files changed, 199 insertions, 106 deletions
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 680fbc795a0a..dba3843c53b8 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -75,6 +75,7 @@ static void free_pwms(struct pwm_chip *chip)
for (i = 0; i < chip->npwm; i++) {
struct pwm_device *pwm = &chip->pwms[i];
+
radix_tree_delete(&pwm_tree, pwm->pwm);
}
@@ -128,13 +129,6 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
set_bit(PWMF_REQUESTED, &pwm->flags);
pwm->label = label;
- /*
- * FIXME: This should be removed once all PWM users properly make use
- * of struct pwm_args to initialize the PWM device. As long as this is
- * here, the PWM state and hardware state can get out of sync.
- */
- pwm_apply_args(pwm);
-
return 0;
}
@@ -233,6 +227,19 @@ void *pwm_get_chip_data(struct pwm_device *pwm)
}
EXPORT_SYMBOL_GPL(pwm_get_chip_data);
+static bool pwm_ops_check(const struct pwm_ops *ops)
+{
+ /* driver supports legacy, non-atomic operation */
+ if (ops->config && ops->enable && ops->disable)
+ return true;
+
+ /* driver supports atomic operation */
+ if (ops->apply)
+ return true;
+
+ return false;
+}
+
/**
* pwmchip_add_with_polarity() - register a new PWM chip
* @chip: the PWM chip to add
@@ -251,8 +258,10 @@ int pwmchip_add_with_polarity(struct pwm_chip *chip,
unsigned int i;
int ret;
- if (!chip || !chip->dev || !chip->ops || !chip->ops->config ||
- !chip->ops->enable || !chip->ops->disable || !chip->npwm)
+ if (!chip || !chip->dev || !chip->ops || !chip->npwm)
+ return -EINVAL;
+
+ if (!pwm_ops_check(chip->ops))
return -EINVAL;
mutex_lock(&pwm_lock);
@@ -261,7 +270,7 @@ int pwmchip_add_with_polarity(struct pwm_chip *chip,
if (ret < 0)
goto out;
- chip->pwms = kzalloc(chip->npwm * sizeof(*pwm), GFP_KERNEL);
+ chip->pwms = kcalloc(chip->npwm, sizeof(*pwm), GFP_KERNEL);
if (!chip->pwms) {
ret = -ENOMEM;
goto out;
@@ -275,8 +284,10 @@ int pwmchip_add_with_polarity(struct pwm_chip *chip,
pwm->chip = chip;
pwm->pwm = chip->base + i;
pwm->hwpwm = i;
- pwm->polarity = polarity;
- mutex_init(&pwm->lock);
+ pwm->state.polarity = polarity;
+
+ if (chip->ops->get_state)
+ chip->ops->get_state(chip, pwm, &pwm->state);
radix_tree_insert(&pwm_tree, pwm->pwm, pwm);
}
@@ -436,107 +447,138 @@ void pwm_free(struct pwm_device *pwm)
EXPORT_SYMBOL_GPL(pwm_free);
/**
- * pwm_config() - change a PWM device configuration
+ * pwm_apply_state() - atomically apply a new state to a PWM device
* @pwm: PWM device
- * @duty_ns: "on" time (in nanoseconds)
- * @period_ns: duration (in nanoseconds) of one cycle
- *
- * Returns: 0 on success or a negative error code on failure.
+ * @state: new state to apply. This can be adjusted by the PWM driver
+ * if the requested config is not achievable, for example,
+ * ->duty_cycle and ->period might be approximated.
*/
-int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
+int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state)
{
int err;
- if (!pwm || duty_ns < 0 || period_ns <= 0 || duty_ns > period_ns)
+ if (!pwm)
return -EINVAL;
- err = pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns);
- if (err)
- return err;
-
- pwm->duty_cycle = duty_ns;
- pwm->period = period_ns;
+ if (!memcmp(state, &pwm->state, sizeof(*state)))
+ return 0;
- return 0;
-}
-EXPORT_SYMBOL_GPL(pwm_config);
+ if (pwm->chip->ops->apply) {
+ err = pwm->chip->ops->apply(pwm->chip, pwm, state);
+ if (err)
+ return err;
-/**
- * pwm_set_polarity() - configure the polarity of a PWM signal
- * @pwm: PWM device
- * @polarity: new polarity of the PWM signal
- *
- * Note that the polarity cannot be configured while the PWM device is
- * enabled.
- *
- * Returns: 0 on success or a negative error code on failure.
- */
-int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity)
-{
- int err;
+ pwm->state = *state;
+ } else {
+ /*
+ * FIXME: restore the initial state in case of error.
+ */
+ if (state->polarity != pwm->state.polarity) {
+ if (!pwm->chip->ops->set_polarity)
+ return -ENOTSUPP;
+
+ /*
+ * Changing the polarity of a running PWM is
+ * only allowed when the PWM driver implements
+ * ->apply().
+ */
+ if (pwm->state.enabled) {
+ pwm->chip->ops->disable(pwm->chip, pwm);
+ pwm->state.enabled = false;
+ }
+
+ err = pwm->chip->ops->set_polarity(pwm->chip, pwm,
+ state->polarity);
+ if (err)
+ return err;
+
+ pwm->state.polarity = state->polarity;
+ }
- if (!pwm || !pwm->chip->ops)
- return -EINVAL;
+ if (state->period != pwm->state.period ||
+ state->duty_cycle != pwm->state.duty_cycle) {
+ err = pwm->chip->ops->config(pwm->chip, pwm,
+ state->duty_cycle,
+ state->period);
+ if (err)
+ return err;
- if (!pwm->chip->ops->set_polarity)
- return -ENOSYS;
+ pwm->state.duty_cycle = state->duty_cycle;
+ pwm->state.period = state->period;
+ }
- mutex_lock(&pwm->lock);
+ if (state->enabled != pwm->state.enabled) {
+ if (state->enabled) {
+ err = pwm->chip->ops->enable(pwm->chip, pwm);
+ if (err)
+ return err;
+ } else {
+ pwm->chip->ops->disable(pwm->chip, pwm);
+ }
- if (pwm_is_enabled(pwm)) {
- err = -EBUSY;
- goto unlock;
+ pwm->state.enabled = state->enabled;
+ }
}
- err = pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity);
- if (err)
- goto unlock;
-
- pwm->polarity = polarity;
-
-unlock:
- mutex_unlock(&pwm->lock);
- return err;
+ return 0;
}
-EXPORT_SYMBOL_GPL(pwm_set_polarity);
+EXPORT_SYMBOL_GPL(pwm_apply_state);
/**
- * pwm_enable() - start a PWM output toggling
+ * pwm_adjust_config() - adjust the current PWM config to the PWM arguments
* @pwm: PWM device
*
- * Returns: 0 on success or a negative error code on failure.
+ * This function will adjust the PWM config to the PWM arguments provided
+ * by the DT or PWM lookup table. This is particularly useful to adapt
+ * the bootloader config to the Linux one.
*/
-int pwm_enable(struct pwm_device *pwm)
+int pwm_adjust_config(struct pwm_device *pwm)
{
- int err = 0;
+ struct pwm_state state;
+ struct pwm_args pargs;
- if (!pwm)
- return -EINVAL;
+ pwm_get_args(pwm, &pargs);
+ pwm_get_state(pwm, &state);
- mutex_lock(&pwm->lock);
+ /*
+ * If the current period is zero it means that either the PWM driver
+ * does not support initial state retrieval or the PWM has not yet
+ * been configured.
+ *
+ * In either case, we setup the new period and polarity, and assign a
+ * duty cycle of 0.
+ */
+ if (!state.period) {
+ state.duty_cycle = 0;
+ state.period = pargs.period;
+ state.polarity = pargs.polarity;
- if (!test_and_set_bit(PWMF_ENABLED, &pwm->flags)) {
- err = pwm->chip->ops->enable(pwm->chip, pwm);
- if (err)
- clear_bit(PWMF_ENABLED, &pwm->flags);
+ return pwm_apply_state(pwm, &state);
}
- mutex_unlock(&pwm->lock);
+ /*
+ * Adjust the PWM duty cycle/period based on the period value provided
+ * in PWM args.
+ */
+ if (pargs.period != state.period) {
+ u64 dutycycle = (u64)state.duty_cycle * pargs.period;
- return err;
-}
-EXPORT_SYMBOL_GPL(pwm_enable);
+ do_div(dutycycle, state.period);
+ state.duty_cycle = dutycycle;
+ state.period = pargs.period;
+ }
-/**
- * pwm_disable() - stop a PWM output toggling
- * @pwm: PWM device
- */
-void pwm_disable(struct pwm_device *pwm)
-{
- if (pwm && test_and_clear_bit(PWMF_ENABLED, &pwm->flags))
- pwm->chip->ops->disable(pwm->chip, pwm);
+ /*
+ * If the polarity changed, we should also change the duty cycle.
+ */
+ if (pargs.polarity != state.polarity) {
+ state.polarity = pargs.polarity;
+ state.duty_cycle = state.period - state.duty_cycle;
+ }
+
+ return pwm_apply_state(pwm, &state);
}
-EXPORT_SYMBOL_GPL(pwm_disable);
+EXPORT_SYMBOL_GPL(pwm_adjust_config);
static struct pwm_chip *of_node_to_pwmchip(struct device_node *np)
{
@@ -754,13 +796,13 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
if (!chip)
goto out;
- pwm->args.period = chosen->period;
- pwm->args.polarity = chosen->polarity;
-
pwm = pwm_request_from_chip(chip, chosen->index, con_id ?: dev_id);
if (IS_ERR(pwm))
goto out;
+ pwm->args.period = chosen->period;
+ pwm->args.polarity = chosen->polarity;
+
out:
mutex_unlock(&pwm_lookup_lock);
return pwm;
@@ -907,15 +949,23 @@ static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s)
for (i = 0; i < chip->npwm; i++) {
struct pwm_device *pwm = &chip->pwms[i];
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
seq_printf(s, " pwm-%-3d (%-20.20s):", i, pwm->label);
if (test_bit(PWMF_REQUESTED, &pwm->flags))
seq_puts(s, " requested");
- if (pwm_is_enabled(pwm))
+ if (state.enabled)
seq_puts(s, " enabled");
+ seq_printf(s, " period: %u ns", state.period);
+ seq_printf(s, " duty: %u ns", state.duty_cycle);
+ seq_printf(s, " polarity: %s",
+ state.polarity ? "inverse" : "normal");
+
seq_puts(s, "\n");
}
}
diff --git a/drivers/pwm/pwm-crc.c b/drivers/pwm/pwm-crc.c
index 7101c7020bf4..bd0ebd04856a 100644
--- a/drivers/pwm/pwm-crc.c
+++ b/drivers/pwm/pwm-crc.c
@@ -75,7 +75,7 @@ static int crc_pwm_config(struct pwm_chip *c, struct pwm_device *pwm,
return -EINVAL;
}
- if (pwm->period != period_ns) {
+ if (pwm_get_period(pwm) != period_ns) {
int clk_div;
/* changing the clk divisor, need to disable fisrt */
diff --git a/drivers/pwm/pwm-lpc18xx-sct.c b/drivers/pwm/pwm-lpc18xx-sct.c
index 9861fed4e67d..19dc64cab2f0 100644
--- a/drivers/pwm/pwm-lpc18xx-sct.c
+++ b/drivers/pwm/pwm-lpc18xx-sct.c
@@ -249,7 +249,7 @@ static int lpc18xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
LPC18XX_PWM_EVSTATEMSK(lpc18xx_data->duty_event),
LPC18XX_PWM_EVSTATEMSK_ALL);
- if (pwm->polarity == PWM_POLARITY_NORMAL) {
+ if (pwm_get_polarity(pwm) == PWM_POLARITY_NORMAL) {
set_event = lpc18xx_pwm->period_event;
clear_event = lpc18xx_data->duty_event;
res_action = LPC18XX_PWM_RES_SET;
diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c
index b7e6ecba7d5c..3e95090cd7cf 100644
--- a/drivers/pwm/pwm-omap-dmtimer.c
+++ b/drivers/pwm/pwm-omap-dmtimer.c
@@ -192,7 +192,7 @@ static int pwm_omap_dmtimer_config(struct pwm_chip *chip,
load_value, load_value, match_value, match_value);
omap->pdata->set_pwm(omap->dm_timer,
- pwm->polarity == PWM_POLARITY_INVERSED,
+ pwm_get_polarity(pwm) == PWM_POLARITY_INVERSED,
true,
PWM_OMAP_DMTIMER_TRIGGER_OVERFLOW_AND_COMPARE);
diff --git a/drivers/pwm/pwm-rcar.c b/drivers/pwm/pwm-rcar.c
index 7b8ac0678137..1c85ecc9e7ac 100644
--- a/drivers/pwm/pwm-rcar.c
+++ b/drivers/pwm/pwm-rcar.c
@@ -157,7 +157,7 @@ static int rcar_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
return div;
/* Let the core driver set pwm->period if disabled and duty_ns == 0 */
- if (!test_bit(PWMF_ENABLED, &pwm->flags) && !duty_ns)
+ if (!pwm_is_enabled(pwm) && !duty_ns)
return 0;
rcar_pwm_update(rp, RCAR_PWMCR_SYNC, RCAR_PWMCR_SYNC, RCAR_PWMCR);
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 67af9f62361f..03a99a53c39e 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -354,7 +354,8 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
val = sun4i_pwm_readl(pwm, PWM_CTRL_REG);
for (i = 0; i < pwm->chip.npwm; i++)
if (!(val & BIT_CH(PWM_ACT_STATE, i)))
- pwm->chip.pwms[i].polarity = PWM_POLARITY_INVERSED;
+ pwm_set_polarity(&pwm->chip.pwms[i],
+ PWM_POLARITY_INVERSED);
clk_disable_unprepare(pwm->clk);
return 0;
diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c
index 9c90886f4123..d98599249a05 100644
--- a/drivers/pwm/sysfs.c
+++ b/drivers/pwm/sysfs.c
@@ -26,6 +26,7 @@
struct pwm_export {
struct device child;
struct pwm_device *pwm;
+ struct mutex lock;
};
static struct pwm_export *child_to_pwm_export(struct device *child)
@@ -45,15 +46,20 @@ static ssize_t period_show(struct device *child,
char *buf)
{
const struct pwm_device *pwm = child_to_pwm_device(child);
+ struct pwm_state state;
- return sprintf(buf, "%u\n", pwm_get_period(pwm));
+ pwm_get_state(pwm, &state);
+
+ return sprintf(buf, "%u\n", state.period);
}
static ssize_t period_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
- struct pwm_device *pwm = child_to_pwm_device(child);
+ struct pwm_export *export = child_to_pwm_export(child);
+ struct pwm_device *pwm = export->pwm;
+ struct pwm_state state;
unsigned int val;
int ret;
@@ -61,7 +67,11 @@ static ssize_t period_store(struct device *child,
if (ret)
return ret;
- ret = pwm_config(pwm, pwm_get_duty_cycle(pwm), val);
+ mutex_lock(&export->lock);
+ pwm_get_state(pwm, &state);
+ state.period = val;
+ ret = pwm_apply_state(pwm, &state);
+ mutex_unlock(&export->lock);
return ret ? : size;
}
@@ -71,15 +81,20 @@ static ssize_t duty_cycle_show(struct device *child,
char *buf)
{
const struct pwm_device *pwm = child_to_pwm_device(child);
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
- return sprintf(buf, "%u\n", pwm_get_duty_cycle(pwm));
+ return sprintf(buf, "%u\n", state.duty_cycle);
}
static ssize_t duty_cycle_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
- struct pwm_device *pwm = child_to_pwm_device(child);
+ struct pwm_export *export = child_to_pwm_export(child);
+ struct pwm_device *pwm = export->pwm;
+ struct pwm_state state;
unsigned int val;
int ret;
@@ -87,7 +102,11 @@ static ssize_t duty_cycle_store(struct device *child,
if (ret)
return ret;
- ret = pwm_config(pwm, val, pwm_get_period(pwm));
+ mutex_lock(&export->lock);
+ pwm_get_state(pwm, &state);
+ state.duty_cycle = val;
+ ret = pwm_apply_state(pwm, &state);
+ mutex_unlock(&export->lock);
return ret ? : size;
}
@@ -97,33 +116,46 @@ static ssize_t enable_show(struct device *child,
char *buf)
{
const struct pwm_device *pwm = child_to_pwm_device(child);
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
- return sprintf(buf, "%d\n", pwm_is_enabled(pwm));
+ return sprintf(buf, "%d\n", state.enabled);
}
static ssize_t enable_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
- struct pwm_device *pwm = child_to_pwm_device(child);
+ struct pwm_export *export = child_to_pwm_export(child);
+ struct pwm_device *pwm = export->pwm;
+ struct pwm_state state;
int val, ret;
ret = kstrtoint(buf, 0, &val);
if (ret)
return ret;
+ mutex_lock(&export->lock);
+
+ pwm_get_state(pwm, &state);
+
switch (val) {
case 0:
- pwm_disable(pwm);
+ state.enabled = false;
break;
case 1:
- ret = pwm_enable(pwm);
+ state.enabled = true;
break;
default:
ret = -EINVAL;
- break;
+ goto unlock;
}
+ pwm_apply_state(pwm, &state);
+
+unlock:
+ mutex_unlock(&export->lock);
return ret ? : size;
}
@@ -133,8 +165,11 @@ static ssize_t polarity_show(struct device *child,
{
const struct pwm_device *pwm = child_to_pwm_device(child);
const char *polarity = "unknown";
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
- switch (pwm_get_polarity(pwm)) {
+ switch (state.polarity) {
case PWM_POLARITY_NORMAL:
polarity = "normal";
break;
@@ -151,8 +186,10 @@ static ssize_t polarity_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
- struct pwm_device *pwm = child_to_pwm_device(child);
+ struct pwm_export *export = child_to_pwm_export(child);
+ struct pwm_device *pwm = export->pwm;
enum pwm_polarity polarity;
+ struct pwm_state state;
int ret;
if (sysfs_streq(buf, "normal"))
@@ -162,7 +199,11 @@ static ssize_t polarity_store(struct device *child,
else
return -EINVAL;
- ret = pwm_set_polarity(pwm, polarity);
+ mutex_lock(&export->lock);
+ pwm_get_state(pwm, &state);
+ state.polarity = polarity;
+ ret = pwm_apply_state(pwm, &state);
+ mutex_unlock(&export->lock);
return ret ? : size;
}
@@ -203,6 +244,7 @@ static int pwm_export_child(struct device *parent, struct pwm_device *pwm)
}
export->pwm = pwm;
+ mutex_init(&export->lock);
export->child.release = pwm_export_release;
export->child.parent = parent;