Use devm_* managed functions to ease slot-gpio users
Subsystems (5)
v2 cover Use devm_* managed functions to ease slot-gpio users
Changes since v1:
* Add kernel doc for mmc_gpio_request/free_ro/cd() to document the use cases.
* Add a patch to remove unncessary mmc_gpio_free_cd() call from existing slot-gpio users.
Shawn Guo (3):
mmc: slot-gpio: use devm_* managed functions to ease users
mmc: remove unncessary mmc_gpio_free_cd() call from slot-gpio users
mmc: sdhci-esdhc-imx: use slot-gpio helpers for CD and WP
drivers/mmc/core/slot-gpio.c | 57 ++++++++++++++++++++++++++++++++---- drivers/mmc/host/sdhci-esdhc-imx.c | 56 ++++++++++------------------------- drivers/mmc/host/sdhci-pxav3.c | 5 ---- drivers/mmc/host/sh_mmcif.c | 6 ---- drivers/mmc/host/tmio_mmc_pio.c | 8 ----- 5 files changed, 67 insertions(+), 65 deletions(-)
Signature · 1 line
1.7.9.5
PATCH v2 1/3 mmc: slot-gpio: use devm_* managed functions to ease users
Use devm_* managed functions, so that slot-gpio users do not have to call mmc_gpio_free_ro/cd to free up resources requested in mmc_gpio_request_ro/cd.
--- drivers/mmc/core/slot-gpio.c | 57 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 6 deletions(-)
drivers/mmc/core/slot-gpio.c +51 -6
@@ -92,6 +92,20 @@ int mmc_gpio_get_cd(struct mmc_host *host) }EXPORT_SYMBOL (mmc_gpio_get_cd); +/** +* mmc_gpio_request_ro - request a gpio for write-protection +* @host: mmc host +* @gpio: gpio number requested +* +* As devm_* managed functions are used in mmc_gpio_request_ro(), client +* drivers do not need to explicitly call mmc_gpio_free_ro() for freeing up, +* if the requesting and freeing are only needed at probing and unbinding time +* for once. However, if client drivers do something special like runtime +* switching for write-protection, they are responsible for calling +* mmc_gpio_request_ro() and mmc_gpio_free_ro() as a pair on their own. +* +* Returns zero on success, else an error. +*/ int mmc_gpio_request_ro (struct mmc_host *host,unsigned int gpio) {struct mmc_gpio *ctx;
@@ -106,7 +120,8 @@ int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio) ctx = host->slot.handler_priv; - ret =gpio_request_one (gpio,GPIOF_DIR_IN , ctx->ro_label); + ret =devm_gpio_request_one (&host->class_dev, gpio,GPIOF_DIR_IN , + ctx->ro_label);if (ret <0 )return ret;
@@ -116,6 +131,20 @@ int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio) }EXPORT_SYMBOL (mmc_gpio_request_ro); +/** +* mmc_gpio_request_cd - request a gpio for card-detection +* @host: mmc host +* @gpio: gpio number requested +* +* As devm_* managed functions are used in mmc_gpio_request_cd(), client +* drivers do not need to explicitly call mmc_gpio_free_cd() for freeing up, +* if the requesting and freeing are only needed@probing and unbinding time +* for once. However, if client drivers do something special like runtime +* switching for card-detection, they are responsible for calling +* mmc_gpio_request_cd() and mmc_gpio_free_cd() as a pair on their own. +* +* Returns zero on success, else an error. +*/ int mmc_gpio_request_cd (struct mmc_host *host,unsigned int gpio) {struct mmc_gpio *ctx;
@@ -128,7 +157,8 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio) ctx = host->slot.handler_priv; - ret =gpio_request_one (gpio,GPIOF_DIR_IN , ctx->cd_label); + ret =devm_gpio_request_one (&host->class_dev, gpio,GPIOF_DIR_IN , + ctx->cd_label);if (ret <0 )/* * don't bother freeing memory. It might still get used by other
@@ -146,7 +176,8 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio) irq = -EINVAL ;if (irq >=0 ) { - ret =request_threaded_irq (irq,NULL , mmc_gpio_cd_irqt, + ret =devm_request_threaded_irq (&host->class_dev, irq, +NULL , mmc_gpio_cd_irqt,IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING |IRQF_ONESHOT , ctx->cd_label, host);if (ret <0 )
@@ -164,6 +195,13 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio) }EXPORT_SYMBOL (mmc_gpio_request_cd); +/** +* mmc_gpio_free_ro - free the write-protection gpio +* @host: mmc host +* +* It's provided only for cases that client drivers need to manually free +* up the write-protection gpio requested by mmc_gpio_request_ro(). +*/ void mmc_gpio_free_ro (struct mmc_host *host) {struct mmc_gpio *ctx = host->slot.handler_priv;
@@ -175,10 +213,17 @@ void mmc_gpio_free_ro(struct mmc_host *host) gpio = ctx->ro_gpio; ctx->ro_gpio = -EINVAL ; -gpio_free (gpio); +devm_gpio_free (&host->class_dev, gpio); }EXPORT_SYMBOL (mmc_gpio_free_ro); +/** +* mmc_gpio_free_cd - free the card-detection gpio +* @host: mmc host +* +* It's provided only for cases that client drivers need to manually free +* up the card-detection gpio requested by mmc_gpio_request_cd(). +*/ void mmc_gpio_free_cd (struct mmc_host *host) {struct mmc_gpio *ctx = host->slot.handler_priv;
@@ -188,13 +233,13 @@ void mmc_gpio_free_cd(struct mmc_host *host)return ;if (host->slot.cd_irq >=0 ) { -free_irq (host->slot.cd_irq, host); +devm_free_irq (&host->class_dev, host->slot.cd_irq, host); host->slot.cd_irq = -EINVAL ; } gpio = ctx->cd_gpio; ctx->cd_gpio = -EINVAL ; -gpio_free (gpio); +devm_gpio_free (&host->class_dev, gpio); }EXPORT_SYMBOL (mmc_gpio_free_cd);
Signature · 1 line
1.7.9.5
PATCH v2 1/3 mmc: slot-gpio: use devm_* managed functions to ease users
Hi Shawn
Sorry for a late reply.
Quoted 5 lines · Shawn Guo
Use devm_* managed functions, so that slot-gpio users do not have to call mmc_gpio_free_ro/cd to free up resources requested in mmc_gpio_request_ro/cd.
Signed-off-by Shawn Guo <[email protected]>
Thanks
Guennadi
Quoted 133 lines
--- drivers/mmc/core/slot-gpio.c | 57 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 6 deletions(-)drivers/mmc/core/slot-gpio.c +51
-6@@ -92,6 +92,20 @@ int mmc_gpio_get_cd(struct mmc_host *host) }EXPORT_SYMBOL (mmc_gpio_get_cd); +/** +* mmc_gpio_request_ro - request a gpio for write-protection +* @host: mmc host +* @gpio: gpio number requested +* +* As devm_* managed functions are used in mmc_gpio_request_ro(), client +* drivers do not need to explicitly call mmc_gpio_free_ro() for freeing up, +* if the requesting and freeing are only needed at probing and unbinding time +* for once. However, if client drivers do something special like runtime +* switching for write-protection, they are responsible for calling +* mmc_gpio_request_ro() and mmc_gpio_free_ro() as a pair on their own. +* +* Returns zero on success, else an error. +*/ int mmc_gpio_request_ro (struct mmc_host *host,unsigned int gpio) {struct mmc_gpio *ctx;@@ -106,7 +120,8 @@ int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio) ctx = host->slot.handler_priv; - ret =gpio_request_one (gpio,GPIOF_DIR_IN , ctx->ro_label); + ret =devm_gpio_request_one (&host->class_dev, gpio,GPIOF_DIR_IN , + ctx->ro_label);if (ret <0 )return ret;@@ -116,6 +131,20 @@ int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio) }EXPORT_SYMBOL (mmc_gpio_request_ro); +/** +* mmc_gpio_request_cd - request a gpio for card-detection +* @host: mmc host +* @gpio: gpio number requested +* +* As devm_* managed functions are used in mmc_gpio_request_cd(), client +* drivers do not need to explicitly call mmc_gpio_free_cd() for freeing up, +* if the requesting and freeing are only needed at probing and unbinding time +* for once. However, if client drivers do something special like runtime +* switching for card-detection, they are responsible for calling +* mmc_gpio_request_cd() and mmc_gpio_free_cd() as a pair on their own. +* +* Returns zero on success, else an error. +*/ int mmc_gpio_request_cd (struct mmc_host *host,unsigned int gpio) {struct mmc_gpio *ctx;@@ -128,7 +157,8 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio) ctx = host->slot.handler_priv; - ret =gpio_request_one (gpio,GPIOF_DIR_IN , ctx->cd_label); + ret =devm_gpio_request_one (&host->class_dev, gpio,GPIOF_DIR_IN , + ctx->cd_label);if (ret <0 )/* * don't bother freeing memory. It might still get used by other @@ -146,7 +176,8 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio) irq = -EINVAL ;if (irq >=0 ) { - ret =request_threaded_irq (irq,NULL , mmc_gpio_cd_irqt, + ret =devm_request_threaded_irq (&host->class_dev, irq, +NULL , mmc_gpio_cd_irqt,IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING |IRQF_ONESHOT , ctx->cd_label, host);if (ret <0 )@@ -164,6 +195,13 @@ int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio) }EXPORT_SYMBOL (mmc_gpio_request_cd); +/** +* mmc_gpio_free_ro - free the write-protection gpio +* @host: mmc host +* +* It's provided only for cases that client drivers need to manually free +* up the write-protection gpio requested by mmc_gpio_request_ro(). +*/ void mmc_gpio_free_ro (struct mmc_host *host) {struct mmc_gpio *ctx = host->slot.handler_priv;@@ -175,10 +213,17 @@ void mmc_gpio_free_ro(struct mmc_host *host) gpio = ctx->ro_gpio; ctx->ro_gpio = -EINVAL ; -gpio_free (gpio); +devm_gpio_free (&host->class_dev, gpio); }EXPORT_SYMBOL (mmc_gpio_free_ro); +/** +* mmc_gpio_free_cd - free the card-detection gpio +* @host: mmc host +* +* It's provided only for cases that client drivers need to manually free +* up the card-detection gpio requested by mmc_gpio_request_cd(). +*/ void mmc_gpio_free_cd (struct mmc_host *host) {struct mmc_gpio *ctx = host->slot.handler_priv;@@ -188,13 +233,13 @@ void mmc_gpio_free_cd(struct mmc_host *host)return ;if (host->slot.cd_irq >=0 ) { -free_irq (host->slot.cd_irq, host); +devm_free_irq (&host->class_dev, host->slot.cd_irq, host); host->slot.cd_irq = -EINVAL ; } gpio = ctx->cd_gpio; ctx->cd_gpio = -EINVAL ; -gpio_free (gpio); +devm_gpio_free (&host->class_dev, gpio); }EXPORT_SYMBOL (mmc_gpio_free_cd);Signature · 1 line
1.7.9.5
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/
PATCH v2 2/3 mmc: remove unncessary mmc_gpio_free_cd() call from slot-gpio users
In reply to Shawn GuoSince slot-gpio uses devm_* managed functions in mmc_gpio_request_cd() now, we can remove those mmc_gpio_free_cd() call from host drivers' .probe() error path and .remove().
--- drivers/mmc/host/sdhci-pxav3.c | 5 ----- drivers/mmc/host/sh_mmcif.c | 6 ------ drivers/mmc/host/tmio_mmc_pio.c | 8 -------- 3 files changed, 19 deletions(-)
drivers/mmc/host/sdhci-pxav3.c +0 -5
@@ -316,7 +316,6 @@ static int sdhci_pxav3_probe(struct platform_device *pdev) err_add_host:clk_disable_unprepare (clk);clk_put (clk); -mmc_gpio_free_cd (host->mmc); err_cd_req: err_clk_get:sdhci_pltfm_free (pdev);
@@ -329,16 +328,12 @@ static int sdhci_pxav3_remove(struct platform_device *pdev)struct sdhci_host *host =platform_get_drvdata (pdev);struct sdhci_pltfm_host *pltfm_host =sdhci_priv (host);struct sdhci_pxa *pxa = pltfm_host->priv; -struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;sdhci_remove_host (host,1 );clk_disable_unprepare (pltfm_host->clk);clk_put (pltfm_host->clk); -if (gpio_is_valid (pdata->ext_cd_gpio)) -mmc_gpio_free_cd (host->mmc); -sdhci_pltfm_free (pdev);kfree (pxa);
drivers/mmc/host/sh_mmcif.c +0 -6
@@ -1410,8 +1410,6 @@ static int sh_mmcif_probe(struct platform_device *pdev)return ret; emmcaddh: -if (pd && pd->use_cd_gpio) -mmc_gpio_free_cd (mmc); erqcd:free_irq (irq[1 ], host); ereqirq1:
@@ -1433,7 +1431,6 @@ ealloch:static int sh_mmcif_remove (struct platform_device *pdev) {struct sh_mmcif_host *host =platform_get_drvdata (pdev); -struct sh_mmcif_plat_data *pd = pdev->dev.platform_data;int irq[2 ]; host->dying = true;
@@ -1442,9 +1439,6 @@ static int sh_mmcif_remove(struct platform_device *pdev)dev_pm_qos_hide_latency_limit (&pdev->dev); -if (pd && pd->use_cd_gpio) -mmc_gpio_free_cd (host->mmc); -mmc_remove_host (host->mmc);sh_mmcif_writel (host->addr,MMCIF_CE_INT_MASK ,MASK_ALL );
drivers/mmc/host/tmio_mmc_pio.c +0 -8
@@ -1060,16 +1060,8 @@ EXPORT_SYMBOL(tmio_mmc_host_probe);void tmio_mmc_host_remove (struct tmio_mmc_host *host) {struct platform_device *pdev = host->pdev; -struct tmio_mmc_data *pdata = host->pdata;struct mmc_host *mmc = host->mmc; -if (pdata->flags &TMIO_MMC_USE_GPIO_CD ) -/* -* This means we can miss a card-eject, but this is anyway -* possible, because of delayed processing of hotplug events. -*/ -mmc_gpio_free_cd (mmc); -if (!host->native_hotplug)pm_runtime_get_sync (&pdev->dev);
Signature · 1 line
1.7.9.5
PATCH v2 2/3 mmc: remove unncessary mmc_gpio_free_cd() call from slot-gpio users
Quoted 5 lines · Shawn Guo
Since slot-gpio uses devm_* managed functions in
mmc_gpio_request_cd()now, we can remove thosemmc_gpio_free_cd()call from host drivers' .probe() error path and .remove().Signed-off-by Shawn Guo <[email protected]>
(for mmcif and tmio)
Thanks
Guennadi
Quoted 91 lines
--- drivers/mmc/host/sdhci-pxav3.c | 5 ----- drivers/mmc/host/sh_mmcif.c | 6 ------ drivers/mmc/host/tmio_mmc_pio.c | 8 -------- 3 files changed, 19 deletions(-)drivers/mmc/host/sdhci-pxav3.c +0
-5@@ -316,7 +316,6 @@ static int sdhci_pxav3_probe(struct platform_device *pdev) err_add_host:clk_disable_unprepare (clk);clk_put (clk); -mmc_gpio_free_cd (host->mmc); err_cd_req: err_clk_get:sdhci_pltfm_free (pdev);@@ -329,16 +328,12 @@ static int sdhci_pxav3_remove(struct platform_device *pdev)struct sdhci_host *host =platform_get_drvdata (pdev);struct sdhci_pltfm_host *pltfm_host =sdhci_priv (host);struct sdhci_pxa *pxa = pltfm_host->priv; -struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;sdhci_remove_host (host,1 );clk_disable_unprepare (pltfm_host->clk);clk_put (pltfm_host->clk); -if (gpio_is_valid (pdata->ext_cd_gpio)) -mmc_gpio_free_cd (host->mmc); -sdhci_pltfm_free (pdev);kfree (pxa);drivers/mmc/host/sh_mmcif.c +0
-6@@ -1410,8 +1410,6 @@ static int sh_mmcif_probe(struct platform_device *pdev)return ret; emmcaddh: -if (pd && pd->use_cd_gpio) -mmc_gpio_free_cd (mmc); erqcd:free_irq (irq[1 ], host); ereqirq1:@@ -1433,7 +1431,6 @@ ealloch:static int sh_mmcif_remove (struct platform_device *pdev) {struct sh_mmcif_host *host =platform_get_drvdata (pdev); -struct sh_mmcif_plat_data *pd = pdev->dev.platform_data;int irq[2 ]; host->dying = true;@@ -1442,9 +1439,6 @@ static int sh_mmcif_remove(struct platform_device *pdev)dev_pm_qos_hide_latency_limit (&pdev->dev); -if (pd && pd->use_cd_gpio) -mmc_gpio_free_cd (host->mmc); -mmc_remove_host (host->mmc);sh_mmcif_writel (host->addr,MMCIF_CE_INT_MASK ,MASK_ALL );drivers/mmc/host/tmio_mmc_pio.c +0
-8@@ -1060,16 +1060,8 @@ EXPORT_SYMBOL(tmio_mmc_host_probe);void tmio_mmc_host_remove (struct tmio_mmc_host *host) {struct platform_device *pdev = host->pdev; -struct tmio_mmc_data *pdata = host->pdata;struct mmc_host *mmc = host->mmc; -if (pdata->flags &TMIO_MMC_USE_GPIO_CD ) -/* -* This means we can miss a card-eject, but this is anyway -* possible, because of delayed processing of hotplug events. -*/ -mmc_gpio_free_cd (mmc); -if (!host->native_hotplug)pm_runtime_get_sync (&pdev->dev);Signature · 1 line
1.7.9.5
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/
PATCH v2 3/3 mmc: sdhci-esdhc-imx: use slot-gpio helpers for CD and WP
In reply to Shawn GuoUse slot-gpio helpers to save some codes in the driver.
--- drivers/mmc/host/sdhci-esdhc-imx.c | 56 +++++++++++------------------------- 1 file changed, 16 insertions(+), 40 deletions(-)
drivers/mmc/host/sdhci-esdhc-imx.c +16 -40
@@ -21,6 +21,7 @@#include <linux/mmc/host.h> #include <linux/mmc/mmc.h> #include <linux/mmc/sdio.h> +#include <linux/mmc/slot-gpio.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_gpio.h>
@@ -147,17 +148,16 @@ static u32 esdhc_readl_le(struct sdhci_host *host, int reg)struct pltfm_imx_data *imx_data = pltfm_host->priv;struct esdhc_platform_data *boarddata = &imx_data->boarddata; -/* fake CARD_PRESENT flag */ u32 val =readl (host->ioaddr + reg); -if (unlikely ((reg ==SDHCI_PRESENT_STATE ) - &&gpio_is_valid (boarddata->cd_gpio))) { -if (gpio_get_value (boarddata->cd_gpio)) -/* no card, if a valid gpio says so... */ +if (unlikely (reg ==SDHCI_PRESENT_STATE )) { +/* +* After SDHCI core gets improved to never query +* SDHCI_CARD_PRESENT state in GPIO case, we can +* remove this check. +*/ +if (boarddata->cd_type ==ESDHC_CD_GPIO ) val &= ~SDHCI_CARD_PRESENT ; -else -/* ... in all other cases assume card is present */ - val |=SDHCI_CARD_PRESENT ; }if (unlikely (reg ==SDHCI_CAPABILITIES )) {
@@ -362,8 +362,7 @@ static unsigned int esdhc_pltfm_get_ro(struct sdhci_host *host)switch (boarddata->wp_type) {case ESDHC_WP_GPIO : -if (gpio_is_valid (boarddata->wp_gpio)) -return gpio_get_value (boarddata->wp_gpio); +return mmc_gpio_get_ro (host->mmc);case ESDHC_WP_CONTROLLER :return !(readl (host->ioaddr +SDHCI_PRESENT_STATE ) &SDHCI_WRITE_PROTECT );
@@ -394,14 +393,6 @@ static struct sdhci_pltfm_data sdhci_esdhc_imx_pdata = { .ops = &sdhci_esdhc_ops, }; -static irqreturn_t cd_irq (int irq,void *data) -{ -struct sdhci_host *sdhost = (struct sdhci_host *)data; - -tasklet_schedule (&sdhost->card_tasklet); -return IRQ_HANDLED ; -}; -#ifdef CONFIG_OF static int sdhci_esdhc_imx_probe_dt(struct platform_device *pdev,
@@ -527,37 +518,22 @@ static int sdhci_esdhc_imx_probe(struct platform_device *pdev)/* write_protect */ if (boarddata->wp_type ==ESDHC_WP_GPIO ) { - err =devm_gpio_request_one (&pdev->dev, boarddata->wp_gpio, -GPIOF_IN ,"ESDHC_WP" ); + err =mmc_gpio_request_ro (host->mmc, boarddata->wp_gpio);if (err) { -dev_warn (mmc_dev (host->mmc), -"no write-protect pin available!\n" ); - boarddata->wp_gpio = -EINVAL ; +dev_err (mmc_dev (host->mmc), +"failed to request write-protect gpio!\n" ); + goto disable_clk; } - }else { - boarddata->wp_gpio = -EINVAL ; + host->mmc->caps2 |=MMC_CAP2_RO_ACTIVE_HIGH ; }/* card_detect */ -if (boarddata->cd_type !=ESDHC_CD_GPIO ) - boarddata->cd_gpio = -EINVAL ; -switch (boarddata->cd_type) {case ESDHC_CD_GPIO : - err =devm_gpio_request_one (&pdev->dev, boarddata->cd_gpio, -GPIOF_IN ,"ESDHC_CD" ); + err =mmc_gpio_request_cd (host->mmc, boarddata->cd_gpio);if (err) {dev_err (mmc_dev (host->mmc), -"no card-detect pin available!\n" ); - goto disable_clk; - } - - err =devm_request_irq (&pdev->dev, -gpio_to_irq (boarddata->cd_gpio), cd_irq, -IRQF_TRIGGER_FALLING |IRQF_TRIGGER_RISING , -mmc_hostname (host->mmc), host); -if (err) { -dev_err (mmc_dev (host->mmc),"request irq error\n" ); +"failed to request card-detect gpio!\n" ); goto disable_clk; }/* fall through */
Signature · 1 line
1.7.9.5
v2 cover Use devm_* managed functions to ease slot-gpio users
In reply to Shawn GuoHi Shawn,
Quoted 11 lines · Shawn Guo
Changes since v1:
* Add kernel doc for mmc_gpio_request/free_ro/cd() to document the use cases.
* Add a patch to remove unncessarymmc_gpio_free_cd()call from existing slot-gpio users.Shawn Guo (3):
mmc: slot-gpio: use devm_* managed functions to ease users
mmc: remove unncessarymmc_gpio_free_cd()call from slot-gpio users
mmc: sdhci-esdhc-imx: use slot-gpio helpers for CD and WP
Quoted 6 lines
drivers/mmc/core/slot-gpio.c | 57 ++++++++++++++++++++++++++++++++---- drivers/mmc/host/sdhci-esdhc-imx.c | 56 ++++++++++------------------------- drivers/mmc/host/sdhci-pxav3.c | 5 ---- drivers/mmc/host/sh_mmcif.c | 6 ---- drivers/mmc/host/tmio_mmc_pio.c | 8 ----- 5 files changed, 67 insertions(+), 65 deletions(-)
Thanks for doing this, all merged to mmc-next for 3.9.
- Chris.
Signature · 2 lines
Chris Ball <[email protected]> <http://printf.net/> One Laptop Per Child