lkml.org 
[lkml]   [2011]   [Aug]   [29]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Date
    Subject[PATCH 2/3] power: support intersil isl9519q charger driver

    This code is based on the original work of Abhijeet
    Dharmapurikar <adharmap@codeaurora.org>.

    The basic driver has been extended to support the
    supplied_to and num_supplicants selectors. These
    fields allow the core power infrastructure to signal
    to the driver listed in the supplied_to field that
    a power change has taken place when that driver
    implements the external_power_changed call back.

    The isl9519q has been extended to implement this
    callback allowing a driver that detects ac on/off
    to signal the change to the isl9519q.

    In the 9519q the supplied_to field is used to tell
    the 9519q what battery device it is supplying power to.
    It queries the battery using the properties
    CHARGE_TO_USE and VOLTAGE_TO_USE to set the correct
    voltage and charge.

    In our implementation we have set up the max8925 to
    signal ac on/off to the intersil 9519q. The 9519q
    will then use the supplied_to field to query the
    battery for power information.

    Signed-off-by: Philip Rakity <prakity@marvell.com>
    ---
    drivers/power/Kconfig | 7 +
    drivers/power/Makefile | 1 +
    drivers/power/isl9519q.c | 493 ++++++++++++++++++++++++++++++++
    include/linux/i2c/isl9519.h | 52 ++++
    include/linux/power/isl9519q_charger.h | 13 +
    5 files changed, 566 insertions(+), 0 deletions(-)
    create mode 100644 drivers/power/isl9519q.c
    create mode 100644 include/linux/i2c/isl9519.h
    create mode 100644 include/linux/power/isl9519q_charger.h

    diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
    index 57de051..c6fa490 100644
    --- a/drivers/power/Kconfig
    +++ b/drivers/power/Kconfig
    @@ -249,4 +249,11 @@ config CHARGER_MAX8998
    Say Y to enable support for the battery charger control sysfs and
    platform data of MAX8998/LP3974 PMICs.

    +config CHARGER_ISL9519
    + tristate "Intersil ISL9519q battery charger driver"
    + depends on I2C
    + default n
    + help
    + Say Y here to enable support for ISL9519q Battery Charger.ß
    +
    endif # POWER_SUPPLY
    diff --git a/drivers/power/Makefile b/drivers/power/Makefile
    index b4af13d..ab86e66 100644
    --- a/drivers/power/Makefile
    +++ b/drivers/power/Makefile
    @@ -38,3 +38,4 @@ obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o
    obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
    obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
    obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
    +obj-$(CONFIG_CHARGER_ISL9519) += isl9519q.o
    diff --git a/drivers/power/isl9519q.c b/drivers/power/isl9519q.c
    new file mode 100644
    index 0000000..685a5aa
    --- /dev/null
    +++ b/drivers/power/isl9519q.c
    @@ -0,0 +1,493 @@
    +/* Copyright (c) 2010 Code Aurora Forum. All rights reserved.
    + *
    + * This program is free software; you can redistribute it and/or modify
    + * it under the terms of the GNU General Public License version 2 and
    + * only version 2 as published by the Free Software Foundation.
    + *
    + * This program is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    + * GNU General Public License for more details.
    + *
    + * You should have received a copy of the GNU General Public License
    + * along with this program; if not, write to the Free Software
    + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    + * 02110-1301, USA.
    + *
    + */
    +
    +#include <linux/i2c.h>
    +#include <linux/gpio.h>
    +#include <linux/errno.h>
    +#include <linux/delay.h>
    +#include <linux/module.h>
    +#include <linux/debugfs.h>
    +#include <linux/workqueue.h>
    +#include <linux/interrupt.h>
    +#include <linux/slab.h>
    +#include <linux/i2c/isl9519.h>
    +#include <linux/power_supply.h>
    +#include <linux/power/isl9519q_charger.h>
    +
    +#define CHG_CURRENT_REG 0x14
    +#define MAX_SYS_VOLTAGE_REG 0x15
    +#define CONTROL_REG 0x3D
    +#define MIN_SYS_VOLTAGE_REG 0x3E
    +#define INPUT_CURRENT_REG 0x3F
    +#define MANUFACTURER_ID_REG 0xFE
    +#define DEVICE_ID_REG 0xFF
    +
    +#define TRCKL_CHG_STATUS_BIT 0x80
    +#define CONTROL_REG_AC_OK (1<<6)
    +#define ISL9519_CHG_PERIOD ((HZ) * 150)
    +
    +struct charger_struct {
    + struct i2c_client *client;
    + struct power_supply psy;
    + struct delayed_work charge_work;
    +
    + int present;
    + int batt_present;
    + bool charging;
    + int chgcurrent;
    + int chgvoltage;
    + int term_current;
    + int max_system_voltage;
    + int min_system_voltage;
    + bool online;
    +};
    +
    +static int isl9519q_read_reg(struct i2c_client *client, int reg, u16 *val)
    +{
    + int ret;
    +
    + ret = i2c_smbus_read_word_data(client, reg);
    +
    + if (ret < 0) {
    + dev_err(&client->dev,
    + "i2c read fail: can't read from %02x: %d\n", reg, ret);
    + return -EAGAIN;
    + } else
    + *val = ret;
    +
    + return 0;
    +}
    +
    +static int isl9519q_write_reg(struct i2c_client *client, int reg,
    + u16 val)
    +{
    + int ret;
    + struct charger_struct *isl_chg;
    +
    + isl_chg = i2c_get_clientdata(client);
    + ret = i2c_smbus_write_word_data(isl_chg->client, reg, val);
    +
    + if (ret < 0) {
    + dev_err(&isl_chg->client->dev,
    + "i2c write fail: can't write %02x to %02x: %d\n",
    + val, reg, ret);
    + return -EAGAIN;
    + }
    + return 0;
    +}
    +
    +
    +static int isl9519q_start_charging(struct charger_struct *isl_chg)
    +{
    + int ret = 0;
    + u16 old_voltage;
    + u16 old_current;
    +
    + dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
    +
    + ret = isl9519q_read_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG,
    + &old_voltage);
    +
    + ret = isl9519q_read_reg(isl_chg->client, CHG_CURRENT_REG,
    + &old_current);
    +
    + ret = isl9519q_write_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG,
    + isl_chg->chgvoltage);
    +
    + if (ret) {
    + dev_err(&isl_chg->client->dev,
    + "%s coulnt write to MAX_SYS_VOLTAGE_REG\n", __func__);
    + goto out;
    + }
    + ret = isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG,
    + isl_chg->chgcurrent);
    + if (ret) {
    + dev_err(&isl_chg->client->dev,
    + "%s coulnt write to CHG_CURRENT_REG\n", __func__);
    + goto out;
    + }
    +
    + ret = isl9519q_read_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG,
    + &old_voltage);
    +
    + ret = isl9519q_read_reg(isl_chg->client, CHG_CURRENT_REG,
    + &old_current);
    +
    + isl_chg->charging = true;
    +
    +out:
    + return ret;
    +}
    +
    +static int isl9519q_stop_charging(struct charger_struct *isl_chg)
    +{
    + int ret = 0;
    +
    + if (!(isl_chg->charging))
    + /* we arent charging */
    + return 0;
    +
    + dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
    +
    + ret = isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, 0);
    + if (ret) {
    + dev_err(&isl_chg->client->dev,
    + "%s coulnt write to current_reg\n", __func__);
    + goto out;
    + }
    +out:
    + return ret;
    +}
    +
    +static void isl9519q_charge(struct work_struct *isl9519_work)
    +{
    + struct charger_struct *isl_chg;
    + struct power_supply *psy;
    + union power_supply_propval ret = {0,};
    + int voltage = 0;
    + int charge = 0;
    + int i;
    +
    + isl_chg = container_of(isl9519_work, struct charger_struct,
    + charge_work.work);
    +
    + dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
    +
    + if (isl_chg->charging) {
    + for (i = 0; i < isl_chg->psy.num_supplicants; i++) {
    + psy = power_supply_get_by_name
    + (isl_chg->psy.supplied_to[i]);
    + if (psy != NULL) {
    + if (psy->get_property(psy,
    + POWER_SUPPLY_PROP_CHARGE_TO_USE, &ret)
    + == 0)
    + charge = ret.intval;
    +
    + if (psy->get_property(psy,
    + POWER_SUPPLY_PROP_VOLTAGE_TO_USE, &ret)
    + == 0)
    + voltage = ret.intval;
    +
    + if (isl_chg->charging) {
    + isl_chg->chgvoltage
    + = (voltage / 1000); /* mV */
    + isl_chg->chgcurrent
    + = (charge / 1000); /* mA */
    + isl9519q_start_charging(isl_chg);
    + }
    + }
    + }
    + schedule_delayed_work(&isl_chg->charge_work,
    + ISL9519_CHG_PERIOD);
    + } else
    + isl9519q_stop_charging(isl_chg);
    +
    +}
    +
    +static void isl9519q_external_power_changed(struct power_supply *pst)
    +{
    + struct charger_struct *isl_chg =
    + container_of(pst, struct charger_struct, psy);
    + u16 temp;
    +
    + if (isl9519q_read_reg(isl_chg->client, CONTROL_REG, &temp) < 0) {
    + isl_chg->online = 0;
    + isl_chg->charging = 0;
    + } else {
    + isl_chg->online = 1;
    + if (temp & CONTROL_REG_AC_OK)
    + isl_chg->charging = 1;
    + else
    + isl_chg->charging = 0;
    + }
    + if (isl_chg->charging)
    + schedule_delayed_work(&isl_chg->charge_work,
    + ISL9519_CHG_PERIOD);
    + else
    + cancel_delayed_work(&isl_chg->charge_work);
    +
    + power_supply_changed(pst);
    +}
    +
    +
    +static int isl9519q_charger_get_property(struct power_supply *psy,
    + enum power_supply_property psp,
    + union power_supply_propval *val)
    +{
    + struct charger_struct *isl_chg =
    + container_of(psy, struct charger_struct, psy);
    + u16 temp;
    + int ret;
    +
    + switch (psp) {
    + case POWER_SUPPLY_PROP_PRESENT:
    + ret = isl9519q_read_reg(isl_chg->client, DEVICE_ID_REG,
    + &temp);
    +
    + isl_chg->present = ret ? 0 : 1;
    + val->intval = isl_chg->present;
    + break;
    + case POWER_SUPPLY_PROP_ONLINE:
    + val->intval = isl_chg->online;
    + break;
    + case POWER_SUPPLY_PROP_VOLTAGE_MAX:
    + ret = isl9519q_read_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG,
    + &temp);
    + if (ret) {
    + dev_err(&isl_chg->client->dev,
    + "%s could not read to MAX_SYS_VOLTAGE_REG"
    + " ret=%d\n",
    + __func__, ret);
    + return -EINVAL;
    + }
    + isl_chg->max_system_voltage = temp;
    + val->intval = temp * 1000; /* convert to uV */
    + break;
    + case POWER_SUPPLY_PROP_VOLTAGE_MIN:
    + ret = isl9519q_read_reg(isl_chg->client, MIN_SYS_VOLTAGE_REG,
    + &temp);
    + if (ret) {
    + dev_err(&isl_chg->client->dev,
    + "%s couldnt read to MIN_SYS_VOLTAGE_REG"
    + " ret=%d\n",
    + __func__, ret);
    + return -EINVAL;
    + }
    + temp &= 0x2F00;
    + isl_chg->min_system_voltage = temp;
    + val->intval = temp * 1000; /* convert to uV */
    + break;
    + case POWER_SUPPLY_PROP_CHARGE_FULL:
    + val->intval = !isl_chg->charging;
    + break;
    + case POWER_SUPPLY_PROP_MANUFACTURER:
    + val->strval = "Intersil";
    + break;
    + default:
    + return -EINVAL;
    + }
    + return 0;
    +}
    +
    +static enum power_supply_property power_props[] = {
    + POWER_SUPPLY_PROP_PRESENT,
    + POWER_SUPPLY_PROP_ONLINE,
    + POWER_SUPPLY_PROP_VOLTAGE_MAX,
    + POWER_SUPPLY_PROP_VOLTAGE_MIN,
    + POWER_SUPPLY_PROP_CHARGE_FULL,
    + POWER_SUPPLY_PROP_MANUFACTURER,
    +};
    +
    +#define MAX_VOLTAGE_REG_MASK 0x3FF0
    +#define MIN_VOLTAGE_REG_MASK 0x3F00
    +#define DEFAULT_MAX_VOLTAGE_REG_VALUE 0x1070
    +#define DEFAULT_MIN_VOLTAGE_REG_VALUE 0x0D00
    +
    +static int isl9519q_init_chip(struct i2c_client *client,
    + struct charger_struct *isl_chg)
    +{
    + int ret;
    + u16 temp;
    +
    + isl_chg->max_system_voltage &= MAX_VOLTAGE_REG_MASK;
    + isl_chg->min_system_voltage &= MIN_VOLTAGE_REG_MASK;
    +
    + if (isl_chg->max_system_voltage == 0)
    + isl_chg->max_system_voltage = DEFAULT_MAX_VOLTAGE_REG_VALUE;
    + if (isl_chg->min_system_voltage == 0)
    + isl_chg->min_system_voltage = DEFAULT_MIN_VOLTAGE_REG_VALUE;
    +
    + ret = isl9519q_write_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG,
    + isl_chg->max_system_voltage);
    + if (ret) {
    + dev_err(&client->dev,
    + "%s couldnt write to MAX_SYS_VOLTAGE_REG ret=%d\n",
    + __func__, ret);
    + goto exit;
    + }
    +
    + ret = isl9519q_write_reg(isl_chg->client, MIN_SYS_VOLTAGE_REG,
    + isl_chg->min_system_voltage);
    + if (ret) {
    + dev_err(&client->dev,
    + "%s couldnt write to MIN_SYS_VOLTAGE_REG ret=%d\n",
    + __func__, ret);
    + goto exit;
    + }
    +
    + ret = isl9519q_read_reg(isl_chg->client, CONTROL_REG,
    + &temp);
    + if (temp & CONTROL_REG_AC_OK)
    + isl_chg->charging = 1;
    + else
    + isl_chg->charging = 0;
    +exit:
    + return ret;
    +}
    +
    +static int __devinit isl9519q_probe(struct i2c_client *client,
    + const struct i2c_device_id *id)
    +{
    + struct isl9519q_platform_data *pdata;
    + struct charger_struct *isl_chg;
    + int ret;
    +
    + ret = 0;
    + pdata = client->dev.platform_data;
    +
    + if (pdata == NULL) {
    + dev_err(&client->dev, "%s no platform data\n", __func__);
    + ret = -EINVAL;
    + goto out;
    + }
    +
    + if (!i2c_check_functionality(client->adapter,
    + I2C_FUNC_SMBUS_WORD_DATA)) {
    + ret = -EIO;
    + goto out;
    + }
    +
    + isl_chg = kzalloc(sizeof(*isl_chg), GFP_KERNEL);
    + if (!isl_chg) {
    + ret = -ENOMEM;
    + goto out;
    + }
    +
    + INIT_DELAYED_WORK(&isl_chg->charge_work, isl9519q_charge);
    + isl_chg->client = client;
    + isl_chg->chgcurrent = pdata->chgcurrent;
    + isl_chg->term_current = pdata->term_current;
    + isl_chg->max_system_voltage = pdata->max_system_voltage;
    + isl_chg->min_system_voltage = pdata->min_system_voltage;
    +
    + /* h/w ignores lower 7 bits of charging current */
    + isl_chg->chgcurrent &= ~0x7F;
    +
    + isl_chg->psy.name = "isl-adapter";
    + isl_chg->psy.type = POWER_SUPPLY_TYPE_MAINS;
    + isl_chg->psy.properties = power_props;
    + isl_chg->psy.num_properties = ARRAY_SIZE(power_props);
    + isl_chg->psy.get_property = isl9519q_charger_get_property;
    + isl_chg->psy.supplied_to = pdata->supplied_to;
    + isl_chg->psy.num_supplicants = pdata->num_supplicants;
    + isl_chg->psy.external_power_changed = isl9519q_external_power_changed;
    +
    + i2c_set_clientdata(client, isl_chg);
    + ret = isl9519q_init_chip(client, isl_chg);
    + if (ret)
    + goto free_isl_chg;
    + ret = power_supply_register(&client->dev, &isl_chg->psy);
    + if (ret)
    + goto free_isl_chg;
    +
    + if (pdata->chg_detection_config) {
    + ret = pdata->chg_detection_config();
    + if (ret) {
    + dev_err(&client->dev, "%s valid config failed ret=%d\n",
    + __func__, ret);
    + goto free_isl_chg;
    + }
    + }
    + schedule_delayed_work(&isl_chg->charge_work,
    + ISL9519_CHG_PERIOD);
    + return 0;
    +
    +free_isl_chg:
    + kfree(isl_chg);
    +out:
    + return ret;
    +}
    +
    +static int __devexit isl9519q_remove(struct i2c_client *client)
    +{
    + struct isl9519q_platform_data *pdata;
    + struct charger_struct *isl_chg = i2c_get_clientdata(client);
    + kfree(isl_chg);
    + pdata = client->dev.platform_data;
    + return 0;
    +}
    +
    +static const struct i2c_device_id isl9519q_id[] = {
    + {"isl9519q", 0},
    + {},
    +};
    +
    +#ifdef CONFIG_PM
    +static int isl9519q_suspend(struct i2c_client *client,
    + pm_message_t state)
    +{
    + struct charger_struct *isl_chg = i2c_get_clientdata(client);
    +
    + dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
    + return 0;
    +}
    +
    +static int isl9519q_resume(struct i2c_client *client)
    +{
    + struct charger_struct *isl_chg = i2c_get_clientdata(client);
    + u16 temp;
    + int ret;
    +
    + dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
    +
    + ret = isl9519q_read_reg(isl_chg->client, CONTROL_REG,
    + &temp);
    + if (!ret && (temp & CONTROL_REG_AC_OK)) {
    + isl_chg->charging = 1;
    + schedule_delayed_work(&isl_chg->charge_work,
    + ISL9519_CHG_PERIOD);
    + } else {
    + isl_chg->charging = 0;
    + isl9519q_stop_charging(isl_chg);
    + cancel_delayed_work(&isl_chg->charge_work);
    + }
    + return 0;
    +}
    +#endif
    +
    +static struct i2c_driver isl9519q_driver = {
    + .driver = {
    + .name = "isl9519q",
    + .owner = THIS_MODULE,
    + },
    + .probe = isl9519q_probe,
    +#ifdef CONFIG_PM
    + .suspend = isl9519q_suspend,
    + .resume = isl9519q_resume,
    +#endif
    + .remove = __devexit_p(isl9519q_remove),
    + .id_table = isl9519q_id,
    +};
    +
    +static int __init isl9519q_init(void)
    +{
    + return i2c_add_driver(&isl9519q_driver);
    +}
    +
    +module_init(isl9519q_init);
    +
    +static void __exit isl9519q_exit(void)
    +{
    + return i2c_del_driver(&isl9519q_driver);
    +}
    +
    +module_exit(isl9519q_exit);
    +
    +MODULE_AUTHOR("Abhijeet Dharmapurikar <adharmap@codeaurora.org>");
    +MODULE_DESCRIPTION("Driver for ISL9519Q Charger chip");
    +MODULE_LICENSE("GPL v2");
    diff --git a/include/linux/i2c/isl9519.h b/include/linux/i2c/isl9519.h
    new file mode 100644
    index 0000000..6814ef0
    --- /dev/null
    +++ b/include/linux/i2c/isl9519.h
    @@ -0,0 +1,52 @@
    +/* Copyright (c) 2010, Code Aurora Forum. All rights reserved.
    + *
    + * Redistribution and use in source and binary forms, with or without
    + * modification, are permitted provided that the following conditions are
    + * met:
    + * * Redistributions of source code must retain the above copyright
    + * notice, this list of conditions and the following disclaimer.
    + * * Redistributions in binary form must reproduce the above
    + * copyright notice, this list of conditions and the following
    + * disclaimer in the documentation and/or other materials provided
    + * with the distribution.
    + * * Neither the name of Code Aurora Forum, Inc. nor the names of its
    + * contributors may be used to endorse or promote products derived
    + * from this software without specific prior written permission.
    + *
    + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
    + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
    + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
    + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
    + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
    + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
    + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
    + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    + *
    + */
    +#ifndef __ISL9519_H__
    +#define __ISL9519_H__
    +
    +/**
    + * struct isl_platform_data
    + * @chgcurrent max current the islchip can draw
    + * @valid_irq interrupt for insertion/removal notification
    + * @valid_n_gpio gpio to debounce insertion/removal
    + * @valid_config machine specific func to configure gpio line
    + * @max_system_voltage the max voltage isl should charge battery to
    + * @min_system_voltage the min voltage isl should trkl charge the
    + * battery
    + * @term_current the battery current when isl charging should stop
    + */
    +struct isl_platform_data {
    + int chgcurrent;
    + int valid_n_gpio;
    + int (*chg_detection_config) (void);
    + int max_system_voltage;
    + int min_system_voltage;
    + int term_current;
    +};
    +
    +#endif
    diff --git a/include/linux/power/isl9519q_charger.h b/include/linux/power/isl9519q_charger.h
    new file mode 100644
    index 0000000..dccf1f2
    --- /dev/null
    +++ b/include/linux/power/isl9519q_charger.h
    @@ -0,0 +1,13 @@
    +#ifndef __ISL9519Q_CHARGER_H_
    +#define __ISL9519Q_CHARGER_H_
    +
    +struct isl9519q_platform_data {
    + char **supplied_to;
    + int num_supplicants;
    + u16 chgcurrent;
    + u16 term_current;
    + u16 max_system_voltage;
    + u16 min_system_voltage;
    + int (*chg_detection_config) (void);
    +};
    +#endif
    --
    1.7.6

    --
    To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
    the body of a message to majordomo@vger.kernel.org
    More majordomo info at http://vger.kernel.org/majordomo-info.html
    Please read the FAQ at http://www.tux.org/lkml/

    \
     
     \ /
      Last update: 2011-08-29 19:09    [W:0.073 / U:2.788 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site