// SPDX-License-Identifier: GPL-2.0-only /* * 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 (pull-up resistor required) 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 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. * 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 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) 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. 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.
*/
/* Convert an led.brightness level (0..255) to a TCA6507 level (0..15) */ staticinlineint TO_LEVEL(int brightness)
{ return brightness >> 4;
}
/* ...and convert back */ staticinlineint TO_BRIGHT(int level)
{ if (level) return (level << 4) | 0xf; return 0;
}
#define NUM_LEDS 7 struct tca6507_chip { int reg_set; /* One bit per register where * a '1' means the register
* should be written */
u8 reg_file[TCA6507_REG_CNT]; /* Bank 2 is Master Intensity and doesn't use times */ 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; /* Set if hardware-blinking */
} leds[NUM_LEDS]; #ifdef CONFIG_GPIOLIB struct gpio_chip gpio; int gpio_map[NUM_LEDS]; #endif
};
staticint choose_times(int msec, int *c1p, int *c2p)
{ /* * Choose two timecodes which add to 'msec' as near as * possible. 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).
*/ 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 msec;
}
} if (diff < 65536) { int actual; if (msec & 1) {
swap(*c2p, *c1p);
}
actual = time_codes[*c1p] + time_codes[*c2p]; if (*c1p < *c2p) return actual + 1; else return actual;
} /* No close match */ return -EINVAL;
}
/* * Update the register file with the appropriate 3-bit state for the * given led.
*/ staticvoid 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 * initialization.
*/ staticvoid set_code(struct tca6507_chip *tca, int reg, int bank, intnew)
{ 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;
tca->reg_set |= 1 << reg;
}
}
/* Update brightness level. */ staticvoid 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 codes for a given bank */ staticvoid set_times(struct tca6507_chip *tca, int bank)
{ int c1, c2; int result;
result = choose_times(tca->bank[bank].ontime, &c1, &c2); if (result < 0) return;
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 = result;
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 = result;
set_code(tca, TCA6507_INITIALIZE, bank, INIT_CODE);
}
for (r = 0; r < TCA6507_REG_CNT; r++) if (set & (1<<r))
i2c_smbus_write_byte_data(cl, r, file[r]);
}
staticvoid 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;
}
staticint 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;
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;
/* * 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) 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;
spin_lock_irqsave(&tca->lock, flags);
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_irqrestore(&tca->lock, flags);
if (tca->reg_set)
schedule_work(&tca->work); return err;
}
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_irqrestore(&tca->lock, flags); if (tca->reg_set)
schedule_work(&tca->work);
staticint tca6507_probe_gpios(struct device *dev, 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_map[gpios] = i;
gpios++;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.