/* * While possible that a user-defined ->get_irq_reg() callback might * be linear enough to support bulk reads, most of the time it won't. * Therefore only allow them if the default callback is being used.
*/ return data->irq_reg_stride == 1 && map->reg_stride == 1 &&
data->get_irq_reg == regmap_irq_get_irq_reg_linear &&
!map->use_single_read;
}
if (d->chip->runtime_pm) {
ret = pm_runtime_get_sync(map->dev); if (ret < 0)
dev_err(map->dev, "IRQ sync failed to resume: %d\n",
ret);
}
if (d->clear_status) { for (i = 0; i < d->chip->num_regs; i++) {
reg = d->get_irq_reg(d, d->chip->status_base, i);
ret = regmap_read(map, reg, &val); if (ret)
dev_err(d->map->dev, "Failed to clear the interrupt status bits\n");
}
d->clear_status = false;
}
/* * If there's been a change in the mask write it back to the * hardware. We rely on the use of the regmap core cache to * suppress pointless writes.
*/ for (i = 0; i < d->chip->num_regs; i++) { if (d->chip->handle_mask_sync)
d->chip->handle_mask_sync(i, d->mask_buf_def[i],
d->mask_buf[i],
d->chip->irq_drv_data);
if (d->chip->mask_base && !d->chip->handle_mask_sync) {
reg = d->get_irq_reg(d, d->chip->mask_base, i);
ret = regmap_update_bits(d->map, reg,
d->mask_buf_def[i],
d->mask_buf[i]); if (ret)
dev_err(d->map->dev, "Failed to sync masks in %x\n", reg);
}
if (d->chip->unmask_base && !d->chip->handle_mask_sync) {
reg = d->get_irq_reg(d, d->chip->unmask_base, i);
ret = regmap_update_bits(d->map, reg,
d->mask_buf_def[i], ~d->mask_buf[i]); if (ret)
dev_err(d->map->dev, "Failed to sync masks in %x\n",
reg);
}
reg = d->get_irq_reg(d, d->chip->wake_base, i); if (d->wake_buf) { if (d->chip->wake_invert)
ret = regmap_update_bits(d->map, reg,
d->mask_buf_def[i],
~d->wake_buf[i]); else
ret = regmap_update_bits(d->map, reg,
d->mask_buf_def[i],
d->wake_buf[i]); if (ret != 0)
dev_err(d->map->dev, "Failed to sync wakes in %x: %d\n",
reg, ret);
}
if (!d->chip->init_ack_masked) continue; /* * Ack all the masked interrupts unconditionally, * OR if there is masked interrupt which hasn't been Acked, * it'll be ignored in irq handler, then may introduce irq storm
*/ if (d->mask_buf[i] && (d->chip->ack_base || d->chip->use_ack)) {
reg = d->get_irq_reg(d, d->chip->ack_base, i);
/* some chips ack by write 0 */ if (d->chip->ack_invert)
ret = regmap_write(map, reg, ~d->mask_buf[i]); else
ret = regmap_write(map, reg, d->mask_buf[i]); if (d->chip->clear_ack) { if (d->chip->ack_invert && !ret)
ret = regmap_write(map, reg, UINT_MAX); elseif (!ret)
ret = regmap_write(map, reg, 0);
} if (ret != 0)
dev_err(d->map->dev, "Failed to ack 0x%x: %d\n",
reg, ret);
}
}
for (i = 0; i < d->chip->num_config_bases; i++) { for (j = 0; j < d->chip->num_config_regs; j++) {
reg = d->get_irq_reg(d, d->chip->config_base[i], j);
ret = regmap_write(map, reg, d->config_buf[i][j]); if (ret)
dev_err(d->map->dev, "Failed to write config %x: %d\n",
reg, ret);
}
}
if (d->chip->runtime_pm)
pm_runtime_put(map->dev);
/* If we've changed our wakeup count propagate it to the parent */ if (d->wake_count < 0) for (i = d->wake_count; i < 0; i++)
disable_irq_wake(d->irq); elseif (d->wake_count > 0) for (i = 0; i < d->wake_count; i++)
enable_irq_wake(d->irq);
/* * The type_in_mask flag means that the underlying hardware uses * separate mask bits for each interrupt trigger type, but we want * to have a single logical interrupt with a configurable type. * * If the interrupt we're enabling defines any supported types * then instead of using the regular mask bits for this interrupt, * use the value previously written to the type buffer at the * corresponding offset in regmap_irq_set_type().
*/ if (d->chip->type_in_mask && irq_data->type.types_supported)
mask = d->type_buf[reg] & irq_data->mask; else
mask = irq_data->mask;
if (d->chip->clear_on_unmask)
d->clear_status = true;
staticinlineint read_sub_irq_data(struct regmap_irq_chip_data *data, unsignedint b)
{ conststruct regmap_irq_chip *chip = data->chip; conststruct regmap_irq_sub_irq_map *subreg; struct regmap *map = data->map; unsignedint reg; int i, ret = 0;
if (!chip->sub_reg_offsets) {
reg = data->get_irq_reg(data, chip->status_base, b);
ret = regmap_read(map, reg, &data->status_buf[b]);
} else { /* * Note we can't use ->get_irq_reg() here because the offsets * in 'subreg' are *not* interchangeable with indices.
*/
subreg = &chip->sub_reg_offsets[b]; for (i = 0; i < subreg->num_regs; i++) { unsignedint offset = subreg->offset[i]; unsignedint index = offset / map->reg_stride;
ret = regmap_read(map, chip->status_base + offset,
&data->status_buf[index]); if (ret) break;
}
} return ret;
}
/* * Read only registers with active IRQs if the chip has 'main status * register'. Else read in the statuses, using a single bulk read if * possible in order to reduce the I/O overheads.
*/
if (chip->no_status) { /* no status register so default to all active */
memset32(data->status_buf, GENMASK(31, 0), chip->num_regs);
} elseif (chip->num_main_regs) { unsignedint max_main_bits;
max_main_bits = (chip->num_main_status_bits) ?
chip->num_main_status_bits : chip->num_regs; /* Clear the status buf as we don't read all status regs */
memset32(data->status_buf, 0, chip->num_regs);
/* We could support bulk read for main status registers * but I don't expect to see devices with really many main * status registers so let's only support single reads for the * sake of simplicity. and add bulk reads only if needed
*/ for (i = 0; i < chip->num_main_regs; i++) {
reg = data->get_irq_reg(data, chip->main_status, i);
ret = regmap_read(map, reg, &data->main_status_buf[i]); if (ret) {
dev_err(map->dev, "Failed to read IRQ status %d\n", ret); return ret;
}
}
/* Read sub registers with active IRQs */ for (i = 0; i < chip->num_main_regs; i++) { unsignedint b; constunsignedlong mreg = data->main_status_buf[i];
for_each_set_bit(b, &mreg, map->format.val_bytes * 8) { if (i * map->format.val_bytes * 8 + b >
max_main_bits) break;
ret = read_sub_irq_data(data, b);
if (ret != 0) {
dev_err(map->dev, "Failed to read IRQ status %d\n", ret); return ret;
}
}
/* * Ignore masked IRQs and ack if we need to; we ack early so * there is no race between handling and acknowledging the * interrupt. We assume that typically few of the interrupts * will fire simultaneously so don't worry about overhead from * doing a write per register.
*/ for (i = 0; i < data->chip->num_regs; i++) {
data->status_buf[i] &= ~data->mask_buf[i];
/** * regmap_irq_get_irq_reg_linear() - Linear IRQ register mapping callback. * @data: Data for the &struct regmap_irq_chip * @base: Base register * @index: Register index * * Returns the register address corresponding to the given @base and @index * by the formula ``base + index * regmap_stride * irq_reg_stride``.
*/ unsignedint regmap_irq_get_irq_reg_linear(struct regmap_irq_chip_data *data, unsignedint base, int index)
{ struct regmap *map = data->map;
return base + index * map->reg_stride * data->irq_reg_stride;
}
EXPORT_SYMBOL_GPL(regmap_irq_get_irq_reg_linear);
/** * regmap_irq_set_type_config_simple() - Simple IRQ type configuration callback. * @buf: Buffer containing configuration register values, this is a 2D array of * `num_config_bases` rows, each of `num_config_regs` elements. * @type: The requested IRQ type. * @irq_data: The IRQ being configured. * @idx: Index of the irq's config registers within each array `buf[i]` * @irq_drv_data: Driver specific IRQ data * * This is a &struct regmap_irq_chip->set_type_config callback suitable for * chips with one config register. Register values are updated according to * the &struct regmap_irq_type data associated with an IRQ.
*/ int regmap_irq_set_type_config_simple(unsignedint **buf, unsignedint type, conststruct regmap_irq *irq_data, int idx, void *irq_drv_data)
{ conststruct regmap_irq_type *t = &irq_data->type;
d->domain = irq_domain_instantiate(&info); if (IS_ERR(d->domain)) {
dev_err(d->map->dev, "Failed to create IRQ domain\n"); return PTR_ERR(d->domain);
}
return 0;
}
/** * regmap_add_irq_chip_fwnode() - Use standard regmap IRQ controller handling * * @fwnode: The firmware node where the IRQ domain should be added to. * @map: The regmap for the device. * @irq: The IRQ the device uses to signal interrupts. * @irq_flags: The IRQF_ flags to use for the primary interrupt. * @irq_base: Allocate at specific IRQ number if irq_base > 0. * @chip: Configuration for the interrupt controller. * @data: Runtime data structure for the controller, allocated on success. * * Returns 0 on success or an errno on failure. * * In order for this to be efficient the chip really should use a * register cache. The chip driver is responsible for restoring the * register values used by the IRQ controller over suspend and resume.
*/ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode, struct regmap *map, int irq, int irq_flags, int irq_base, conststruct regmap_irq_chip *chip, struct regmap_irq_chip_data **data)
{ struct regmap_irq_chip_data *d; int i; int ret = -ENOMEM;
u32 reg;
if (chip->num_regs <= 0) return -EINVAL;
if (chip->clear_on_unmask && (chip->ack_base || chip->use_ack)) return -EINVAL;
if (chip->mask_base && chip->unmask_base && !chip->mask_unmask_non_inverted) return -EINVAL;
for (i = 0; i < chip->num_irqs; i++) { if (chip->irqs[i].reg_offset % map->reg_stride) return -EINVAL; if (chip->irqs[i].reg_offset / map->reg_stride >=
chip->num_regs) return -EINVAL;
}
if (irq_base) {
irq_base = irq_alloc_descs(irq_base, 0, chip->num_irqs, 0); if (irq_base < 0) {
dev_warn(map->dev, "Failed to allocate IRQs: %d\n",
irq_base); return irq_base;
}
}
d = kzalloc(sizeof(*d), GFP_KERNEL); if (!d) return -ENOMEM;
if (chip->num_main_regs) {
d->main_status_buf = kcalloc(chip->num_main_regs, sizeof(*d->main_status_buf),
GFP_KERNEL);
if (!d->main_status_buf) goto err_alloc;
}
d->status_buf = kcalloc(chip->num_regs, sizeof(*d->status_buf),
GFP_KERNEL); if (!d->status_buf) goto err_alloc;
if (chip->status_is_level) {
d->prev_status_buf = kcalloc(chip->num_regs, sizeof(*d->prev_status_buf),
GFP_KERNEL); if (!d->prev_status_buf) goto err_alloc;
}
d->mask_buf = kcalloc(chip->num_regs, sizeof(*d->mask_buf),
GFP_KERNEL); if (!d->mask_buf) goto err_alloc;
d->mask_buf_def = kcalloc(chip->num_regs, sizeof(*d->mask_buf_def),
GFP_KERNEL); if (!d->mask_buf_def) goto err_alloc;
if (chip->wake_base) {
d->wake_buf = kcalloc(chip->num_regs, sizeof(*d->wake_buf),
GFP_KERNEL); if (!d->wake_buf) goto err_alloc;
}
if (chip->type_in_mask) {
d->type_buf_def = kcalloc(chip->num_regs, sizeof(*d->type_buf_def), GFP_KERNEL); if (!d->type_buf_def) goto err_alloc;
d->type_buf = kcalloc(chip->num_regs, sizeof(*d->type_buf), GFP_KERNEL); if (!d->type_buf) goto err_alloc;
}
if (chip->num_config_bases && chip->num_config_regs) { /* * Create config_buf[num_config_bases][num_config_regs]
*/
d->config_buf = kcalloc(chip->num_config_bases, sizeof(*d->config_buf), GFP_KERNEL); if (!d->config_buf) goto err_alloc;
for (i = 0; i < chip->num_config_bases; i++) {
d->config_buf[i] = kcalloc(chip->num_config_regs, sizeof(**d->config_buf),
GFP_KERNEL); if (!d->config_buf[i]) goto err_alloc;
}
}
if (chip->irq_reg_stride)
d->irq_reg_stride = chip->irq_reg_stride; else
d->irq_reg_stride = 1;
if (chip->get_irq_reg)
d->get_irq_reg = chip->get_irq_reg; else
d->get_irq_reg = regmap_irq_get_irq_reg_linear;
if (regmap_irq_can_bulk_read_status(d)) {
d->status_reg_buf = kmalloc_array(chip->num_regs,
map->format.val_bytes,
GFP_KERNEL); if (!d->status_reg_buf) goto err_alloc;
}
/* * If one regmap-irq is the parent of another then we'll try * to lock the child with the parent locked, use an explicit * lock_key so lockdep can figure out what's going on.
*/
lockdep_register_key(&d->lock_key);
mutex_init_with_key(&d->lock, &d->lock_key);
for (i = 0; i < chip->num_irqs; i++)
d->mask_buf_def[chip->irqs[i].reg_offset / map->reg_stride]
|= chip->irqs[i].mask;
/* Mask all the interrupts by default */ for (i = 0; i < chip->num_regs; i++) {
d->mask_buf[i] = d->mask_buf_def[i];
if (chip->handle_mask_sync) {
ret = chip->handle_mask_sync(i, d->mask_buf_def[i],
d->mask_buf[i],
chip->irq_drv_data); if (ret) goto err_mutex;
}
if (chip->mask_base && !chip->handle_mask_sync) {
reg = d->get_irq_reg(d, chip->mask_base, i);
ret = regmap_update_bits(d->map, reg,
d->mask_buf_def[i],
d->mask_buf[i]); if (ret) {
dev_err(map->dev, "Failed to set masks in 0x%x: %d\n",
reg, ret); goto err_mutex;
}
}
if (chip->unmask_base && !chip->handle_mask_sync) {
reg = d->get_irq_reg(d, chip->unmask_base, i);
ret = regmap_update_bits(d->map, reg,
d->mask_buf_def[i], ~d->mask_buf[i]); if (ret) {
dev_err(map->dev, "Failed to set masks in 0x%x: %d\n",
reg, ret); goto err_mutex;
}
}
if (!chip->init_ack_masked) continue;
/* Ack masked but set interrupts */ if (d->chip->no_status) { /* no status register so default to all active */
d->status_buf[i] = UINT_MAX;
} else {
reg = d->get_irq_reg(d, d->chip->status_base, i);
ret = regmap_read(map, reg, &d->status_buf[i]); if (ret != 0) {
dev_err(map->dev, "Failed to read IRQ status: %d\n",
ret); goto err_mutex;
}
}
if (chip->status_invert)
d->status_buf[i] = ~d->status_buf[i];
if (d->status_buf[i] && (chip->ack_base || chip->use_ack)) {
reg = d->get_irq_reg(d, d->chip->ack_base, i); if (chip->ack_invert)
ret = regmap_write(map, reg,
~(d->status_buf[i] & d->mask_buf[i])); else
ret = regmap_write(map, reg,
d->status_buf[i] & d->mask_buf[i]); if (chip->clear_ack) { if (chip->ack_invert && !ret)
ret = regmap_write(map, reg, UINT_MAX); elseif (!ret)
ret = regmap_write(map, reg, 0);
} if (ret != 0) {
dev_err(map->dev, "Failed to ack 0x%x: %d\n",
reg, ret); goto err_mutex;
}
}
}
/* Wake is disabled by default */ if (d->wake_buf) { for (i = 0; i < chip->num_regs; i++) {
d->wake_buf[i] = d->mask_buf_def[i];
reg = d->get_irq_reg(d, d->chip->wake_base, i);
if (chip->wake_invert)
ret = regmap_update_bits(d->map, reg,
d->mask_buf_def[i],
0); else
ret = regmap_update_bits(d->map, reg,
d->mask_buf_def[i],
d->wake_buf[i]); if (ret != 0) {
dev_err(map->dev, "Failed to set masks in 0x%x: %d\n",
reg, ret); goto err_mutex;
}
}
}
/* Store current levels */ if (chip->status_is_level) {
ret = read_irq_data(d); if (ret < 0) goto err_mutex;
ret = regmap_irq_create_domain(fwnode, irq_base, chip, d); if (ret) goto err_mutex;
ret = request_threaded_irq(irq, NULL, regmap_irq_thread,
irq_flags | IRQF_ONESHOT,
chip->name, d); if (ret != 0) {
dev_err(map->dev, "Failed to request IRQ %d for %s: %d\n",
irq, chip->name, ret); goto err_domain;
}
*data = d;
return 0;
err_domain: /* Should really dispose of the domain but... */
err_mutex:
mutex_destroy(&d->lock);
lockdep_unregister_key(&d->lock_key);
err_alloc:
kfree(d->type_buf);
kfree(d->type_buf_def);
kfree(d->wake_buf);
kfree(d->mask_buf_def);
kfree(d->mask_buf);
kfree(d->main_status_buf);
kfree(d->status_buf);
kfree(d->prev_status_buf);
kfree(d->status_reg_buf); if (d->config_buf) { for (i = 0; i < chip->num_config_bases; i++)
kfree(d->config_buf[i]);
kfree(d->config_buf);
}
kfree(d); return ret;
}
EXPORT_SYMBOL_GPL(regmap_add_irq_chip_fwnode);
/** * regmap_add_irq_chip() - Use standard regmap IRQ controller handling * * @map: The regmap for the device. * @irq: The IRQ the device uses to signal interrupts. * @irq_flags: The IRQF_ flags to use for the primary interrupt. * @irq_base: Allocate at specific IRQ number if irq_base > 0. * @chip: Configuration for the interrupt controller. * @data: Runtime data structure for the controller, allocated on success. * * Returns 0 on success or an errno on failure. * * This is the same as regmap_add_irq_chip_fwnode, except that the firmware * node of the regmap is used.
*/ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags, int irq_base, conststruct regmap_irq_chip *chip, struct regmap_irq_chip_data **data)
{ return regmap_add_irq_chip_fwnode(dev_fwnode(map->dev), map, irq,
irq_flags, irq_base, chip, data);
}
EXPORT_SYMBOL_GPL(regmap_add_irq_chip);
/** * regmap_del_irq_chip() - Stop interrupt handling for a regmap IRQ chip * * @irq: Primary IRQ for the device * @d: ®map_irq_chip_data allocated by regmap_add_irq_chip() * * This function also disposes of all mapped IRQs on the chip.
*/ void regmap_del_irq_chip(int irq, struct regmap_irq_chip_data *d)
{ unsignedint virq; int i, hwirq;
if (!d) return;
free_irq(irq, d);
/* Dispose all virtual irq from irq domain before removing it */ for (hwirq = 0; hwirq < d->chip->num_irqs; hwirq++) { /* Ignore hwirq if holes in the IRQ list */ if (!d->chip->irqs[hwirq].mask) continue;
/* * Find the virtual irq of hwirq on chip and if it is * there then dispose it
*/
virq = irq_find_mapping(d->domain, hwirq); if (virq)
irq_dispose_mapping(virq);
}
irq_domain_remove(d->domain);
kfree(d->type_buf);
kfree(d->type_buf_def);
kfree(d->wake_buf);
kfree(d->mask_buf_def);
kfree(d->mask_buf);
kfree(d->main_status_buf);
kfree(d->status_reg_buf);
kfree(d->status_buf);
kfree(d->prev_status_buf); if (d->config_buf) { for (i = 0; i < d->chip->num_config_bases; i++)
kfree(d->config_buf[i]);
kfree(d->config_buf);
}
mutex_destroy(&d->lock);
lockdep_unregister_key(&d->lock_key);
kfree(d);
}
EXPORT_SYMBOL_GPL(regmap_del_irq_chip);
/** * devm_regmap_add_irq_chip_fwnode() - Resource managed regmap_add_irq_chip_fwnode() * * @dev: The device pointer on which irq_chip belongs to. * @fwnode: The firmware node where the IRQ domain should be added to. * @map: The regmap for the device. * @irq: The IRQ the device uses to signal interrupts * @irq_flags: The IRQF_ flags to use for the primary interrupt. * @irq_base: Allocate at specific IRQ number if irq_base > 0. * @chip: Configuration for the interrupt controller. * @data: Runtime data structure for the controller, allocated on success * * Returns 0 on success or an errno on failure. * * The ®map_irq_chip_data will be automatically released when the device is * unbound.
*/ int devm_regmap_add_irq_chip_fwnode(struct device *dev, struct fwnode_handle *fwnode, struct regmap *map, int irq, int irq_flags, int irq_base, conststruct regmap_irq_chip *chip, struct regmap_irq_chip_data **data)
{ struct regmap_irq_chip_data **ptr, *d; int ret;
ptr = devres_alloc(devm_regmap_irq_chip_release, sizeof(*ptr),
GFP_KERNEL); if (!ptr) return -ENOMEM;
ret = regmap_add_irq_chip_fwnode(fwnode, map, irq, irq_flags, irq_base,
chip, &d); if (ret < 0) {
devres_free(ptr); return ret;
}
/** * devm_regmap_add_irq_chip() - Resource managed regmap_add_irq_chip() * * @dev: The device pointer on which irq_chip belongs to. * @map: The regmap for the device. * @irq: The IRQ the device uses to signal interrupts * @irq_flags: The IRQF_ flags to use for the primary interrupt. * @irq_base: Allocate at specific IRQ number if irq_base > 0. * @chip: Configuration for the interrupt controller. * @data: Runtime data structure for the controller, allocated on success * * Returns 0 on success or an errno on failure. * * The ®map_irq_chip_data will be automatically released when the device is * unbound.
*/ int devm_regmap_add_irq_chip(struct device *dev, struct regmap *map, int irq, int irq_flags, int irq_base, conststruct regmap_irq_chip *chip, struct regmap_irq_chip_data **data)
{ return devm_regmap_add_irq_chip_fwnode(dev, dev_fwnode(map->dev), map,
irq, irq_flags, irq_base, chip,
data);
}
EXPORT_SYMBOL_GPL(devm_regmap_add_irq_chip);
/** * devm_regmap_del_irq_chip() - Resource managed regmap_del_irq_chip() * * @dev: Device for which the resource was allocated. * @irq: Primary IRQ for the device. * @data: ®map_irq_chip_data allocated by regmap_add_irq_chip(). * * A resource managed version of regmap_del_irq_chip().
*/ void devm_regmap_del_irq_chip(struct device *dev, int irq, struct regmap_irq_chip_data *data)
{ int rc;
if (rc != 0)
WARN_ON(rc);
}
EXPORT_SYMBOL_GPL(devm_regmap_del_irq_chip);
/** * regmap_irq_chip_get_base() - Retrieve interrupt base for a regmap IRQ chip * * @data: regmap irq controller to operate on. * * Useful for drivers to request their own IRQs.
*/ int regmap_irq_chip_get_base(struct regmap_irq_chip_data *data)
{
WARN_ON(!data->irq_base); return data->irq_base;
}
EXPORT_SYMBOL_GPL(regmap_irq_chip_get_base);
/** * regmap_irq_get_virq() - Map an interrupt on a chip to a virtual IRQ * * @data: regmap irq controller to operate on. * @irq: index of the interrupt requested in the chip IRQs. * * Useful for drivers to request their own IRQs.
*/ int regmap_irq_get_virq(struct regmap_irq_chip_data *data, int irq)
{ /* Handle holes in the IRQ list */ if (!data->chip->irqs[irq].mask) return -EINVAL;
/** * regmap_irq_get_domain() - Retrieve the irq_domain for the chip * * @data: regmap_irq controller to operate on. * * Useful for drivers to request their own IRQs and for integration * with subsystems. For ease of integration NULL is accepted as a * domain, allowing devices to just call this even if no domain is * allocated.
*/ struct irq_domain *regmap_irq_get_domain(struct regmap_irq_chip_data *data)
{ if (data) return data->domain; else return NULL;
}
EXPORT_SYMBOL_GPL(regmap_irq_get_domain);
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.