All of lore.kernel.org
 help / color / mirror / Atom feed
From: NeilBrown <neilb@suse.de>
To: Andrew Morton <akpm@linux-foundation.org>
Cc: Randy Dunlap <rdunlap@xenotime.net>,
	rpurdie@rpsys.net, linux-kernel@vger.kernel.org
Subject: Re: [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available.
Date: Fri, 30 Dec 2011 11:35:58 +1100	[thread overview]
Message-ID: <20111230113558.4e04ea5d@notabene.brown> (raw)
In-Reply-To: <20111220142331.e81f7dfc.akpm@linux-foundation.org>

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

On Tue, 20 Dec 2011 14:23:31 -0800 Andrew Morton <akpm@linux-foundation.org>
wrote:

> On Wed, 21 Dec 2011 08:03:00 +1100
> NeilBrown <neilb@suse.de> wrote:
> 
> > I might just go an re-review all the code.  And repeat the tests.
> 
> Here's the current patch as I see it:

Thanks.

Here is a patch against that which is the result of a review and more testing
*after* making the changes :-)

NeilBrown

From a2081eb20047e9254137e37931e3b47193d8e363 Mon Sep 17 00:00:00 2001
From: NeilBrown <neilb@suse.de>
Date: Thu, 29 Dec 2011 17:37:20 +1100
Subject: [PATCH] LEDS tca6507 fixes

- various improvements to comments.
- bug in choose_times() which was counting down instead of up
- Change choice algorithm to allow change-time to be longer
  if requested 'msec' is odd (i.e. use lsb of 'msec' as a flag).
- bug in set_code wasn't shifting mask properly.
- dev_dbg messages so we can monitor the choice funciton.
- make spinlock irq-safe so that gpio.set() and others can be
  called from interrupt handler.
- set client_data before ->setup call back as it could set the
  GPIO value immediately.

Signed-off-by: NeilBrown <neilb@suse.de>

diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c
index b693de4..133f89f 100644
--- a/drivers/leds/leds-tca6507.c
+++ b/drivers/leds/leds-tca6507.c
@@ -24,29 +24,41 @@
  * This driver does not support double-blink so 'second-off' always matches
  * 'off'.
  *
- * Only 16 different times can be programmed is a roughly logarithmic scale from
- * 64ms to 16320ms.   Times that cannot be closely matched with these must be
+ * Only 16 different times can be programmed in a roughly logarithmic scale from
+ * 64ms to 16320ms.  To be precise the possible times are:
+ *    0, 64, 128, 192, 256, 384, 512, 768,
+ *    1024, 1536, 2048, 3072, 4096, 5760, 8128, 16320
+ *
+ * Times that cannot be closely matched with these must be
  * handled in software.  This driver allows 12.5% error in matching.
  *
  * This driver does not allow rise/fall rates to be set explicitly.  When trying
  * to match a given 'on' or 'off' period, an appropriate pair of 'change' and
- * 'hold' times are chosen to get a close match, with the 'change' being the
- * smaller.
+ * 'hold' times are chosen to get a close match.  If the target delay is even,
+ * the 'change' number will be the smaller; if odd, the 'hold' number will be
+ * the smaller.
+
+ * Choosing pairs of delays with 12.5% errors allows us to match delays in the
+ * ranges: 56-72, 112-144, 168-216, 224-27504, 28560-36720.
+ * 26% of the achievable sums can be matched by multiple pairings. For example
+ * 1536 == 1536+0, 1024+512, or 768+768.  This driver will always choose the
+ * pairing with the least maximum - 768+768 in this case.  Other pairings are
+ * not available.
  *
  * Access to the 3 levels and 2 blinks are on a first-come, first-served basis.
  * Access can be shared by multiple leds if they have the same level and
  * either same blink rates, or some don't blink.
  * When a led changes, it relinquishes access and tries again, so it might
  * lose access to hardware blink.
- * If a blink engine cannot be allocated, software blink is used.  If the
- * desired brightness cannot be allocated, the closest available non-zero
+ * If a blink engine cannot be allocated, software blink is used.
+ * If the desired brightness cannot be allocated, the closest available non-zero
  * brightness is used.  As 'full' is always available, the worst case would be
  * to have two different blink rates at '1', with Max at '2', then other leds
  * will have to choose between '2' and '16'.  Hopefully this is not likely.
  *
- * Each bank (BANK0 and BANK1) have two usage counts - Leds using the brightness
- * and leds using the blink.  It can only be reprogrammed when appropriate
- * counter is zero.  The MASTER level has as single usage count.
+ * Each bank (BANK0 and BANK1) has two usage counts - LEDs using the brightness
+ * and LEDs using the blink.  It can only be reprogrammed when the appropriate
+ * counter is zero.  The MASTER level has a single usage count.
  *
  * Each Led has programmable 'on' and 'off' time as milliseconds.  With each
  * there is a flag saying if it was explicitly requested or defaulted.
@@ -58,8 +70,10 @@
  * lists for each output: the name, default trigger, and whether the signal
  * is being used as a GPiO rather than an led.  'struct led_plaform_data'
  * is used for this.  If 'name' is NULL, the output isn't used.  If 'flags'
- * is non-zero, the output is a GPO.  The 'flags' for the first GPIO should
- * be the base gpio number, or -1.
+ * is TCA6507_MAKE_CPIO, the output is a GPO.
+ * The "struct led_platform_data" can be embedded in a
+ * "struct tca6507_platform_data" which adds a 'gpio_base' for the GPiOs,
+ * and a 'setup' callback which is called once the GPiOs are available.
  *
  */
 
@@ -74,6 +88,7 @@
 
 /* LED select registers determine the source that drives LED outputs */
 #define TCA6507_LS_LED_OFF	0x0	/* Output HI-Z (off) */
+#define TCA6507_LS_LED_OFF1	0x1	/* Output HI-Z (off) - not used */
 #define TCA6507_LS_LED_PWM0	0x2	/* Output LOW with Bank0 rate */
 #define TCA6507_LS_LED_PWM1	0x3	/* Output LOW with Bank1 rate */
 #define TCA6507_LS_LED_ON	0x4	/* Output LOW (on) */
@@ -91,7 +106,7 @@ static int bank_source[3] = {
 	TCA6507_LS_LED_PWM1,
 	TCA6507_LS_LED_MIR,
 };
-static int blink_source[3] = {
+static int blink_source[2] = {
 	TCA6507_LS_BLINK0,
 	TCA6507_LS_BLINK1,
 };
@@ -99,6 +114,10 @@ static int blink_source[3] = {
 /* PWM registers */
 #define	TCA6507_REG_CNT			11
 
+/*
+ * 0x00, 0x01, 0x02 encode the TCA6507_LS_* values, each output
+ * owns one bit in each register
+ */
 #define	TCA6507_FADE_ON			0x03
 #define	TCA6507_FULL_ON			0x04
 #define	TCA6507_FADE_OFF		0x05
@@ -132,10 +151,11 @@ static inline int TO_BRIGHT(int level)
 
 #define NUM_LEDS 7
 struct tca6507_chip {
-	int			reg_set;	/* a '1' means the register
+	int			reg_set;	/* One bit per register where
+						 * a '1' means the register
 						 * should be written */
 	u8			reg_file[TCA6507_REG_CNT];
-	/* Bank 0 is Master Intensity */
+	/* Bank 2 is Master Intensity and doesn't use times */
 	struct bank {
 		int level;
 		int ontime, offtime;
@@ -153,7 +173,7 @@ struct tca6507_chip {
 		int			ontime, offtime;
 		int			on_dflt, off_dflt;
 		int			bank;	/* Bank used, or -1 */
-		int			blink;	/* 1 if we are hardware-blinking */
+		int			blink;	/* Set if hardware-blinking */
 	} leds[NUM_LEDS];
 #ifdef CONFIG_GPIOLIB
 	struct gpio_chip		gpio;
@@ -172,9 +192,13 @@ static int choose_times(int msec, int *c1p, int *c2p)
 {
 	/*
 	 * Choose two timecodes which add to 'msec' as near as possible.
-	 * The first returned should be the larger and is the 'on' of 'off'
-	 * time.  The second will be used as a 'fade-on' or 'fade-off' time.
-	 * If cannot get within 1/8, fail.
+	 * The first returned is the 'on' or 'off' time.  The second is to be
+	 * used as a 'fade-on' or 'fade-off' time.  If 'msec' is even,
+	 * the first will not be smaller than the second.  If 'msec' is odd,
+	 * the first will not be larger than the second.
+	 * If we cannot get a sum within 1/8 of 'msec' fail with -EINVAL,
+	 * otherwise return the sum that was achieved, plus 1 if the first is
+	 * smaller.
 	 * If two possibilities are equally good (e.g. 512+0, 256+256), choose
 	 * the first pair so there is more change-time visible (i.e. it is
 	 * softer).
@@ -193,7 +217,7 @@ static int choose_times(int msec, int *c1p, int *c2p)
 			continue;
 		if (t > tmax)
 			break;
-		for (c2 = 0; c2 <= c1; c2--) {
+		for (c2 = 0; c2 <= c1; c2++) {
 			int tt = t + time_codes[c2];
 			int d;
 			if (tt < tmin)
@@ -209,11 +233,22 @@ static int choose_times(int msec, int *c1p, int *c2p)
 			*c2p = c2;
 			diff = d;
 			if (d == 0)
-				return 0;
+				return msec;
 		}
 	}
-	if (diff < 65536)
-		return 0;
+	if (diff < 65536) {
+		int actual;
+		if (msec & 1) {
+			c1 = *c2p;
+			*c2p = *c1p;
+			*c1p = c1;
+		}
+		actual = time_codes[*c1p] + time_codes[*c2p];
+		if (*c1p < *c2p)
+			return actual + 1;
+		else
+			return actual;
+	}
 	/* No close match */
 	return -EINVAL;
 }
@@ -244,10 +279,13 @@ static void set_select(struct tca6507_chip *tca, int led, int val)
  */
 static void set_code(struct tca6507_chip *tca, int reg, int bank, int new)
 {
-	int mask = (0xF << bank);
-	int n = tca->reg_file[reg] & ~mask;
-	if (bank)
+	int mask = 0xF;
+	int n;
+	if (bank) {
+		mask <<= 4;
 		new <<= 4;
+	}
+	n = tca->reg_file[reg] & ~mask;
 	n |= new;
 	if (tca->reg_file[reg] != n) {
 		tca->reg_file[reg] = n;
@@ -274,17 +312,24 @@ static void set_level(struct tca6507_chip *tca, int bank, int level)
 static void set_times(struct tca6507_chip *tca, int bank)
 {
 	int c1, c2;
+	int result;
 
-	choose_times(tca->bank[bank].ontime, &c1, &c2);
+	result = choose_times(tca->bank[bank].ontime, &c1, &c2);
+	dev_dbg(&tca->client->dev,
+		"Chose on  times %d(%d) %d(%d) for %dms\n", c1, time_codes[c1],
+		c2, time_codes[c2], tca->bank[bank].ontime);
 	set_code(tca, TCA6507_FADE_ON, bank, c2);
 	set_code(tca, TCA6507_FULL_ON, bank, c1);
-	tca->bank[bank].ontime = time_codes[c1] + time_codes[c2];
+	tca->bank[bank].ontime = result;
 
-	choose_times(tca->bank[bank].offtime, &c1, &c2);
+	result = choose_times(tca->bank[bank].offtime, &c1, &c2);
+	dev_dbg(&tca->client->dev,
+		"Chose off times %d(%d) %d(%d) for %dms\n", c1, time_codes[c1],
+		c2, time_codes[c2], tca->bank[bank].offtime);
 	set_code(tca, TCA6507_FADE_OFF, bank, c2);
 	set_code(tca, TCA6507_FIRST_OFF, bank, c1);
 	set_code(tca, TCA6507_SECOND_OFF, bank, c1);
-	tca->bank[bank].offtime = time_codes[c1] + time_codes[c2];
+	tca->bank[bank].offtime = result;
 
 	set_code(tca, TCA6507_INITIALIZE, bank, INIT_CODE);
 }
@@ -300,11 +345,11 @@ static void tca6507_work(struct work_struct *work)
 	u8 file[TCA6507_REG_CNT];
 	int r;
 
-	spin_lock(&tca->lock);
+	spin_lock_irq(&tca->lock);
 	set = tca->reg_set;
 	memcpy(file, tca->reg_file, TCA6507_REG_CNT);
 	tca->reg_set = 0;
-	spin_unlock(&tca->lock);
+	spin_unlock_irq(&tca->lock);
 
 	for (r = 0; r < TCA6507_REG_CNT; r++)
 		if (set & (1<<r))
@@ -327,7 +372,7 @@ static void led_release(struct tca6507_led *led)
 
 static int led_prepare(struct tca6507_led *led)
 {
-	/* Assign this led to a bank. configuring that bank if necessary */
+	/* Assign this led to a bank, configuring that bank if necessary. */
 	int level = TO_LEVEL(led->led_cdev.brightness);
 	struct tca6507_chip *tca = led->chip;
 	int c1, c2;
@@ -386,7 +431,11 @@ static int led_prepare(struct tca6507_led *led)
 		return 0;
 	}
 
-	/* We have on/off time so we need to try to allocate a timing bank. */
+	/*
+	 * We have on/off time so we need to try to allocate a timing bank.
+	 * First check if times are compatible with hardware and give up if
+	 * not.
+	 */
 	if (choose_times(led->ontime, &c1, &c2) < 0)
 		return -EINVAL;
 	if (choose_times(led->offtime, &c1, &c2) < 0)
@@ -466,8 +515,9 @@ static int led_assign(struct tca6507_led *led)
 {
 	struct tca6507_chip *tca = led->chip;
 	int err;
+	unsigned long flags;
 
-	spin_lock(&tca->lock);
+	spin_lock_irqsave(&tca->lock, flags);
 	led_release(led);
 	err = led_prepare(led);
 	if (err) {
@@ -479,7 +529,7 @@ static int led_assign(struct tca6507_led *led)
 		led->offtime = 0;
 		led_prepare(led);
 	}
-	spin_unlock(&tca->lock);
+	spin_unlock_irqrestore(&tca->lock, flags);
 
 	if (tca->reg_set)
 		schedule_work(&tca->work);
@@ -539,15 +589,16 @@ static void tca6507_gpio_set_value(struct gpio_chip *gc,
 				   unsigned offset, int val)
 {
 	struct tca6507_chip *tca = container_of(gc, struct tca6507_chip, gpio);
+	unsigned long flags;
 
-	spin_lock(&tca->lock);
+	spin_lock_irqsave(&tca->lock, flags);
 	/*
 	 * 'OFF' is floating high, and 'ON' is pulled down, so it has the
 	 * inverse sense of 'val'.
 	 */
 	set_select(tca, tca->gpio_map[offset],
 		   val ? TCA6507_LS_LED_OFF : TCA6507_LS_LED_ON);
-	spin_unlock(&tca->lock);
+	spin_unlock_irqrestore(&tca->lock, flags);
 	if (tca->reg_set)
 		schedule_work(&tca->work);
 }
@@ -558,6 +609,7 @@ static int tca6507_gpio_direction_output(struct gpio_chip *gc,
 	tca6507_gpio_set_value(gc, offset, val);
 	return 0;
 }
+
 static int tca6507_probe_gpios(struct i2c_client *client,
 			       struct tca6507_chip *tca,
 			       struct tca6507_platform_data *pdata)
@@ -643,6 +695,7 @@ static int __devinit tca6507_probe(struct i2c_client *client,
 	tca->client = client;
 	INIT_WORK(&tca->work, tca6507_work);
 	spin_lock_init(&tca->lock);
+	i2c_set_clientdata(client, tca);
 
 	for (i = 0; i < NUM_LEDS; i++) {
 		struct tca6507_led *l = tca->leds + i;
@@ -665,7 +718,6 @@ static int __devinit tca6507_probe(struct i2c_client *client,
 	err = tca6507_probe_gpios(client, tca, pdata);
 	if (err)
 		goto exit;
-	i2c_set_clientdata(client, tca);
 	/* set all registers to known state - zero */
 	tca->reg_set = 0x7f;
 	schedule_work(&tca->work);
@@ -675,6 +727,8 @@ exit:
 	while (i--)
 		if (tca->leds[i].led_cdev.name)
 			led_classdev_unregister(&tca->leds[i].led_cdev);
+	cancel_work_sync(&tca->work);
+	i2c_set_clientdata(client, NULL);
 	kfree(tca);
 	return err;
 }
@@ -691,8 +745,8 @@ static int __devexit tca6507_remove(struct i2c_client *client)
 	}
 	tca6507_remove_gpio(tca);
 	cancel_work_sync(&tca->work);
-	kfree(tca);
 	i2c_set_clientdata(client, NULL);
+	kfree(tca);
 
 	return 0;
 }

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 828 bytes --]

  reply	other threads:[~2011-12-30  0:36 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-12-20  5:44 [PATCH 0/2] Two fixes for leds-tca6507 - Version 2 NeilBrown
2011-12-20  5:44 ` [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available NeilBrown
2011-12-20 20:16   ` Randy Dunlap
2011-12-20 20:26     ` Randy Dunlap
2011-12-20 21:03       ` NeilBrown
2011-12-20 22:23         ` Andrew Morton
2011-12-30  0:35           ` NeilBrown [this message]
2011-12-20  5:44 ` [PATCH 2/2] leds-tca6507 - fix off by one error NeilBrown
  -- strict thread matches above, loose matches on Subject: below --
2011-12-19  0:20 [PATCH 0/2] Two fixes for leds-tca6507 NeilBrown
2011-12-19  0:20 ` [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available NeilBrown
2011-12-19 20:34   ` Randy Dunlap
2011-12-20  5:37     ` NeilBrown

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=20111230113558.4e04ea5d@notabene.brown \
    --to=neilb@suse.de \
    --cc=akpm@linux-foundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=rdunlap@xenotime.net \
    --cc=rpurdie@rpsys.net \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.