aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/leds/leds-tca6507.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds/leds-tca6507.c')
-rw-r--r--drivers/leds/leds-tca6507.c132
1 files changed, 93 insertions, 39 deletions
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;
}