/* * struct sc27xx_fgu_data: describe the FGU device * @regmap: regmap for register access * @dev: platform device * @battery: battery power supply * @base: the base offset for the controller * @lock: protect the structure * @gpiod: GPIO for battery detection * @channel: IIO channel to get battery temperature * @charge_chan: IIO channel to get charge voltage * @internal_resist: the battery internal resistance in mOhm * @total_cap: the total capacity of the battery in mAh * @init_cap: the initial capacity of the battery in mAh * @alarm_cap: the alarm capacity * @init_clbcnt: the initial coulomb counter * @max_volt: the maximum constant input voltage in millivolt * @min_volt: the minimum drained battery voltage in microvolt * @boot_volt: the voltage measured during boot in microvolt * @table_len: the capacity table length * @resist_table_len: the resistance table length * @cur_1000ma_adc: ADC value corresponding to 1000 mA * @vol_1000mv_adc: ADC value corresponding to 1000 mV * @calib_resist: the real resistance of coulomb counter chip in uOhm * @cap_table: capacity table with corresponding ocv * @resist_table: resistance percent table with corresponding temperature
*/ struct sc27xx_fgu_data { struct regmap *regmap; struct device *dev; struct power_supply *battery;
u32 base; struct mutex lock; struct gpio_desc *gpiod; struct iio_channel *channel; struct iio_channel *charge_chan; bool bat_present; int internal_resist; int total_cap; int init_cap; int alarm_cap; int init_clbcnt; int max_volt; int min_volt; int boot_volt; int table_len; int resist_table_len; int cur_1000ma_adc; int vol_1000mv_adc; int calib_resist; struct power_supply_battery_ocv_table *cap_table; struct power_supply_resistance_temp_table *resist_table;
};
staticint sc27xx_fgu_cap_to_clbcnt(struct sc27xx_fgu_data *data, int capacity); staticvoid sc27xx_fgu_capacity_calibration(struct sc27xx_fgu_data *data, int cap, bool int_mode); staticvoid sc27xx_fgu_adjust_cap(struct sc27xx_fgu_data *data, int cap); staticint sc27xx_fgu_get_temp(struct sc27xx_fgu_data *data, int *temp);
staticbool sc27xx_fgu_is_first_poweron(struct sc27xx_fgu_data *data)
{ int ret, status, cap, mode;
ret = regmap_read(data->regmap,
data->base + SC27XX_FGU_USER_AREA_STATUS, &status); if (ret) returnfalse;
/* * We use low 4 bits to save the last battery capacity and high 12 bits * to save the system boot mode.
*/
mode = (status & SC27XX_FGU_MODE_AREA_MASK) >> SC27XX_FGU_MODE_AREA_SHIFT;
cap = status & SC27XX_FGU_CAP_AREA_MASK;
/* * When FGU has been powered down, the user area registers became * default value (0xffff), which can be used to valid if the system is * first power on or not.
*/ if (mode == SC27XX_FGU_FIRST_POWERTON || cap == SC27XX_FGU_DEFAULT_CAP) returntrue;
returnfalse;
}
staticint sc27xx_fgu_save_boot_mode(struct sc27xx_fgu_data *data, int boot_mode)
{ int ret;
ret = regmap_update_bits(data->regmap,
data->base + SC27XX_FGU_USER_AREA_CLEAR,
SC27XX_FGU_MODE_AREA_MASK,
SC27XX_FGU_MODE_AREA_MASK); if (ret) return ret;
/* * Since the user area registers are put on power always-on region, * then these registers changing time will be a little long. Thus * here we should delay 200us to wait until values are updated * successfully according to the datasheet.
*/
udelay(200);
ret = regmap_update_bits(data->regmap,
data->base + SC27XX_FGU_USER_AREA_SET,
SC27XX_FGU_MODE_AREA_MASK,
boot_mode << SC27XX_FGU_MODE_AREA_SHIFT); if (ret) return ret;
/* * Since the user area registers are put on power always-on region, * then these registers changing time will be a little long. Thus * here we should delay 200us to wait until values are updated * successfully according to the datasheet.
*/
udelay(200);
/* * According to the datasheet, we should set the USER_AREA_CLEAR to 0 to * make the user area data available, otherwise we can not save the user * area data.
*/ return regmap_update_bits(data->regmap,
data->base + SC27XX_FGU_USER_AREA_CLEAR,
SC27XX_FGU_MODE_AREA_MASK, 0);
}
staticint sc27xx_fgu_save_last_cap(struct sc27xx_fgu_data *data, int cap)
{ int ret;
ret = regmap_update_bits(data->regmap,
data->base + SC27XX_FGU_USER_AREA_CLEAR,
SC27XX_FGU_CAP_AREA_MASK,
SC27XX_FGU_CAP_AREA_MASK); if (ret) return ret;
/* * Since the user area registers are put on power always-on region, * then these registers changing time will be a little long. Thus * here we should delay 200us to wait until values are updated * successfully according to the datasheet.
*/
udelay(200);
ret = regmap_update_bits(data->regmap,
data->base + SC27XX_FGU_USER_AREA_SET,
SC27XX_FGU_CAP_AREA_MASK, cap); if (ret) return ret;
/* * Since the user area registers are put on power always-on region, * then these registers changing time will be a little long. Thus * here we should delay 200us to wait until values are updated * successfully according to the datasheet.
*/
udelay(200);
/* * According to the datasheet, we should set the USER_AREA_CLEAR to 0 to * make the user area data available, otherwise we can not save the user * area data.
*/ return regmap_update_bits(data->regmap,
data->base + SC27XX_FGU_USER_AREA_CLEAR,
SC27XX_FGU_CAP_AREA_MASK, 0);
}
staticint sc27xx_fgu_read_last_cap(struct sc27xx_fgu_data *data, int *cap)
{ int ret, value;
ret = regmap_read(data->regmap,
data->base + SC27XX_FGU_USER_AREA_STATUS, &value); if (ret) return ret;
*cap = value & SC27XX_FGU_CAP_AREA_MASK; return 0;
}
/* * When system boots on, we can not read battery capacity from coulomb * registers, since now the coulomb registers are invalid. So we should * calculate the battery open circuit voltage, and get current battery * capacity according to the capacity table.
*/ staticint sc27xx_fgu_get_boot_capacity(struct sc27xx_fgu_data *data, int *cap)
{ int volt, cur, oci, ocv, ret; bool is_first_poweron = sc27xx_fgu_is_first_poweron(data);
/* * If system is not the first power on, we should use the last saved * battery capacity as the initial battery capacity. Otherwise we should * re-calculate the initial battery capacity.
*/ if (!is_first_poweron) {
ret = sc27xx_fgu_read_last_cap(data, cap); if (ret) return ret;
/* * After system booting on, the SC27XX_FGU_CLBCNT_QMAXL register saved * the first sampled open circuit current.
*/
ret = regmap_read(data->regmap, data->base + SC27XX_FGU_CLBCNT_QMAXL,
&cur); if (ret) return ret;
cur <<= 1;
oci = sc27xx_fgu_adc_to_current(data, cur - SC27XX_FGU_CUR_BASIC_ADC);
/* * Should get the OCV from SC27XX_FGU_POCV register at the system * beginning. It is ADC values reading from registers which need to * convert the corresponding voltage.
*/
ret = regmap_read(data->regmap, data->base + SC27XX_FGU_POCV, &volt); if (ret) return ret;
volt = sc27xx_fgu_adc_to_voltage(data, volt);
ocv = volt * 1000 - oci * data->internal_resist;
data->boot_volt = ocv;
/* * Parse the capacity table to look up the correct capacity percent * according to current battery's corresponding OCV values.
*/
*cap = power_supply_ocv2cap_simple(data->cap_table, data->table_len,
ocv);
ret = sc27xx_fgu_save_last_cap(data, *cap); if (ret) return ret;
staticint sc27xx_fgu_get_vol_now(struct sc27xx_fgu_data *data, int *val)
{ int ret;
u32 vol;
ret = regmap_read(data->regmap, data->base + SC27XX_FGU_VOLTAGE_BUF,
&vol); if (ret) return ret;
/* * It is ADC values reading from registers which need to convert to * corresponding voltage values.
*/
*val = sc27xx_fgu_adc_to_voltage(data, vol);
return 0;
}
staticint sc27xx_fgu_get_cur_now(struct sc27xx_fgu_data *data, int *val)
{ int ret;
u32 cur;
ret = regmap_read(data->regmap, data->base + SC27XX_FGU_CURRENT_BUF,
&cur); if (ret) return ret;
/* * It is ADC values reading from registers which need to convert to * corresponding current values.
*/
*val = sc27xx_fgu_adc_to_current(data, cur - SC27XX_FGU_CUR_BASIC_ADC);
return 0;
}
staticint sc27xx_fgu_get_capacity(struct sc27xx_fgu_data *data, int *cap)
{ int ret, cur_clbcnt, delta_clbcnt, delta_cap, temp;
/* Get current coulomb counters firstly */
ret = sc27xx_fgu_get_clbcnt(data, &cur_clbcnt); if (ret) return ret;
delta_clbcnt = cur_clbcnt - data->init_clbcnt;
/* * Convert coulomb counter to delta capacity (mAh), and set multiplier * as 10 to improve the precision.
*/
temp = DIV_ROUND_CLOSEST(delta_clbcnt * 10, 36 * SC27XX_FGU_SAMPLE_HZ);
temp = sc27xx_fgu_adc_to_current(data, temp / 1000);
/* * Convert to capacity percent of the battery total capacity, * and multiplier is 100 too.
*/
delta_cap = DIV_ROUND_CLOSEST(temp * 100, data->total_cap);
*cap = delta_cap + data->init_cap;
/* Calibrate the battery capacity in a normal range. */
sc27xx_fgu_capacity_calibration(data, *cap, false);
return 0;
}
staticint sc27xx_fgu_get_vbat_vol(struct sc27xx_fgu_data *data, int *val)
{ int ret, vol;
ret = regmap_read(data->regmap, data->base + SC27XX_FGU_VOLTAGE, &vol); if (ret) return ret;
/* * It is ADC values reading from registers which need to convert to * corresponding voltage values.
*/
*val = sc27xx_fgu_adc_to_voltage(data, vol);
return 0;
}
staticint sc27xx_fgu_get_current(struct sc27xx_fgu_data *data, int *val)
{ int ret, cur;
ret = regmap_read(data->regmap, data->base + SC27XX_FGU_CURRENT, &cur); if (ret) return ret;
/* * It is ADC values reading from registers which need to convert to * corresponding current values.
*/
*val = sc27xx_fgu_adc_to_current(data, cur - SC27XX_FGU_CUR_BASIC_ADC);
return 0;
}
staticint sc27xx_fgu_get_vbat_ocv(struct sc27xx_fgu_data *data, int *val)
{ int vol, cur, ret, temp, resistance;
ret = sc27xx_fgu_get_vbat_vol(data, &vol); if (ret) return ret;
ret = sc27xx_fgu_get_current(data, &cur); if (ret) return ret;
resistance = data->internal_resist; if (data->resist_table_len > 0) {
ret = sc27xx_fgu_get_temp(data, &temp); if (ret) return ret;
staticint sc27xx_fgu_get_status(struct sc27xx_fgu_data *data, int *status)
{ union power_supply_propval val; struct power_supply *psy; int i, ret = -EINVAL;
for (i = 0; i < ARRAY_SIZE(sc27xx_charger_supply_name); i++) {
psy = power_supply_get_by_name(sc27xx_charger_supply_name[i]); if (!psy) continue;
ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS,
&val);
power_supply_put(psy); if (ret) return ret;
*status = val.intval;
}
return ret;
}
staticint sc27xx_fgu_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val)
{ struct sc27xx_fgu_data *data = power_supply_get_drvdata(psy); int ret = 0; int value;
mutex_lock(&data->lock);
switch (psp) { case POWER_SUPPLY_PROP_STATUS:
ret = sc27xx_fgu_get_status(data, &value); if (ret) goto error;
val->intval = value; break;
case POWER_SUPPLY_PROP_HEALTH:
ret = sc27xx_fgu_get_health(data, &value); if (ret) goto error;
val->intval = value; break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = data->bat_present; break;
case POWER_SUPPLY_PROP_TEMP:
ret = sc27xx_fgu_get_temp(data, &value); if (ret) goto error;
val->intval = value; break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break;
case POWER_SUPPLY_PROP_CAPACITY:
ret = sc27xx_fgu_get_capacity(data, &value); if (ret) goto error;
val->intval = value; break;
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
ret = sc27xx_fgu_get_vbat_vol(data, &value); if (ret) goto error;
val->intval = value * 1000; break;
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
ret = sc27xx_fgu_get_vbat_ocv(data, &value); if (ret) goto error;
val->intval = value; break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = sc27xx_fgu_get_charge_vol(data, &value); if (ret) goto error;
val->intval = value; break;
case POWER_SUPPLY_PROP_CURRENT_AVG:
ret = sc27xx_fgu_get_current(data, &value); if (ret) goto error;
val->intval = value * 1000; break;
case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
val->intval = data->total_cap * 1000; break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
ret = sc27xx_fgu_get_clbcnt(data, &value); if (ret) goto error;
switch (psp) { case POWER_SUPPLY_PROP_CAPACITY:
ret = sc27xx_fgu_save_last_cap(data, val->intval); if (ret < 0)
dev_err(data->dev, "failed to save battery capacity\n"); break;
case POWER_SUPPLY_PROP_CALIBRATE:
sc27xx_fgu_adjust_cap(data, val->intval);
ret = 0; break;
case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
data->total_cap = val->intval / 1000;
ret = 0; break;
staticvoid sc27xx_fgu_adjust_cap(struct sc27xx_fgu_data *data, int cap)
{ int ret;
data->init_cap = cap;
ret = sc27xx_fgu_get_clbcnt(data, &data->init_clbcnt); if (ret)
dev_err(data->dev, "failed to get init coulomb counter\n");
}
staticvoid sc27xx_fgu_capacity_calibration(struct sc27xx_fgu_data *data, int cap, bool int_mode)
{ int ret, ocv, chg_sts, adc;
ret = sc27xx_fgu_get_vbat_ocv(data, &ocv); if (ret) {
dev_err(data->dev, "get battery ocv error.\n"); return;
}
ret = sc27xx_fgu_get_status(data, &chg_sts); if (ret) {
dev_err(data->dev, "get charger status error.\n"); return;
}
/* * If we are in charging mode, then we do not need to calibrate the * lower capacity.
*/ if (chg_sts == POWER_SUPPLY_STATUS_CHARGING) return;
if ((ocv > data->cap_table[0].ocv && cap < 100) || cap > 100) { /* * If current OCV value is larger than the max OCV value in * OCV table, or the current capacity is larger than 100, * we should force the inititial capacity to 100.
*/
sc27xx_fgu_adjust_cap(data, 100);
} elseif (ocv <= data->cap_table[data->table_len - 1].ocv) { /* * If current OCV value is leass than the minimum OCV value in * OCV table, we should force the inititial capacity to 0.
*/
sc27xx_fgu_adjust_cap(data, 0);
} elseif ((ocv > data->cap_table[data->table_len - 1].ocv && cap <= 0) ||
(ocv > data->min_volt && cap <= data->alarm_cap)) { /* * If current OCV value is not matchable with current capacity, * we should re-calculate current capacity by looking up the * OCV table.
*/ int cur_cap = power_supply_ocv2cap_simple(data->cap_table,
data->table_len, ocv);
sc27xx_fgu_adjust_cap(data, cur_cap);
} elseif (ocv <= data->min_volt) { /* * If current OCV value is less than the low alarm voltage, but * current capacity is larger than the alarm capacity, we should * adjust the inititial capacity to alarm capacity.
*/ if (cap > data->alarm_cap) {
sc27xx_fgu_adjust_cap(data, data->alarm_cap);
} else { int cur_cap;
/* * If current capacity is equal with 0 or less than 0 * (some error occurs), we should adjust inititial * capacity to the capacity corresponding to current OCV * value.
*/
cur_cap = power_supply_ocv2cap_simple(data->cap_table,
data->table_len,
ocv);
sc27xx_fgu_adjust_cap(data, cur_cap);
}
if (!int_mode) return;
/* * After adjusting the battery capacity, we should set the * lowest alarm voltage instead.
*/
data->min_volt = data->cap_table[data->table_len - 1].ocv;
data->alarm_cap = power_supply_ocv2cap_simple(data->cap_table,
data->table_len,
data->min_volt);
ret = regmap_read(data->regmap, data->base + SC27XX_FGU_INT_STS,
&status); if (ret) goto out;
ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_INT_CLR,
status, status); if (ret) goto out;
/* * When low overload voltage interrupt happens, we should calibrate the * battery capacity in lower voltage stage.
*/ if (!(status & SC27XX_FGU_LOW_OVERLOAD_INT)) goto out;
ret = sc27xx_fgu_get_capacity(data, &cap); if (ret) goto out;
state = gpiod_get_value_cansleep(data->gpiod); if (state < 0) {
dev_err(data->dev, "failed to get gpio state\n");
mutex_unlock(&data->lock); return IRQ_RETVAL(state);
}
staticint sc27xx_fgu_cap_to_clbcnt(struct sc27xx_fgu_data *data, int capacity)
{ /* * Get current capacity (mAh) = battery total capacity (mAh) * * current capacity percent (capacity / 100).
*/ int cur_cap = DIV_ROUND_CLOSEST(data->total_cap * capacity, 100);
/* * Convert current capacity (mAh) to coulomb counter according to the * formula: 1 mAh =3.6 coulomb.
*/ return DIV_ROUND_CLOSEST(cur_cap * 36 * data->cur_1000ma_adc * SC27XX_FGU_SAMPLE_HZ, 10);
}
/* * For SC27XX fuel gauge device, we only use one ocv-capacity * table in normal temperature 20 Celsius.
*/
table = power_supply_find_ocv2cap_table(info, 20, &data->table_len); if (!table) return -EINVAL;
ret = sc27xx_fgu_calibration(data); if (ret) return ret;
/* Enable the FGU module */
ret = regmap_update_bits(data->regmap, SC27XX_MODULE_EN0,
SC27XX_FGU_EN, SC27XX_FGU_EN); if (ret) {
dev_err(data->dev, "failed to enable fgu\n"); return ret;
}
/* Enable the FGU RTC clock to make it work */
ret = regmap_update_bits(data->regmap, SC27XX_CLK_EN0,
SC27XX_FGU_RTC_EN, SC27XX_FGU_RTC_EN); if (ret) {
dev_err(data->dev, "failed to enable fgu RTC clock\n"); goto disable_fgu;
}
ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_INT_CLR,
SC27XX_FGU_INT_MASK, SC27XX_FGU_INT_MASK); if (ret) {
dev_err(data->dev, "failed to clear interrupt status\n"); goto disable_clk;
}
/* * Set the voltage low overload threshold, which means when the battery * voltage is lower than this threshold, the controller will generate * one interrupt to notify.
*/
alarm_adc = sc27xx_fgu_voltage_to_adc(data, data->min_volt / 1000);
ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_LOW_OVERLOAD,
SC27XX_FGU_LOW_OVERLOAD_MASK, alarm_adc); if (ret) {
dev_err(data->dev, "failed to set fgu low overload\n"); goto disable_clk;
}
/* * Set the coulomb counter delta threshold, that means when the coulomb * counter change is multiples of the delta threshold, the controller * will generate one interrupt to notify the users to update the battery * capacity. Now we set the delta threshold as a counter value of 1% * capacity.
*/
delta_clbcnt = sc27xx_fgu_cap_to_clbcnt(data, 1);
ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_CLBCNT_DELTL,
SC27XX_FGU_CLBCNT_MASK, delta_clbcnt); if (ret) {
dev_err(data->dev, "failed to set low delta coulomb counter\n"); goto disable_clk;
}
ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_CLBCNT_DELTH,
SC27XX_FGU_CLBCNT_MASK,
delta_clbcnt >> SC27XX_FGU_CLBCNT_SHIFT); if (ret) {
dev_err(data->dev, "failed to set high delta coulomb counter\n"); goto disable_clk;
}
/* * Get the boot battery capacity when system powers on, which is used to * initialize the coulomb counter. After that, we can read the coulomb * counter to measure the battery capacity.
*/
ret = sc27xx_fgu_get_boot_capacity(data, &data->init_cap); if (ret) {
dev_err(data->dev, "failed to get boot capacity\n"); goto disable_clk;
}
/* * Convert battery capacity to the corresponding initial coulomb counter * and set into coulomb counter registers.
*/
data->init_clbcnt = sc27xx_fgu_cap_to_clbcnt(data, data->init_cap);
ret = sc27xx_fgu_set_clbcnt(data, data->init_clbcnt); if (ret) {
dev_err(data->dev, "failed to initialize coulomb counter\n"); goto disable_clk;
}
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM;
data->regmap = dev_get_regmap(dev->parent, NULL); if (!data->regmap) {
dev_err(dev, "failed to get regmap\n"); return -ENODEV;
}
ret = device_property_read_u32(dev, "reg", &data->base); if (ret) {
dev_err(dev, "failed to get fgu address\n"); return ret;
}
ret = device_property_read_u32(&pdev->dev, "sprd,calib-resistance-micro-ohms",
&data->calib_resist); if (ret) {
dev_err(&pdev->dev, "failed to get fgu calibration resistance\n"); return ret;
}
data->channel = devm_iio_channel_get(dev, "bat-temp"); if (IS_ERR(data->channel)) {
dev_err(dev, "failed to get IIO channel\n"); return PTR_ERR(data->channel);
}
data->charge_chan = devm_iio_channel_get(dev, "charge-vol"); if (IS_ERR(data->charge_chan)) {
dev_err(dev, "failed to get charge IIO channel\n"); return PTR_ERR(data->charge_chan);
}
data->gpiod = devm_gpiod_get(dev, "battery-detect", GPIOD_IN); if (IS_ERR(data->gpiod)) {
data->gpiod = devm_gpiod_get(dev, "bat-detect", GPIOD_IN); if (IS_ERR(data->gpiod)) {
dev_err(dev, "failed to get battery detection GPIO\n"); return PTR_ERR(data->gpiod);
}
dev_warn(dev, "bat-detect is deprecated, please use battery-detect\n");
}
ret = gpiod_get_value_cansleep(data->gpiod); if (ret < 0) {
dev_err(dev, "failed to get gpio state\n"); return ret;
}
ret = sc27xx_fgu_get_status(data, &status); if (ret) return ret;
/* * If we are charging, then no need to enable the FGU interrupts to * adjust the battery capacity.
*/ if (status != POWER_SUPPLY_STATUS_NOT_CHARGING &&
status != POWER_SUPPLY_STATUS_DISCHARGING) return 0;
ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_INT_EN,
SC27XX_FGU_LOW_OVERLOAD_INT,
SC27XX_FGU_LOW_OVERLOAD_INT); if (ret) {
dev_err(data->dev, "failed to enable low voltage interrupt\n"); return ret;
}
ret = sc27xx_fgu_get_vbat_ocv(data, &ocv); if (ret) goto disable_int;
/* * If current OCV is less than the minimum voltage, we should enable the * coulomb counter threshold interrupt to notify events to adjust the * battery capacity.
*/ if (ocv < data->min_volt) {
ret = regmap_update_bits(data->regmap,
data->base + SC27XX_FGU_INT_EN,
SC27XX_FGU_CLBCNT_DELTA_INT,
SC27XX_FGU_CLBCNT_DELTA_INT); if (ret) {
dev_err(data->dev, "failed to enable coulomb threshold int\n"); goto disable_int;
}
}
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.