linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] Two fixes for leds-tca6507
@ 2011-12-19  0:20 NeilBrown
  2011-12-19  0:20 ` [PATCH 2/2] leds-tca6507 - fix off by one error NeilBrown
  2011-12-19  0:20 ` [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available NeilBrown
  0 siblings, 2 replies; 11+ messages in thread
From: NeilBrown @ 2011-12-19  0:20 UTC (permalink / raw)
  To: Andrew Morton
  Cc: rpurdie, linux-kernel, Randy Dunlap, Stephen Rothwell,
	Dan Carpenter

The driver didn't compile on arches which did not define GPIOLIB.
  This this by excluding gpio support on those archs.
  Reported by: Randy Dunlap <rdunlap@xenotime.net> and
     Stephen Rothwell <sfr@canb.auug.org.au>

off-by-one error in choose_times
  Reported by: Dan Carpenter <dan.carpenter@oracle.com>

Thanks,
NeilBrown
  

---

NeilBrown (2):
      leds-tca6507 - fix off by one error.
      leds-tca6507: allow driver to compile when GPIOLIB is not available.


 drivers/leds/leds-tca6507.c  |   83 +++++++++++++++++++++++++++++-------------
 include/linux/leds-tca6507.h |    3 +-
 2 files changed, 59 insertions(+), 27 deletions(-)

-- 
Signature


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available.
  2011-12-19  0:20 [PATCH 0/2] Two fixes for leds-tca6507 NeilBrown
  2011-12-19  0:20 ` [PATCH 2/2] leds-tca6507 - fix off by one error NeilBrown
@ 2011-12-19  0:20 ` NeilBrown
  2011-12-19 20:34   ` Randy Dunlap
  1 sibling, 1 reply; 11+ messages in thread
From: NeilBrown @ 2011-12-19  0:20 UTC (permalink / raw)
  To: Andrew Morton; +Cc: rpurdie, linux-kernel, Randy Dunlap, Stephen Rothwell

This driver can configure the outputs as GPIO line instead of LEDs.  But
that only works if GPIOLIB is available.  So make that code conditional
on the library's availability.

Also remove the 'teardown' callback as it is never called and should
never be needed.

Reported-by: Randy Dunlap <rdunlap@xenotime.net>,
Reported-by: Stephen Rothwell <sfr@canb.auug.org.au>,
Signed-off-by: NeilBrown <neilb@suse.de>
---

 drivers/leds/leds-tca6507.c  |   75 +++++++++++++++++++++++++++++-------------
 include/linux/leds-tca6507.h |    3 +-
 2 files changed, 53 insertions(+), 25 deletions(-)

diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c
index a7ea4cf..c9cc3f7 100644
--- a/drivers/leds/leds-tca6507.c
+++ b/drivers/leds/leds-tca6507.c
@@ -149,9 +149,11 @@ struct tca6507_chip {
 		int			bank;	/* Bank used, or -1 */
 		int			blink;	/* 1 if we are hardware-blinking */
 	} leds[NUM_LEDS];
+#ifdef CONFIG_GPIOLIB
 	struct gpio_chip		gpio;
 	const char			*gpio_name[NUM_LEDS];
 	int				gpio_map[NUM_LEDS];
+#endif
 };
 
 static const struct i2c_device_id tca6507_id[] = {
@@ -520,6 +522,7 @@ static int tca6507_blink_set(struct led_classdev *led_cdev,
 	return 0;
 }
 
+#ifdef CONFIG_GPIOLIB
 static void tca6507_gpio_set_value(struct gpio_chip *gc,
 				   unsigned offset, int val)
 {
@@ -542,6 +545,51 @@ 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)
+{
+	int err;
+	int i = 0;
+	int gpios = 0;
+
+	for (i = 0; i < NUM_LEDS; i++)
+		if (pdata->leds.leds[i].name && pdata->leds.leds[i].flags) {
+			/* Configure as a gpio */
+			tca->gpio_name[gpios] = pdata->leds.leds[i].name;
+			tca->gpio_map[gpios] = i;
+			gpios++;
+		}
+
+	if (!gpios)
+		return 0;
+
+	tca->gpio.label = "gpio-tca6507";
+	tca->gpio.names = tca->gpio_name;
+	tca->gpio.ngpio = gpios;
+	tca->gpio.base = pdata->gpio_base;
+	tca->gpio.owner = THIS_MODULE;
+	tca->gpio.direction_output = tca6507_gpio_direction_output;
+	tca->gpio.set = tca6507_gpio_set_value;
+	tca->gpio.dev = &client->dev;
+	err = gpiochip_add(&tca->gpio);
+	if (err) {
+		tca->gpio.ngpio = 0;
+		return err;
+	}
+	if (pdata->setup)
+		pdata->setup(tca->gpio.base, tca->gpio.ngpio);
+	return 0;
+}
+
+#else /* CONFIG_GPIOLIB */
+static int tca6507_probe_gpios(struct i2c_client *client,
+			       struct tca6507_chip *tca,
+			       struct tca6507_platform_data *pdata)
+{
+	return 0;
+}
+#endif /* CONFIG_GPIOLIB */
 
 static int __devinit tca6507_probe(struct i2c_client *client,
 				   const struct i2c_device_id *id)
@@ -551,7 +599,6 @@ static int __devinit tca6507_probe(struct i2c_client *client,
 	struct tca6507_platform_data *pdata;
 	int err;
 	int i = 0;
-	int gpios = 0;
 
 	adapter = to_i2c_adapter(client->dev.parent);
 	pdata = client->dev.platform_data;
@@ -590,30 +637,10 @@ static int __devinit tca6507_probe(struct i2c_client *client,
 			if (err < 0)
 				goto exit;
 		}
-		if (pdata->leds.leds[i].name && pdata->leds.leds[i].flags) {
-			/* Configure as a gpio */
-			tca->gpio_name[gpios] = pdata->leds.leds[i].name;
-			tca->gpio_map[gpios] = i;
-			gpios++;
-		}
-	}
-	if (gpios) {
-		tca->gpio.label = "gpio-tca6507";
-		tca->gpio.names = tca->gpio_name;
-		tca->gpio.ngpio = gpios;
-		tca->gpio.base = pdata->gpio_base;
-		tca->gpio.owner = THIS_MODULE;
-		tca->gpio.direction_output = tca6507_gpio_direction_output;
-		tca->gpio.set = tca6507_gpio_set_value;
-		tca->gpio.dev = &client->dev;
-		err = gpiochip_add(&tca->gpio);
-		if (err) {
-			tca->gpio.ngpio = 0;
-			goto exit;
-		}
-		if (pdata->setup)
-			pdata->setup(tca->gpio.base, tca->gpio.ngpio);
 	}
+	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;
diff --git a/include/linux/leds-tca6507.h b/include/linux/leds-tca6507.h
index 3b8ac62..dcabf4f 100644
--- a/include/linux/leds-tca6507.h
+++ b/include/linux/leds-tca6507.h
@@ -24,9 +24,10 @@
 
 struct tca6507_platform_data {
 	struct led_platform_data leds;
+#ifdef CONFIG_GPIOLIB
 	int gpio_base;
 	void (*setup)(unsigned gpio_base, unsigned ngpio);
-	void (*teardown)(unsigned gpio_base, unsigned ngpio);
+#endif
 };
 
 #define	TCA6507_MAKE_GPIO 1



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 2/2] leds-tca6507 - fix off by one error.
  2011-12-19  0:20 [PATCH 0/2] Two fixes for leds-tca6507 NeilBrown
@ 2011-12-19  0:20 ` NeilBrown
  2011-12-19  0:20 ` [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available NeilBrown
  1 sibling, 0 replies; 11+ messages in thread
From: NeilBrown @ 2011-12-19  0:20 UTC (permalink / raw)
  To: Andrew Morton; +Cc: rpurdie, linux-kernel, Dan Carpenter

When walking the list of possible time codes we can fall off the end.
Fix that, and also improved the commentary.

Reported-by: Dan Carpenter <dan.carpenter@oracle.com>
Signed-off-by: NeilBrown <neilb@suse.de>
---

 drivers/leds/leds-tca6507.c |    8 ++++++--
 1 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c
index c9cc3f7..75dc5e4 100644
--- a/drivers/leds/leds-tca6507.c
+++ b/drivers/leds/leds-tca6507.c
@@ -165,7 +165,8 @@ MODULE_DEVICE_TABLE(i2c, tca6507);
 static int choose_times(int msec, int *c1p, int *c2p)
 {
 	/* Chose two timecodes which add to 'msec' as near as possible.
-	 * The first returned should be the larger.
+	 * 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.
 	 * 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).
@@ -175,7 +176,10 @@ static int choose_times(int msec, int *c1p, int *c2p)
 	int tmin = msec * 7 / 8;
 	int diff = 65536;
 
-	for (c1 = 1; c1 <= TIMECODES; c1++) {
+	/* We start at '1' to ensure we never even think of choosing a
+	 * total time of '0'.
+	 */
+	for (c1 = 1; c1 < TIMECODES; c1++) {
 		int t = time_codes[c1];
 		if (t*2 < tmin)
 			continue;



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available.
  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
  0 siblings, 1 reply; 11+ messages in thread
From: Randy Dunlap @ 2011-12-19 20:34 UTC (permalink / raw)
  To: NeilBrown; +Cc: Andrew Morton, rpurdie, linux-kernel, Stephen Rothwell

On 12/18/2011 04:20 PM, NeilBrown wrote:
> This driver can configure the outputs as GPIO line instead of LEDs.  But
> that only works if GPIOLIB is available.  So make that code conditional
> on the library's availability.
> 
> Also remove the 'teardown' callback as it is never called and should
> never be needed.
> 
> Reported-by: Randy Dunlap <rdunlap@xenotime.net>,
> Reported-by: Stephen Rothwell <sfr@canb.auug.org.au>,
> Signed-off-by: NeilBrown <neilb@suse.de>

Hi Neil,

with linux-next 20111219 and this patch applied, I now get:

drivers/leds/leds-tca6507.c: In function 'tca6507_remove':
drivers/leds/leds-tca6507.c:677:9: error: 'struct tca6507_chip' has no member named 'gpio'
drivers/leds/leds-tca6507.c:678:3: error: implicit declaration of function 'gpiochip_remove'
drivers/leds/leds-tca6507.c:678:33: error: 'struct tca6507_chip' has no member named 'gpio'

when GPIOLIB is not enabled.


> ---
> 
>  drivers/leds/leds-tca6507.c  |   75 +++++++++++++++++++++++++++++-------------
>  include/linux/leds-tca6507.h |    3 +-
>  2 files changed, 53 insertions(+), 25 deletions(-)
> 
> diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c
> index a7ea4cf..c9cc3f7 100644
> --- a/drivers/leds/leds-tca6507.c
> +++ b/drivers/leds/leds-tca6507.c
> @@ -149,9 +149,11 @@ struct tca6507_chip {
>  		int			bank;	/* Bank used, or -1 */
>  		int			blink;	/* 1 if we are hardware-blinking */
>  	} leds[NUM_LEDS];
> +#ifdef CONFIG_GPIOLIB
>  	struct gpio_chip		gpio;
>  	const char			*gpio_name[NUM_LEDS];
>  	int				gpio_map[NUM_LEDS];
> +#endif
>  };
>  
>  static const struct i2c_device_id tca6507_id[] = {
> @@ -520,6 +522,7 @@ static int tca6507_blink_set(struct led_classdev *led_cdev,
>  	return 0;
>  }
>  
> +#ifdef CONFIG_GPIOLIB
>  static void tca6507_gpio_set_value(struct gpio_chip *gc,
>  				   unsigned offset, int val)
>  {
> @@ -542,6 +545,51 @@ 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)
> +{
> +	int err;
> +	int i = 0;
> +	int gpios = 0;
> +
> +	for (i = 0; i < NUM_LEDS; i++)
> +		if (pdata->leds.leds[i].name && pdata->leds.leds[i].flags) {
> +			/* Configure as a gpio */
> +			tca->gpio_name[gpios] = pdata->leds.leds[i].name;
> +			tca->gpio_map[gpios] = i;
> +			gpios++;
> +		}
> +
> +	if (!gpios)
> +		return 0;
> +
> +	tca->gpio.label = "gpio-tca6507";
> +	tca->gpio.names = tca->gpio_name;
> +	tca->gpio.ngpio = gpios;
> +	tca->gpio.base = pdata->gpio_base;
> +	tca->gpio.owner = THIS_MODULE;
> +	tca->gpio.direction_output = tca6507_gpio_direction_output;
> +	tca->gpio.set = tca6507_gpio_set_value;
> +	tca->gpio.dev = &client->dev;
> +	err = gpiochip_add(&tca->gpio);
> +	if (err) {
> +		tca->gpio.ngpio = 0;
> +		return err;
> +	}
> +	if (pdata->setup)
> +		pdata->setup(tca->gpio.base, tca->gpio.ngpio);
> +	return 0;
> +}
> +
> +#else /* CONFIG_GPIOLIB */
> +static int tca6507_probe_gpios(struct i2c_client *client,
> +			       struct tca6507_chip *tca,
> +			       struct tca6507_platform_data *pdata)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_GPIOLIB */
>  
>  static int __devinit tca6507_probe(struct i2c_client *client,
>  				   const struct i2c_device_id *id)
> @@ -551,7 +599,6 @@ static int __devinit tca6507_probe(struct i2c_client *client,
>  	struct tca6507_platform_data *pdata;
>  	int err;
>  	int i = 0;
> -	int gpios = 0;
>  
>  	adapter = to_i2c_adapter(client->dev.parent);
>  	pdata = client->dev.platform_data;
> @@ -590,30 +637,10 @@ static int __devinit tca6507_probe(struct i2c_client *client,
>  			if (err < 0)
>  				goto exit;
>  		}
> -		if (pdata->leds.leds[i].name && pdata->leds.leds[i].flags) {
> -			/* Configure as a gpio */
> -			tca->gpio_name[gpios] = pdata->leds.leds[i].name;
> -			tca->gpio_map[gpios] = i;
> -			gpios++;
> -		}
> -	}
> -	if (gpios) {
> -		tca->gpio.label = "gpio-tca6507";
> -		tca->gpio.names = tca->gpio_name;
> -		tca->gpio.ngpio = gpios;
> -		tca->gpio.base = pdata->gpio_base;
> -		tca->gpio.owner = THIS_MODULE;
> -		tca->gpio.direction_output = tca6507_gpio_direction_output;
> -		tca->gpio.set = tca6507_gpio_set_value;
> -		tca->gpio.dev = &client->dev;
> -		err = gpiochip_add(&tca->gpio);
> -		if (err) {
> -			tca->gpio.ngpio = 0;
> -			goto exit;
> -		}
> -		if (pdata->setup)
> -			pdata->setup(tca->gpio.base, tca->gpio.ngpio);
>  	}
> +	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;
> diff --git a/include/linux/leds-tca6507.h b/include/linux/leds-tca6507.h
> index 3b8ac62..dcabf4f 100644
> --- a/include/linux/leds-tca6507.h
> +++ b/include/linux/leds-tca6507.h
> @@ -24,9 +24,10 @@
>  
>  struct tca6507_platform_data {
>  	struct led_platform_data leds;
> +#ifdef CONFIG_GPIOLIB
>  	int gpio_base;
>  	void (*setup)(unsigned gpio_base, unsigned ngpio);
> -	void (*teardown)(unsigned gpio_base, unsigned ngpio);
> +#endif
>  };
>  
>  #define	TCA6507_MAKE_GPIO 1
> 
> 
> --


-- 
~Randy
*** Remember to use Documentation/SubmitChecklist when testing your code ***

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available.
  2011-12-19 20:34   ` Randy Dunlap
@ 2011-12-20  5:37     ` NeilBrown
  0 siblings, 0 replies; 11+ messages in thread
From: NeilBrown @ 2011-12-20  5:37 UTC (permalink / raw)
  To: Randy Dunlap; +Cc: Andrew Morton, rpurdie, linux-kernel, Stephen Rothwell

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

On Mon, 19 Dec 2011 12:34:18 -0800 Randy Dunlap <rdunlap@xenotime.net> wrote:

> On 12/18/2011 04:20 PM, NeilBrown wrote:
> > This driver can configure the outputs as GPIO line instead of LEDs.  But
> > that only works if GPIOLIB is available.  So make that code conditional
> > on the library's availability.
> > 
> > Also remove the 'teardown' callback as it is never called and should
> > never be needed.
> > 
> > Reported-by: Randy Dunlap <rdunlap@xenotime.net>,
> > Reported-by: Stephen Rothwell <sfr@canb.auug.org.au>,
> > Signed-off-by: NeilBrown <neilb@suse.de>
> 
> Hi Neil,
> 
> with linux-next 20111219 and this patch applied, I now get:
> 
> drivers/leds/leds-tca6507.c: In function 'tca6507_remove':
> drivers/leds/leds-tca6507.c:677:9: error: 'struct tca6507_chip' has no member named 'gpio'
> drivers/leds/leds-tca6507.c:678:3: error: implicit declaration of function 'gpiochip_remove'
> drivers/leds/leds-tca6507.c:678:33: error: 'struct tca6507_chip' has no member named 'gpio'
> 
> when GPIOLIB is not enabled.

Drat - I forgot the 'remove' code.

I cannot easily compile for an ARCH that doesn't include GPIOLIB, but it
seems that if I put 
  #undef CONFIG_GPIOLIB
at the top of leds-tca6507.c it comes close enough for testing.
So the next version should get that right.  I'll repost.

Thanks,
NeilBrown



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

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available.
  2011-12-20  5:44 [PATCH 0/2] Two fixes for leds-tca6507 - Version 2 NeilBrown
@ 2011-12-20  5:44 ` NeilBrown
  2011-12-20 20:16   ` Randy Dunlap
  0 siblings, 1 reply; 11+ messages in thread
From: NeilBrown @ 2011-12-20  5:44 UTC (permalink / raw)
  To: Andrew Morton; +Cc: rpurdie, linux-kernel, Randy Dunlap

This driver can configure the outputs as GPIO line instead of LEDs.  But
that only works if GPIOLIB is available.  So make that code conditional
on the library's availability.

Also remove the 'teardown' callback as it is never called and should
never be needed.

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

 drivers/leds/leds-tca6507.c  |   92 +++++++++++++++++++++++++++++-------------
 include/linux/leds-tca6507.h |    3 +
 2 files changed, 65 insertions(+), 30 deletions(-)

diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c
index a7ea4cf..3565a61 100644
--- a/drivers/leds/leds-tca6507.c
+++ b/drivers/leds/leds-tca6507.c
@@ -149,9 +149,11 @@ struct tca6507_chip {
 		int			bank;	/* Bank used, or -1 */
 		int			blink;	/* 1 if we are hardware-blinking */
 	} leds[NUM_LEDS];
+#ifdef CONFIG_GPIOLIB
 	struct gpio_chip		gpio;
 	const char			*gpio_name[NUM_LEDS];
 	int				gpio_map[NUM_LEDS];
+#endif
 };
 
 static const struct i2c_device_id tca6507_id[] = {
@@ -520,6 +522,7 @@ static int tca6507_blink_set(struct led_classdev *led_cdev,
 	return 0;
 }
 
+#ifdef CONFIG_GPIOLIB
 static void tca6507_gpio_set_value(struct gpio_chip *gc,
 				   unsigned offset, int val)
 {
@@ -542,6 +545,62 @@ 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)
+{
+	int err;
+	int i = 0;
+	int gpios = 0;
+
+	for (i = 0; i < NUM_LEDS; i++)
+		if (pdata->leds.leds[i].name && pdata->leds.leds[i].flags) {
+			/* Configure as a gpio */
+			tca->gpio_name[gpios] = pdata->leds.leds[i].name;
+			tca->gpio_map[gpios] = i;
+			gpios++;
+		}
+
+	if (!gpios)
+		return 0;
+
+	tca->gpio.label = "gpio-tca6507";
+	tca->gpio.names = tca->gpio_name;
+	tca->gpio.ngpio = gpios;
+	tca->gpio.base = pdata->gpio_base;
+	tca->gpio.owner = THIS_MODULE;
+	tca->gpio.direction_output = tca6507_gpio_direction_output;
+	tca->gpio.set = tca6507_gpio_set_value;
+	tca->gpio.dev = &client->dev;
+	err = gpiochip_add(&tca->gpio);
+	if (err) {
+		tca->gpio.ngpio = 0;
+		return err;
+	}
+	if (pdata->setup)
+		pdata->setup(tca->gpio.base, tca->gpio.ngpio);
+	return 0;
+}
+
+static void tca6507_remove_gpio(struct tca6507_chip *tca)
+{
+	if (tca->gpio.ngpio) {
+		int err = gpiochip_remove(&tca->gpio);
+		dev_err(&tca->client->dev, "%s failed, %d\n",
+			"gpiochip_remove()", err);
+	}
+}
+#else /* CONFIG_GPIOLIB */
+static int tca6507_probe_gpios(struct i2c_client *client,
+			       struct tca6507_chip *tca,
+			       struct tca6507_platform_data *pdata)
+{
+	return 0;
+}
+static void tca6507_remove_gpio(struct tca6507_chip *tca)
+{
+}
+#endif /* CONFIG_GPIOLIB */
 
 static int __devinit tca6507_probe(struct i2c_client *client,
 				   const struct i2c_device_id *id)
@@ -551,7 +610,6 @@ static int __devinit tca6507_probe(struct i2c_client *client,
 	struct tca6507_platform_data *pdata;
 	int err;
 	int i = 0;
-	int gpios = 0;
 
 	adapter = to_i2c_adapter(client->dev.parent);
 	pdata = client->dev.platform_data;
@@ -590,30 +648,10 @@ static int __devinit tca6507_probe(struct i2c_client *client,
 			if (err < 0)
 				goto exit;
 		}
-		if (pdata->leds.leds[i].name && pdata->leds.leds[i].flags) {
-			/* Configure as a gpio */
-			tca->gpio_name[gpios] = pdata->leds.leds[i].name;
-			tca->gpio_map[gpios] = i;
-			gpios++;
-		}
-	}
-	if (gpios) {
-		tca->gpio.label = "gpio-tca6507";
-		tca->gpio.names = tca->gpio_name;
-		tca->gpio.ngpio = gpios;
-		tca->gpio.base = pdata->gpio_base;
-		tca->gpio.owner = THIS_MODULE;
-		tca->gpio.direction_output = tca6507_gpio_direction_output;
-		tca->gpio.set = tca6507_gpio_set_value;
-		tca->gpio.dev = &client->dev;
-		err = gpiochip_add(&tca->gpio);
-		if (err) {
-			tca->gpio.ngpio = 0;
-			goto exit;
-		}
-		if (pdata->setup)
-			pdata->setup(tca->gpio.base, tca->gpio.ngpio);
 	}
+	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;
@@ -638,11 +676,7 @@ static int __devexit tca6507_remove(struct i2c_client *client)
 		if (tca_leds[i].led_cdev.name)
 			led_classdev_unregister(&tca_leds[i].led_cdev);
 	}
-	if (tca->gpio.ngpio) {
-		int err = gpiochip_remove(&tca->gpio);
-		dev_err(&tca->client->dev, "%s failed, %d\n",
-			"gpiochip_remove()", err);
-	}
+	tca6507_remove_gpio(tca);
 	cancel_work_sync(&tca->work);
 	kfree(tca);
 	i2c_set_clientdata(client, NULL);
diff --git a/include/linux/leds-tca6507.h b/include/linux/leds-tca6507.h
index 3b8ac62..dcabf4f 100644
--- a/include/linux/leds-tca6507.h
+++ b/include/linux/leds-tca6507.h
@@ -24,9 +24,10 @@
 
 struct tca6507_platform_data {
 	struct led_platform_data leds;
+#ifdef CONFIG_GPIOLIB
 	int gpio_base;
 	void (*setup)(unsigned gpio_base, unsigned ngpio);
-	void (*teardown)(unsigned gpio_base, unsigned ngpio);
+#endif
 };
 
 #define	TCA6507_MAKE_GPIO 1



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available.
  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
  0 siblings, 1 reply; 11+ messages in thread
From: Randy Dunlap @ 2011-12-20 20:16 UTC (permalink / raw)
  To: NeilBrown; +Cc: Andrew Morton, rpurdie, linux-kernel

On 12/19/2011 09:44 PM, NeilBrown wrote:
> This driver can configure the outputs as GPIO line instead of LEDs.  But
> that only works if GPIOLIB is available.  So make that code conditional
> on the library's availability.
> 
> Also remove the 'teardown' callback as it is never called and should
> never be needed.
> 
> Signed-off-by: NeilBrown <neilb@suse.de>

Thanks.  This builds.

Acked-by: Randy Dunlap <rdunlap@xenotime.net>


> ---
> 
>  drivers/leds/leds-tca6507.c  |   92 +++++++++++++++++++++++++++++-------------
>  include/linux/leds-tca6507.h |    3 +
>  2 files changed, 65 insertions(+), 30 deletions(-)
> 
> diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c
> index a7ea4cf..3565a61 100644
> --- a/drivers/leds/leds-tca6507.c
> +++ b/drivers/leds/leds-tca6507.c
> @@ -149,9 +149,11 @@ struct tca6507_chip {
>  		int			bank;	/* Bank used, or -1 */
>  		int			blink;	/* 1 if we are hardware-blinking */
>  	} leds[NUM_LEDS];
> +#ifdef CONFIG_GPIOLIB
>  	struct gpio_chip		gpio;
>  	const char			*gpio_name[NUM_LEDS];
>  	int				gpio_map[NUM_LEDS];
> +#endif
>  };
>  
>  static const struct i2c_device_id tca6507_id[] = {
> @@ -520,6 +522,7 @@ static int tca6507_blink_set(struct led_classdev *led_cdev,
>  	return 0;
>  }
>  
> +#ifdef CONFIG_GPIOLIB
>  static void tca6507_gpio_set_value(struct gpio_chip *gc,
>  				   unsigned offset, int val)
>  {
> @@ -542,6 +545,62 @@ 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)
> +{
> +	int err;
> +	int i = 0;
> +	int gpios = 0;
> +
> +	for (i = 0; i < NUM_LEDS; i++)
> +		if (pdata->leds.leds[i].name && pdata->leds.leds[i].flags) {
> +			/* Configure as a gpio */
> +			tca->gpio_name[gpios] = pdata->leds.leds[i].name;
> +			tca->gpio_map[gpios] = i;
> +			gpios++;
> +		}
> +
> +	if (!gpios)
> +		return 0;
> +
> +	tca->gpio.label = "gpio-tca6507";
> +	tca->gpio.names = tca->gpio_name;
> +	tca->gpio.ngpio = gpios;
> +	tca->gpio.base = pdata->gpio_base;
> +	tca->gpio.owner = THIS_MODULE;
> +	tca->gpio.direction_output = tca6507_gpio_direction_output;
> +	tca->gpio.set = tca6507_gpio_set_value;
> +	tca->gpio.dev = &client->dev;
> +	err = gpiochip_add(&tca->gpio);
> +	if (err) {
> +		tca->gpio.ngpio = 0;
> +		return err;
> +	}
> +	if (pdata->setup)
> +		pdata->setup(tca->gpio.base, tca->gpio.ngpio);
> +	return 0;
> +}
> +
> +static void tca6507_remove_gpio(struct tca6507_chip *tca)
> +{
> +	if (tca->gpio.ngpio) {
> +		int err = gpiochip_remove(&tca->gpio);
> +		dev_err(&tca->client->dev, "%s failed, %d\n",
> +			"gpiochip_remove()", err);
> +	}
> +}
> +#else /* CONFIG_GPIOLIB */
> +static int tca6507_probe_gpios(struct i2c_client *client,
> +			       struct tca6507_chip *tca,
> +			       struct tca6507_platform_data *pdata)
> +{
> +	return 0;
> +}
> +static void tca6507_remove_gpio(struct tca6507_chip *tca)
> +{
> +}
> +#endif /* CONFIG_GPIOLIB */
>  
>  static int __devinit tca6507_probe(struct i2c_client *client,
>  				   const struct i2c_device_id *id)
> @@ -551,7 +610,6 @@ static int __devinit tca6507_probe(struct i2c_client *client,
>  	struct tca6507_platform_data *pdata;
>  	int err;
>  	int i = 0;
> -	int gpios = 0;
>  
>  	adapter = to_i2c_adapter(client->dev.parent);
>  	pdata = client->dev.platform_data;
> @@ -590,30 +648,10 @@ static int __devinit tca6507_probe(struct i2c_client *client,
>  			if (err < 0)
>  				goto exit;
>  		}
> -		if (pdata->leds.leds[i].name && pdata->leds.leds[i].flags) {
> -			/* Configure as a gpio */
> -			tca->gpio_name[gpios] = pdata->leds.leds[i].name;
> -			tca->gpio_map[gpios] = i;
> -			gpios++;
> -		}
> -	}
> -	if (gpios) {
> -		tca->gpio.label = "gpio-tca6507";
> -		tca->gpio.names = tca->gpio_name;
> -		tca->gpio.ngpio = gpios;
> -		tca->gpio.base = pdata->gpio_base;
> -		tca->gpio.owner = THIS_MODULE;
> -		tca->gpio.direction_output = tca6507_gpio_direction_output;
> -		tca->gpio.set = tca6507_gpio_set_value;
> -		tca->gpio.dev = &client->dev;
> -		err = gpiochip_add(&tca->gpio);
> -		if (err) {
> -			tca->gpio.ngpio = 0;
> -			goto exit;
> -		}
> -		if (pdata->setup)
> -			pdata->setup(tca->gpio.base, tca->gpio.ngpio);
>  	}
> +	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;
> @@ -638,11 +676,7 @@ static int __devexit tca6507_remove(struct i2c_client *client)
>  		if (tca_leds[i].led_cdev.name)
>  			led_classdev_unregister(&tca_leds[i].led_cdev);
>  	}
> -	if (tca->gpio.ngpio) {
> -		int err = gpiochip_remove(&tca->gpio);
> -		dev_err(&tca->client->dev, "%s failed, %d\n",
> -			"gpiochip_remove()", err);
> -	}
> +	tca6507_remove_gpio(tca);
>  	cancel_work_sync(&tca->work);
>  	kfree(tca);
>  	i2c_set_clientdata(client, NULL);
> diff --git a/include/linux/leds-tca6507.h b/include/linux/leds-tca6507.h
> index 3b8ac62..dcabf4f 100644
> --- a/include/linux/leds-tca6507.h
> +++ b/include/linux/leds-tca6507.h
> @@ -24,9 +24,10 @@
>  
>  struct tca6507_platform_data {
>  	struct led_platform_data leds;
> +#ifdef CONFIG_GPIOLIB
>  	int gpio_base;
>  	void (*setup)(unsigned gpio_base, unsigned ngpio);
> -	void (*teardown)(unsigned gpio_base, unsigned ngpio);
> +#endif
>  };
>  
>  #define	TCA6507_MAKE_GPIO 1
> 
> 


-- 
~Randy
*** Remember to use Documentation/SubmitChecklist when testing your code ***

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available.
  2011-12-20 20:16   ` Randy Dunlap
@ 2011-12-20 20:26     ` Randy Dunlap
  2011-12-20 21:03       ` NeilBrown
  0 siblings, 1 reply; 11+ messages in thread
From: Randy Dunlap @ 2011-12-20 20:26 UTC (permalink / raw)
  To: NeilBrown; +Cc: Andrew Morton, rpurdie, linux-kernel

On 12/20/2011 12:16 PM, Randy Dunlap wrote:
> On 12/19/2011 09:44 PM, NeilBrown wrote:
>> This driver can configure the outputs as GPIO line instead of LEDs.  But
>> that only works if GPIOLIB is available.  So make that code conditional
>> on the library's availability.
>>
>> Also remove the 'teardown' callback as it is never called and should
>> never be needed.
>>
>> Signed-off-by: NeilBrown <neilb@suse.de>
> 
> Thanks.  This builds.
> 
> Acked-by: Randy Dunlap <rdunlap@xenotime.net>

Neil,
You might want to check this also:

drivers/leds/leds-tca6507.c:358:17: warning: array subscript is above array bounds
drivers/leds/leds-tca6507.c:357:17: warning: array subscript is above array bounds


-- 
~Randy
*** Remember to use Documentation/SubmitChecklist when testing your code ***

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available.
  2011-12-20 20:26     ` Randy Dunlap
@ 2011-12-20 21:03       ` NeilBrown
  2011-12-20 22:23         ` Andrew Morton
  0 siblings, 1 reply; 11+ messages in thread
From: NeilBrown @ 2011-12-20 21:03 UTC (permalink / raw)
  To: Randy Dunlap; +Cc: Andrew Morton, rpurdie, linux-kernel

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

On Tue, 20 Dec 2011 12:26:51 -0800 Randy Dunlap <rdunlap@xenotime.net> wrote:

> On 12/20/2011 12:16 PM, Randy Dunlap wrote:
> > On 12/19/2011 09:44 PM, NeilBrown wrote:
> >> This driver can configure the outputs as GPIO line instead of LEDs.  But
> >> that only works if GPIOLIB is available.  So make that code conditional
> >> on the library's availability.
> >>
> >> Also remove the 'teardown' callback as it is never called and should
> >> never be needed.
> >>
> >> Signed-off-by: NeilBrown <neilb@suse.de>
> > 
> > Thanks.  This builds.
> > 
> > Acked-by: Randy Dunlap <rdunlap@xenotime.net>
> 
> Neil,
> You might want to check this also:
> 
> drivers/leds/leds-tca6507.c:358:17: warning: array subscript is above array bounds
> drivers/leds/leds-tca6507.c:357:17: warning: array subscript is above array bounds
> 
> 

Argh..

diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c
index 75dc5e4..ce20ab7 100644
--- a/drivers/leds/leds-tca6507.c
+++ b/drivers/leds/leds-tca6507.c
@@ -349,7 +349,7 @@ static int led_prepare(struct tca6507_led *led)
 			return 0;
 		}
 
-		for (i = MASTER; i >= BANK0; i++) {
+		for (i = MASTER; i >= BANK0; i--) {
 			int d;
 			if (tca->bank[i].level == level ||
 			    tca->bank[i].level_use == 0) {

That loop used to go up..

I might just go an re-review all the code.  And repeat the tests.

Thanks a lot.

NeilBrown

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

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available.
  2011-12-20 21:03       ` NeilBrown
@ 2011-12-20 22:23         ` Andrew Morton
  2011-12-30  0:35           ` NeilBrown
  0 siblings, 1 reply; 11+ messages in thread
From: Andrew Morton @ 2011-12-20 22:23 UTC (permalink / raw)
  To: NeilBrown; +Cc: Randy Dunlap, rpurdie, linux-kernel

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:


From: NeilBrown <neilb@suse.de>
Subject: leds: add driver for TCA6507 LED controller

TI's TCA6507 is the LED driver in the GTA04 Openmoko motherboard.  The
driver provides full support for brightness levels and hardware blinking.

This driver can drive each of 7 outputs as an LED or a GPIO output,
and provides hardware-assist blinking.

[akpm@linux-foundation.org: fix __mod_i2c_device_table alias]
[akpm@linux-foundation.org: coding-style fixes]
Signed-off-by: NeilBrown <neilb@suse.de>
Cc: Richard Purdie <rpurdie@rpsys.net>
Cc: Randy Dunlap <rdunlap@xenotime.net>
Cc: Dan Carpenter <dan.carpenter@oracle.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---

 drivers/leds/Kconfig         |    8 
 drivers/leds/Makefile        |    1 
 drivers/leds/leds-tca6507.c  |  725 +++++++++++++++++++++++++++++++++
 include/linux/leds-tca6507.h |   34 +
 4 files changed, 768 insertions(+)

diff -puN drivers/leds/Kconfig~leds-add-driver-for-tca6507-led-controller drivers/leds/Kconfig
--- a/drivers/leds/Kconfig~leds-add-driver-for-tca6507-led-controller
+++ a/drivers/leds/Kconfig
@@ -388,6 +388,14 @@ config LEDS_RENESAS_TPU
 	  pin function. The latter to support brightness control.
 	  Brightness control is supported but hardware blinking is not.
 
+config LEDS_TCA6507
+	tristate "LED Support for TCA6507 I2C chip"
+	depends on LEDS_CLASS && I2C
+	help
+	  This option enables support for LEDs connected to TC6507
+	  LED driver chips accessed via the I2C bus.
+	  Driver support brightness control and hardware-assisted blinking.
+
 config LEDS_TRIGGERS
 	bool "LED Trigger support"
 	depends on LEDS_CLASS
diff -puN drivers/leds/Makefile~leds-add-driver-for-tca6507-led-controller drivers/leds/Makefile
--- a/drivers/leds/Makefile~leds-add-driver-for-tca6507-led-controller
+++ a/drivers/leds/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_LEDS_NS2)			+= leds-ns2.o
 obj-$(CONFIG_LEDS_NETXBIG)		+= leds-netxbig.o
 obj-$(CONFIG_LEDS_ASIC3)		+= leds-asic3.o
 obj-$(CONFIG_LEDS_RENESAS_TPU)		+= leds-renesas-tpu.o
+obj-$(CONFIG_LEDS_TCA6507)		+= leds-tca6507.o
 
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
diff -puN /dev/null drivers/leds/leds-tca6507.c
--- /dev/null
+++ a/drivers/leds/leds-tca6507.c
@@ -0,0 +1,725 @@
+/*
+ * leds-tca6507
+ *
+ * The TCA6507 is a programmable LED controller that can drive 7
+ * separate lines either by holding them low, or by pulsing them
+ * with modulated width.
+ * The modulation can be varied in a simple pattern to produce a blink or
+ * double-blink.
+ *
+ * This driver can configure each line either as a 'GPIO' which is out-only
+ * (no pull-up) or as an LED with variable brightness and hardware-assisted
+ * blinking.
+ *
+ * Apart from OFF and ON there are three programmable brightness levels which
+ * can be programmed from 0 to 15 and indicate how many 500usec intervals in
+ * each 8msec that the led is 'on'.  The levels are named MASTER, BANK0 and
+ * BANK1.
+ *
+ * There are two different blink rates that can be programmed, each with
+ * separate time for rise, on, fall, off and second-off.  Thus if 3 or more
+ * different non-trivial rates are required, software must be used for the extra
+ * rates. The two different blink rates must align with the two levels BANK0 and
+ * BANK1.
+ * 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
+ * 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.
+ *
+ * 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
+ * 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 Led has programmable 'on' and 'off' time as milliseconds.  With each
+ * there is a flag saying if it was explicitly requested or defaulted.
+ * Similarly the banks know if each time was explicit or a default.  Defaults
+ * are permitted to be changed freely - they are not recognised when matching.
+ *
+ *
+ * An led-tca6507 device must be provided with platform data.  This data
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/workqueue.h>
+#include <linux/leds-tca6507.h>
+
+/* LED select registers determine the source that drives LED outputs */
+#define TCA6507_LS_LED_OFF	0x0	/* Output HI-Z (off) */
+#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) */
+#define TCA6507_LS_LED_MIR	0x5	/* Output LOW with Master Intensity */
+#define TCA6507_LS_BLINK0	0x6	/* Blink at Bank0 rate */
+#define TCA6507_LS_BLINK1	0x7	/* Blink at Bank1 rate */
+
+enum {
+	BANK0,
+	BANK1,
+	MASTER,
+};
+static int bank_source[3] = {
+	TCA6507_LS_LED_PWM0,
+	TCA6507_LS_LED_PWM1,
+	TCA6507_LS_LED_MIR,
+};
+static int blink_source[3] = {
+	TCA6507_LS_BLINK0,
+	TCA6507_LS_BLINK1,
+};
+
+/* PWM registers */
+#define	TCA6507_REG_CNT			11
+
+#define	TCA6507_FADE_ON			0x03
+#define	TCA6507_FULL_ON			0x04
+#define	TCA6507_FADE_OFF		0x05
+#define	TCA6507_FIRST_OFF		0x06
+#define	TCA6507_SECOND_OFF		0x07
+#define	TCA6507_MAX_INTENSITY		0x08
+#define	TCA6507_MASTER_INTENSITY	0x09
+#define	TCA6507_INITIALIZE		0x0A
+
+#define	INIT_CODE			0x8
+
+#define TIMECODES 16
+static int time_codes[TIMECODES] = {
+	0, 64, 128, 192, 256, 384, 512, 768,
+	1024, 1536, 2048, 3072, 4096, 5760, 8128, 16320
+};
+
+/* Convert an led.brightness level (0..255) to a TCA6507 level (0..15) */
+static inline int TO_LEVEL(int brightness)
+{
+	return brightness >> 4;
+}
+
+/* ...and convert back */
+static inline int TO_BRIGHT(int level)
+{
+	if (level)
+		return (level << 4) | 0xf;
+	return 0;
+}
+
+#define NUM_LEDS 7
+struct tca6507_chip {
+	int			reg_set;	/* a '1' means the register
+						 * should be written */
+	u8			reg_file[TCA6507_REG_CNT];
+	/* Bank 0 is Master Intensity */
+	struct bank {
+		int level;
+		int ontime, offtime;
+		int on_dflt, off_dflt;
+		int time_use, level_use;
+	} bank[3];
+	struct i2c_client	*client;
+	struct work_struct	work;
+	spinlock_t		lock;
+
+	struct tca6507_led {
+		struct tca6507_chip	*chip;
+		struct led_classdev	led_cdev;
+		int			num;
+		int			ontime, offtime;
+		int			on_dflt, off_dflt;
+		int			bank;	/* Bank used, or -1 */
+		int			blink;	/* 1 if we are hardware-blinking */
+	} leds[NUM_LEDS];
+#ifdef CONFIG_GPIOLIB
+	struct gpio_chip		gpio;
+	const char			*gpio_name[NUM_LEDS];
+	int				gpio_map[NUM_LEDS];
+#endif
+};
+
+static const struct i2c_device_id tca6507_id[] = {
+	{ "tca6507" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tca6507_id);
+
+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.
+	 * 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).
+	 */
+	int c1, c2;
+	int tmax = msec * 9 / 8;
+	int tmin = msec * 7 / 8;
+	int diff = 65536;
+
+	/* We start at '1' to ensure we never even think of choosing a
+	 * total time of '0'.
+	 */
+	for (c1 = 1; c1 < TIMECODES; c1++) {
+		int t = time_codes[c1];
+		if (t*2 < tmin)
+			continue;
+		if (t > tmax)
+			break;
+		for (c2 = 0; c2 <= c1; c2--) {
+			int tt = t + time_codes[c2];
+			int d;
+			if (tt < tmin)
+				continue;
+			if (tt > tmax)
+				break;
+			/* This works! */
+			d = abs(msec - tt);
+			if (d >= diff)
+				continue;
+			/* Best yet */
+			*c1p = c1;
+			*c2p = c2;
+			diff = d;
+			if (d == 0)
+				return 0;
+		}
+	}
+	if (diff < 65536)
+		return 0;
+	/* No close match */
+	return -EINVAL;
+}
+
+/*
+ * Update the register file with the appropriate 3-bit state for
+ * the given led.
+ */
+static void set_select(struct tca6507_chip *tca, int led, int val)
+{
+	int mask = (1 << led);
+	int bit;
+
+	for (bit = 0; bit < 3; bit++) {
+		int n = tca->reg_file[bit] & ~mask;
+		if (val & (1 << bit))
+			n |= mask;
+		if (tca->reg_file[bit] != n) {
+			tca->reg_file[bit] = n;
+			tca->reg_set |= (1 << bit);
+		}
+	}
+}
+
+/* Update the register file with the appropriate 4-bit code for
+ * one bank or other.  This can be used for timers, for levels, or
+ * for initialisation.
+ */
+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)
+		new <<= 4;
+	n |= new;
+	if (tca->reg_file[reg] != n) {
+		tca->reg_file[reg] = n;
+		tca->reg_set |= 1 << reg;
+	}
+}
+
+/* Update brightness level. */
+static void set_level(struct tca6507_chip *tca, int bank, int level)
+{
+	switch (bank) {
+	case BANK0:
+	case BANK1:
+		set_code(tca, TCA6507_MAX_INTENSITY, bank, level);
+		break;
+	case MASTER:
+		set_code(tca, TCA6507_MASTER_INTENSITY, 0, level);
+		break;
+	}
+	tca->bank[bank].level = level;
+}
+
+/* Record all relevant time code for a given bank */
+static void set_times(struct tca6507_chip *tca, int bank)
+{
+	int c1, c2;
+
+	choose_times(tca->bank[bank].ontime, &c1, &c2);
+	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];
+
+	choose_times(tca->bank[bank].offtime, &c1, &c2);
+	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];
+
+	set_code(tca, TCA6507_INITIALIZE, bank, INIT_CODE);
+}
+
+/* Write all needed register of tca6507 */
+
+static void tca6507_work(struct work_struct *work)
+{
+	struct tca6507_chip *tca = container_of(work, struct tca6507_chip,
+						work);
+	struct i2c_client *cl = tca->client;
+	int set;
+	u8 file[TCA6507_REG_CNT];
+	int r;
+
+	spin_lock(&tca->lock);
+	set = tca->reg_set;
+	memcpy(file, tca->reg_file, TCA6507_REG_CNT);
+	tca->reg_set = 0;
+	spin_unlock(&tca->lock);
+
+	for (r = 0; r < TCA6507_REG_CNT; r++)
+		if (set & (1<<r))
+			i2c_smbus_write_byte_data(cl, r, file[r]);
+}
+
+static void led_release(struct tca6507_led *led)
+{
+	/* If led owns any resource, release it. */
+	struct tca6507_chip *tca = led->chip;
+	if (led->bank >= 0) {
+		struct bank *b = tca->bank + led->bank;
+		if (led->blink)
+			b->time_use--;
+		b->level_use--;
+	}
+	led->blink = 0;
+	led->bank = -1;
+}
+
+static int led_prepare(struct tca6507_led *led)
+{
+	/* 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;
+	int i;
+	struct bank *b;
+	int need_init = 0;
+
+	led->led_cdev.brightness = TO_BRIGHT(level);
+	if (level == 0) {
+		set_select(tca, led->num, TCA6507_LS_LED_OFF);
+		return 0;
+	}
+
+	if (led->ontime == 0 || led->offtime == 0) {
+		/*
+		 * Just set the brightness, choosing first usable bank.
+		 * If none perfect, choose best.
+		 * Count backwards so we check MASTER bank first
+		 * to avoid wasting a timer.
+		 */
+		int best = -1;/* full-on */
+		int diff = 15-level;
+
+		if (level == 15) {
+			set_select(tca, led->num, TCA6507_LS_LED_ON);
+			return 0;
+		}
+
+		for (i = MASTER; i >= BANK0; i--) {
+			int d;
+			if (tca->bank[i].level == level ||
+			    tca->bank[i].level_use == 0) {
+				best = i;
+				break;
+			}
+			d = abs(level - tca->bank[i].level);
+			if (d < diff) {
+				diff = d;
+				best = i;
+			}
+		}
+		if (best == -1) {
+			/* Best brightness is full-on */
+			set_select(tca, led->num, TCA6507_LS_LED_ON);
+			led->led_cdev.brightness = LED_FULL;
+			return 0;
+		}
+
+		if (!tca->bank[best].level_use)
+			set_level(tca, best, level);
+
+		tca->bank[best].level_use++;
+		led->bank = best;
+		set_select(tca, led->num, bank_source[best]);
+		led->led_cdev.brightness = TO_BRIGHT(tca->bank[best].level);
+		return 0;
+	}
+
+	/* We have on/off time so we need to try to allocate a timing bank. */
+	if (choose_times(led->ontime, &c1, &c2) < 0)
+		return -EINVAL;
+	if (choose_times(led->offtime, &c1, &c2) < 0)
+		return -EINVAL;
+
+	for (i = BANK0; i <= BANK1; i++) {
+		if (tca->bank[i].level_use == 0)
+			/* not in use - it is ours! */
+			break;
+		if (tca->bank[i].level != level)
+			/* Incompatible level - skip */
+			/* FIX: if timer matches we maybe should consider
+			 * this anyway...
+			 */
+			continue;
+
+		if (tca->bank[i].time_use == 0)
+			/* Timer not in use, and level matches - use it */
+			break;
+
+		if (!(tca->bank[i].on_dflt ||
+		      led->on_dflt ||
+		      tca->bank[i].ontime == led->ontime))
+			/* on time is incompatible */
+			continue;
+
+		if (!(tca->bank[i].off_dflt ||
+		      led->off_dflt ||
+		      tca->bank[i].offtime == led->offtime))
+			/* off time is incompatible */
+			continue;
+
+		/* looks like a suitable match */
+		break;
+	}
+
+	if (i > BANK1)
+		/* Nothing matches - how sad */
+		return -EINVAL;
+
+	b = &tca->bank[i];
+	if (b->level_use == 0)
+		set_level(tca, i, level);
+	b->level_use++;
+	led->bank = i;
+
+	if (b->on_dflt ||
+	    !led->on_dflt ||
+	    b->time_use == 0) {
+		b->ontime = led->ontime;
+		b->on_dflt = led->on_dflt;
+		need_init = 1;
+	}
+
+	if (b->off_dflt ||
+	    !led->off_dflt ||
+	    b->time_use == 0) {
+		b->offtime = led->offtime;
+		b->off_dflt = led->off_dflt;
+		need_init = 1;
+	}
+
+	if (need_init)
+		set_times(tca, i);
+
+	led->ontime = b->ontime;
+	led->offtime = b->offtime;
+
+	b->time_use++;
+	led->blink = 1;
+	led->led_cdev.brightness = TO_BRIGHT(b->level);
+	set_select(tca, led->num, blink_source[i]);
+	return 0;
+}
+
+static int led_assign(struct tca6507_led *led)
+{
+	struct tca6507_chip *tca = led->chip;
+	int err;
+
+	spin_lock(&tca->lock);
+	led_release(led);
+	err = led_prepare(led);
+	if (err) {
+		/*
+		 * Can only fail on timer setup.  In that case we need to
+		 * re-establish as steady level.
+		 */
+		led->ontime = 0;
+		led->offtime = 0;
+		led_prepare(led);
+	}
+	spin_unlock(&tca->lock);
+
+	if (tca->reg_set)
+		schedule_work(&tca->work);
+	return err;
+}
+
+static void tca6507_brightness_set(struct led_classdev *led_cdev,
+				   enum led_brightness brightness)
+{
+	struct tca6507_led *led = container_of(led_cdev, struct tca6507_led,
+					       led_cdev);
+	led->led_cdev.brightness = brightness;
+	led->ontime = 0;
+	led->offtime = 0;
+	led_assign(led);
+}
+
+static int tca6507_blink_set(struct led_classdev *led_cdev,
+			     unsigned long *delay_on,
+			     unsigned long *delay_off)
+{
+	struct tca6507_led *led = container_of(led_cdev, struct tca6507_led,
+					       led_cdev);
+
+	if (*delay_on == 0)
+		led->on_dflt = 1;
+	else if (delay_on != &led_cdev->blink_delay_on)
+		led->on_dflt = 0;
+	led->ontime = *delay_on;
+
+	if (*delay_off == 0)
+		led->off_dflt = 1;
+	else if (delay_off != &led_cdev->blink_delay_off)
+		led->off_dflt = 0;
+	led->offtime = *delay_off;
+
+	if (led->ontime == 0)
+		led->ontime = 512;
+	if (led->offtime == 0)
+		led->offtime = 512;
+
+	if (led->led_cdev.brightness == LED_OFF)
+		led->led_cdev.brightness = LED_FULL;
+	if (led_assign(led) < 0) {
+		led->ontime = 0;
+		led->offtime = 0;
+		led->led_cdev.brightness = LED_OFF;
+		return -EINVAL;
+	}
+	*delay_on = led->ontime;
+	*delay_off = led->offtime;
+	return 0;
+}
+
+#ifdef CONFIG_GPIOLIB
+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);
+
+	spin_lock(&tca->lock);
+	/*
+	 * '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);
+	if (tca->reg_set)
+		schedule_work(&tca->work);
+}
+
+static int tca6507_gpio_direction_output(struct gpio_chip *gc,
+					  unsigned offset, int val)
+{
+	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)
+{
+	int err;
+	int i = 0;
+	int gpios = 0;
+
+	for (i = 0; i < NUM_LEDS; i++)
+		if (pdata->leds.leds[i].name && pdata->leds.leds[i].flags) {
+			/* Configure as a gpio */
+			tca->gpio_name[gpios] = pdata->leds.leds[i].name;
+			tca->gpio_map[gpios] = i;
+			gpios++;
+		}
+
+	if (!gpios)
+		return 0;
+
+	tca->gpio.label = "gpio-tca6507";
+	tca->gpio.names = tca->gpio_name;
+	tca->gpio.ngpio = gpios;
+	tca->gpio.base = pdata->gpio_base;
+	tca->gpio.owner = THIS_MODULE;
+	tca->gpio.direction_output = tca6507_gpio_direction_output;
+	tca->gpio.set = tca6507_gpio_set_value;
+	tca->gpio.dev = &client->dev;
+	err = gpiochip_add(&tca->gpio);
+	if (err) {
+		tca->gpio.ngpio = 0;
+		return err;
+	}
+	if (pdata->setup)
+		pdata->setup(tca->gpio.base, tca->gpio.ngpio);
+	return 0;
+}
+
+static void tca6507_remove_gpio(struct tca6507_chip *tca)
+{
+	if (tca->gpio.ngpio) {
+		int err = gpiochip_remove(&tca->gpio);
+		dev_err(&tca->client->dev, "%s failed, %d\n",
+			"gpiochip_remove()", err);
+	}
+}
+#else /* CONFIG_GPIOLIB */
+static int tca6507_probe_gpios(struct i2c_client *client,
+			       struct tca6507_chip *tca,
+			       struct tca6507_platform_data *pdata)
+{
+	return 0;
+}
+static void tca6507_remove_gpio(struct tca6507_chip *tca)
+{
+}
+#endif /* CONFIG_GPIOLIB */
+
+static int __devinit tca6507_probe(struct i2c_client *client,
+				   const struct i2c_device_id *id)
+{
+	struct tca6507_chip *tca;
+	struct i2c_adapter *adapter;
+	struct tca6507_platform_data *pdata;
+	int err;
+	int i = 0;
+
+	adapter = to_i2c_adapter(client->dev.parent);
+	pdata = client->dev.platform_data;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_I2C))
+		return -EIO;
+
+	if (!pdata || pdata->leds.num_leds != NUM_LEDS) {
+		dev_err(&client->dev, "Need %d entries in platform-data list\n",
+			NUM_LEDS);
+		return -ENODEV;
+	}
+	err = -ENOMEM;
+	tca = kzalloc(sizeof(*tca), GFP_KERNEL);
+	if (!tca)
+		goto exit;
+
+	tca->client = client;
+	INIT_WORK(&tca->work, tca6507_work);
+	spin_lock_init(&tca->lock);
+
+	for (i = 0; i < NUM_LEDS; i++) {
+		struct tca6507_led *l = tca->leds + i;
+
+		l->chip = tca;
+		l->num = i;
+		if (pdata->leds.leds[i].name && !pdata->leds.leds[i].flags) {
+			l->led_cdev.name = pdata->leds.leds[i].name;
+			l->led_cdev.default_trigger
+				= pdata->leds.leds[i].default_trigger;
+			l->led_cdev.brightness_set = tca6507_brightness_set;
+			l->led_cdev.blink_set = tca6507_blink_set;
+			l->bank = -1;
+			err = led_classdev_register(&client->dev,
+						    &l->led_cdev);
+			if (err < 0)
+				goto exit;
+		}
+	}
+	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);
+
+	return 0;
+exit:
+	while (i--)
+		if (tca->leds[i].led_cdev.name)
+			led_classdev_unregister(&tca->leds[i].led_cdev);
+	kfree(tca);
+	return err;
+}
+
+static int __devexit tca6507_remove(struct i2c_client *client)
+{
+	int i;
+	struct tca6507_chip *tca = i2c_get_clientdata(client);
+	struct tca6507_led *tca_leds = tca->leds;
+
+	for (i = 0; i < NUM_LEDS; i++) {
+		if (tca_leds[i].led_cdev.name)
+			led_classdev_unregister(&tca_leds[i].led_cdev);
+	}
+	tca6507_remove_gpio(tca);
+	cancel_work_sync(&tca->work);
+	kfree(tca);
+	i2c_set_clientdata(client, NULL);
+
+	return 0;
+}
+
+static struct i2c_driver tca6507_driver = {
+	.driver   = {
+		.name    = "leds-tca6507",
+		.owner   = THIS_MODULE,
+	},
+	.probe    = tca6507_probe,
+	.remove   = __devexit_p(tca6507_remove),
+	.id_table = tca6507_id,
+};
+
+static int __init tca6507_leds_init(void)
+{
+	return i2c_add_driver(&tca6507_driver);
+}
+
+static void __exit tca6507_leds_exit(void)
+{
+	i2c_del_driver(&tca6507_driver);
+}
+
+module_init(tca6507_leds_init);
+module_exit(tca6507_leds_exit);
+
+MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
+MODULE_DESCRIPTION("TCA6507 LED/GPO driver");
+MODULE_LICENSE("GPL v2");
diff -puN /dev/null include/linux/leds-tca6507.h
--- /dev/null
+++ a/include/linux/leds-tca6507.h
@@ -0,0 +1,34 @@
+/*
+ * TCA6507 LED chip driver.
+ *
+ * Copyright (C) 2011 Neil Brown <neil@brown.name>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef __LINUX_TCA6507_H
+#define __LINUX_TCA6507_H
+#include <linux/leds.h>
+
+struct tca6507_platform_data {
+	struct led_platform_data leds;
+#ifdef CONFIG_GPIOLIB
+	int gpio_base;
+	void (*setup)(unsigned gpio_base, unsigned ngpio);
+#endif
+};
+
+#define	TCA6507_MAKE_GPIO 1
+#endif /* __LINUX_TCA6507_H*/
_


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/2] leds-tca6507: allow driver to compile when GPIOLIB is not available.
  2011-12-20 22:23         ` Andrew Morton
@ 2011-12-30  0:35           ` NeilBrown
  0 siblings, 0 replies; 11+ messages in thread
From: NeilBrown @ 2011-12-30  0:35 UTC (permalink / raw)
  To: Andrew Morton; +Cc: Randy Dunlap, rpurdie, linux-kernel

[-- 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 --]

^ permalink raw reply related	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2011-12-30  0:36 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-12-19  0:20 [PATCH 0/2] Two fixes for leds-tca6507 NeilBrown
2011-12-19  0:20 ` [PATCH 2/2] leds-tca6507 - fix off by one error 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
  -- strict thread matches above, loose matches on Subject: below --
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 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).