linux-omap.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Thierry Reding <thierry.reding@avionic-design.de>
To: Peter Ujfalusi <peter.ujfalusi@ti.com>
Cc: Tero Kristo <t-kristo@ti.com>,
	Grazvydas Ignotas <notasas@gmail.com>,
	linux-kernel@vger.kernel.org, linux-omap@vger.kernel.org,
	Linus Walleij <linus.walleij@linaro.org>
Subject: Re: [PATCH v3 2/3] pwm: New driver to support PWMs on TWL4030/6030 series of PMICs
Date: Fri, 23 Nov 2012 15:58:45 +0100	[thread overview]
Message-ID: <20121123145844.GA16810@avionic-0098.adnet.avionic-design.de> (raw)
In-Reply-To: <1353405382-9226-3-git-send-email-peter.ujfalusi@ti.com>

[-- Attachment #1: Type: text/plain, Size: 8404 bytes --]

On Tue, Nov 20, 2012 at 10:56:21AM +0100, Peter Ujfalusi wrote:
> The driver supports the following PWM outputs:
> TWL4030 PWM0 and PWM1
> TWL6030 PWM1 and PWM2
> 
> On TWL4030 the PWM signals are muxed. Upon requesting the PWM the driver
> will select the correct mux so the PWM can be used. When the PWM has been
> freed the original configuration going to be restored.

"configuration is going to be"

> +config PWM_TWL
> +	tristate "TWL4030/6030 PWM support"
> +	select HAVE_PWM

Why do you select HAVE_PWM?

> +static int twl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
> +			      int duty_ns, int period_ns)
> +{
> +	int duty_cycle = DIV_ROUND_UP(duty_ns * TWL_PWM_MAX, period_ns);
> +	u8 pwm_config[2] = {1, 0};
> +	int base, ret;
> +
> +	/*
> +	 * To configure the duty period:
> +	 * On-cycle is set to 1 (the minimum allowed value)
> +	 * The off time of 0 is not configurable, so the mapping is:
> +	 * 0 -> off cycle = 2,
> +	 * 1 -> off cycle = 2,
> +	 * 2 -> off cycle = 3,
> +	 * 126 - > off cycle 127,
> +	 * 127 - > off cycle 1
> +	 * When on cycle == off cycle the PWM will be always on
> +	 */
> +	duty_cycle += 1;

The canonical form to write this would be "duty_cycle++", but maybe it
would even be better to add it to the expression that defines
duty_cycle?

> +static int twl4030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	int ret;
> +	u8 val;
> +
> +	ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label);
> +		return ret;
> +	}
> +
> +	val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE);
> +
> +	ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
> +	if (ret < 0)
> +		dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
> +
> +	val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE);
> +
> +	ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
> +	if (ret < 0)
> +		dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
> +
> +	return ret;
> +}

Does this perhaps need some locking to make sure the read-modify-write
sequence isn't interrupted?

> +static void twl4030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	int ret;
> +	u8 val;
> +
> +	ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label);
> +		return;
> +	}
> +
> +	val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE);
> +
> +	ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
> +	if (ret < 0)
> +		dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
> +
> +	val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE);
> +
> +	ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
> +	if (ret < 0)
> +		dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
> +}

Same here.

> +static int twl4030_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
> +						chip);

This could use a macro like to_twl(chip).

> +	int ret;
> +	u8 val, mask, bits;
> +
> +	ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label);
> +		return ret;
> +	}
> +
> +	if (pwm->hwpwm) {
> +		/* PWM 1 */
> +		mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
> +		bits = TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1;
> +	} else {
> +		/* PWM 0 */
> +		mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
> +		bits = TWL4030_GPIO6_PWM0_MUTE_PWM0;
> +	}
> +
> +	/* Save the current MUX configuration for the PWM */
> +	twl->twl4030_pwm_mux &= ~mask;
> +	twl->twl4030_pwm_mux |= (val & mask);
> +
> +	/* Select PWM functionality */
> +	val &= ~mask;
> +	val |= bits;
> +
> +	ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG);
> +	if (ret < 0)
> +		dev_err(chip->dev, "%s: Failed to request PWM\n", pwm->label);
> +
> +	return ret;
> +}

Again, more read-modify-write without locking.

> +
> +static void twl4030_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
> +						chip);
> +	int ret;
> +	u8 val, mask;
> +
> +	ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label);
> +		return;
> +	}
> +
> +	if (pwm->hwpwm)
> +		/* PWM 1 */
> +		mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
> +	else
> +		/* PWM 0 */
> +		mask = TWL4030_GPIO6_PWM0_MUTE_MASK;

I'm not sure how useful these comments are. You have both an explicit
check for pwm->hwpwm to make it obvious that it isn't 0 and the mask
macros contain the PWM0 and PWM1 substrings respectively.

You could make it even more explicit perhaps by turning the check into:

	if (pwm->hwpwm == 1)

But either way I think you can drop the /* PWM 1 */ and /* PWM 0 */
comments.

> +
> +	/* Restore the MUX configuration for the PWM */
> +	val &= ~mask;
> +	val |= (twl->twl4030_pwm_mux & mask);
> +
> +	ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG);
> +	if (ret < 0)
> +		dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label);
> +}
> +
> +static int twl6030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
> +						chip);
> +	int ret;
> +	u8 val;
> +
> +	val = twl->twl6030_toggle3;
> +	val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
> +	val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR);
> +
> +	ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
> +		return ret;
> +	}
> +
> +	twl->twl6030_toggle3 = val;
> +
> +	return 0;
> +}
> +
> +static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
> +						chip);
> +	int ret;
> +	u8 val;
> +
> +	val = twl->twl6030_toggle3;
> +	val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR);
> +	val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
> +
> +	ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "%s: Failed to read TOGGLE3\n", pwm->label);
> +		return;
> +	}
> +
> +	val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
> +
> +	ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
> +		return;
> +	}
> +
> +	twl->twl6030_toggle3 = val;
> +}
> +
> +static struct pwm_ops twl_pwm_ops = {
> +	.config = twl_pwm_config,
> +};

It might actually be worth to split this into two pwm_ops structures,
one for 4030 and one for 6030. In that case you would still be able to
make them const, which probably outweighs the space savings here.

Actually, I think this is even potentially buggy since you may have more
than one instance of this driver. For instance if you have a TWL6030 and
a TWL4030 in a single system this will break horribly since you'll over-
write the pwm_ops of one of them.

> +	if (twl_class_is_4030()) {
> +		twl_pwm_ops.enable = twl4030_pwm_enable;
> +		twl_pwm_ops.disable = twl4030_pwm_disable;
> +		twl_pwm_ops.request = twl4030_pwm_request;
> +		twl_pwm_ops.free = twl4030_pwm_free;

This would become:

		twl->chip.ops = &twl4030_pwm_ops;

> +	} else {
> +		twl_pwm_ops.enable = twl6030_pwm_enable;
> +		twl_pwm_ops.disable = twl6030_pwm_disable;

and:
		twl->chip.ops = &twl6030_pwm_ops;

> +static struct of_device_id twl_pwm_of_match[] = {
> +	{ .compatible = "ti,twl4030-pwm" },
> +	{ .compatible = "ti,twl6030-pwm" },
> +};
> +
> +MODULE_DEVICE_TABLE(of, twl_pwm_of_match);

Nit: I think the blank line between "};" and "MODULE_DEVICE_TABLE(...)"
can go away. And you might want to protect this with an "#ifdef
CONFIG_OF" since you don't depend on OF.

Thierry

[-- Attachment #2: Type: application/pgp-signature, Size: 836 bytes --]

  reply	other threads:[~2012-11-23 14:58 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-11-20  9:56 [PATCH v3 0/3] pwm: Drivers for twl4030/6030 PWMs and LEDs Peter Ujfalusi
2012-11-20  9:56 ` [PATCH v3 1/3] pwm: Remove pwm-twl6030 driver Peter Ujfalusi
2012-11-23 15:05   ` Thierry Reding
2012-11-26  9:11     ` Peter Ujfalusi
2012-11-20  9:56 ` [PATCH v3 2/3] pwm: New driver to support PWMs on TWL4030/6030 series of PMICs Peter Ujfalusi
2012-11-23 14:58   ` Thierry Reding [this message]
2012-11-26  9:31     ` Peter Ujfalusi
2012-11-20  9:56 ` [PATCH v3 3/3] pwm: New driver to support PWM driven LEDs " Peter Ujfalusi
2012-11-20 11:54   ` Peter Ujfalusi
2012-11-20 11:58     ` Thierry Reding
2012-11-23  9:51       ` Peter Ujfalusi
2012-11-23 15:04   ` Thierry Reding
2012-11-26  8:30     ` Peter Ujfalusi
2012-11-26  9:17       ` Thierry Reding
2012-11-26  9:33         ` Peter Ujfalusi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20121123145844.GA16810@avionic-0098.adnet.avionic-design.de \
    --to=thierry.reding@avionic-design.de \
    --cc=linus.walleij@linaro.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-omap@vger.kernel.org \
    --cc=notasas@gmail.com \
    --cc=peter.ujfalusi@ti.com \
    --cc=t-kristo@ti.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).