Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Stefan Popa | 2931 | 99.19% | 1 | 20.00% |
Wei Yongjun | 16 | 0.54% | 2 | 40.00% |
Dan Carpenter | 6 | 0.20% | 1 | 20.00% |
Thomas Gleixner | 2 | 0.07% | 1 | 20.00% |
Total | 2955 | 5 |
// SPDX-License-Identifier: GPL-2.0-only /* * ADP5061 I2C Programmable Linear Battery Charger * * Copyright 2018 Analog Devices Inc. */ #include <linux/init.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/i2c.h> #include <linux/delay.h> #include <linux/pm.h> #include <linux/mod_devicetable.h> #include <linux/power_supply.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/regmap.h> /* ADP5061 registers definition */ #define ADP5061_ID 0x00 #define ADP5061_REV 0x01 #define ADP5061_VINX_SET 0x02 #define ADP5061_TERM_SET 0x03 #define ADP5061_CHG_CURR 0x04 #define ADP5061_VOLTAGE_TH 0x05 #define ADP5061_TIMER_SET 0x06 #define ADP5061_FUNC_SET_1 0x07 #define ADP5061_FUNC_SET_2 0x08 #define ADP5061_INT_EN 0x09 #define ADP5061_INT_ACT 0x0A #define ADP5061_CHG_STATUS_1 0x0B #define ADP5061_CHG_STATUS_2 0x0C #define ADP5061_FAULT 0x0D #define ADP5061_BATTERY_SHORT 0x10 #define ADP5061_IEND 0x11 /* ADP5061_VINX_SET */ #define ADP5061_VINX_SET_ILIM_MSK GENMASK(3, 0) #define ADP5061_VINX_SET_ILIM_MODE(x) (((x) & 0x0F) << 0) /* ADP5061_TERM_SET */ #define ADP5061_TERM_SET_VTRM_MSK GENMASK(7, 2) #define ADP5061_TERM_SET_VTRM_MODE(x) (((x) & 0x3F) << 2) #define ADP5061_TERM_SET_CHG_VLIM_MSK GENMASK(1, 0) #define ADP5061_TERM_SET_CHG_VLIM_MODE(x) (((x) & 0x03) << 0) /* ADP5061_CHG_CURR */ #define ADP5061_CHG_CURR_ICHG_MSK GENMASK(6, 2) #define ADP5061_CHG_CURR_ICHG_MODE(x) (((x) & 0x1F) << 2) #define ADP5061_CHG_CURR_ITRK_DEAD_MSK GENMASK(1, 0) #define ADP5061_CHG_CURR_ITRK_DEAD_MODE(x) (((x) & 0x03) << 0) /* ADP5061_VOLTAGE_TH */ #define ADP5061_VOLTAGE_TH_DIS_RCH_MSK BIT(7) #define ADP5061_VOLTAGE_TH_DIS_RCH_MODE(x) (((x) & 0x01) << 7) #define ADP5061_VOLTAGE_TH_VRCH_MSK GENMASK(6, 5) #define ADP5061_VOLTAGE_TH_VRCH_MODE(x) (((x) & 0x03) << 5) #define ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK GENMASK(4, 3) #define ADP5061_VOLTAGE_TH_VTRK_DEAD_MODE(x) (((x) & 0x03) << 3) #define ADP5061_VOLTAGE_TH_VWEAK_MSK GENMASK(2, 0) #define ADP5061_VOLTAGE_TH_VWEAK_MODE(x) (((x) & 0x07) << 0) /* ADP5061_CHG_STATUS_1 */ #define ADP5061_CHG_STATUS_1_VIN_OV(x) (((x) >> 7) & 0x1) #define ADP5061_CHG_STATUS_1_VIN_OK(x) (((x) >> 6) & 0x1) #define ADP5061_CHG_STATUS_1_VIN_ILIM(x) (((x) >> 5) & 0x1) #define ADP5061_CHG_STATUS_1_THERM_LIM(x) (((x) >> 4) & 0x1) #define ADP5061_CHG_STATUS_1_CHDONE(x) (((x) >> 3) & 0x1) #define ADP5061_CHG_STATUS_1_CHG_STATUS(x) (((x) >> 0) & 0x7) /* ADP5061_CHG_STATUS_2 */ #define ADP5061_CHG_STATUS_2_THR_STATUS(x) (((x) >> 5) & 0x7) #define ADP5061_CHG_STATUS_2_RCH_LIM_INFO(x) (((x) >> 3) & 0x1) #define ADP5061_CHG_STATUS_2_BAT_STATUS(x) (((x) >> 0) & 0x7) /* ADP5061_IEND */ #define ADP5061_IEND_IEND_MSK GENMASK(7, 5) #define ADP5061_IEND_IEND_MODE(x) (((x) & 0x07) << 5) #define ADP5061_NO_BATTERY 0x01 #define ADP5061_ICHG_MAX 1300 // mA enum adp5061_chg_status { ADP5061_CHG_OFF, ADP5061_CHG_TRICKLE, ADP5061_CHG_FAST_CC, ADP5061_CHG_FAST_CV, ADP5061_CHG_COMPLETE, ADP5061_CHG_LDO_MODE, ADP5061_CHG_TIMER_EXP, ADP5061_CHG_BAT_DET, }; static const int adp5061_chg_type[4] = { [ADP5061_CHG_OFF] = POWER_SUPPLY_CHARGE_TYPE_NONE, [ADP5061_CHG_TRICKLE] = POWER_SUPPLY_CHARGE_TYPE_TRICKLE, [ADP5061_CHG_FAST_CC] = POWER_SUPPLY_CHARGE_TYPE_FAST, [ADP5061_CHG_FAST_CV] = POWER_SUPPLY_CHARGE_TYPE_FAST, }; static const int adp5061_vweak_th[8] = { 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, }; static const int adp5061_prechg_current[4] = { 5, 10, 20, 80, }; static const int adp5061_vmin[4] = { 2000, 2500, 2600, 2900, }; static const int adp5061_const_chg_vmax[4] = { 3200, 3400, 3700, 3800, }; static const int adp5061_const_ichg[24] = { 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000, 1050, 1100, 1200, 1300, }; static const int adp5061_vmax[36] = { 3800, 3820, 3840, 3860, 3880, 3900, 3920, 3940, 3960, 3980, 4000, 4020, 4040, 4060, 4080, 4100, 4120, 4140, 4160, 4180, 4200, 4220, 4240, 4260, 4280, 4300, 4320, 4340, 4360, 4380, 4400, 4420, 4440, 4460, 4480, 4500, }; static const int adp5061_in_current_lim[16] = { 100, 150, 200, 250, 300, 400, 500, 600, 700, 800, 900, 1000, 1200, 1500, 1800, 2100, }; static const int adp5061_iend[8] = { 12500, 32500, 52500, 72500, 92500, 117500, 142500, 170000, }; struct adp5061_state { struct i2c_client *client; struct regmap *regmap; struct power_supply *psy; }; static int adp5061_get_array_index(const int *array, u8 size, int val) { int i; for (i = 1; i < size; i++) { if (val < array[i]) break; } return i-1; } static int adp5061_get_status(struct adp5061_state *st, u8 *status1, u8 *status2) { u8 buf[2]; int ret; /* CHG_STATUS1 and CHG_STATUS2 are adjacent regs */ ret = regmap_bulk_read(st->regmap, ADP5061_CHG_STATUS_1, &buf[0], 2); if (ret < 0) return ret; *status1 = buf[0]; *status2 = buf[1]; return ret; } static int adp5061_get_input_current_limit(struct adp5061_state *st, union power_supply_propval *val) { unsigned int regval; int mode, ret; ret = regmap_read(st->regmap, ADP5061_VINX_SET, ®val); if (ret < 0) return ret; mode = ADP5061_VINX_SET_ILIM_MODE(regval); val->intval = adp5061_in_current_lim[mode] * 1000; return ret; } static int adp5061_set_input_current_limit(struct adp5061_state *st, int val) { int index; /* Convert from uA to mA */ val /= 1000; index = adp5061_get_array_index(adp5061_in_current_lim, ARRAY_SIZE(adp5061_in_current_lim), val); if (index < 0) return index; return regmap_update_bits(st->regmap, ADP5061_VINX_SET, ADP5061_VINX_SET_ILIM_MSK, ADP5061_VINX_SET_ILIM_MODE(index)); } static int adp5061_set_min_voltage(struct adp5061_state *st, int val) { int index; /* Convert from uV to mV */ val /= 1000; index = adp5061_get_array_index(adp5061_vmin, ARRAY_SIZE(adp5061_vmin), val); if (index < 0) return index; return regmap_update_bits(st->regmap, ADP5061_VOLTAGE_TH, ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK, ADP5061_VOLTAGE_TH_VTRK_DEAD_MODE(index)); } static int adp5061_get_min_voltage(struct adp5061_state *st, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(st->regmap, ADP5061_VOLTAGE_TH, ®val); if (ret < 0) return ret; regval = ((regval & ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK) >> 3); val->intval = adp5061_vmin[regval] * 1000; return ret; } static int adp5061_get_chg_volt_lim(struct adp5061_state *st, union power_supply_propval *val) { unsigned int regval; int mode, ret; ret = regmap_read(st->regmap, ADP5061_TERM_SET, ®val); if (ret < 0) return ret; mode = ADP5061_TERM_SET_CHG_VLIM_MODE(regval); val->intval = adp5061_const_chg_vmax[mode] * 1000; return ret; } static int adp5061_get_max_voltage(struct adp5061_state *st, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(st->regmap, ADP5061_TERM_SET, ®val); if (ret < 0) return ret; regval = ((regval & ADP5061_TERM_SET_VTRM_MSK) >> 2) - 0x0F; if (regval >= ARRAY_SIZE(adp5061_vmax)) regval = ARRAY_SIZE(adp5061_vmax) - 1; val->intval = adp5061_vmax[regval] * 1000; return ret; } static int adp5061_set_max_voltage(struct adp5061_state *st, int val) { int vmax_index; /* Convert from uV to mV */ val /= 1000; if (val > 4500) val = 4500; vmax_index = adp5061_get_array_index(adp5061_vmax, ARRAY_SIZE(adp5061_vmax), val); if (vmax_index < 0) return vmax_index; vmax_index += 0x0F; return regmap_update_bits(st->regmap, ADP5061_TERM_SET, ADP5061_TERM_SET_VTRM_MSK, ADP5061_TERM_SET_VTRM_MODE(vmax_index)); } static int adp5061_set_const_chg_vmax(struct adp5061_state *st, int val) { int index; /* Convert from uV to mV */ val /= 1000; index = adp5061_get_array_index(adp5061_const_chg_vmax, ARRAY_SIZE(adp5061_const_chg_vmax), val); if (index < 0) return index; return regmap_update_bits(st->regmap, ADP5061_TERM_SET, ADP5061_TERM_SET_CHG_VLIM_MSK, ADP5061_TERM_SET_CHG_VLIM_MODE(index)); } static int adp5061_set_const_chg_current(struct adp5061_state *st, int val) { int index; /* Convert from uA to mA */ val /= 1000; if (val > ADP5061_ICHG_MAX) val = ADP5061_ICHG_MAX; index = adp5061_get_array_index(adp5061_const_ichg, ARRAY_SIZE(adp5061_const_ichg), val); if (index < 0) return index; return regmap_update_bits(st->regmap, ADP5061_CHG_CURR, ADP5061_CHG_CURR_ICHG_MSK, ADP5061_CHG_CURR_ICHG_MODE(index)); } static int adp5061_get_const_chg_current(struct adp5061_state *st, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(st->regmap, ADP5061_CHG_CURR, ®val); if (ret < 0) return ret; regval = ((regval & ADP5061_CHG_CURR_ICHG_MSK) >> 2); if (regval >= ARRAY_SIZE(adp5061_const_ichg)) regval = ARRAY_SIZE(adp5061_const_ichg) - 1; val->intval = adp5061_const_ichg[regval] * 1000; return ret; } static int adp5061_get_prechg_current(struct adp5061_state *st, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(st->regmap, ADP5061_CHG_CURR, ®val); if (ret < 0) return ret; regval &= ADP5061_CHG_CURR_ITRK_DEAD_MSK; val->intval = adp5061_prechg_current[regval] * 1000; return ret; } static int adp5061_set_prechg_current(struct adp5061_state *st, int val) { int index; /* Convert from uA to mA */ val /= 1000; index = adp5061_get_array_index(adp5061_prechg_current, ARRAY_SIZE(adp5061_prechg_current), val); if (index < 0) return index; return regmap_update_bits(st->regmap, ADP5061_CHG_CURR, ADP5061_CHG_CURR_ITRK_DEAD_MSK, ADP5061_CHG_CURR_ITRK_DEAD_MODE(index)); } static int adp5061_get_vweak_th(struct adp5061_state *st, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(st->regmap, ADP5061_VOLTAGE_TH, ®val); if (ret < 0) return ret; regval &= ADP5061_VOLTAGE_TH_VWEAK_MSK; val->intval = adp5061_vweak_th[regval] * 1000; return ret; } static int adp5061_set_vweak_th(struct adp5061_state *st, int val) { int index; /* Convert from uV to mV */ val /= 1000; index = adp5061_get_array_index(adp5061_vweak_th, ARRAY_SIZE(adp5061_vweak_th), val); if (index < 0) return index; return regmap_update_bits(st->regmap, ADP5061_VOLTAGE_TH, ADP5061_VOLTAGE_TH_VWEAK_MSK, ADP5061_VOLTAGE_TH_VWEAK_MODE(index)); } static int adp5061_get_chg_type(struct adp5061_state *st, union power_supply_propval *val) { u8 status1, status2; int chg_type, ret; ret = adp5061_get_status(st, &status1, &status2); if (ret < 0) return ret; chg_type = ADP5061_CHG_STATUS_1_CHG_STATUS(status1); if (chg_type >= ARRAY_SIZE(adp5061_chg_type)) val->intval = POWER_SUPPLY_STATUS_UNKNOWN; else val->intval = adp5061_chg_type[chg_type]; return ret; } static int adp5061_get_charger_status(struct adp5061_state *st, union power_supply_propval *val) { u8 status1, status2; int ret; ret = adp5061_get_status(st, &status1, &status2); if (ret < 0) return ret; switch (ADP5061_CHG_STATUS_1_CHG_STATUS(status1)) { case ADP5061_CHG_OFF: val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; case ADP5061_CHG_TRICKLE: case ADP5061_CHG_FAST_CC: case ADP5061_CHG_FAST_CV: val->intval = POWER_SUPPLY_STATUS_CHARGING; break; case ADP5061_CHG_COMPLETE: val->intval = POWER_SUPPLY_STATUS_FULL; break; case ADP5061_CHG_TIMER_EXP: /* The battery must be discharging if there is a charge fault */ val->intval = POWER_SUPPLY_STATUS_DISCHARGING; break; default: val->intval = POWER_SUPPLY_STATUS_UNKNOWN; } return ret; } static int adp5061_get_battery_status(struct adp5061_state *st, union power_supply_propval *val) { u8 status1, status2; int ret; ret = adp5061_get_status(st, &status1, &status2); if (ret < 0) return ret; switch (ADP5061_CHG_STATUS_2_BAT_STATUS(status2)) { case 0x0: /* Battery monitor off */ case 0x1: /* No battery */ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; break; case 0x2: /* VBAT < VTRK */ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; break; case 0x3: /* VTRK < VBAT_SNS < VWEAK */ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; break; case 0x4: /* VBAT_SNS > VWEAK */ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; break; default: val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; break; } return ret; } static int adp5061_get_termination_current(struct adp5061_state *st, union power_supply_propval *val) { unsigned int regval; int ret; ret = regmap_read(st->regmap, ADP5061_IEND, ®val); if (ret < 0) return ret; regval = (regval & ADP5061_IEND_IEND_MSK) >> 5; val->intval = adp5061_iend[regval]; return ret; } static int adp5061_set_termination_current(struct adp5061_state *st, int val) { int index; index = adp5061_get_array_index(adp5061_iend, ARRAY_SIZE(adp5061_iend), val); if (index < 0) return index; return regmap_update_bits(st->regmap, ADP5061_IEND, ADP5061_IEND_IEND_MSK, ADP5061_IEND_IEND_MODE(index)); } static int adp5061_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct adp5061_state *st = power_supply_get_drvdata(psy); u8 status1, status2; int mode, ret; switch (psp) { case POWER_SUPPLY_PROP_PRESENT: ret = adp5061_get_status(st, &status1, &status2); if (ret < 0) return ret; mode = ADP5061_CHG_STATUS_2_BAT_STATUS(status2); if (mode == ADP5061_NO_BATTERY) val->intval = 0; else val->intval = 1; break; case POWER_SUPPLY_PROP_CHARGE_TYPE: return adp5061_get_chg_type(st, val); case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: /* This property is used to indicate the input current * limit into VINx (ILIM) */ return adp5061_get_input_current_limit(st, val); case POWER_SUPPLY_PROP_VOLTAGE_MAX: /* This property is used to indicate the termination * voltage (VTRM) */ return adp5061_get_max_voltage(st, val); case POWER_SUPPLY_PROP_VOLTAGE_MIN: /* * This property is used to indicate the trickle to fast * charge threshold (VTRK_DEAD) */ return adp5061_get_min_voltage(st, val); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: /* This property is used to indicate the charging * voltage limit (CHG_VLIM) */ return adp5061_get_chg_volt_lim(st, val); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: /* * This property is used to indicate the value of the constant * current charge (ICHG) */ return adp5061_get_const_chg_current(st, val); case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: /* * This property is used to indicate the value of the trickle * and weak charge currents (ITRK_DEAD) */ return adp5061_get_prechg_current(st, val); case POWER_SUPPLY_PROP_VOLTAGE_AVG: /* * This property is used to set the VWEAK threshold * bellow this value, weak charge mode is entered * above this value, fast chargerge mode is entered */ return adp5061_get_vweak_th(st, val); case POWER_SUPPLY_PROP_STATUS: /* * Indicate the charger status in relation to power * supply status property */ return adp5061_get_charger_status(st, val); case POWER_SUPPLY_PROP_CAPACITY_LEVEL: /* * Indicate the battery status in relation to power * supply capacity level property */ return adp5061_get_battery_status(st, val); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: /* Indicate the values of the termination current */ return adp5061_get_termination_current(st, val); default: return -EINVAL; } return 0; } static int adp5061_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct adp5061_state *st = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return adp5061_set_input_current_limit(st, val->intval); case POWER_SUPPLY_PROP_VOLTAGE_MAX: return adp5061_set_max_voltage(st, val->intval); case POWER_SUPPLY_PROP_VOLTAGE_MIN: return adp5061_set_min_voltage(st, val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: return adp5061_set_const_chg_vmax(st, val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: return adp5061_set_const_chg_current(st, val->intval); case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: return adp5061_set_prechg_current(st, val->intval); case POWER_SUPPLY_PROP_VOLTAGE_AVG: return adp5061_set_vweak_th(st, val->intval); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return adp5061_set_termination_current(st, val->intval); default: return -EINVAL; } return 0; } static int adp5061_prop_writeable(struct power_supply *psy, enum power_supply_property psp) { switch (psp) { case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: case POWER_SUPPLY_PROP_VOLTAGE_MAX: case POWER_SUPPLY_PROP_VOLTAGE_MIN: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: case POWER_SUPPLY_PROP_VOLTAGE_AVG: case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return 1; default: return 0; } } static enum power_supply_property adp5061_props[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, POWER_SUPPLY_PROP_VOLTAGE_MAX, POWER_SUPPLY_PROP_VOLTAGE_MIN, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, POWER_SUPPLY_PROP_PRECHARGE_CURRENT, POWER_SUPPLY_PROP_VOLTAGE_AVG, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CAPACITY_LEVEL, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, }; static const struct regmap_config adp5061_regmap_config = { .reg_bits = 8, .val_bits = 8, }; static const struct power_supply_desc adp5061_desc = { .name = "adp5061", .type = POWER_SUPPLY_TYPE_USB, .get_property = adp5061_get_property, .set_property = adp5061_set_property, .property_is_writeable = adp5061_prop_writeable, .properties = adp5061_props, .num_properties = ARRAY_SIZE(adp5061_props), }; static int adp5061_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct power_supply_config psy_cfg = {}; struct adp5061_state *st; st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL); if (!st) return -ENOMEM; st->client = client; st->regmap = devm_regmap_init_i2c(client, &adp5061_regmap_config); if (IS_ERR(st->regmap)) { dev_err(&client->dev, "Failed to initialize register map\n"); return -EINVAL; } i2c_set_clientdata(client, st); psy_cfg.drv_data = st; st->psy = devm_power_supply_register(&client->dev, &adp5061_desc, &psy_cfg); if (IS_ERR(st->psy)) { dev_err(&client->dev, "Failed to register power supply\n"); return PTR_ERR(st->psy); } return 0; } static const struct i2c_device_id adp5061_id[] = { { "adp5061", 0}, { } }; MODULE_DEVICE_TABLE(i2c, adp5061_id); static struct i2c_driver adp5061_driver = { .driver = { .name = KBUILD_MODNAME, }, .probe = adp5061_probe, .id_table = adp5061_id, }; module_i2c_driver(adp5061_driver); MODULE_DESCRIPTION("Analog Devices adp5061 battery charger driver"); MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>"); MODULE_LICENSE("GPL v2");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1